Files
EKDOS/backend/bin/ekdos
T
Kyle Müller 7d82e807fc
EK-DOS-WEB bauen und ausrollen / frontend (push) Failing after 22s
EK-DOS-WEB bauen und ausrollen / backend (push) Failing after 27s
EK-DOS-WEB bauen und ausrollen / deploy (push) Skipped
Initial
2026-09-06 13:25:44 +02:00

251 lines
8.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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');
}