katalon-cli 0.1.20__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- katalon_cli-0.3.0/PKG-INFO +171 -0
- katalon_cli-0.3.0/README.md +157 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/pyproject.toml +1 -1
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/pyproject.toml.orig +1 -1
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/compose_gen.py +31 -4
- katalon_cli-0.3.0/src/katalon_cli/core/permissions.py +113 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/state.py +1 -1
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/main.py +128 -7
- katalon_cli-0.3.0/src/katalon_cli/templates/Caddyfile.j2 +3 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/templates/compose.yaml.j2 +49 -3
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/templates/nginx.conf.j2 +24 -6
- katalon_cli-0.1.20/PKG-INFO +0 -94
- katalon_cli-0.1.20/README.md +0 -80
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/__init__.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/__init__.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/backup.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/checks.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/docker.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/paths.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/reindex.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/release.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/core/selfupdate.py +0 -0
- {katalon_cli-0.1.20 → katalon_cli-0.3.0}/src/katalon_cli/py.typed +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: katalon-cli
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Installer & Updater CLI für Katalon Production-Instanzen
|
|
5
|
+
Author: Karl Krägelin
|
|
6
|
+
Author-email: Karl Krägelin <karlkraegelin@gmail.com>
|
|
7
|
+
Requires-Dist: typer>=0.15
|
|
8
|
+
Requires-Dist: rich>=13.9
|
|
9
|
+
Requires-Dist: httpx>=0.28
|
|
10
|
+
Requires-Dist: pydantic>=2.9
|
|
11
|
+
Requires-Dist: jinja2>=3.1
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# katalon-cli
|
|
16
|
+
|
|
17
|
+
Installer & Updater für [Katalon Collections](https://github.com/katalon-collections/katalon) Production-Instanzen.
|
|
18
|
+
Verwaltet eine Instanz unter einem frei gewählten Zielverzeichnis — pullt gepinnte
|
|
19
|
+
Release-Images, generiert `compose.yaml`, macht Backups vor jedem Update, kann rollbacken.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
25
|
+
uv tool install katalon-cli
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Nutzung
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
katalon install # interaktiver Setup-Wizard (Rich-Prompts + Progress)
|
|
32
|
+
katalon start / stop / status
|
|
33
|
+
katalon check-updates # prüft nur, ob ein neues Release verfügbar ist
|
|
34
|
+
katalon update # interaktives Update, Backup zuerst immer
|
|
35
|
+
katalon rollback # letztes Backup einspielen
|
|
36
|
+
katalon doctor # Docker, Diskspace, Ports, Schreibrechte von MEDIA_ROOT prüfen
|
|
37
|
+
katalon backup # manuelles Backup
|
|
38
|
+
katalon logs [service]
|
|
39
|
+
katalon manage <args> # katalon-manage im api-Container (z.B. reset-admin, create-user)
|
|
40
|
+
katalon restore DUMP # DB durch pg_dump ersetzen (-Fc oder SQL), danach Reindex
|
|
41
|
+
katalon reindex # Suchindex neu aufbauen, mit Fortschrittsanzeige
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Ein DB-Dump enthält nicht den Suchindex (Elasticsearch). `restore` baut ihn deshalb nach dem
|
|
45
|
+
Einspielen neu auf (`--no-reindex` zum Überspringen, später `katalon reindex`). Bei großen
|
|
46
|
+
Beständen dauert das lange (ca. 45 Min. für 80k Datensätze). Außerdem prüft `katalon` einmal
|
|
47
|
+
täglich auf neue Versionen (abschalten: `KATALON_NO_UPDATE_CHECK=1`).
|
|
48
|
+
|
|
49
|
+
`install` ohne `--dir` fragt das Zielverzeichnis interaktiv ab. Nach erfolgreicher Einrichtung
|
|
50
|
+
wird der absolute Pfad benutzerweit in `~/.config/katalon/instance` gespeichert.
|
|
51
|
+
Bei gesetztem, absolutem `XDG_CONFIG_HOME` liegt die Datei stattdessen unter
|
|
52
|
+
`$XDG_CONFIG_HOME/katalon/instance`. Das Konfigurationsverzeichnis wird bei Bedarf angelegt.
|
|
53
|
+
Alle weiteren Befehle verwenden den gespeicherten Pfad, unabhängig vom Arbeitsverzeichnis.
|
|
54
|
+
Ohne gespeicherten Pfad ist der Default auf allen Plattformen `~/katalon`.
|
|
55
|
+
|
|
56
|
+
Alle Befehle unterstützen `--dir PATH`: überschreibt den Pfad nur für diesen Aufruf.
|
|
57
|
+
Eine erfolgreiche neue Installation speichert ihr Zielverzeichnis als neuen Default.
|
|
58
|
+
Bestehende Instanzen lassen sich weiterhin mit `--dir` verwenden; alternativ ihren absoluten
|
|
59
|
+
Pfad als einzige Zeile in die Konfigurationsdatei eintragen.
|
|
60
|
+
`install`/`update` fragen interaktiv (Auswahl über `rich.prompt`),
|
|
61
|
+
`update`/`rollback` haben `--yes` zum Überspringen der Rückfrage.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
katalon install --dir ~/meine-katalon-instanz
|
|
65
|
+
katalon start
|
|
66
|
+
katalon status
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## API-Adresse
|
|
70
|
+
|
|
71
|
+
Die REST-API liegt direkt unter der Base-URL, ohne `/api`-Präfix: `<KATALON_BASE_URL>/v1/...`
|
|
72
|
+
(z. B. Login unter `http://127.0.0.1:8080/v1/...` bei `behind-proxy`). Unter `/api/` liegen nur
|
|
73
|
+
die OpenAPI-Dokumentation (`/api/docs`, `/api/openapi.json`) und interne Endpunkte;
|
|
74
|
+
`/api/v1/...` liefert für normale API-Aufrufe 404.
|
|
75
|
+
|
|
76
|
+
## Medienverzeichnis und Rechte
|
|
77
|
+
|
|
78
|
+
API und Worker laufen im Container als unprivilegierter User (`USER` im Image). `MEDIA_ROOT`
|
|
79
|
+
ist ein Bind-Mount vom Host und muss für diesen User beschreibbar sein. `katalon start`
|
|
80
|
+
(auch beim Install), `update` und `rollback` lesen UID/GID per `id` aus dem api-Container und
|
|
81
|
+
setzen in einem kurzlebigen root-Container desselben Images den Eigentümer aller Einträge
|
|
82
|
+
unter `MEDIA_ROOT`, die noch einem anderen Eigentümer gehören. Dateiinhalte werden nicht
|
|
83
|
+
verändert; ein erneuter Lauf ändert nichts. `katalon doctor` meldet ein nicht beschreibbares
|
|
84
|
+
`MEDIA_ROOT`, bevor Uploads mit HTTP 500 scheitern.
|
|
85
|
+
|
|
86
|
+
## Bild-Cache (Cantaloupe)
|
|
87
|
+
|
|
88
|
+
Vorschaubilder und Viewer-Kacheln rechnet Cantaloupe aus den Originalen und legt sie im
|
|
89
|
+
Instanzverzeichnis unter `cantaloupe-cache/` ab (abweichend: `CANTALOUPE_CACHE_DIR` in `.env`).
|
|
90
|
+
Der Cache übersteht Neustarts und Updates; ein kurzlebiger Init-Container übergibt das
|
|
91
|
+
Verzeichnis vor jedem Start dem Cantaloupe-User. Abgelaufene Derivate
|
|
92
|
+
(`CANTALOUPE_CACHE_SERVER_DERIVATIVE_TTL_SECONDS`, Standard 30 Tage) löscht Cantaloupe selbst.
|
|
93
|
+
|
|
94
|
+
Eine Größenbegrenzung kennt Cantaloupe nicht. Als Richtwert ~25 KB pro Vorschaubild plus
|
|
95
|
+
Kacheln der aufgerufenen Detailansichten einplanen; wer den Platz hart begrenzen muss, legt
|
|
96
|
+
`CANTALOUPE_CACHE_DIR` auf ein eigenes Dateisystem bzw. Quota-Verzeichnis. Hosts ohne
|
|
97
|
+
Plattenplatz nutzen mit `CANTALOUPE_FILESYSTEMCACHE_PATHNAME=/tmpfs/cache` einen RAM-Cache
|
|
98
|
+
(`CANTALOUPE_CACHE_TMPFS_SIZE`, Standard `512m`), der nach jedem Neustart leer ist.
|
|
99
|
+
|
|
100
|
+
Vorschaubilder lassen sich vorab berechnen, damit schon der erste Aufruf der Trefferliste
|
|
101
|
+
schnell ist: `katalon media warm-thumbnails` (Hauptbilder zuerst, dann die übrigen; mit
|
|
102
|
+
`--primary-only` nur Hauptbilder, `--status` zeigt den Fortschritt). Der Lauf arbeitet im
|
|
103
|
+
Worker in kleinen Paketen, belegt höchstens `THUMBNAIL_WARM_CONCURRENCY` (Standard 3)
|
|
104
|
+
Cantaloupe-Kerne und setzt nach einer Unterbrechung beim letzten Stand fort. Neu hochgeladene
|
|
105
|
+
Bilder werden automatisch vorberechnet.
|
|
106
|
+
|
|
107
|
+
Bestehende Instanzen stellt `katalon update` automatisch um (neue `compose.yaml`); der bisherige
|
|
108
|
+
RAM-Cache entfällt dabei. Releases, die eine neuere CLI voraussetzen, bricht `katalon update`
|
|
109
|
+
mit einem Hinweis auf `uv tool upgrade katalon-cli` ab.
|
|
110
|
+
|
|
111
|
+
## Domain und HTTPS
|
|
112
|
+
|
|
113
|
+
Für eine lokale Installation bleibt `http://localhost` mit TLS-Modus `none` der Standard.
|
|
114
|
+
Bei einer öffentlichen Domain schlägt der Setup-Wizard `caddy` vor. Du kannst auch nur
|
|
115
|
+
`sammlung.example.org` eingeben; mit Caddy wird daraus `https://sammlung.example.org`.
|
|
116
|
+
|
|
117
|
+
Vor der Installation müssen die DNS-Einträge wirksam sein: Der A-Record zeigt auf die öffentliche
|
|
118
|
+
IPv4-Adresse des Servers. Ein vorhandener AAAA-Record muss auf dessen erreichbare IPv6-Adresse
|
|
119
|
+
zeigen. TCP-Ports 80 und 443 müssen frei und öffentlich erreichbar sein, auch durch Server- und
|
|
120
|
+
Provider-Firewalls sowie gegebenenfalls Portweiterleitungen. Docker und Docker Compose müssen
|
|
121
|
+
installiert sein. Die CLI prüft freie Ports lokal, aber keine öffentliche DNS-Auflösung oder
|
|
122
|
+
Erreichbarkeit von außen.
|
|
123
|
+
|
|
124
|
+
Die TLS-Modi:
|
|
125
|
+
|
|
126
|
+
- `caddy`: Caddy läuft als Docker-Container vor dem internen nginx, belegt Ports 80 und 443,
|
|
127
|
+
beantragt öffentlich vertrauenswürdige Zertifikate und erneuert sie automatisch.
|
|
128
|
+
HTTP wird auf HTTPS umgeleitet. Zertifikate und Zustand liegen dauerhaft in den Docker-Volumes
|
|
129
|
+
`caddy_data` und `caddy_config`, die bei Neustarts, Updates und Rollbacks erhalten bleiben.
|
|
130
|
+
Dafür muss Caddy weiterlaufen, DNS muss stimmen und der Server erreichbar bleiben.
|
|
131
|
+
- `standalone`: nginx verwendet ein selbstsigniertes Zertifikat. Browser zeigen eine Warnung;
|
|
132
|
+
es gibt keine automatische Erneuerung. Eigene Zertifikate können unter `<dir>/certs/`
|
|
133
|
+
als `fullchain.pem` und `privkey.pem` abgelegt werden. Ihre Erneuerung und das anschließende
|
|
134
|
+
Neuladen von nginx musst du selbst einrichten.
|
|
135
|
+
- `behind-proxy`: Ein eigener Reverse-Proxy übernimmt HTTPS und Zertifikate.
|
|
136
|
+
Katalon ist standardmäßig nur auf `127.0.0.1:8080` erreichbar.
|
|
137
|
+
- `none`: HTTP ohne TLS, für lokale Tests oder IP-Adressen.
|
|
138
|
+
|
|
139
|
+
Die erste Zertifikatsausstellung kann nach dem Start kurz dauern. Bei HTTPS-Problemen:
|
|
140
|
+
`katalon logs caddy`. Caddy-Konfiguration und Zertifikatsverwaltung sind in der
|
|
141
|
+
[Caddy-Dokumentation](https://caddyserver.com/docs/automatic-https) beschrieben.
|
|
142
|
+
Die CLI erzeugt `<dir>/Caddyfile` bei Installation, Update und Rollback neu;
|
|
143
|
+
instanzspezifische Compose-Anpassungen gehören in `compose.override.yaml`.
|
|
144
|
+
|
|
145
|
+
## Entwicklung
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
uv sync
|
|
149
|
+
uv run katalon --help
|
|
150
|
+
uv run pytest
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Architektur
|
|
154
|
+
|
|
155
|
+
- `core/state.py` — `installation.json`, Single Source of Truth für laufende Version.
|
|
156
|
+
- `core/release.py` — GitHub-Release-Metadaten (`katalon-release.json` Asset). Enthält
|
|
157
|
+
optional `env_vars`/`deprecated_env_vars`: `install`/`update` ergänzen neue Pflichtvariablen
|
|
158
|
+
automatisch (Secret generieren oder Default übernehmen), fragen bei fehlendem Default interaktiv
|
|
159
|
+
nach (`--yes` bricht dann ab), tragen neue optionale Variablen auskommentiert in `.env` ein und
|
|
160
|
+
warnen vor veralteten Variablen, die noch in der `.env` stehen.
|
|
161
|
+
- `core/compose_gen.py` + `templates/compose.yaml.j2` + `templates/nginx.conf.j2` — rendert `compose.yaml`
|
|
162
|
+
+ `nginx.conf` und bei `tls_mode=caddy` das `Caddyfile` aus Version + TLS-Modus;
|
|
163
|
+
erzeugt bei `tls_mode=standalone` ein selbstsigniertes
|
|
164
|
+
Zertifikat unter `<dir>/certs/` (eigenes Zertifikat dort ablegen, um es zu ersetzen).
|
|
165
|
+
`install` fragt zusätzlich `MEDIA_ROOT` ab und legt `<dir>/compose.override.yaml.example` an
|
|
166
|
+
(instanzspezifische Overrides — umbenennen zu `compose.override.yaml`, wird automatisch eingebunden
|
|
167
|
+
und bei Updates nicht angefasst). Optionale Env-Vars (Admin-Login, SMTP, Geonames, …) landet
|
|
168
|
+
auskommentiert in `.env`.
|
|
169
|
+
- `core/backup.py` — `pg_dump` vor jedem Update; Rollback restauriert Dump statt `alembic downgrade`.
|
|
170
|
+
- `core/docker.py` — dünner `docker compose`-Subprocess-Wrapper.
|
|
171
|
+
- `main.py` — Typer-Commands, interaktive Teile über `rich.prompt`/`rich.progress`.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# katalon-cli
|
|
2
|
+
|
|
3
|
+
Installer & Updater für [Katalon Collections](https://github.com/katalon-collections/katalon) Production-Instanzen.
|
|
4
|
+
Verwaltet eine Instanz unter einem frei gewählten Zielverzeichnis — pullt gepinnte
|
|
5
|
+
Release-Images, generiert `compose.yaml`, macht Backups vor jedem Update, kann rollbacken.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
11
|
+
uv tool install katalon-cli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Nutzung
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
katalon install # interaktiver Setup-Wizard (Rich-Prompts + Progress)
|
|
18
|
+
katalon start / stop / status
|
|
19
|
+
katalon check-updates # prüft nur, ob ein neues Release verfügbar ist
|
|
20
|
+
katalon update # interaktives Update, Backup zuerst immer
|
|
21
|
+
katalon rollback # letztes Backup einspielen
|
|
22
|
+
katalon doctor # Docker, Diskspace, Ports, Schreibrechte von MEDIA_ROOT prüfen
|
|
23
|
+
katalon backup # manuelles Backup
|
|
24
|
+
katalon logs [service]
|
|
25
|
+
katalon manage <args> # katalon-manage im api-Container (z.B. reset-admin, create-user)
|
|
26
|
+
katalon restore DUMP # DB durch pg_dump ersetzen (-Fc oder SQL), danach Reindex
|
|
27
|
+
katalon reindex # Suchindex neu aufbauen, mit Fortschrittsanzeige
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Ein DB-Dump enthält nicht den Suchindex (Elasticsearch). `restore` baut ihn deshalb nach dem
|
|
31
|
+
Einspielen neu auf (`--no-reindex` zum Überspringen, später `katalon reindex`). Bei großen
|
|
32
|
+
Beständen dauert das lange (ca. 45 Min. für 80k Datensätze). Außerdem prüft `katalon` einmal
|
|
33
|
+
täglich auf neue Versionen (abschalten: `KATALON_NO_UPDATE_CHECK=1`).
|
|
34
|
+
|
|
35
|
+
`install` ohne `--dir` fragt das Zielverzeichnis interaktiv ab. Nach erfolgreicher Einrichtung
|
|
36
|
+
wird der absolute Pfad benutzerweit in `~/.config/katalon/instance` gespeichert.
|
|
37
|
+
Bei gesetztem, absolutem `XDG_CONFIG_HOME` liegt die Datei stattdessen unter
|
|
38
|
+
`$XDG_CONFIG_HOME/katalon/instance`. Das Konfigurationsverzeichnis wird bei Bedarf angelegt.
|
|
39
|
+
Alle weiteren Befehle verwenden den gespeicherten Pfad, unabhängig vom Arbeitsverzeichnis.
|
|
40
|
+
Ohne gespeicherten Pfad ist der Default auf allen Plattformen `~/katalon`.
|
|
41
|
+
|
|
42
|
+
Alle Befehle unterstützen `--dir PATH`: überschreibt den Pfad nur für diesen Aufruf.
|
|
43
|
+
Eine erfolgreiche neue Installation speichert ihr Zielverzeichnis als neuen Default.
|
|
44
|
+
Bestehende Instanzen lassen sich weiterhin mit `--dir` verwenden; alternativ ihren absoluten
|
|
45
|
+
Pfad als einzige Zeile in die Konfigurationsdatei eintragen.
|
|
46
|
+
`install`/`update` fragen interaktiv (Auswahl über `rich.prompt`),
|
|
47
|
+
`update`/`rollback` haben `--yes` zum Überspringen der Rückfrage.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
katalon install --dir ~/meine-katalon-instanz
|
|
51
|
+
katalon start
|
|
52
|
+
katalon status
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API-Adresse
|
|
56
|
+
|
|
57
|
+
Die REST-API liegt direkt unter der Base-URL, ohne `/api`-Präfix: `<KATALON_BASE_URL>/v1/...`
|
|
58
|
+
(z. B. Login unter `http://127.0.0.1:8080/v1/...` bei `behind-proxy`). Unter `/api/` liegen nur
|
|
59
|
+
die OpenAPI-Dokumentation (`/api/docs`, `/api/openapi.json`) und interne Endpunkte;
|
|
60
|
+
`/api/v1/...` liefert für normale API-Aufrufe 404.
|
|
61
|
+
|
|
62
|
+
## Medienverzeichnis und Rechte
|
|
63
|
+
|
|
64
|
+
API und Worker laufen im Container als unprivilegierter User (`USER` im Image). `MEDIA_ROOT`
|
|
65
|
+
ist ein Bind-Mount vom Host und muss für diesen User beschreibbar sein. `katalon start`
|
|
66
|
+
(auch beim Install), `update` und `rollback` lesen UID/GID per `id` aus dem api-Container und
|
|
67
|
+
setzen in einem kurzlebigen root-Container desselben Images den Eigentümer aller Einträge
|
|
68
|
+
unter `MEDIA_ROOT`, die noch einem anderen Eigentümer gehören. Dateiinhalte werden nicht
|
|
69
|
+
verändert; ein erneuter Lauf ändert nichts. `katalon doctor` meldet ein nicht beschreibbares
|
|
70
|
+
`MEDIA_ROOT`, bevor Uploads mit HTTP 500 scheitern.
|
|
71
|
+
|
|
72
|
+
## Bild-Cache (Cantaloupe)
|
|
73
|
+
|
|
74
|
+
Vorschaubilder und Viewer-Kacheln rechnet Cantaloupe aus den Originalen und legt sie im
|
|
75
|
+
Instanzverzeichnis unter `cantaloupe-cache/` ab (abweichend: `CANTALOUPE_CACHE_DIR` in `.env`).
|
|
76
|
+
Der Cache übersteht Neustarts und Updates; ein kurzlebiger Init-Container übergibt das
|
|
77
|
+
Verzeichnis vor jedem Start dem Cantaloupe-User. Abgelaufene Derivate
|
|
78
|
+
(`CANTALOUPE_CACHE_SERVER_DERIVATIVE_TTL_SECONDS`, Standard 30 Tage) löscht Cantaloupe selbst.
|
|
79
|
+
|
|
80
|
+
Eine Größenbegrenzung kennt Cantaloupe nicht. Als Richtwert ~25 KB pro Vorschaubild plus
|
|
81
|
+
Kacheln der aufgerufenen Detailansichten einplanen; wer den Platz hart begrenzen muss, legt
|
|
82
|
+
`CANTALOUPE_CACHE_DIR` auf ein eigenes Dateisystem bzw. Quota-Verzeichnis. Hosts ohne
|
|
83
|
+
Plattenplatz nutzen mit `CANTALOUPE_FILESYSTEMCACHE_PATHNAME=/tmpfs/cache` einen RAM-Cache
|
|
84
|
+
(`CANTALOUPE_CACHE_TMPFS_SIZE`, Standard `512m`), der nach jedem Neustart leer ist.
|
|
85
|
+
|
|
86
|
+
Vorschaubilder lassen sich vorab berechnen, damit schon der erste Aufruf der Trefferliste
|
|
87
|
+
schnell ist: `katalon media warm-thumbnails` (Hauptbilder zuerst, dann die übrigen; mit
|
|
88
|
+
`--primary-only` nur Hauptbilder, `--status` zeigt den Fortschritt). Der Lauf arbeitet im
|
|
89
|
+
Worker in kleinen Paketen, belegt höchstens `THUMBNAIL_WARM_CONCURRENCY` (Standard 3)
|
|
90
|
+
Cantaloupe-Kerne und setzt nach einer Unterbrechung beim letzten Stand fort. Neu hochgeladene
|
|
91
|
+
Bilder werden automatisch vorberechnet.
|
|
92
|
+
|
|
93
|
+
Bestehende Instanzen stellt `katalon update` automatisch um (neue `compose.yaml`); der bisherige
|
|
94
|
+
RAM-Cache entfällt dabei. Releases, die eine neuere CLI voraussetzen, bricht `katalon update`
|
|
95
|
+
mit einem Hinweis auf `uv tool upgrade katalon-cli` ab.
|
|
96
|
+
|
|
97
|
+
## Domain und HTTPS
|
|
98
|
+
|
|
99
|
+
Für eine lokale Installation bleibt `http://localhost` mit TLS-Modus `none` der Standard.
|
|
100
|
+
Bei einer öffentlichen Domain schlägt der Setup-Wizard `caddy` vor. Du kannst auch nur
|
|
101
|
+
`sammlung.example.org` eingeben; mit Caddy wird daraus `https://sammlung.example.org`.
|
|
102
|
+
|
|
103
|
+
Vor der Installation müssen die DNS-Einträge wirksam sein: Der A-Record zeigt auf die öffentliche
|
|
104
|
+
IPv4-Adresse des Servers. Ein vorhandener AAAA-Record muss auf dessen erreichbare IPv6-Adresse
|
|
105
|
+
zeigen. TCP-Ports 80 und 443 müssen frei und öffentlich erreichbar sein, auch durch Server- und
|
|
106
|
+
Provider-Firewalls sowie gegebenenfalls Portweiterleitungen. Docker und Docker Compose müssen
|
|
107
|
+
installiert sein. Die CLI prüft freie Ports lokal, aber keine öffentliche DNS-Auflösung oder
|
|
108
|
+
Erreichbarkeit von außen.
|
|
109
|
+
|
|
110
|
+
Die TLS-Modi:
|
|
111
|
+
|
|
112
|
+
- `caddy`: Caddy läuft als Docker-Container vor dem internen nginx, belegt Ports 80 und 443,
|
|
113
|
+
beantragt öffentlich vertrauenswürdige Zertifikate und erneuert sie automatisch.
|
|
114
|
+
HTTP wird auf HTTPS umgeleitet. Zertifikate und Zustand liegen dauerhaft in den Docker-Volumes
|
|
115
|
+
`caddy_data` und `caddy_config`, die bei Neustarts, Updates und Rollbacks erhalten bleiben.
|
|
116
|
+
Dafür muss Caddy weiterlaufen, DNS muss stimmen und der Server erreichbar bleiben.
|
|
117
|
+
- `standalone`: nginx verwendet ein selbstsigniertes Zertifikat. Browser zeigen eine Warnung;
|
|
118
|
+
es gibt keine automatische Erneuerung. Eigene Zertifikate können unter `<dir>/certs/`
|
|
119
|
+
als `fullchain.pem` und `privkey.pem` abgelegt werden. Ihre Erneuerung und das anschließende
|
|
120
|
+
Neuladen von nginx musst du selbst einrichten.
|
|
121
|
+
- `behind-proxy`: Ein eigener Reverse-Proxy übernimmt HTTPS und Zertifikate.
|
|
122
|
+
Katalon ist standardmäßig nur auf `127.0.0.1:8080` erreichbar.
|
|
123
|
+
- `none`: HTTP ohne TLS, für lokale Tests oder IP-Adressen.
|
|
124
|
+
|
|
125
|
+
Die erste Zertifikatsausstellung kann nach dem Start kurz dauern. Bei HTTPS-Problemen:
|
|
126
|
+
`katalon logs caddy`. Caddy-Konfiguration und Zertifikatsverwaltung sind in der
|
|
127
|
+
[Caddy-Dokumentation](https://caddyserver.com/docs/automatic-https) beschrieben.
|
|
128
|
+
Die CLI erzeugt `<dir>/Caddyfile` bei Installation, Update und Rollback neu;
|
|
129
|
+
instanzspezifische Compose-Anpassungen gehören in `compose.override.yaml`.
|
|
130
|
+
|
|
131
|
+
## Entwicklung
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv sync
|
|
135
|
+
uv run katalon --help
|
|
136
|
+
uv run pytest
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Architektur
|
|
140
|
+
|
|
141
|
+
- `core/state.py` — `installation.json`, Single Source of Truth für laufende Version.
|
|
142
|
+
- `core/release.py` — GitHub-Release-Metadaten (`katalon-release.json` Asset). Enthält
|
|
143
|
+
optional `env_vars`/`deprecated_env_vars`: `install`/`update` ergänzen neue Pflichtvariablen
|
|
144
|
+
automatisch (Secret generieren oder Default übernehmen), fragen bei fehlendem Default interaktiv
|
|
145
|
+
nach (`--yes` bricht dann ab), tragen neue optionale Variablen auskommentiert in `.env` ein und
|
|
146
|
+
warnen vor veralteten Variablen, die noch in der `.env` stehen.
|
|
147
|
+
- `core/compose_gen.py` + `templates/compose.yaml.j2` + `templates/nginx.conf.j2` — rendert `compose.yaml`
|
|
148
|
+
+ `nginx.conf` und bei `tls_mode=caddy` das `Caddyfile` aus Version + TLS-Modus;
|
|
149
|
+
erzeugt bei `tls_mode=standalone` ein selbstsigniertes
|
|
150
|
+
Zertifikat unter `<dir>/certs/` (eigenes Zertifikat dort ablegen, um es zu ersetzen).
|
|
151
|
+
`install` fragt zusätzlich `MEDIA_ROOT` ab und legt `<dir>/compose.override.yaml.example` an
|
|
152
|
+
(instanzspezifische Overrides — umbenennen zu `compose.override.yaml`, wird automatisch eingebunden
|
|
153
|
+
und bei Updates nicht angefasst). Optionale Env-Vars (Admin-Login, SMTP, Geonames, …) landet
|
|
154
|
+
auskommentiert in `.env`.
|
|
155
|
+
- `core/backup.py` — `pg_dump` vor jedem Update; Rollback restauriert Dump statt `alembic downgrade`.
|
|
156
|
+
- `core/docker.py` — dünner `docker compose`-Subprocess-Wrapper.
|
|
157
|
+
- `main.py` — Typer-Commands, interaktive Teile über `rich.prompt`/`rich.progress`.
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
import subprocess
|
|
7
|
+
from ipaddress import ip_address
|
|
6
8
|
from pathlib import Path
|
|
9
|
+
from urllib.parse import urlparse
|
|
7
10
|
|
|
8
11
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
9
12
|
|
|
@@ -22,8 +25,8 @@ def render_compose(
|
|
|
22
25
|
tls_mode: str,
|
|
23
26
|
registry: str = "ghcr.io/katalon-collections/katalon",
|
|
24
27
|
) -> str:
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
if tls_mode == "caddy":
|
|
29
|
+
caddy_host(base_url)
|
|
27
30
|
parsed_port = urlparse(base_url).port
|
|
28
31
|
template = _env.get_template("compose.yaml.j2")
|
|
29
32
|
return template.render(
|
|
@@ -47,6 +50,10 @@ def write_compose(instance_dir: Path, **kwargs) -> Path:
|
|
|
47
50
|
nginx_path = instance_dir / "nginx.conf"
|
|
48
51
|
nginx_path.write_text(render_nginx_conf(tls_mode=kwargs["tls_mode"]))
|
|
49
52
|
|
|
53
|
+
if kwargs["tls_mode"] == "caddy":
|
|
54
|
+
template = _env.get_template("Caddyfile.j2")
|
|
55
|
+
(instance_dir / "Caddyfile").write_text(template.render(host=caddy_host(kwargs["base_url"])))
|
|
56
|
+
|
|
50
57
|
if kwargs["tls_mode"] == "standalone":
|
|
51
58
|
ensure_self_signed_cert(instance_dir, host=_host_of(kwargs["base_url"]))
|
|
52
59
|
|
|
@@ -79,7 +86,27 @@ def ensure_self_signed_cert(instance_dir: Path, *, host: str) -> None:
|
|
|
79
86
|
)
|
|
80
87
|
|
|
81
88
|
|
|
82
|
-
def
|
|
83
|
-
|
|
89
|
+
def caddy_host(base_url: str) -> str:
|
|
90
|
+
"""Validiert die öffentliche HTTPS-Adresse vor dem Rendern der Caddy-Konfiguration."""
|
|
91
|
+
parsed = urlparse(base_url)
|
|
92
|
+
host = (parsed.hostname or "").encode("idna").decode("ascii")
|
|
93
|
+
try:
|
|
94
|
+
ip_address(host)
|
|
95
|
+
is_ip = True
|
|
96
|
+
except ValueError:
|
|
97
|
+
is_ip = False
|
|
98
|
+
label = r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?"
|
|
99
|
+
if (
|
|
100
|
+
parsed.scheme != "https" or parsed.port not in (None, 443)
|
|
101
|
+
or parsed.username is not None or parsed.password is not None
|
|
102
|
+
or parsed.path not in ("", "/") or parsed.query or parsed.fragment
|
|
103
|
+
or is_ip or len(host) > 253
|
|
104
|
+
or not re.fullmatch(rf"{label}(?:\.{label})+", host)
|
|
105
|
+
or host.endswith((".localhost", ".local"))
|
|
106
|
+
):
|
|
107
|
+
raise ValueError("Caddy benötigt eine öffentliche Domain als https://domain.tld ohne Pfad (Port 443).")
|
|
108
|
+
return host
|
|
84
109
|
|
|
110
|
+
|
|
111
|
+
def _host_of(base_url: str) -> str:
|
|
85
112
|
return urlparse(base_url).hostname or "localhost"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Schreibrechte für Bind-Mounts, in die API und Worker schreiben.
|
|
2
|
+
|
|
3
|
+
API- und Worker-Image laufen als unprivilegierter User (Dockerfile `USER`). Ein vom
|
|
4
|
+
Installer oder per Hand angelegtes Host-Verzeichnis gehört dagegen meist root. Die
|
|
5
|
+
UID/GID wird nicht im CLI hinterlegt, sondern zur Laufzeit aus dem Container gelesen
|
|
6
|
+
(`id` im api-Service, inkl. eventuellem `user:` aus compose.override.yaml).
|
|
7
|
+
|
|
8
|
+
Die Korrektur läuft als root in einem Wegwerf-Container desselben Images — dadurch
|
|
9
|
+
funktioniert sie auch, wenn die CLI selbst nicht als root läuft. Sie ändert nur Eigentümer
|
|
10
|
+
von Einträgen mit falschem Eigentümer, nie Inhalte, und ist damit idempotent.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from . import docker
|
|
20
|
+
|
|
21
|
+
# Container-Pfad des MEDIA_ROOT-Bind-Mounts (compose.yaml.j2, api + worker).
|
|
22
|
+
CONTAINER_MEDIA_ROOT = "/var/lib/katalon/media"
|
|
23
|
+
# Services, die in CONTAINER_MEDIA_ROOT schreiben.
|
|
24
|
+
WRITER_SERVICES = ("api", "worker")
|
|
25
|
+
# Service, dessen Image-User als Eigentümer gesetzt wird.
|
|
26
|
+
OWNER_SERVICE = "api"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PermissionFixError(RuntimeError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ContainerUser:
|
|
35
|
+
uid: int
|
|
36
|
+
gid: int
|
|
37
|
+
name: str = ""
|
|
38
|
+
|
|
39
|
+
def __str__(self) -> str:
|
|
40
|
+
label = f"{self.uid}:{self.gid}"
|
|
41
|
+
return f"{label} ({self.name})" if self.name else label
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_ID_RE = re.compile(r"uid=(\d+)(?:\(([^)]*)\))?\s+gid=(\d+)")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_id_output(output: str) -> ContainerUser:
|
|
48
|
+
match = _ID_RE.search(output)
|
|
49
|
+
if not match:
|
|
50
|
+
raise PermissionFixError(f"Unerwartete Ausgabe von `id`: {output.strip()!r}")
|
|
51
|
+
return ContainerUser(uid=int(match.group(1)), gid=int(match.group(3)), name=match.group(2) or "")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _run(instance_dir: Path, service: str, *args: str, root: bool = False):
|
|
55
|
+
user = ["--user", "0:0"] if root else []
|
|
56
|
+
return docker.compose(
|
|
57
|
+
instance_dir,
|
|
58
|
+
"run", "--rm", "--no-deps", "-T", *user, "--entrypoint", args[0], service, *args[1:],
|
|
59
|
+
check=False,
|
|
60
|
+
capture=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def container_user(instance_dir: Path, service: str = OWNER_SERVICE) -> ContainerUser:
|
|
65
|
+
"""UID/GID, unter der `service` tatsächlich läuft (Image-USER bzw. compose `user:`)."""
|
|
66
|
+
result = _run(instance_dir, service, "id")
|
|
67
|
+
if result.returncode != 0:
|
|
68
|
+
raise PermissionFixError(
|
|
69
|
+
f"UID des {service}-Containers nicht ermittelbar: {(result.stderr or result.stdout).strip()}"
|
|
70
|
+
)
|
|
71
|
+
return parse_id_output(result.stdout)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def chown_args(path: str, user: ContainerUser) -> list[str]:
|
|
75
|
+
"""`find`-Aufruf, der nur Einträge mit falschem Eigentümer umstellt (Inhalte bleiben unberührt)."""
|
|
76
|
+
owner = f"{user.uid}:{user.gid}"
|
|
77
|
+
return [
|
|
78
|
+
"find", path, "-xdev",
|
|
79
|
+
"(", "!", "-user", str(user.uid), "-o", "!", "-group", str(user.gid), ")",
|
|
80
|
+
"-exec", "chown", "-h", owner, "{}", "+",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def is_writable(instance_dir: Path, service: str) -> bool:
|
|
85
|
+
result = _run(instance_dir, service, "test", "-w", CONTAINER_MEDIA_ROOT)
|
|
86
|
+
return result.returncode == 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def unwritable_services(instance_dir: Path) -> list[str]:
|
|
90
|
+
return [service for service in WRITER_SERVICES if not is_writable(instance_dir, service)]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def fix_media_permissions(instance_dir: Path) -> ContainerUser:
|
|
94
|
+
"""Setzt den Eigentümer von MEDIA_ROOT auf den Container-User und prüft danach die Schreibrechte."""
|
|
95
|
+
user = container_user(instance_dir)
|
|
96
|
+
result = _run(instance_dir, OWNER_SERVICE, *chown_args(CONTAINER_MEDIA_ROOT, user), root=True)
|
|
97
|
+
if result.returncode != 0:
|
|
98
|
+
raise PermissionFixError(
|
|
99
|
+
f"Eigentümer von MEDIA_ROOT konnte nicht auf {user} gesetzt werden: "
|
|
100
|
+
f"{(result.stderr or result.stdout).strip()}"
|
|
101
|
+
)
|
|
102
|
+
failing = unwritable_services(instance_dir)
|
|
103
|
+
if failing:
|
|
104
|
+
raise PermissionFixError(
|
|
105
|
+
f"MEDIA_ROOT ist nach der Korrektur für {', '.join(failing)} nicht beschreibbar "
|
|
106
|
+
f"(Eigentümer jetzt {user})."
|
|
107
|
+
)
|
|
108
|
+
return user
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def manual_fix_hint(media_root: str, user: ContainerUser | None) -> str:
|
|
112
|
+
owner = f"{user.uid}:{user.gid}" if user else "<UID>:<GID>"
|
|
113
|
+
return f"sudo chown -R {owner} {media_root}"
|
|
@@ -24,7 +24,7 @@ class InstallationState(BaseModel):
|
|
|
24
24
|
version: str
|
|
25
25
|
compose_revision: int
|
|
26
26
|
base_url: str
|
|
27
|
-
tls_mode: str # "standalone" | "behind-proxy" | "none"
|
|
27
|
+
tls_mode: str # "caddy" | "standalone" | "behind-proxy" | "none"
|
|
28
28
|
installed_at: datetime
|
|
29
29
|
history: list[HistoryEntry] = Field(default_factory=list)
|
|
30
30
|
|
|
@@ -28,9 +28,9 @@ from rich.prompt import Confirm, Prompt
|
|
|
28
28
|
from rich.table import Table
|
|
29
29
|
|
|
30
30
|
from .core import backup as backup_mod
|
|
31
|
-
from .core import checks, docker, release, selfupdate
|
|
31
|
+
from .core import checks, docker, permissions, release, selfupdate
|
|
32
32
|
from .core import reindex as reindex_mod
|
|
33
|
-
from .core.compose_gen import write_compose
|
|
33
|
+
from .core.compose_gen import caddy_host, write_compose
|
|
34
34
|
from .core.paths import default_instance_dir, save_instance_dir
|
|
35
35
|
from .core.state import InstallationState, instance_dir_or_raise
|
|
36
36
|
|
|
@@ -57,8 +57,9 @@ def _main(
|
|
|
57
57
|
pass
|
|
58
58
|
|
|
59
59
|
TLS_CHOICES = {
|
|
60
|
-
"
|
|
61
|
-
"
|
|
60
|
+
"caddy": "Caddy – automatische HTTPS-Zertifikate und Erneuerung (Ports 80+443)",
|
|
61
|
+
"standalone": "Selbstsigniert – eigenes nginx, Browserwarnung, keine automatische Erneuerung (Ports 80+443)",
|
|
62
|
+
"behind-proxy": "Hinter eigenem Reverse-Proxy – Zertifikate dort verwalten",
|
|
62
63
|
"none": "Kein TLS (lokal / IP)",
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -247,6 +248,10 @@ def _run_checks(dir: Path, ports: list[int] | None = None, need_openssl: bool =
|
|
|
247
248
|
def _resolve_ports_and_base_url(
|
|
248
249
|
base_url: str, tls_mode: str, is_localhost: bool
|
|
249
250
|
) -> tuple[str, list[int]]:
|
|
251
|
+
if tls_mode == "caddy":
|
|
252
|
+
base_url = urlparse(base_url)._replace(scheme="https").geturl()
|
|
253
|
+
caddy_host(base_url)
|
|
254
|
+
return base_url, [80, 443]
|
|
250
255
|
if tls_mode == "standalone":
|
|
251
256
|
return base_url, [80, 443]
|
|
252
257
|
|
|
@@ -308,16 +313,37 @@ def install(
|
|
|
308
313
|
"sonst echte Domain (dann folgt TLS-Auswahl)."
|
|
309
314
|
)
|
|
310
315
|
base_url = Prompt.ask("KATALON_BASE_URL", default="http://localhost")
|
|
316
|
+
if "://" not in base_url:
|
|
317
|
+
base_url = f"http://{base_url}"
|
|
311
318
|
|
|
312
319
|
console.print()
|
|
313
320
|
console.print("MEDIA_ROOT — Verzeichnis auf dem Host, in dem Katalon Mediendateien ablegt.")
|
|
314
321
|
media_root = Prompt.ask("MEDIA_ROOT", default=str(dir / "media"))
|
|
315
322
|
|
|
316
323
|
is_localhost = urlparse(base_url).hostname in (None, "localhost", "127.0.0.1")
|
|
324
|
+
try:
|
|
325
|
+
caddy_host(f"https://{urlparse(base_url).hostname or ''}")
|
|
326
|
+
is_domain = True
|
|
327
|
+
except ValueError:
|
|
328
|
+
is_domain = False
|
|
317
329
|
console.print()
|
|
318
|
-
|
|
319
|
-
console.print(
|
|
320
|
-
|
|
330
|
+
if is_domain:
|
|
331
|
+
console.print(
|
|
332
|
+
"Für öffentliches HTTPS: Der A-Record muss auf die öffentliche IPv4-Adresse dieses Servers zeigen. "
|
|
333
|
+
"Ein vorhandener AAAA-Record muss auf seine erreichbare IPv6-Adresse zeigen. "
|
|
334
|
+
"DNS-Änderungen müssen bereits wirksam sein."
|
|
335
|
+
)
|
|
336
|
+
console.print(
|
|
337
|
+
"Für Caddy müssen TCP-Ports 80 und 443 öffentlich erreichbar sein "
|
|
338
|
+
"(Server-/Provider-Firewall, ggf. Portweiterleitung). "
|
|
339
|
+
"Caddy beantragt und erneuert Zertifikate automatisch, solange es läuft und erreichbar bleibt. "
|
|
340
|
+
"Die CLI prüft freie Ports lokal; öffentliche DNS-Auflösung und Erreichbarkeit musst du sicherstellen."
|
|
341
|
+
)
|
|
342
|
+
choices = [key for key in TLS_CHOICES if key != "caddy" or is_domain]
|
|
343
|
+
for key in choices:
|
|
344
|
+
console.print(f" [cyan]{key}[/] – {TLS_CHOICES[key]}")
|
|
345
|
+
default_tls = "none" if is_localhost else ("caddy" if is_domain else "standalone")
|
|
346
|
+
tls_mode = Prompt.ask("TLS-Modus", choices=choices, default=default_tls)
|
|
321
347
|
|
|
322
348
|
console.print()
|
|
323
349
|
base_url, ports = _resolve_ports_and_base_url(base_url, tls_mode, is_localhost)
|
|
@@ -403,6 +429,7 @@ def _print_next_steps(dir: Path, base_url: str, show_credentials: bool = False)
|
|
|
403
429
|
console.print()
|
|
404
430
|
console.print(f"Portal: [bold]{base_url}/[/]")
|
|
405
431
|
console.print(f"Admin: [bold]{base_url}/admin/[/]")
|
|
432
|
+
console.print(f"API: [bold]{base_url}/v1/[/] (OpenAPI-Doku: {base_url}/api/docs)")
|
|
406
433
|
creds = _first_run_credentials(dir) if show_credentials else None
|
|
407
434
|
if not show_credentials:
|
|
408
435
|
console.print("Admin-Passwort vergessen? [bold]katalon manage reset-admin[/]")
|
|
@@ -423,10 +450,55 @@ def _print_next_steps(dir: Path, base_url: str, show_credentials: bool = False)
|
|
|
423
450
|
console.print("Docs: [bold]https://katalon-collections.github.io/katalon-docs/[/]")
|
|
424
451
|
|
|
425
452
|
|
|
453
|
+
def _media_root(dir: Path) -> str:
|
|
454
|
+
value = _env_value(dir, "MEDIA_ROOT") or "./media"
|
|
455
|
+
path = Path(value)
|
|
456
|
+
return str(path if path.is_absolute() else dir / path)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _require_installer_version(minimum: str) -> None:
|
|
460
|
+
"""Bricht ab, wenn das Release eine neuere CLI braucht (z. B. neues Compose-Template).
|
|
461
|
+
|
|
462
|
+
Sonst würde eine alte CLI ihr altes compose.yaml-Template schreiben und das Release
|
|
463
|
+
liefe ohne die Topologie, für die es gebaut ist."""
|
|
464
|
+
current = version("katalon-cli")
|
|
465
|
+
if selfupdate.is_newer(minimum, current):
|
|
466
|
+
console.print(
|
|
467
|
+
f"[red]✖ Dieses Release braucht katalon-cli ≥ {minimum} (installiert: {current}).[/]\n"
|
|
468
|
+
" Zuerst aktualisieren: [bold]uv tool upgrade katalon-cli[/], dann erneut "
|
|
469
|
+
"[bold]katalon update[/]."
|
|
470
|
+
)
|
|
471
|
+
raise typer.Exit(1)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _ensure_media_permissions(dir: Path) -> bool:
|
|
475
|
+
"""Macht MEDIA_ROOT für den Container-User von API/Worker beschreibbar (idempotent).
|
|
476
|
+
|
|
477
|
+
Fehler brechen nicht ab: Der Stack startet trotzdem, nur Uploads schlagen fehl."""
|
|
478
|
+
try:
|
|
479
|
+
user = permissions.fix_media_permissions(dir)
|
|
480
|
+
except permissions.PermissionFixError as exc:
|
|
481
|
+
console.print(f"[red]✖ MEDIA_ROOT {_media_root(dir)}: {exc}[/]")
|
|
482
|
+
console.print(
|
|
483
|
+
" Uploads schlagen sonst mit HTTP 500 fehl. Manuell beheben: "
|
|
484
|
+
f"[bold]{permissions.manual_fix_hint(_media_root(dir), None)}[/] "
|
|
485
|
+
"(UID/GID: `katalon doctor`)"
|
|
486
|
+
)
|
|
487
|
+
return False
|
|
488
|
+
console.print(f"[green]✔[/] MEDIA_ROOT beschreibbar für Container-User {user}.")
|
|
489
|
+
return True
|
|
490
|
+
|
|
491
|
+
|
|
426
492
|
def _start(dir: Path, show_credentials: bool = False) -> None:
|
|
493
|
+
_ensure_media_permissions(dir)
|
|
427
494
|
docker.compose(dir, "up", "-d")
|
|
428
495
|
console.print("[green]✔[/] Stack gestartet.")
|
|
429
496
|
state = InstallationState.load(dir)
|
|
497
|
+
if state.tls_mode == "caddy":
|
|
498
|
+
console.print(
|
|
499
|
+
"Caddy beantragt beim ersten Start das HTTPS-Zertifikat; das kann kurz dauern. "
|
|
500
|
+
"Bei HTTPS-Problemen: [bold]katalon logs caddy[/]."
|
|
501
|
+
)
|
|
430
502
|
_print_next_steps(dir, state.base_url, show_credentials=show_credentials)
|
|
431
503
|
|
|
432
504
|
|
|
@@ -502,6 +574,32 @@ def manage(
|
|
|
502
574
|
raise typer.Exit(result.returncode)
|
|
503
575
|
|
|
504
576
|
|
|
577
|
+
media_app = typer.Typer(no_args_is_help=True, help="Medien: Vorschaubilder vorberechnen.")
|
|
578
|
+
app.add_typer(media_app, name="media")
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
@media_app.command("warm-thumbnails")
|
|
582
|
+
def media_warm_thumbnails(
|
|
583
|
+
dir: Path | None = typer.Option(None, "--dir"),
|
|
584
|
+
primary_only: bool = typer.Option(False, "--primary-only", help="Nur das Hauptbild jedes Objekts."),
|
|
585
|
+
restart: bool = typer.Option(False, "--restart", help="Von vorn beginnen statt fortsetzen."),
|
|
586
|
+
status: bool = typer.Option(False, "--status", help="Nur den Fortschritt anzeigen."),
|
|
587
|
+
):
|
|
588
|
+
"""Vorschaubilder im Cantaloupe-Cache vorberechnen (läuft im Worker, fortsetzbar).
|
|
589
|
+
|
|
590
|
+
Sinnvoll nach Installation, Massenimport oder Umstellung des Bild-Caches."""
|
|
591
|
+
dir = instance_dir_or_raise(dir)
|
|
592
|
+
args = ["warm-thumbnails"]
|
|
593
|
+
if primary_only:
|
|
594
|
+
args.append("--primary-only")
|
|
595
|
+
if restart:
|
|
596
|
+
args.append("--restart")
|
|
597
|
+
if status:
|
|
598
|
+
args.append("--status")
|
|
599
|
+
result = docker.compose(dir, "exec", "api", "katalon-manage", *args, check=False)
|
|
600
|
+
raise typer.Exit(result.returncode)
|
|
601
|
+
|
|
602
|
+
|
|
505
603
|
def _ensure_db_running(dir: Path, yes: bool = False) -> None:
|
|
506
604
|
if docker.is_running(dir, "db"):
|
|
507
605
|
return
|
|
@@ -537,8 +635,28 @@ def doctor(dir: Path | None = typer.Option(None, "--dir")):
|
|
|
537
635
|
"[yellow]⚠ Hinweis: Port 443 ist belegt. Falls Browser http://localhost/admin/ "
|
|
538
636
|
"auf https:// umleiten, Base-URL auf z. B. http://localhost:8080 anpassen.[/]"
|
|
539
637
|
)
|
|
638
|
+
_check_media_writable(dir)
|
|
540
639
|
docker.compose(dir, "ps")
|
|
541
640
|
|
|
641
|
+
|
|
642
|
+
def _check_media_writable(dir: Path) -> bool:
|
|
643
|
+
media_root = _media_root(dir)
|
|
644
|
+
try:
|
|
645
|
+
user = permissions.container_user(dir)
|
|
646
|
+
failing = permissions.unwritable_services(dir)
|
|
647
|
+
except (permissions.PermissionFixError, docker.DockerError) as exc:
|
|
648
|
+
console.print(f"[yellow]⚠[/] MEDIA_ROOT: Prüfung nicht möglich — {exc}")
|
|
649
|
+
return False
|
|
650
|
+
if failing:
|
|
651
|
+
console.print(
|
|
652
|
+
f"[red]✖[/] MEDIA_ROOT: {media_root} ist für {', '.join(failing)} "
|
|
653
|
+
f"(Container-User {user}) nicht beschreibbar — Medien-Uploads scheitern mit HTTP 500. "
|
|
654
|
+
f"Beheben: [bold]katalon start[/] oder [bold]{permissions.manual_fix_hint(media_root, user)}[/]"
|
|
655
|
+
)
|
|
656
|
+
return False
|
|
657
|
+
console.print(f"[green]✔[/] MEDIA_ROOT: {media_root} beschreibbar für Container-User {user}")
|
|
658
|
+
return True
|
|
659
|
+
|
|
542
660
|
@app.command()
|
|
543
661
|
def check_updates(dir: Path | None = typer.Option(None, "--dir")):
|
|
544
662
|
"""Prüft, ob eine neue Katalon-Version verfügbar ist."""
|
|
@@ -553,6 +671,7 @@ def check_updates(dir: Path | None = typer.Option(None, "--dir")):
|
|
|
553
671
|
if meta.version == state.version:
|
|
554
672
|
console.print(f"[green]✔[/] Bereits auf aktueller Version {meta.version}.")
|
|
555
673
|
return
|
|
674
|
+
_require_installer_version(meta.minimum_installer_version)
|
|
556
675
|
console.print(f"Update verfügbar: [bold]{state.version}[/] → [bold]{meta.version}[/]")
|
|
557
676
|
|
|
558
677
|
|
|
@@ -620,6 +739,7 @@ def update(
|
|
|
620
739
|
lambda: write_compose(dir, version=meta.version, base_url=state.base_url, tls_mode=state.tls_mode),
|
|
621
740
|
),
|
|
622
741
|
("Images pullen", lambda: docker.compose(dir, "pull")),
|
|
742
|
+
("Medienrechte prüfen", lambda: _ensure_media_permissions(dir)),
|
|
623
743
|
# DB-Migration läuft automatisch im api-Container-Entrypoint beim Start.
|
|
624
744
|
("Container neu starten", lambda: docker.compose(dir, "up", "-d")),
|
|
625
745
|
]
|
|
@@ -703,6 +823,7 @@ def rollback(
|
|
|
703
823
|
write_compose(dir, version=previous.version, base_url=state.base_url, tls_mode=state.tls_mode)
|
|
704
824
|
docker.compose(dir, "pull")
|
|
705
825
|
|
|
826
|
+
_ensure_media_permissions(dir)
|
|
706
827
|
docker.compose(dir, "up", "-d")
|
|
707
828
|
console.print("[green]✔[/] Rollback abgeschlossen.")
|
|
708
829
|
|
|
@@ -39,15 +39,31 @@ services:
|
|
|
39
39
|
timeout: 10s
|
|
40
40
|
retries: 20
|
|
41
41
|
|
|
42
|
+
# Legt den Derivat-Cache an und übergibt ihn dem Cantaloupe-User des Images.
|
|
43
|
+
# Ändert nur Einträge mit falschem Eigentümer (idempotent, schnell bei vollem Cache).
|
|
44
|
+
cantaloupe-cache-init:
|
|
45
|
+
image: islandora/cantaloupe:main
|
|
46
|
+
user: "0:0"
|
|
47
|
+
entrypoint: ["sh", "-c", "mkdir -p /data/cache && find /data ! -user cantaloupe -exec chown cantaloupe:cantaloupe {} +"]
|
|
48
|
+
volumes:
|
|
49
|
+
- ${CANTALOUPE_CACHE_DIR:-./cantaloupe-cache}:/data
|
|
50
|
+
restart: "no"
|
|
51
|
+
|
|
42
52
|
cantaloupe:
|
|
43
53
|
image: islandora/cantaloupe:main
|
|
44
54
|
restart: unless-stopped
|
|
45
55
|
volumes:
|
|
46
56
|
- ${MEDIA_ROOT:-./media}:/var/lib/cantaloupe/images:ro
|
|
57
|
+
# Derivat-Cache auf Platte, übersteht Neustarts. Ohne Plattenplatz:
|
|
58
|
+
# CANTALOUPE_FILESYSTEMCACHE_PATHNAME=/tmpfs/cache in .env (RAM, leer nach Neustart).
|
|
59
|
+
- ${CANTALOUPE_CACHE_DIR:-./cantaloupe-cache}:/data
|
|
47
60
|
- type: tmpfs
|
|
48
|
-
target: /
|
|
61
|
+
target: /tmpfs
|
|
49
62
|
tmpfs:
|
|
50
63
|
size: ${CANTALOUPE_CACHE_TMPFS_SIZE:-512m}
|
|
64
|
+
mode: 1023 # = 0o1777 (Compose liest den Wert dezimal)
|
|
65
|
+
depends_on:
|
|
66
|
+
cantaloupe-cache-init: { condition: service_completed_successfully }
|
|
51
67
|
environment:
|
|
52
68
|
CANTALOUPE_SOURCE_STATIC: FilesystemSource
|
|
53
69
|
CANTALOUPE_FILESYSTEMSOURCE_LOOKUP_STRATEGY: BasicLookupStrategy
|
|
@@ -55,10 +71,19 @@ services:
|
|
|
55
71
|
CANTALOUPE_BASE_URI: ${CANTALOUPE_PUBLIC_URL:-http://localhost}
|
|
56
72
|
CANTALOUPE_CACHE_SERVER_DERIVATIVE: FilesystemCache
|
|
57
73
|
CANTALOUPE_CACHE_SERVER_DERIVATIVE_ENABLED: "true"
|
|
58
|
-
CANTALOUPE_FILESYSTEMCACHE_PATHNAME: /
|
|
74
|
+
CANTALOUPE_FILESYSTEMCACHE_PATHNAME: ${CANTALOUPE_FILESYSTEMCACHE_PATHNAME:-/data/cache}
|
|
75
|
+
# Der Cache-Worker löscht Derivate älter als die TTL; ohne ihn wächst der Cache unbegrenzt.
|
|
76
|
+
CANTALOUPE_CACHE_SERVER_WORKER_ENABLED: "true"
|
|
77
|
+
CANTALOUPE_CACHE_SERVER_DERIVATIVE_TTL_SECONDS: ${CANTALOUPE_CACHE_SERVER_DERIVATIVE_TTL_SECONDS:-2592000}
|
|
59
78
|
CANTALOUPE_PROCESSOR_MANUALSELECTIONSTRATEGY_JPG: Java2dProcessor
|
|
79
|
+
# Fallback ohne nginx davor: nie "public". nginx setzt Cache-Control für /iiif/
|
|
80
|
+
# nach Sichtbarkeit (X-Media-Visibility aus /v1/media/_authorize).
|
|
81
|
+
CANTALOUPE_CACHE_CLIENT_PUBLIC: "false"
|
|
82
|
+
CANTALOUPE_CACHE_CLIENT_PRIVATE: "true"
|
|
60
83
|
healthcheck:
|
|
61
|
-
|
|
84
|
+
# Prüft neben der Erreichbarkeit, dass der Cantaloupe-Prozess in seinen Cache
|
|
85
|
+
# schreiben kann — sonst liefert er Bilder mit 0 Bytes bei HTTP 200.
|
|
86
|
+
test: ["CMD-SHELL", "curl -sf http://localhost:8182/iiif/3 && runuser -u cantaloupe -- sh -c 'd=$${CANTALOUPE_FILESYSTEMCACHE_PATHNAME:-/data/cache}; mkdir -p \"$$d\" && touch \"$$d/.healthcheck\" && rm -f \"$$d/.healthcheck\"'"]
|
|
62
87
|
interval: 10s
|
|
63
88
|
timeout: 5s
|
|
64
89
|
start_period: 300s
|
|
@@ -159,6 +184,7 @@ services:
|
|
|
159
184
|
{% if tls_mode == "standalone" %}
|
|
160
185
|
- ./certs:/etc/nginx/certs:ro
|
|
161
186
|
{% endif %}
|
|
187
|
+
{% if tls_mode != "caddy" %}
|
|
162
188
|
ports:
|
|
163
189
|
{% if tls_mode == "standalone" %}
|
|
164
190
|
- "80:80"
|
|
@@ -167,12 +193,32 @@ services:
|
|
|
167
193
|
- "127.0.0.1:{{ port or 8080 }}:80"
|
|
168
194
|
{% else %}
|
|
169
195
|
- "{{ port or 80 }}:80"
|
|
196
|
+
{% endif %}
|
|
170
197
|
{% endif %}
|
|
171
198
|
depends_on:
|
|
172
199
|
api: { condition: service_healthy }
|
|
173
200
|
admin: { condition: service_healthy }
|
|
174
201
|
portal: { condition: service_healthy }
|
|
175
202
|
|
|
203
|
+
{% if tls_mode == "caddy" %}
|
|
204
|
+
caddy:
|
|
205
|
+
image: caddy:2-alpine
|
|
206
|
+
restart: unless-stopped
|
|
207
|
+
ports:
|
|
208
|
+
- "80:80"
|
|
209
|
+
- "443:443"
|
|
210
|
+
volumes:
|
|
211
|
+
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
212
|
+
- caddy_data:/data
|
|
213
|
+
- caddy_config:/config
|
|
214
|
+
depends_on:
|
|
215
|
+
nginx: { condition: service_started }
|
|
216
|
+
|
|
217
|
+
{% endif %}
|
|
176
218
|
volumes:
|
|
177
219
|
db_data:
|
|
178
220
|
es_data:
|
|
221
|
+
{% if tls_mode == "caddy" %}
|
|
222
|
+
caddy_data:
|
|
223
|
+
caddy_config:
|
|
224
|
+
{% endif %}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
resolver 127.0.0.11 valid=10s ipv6=off;
|
|
2
2
|
|
|
3
|
+
# Cache-Control for /iiif/ from the media's visibility (set by /v1/media/_authorize via
|
|
4
|
+
# auth_request_set): shared caches (CDN, proxies) may keep only what anonymous
|
|
5
|
+
# visitors may see; derivatives of internal media stay in the requesting browser.
|
|
6
|
+
map $media_visibility $iiif_cache_control {
|
|
7
|
+
public "public, max-age=2592000";
|
|
8
|
+
default "private, max-age=86400";
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
server {
|
|
4
12
|
{% if tls_mode == "standalone" %}
|
|
5
13
|
listen 80;
|
|
@@ -23,7 +31,7 @@ server {
|
|
|
23
31
|
proxy_set_header Host $host;
|
|
24
32
|
proxy_set_header X-Real-IP $remote_addr;
|
|
25
33
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
26
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
34
|
+
proxy_set_header X-Forwarded-Proto {{ '$http_x_forwarded_proto' if tls_mode == 'caddy' else '$scheme' }};
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
location /v1/ {
|
|
@@ -32,7 +40,7 @@ server {
|
|
|
32
40
|
proxy_set_header Host $host;
|
|
33
41
|
proxy_set_header X-Real-IP $remote_addr;
|
|
34
42
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
35
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
43
|
+
proxy_set_header X-Forwarded-Proto {{ '$http_x_forwarded_proto' if tls_mode == 'caddy' else '$scheme' }};
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
location /portal/v1/ {
|
|
@@ -41,7 +49,7 @@ server {
|
|
|
41
49
|
proxy_set_header Host $host;
|
|
42
50
|
proxy_set_header X-Real-IP $remote_addr;
|
|
43
51
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
44
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
52
|
+
proxy_set_header X-Forwarded-Proto {{ '$http_x_forwarded_proto' if tls_mode == 'caddy' else '$scheme' }};
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
location = /health {
|
|
@@ -50,7 +58,7 @@ server {
|
|
|
50
58
|
proxy_set_header Host $host;
|
|
51
59
|
proxy_set_header X-Real-IP $remote_addr;
|
|
52
60
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
53
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
61
|
+
proxy_set_header X-Forwarded-Proto {{ '$http_x_forwarded_proto' if tls_mode == 'caddy' else '$scheme' }};
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
location = /oai {
|
|
@@ -59,7 +67,7 @@ server {
|
|
|
59
67
|
proxy_set_header Host $host;
|
|
60
68
|
proxy_set_header X-Real-IP $remote_addr;
|
|
61
69
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
62
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
70
|
+
proxy_set_header X-Forwarded-Proto {{ '$http_x_forwarded_proto' if tls_mode == 'caddy' else '$scheme' }};
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
location ^~ /ark:/ {
|
|
@@ -68,17 +76,27 @@ server {
|
|
|
68
76
|
proxy_set_header Host $host;
|
|
69
77
|
proxy_set_header X-Real-IP $remote_addr;
|
|
70
78
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
71
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
79
|
+
proxy_set_header X-Forwarded-Proto {{ '$http_x_forwarded_proto' if tls_mode == 'caddy' else '$scheme' }};
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
location /iiif/ {
|
|
75
83
|
auth_request /internal/media-authorize;
|
|
84
|
+
auth_request_set $media_visibility $upstream_http_x_media_visibility;
|
|
76
85
|
set $upstream_cantaloupe cantaloupe:8182;
|
|
77
86
|
proxy_pass http://$upstream_cantaloupe;
|
|
78
87
|
proxy_set_header Host $host;
|
|
79
88
|
proxy_set_header X-Real-IP $remote_addr;
|
|
80
89
|
proxy_read_timeout 300s;
|
|
81
90
|
proxy_buffering off;
|
|
91
|
+
# Cantaloupe marks every derivative public; decide by visibility instead.
|
|
92
|
+
proxy_hide_header Cache-Control;
|
|
93
|
+
add_header Cache-Control $iiif_cache_control;
|
|
94
|
+
# An add_header here drops the server-level ones: repeat them.
|
|
95
|
+
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob: https:; media-src 'self' blob: https:; connect-src 'self' https:; frame-src 'self' https://www.openstreetmap.org; worker-src 'self' blob:" always;
|
|
96
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
97
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
98
|
+
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
99
|
+
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
location = /internal/media-authorize {
|
katalon_cli-0.1.20/PKG-INFO
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: katalon-cli
|
|
3
|
-
Version: 0.1.20
|
|
4
|
-
Summary: Installer & Updater CLI für Katalon Production-Instanzen
|
|
5
|
-
Author: Karl Krägelin
|
|
6
|
-
Author-email: Karl Krägelin <karlkraegelin@gmail.com>
|
|
7
|
-
Requires-Dist: typer>=0.15
|
|
8
|
-
Requires-Dist: rich>=13.9
|
|
9
|
-
Requires-Dist: httpx>=0.28
|
|
10
|
-
Requires-Dist: pydantic>=2.9
|
|
11
|
-
Requires-Dist: jinja2>=3.1
|
|
12
|
-
Requires-Python: >=3.12
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
|
|
15
|
-
# katalon-cli
|
|
16
|
-
|
|
17
|
-
Installer & Updater für [Katalon Collections](https://github.com/katalon-collections/katalon) Production-Instanzen.
|
|
18
|
-
Verwaltet eine Instanz unter einem frei gewählten Zielverzeichnis — pullt gepinnte
|
|
19
|
-
Release-Images, generiert `compose.yaml`, macht Backups vor jedem Update, kann rollbacken.
|
|
20
|
-
|
|
21
|
-
## Installation
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
25
|
-
uv tool install katalon-cli
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Nutzung
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
katalon install # interaktiver Setup-Wizard (Rich-Prompts + Progress)
|
|
32
|
-
katalon start / stop / status
|
|
33
|
-
katalon check-updates # prüft nur, ob ein neues Release verfügbar ist
|
|
34
|
-
katalon update # interaktives Update, Backup zuerst immer
|
|
35
|
-
katalon rollback # letztes Backup einspielen
|
|
36
|
-
katalon doctor # Docker, Diskspace, Ports prüfen
|
|
37
|
-
katalon backup # manuelles Backup
|
|
38
|
-
katalon logs [service]
|
|
39
|
-
katalon manage <args> # katalon-manage im api-Container (z.B. reset-admin, create-user)
|
|
40
|
-
katalon restore DUMP # DB durch pg_dump ersetzen (-Fc oder SQL), danach Reindex
|
|
41
|
-
katalon reindex # Suchindex neu aufbauen, mit Fortschrittsanzeige
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Ein DB-Dump enthält nicht den Suchindex (Elasticsearch). `restore` baut ihn deshalb nach dem
|
|
45
|
-
Einspielen neu auf (`--no-reindex` zum Überspringen, später `katalon reindex`). Bei großen
|
|
46
|
-
Beständen dauert das lange (ca. 45 Min. für 80k Datensätze). Außerdem prüft `katalon` einmal
|
|
47
|
-
täglich auf neue Versionen (abschalten: `KATALON_NO_UPDATE_CHECK=1`).
|
|
48
|
-
|
|
49
|
-
`install` ohne `--dir` fragt das Zielverzeichnis interaktiv ab. Nach erfolgreicher Einrichtung
|
|
50
|
-
wird der absolute Pfad benutzerweit in `~/.config/katalon/instance` gespeichert.
|
|
51
|
-
Bei gesetztem, absolutem `XDG_CONFIG_HOME` liegt die Datei stattdessen unter
|
|
52
|
-
`$XDG_CONFIG_HOME/katalon/instance`. Das Konfigurationsverzeichnis wird bei Bedarf angelegt.
|
|
53
|
-
Alle weiteren Befehle verwenden den gespeicherten Pfad, unabhängig vom Arbeitsverzeichnis.
|
|
54
|
-
Ohne gespeicherten Pfad ist der Default auf allen Plattformen `~/katalon`.
|
|
55
|
-
|
|
56
|
-
Alle Befehle unterstützen `--dir PATH`: überschreibt den Pfad nur für diesen Aufruf.
|
|
57
|
-
Eine erfolgreiche neue Installation speichert ihr Zielverzeichnis als neuen Default.
|
|
58
|
-
Bestehende Instanzen lassen sich weiterhin mit `--dir` verwenden; alternativ ihren absoluten
|
|
59
|
-
Pfad als einzige Zeile in die Konfigurationsdatei eintragen.
|
|
60
|
-
`install`/`update` fragen interaktiv (Auswahl über `rich.prompt`),
|
|
61
|
-
`update`/`rollback` haben `--yes` zum Überspringen der Rückfrage.
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
katalon install --dir ~/meine-katalon-instanz
|
|
65
|
-
katalon start
|
|
66
|
-
katalon status
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Entwicklung
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
uv sync
|
|
73
|
-
uv run katalon --help
|
|
74
|
-
uv run pytest
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Architektur
|
|
78
|
-
|
|
79
|
-
- `core/state.py` — `installation.json`, Single Source of Truth für laufende Version.
|
|
80
|
-
- `core/release.py` — GitHub-Release-Metadaten (`katalon-release.json` Asset). Enthält
|
|
81
|
-
optional `env_vars`/`deprecated_env_vars`: `install`/`update` ergänzen neue Pflichtvariablen
|
|
82
|
-
automatisch (Secret generieren oder Default übernehmen), fragen bei fehlendem Default interaktiv
|
|
83
|
-
nach (`--yes` bricht dann ab), tragen neue optionale Variablen auskommentiert in `.env` ein und
|
|
84
|
-
warnen vor veralteten Variablen, die noch in der `.env` stehen.
|
|
85
|
-
- `core/compose_gen.py` + `templates/compose.yaml.j2` + `templates/nginx.conf.j2` — rendert `compose.yaml`
|
|
86
|
-
+ `nginx.conf` aus Version + TLS-Modus; erzeugt bei `tls_mode=standalone` ein selbstsigniertes
|
|
87
|
-
Zertifikat unter `<dir>/certs/` (eigenes Zertifikat dort ablegen, um es zu ersetzen).
|
|
88
|
-
`install` fragt zusätzlich `MEDIA_ROOT` ab und legt `<dir>/compose.override.yaml.example` an
|
|
89
|
-
(instanzspezifische Overrides — umbenennen zu `compose.override.yaml`, wird automatisch eingebunden
|
|
90
|
-
und bei Updates nicht angefasst). Optionale Env-Vars (Admin-Login, SMTP, Geonames, …) landet
|
|
91
|
-
auskommentiert in `.env`.
|
|
92
|
-
- `core/backup.py` — `pg_dump` vor jedem Update; Rollback restauriert Dump statt `alembic downgrade`.
|
|
93
|
-
- `core/docker.py` — dünner `docker compose`-Subprocess-Wrapper.
|
|
94
|
-
- `main.py` — Typer-Commands, interaktive Teile über `rich.prompt`/`rich.progress`.
|
katalon_cli-0.1.20/README.md
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# katalon-cli
|
|
2
|
-
|
|
3
|
-
Installer & Updater für [Katalon Collections](https://github.com/katalon-collections/katalon) Production-Instanzen.
|
|
4
|
-
Verwaltet eine Instanz unter einem frei gewählten Zielverzeichnis — pullt gepinnte
|
|
5
|
-
Release-Images, generiert `compose.yaml`, macht Backups vor jedem Update, kann rollbacken.
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
11
|
-
uv tool install katalon-cli
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Nutzung
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
katalon install # interaktiver Setup-Wizard (Rich-Prompts + Progress)
|
|
18
|
-
katalon start / stop / status
|
|
19
|
-
katalon check-updates # prüft nur, ob ein neues Release verfügbar ist
|
|
20
|
-
katalon update # interaktives Update, Backup zuerst immer
|
|
21
|
-
katalon rollback # letztes Backup einspielen
|
|
22
|
-
katalon doctor # Docker, Diskspace, Ports prüfen
|
|
23
|
-
katalon backup # manuelles Backup
|
|
24
|
-
katalon logs [service]
|
|
25
|
-
katalon manage <args> # katalon-manage im api-Container (z.B. reset-admin, create-user)
|
|
26
|
-
katalon restore DUMP # DB durch pg_dump ersetzen (-Fc oder SQL), danach Reindex
|
|
27
|
-
katalon reindex # Suchindex neu aufbauen, mit Fortschrittsanzeige
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Ein DB-Dump enthält nicht den Suchindex (Elasticsearch). `restore` baut ihn deshalb nach dem
|
|
31
|
-
Einspielen neu auf (`--no-reindex` zum Überspringen, später `katalon reindex`). Bei großen
|
|
32
|
-
Beständen dauert das lange (ca. 45 Min. für 80k Datensätze). Außerdem prüft `katalon` einmal
|
|
33
|
-
täglich auf neue Versionen (abschalten: `KATALON_NO_UPDATE_CHECK=1`).
|
|
34
|
-
|
|
35
|
-
`install` ohne `--dir` fragt das Zielverzeichnis interaktiv ab. Nach erfolgreicher Einrichtung
|
|
36
|
-
wird der absolute Pfad benutzerweit in `~/.config/katalon/instance` gespeichert.
|
|
37
|
-
Bei gesetztem, absolutem `XDG_CONFIG_HOME` liegt die Datei stattdessen unter
|
|
38
|
-
`$XDG_CONFIG_HOME/katalon/instance`. Das Konfigurationsverzeichnis wird bei Bedarf angelegt.
|
|
39
|
-
Alle weiteren Befehle verwenden den gespeicherten Pfad, unabhängig vom Arbeitsverzeichnis.
|
|
40
|
-
Ohne gespeicherten Pfad ist der Default auf allen Plattformen `~/katalon`.
|
|
41
|
-
|
|
42
|
-
Alle Befehle unterstützen `--dir PATH`: überschreibt den Pfad nur für diesen Aufruf.
|
|
43
|
-
Eine erfolgreiche neue Installation speichert ihr Zielverzeichnis als neuen Default.
|
|
44
|
-
Bestehende Instanzen lassen sich weiterhin mit `--dir` verwenden; alternativ ihren absoluten
|
|
45
|
-
Pfad als einzige Zeile in die Konfigurationsdatei eintragen.
|
|
46
|
-
`install`/`update` fragen interaktiv (Auswahl über `rich.prompt`),
|
|
47
|
-
`update`/`rollback` haben `--yes` zum Überspringen der Rückfrage.
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
katalon install --dir ~/meine-katalon-instanz
|
|
51
|
-
katalon start
|
|
52
|
-
katalon status
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Entwicklung
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
uv sync
|
|
59
|
-
uv run katalon --help
|
|
60
|
-
uv run pytest
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Architektur
|
|
64
|
-
|
|
65
|
-
- `core/state.py` — `installation.json`, Single Source of Truth für laufende Version.
|
|
66
|
-
- `core/release.py` — GitHub-Release-Metadaten (`katalon-release.json` Asset). Enthält
|
|
67
|
-
optional `env_vars`/`deprecated_env_vars`: `install`/`update` ergänzen neue Pflichtvariablen
|
|
68
|
-
automatisch (Secret generieren oder Default übernehmen), fragen bei fehlendem Default interaktiv
|
|
69
|
-
nach (`--yes` bricht dann ab), tragen neue optionale Variablen auskommentiert in `.env` ein und
|
|
70
|
-
warnen vor veralteten Variablen, die noch in der `.env` stehen.
|
|
71
|
-
- `core/compose_gen.py` + `templates/compose.yaml.j2` + `templates/nginx.conf.j2` — rendert `compose.yaml`
|
|
72
|
-
+ `nginx.conf` aus Version + TLS-Modus; erzeugt bei `tls_mode=standalone` ein selbstsigniertes
|
|
73
|
-
Zertifikat unter `<dir>/certs/` (eigenes Zertifikat dort ablegen, um es zu ersetzen).
|
|
74
|
-
`install` fragt zusätzlich `MEDIA_ROOT` ab und legt `<dir>/compose.override.yaml.example` an
|
|
75
|
-
(instanzspezifische Overrides — umbenennen zu `compose.override.yaml`, wird automatisch eingebunden
|
|
76
|
-
und bei Updates nicht angefasst). Optionale Env-Vars (Admin-Login, SMTP, Geonames, …) landet
|
|
77
|
-
auskommentiert in `.env`.
|
|
78
|
-
- `core/backup.py` — `pg_dump` vor jedem Update; Rollback restauriert Dump statt `alembic downgrade`.
|
|
79
|
-
- `core/docker.py` — dünner `docker compose`-Subprocess-Wrapper.
|
|
80
|
-
- `main.py` — Typer-Commands, interaktive Teile über `rich.prompt`/`rich.progress`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|