Initial
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Wartungs-CLI.
|
||||
*
|
||||
* php bin/ekdos migrate Schema anlegen oder fortschreiben
|
||||
* php bin/ekdos user:create Benutzer anlegen (Passwort wird abgefragt)
|
||||
* php bin/ekdos user:list Benutzer auflisten
|
||||
* php bin/ekdos user:password <name> Passwort zuruecksetzen
|
||||
* php bin/ekdos cache:flush n8n-Cache leeren
|
||||
* php bin/ekdos check Postgres, Redis und n8n pruefen
|
||||
*
|
||||
* Der erste Administrator wird mit user:create angelegt. Es gibt bewusst keinen
|
||||
* Bootstrap-Benutzer aus der Umgebung: ein Passwort in einer .env ist ein
|
||||
* Passwort, das niemand mehr aendert.
|
||||
*/
|
||||
|
||||
use Ekdos\Auth\AuthService;
|
||||
use Ekdos\Bootstrap\Container;
|
||||
use Ekdos\N8n\Cache;
|
||||
use Ekdos\N8n\Client;
|
||||
use Ekdos\N8n\Endpoints;
|
||||
use Ekdos\Support\Config;
|
||||
use Ekdos\Users\Role;
|
||||
use Ekdos\Users\UserRepository;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$projectRoot = dirname(__DIR__);
|
||||
$command = $argv[1] ?? 'help';
|
||||
|
||||
function out(string $line = ''): void
|
||||
{
|
||||
fwrite(STDOUT, $line . PHP_EOL);
|
||||
}
|
||||
|
||||
function fail(string $line): never
|
||||
{
|
||||
fwrite(STDERR, $line . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/** Liest eine Eingabe ohne Bildschirmausgabe. */
|
||||
function readSecret(string $prompt): string
|
||||
{
|
||||
fwrite(STDERR, $prompt);
|
||||
|
||||
if (DIRECTORY_SEPARATOR === '/' && function_exists('shell_exec')) {
|
||||
shell_exec('stty -echo 2>/dev/null');
|
||||
}
|
||||
|
||||
$value = trim((string) fgets(STDIN));
|
||||
|
||||
if (DIRECTORY_SEPARATOR === '/' && function_exists('shell_exec')) {
|
||||
shell_exec('stty echo 2>/dev/null');
|
||||
}
|
||||
|
||||
fwrite(STDERR, PHP_EOL);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
function ask(string $prompt, string $fallback = ''): string
|
||||
{
|
||||
fwrite(STDERR, $prompt);
|
||||
$value = trim((string) fgets(STDIN));
|
||||
|
||||
return $value !== '' ? $value : $fallback;
|
||||
}
|
||||
|
||||
$container = Container::build($projectRoot);
|
||||
|
||||
switch ($command) {
|
||||
case 'migrate':
|
||||
/** @var PDO $db */
|
||||
$db = $container->get(PDO::class);
|
||||
$db->exec('create table if not exists schema_migrations (version text primary key, applied_at timestamptz not null default now())');
|
||||
|
||||
$applied = $db->query('select version from schema_migrations')->fetchAll(PDO::FETCH_COLUMN);
|
||||
$files = glob($projectRoot . '/migrations/*.sql') ?: [];
|
||||
sort($files);
|
||||
$ran = 0;
|
||||
|
||||
foreach ($files as $file) {
|
||||
$version = basename($file, '.sql');
|
||||
|
||||
if (in_array($version, $applied, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sql = file_get_contents($file);
|
||||
|
||||
if ($sql === false) {
|
||||
fail('Migration nicht lesbar: ' . $file);
|
||||
}
|
||||
|
||||
$db->beginTransaction();
|
||||
|
||||
try {
|
||||
$db->exec($sql);
|
||||
$statement = $db->prepare('insert into schema_migrations (version) values (:version)');
|
||||
$statement->execute(['version' => $version]);
|
||||
$db->commit();
|
||||
} catch (Throwable $exception) {
|
||||
$db->rollBack();
|
||||
fail('Migration ' . $version . ' fehlgeschlagen: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
out(' angewendet: ' . $version);
|
||||
$ran++;
|
||||
}
|
||||
|
||||
out($ran === 0 ? 'Das Schema ist aktuell.' : $ran . ' Migration(en) angewendet.');
|
||||
break;
|
||||
|
||||
case 'user:create':
|
||||
/** @var UserRepository $users */
|
||||
$users = $container->get(UserRepository::class);
|
||||
|
||||
$username = ask('Benutzername: ');
|
||||
$displayName = ask('Anzeigename: ', $username);
|
||||
$roleInput = ask('Rolle [admin|inhaber|buero] (admin): ', 'admin');
|
||||
$role = Role::tryFrom($roleInput) ?? fail('Unbekannte Rolle: ' . $roleInput);
|
||||
|
||||
if ($username === '') {
|
||||
fail('Der Benutzername darf nicht leer sein.');
|
||||
}
|
||||
|
||||
if ($users->usernameTaken($username)) {
|
||||
fail('Dieser Benutzername ist bereits vergeben.');
|
||||
}
|
||||
|
||||
$password = readSecret('Passwort: ');
|
||||
|
||||
if (($problem = AuthService::rejectWeakPassword($password)) !== null) {
|
||||
fail($problem);
|
||||
}
|
||||
|
||||
if ($password !== readSecret('Passwort wiederholen: ')) {
|
||||
fail('Die Passwörter stimmen nicht überein.');
|
||||
}
|
||||
|
||||
$user = $users->create($username, $displayName, $role, AuthService::hash($password));
|
||||
$users->audit(null, 'CLI', 'user.create', $user->id, $user->username, ['role' => $role->value]);
|
||||
out('Angelegt: ' . $user->username . ' (' . $user->role->label() . ')');
|
||||
break;
|
||||
|
||||
case 'user:list':
|
||||
/** @var UserRepository $users */
|
||||
$users = $container->get(UserRepository::class);
|
||||
$rows = $users->all();
|
||||
|
||||
if ($rows === []) {
|
||||
out('Es ist noch kein Benutzer angelegt. Anlegen mit: php bin/ekdos user:create');
|
||||
break;
|
||||
}
|
||||
|
||||
out(sprintf('%-22s %-24s %-14s %-8s %s', 'BENUTZER', 'NAME', 'ROLLE', 'AKTIV', 'LETZTE ANMELDUNG'));
|
||||
|
||||
foreach ($rows as $user) {
|
||||
out(sprintf(
|
||||
'%-22s %-24s %-14s %-8s %s',
|
||||
$user->username,
|
||||
$user->displayName,
|
||||
$user->role->value,
|
||||
$user->isActive ? 'ja' : 'nein',
|
||||
$user->lastLoginAt ?? '–',
|
||||
));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'user:password':
|
||||
/** @var UserRepository $users */
|
||||
$users = $container->get(UserRepository::class);
|
||||
$username = $argv[2] ?? fail('Aufruf: php bin/ekdos user:password <benutzername>');
|
||||
$user = $users->findByUsernameWithHash($username) ?? fail('Unbekannter Benutzer: ' . $username);
|
||||
|
||||
$password = readSecret('Neues Passwort für ' . $user->username . ': ');
|
||||
|
||||
if (($problem = AuthService::rejectWeakPassword($password)) !== null) {
|
||||
fail($problem);
|
||||
}
|
||||
|
||||
$users->update($user->id, ['password_hash' => AuthService::hash($password)]);
|
||||
$container->get(\Ekdos\Auth\SessionStore::class)->destroyAllFor($user->id);
|
||||
$users->audit(null, 'CLI', 'user.password', $user->id, $user->username);
|
||||
out('Passwort geändert. Offene Sitzungen dieses Benutzers wurden beendet.');
|
||||
break;
|
||||
|
||||
case 'cache:flush':
|
||||
/** @var Cache $cache */
|
||||
$cache = $container->get(Cache::class);
|
||||
out($cache->flushAll() . ' zwischengespeicherte n8n-Antworten verworfen.');
|
||||
break;
|
||||
|
||||
case 'check':
|
||||
/** @var Config $config */
|
||||
$config = $container->get(Config::class);
|
||||
$problems = 0;
|
||||
|
||||
try {
|
||||
$container->get(PDO::class)->query('select 1');
|
||||
out(' Postgres erreichbar');
|
||||
} catch (Throwable $exception) {
|
||||
out(' Postgres FEHLER: ' . $exception->getMessage());
|
||||
$problems++;
|
||||
}
|
||||
|
||||
try {
|
||||
$container->get(Redis::class)->ping();
|
||||
out(' Redis erreichbar');
|
||||
} catch (Throwable $exception) {
|
||||
out(' Redis FEHLER: ' . $exception->getMessage());
|
||||
$problems++;
|
||||
}
|
||||
|
||||
/** @var Endpoints $endpoints */
|
||||
$endpoints = $container->get(Endpoints::class);
|
||||
/** @var Client $n8n */
|
||||
$n8n = $container->get(Client::class);
|
||||
$reply = $n8n->get($endpoints->openTickets());
|
||||
|
||||
if ($reply->status === 0) {
|
||||
out(' n8n FEHLER: nicht erreichbar unter ' . $endpoints->openTickets());
|
||||
$problems++;
|
||||
} else {
|
||||
out(' n8n HTTP ' . $reply->status . ' von ' . $endpoints->openTickets());
|
||||
}
|
||||
|
||||
foreach (['N8N_CUSTOMER_KEY', 'N8N_INVOICE_SYNC_SECRET'] as $secret) {
|
||||
if (!$config->has($secret)) {
|
||||
out(' Hinweis ' . $secret . ' ist nicht gesetzt; betroffene Funktionen antworten mit 503.');
|
||||
}
|
||||
}
|
||||
|
||||
exit($problems === 0 ? 0 : 1);
|
||||
|
||||
default:
|
||||
out('EK-DOS-WEB Wartungs-CLI');
|
||||
out();
|
||||
out(' php bin/ekdos migrate Schema anlegen oder fortschreiben');
|
||||
out(' php bin/ekdos user:create Benutzer anlegen');
|
||||
out(' php bin/ekdos user:list Benutzer auflisten');
|
||||
out(' php bin/ekdos user:password <name> Passwort zurücksetzen');
|
||||
out(' php bin/ekdos cache:flush n8n-Cache leeren');
|
||||
out(' php bin/ekdos check Postgres, Redis und n8n prüfen');
|
||||
}
|
||||
Reference in New Issue
Block a user