Assistent mit Menuefuehrung, dazu zwei Haertungen

kerio_tui.py fuehrt durch Umbenennung und Uebernahme, ohne dass man
Parameter kennen muss: Benutzer und Domains kommen zur Auswahl, verwaiste
Mailbox-Verzeichnisse findet der Assistent selbst, und vor dem Ausfuehren
zeigt er den gleichwertigen Kommandozeilenaufruf. Nur curses aus der
Standardbibliothek, keine neue Abhaengigkeit.

Beim Durchfahren ueber ein PTY kamen zwei Dinge heraus:

keypad(True) loest die Pfeiltasten auf diesem Server nicht auf - getch()
liefert 27, 91, 66 statt KEY_DOWN, wofuer ein unpassendes terminfo
genuegt. Das fuehrende ESC haette als "zurueck" gegolten und den
Assistenten bei jedem Pfeildruck verlassen. Er loest die Sequenzen jetzt
selbst auf; ein einzelnes ESC wirkt weiterhin als Abbruch.

Und: Bricht die Verbindung weg, waehrend Kerio gestoppt ist, stirbt das
Script an SIGHUP und laesst den Dienst unten - genau so passiert, als der
Testtreiber die Verbindung schloss. ServiceController.abgesichert() faengt
SIGHUP, SIGTERM und SIGINT ab und startet den Dienst wieder, bevor der
Abbruch durchgereicht wird. Gegen den Testserver geprueft: Signal mitten
im Stopp-Fenster, Dienst kommt hoch, Daten unberuehrt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-08-07 14:54:33 +02:00
co-authored by Claude Opus 5
parent b9cbf65fac
commit 24070cba4e
5 changed files with 751 additions and 69 deletions
+76 -68
View File
@@ -373,35 +373,39 @@ def adopt_store(args: argparse.Namespace, config: UsersConfig,
print()
was_running = service.is_running()
service.stop()
done: list[tuple[Path, Path]] = []
try:
check_rename_targets(moves)
done = apply_store_renames(moves)
# Ab hier ist Kerio gestoppt. Der Waechter sorgt dafuer, dass der Dienst
# auch dann wieder hochkommt, wenn das Script abgeschossen wird.
with service.abgesichert(bool(was_running)):
service.stop()
try:
check_rename_targets(moves)
done = apply_store_renames(moves)
# Die uebernommene Mailbox traegt noch die Identitaet des alten
# Benutzers. stats.usr legt Kerio selbst neu an, die Kontaktkarte
# nicht - die muss umgeschrieben werden.
for _, target in done:
if ".abgeloest-" in target.name:
continue
if rewrite_personal_vcard(
target,
f"{args.old_login}@{domain}",
f"{args.new_login}@{domain}",
target_user["fullName"]):
print(f" Kontaktkarte auf {target_user['fullName']} umgeschrieben")
stats = target / "stats.usr"
if stats.is_file():
stats.rename(target / f"stats.usr.alt-{stamp}")
print(" Beiseite: stats.usr (Kerio legt sie neu an)")
except (AbortedError, OSError) as exc:
print(f"\nFEHLER: {exc}", file=sys.stderr)
revert_store_renames(done)
if was_running:
service.start()
return 2
# Die uebernommene Mailbox traegt noch die Identitaet des alten
# Benutzers. stats.usr legt Kerio selbst neu an, die Kontaktkarte
# nicht - die muss umgeschrieben werden.
for _, target in done:
if ".abgeloest-" in target.name:
continue
if rewrite_personal_vcard(
target,
f"{args.old_login}@{domain}",
f"{args.new_login}@{domain}",
target_user["fullName"]):
print(f" Kontaktkarte auf {target_user['fullName']} "
"umgeschrieben")
stats = target / "stats.usr"
if stats.is_file():
stats.rename(target / f"stats.usr.alt-{stamp}")
print(" Beiseite: stats.usr (Kerio legt sie neu an)")
except (AbortedError, OSError) as exc:
print(f"\nFEHLER: {exc}", file=sys.stderr)
revert_store_renames(done)
if was_running:
service.start()
return 2
if was_running:
service.start()
@@ -513,54 +517,58 @@ def rename_user(args: argparse.Namespace, config: UsersConfig,
# -- Schreiben: users.cfg und Verzeichnisse in einem Stopp-Fenster ----
print()
was_running = service.is_running()
service.stop()
done: list[tuple[Path, Path]] = []
backup: Path | None = None
try:
# Nach dem Stoppen neu einlesen: Kerio hat users.cfg beim Beenden
# aus dem Speicher zurueckgeschrieben, unsere Kopie ist veraltet.
fresh = UsersConfig(config.path)
backup = fresh.backup(BACKUP_SUFFIX)
print(f" Backup: {backup}")
# Ab hier ist Kerio gestoppt. Der Waechter sorgt dafuer, dass der Dienst
# auch dann wieder hochkommt, wenn das Script abgeschossen wird - etwa
# weil die SSH-Verbindung wegbricht.
with service.abgesichert(bool(was_running)):
service.stop()
try:
# Nach dem Stoppen neu einlesen: Kerio hat users.cfg beim Beenden
# aus dem Speicher zurueckgeschrieben, unsere Kopie ist veraltet.
fresh = UsersConfig(config.path)
backup = fresh.backup(BACKUP_SUFFIX)
print(f" Backup: {backup}")
changed = fresh.rename_user(
old_login, new_login, domain,
args.full_name if needs_fullname else None)
if changed == 0:
raise AbortedError(
"Kein passender Eintrag in users.cfg gefunden - nichts geaendert."
)
fresh.save()
print(f" users.cfg: {changed} Eintraege angepasst "
f"(Listen: User, UserAdditionalData)")
changed = fresh.rename_user(
old_login, new_login, domain,
args.full_name if needs_fullname else None)
if changed == 0:
raise AbortedError(
"Kein passender Eintrag in users.cfg gefunden - nichts geaendert."
)
fresh.save()
print(f" users.cfg: {changed} Eintraege angepasst "
f"(Listen: User, UserAdditionalData)")
if renames:
# Zielpruefung wiederholen: der Zustand kann sich seit der
# Vorpruefung geaendert haben.
check_rename_targets(renames)
done = apply_store_renames(renames)
if renames:
# Zielpruefung wiederholen: der Zustand kann sich seit der
# Vorpruefung geaendert haben.
check_rename_targets(renames)
done = apply_store_renames(renames)
# Die Kontaktkarte traegt noch die alte Adresse - Kerio zieht dort
# nur den Namen nach, nicht die Adresse.
display_name = args.full_name if needs_fullname else user["fullName"]
for _, target in done:
if rewrite_personal_vcard(target, f"{old_login}@{domain}",
f"{new_login}@{domain}", display_name):
print(" Kontaktkarte auf die neue Adresse umgeschrieben")
except (AbortedError, OSError) as exc:
print(f"\nFEHLER: {exc}", file=sys.stderr)
revert_store_renames(done)
if backup and backup.is_file():
try:
shutil.copy2(backup, config.path)
print(f" users.cfg aus {backup} zurueckgesetzt.", file=sys.stderr)
except OSError as restore_exc:
print(f" WARNUNG: Zuruecksetzen von users.cfg fehlgeschlagen: "
f"{restore_exc}", file=sys.stderr)
if was_running:
service.start()
return 2
# Die Kontaktkarte traegt noch die alte Adresse - Kerio zieht dort
# nur den Namen nach, nicht die Adresse.
display_name = args.full_name if needs_fullname else user["fullName"]
for _, target in done:
if rewrite_personal_vcard(target, f"{old_login}@{domain}",
f"{new_login}@{domain}", display_name):
print(" Kontaktkarte auf die neue Adresse umgeschrieben")
except (AbortedError, OSError) as exc:
print(f"\nFEHLER: {exc}", file=sys.stderr)
revert_store_renames(done)
if backup and backup.is_file():
try:
shutil.copy2(backup, config.path)
print(f" users.cfg aus {backup} zurueckgesetzt.", file=sys.stderr)
except OSError as restore_exc:
print(f" WARNUNG: Zuruecksetzen von users.cfg fehlgeschlagen: "
f"{restore_exc}", file=sys.stderr)
if was_running:
service.start()
return 2
if was_running:
service.start()