Trial-Setup-Script entfernt, funktionierende Trial-URL dokumentiert

Das Script hat den Erstkonfigurationsassistenten zwar zuverlaessig
uebersprungen, aber nicht das eigentliche Ziel erreicht: der Schalter
ConfigWizardDone gibt nur Administrationskonsole und Admin-API frei und
aktiviert keinen Testzeitraum. Ohne Lizenzschluessel bleiben IMAP, POP3
und Webmail gesperrt, damit taugt der Weg nicht fuer echten Mailbetrieb.

An seiner Stelle steht jetzt ein README-Abschnitt mit der Adresse, die
tatsaechlich funktioniert. Der Link im Assistenten zeigt noch auf
kerio.com; GFI hat auf gfi.ai umgestellt, und alle alten Download- und
Trial-Adressen leiten auf

  https://gfi.ai/products-and-solutions/email-and-messaging-solutions/kerioconnect/free-trial

Dazu zwei Punkte zur Fehlersuche, falls ein Schluessel abgelehnt wird:
error.log unterscheidet zwischen ungueltig und abgelaufen ("License is
expired"), und registration.kerio.com ist nicht mehr aufloesbar, waehrend
secure.kerio.com es noch ist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-08-07 13:56:41 +02:00
co-authored by Claude Opus 5
parent 5011491067
commit ba21a3aa7d
2 changed files with 41 additions and 395 deletions
+41 -35
View File
@@ -5,12 +5,11 @@ Werkzeuge für Kerio Connect auf der Kommandozeile.
| Datei | Zweck |
| --- | --- |
| [`kerio_rename_user.py`](kerio_rename_user.py) | Benutzer umbenennen — Login-Name **und** Mailbox-Verzeichnis im Store |
| [`kerio_trial_setup.py`](kerio_trial_setup.py) | Am Erstkonfigurationsassistenten vorbei, wenn dessen Lizenzschritt klemmt |
| [`kerio_users_config.py`](kerio_users_config.py) | Lesen und Ändern von `users.cfg` |
| [`kerio_common.py`](kerio_common.py) | Gemeinsame Bausteine: Admin-API-Client, Dienststeuerung |
Beide Scripts laufen **lokal auf dem Kerio-Server als root**, brauchen nur die
Python-Standardbibliothek und bieten `--dry-run`.
Das Script läuft **lokal auf dem Kerio-Server als root**, braucht nur die
Python-Standardbibliothek und bietet `--dry-run`.
Getestet gegen **Kerio Connect 10.0.9 patch 2** auf Debian 12.
@@ -147,48 +146,55 @@ legt anschließend in der Administrationskonsole einen Alias an.
---
# Erstkonfigurationsassistent überspringen
# Testlizenz besorgen
## Warum
Der Erstkonfigurationsassistent verlangt im Schritt „Registrierte Testaktivierung"
eine Testlizenznummer samt CAPTCHA. Der Link „Lizenznummer für Testversion
erhalten" im Assistenten zeigt noch auf die alte `kerio.com`-Adresse und läuft
ins Leere: GFI hat die Domain auf **`gfi.ai`** umgestellt.
Der Assistent verlangt im Schritt „Registrierte Testaktivierung" eine
Testlizenznummer samt CAPTCHA. Der dokumentierte Link „Im unregistrierten Modus
aktivieren" fehlt je nach Version oder ist im Fenster abgeschnitten, und die
verlinkte Registrierungsseite ist seit der Übernahme durch GFI teilweise tot —
man kommt im Assistenten nicht weiter.
Die funktionierende Adresse — dort die E-Mail-Adresse eintragen, die Nummer kommt
per Mail:
Kerio kennt dafür den Schalter `ConfigWizardDone` in `mailserver.cfg`. Steht er
auf `1`, öffnet die Administrationskonsole den Assistenten nicht mehr.
**<https://gfi.ai/products-and-solutions/email-and-messaging-solutions/kerioconnect/free-trial>**
## Was das bringt — und was nicht
Die alten Adressen `www.gfi.com/.../kerio-connect/download` und
`gfi.ai/.../kerio-connect/download` leiten inzwischen alle dorthin um.
Danach sind **Administrationskonsole und Admin-API voll nutzbar**, und dieses
Repo lässt sich damit testen.
Die Nummer lässt sich entweder im Assistenten eintragen oder später aus der
laufenden Instanz heraus: **Dashboard → „Become a registered trial user"**.
Beides braucht ausgehendes HTTPS auf Port 443.
**Der Mailbetrieb bleibt aber gesperrt.** Ohne Lizenzschlüssel weisen IMAP, POP3
und Webmail jede Verbindung ab (`Server license expired` beim Client,
`Product name does not match` im Log), und `error.log` meldet beim Start
`No license key found`. Der Schalter überspringt nur den Dialog — er aktiviert
keinen Testzeitraum.
## Ohne Lizenz geht kein Mailbetrieb
r echten Mailbetrieb führt kein Weg an einer Trial-Lizenznummer vorbei:
**Dashboard → „Become a registered trial user"**, oder auf der GFI-Downloadseite
„Try free for 30 days". Braucht ausgehendes HTTPS auf Port 443.
Der Schalter `ConfigWizardDone` in `mailserver.cfg` überspringt zwar den
Assistenten und gibt Administrationskonsole und Admin-API frei — er aktiviert
aber **keinen Testzeitraum**. Ohne Lizenzschlüssel weisen IMAP, POP3 und Webmail
jede Verbindung ab (`Server license expired` beim Client, `Product name does not
match` im Log), und `error.log` meldet beim Start `No license key found`. Eine
API zum Aktivieren gibt es nicht.
## Benutzung
Wer den Assistenten neu durchlaufen will, setzt `ConfigWizardDone` zurück auf `0`
oder löscht `mailserver.cfg` ganz — Kerio legt beim nächsten Start eine frische
an. Achtung: die Domänendefinition steht in dieser Datei, die Benutzer in
`users.cfg` sind danach verwaist.
```bash
./kerio_trial_setup.py --status # Zustand anzeigen (kein root nötig)
sudo ./kerio_trial_setup.py --skip-wizard # Assistenten überspringen
sudo ./kerio_trial_setup.py --restore # Backup zurückspielen
**`mailserver.cfg` und `users.cfg` dürfen nur bei gestopptem Dienst bearbeitet
werden.** Kerio schreibt seine Konfiguration beim Beenden aus dem Speicher
zurück und überschreibt sonst jede Änderung am laufenden Dienst.
## Wenn der Schlüssel abgelehnt wird
Lohnt ein Blick in `store/logs/error.log` — die Meldung dort unterscheidet
zwischen ungültig und abgelaufen:
```
registration.cpp: License error: Unable to set license (License is expired).
```
## Der springende Punkt
`mailserver.cfg` darf **nur bei gestopptem Dienst** bearbeitet werden. Kerio
schreibt seine Konfiguration beim Beenden aus dem Speicher zurück und würde eine
Änderung am laufenden Dienst wieder überschreiben. Das gilt für `users.cfg`
genauso — beide Scripts halten sich daran.
Und ein DNS-Test: `registration.kerio.com` ist nicht mehr auflösbar,
`secure.kerio.com` schon. Ein Assistent, der noch den alten Host kontaktiert,
kann die Nummer nicht validieren.
---
@@ -197,5 +203,5 @@ genauso — beide Scripts halten sich daran.
- [Administration API — Users](https://manuals.gfi.com/en/kerio/api/connect/admin/reference/interfacekerio_1_1jsonapi_1_1admin_1_1_users.html)
- [Sample communication (Session.login, X-Token)](https://manuals.gfi.com/en/kerio/api/connect/admin/reference/sample_communication.html)
- [Registering Kerio Connect (unregistered mode)](https://manuals.gfi.com/en/kerio/connect/content/registration-and-licenses/registering-kerio-connect-1134.html)
- [Initial Wizard Settings Are Not Saving (ConfigWizardDone)](https://support.kerioconnect.gfi.com/article/114858-initial-wizard-settings-are-not-saving-in-kerio-connect)
- [Kerio Connect Testversion (30 Tage)](https://gfi.ai/products-and-solutions/email-and-messaging-solutions/kerioconnect/free-trial)
- [Modifying the mailserver.cfg](https://support.kerioconnect.gfi.com/en-us/article/114788-modifying-the-mailserver-cfg)
-360
View File
@@ -1,360 +0,0 @@
#!/usr/bin/env python3
"""
Bringt eine frische Kerio-Connect-Installation am Erstkonfigurationsassistenten
vorbei, wenn dessen Lizenzierungsschritt nicht durchlaufbar ist.
Hintergrund: Der Assistent verlangt im Schritt "Registrierte Testaktivierung"
eine Testlizenznummer samt CAPTCHA. Der Link "Im unregistrierten Modus
aktivieren" fehlt je nach Version oder ist im Fenster abgeschnitten, und die
verlinkte Registrierungsseite ist seit der Uebernahme durch GFI teilweise tot.
Kerio kennt dafuer den Schalter ConfigWizardDone in mailserver.cfg. Steht er
auf 1, oeffnet die Administrationskonsole den Assistenten nicht mehr und der
Server laeuft 30 Tage ab Installation im unregistrierten Modus.
Was im unregistrierten Modus fehlt: Antivirus-Updates, ActiveSync, Greylisting,
Hersteller-Support. Nach 30 Tagen stoppt die Engine. Benutzerverwaltung,
Administrationskonsole und Admin-API sind nicht eingeschraenkt.
WICHTIG: mailserver.cfg darf nur bei gestopptem Dienst bearbeitet werden.
Kerio schreibt seine Konfiguration beim Beenden aus dem Speicher zurueck und
wuerde eine Aenderung am laufenden Dienst wieder ueberschreiben. Genau das
erledigt dieses Script in der richtigen Reihenfolge.
Das Script muss LOKAL AUF DEM KERIO-SERVER ALS ROOT laufen.
Beispiele:
./kerio_trial_setup.py --status # nur anzeigen
sudo ./kerio_trial_setup.py --skip-wizard # Assistenten ueberspringen
sudo ./kerio_trial_setup.py --restore # letztes Backup zuruecksetzen
"""
from __future__ import annotations
import argparse
import os
import re
import shutil
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path
from kerio_common import (
DEFAULT_INSTALL_DIR,
DEFAULT_SERVICE,
AbortedError,
ServiceController,
)
# Kerio deaktiviert die Engine 30 Tage nach der Installation.
TRIAL_DAYS = 30
BACKUP_SUFFIX = ".bak-trial-setup"
# <variable name="ConfigWizardDone">0</variable>
WIZARD_PATTERN = re.compile(
r'(<variable\s+name="ConfigWizardDone"\s*>)([^<]*)(</variable>)'
)
class ConfigFile:
"""Liest und schreibt mailserver.cfg mit Backup."""
def __init__(self, path: Path) -> None:
self.path = path
if not path.is_file():
raise AbortedError(
f"'{path}' existiert nicht. Installationsverzeichnis mit "
"--install-dir angeben."
)
self.text = path.read_text(encoding="utf-8")
# -- Lesen -------------------------------------------------------------
def wizard_done(self) -> str | None:
match = WIZARD_PATTERN.search(self.text)
return match.group(2).strip() if match else None
def trial_id(self) -> str | None:
match = re.search(
r'<variable\s+name="TrialID"\s*>([^<]*)</variable>', self.text)
return match.group(1).strip() if match else None
def store_dir(self) -> str | None:
match = re.search(
r'<variable\s+name="StoreDir"\s*>([^<]*)</variable>', self.text)
return match.group(1).strip() if match else None
# -- Schreiben ---------------------------------------------------------
def set_wizard_done(self, value: str = "1") -> bool:
"""Setzt ConfigWizardDone. Gibt False zurueck, wenn schon so."""
match = WIZARD_PATTERN.search(self.text)
if match is None:
raise AbortedError(
"ConfigWizardDone steht nicht in der Konfiguration. "
"Andere Kerio-Version? Bitte die Datei von Hand pruefen."
)
if match.group(2).strip() == value:
return False
self.text = WIZARD_PATTERN.sub(rf"\g<1>{value}\g<3>", self.text, count=1)
return True
def backup(self) -> Path:
target = self.path.with_name(self.path.name + BACKUP_SUFFIX)
shutil.copy2(self.path, target)
return target
def save(self) -> None:
# Ueber eine temporaere Datei im selben Verzeichnis, damit die
# Konfiguration nie halb geschrieben auf der Platte liegt.
temp = self.path.with_name(self.path.name + ".tmp-trial-setup")
temp.write_text(self.text, encoding="utf-8")
shutil.copystat(self.path, temp)
os.replace(temp, self.path)
def estimate_install_date(install_dir: Path, config: ConfigFile) -> datetime | None:
"""Schaetzt den Installationszeitpunkt.
Kerio fuehrt kein Installationsdatum in der Konfiguration, deshalb bleibt
nur der Umweg ueber Zeitstempel:
1. der Eintrag der Paketverwaltung - am zuverlaessigsten
2. sonst der aelteste Zeitstempel unter den Store-Unterverzeichnissen,
die Kerio einmalig beim ersten Start anlegt
Verzeichnisse wie store/ selbst taugen nicht: ihr Zeitstempel wandert
mit jeder Aenderung darin. Der Wert ist eine Schaetzung, kein Beweis.
"""
for package_file in (
Path("/var/lib/dpkg/info/kerio-connect.list"),
Path("/var/lib/dpkg/info/kerio-connect:amd64.list"),
):
if package_file.is_file():
try:
return datetime.fromtimestamp(package_file.stat().st_mtime)
except OSError:
pass
store = Path(config.store_dir() or (install_dir / "store"))
stamps = []
for name in ("queue", "saas", "spamassassin", "logs"):
path = store / name
if path.is_dir():
try:
stamps.append(path.stat().st_mtime)
except OSError:
continue
if stamps:
return datetime.fromtimestamp(min(stamps))
return None
def print_status(config: ConfigFile, install_dir: Path,
service: ServiceController) -> None:
print("Status")
print("------")
print(f" Konfiguration : {config.path}")
running = service.is_running()
state = {True: "laeuft", False: "gestoppt", None: "unbekannt"}[running]
print(f" Dienst : {service.describe()} -> {state}")
wizard = config.wizard_done()
if wizard == "1":
wizard_text = "1 (Assistent wird uebersprungen)"
elif wizard == "0":
wizard_text = "0 (Assistent erscheint beim Oeffnen der Konsole)"
else:
wizard_text = f"{wizard!r} (unerwarteter Wert)"
print(f" ConfigWizard : {wizard_text}")
trial = config.trial_id()
print(f" TrialID : {trial or '(leer - unregistrierter Modus)'}")
license_dir = install_dir / "license"
if license_dir.is_dir():
entries = [e for e in license_dir.iterdir() if not e.name.startswith(".")]
print(f" Lizenzdateien : {len(entries) or 'keine'}")
installed = estimate_install_date(install_dir, config)
if installed:
expires = installed + timedelta(days=TRIAL_DAYS)
remaining = (expires - datetime.now()).days
print(f" Installiert : ~{installed:%Y-%m-%d %H:%M} (geschaetzt)")
if remaining >= 0:
print(f" Testzeitraum : laeuft ~{expires:%Y-%m-%d} ab "
f"(noch ~{remaining} Tage)")
else:
print(f" Testzeitraum : seit ~{expires:%Y-%m-%d} abgelaufen "
"- die Engine wird deaktiviert sein")
def skip_wizard(config: ConfigFile, service: ServiceController,
args: argparse.Namespace) -> int:
if config.wizard_done() == "1":
print("\nConfigWizardDone steht bereits auf 1 - nichts zu tun.")
return 0
service.preflight()
print("\nGeplante Aenderung")
print("------------------")
print(f" {config.path}")
print(" ConfigWizardDone: 0 -> 1")
print(f" Backup nach : {config.path.name}{BACKUP_SUFFIX}")
print(f" Dienst : {service.describe()} (stoppen, aendern, starten)")
if args.dry_run:
print("\n--dry-run: es wurde nichts geaendert.")
return 0
if not args.yes:
print("\nAchtung: der Kerio-Dienst wird dabei kurz gestoppt.")
if input("Jetzt durchfuehren? [j/N] ").strip().lower() not in ("j", "ja", "y", "yes"):
print("Abgebrochen.")
return 1
print()
was_running = service.is_running()
service.stop()
try:
# Erst nach dem Stoppen neu einlesen: Kerio hat die Datei beim
# Beenden zurueckgeschrieben, unsere Kopie von vorhin ist veraltet.
fresh = ConfigFile(config.path)
backup = fresh.backup()
print(f" Backup: {backup}")
if not fresh.set_wizard_done("1"):
print(" ConfigWizardDone stand nach dem Stoppen schon auf 1.")
else:
fresh.save()
print(" ConfigWizardDone auf 1 gesetzt.")
except (AbortedError, OSError) as exc:
print(f"\nFEHLER: {exc}", file=sys.stderr)
if was_running:
service.start()
return 2
finally:
if was_running:
service.start()
# Gegenlesen: haelt der Wert, nachdem Kerio wieder laeuft?
time.sleep(2)
check = ConfigFile(config.path)
if check.wizard_done() != "1":
print("\n WARNUNG: Der Wert steht nach dem Start nicht mehr auf 1. "
"Lief der Dienst beim Schreiben doch noch?", file=sys.stderr)
return 2
print("\nFertig. Die Administrationskonsole oeffnet den Assistenten nicht mehr.")
print("Kerio laeuft im unregistrierten Modus - 30 Tage ab Installation.")
return 0
def restore(config_path: Path, service: ServiceController,
args: argparse.Namespace) -> int:
backup = config_path.with_name(config_path.name + BACKUP_SUFFIX)
if not backup.is_file():
raise AbortedError(f"Kein Backup gefunden: {backup}")
print(f"\nStellt '{backup}' nach '{config_path}' wieder her.")
if args.dry_run:
print("--dry-run: es wurde nichts geaendert.")
return 0
if not args.yes:
if input("Jetzt zuruecksetzen? [j/N] ").strip().lower() not in ("j", "ja", "y", "yes"):
print("Abgebrochen.")
return 1
print()
was_running = service.is_running()
service.stop()
try:
shutil.copy2(backup, config_path)
print(f" Wiederhergestellt aus {backup}")
finally:
if was_running:
service.start()
return 0
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Kerio Connect am Erstkonfigurationsassistenten vorbeibringen.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument("--install-dir", default=DEFAULT_INSTALL_DIR,
help=f"Kerio-Installationsverzeichnis "
f"(Standard: {DEFAULT_INSTALL_DIR})")
parser.add_argument("--service-name", default=DEFAULT_SERVICE,
help=f"Name des Kerio-Dienstes (Standard: {DEFAULT_SERVICE})")
parser.add_argument("--service-manager", default="auto",
choices=("auto", "systemd", "initd", "none"),
help="Wie der Dienst gestoppt wird")
parser.add_argument("--service-wait", type=int, default=90,
help="Sekunden Wartezeit aufs Stoppen (Standard: 90)")
action = parser.add_mutually_exclusive_group()
action.add_argument("--status", action="store_true",
help="Nur den Zustand anzeigen (Standard)")
action.add_argument("--skip-wizard", action="store_true",
help="ConfigWizardDone auf 1 setzen")
action.add_argument("--restore", action="store_true",
help="Konfiguration aus dem Backup zuruecksetzen")
parser.add_argument("--dry-run", action="store_true",
help="Nur anzeigen, was passieren wuerde")
parser.add_argument("--yes", action="store_true",
help="Rueckfrage ueberspringen")
args = parser.parse_args(argv)
if (args.skip_wizard or args.restore) and not args.dry_run and os.geteuid() != 0:
parser.error(
"Zum Aendern der Konfiguration werden root-Rechte benoetigt. "
"Mit sudo starten (oder --dry-run zum Testen)."
)
return args
def main(argv: list[str]) -> int:
args = parse_args(argv)
install_dir = Path(args.install_dir)
config_path = install_dir / "mailserver.cfg"
service = ServiceController(
name=args.service_name,
mode=args.service_manager,
wait_seconds=args.service_wait,
dry_run=args.dry_run,
)
try:
if args.restore:
return restore(config_path, service, args)
config = ConfigFile(config_path)
print_status(config, install_dir, service)
if args.skip_wizard:
return skip_wizard(config, service, args)
return 0
except AbortedError as exc:
print(f"\nFEHLER: {exc}", file=sys.stderr)
return 2
except PermissionError as exc:
print(f"\nFEHLER: Keine Berechtigung: {exc}\nMit sudo starten.", file=sys.stderr)
return 2
except KeyboardInterrupt:
print("\nAbgebrochen.", file=sys.stderr)
return 130
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))