mirror of
https://github.com/ManfredAabye/opensimMULTITOOLS-II.git
synced 2026-07-30 02:32:22 +00:00
010420262055 first
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# OSMTool Web PHP (DE/EN/FR/ES)
|
||||
|
||||
This folder contains a minimal and secure PHP web controller for `osmtool_main.sh`.
|
||||
|
||||
## Features
|
||||
|
||||
- Login-protected web UI
|
||||
- Language switch: `de`, `en`, `fr`, `es`
|
||||
- CSRF protection
|
||||
- Whitelisted actions only
|
||||
- Runner script wrapper for safer command execution
|
||||
|
||||
## File Layout
|
||||
|
||||
- `public/index.php` - web UI and request handling
|
||||
- `src/bootstrap.php` - session, auth helpers, CSRF
|
||||
- `src/i18n.php` - translations
|
||||
- `src/runner.php` - command whitelist and execution
|
||||
- `src/config.sample.php` - configuration template
|
||||
- `bin/osmtool_web_runner.sh` - controlled bridge to `osmtool_main.sh`
|
||||
|
||||
## Setup
|
||||
|
||||
1. Typical Linux deploy path:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/html/osmtool-web
|
||||
sudo cp -a webphp/. /var/www/html/osmtool-web/
|
||||
sudo chown -R www-data:www-data /var/www/html/osmtool-web
|
||||
```
|
||||
|
||||
1. Edit config values:
|
||||
|
||||
```bash
|
||||
nano /var/www/html/osmtool-web/src/config.php
|
||||
```
|
||||
|
||||
1. Set execute bit for runner and main script:
|
||||
|
||||
```bash
|
||||
chmod +x /var/www/html/osmtool-web/bin/osmtool_web_runner.sh
|
||||
chmod +x /opt/opensimMULTITOOLS-II/osmtool_main.sh
|
||||
```
|
||||
|
||||
1. Configure web server document root to:
|
||||
|
||||
```text
|
||||
/var/www/html/osmtool-web/public
|
||||
```
|
||||
|
||||
1. Open in browser and log in with `web_password` from config.
|
||||
|
||||
## Sudoers Example (recommended)
|
||||
|
||||
Use `visudo` and allow only the web runner:
|
||||
|
||||
```text
|
||||
www-data ALL=(manni) NOPASSWD: /var/www/html/osmtool-web/bin/osmtool_web_runner.sh
|
||||
```
|
||||
|
||||
Then keep `use_sudo=true` and `sudo_user=manni` in config.
|
||||
|
||||
## Supported Actions
|
||||
|
||||
- `start`
|
||||
- `stop`
|
||||
- `restart`
|
||||
- `smoke`
|
||||
- `report`
|
||||
- `health`
|
||||
- `cron-list`
|
||||
|
||||
## Notes
|
||||
|
||||
- Do not expose this UI publicly without HTTPS and IP restrictions.
|
||||
- Keep the password strong and unique.
|
||||
- Extend actions only in `src/runner.php` and `bin/osmtool_web_runner.sh` together.
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
MAIN_SCRIPT="$PROJECT_ROOT/osmtool_main.sh"
|
||||
|
||||
if [[ ! -x "$MAIN_SCRIPT" ]]; then
|
||||
echo "ERROR: osmtool_main.sh not executable: $MAIN_SCRIPT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACTION="${1:-}"
|
||||
PROFILE="${2:-grid-sim}"
|
||||
LANG="${3:-de}"
|
||||
WORKDIR="${4:-/opt}"
|
||||
|
||||
case "$PROFILE" in
|
||||
grid-sim|robust|standalone) ;;
|
||||
*) echo "ERROR: invalid profile" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
case "$LANG" in
|
||||
de|en|fr|es) ;;
|
||||
*) echo "ERROR: invalid language" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
run_main() {
|
||||
bash "$MAIN_SCRIPT" --mode cli --profile "$PROFILE" --lang "$LANG" --workdir "$WORKDIR" "$@"
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
start)
|
||||
run_main --module startstop --action start
|
||||
;;
|
||||
stop)
|
||||
run_main --module startstop --action stop
|
||||
;;
|
||||
restart)
|
||||
run_main --module startstop --action restart
|
||||
;;
|
||||
smoke)
|
||||
run_main --module smoke --action run
|
||||
;;
|
||||
report)
|
||||
run_main --module report --action generate
|
||||
;;
|
||||
health)
|
||||
run_main --module health --action run
|
||||
;;
|
||||
cron-list)
|
||||
run_main --module cron --action list
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unsupported action" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../src/bootstrap.php';
|
||||
require_once __DIR__ . '/../src/runner.php';
|
||||
|
||||
$lang = detect_lang(supported_languages());
|
||||
$_SESSION['lang'] = $lang;
|
||||
|
||||
$message = '';
|
||||
$messageType = 'ok';
|
||||
$resultOutput = '';
|
||||
$resultCode = null;
|
||||
|
||||
if (isset($_GET['logout'])) {
|
||||
$_SESSION = [];
|
||||
session_destroy();
|
||||
header('Location: index.php?lang=' . urlencode($lang));
|
||||
exit;
|
||||
}
|
||||
|
||||
if (($_POST['form'] ?? '') === 'login') {
|
||||
$postedLang = detect_lang(supported_languages());
|
||||
$_SESSION['lang'] = $postedLang;
|
||||
$lang = $postedLang;
|
||||
|
||||
if (!verify_csrf($_POST['csrf'] ?? null)) {
|
||||
$message = t($lang, 'csrf_error');
|
||||
$messageType = 'error';
|
||||
} else {
|
||||
$password = (string)($_POST['password'] ?? '');
|
||||
$expected = (string)($config['web_password'] ?? '');
|
||||
if ($expected !== '' && hash_equals($expected, $password)) {
|
||||
$_SESSION['auth'] = true;
|
||||
header('Location: index.php?lang=' . urlencode($lang));
|
||||
exit;
|
||||
}
|
||||
$message = t($lang, 'invalid_login');
|
||||
$messageType = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
if (is_logged_in() && (($_POST['form'] ?? '') === 'run')) {
|
||||
if (!verify_csrf($_POST['csrf'] ?? null)) {
|
||||
$message = t($lang, 'csrf_error');
|
||||
$messageType = 'error';
|
||||
} else {
|
||||
$action = (string)($_POST['action'] ?? '');
|
||||
$profile = (string)($_POST['profile'] ?? 'grid-sim');
|
||||
$workdir = (string)($_POST['workdir'] ?? (string)($config['default_workdir'] ?? '/opt'));
|
||||
|
||||
$result = run_whitelisted_action($config, $action, $profile, $lang, $workdir);
|
||||
$resultOutput = (string)($result['output'] ?? '');
|
||||
$resultCode = (int)($result['exitCode'] ?? 1);
|
||||
|
||||
if (($result['ok'] ?? false) === true) {
|
||||
$message = t($lang, 'status_ok') . ' (exit=' . $resultCode . ')';
|
||||
$messageType = 'ok';
|
||||
} else {
|
||||
$message = t($lang, 'status_error') . ' (exit=' . $resultCode . ')';
|
||||
$messageType = 'error';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$actions = [
|
||||
'start' => t($lang, 'action_start'),
|
||||
'stop' => t($lang, 'action_stop'),
|
||||
'restart' => t($lang, 'action_restart'),
|
||||
'smoke' => t($lang, 'action_smoke'),
|
||||
'report' => t($lang, 'action_report'),
|
||||
'health' => t($lang, 'action_health'),
|
||||
'cron-list' => t($lang, 'action_cron_list'),
|
||||
];
|
||||
|
||||
$profiles = [
|
||||
'grid-sim' => 'grid-sim',
|
||||
'robust' => 'robust',
|
||||
'standalone' => 'standalone',
|
||||
];
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="<?= h($lang) ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= h(t($lang, 'title')) ?></title>
|
||||
<style>
|
||||
:root {
|
||||
--bg1: #0f172a;
|
||||
--bg2: #1e293b;
|
||||
--card: #111827;
|
||||
--line: #334155;
|
||||
--text: #e5e7eb;
|
||||
--muted: #94a3b8;
|
||||
--ok: #16a34a;
|
||||
--err: #dc2626;
|
||||
--btn: #0284c7;
|
||||
--btnHover: #0369a1;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", Tahoma, sans-serif;
|
||||
background: radial-gradient(1000px 600px at 20% 0%, #1d4ed8 0%, transparent 60%),
|
||||
radial-gradient(900px 500px at 100% 100%, #0ea5e9 0%, transparent 50%),
|
||||
linear-gradient(160deg, var(--bg1), var(--bg2));
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
.wrap { max-width: 980px; margin: 0 auto; }
|
||||
.card {
|
||||
background: color-mix(in oklab, var(--card), black 8%);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 18px;
|
||||
box-shadow: 0 12px 40px rgba(0,0,0,.25);
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
h1 { margin: 0 0 8px; font-size: 1.7rem; }
|
||||
p { margin: 0 0 12px; color: var(--muted); }
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
label { display: block; margin-bottom: 6px; font-weight: 600; }
|
||||
input, select, button {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--line);
|
||||
background: #0b1220;
|
||||
color: var(--text);
|
||||
padding: 10px 12px;
|
||||
}
|
||||
button {
|
||||
background: var(--btn);
|
||||
border: none;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover { background: var(--btnHover); }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.full { grid-column: 1 / -1; }
|
||||
.msg {
|
||||
margin: 10px 0;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.ok { background: rgba(22,163,74,.2); border: 1px solid rgba(22,163,74,.5); }
|
||||
.error { background: rgba(220,38,38,.2); border: 1px solid rgba(220,38,38,.5); }
|
||||
pre {
|
||||
background: #020617;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
max-height: 420px;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.lang-links a {
|
||||
color: #bae6fd;
|
||||
margin-right: 8px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.lang-links a:hover { text-decoration: underline; }
|
||||
@media (max-width: 800px) {
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="card">
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<h1><?= h(t($lang, 'title')) ?></h1>
|
||||
<p><?= h(t($lang, 'subtitle')) ?></p>
|
||||
</div>
|
||||
<div class="lang-links">
|
||||
<a href="?lang=de">DE</a>
|
||||
<a href="?lang=en">EN</a>
|
||||
<a href="?lang=fr">FR</a>
|
||||
<a href="?lang=es">ES</a>
|
||||
<?php if (is_logged_in()): ?>
|
||||
| <a href="?logout=1&lang=<?= h($lang) ?>"><?= h(t($lang, 'logout')) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($message !== ''): ?>
|
||||
<div class="msg <?= h($messageType) ?>"><?= h($message) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!is_logged_in()): ?>
|
||||
<form method="post">
|
||||
<input type="hidden" name="form" value="login">
|
||||
<input type="hidden" name="csrf" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="lang" value="<?= h($lang) ?>">
|
||||
<div class="grid">
|
||||
<div class="full">
|
||||
<label><?= h(t($lang, 'password')) ?></label>
|
||||
<input type="password" name="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
<div class="full">
|
||||
<button type="submit"><?= h(t($lang, 'login')) ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<p><?= h(t($lang, 'hint')) ?></p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="form" value="run">
|
||||
<input type="hidden" name="csrf" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="lang" value="<?= h($lang) ?>">
|
||||
|
||||
<div class="grid">
|
||||
<div>
|
||||
<label><?= h(t($lang, 'profile')) ?></label>
|
||||
<select name="profile" required>
|
||||
<?php foreach ($profiles as $value => $label): ?>
|
||||
<option value="<?= h($value) ?>"><?= h($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label><?= h(t($lang, 'action')) ?></label>
|
||||
<select name="action" required>
|
||||
<?php foreach ($actions as $value => $label): ?>
|
||||
<option value="<?= h($value) ?>"><?= h($label) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="full">
|
||||
<label><?= h(t($lang, 'workdir')) ?></label>
|
||||
<input name="workdir" value="<?= h((string)($config['default_workdir'] ?? '/opt')) ?>">
|
||||
</div>
|
||||
<div class="full">
|
||||
<button type="submit"><?= h(t($lang, 'execute')) ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($resultCode !== null): ?>
|
||||
<div class="card" style="margin-top:14px;">
|
||||
<label><?= h(t($lang, 'output')) ?></label>
|
||||
<pre><?= h($resultOutput) ?></pre>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$configFile = __DIR__ . '/config.php';
|
||||
if (!is_file($configFile)) {
|
||||
$configFile = __DIR__ . '/config.sample.php';
|
||||
}
|
||||
|
||||
$config = require $configFile;
|
||||
|
||||
if (!isset($config['session_key']) || !is_string($config['session_key'])) {
|
||||
throw new RuntimeException('Missing session_key in config.');
|
||||
}
|
||||
|
||||
session_name('osmtool_web');
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
]);
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['session_seed'])) {
|
||||
$_SESSION['session_seed'] = hash('sha256', $config['session_key'] . random_int(1000, 999999));
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/i18n.php';
|
||||
|
||||
function is_logged_in(): bool
|
||||
{
|
||||
return isset($_SESSION['auth']) && $_SESSION['auth'] === true;
|
||||
}
|
||||
|
||||
function require_login(): void
|
||||
{
|
||||
if (!is_logged_in()) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
function csrf_token(): string
|
||||
{
|
||||
if (!isset($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
function verify_csrf(?string $token): bool
|
||||
{
|
||||
if (!isset($_SESSION['csrf_token']) || !is_string($token)) {
|
||||
return false;
|
||||
}
|
||||
return hash_equals($_SESSION['csrf_token'], $token);
|
||||
}
|
||||
|
||||
function h(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'session_key' => 'change-me-super-random-session-key',
|
||||
'web_password' => 'ChangeMeNow-2026',
|
||||
// Default assumes deployment under /var/www/html/osmtool-web
|
||||
'runner_path' => realpath(__DIR__ . '/../bin/osmtool_web_runner.sh') ?: (__DIR__ . '/../bin/osmtool_web_runner.sh'),
|
||||
'command_timeout_seconds' => 120,
|
||||
'default_workdir' => '/opt',
|
||||
'use_sudo' => true,
|
||||
'sudo_user' => 'manni',
|
||||
];
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// Set this to a strong random value in production.
|
||||
'session_key' => 'change-me-super-random-session-key',
|
||||
|
||||
// Default login password. Replace immediately in production.
|
||||
'web_password' => 'ChangeMeNow-2026',
|
||||
|
||||
// Default assumes deployment under /var/www/html/osmtool-web.
|
||||
// You can keep this dynamic path or set an absolute path manually.
|
||||
'runner_path' => realpath(__DIR__ . '/../bin/osmtool_web_runner.sh') ?: (__DIR__ . '/../bin/osmtool_web_runner.sh'),
|
||||
|
||||
// Web timeout in seconds.
|
||||
'command_timeout_seconds' => 120,
|
||||
|
||||
// Default workdir passed to osmtool_main.sh.
|
||||
'default_workdir' => '/opt',
|
||||
|
||||
// Allow running through sudo if needed.
|
||||
'use_sudo' => true,
|
||||
|
||||
// If use_sudo=true, command becomes: sudo -n <sudo_user> <runner>
|
||||
// Example: www-data (Linux) or your service account.
|
||||
'sudo_user' => 'manni',
|
||||
];
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function supported_languages(): array
|
||||
{
|
||||
return ['de', 'en', 'fr', 'es'];
|
||||
}
|
||||
|
||||
function translations(): array
|
||||
{
|
||||
return [
|
||||
'de' => [
|
||||
'title' => 'OSMTool Web Steuerung',
|
||||
'subtitle' => 'Sichere Web-Oberflaeche fuer OpenSim Aktionen',
|
||||
'login_title' => 'Anmeldung',
|
||||
'password' => 'Passwort',
|
||||
'login' => 'Einloggen',
|
||||
'logout' => 'Ausloggen',
|
||||
'language' => 'Sprache',
|
||||
'profile' => 'Profil',
|
||||
'action' => 'Aktion',
|
||||
'execute' => 'Ausfuehren',
|
||||
'workdir' => 'Workdir',
|
||||
'output' => 'Ausgabe',
|
||||
'status_ok' => 'Erfolgreich',
|
||||
'status_error' => 'Fehler',
|
||||
'invalid_login' => 'Anmeldung fehlgeschlagen.',
|
||||
'csrf_error' => 'Ungueltiges Formular-Token.',
|
||||
'not_allowed' => 'Aktion nicht erlaubt.',
|
||||
'run_failed' => 'Ausfuehrung fehlgeschlagen.',
|
||||
'hint' => 'Es werden nur freigegebene Aktionen ausgefuehrt.',
|
||||
'action_start' => 'Start',
|
||||
'action_stop' => 'Stop',
|
||||
'action_restart' => 'Neustart',
|
||||
'action_smoke' => 'Smoke-Test',
|
||||
'action_report' => 'Report erzeugen',
|
||||
'action_health' => 'Healthcheck',
|
||||
'action_cron_list' => 'Cronjobs anzeigen',
|
||||
],
|
||||
'en' => [
|
||||
'title' => 'OSMTool Web Control',
|
||||
'subtitle' => 'Secure web interface for OpenSim actions',
|
||||
'login_title' => 'Login',
|
||||
'password' => 'Password',
|
||||
'login' => 'Sign in',
|
||||
'logout' => 'Sign out',
|
||||
'language' => 'Language',
|
||||
'profile' => 'Profile',
|
||||
'action' => 'Action',
|
||||
'execute' => 'Execute',
|
||||
'workdir' => 'Workdir',
|
||||
'output' => 'Output',
|
||||
'status_ok' => 'Success',
|
||||
'status_error' => 'Error',
|
||||
'invalid_login' => 'Login failed.',
|
||||
'csrf_error' => 'Invalid form token.',
|
||||
'not_allowed' => 'Action not allowed.',
|
||||
'run_failed' => 'Execution failed.',
|
||||
'hint' => 'Only whitelisted actions are executed.',
|
||||
'action_start' => 'Start',
|
||||
'action_stop' => 'Stop',
|
||||
'action_restart' => 'Restart',
|
||||
'action_smoke' => 'Smoke test',
|
||||
'action_report' => 'Generate report',
|
||||
'action_health' => 'Health check',
|
||||
'action_cron_list' => 'List cron jobs',
|
||||
],
|
||||
'fr' => [
|
||||
'title' => 'Controle Web OSMTool',
|
||||
'subtitle' => 'Interface web securisee pour les actions OpenSim',
|
||||
'login_title' => 'Connexion',
|
||||
'password' => 'Mot de passe',
|
||||
'login' => 'Se connecter',
|
||||
'logout' => 'Se deconnecter',
|
||||
'language' => 'Langue',
|
||||
'profile' => 'Profil',
|
||||
'action' => 'Action',
|
||||
'execute' => 'Executer',
|
||||
'workdir' => 'Repertoire de travail',
|
||||
'output' => 'Sortie',
|
||||
'status_ok' => 'Succes',
|
||||
'status_error' => 'Erreur',
|
||||
'invalid_login' => 'Echec de connexion.',
|
||||
'csrf_error' => 'Jeton de formulaire invalide.',
|
||||
'not_allowed' => 'Action non autorisee.',
|
||||
'run_failed' => 'Execution echouee.',
|
||||
'hint' => 'Seules les actions en liste blanche sont executees.',
|
||||
'action_start' => 'Demarrer',
|
||||
'action_stop' => 'Arreter',
|
||||
'action_restart' => 'Redemarrer',
|
||||
'action_smoke' => 'Test smoke',
|
||||
'action_report' => 'Generer un rapport',
|
||||
'action_health' => 'Controle de sante',
|
||||
'action_cron_list' => 'Lister les cron jobs',
|
||||
],
|
||||
'es' => [
|
||||
'title' => 'Control Web OSMTool',
|
||||
'subtitle' => 'Interfaz web segura para acciones de OpenSim',
|
||||
'login_title' => 'Inicio de sesion',
|
||||
'password' => 'Contrasena',
|
||||
'login' => 'Entrar',
|
||||
'logout' => 'Salir',
|
||||
'language' => 'Idioma',
|
||||
'profile' => 'Perfil',
|
||||
'action' => 'Accion',
|
||||
'execute' => 'Ejecutar',
|
||||
'workdir' => 'Directorio de trabajo',
|
||||
'output' => 'Salida',
|
||||
'status_ok' => 'Correcto',
|
||||
'status_error' => 'Error',
|
||||
'invalid_login' => 'Error de inicio de sesion.',
|
||||
'csrf_error' => 'Token de formulario invalido.',
|
||||
'not_allowed' => 'Accion no permitida.',
|
||||
'run_failed' => 'La ejecucion fallo.',
|
||||
'hint' => 'Solo se ejecutan acciones permitidas.',
|
||||
'action_start' => 'Iniciar',
|
||||
'action_stop' => 'Detener',
|
||||
'action_restart' => 'Reiniciar',
|
||||
'action_smoke' => 'Prueba smoke',
|
||||
'action_report' => 'Generar informe',
|
||||
'action_health' => 'Chequeo de salud',
|
||||
'action_cron_list' => 'Listar cron jobs',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function detect_lang(array $supported): string
|
||||
{
|
||||
$lang = $_GET['lang'] ?? $_POST['lang'] ?? $_SESSION['lang'] ?? 'de';
|
||||
if (!is_string($lang) || !in_array($lang, $supported, true)) {
|
||||
return 'de';
|
||||
}
|
||||
return $lang;
|
||||
}
|
||||
|
||||
function t(string $lang, string $key): string
|
||||
{
|
||||
$all = translations();
|
||||
return $all[$lang][$key] ?? $all['en'][$key] ?? $key;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function allowed_profiles(): array
|
||||
{
|
||||
return ['grid-sim', 'robust', 'standalone'];
|
||||
}
|
||||
|
||||
function allowed_actions(): array
|
||||
{
|
||||
return ['start', 'stop', 'restart', 'smoke', 'report', 'health', 'cron-list'];
|
||||
}
|
||||
|
||||
function build_command(array $config, string $action, string $profile, string $lang, string $workdir): array
|
||||
{
|
||||
$runnerPath = (string)($config['runner_path'] ?? '');
|
||||
if ($runnerPath === '') {
|
||||
throw new RuntimeException('runner_path missing in config.');
|
||||
}
|
||||
|
||||
$base = [];
|
||||
$useSudo = (bool)($config['use_sudo'] ?? false);
|
||||
if ($useSudo) {
|
||||
$sudoUser = (string)($config['sudo_user'] ?? '');
|
||||
if ($sudoUser === '') {
|
||||
throw new RuntimeException('sudo_user missing in config.');
|
||||
}
|
||||
$base = ['sudo', '-n', '-u', $sudoUser];
|
||||
}
|
||||
|
||||
return array_merge($base, [$runnerPath, $action, $profile, $lang, $workdir]);
|
||||
}
|
||||
|
||||
function run_whitelisted_action(array $config, string $action, string $profile, string $lang, string $workdir): array
|
||||
{
|
||||
if (!in_array($action, allowed_actions(), true)) {
|
||||
return ['ok' => false, 'exitCode' => 2, 'output' => "ERROR: action not allowed\n"];
|
||||
}
|
||||
|
||||
if (!in_array($profile, allowed_profiles(), true)) {
|
||||
return ['ok' => false, 'exitCode' => 2, 'output' => "ERROR: invalid profile\n"];
|
||||
}
|
||||
|
||||
if (!in_array($lang, supported_languages(), true)) {
|
||||
return ['ok' => false, 'exitCode' => 2, 'output' => "ERROR: invalid language\n"];
|
||||
}
|
||||
|
||||
$workdirClean = trim($workdir);
|
||||
if ($workdirClean === '') {
|
||||
$workdirClean = (string)($config['default_workdir'] ?? '/opt');
|
||||
}
|
||||
|
||||
$cmd = build_command($config, $action, $profile, $lang, $workdirClean);
|
||||
$descriptorSpec = [
|
||||
0 => ['pipe', 'r'],
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
];
|
||||
|
||||
$process = proc_open($cmd, $descriptorSpec, $pipes);
|
||||
if (!is_resource($process)) {
|
||||
return ['ok' => false, 'exitCode' => 1, 'output' => "ERROR: unable to start process\n"];
|
||||
}
|
||||
|
||||
fclose($pipes[0]);
|
||||
|
||||
$stdout = stream_get_contents($pipes[1]);
|
||||
$stderr = stream_get_contents($pipes[2]);
|
||||
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$exitCode = proc_close($process);
|
||||
$output = (string)$stdout . (string)$stderr;
|
||||
|
||||
return ['ok' => $exitCode === 0, 'exitCode' => $exitCode, 'output' => $output];
|
||||
}
|
||||
Reference in New Issue
Block a user