Ausgabe fuer Sub-Lizenzserver: kein Weg zur Rolle master
This commit is contained in:
1 parent
fd333f60ff
commit
157a244900
9 files changed
+548
-124
No files matched your search
@@ -4,6 +4,13 @@ Ausgabe und Verwaltung der Lizenzen für TESM: Kunde -> Ticket -> Lizenz,
|
|||||||
Aktivierung online und offline, Kundenportal, Selbstbedienung, Versand über
|
Aktivierung online und offline, Kundenportal, Selbstbedienung, Versand über
|
||||||
MS Graph und eine Hierarchie aus Unter-Lizenzservern.
|
MS Graph und eine Hierarchie aus Unter-Lizenzservern.
|
||||||
|
|
||||||
|
> **Diese Ausgabe ist für Sub-Lizenzserver.** Ein Sub-Lizenzserver bekommt
|
||||||
|
> seine eigene Lizenz von der Wurzelinstanz und stellt danach selbst
|
||||||
|
> Kundenlizenzen aus. Was er nicht kann: sich selbst die Rolle `master` geben --
|
||||||
|
> der Code dafür liegt nicht in diesem Paket, sondern nur in der Master-Ausgabe
|
||||||
|
> (privates Repository `tesm-license-master`). Das ist keine Einstellung, die
|
||||||
|
> sich umlegen lässt, sondern ein fehlendes Verzeichnis.
|
||||||
|
|
||||||
**Dieses Repository enthält genau das, was zum Installieren und Betreiben des
|
**Dieses Repository enthält genau das, was zum Installieren und Betreiben des
|
||||||
Lizenzservers gebraucht wird** -- nichts darüber hinaus:
|
Lizenzservers gebraucht wird** -- nichts darüber hinaus:
|
||||||
|
|
||||||
@@ -21,6 +28,7 @@ Die verwaltete Anwendung liegt hier **nicht** mit drin.
|
|||||||
| Wo sonst noch | |
|
| Wo sonst noch | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [alientim/tesm](https://gitea.int.eertmoed.net/alientim/tesm) | TESM selbst |
|
| [alientim/tesm](https://gitea.int.eertmoed.net/alientim/tesm) | TESM selbst |
|
||||||
|
| [alientim/tesm-license-master](https://gitea.int.eertmoed.net/alientim/tesm-license-master) | Master-Ausgabe (privat): die Wurzelinstanz |
|
||||||
| [alientim/TESM-DEV](https://gitea.int.eertmoed.net/alientim/TESM-DEV) | Entwicklung: beide Anwendungen, Tests, Architekturdoku |
|
| [alientim/TESM-DEV](https://gitea.int.eertmoed.net/alientim/TESM-DEV) | Entwicklung: beide Anwendungen, Tests, Architekturdoku |
|
||||||
|
|
||||||
## Zweige
|
## Zweige
|
||||||
@@ -34,16 +42,24 @@ Die verwaltete Anwendung liegt hier **nicht** mit drin.
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Eine einzelne Datei genügt -- sie lädt das Release selbst:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo bash deploy/install.sh --app tesm-license --https --domain lizenz.firma.de
|
curl -fsSLO https://gitea.int.eertmoed.net/alientim/tesm-license/raw/branch/main/install.sh
|
||||||
sudo -u tesm-license tesm-license-admin init-key
|
sudo bash install.sh --app tesm-license --https --domain lizenz.firma.de
|
||||||
sudo -u tesm-license tesm-license-admin bootstrap
|
|
||||||
sudo -u tesm-license tesm-license-admin create-admin
|
sudo -u tesm-license tesm-license-admin create-admin
|
||||||
```
|
```
|
||||||
|
|
||||||
**`init-key` erzeugt den Signaturschlüssel dieses Servers. Sichern Sie ihn
|
Aus einem entpackten Paket ist es derselbe Aufruf ohne `curl`.
|
||||||
sofort.** Ohne ihn lässt sich keine ausgestellte Lizenz mehr erneuern, und
|
|
||||||
Kunden müssten einen Ausstellerwechsel bestätigen.
|
Den **Signaturschlüssel** legt der Installer selbst an -- mit ihm signiert
|
||||||
|
dieser Server seine Kundenlizenzen. **Sichern Sie ihn sofort**
|
||||||
|
(`data/master_signing_key.json`): ohne ihn ist keine ausgestellte Lizenz mehr
|
||||||
|
prüfbar, und Kunden müssten einen Ausstellerwechsel bestätigen. Eine
|
||||||
|
Aktualisierung fasst ihn nie an.
|
||||||
|
|
||||||
|
Die **eigene Lizenz** dieses Servers kommt von der Wurzelinstanz: dort eine
|
||||||
|
Lizenz für diesen Server anfordern und unter "Eigene Lizenz" einspielen.
|
||||||
|
|
||||||
Zielzustand: **Port 443 mit TLS, Port 80 leitet dorthin um** -- ausser dem
|
Zielzustand: **Port 443 mit TLS, Port 80 leitet dorthin um** -- ausser dem
|
||||||
ACME-Pfad, der erreichbar bleiben muss, sonst kann Let's Encrypt nie erneuern.
|
ACME-Pfad, der erreichbar bleiben muss, sonst kann Let's Encrypt nie erneuern.
|
||||||
@@ -57,7 +73,7 @@ für nginx-Site, logrotate, Zertifikate und Sitzungscookie.
|
|||||||
### Aktualisieren
|
### Aktualisieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo bash deploy/update.sh --app tesm-license
|
sudo bash /srv/tesm-license/deploy/update.sh tesm-license
|
||||||
```
|
```
|
||||||
|
|
||||||
Vor jedem Update entsteht ein vollständiges Backup, danach läuft ein
|
Vor jedem Update entsteht ein vollständiges Backup, danach läuft ein
|
||||||
@@ -70,7 +86,6 @@ Health-Check mit automatischem Rollback.
|
|||||||
```bash
|
```bash
|
||||||
sudo -u tesm-license tesm-license-admin status
|
sudo -u tesm-license tesm-license-admin status
|
||||||
sudo -u tesm-license tesm-license-admin init-key # Signaturschlüssel
|
sudo -u tesm-license tesm-license-admin init-key # Signaturschlüssel
|
||||||
sudo -u tesm-license tesm-license-admin bootstrap # Grunddaten
|
|
||||||
sudo -u tesm-license tesm-license-admin create-admin
|
sudo -u tesm-license tesm-license-admin create-admin
|
||||||
sudo -u tesm-license tesm-license-admin migrate-legacy # Altbestand übernehmen
|
sudo -u tesm-license tesm-license-admin migrate-legacy # Altbestand übernehmen
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ musste (und ``licensing.py`` byte-identisch gehalten werden *musste*).
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -51,6 +52,18 @@ ENV_PREFIX = "TESM_LICENSE_"
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def is_master_edition() -> bool:
|
||||||
|
"""Ist das hier die Master-Ausgabe?
|
||||||
|
|
||||||
|
Entscheidet die **Anwesenheit** des Teilpakets ``tesm_license.master``, nicht
|
||||||
|
eine Einstellung: eine Einstellung koennte der lokale Administrator eines
|
||||||
|
Sub-Lizenzservers umstellen, eine fehlende Datei nicht. Das Release fuer
|
||||||
|
Sub-Lizenzserver bringt das Teilpaket nicht mit -- dort gibt es also
|
||||||
|
ueberhaupt keinen Code, der die Rolle ``master`` erzeugen koennte.
|
||||||
|
"""
|
||||||
|
return importlib.util.find_spec("tesm_license.master") is not None
|
||||||
|
|
||||||
|
|
||||||
def master_key_path(config: Any) -> Path:
|
def master_key_path(config: Any) -> Path:
|
||||||
return Path(config.data_dir) / "master_signing_key.json"
|
return Path(config.data_dir) / "master_signing_key.json"
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from tesm_core.licensing_client import LICENSED_FEATURES, LicenseError
|
|||||||
|
|
||||||
import tesm_licensing as lic
|
import tesm_licensing as lic
|
||||||
|
|
||||||
|
from .. import is_master_edition
|
||||||
from ..services import master_key
|
from ..services import master_key
|
||||||
from .common import can_issue_license_server_licenses, log_event, signing_key
|
from .common import can_issue_license_server_licenses, log_event, signing_key
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ def overview():
|
|||||||
),
|
),
|
||||||
signing_key_health=master_key.health(extension.registry["master_key_path"]),
|
signing_key_health=master_key.health(extension.registry["master_key_path"]),
|
||||||
may_issue_license_server=can_issue_license_server_licenses(),
|
may_issue_license_server=can_issue_license_server_licenses(),
|
||||||
|
master_edition=is_master_edition(),
|
||||||
grace_days=lic.GRACE_PERIOD_DAYS,
|
grace_days=lic.GRACE_PERIOD_DAYS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
"""Kommandozeilenwerkzeuge des Lizenzservers.
|
"""Kommandozeilenwerkzeuge des Lizenzservers.
|
||||||
|
|
||||||
Enthaelt insbesondere ``bootstrap`` -- die eigene Lizenz der Wurzelinstanz.
|
``init-key`` erzeugt den Signaturschluessel dieser Installation -- den braucht
|
||||||
|
**jeder** Lizenzserver, um Kundenlizenzen zu signieren, auch ein
|
||||||
|
Sub-Lizenzserver.
|
||||||
|
|
||||||
Warum das ein lokales Skript ist und keine Funktion in der Oberflaeche: die
|
``bootstrap`` stellt die eigene Lizenz der Wurzelinstanz aus und ist damit der
|
||||||
Master-Lizenz ist die einzige Lizenz, fuer die es **keine hoehere Instanz gibt**,
|
einzige Weg, auf dem eine Lizenzdatei die Rolle ``master`` bekommt (ueber
|
||||||
die sie bestaetigen koennte. Ein Aktivierungs-Handshake gegen sich selbst waere
|
Tickets ist sie auf jedem Server abgewiesen). Die Arbeit dazu liegt im
|
||||||
Theater. Das Skript signiert die Datei mit dem eigenen Schluessel und setzt den
|
Teilpaket ``tesm_license.master``, das nur die Master-Ausgabe mitbringt; fehlt
|
||||||
Aktivierungszustand direkt -- und nur so kann eine Lizenzdatei je die Rolle
|
es, sagt der Befehl das und tut nichts. Siehe dort fuer die Begruendung.
|
||||||
``master`` tragen. Die Ticket-Oberflaeche bietet diese Rolle an keiner Stelle an.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -20,7 +21,6 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import tesm_licensing as lic
|
import tesm_licensing as lic
|
||||||
import tesm_licensing_server as licsrv
|
|
||||||
from tesm_core import audit
|
from tesm_core import audit
|
||||||
from tesm_core.cli_web import add_web_setup, run_web_setup
|
from tesm_core.cli_web import add_web_setup, run_web_setup
|
||||||
from tesm_core.db import Database
|
from tesm_core.db import Database
|
||||||
@@ -28,7 +28,7 @@ from tesm_core.extension import core
|
|||||||
from tesm_core.rbac import service as rbac
|
from tesm_core.rbac import service as rbac
|
||||||
from tesm_core.security import passwords
|
from tesm_core.security import passwords
|
||||||
|
|
||||||
from . import create_app, master_key_path
|
from . import create_app, is_master_edition, master_key_path
|
||||||
from .services import legacy_import, master_key
|
from .services import legacy_import, master_key
|
||||||
|
|
||||||
|
|
||||||
@@ -62,73 +62,29 @@ def cmd_init_key(args: argparse.Namespace) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def cmd_bootstrap(args: argparse.Namespace) -> int:
|
def cmd_bootstrap(args: argparse.Namespace) -> int:
|
||||||
"""Stellt die eigene Lizenz dieser Wurzelinstanz aus."""
|
"""Stellt die eigene Lizenz dieser Wurzelinstanz aus -- nur in der Master-Ausgabe."""
|
||||||
|
if not is_master_edition():
|
||||||
|
for zeile in (
|
||||||
|
"Diese Ausgabe ist fuer Sub-Lizenzserver und kann keine Master-Lizenz erzeugen.",
|
||||||
|
"",
|
||||||
|
"Ein Sub-Lizenzserver bekommt seine Lizenz vom Master-Lizenzserver und spielt",
|
||||||
|
"sie unter 'Eigene Lizenz' ein. Danach stellt er selbst Kundenlizenzen aus --",
|
||||||
|
"mit seinem eigenen Signaturschluessel, der beim Installieren entstanden ist.",
|
||||||
|
"",
|
||||||
|
"Die Master-Ausgabe liegt im Repository tesm-license-master.",
|
||||||
|
):
|
||||||
|
print(zeile, file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
from .master import bootstrap as eigene_lizenz_ausstellen
|
||||||
|
|
||||||
app, extension = _app_and_core()
|
app, extension = _app_and_core()
|
||||||
key = master_key.ensure(master_key_path(extension.config))
|
document, state = eigene_lizenz_ausstellen(
|
||||||
|
app, extension, days=int(args.days), name=args.name
|
||||||
with app.app_context(), extension.database.session() as conn:
|
)
|
||||||
vendor = {
|
|
||||||
"name": str(extension.settings.get(conn, "vendor_name") or "Eigenbetrieb"),
|
|
||||||
"email": str(extension.settings.get(conn, "vendor_email") or ""),
|
|
||||||
"phone": str(extension.settings.get(conn, "vendor_phone") or ""),
|
|
||||||
"address": str(extension.settings.get(conn, "vendor_address") or ""),
|
|
||||||
"logo_base64": "",
|
|
||||||
}
|
|
||||||
bundle, _ = licsrv.issue_license(
|
|
||||||
customer={"name": args.name, "contact_email": vendor["email"]},
|
|
||||||
license_type="enterprise",
|
|
||||||
modules=[],
|
|
||||||
valid_days=int(args.days),
|
|
||||||
master_private_key_b64=key.private_key,
|
|
||||||
master_public_key_b64=key.public_key,
|
|
||||||
# Bewusst leer: es gibt keinen uebergeordneten Endpunkt. Damit
|
|
||||||
# laeuft der Handshake fuer diese Lizenz nie ins Netz.
|
|
||||||
master_endpoint="",
|
|
||||||
vendor=vendor,
|
|
||||||
role="master",
|
|
||||||
)
|
|
||||||
document, client_key = lic.split_bundle(bundle)
|
|
||||||
|
|
||||||
config = extension.config
|
|
||||||
config.license_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
config.license_path.write_text(
|
|
||||||
json.dumps(document, indent=2, sort_keys=True, ensure_ascii=False) + "\n",
|
|
||||||
encoding="utf-8",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
config.license_path.chmod(0o640)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
from tesm_core.keystore import write_private_file
|
|
||||||
|
|
||||||
write_private_file(
|
|
||||||
config.license_key_path,
|
|
||||||
json.dumps({"license_id": document["license_id"], "client_key": client_key}) + "\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
with extension.database.transaction(conn):
|
|
||||||
conn.execute(
|
|
||||||
"INSERT INTO trusted_issuer_keys (key_id, public_key, label, source, added_at, "
|
|
||||||
"added_by) VALUES (?,?,?,'bootstrap',datetime('now'),'cli') "
|
|
||||||
"ON CONFLICT(key_id) DO NOTHING",
|
|
||||||
(key.key_id, key.public_key, "Eigener Signaturschluessel"),
|
|
||||||
)
|
|
||||||
# Es gibt keine hoehere Instanz, die diese Lizenz aktivieren koennte.
|
|
||||||
extension.settings.set(conn, "license_activation_status", "active", actor="cli")
|
|
||||||
extension.settings.set(conn, "license_pending_request", "", actor="cli")
|
|
||||||
audit.log(
|
|
||||||
conn,
|
|
||||||
actor="cli",
|
|
||||||
action="own_license.bootstrapped",
|
|
||||||
target=document["license_id"],
|
|
||||||
detail=f"Rolle master, {args.days} Tage, Schluessel {key.key_id}",
|
|
||||||
severity="warning",
|
|
||||||
)
|
|
||||||
state = extension.license.reload(conn) if extension.license else None
|
|
||||||
|
|
||||||
print(f"Master-Lizenz ausgestellt: {document['license_id']}")
|
print(f"Master-Lizenz ausgestellt: {document['license_id']}")
|
||||||
print(f"Gueltig bis: {document['expires_at']}")
|
print(f"Gueltig bis: {document['expires_at']}")
|
||||||
print(f"Datei: {config.license_path}")
|
print(f"Datei: {extension.config.license_path}")
|
||||||
if state is not None:
|
if state is not None:
|
||||||
print(f"Zustand: gueltig={state.valid}, aktiviert={state.activated}, Rolle={state.role}")
|
print(f"Zustand: gueltig={state.valid}, aktiviert={state.activated}, Rolle={state.role}")
|
||||||
return 0
|
return 0
|
||||||
@@ -434,7 +390,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
init_key.set_defaults(func=cmd_init_key)
|
init_key.set_defaults(func=cmd_init_key)
|
||||||
|
|
||||||
bootstrap = sub.add_parser(
|
bootstrap = sub.add_parser(
|
||||||
"bootstrap", help="Eigene Master-Lizenz dieser Wurzelinstanz ausstellen"
|
"bootstrap",
|
||||||
|
help=(
|
||||||
|
"Eigene Master-Lizenz dieser Wurzelinstanz ausstellen"
|
||||||
|
if is_master_edition()
|
||||||
|
else "nicht in dieser Ausgabe (nur Master-Lizenzserver)"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
bootstrap.add_argument("--days", type=int, default=3650)
|
bootstrap.add_argument("--days", type=int, default=3650)
|
||||||
bootstrap.add_argument("--name", default="Lizenzserver (Eigenbetrieb)")
|
bootstrap.add_argument("--name", default="Lizenzserver (Eigenbetrieb)")
|
||||||
|
|||||||
@@ -116,9 +116,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ empty_state('Keine gültige Lizenz',
|
{% if master_edition %}
|
||||||
'Spielen Sie eine Lizenzdatei ein oder erzeugen Sie mit "tesm-license-admin bootstrap"
|
{{ empty_state('Keine gültige Lizenz',
|
||||||
die eigene Lizenz der Wurzelinstanz.', 'key') }}
|
'Spielen Sie eine Lizenzdatei ein oder erzeugen Sie mit "tesm-license-admin bootstrap"
|
||||||
|
die eigene Lizenz der Wurzelinstanz.', 'key') }}
|
||||||
|
{% else %}
|
||||||
|
{{ empty_state('Keine gültige Lizenz',
|
||||||
|
'Diese Ausgabe ist ein Sub-Lizenzserver: fordern Sie beim Master-Lizenzserver eine
|
||||||
|
Lizenz für diesen Server an und spielen Sie die Datei hier ein. Eine Master-Lizenz
|
||||||
|
kann diese Ausgabe nicht erzeugen.', 'key') }}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if pending %}
|
{% if pending %}
|
||||||
|
|||||||
+165
-20
@@ -28,6 +28,9 @@ set -euo pipefail
|
|||||||
|
|
||||||
APP=""
|
APP=""
|
||||||
INSTANCE=""
|
INSTANCE=""
|
||||||
|
#: Verzeichnis der Umgebungsdateien. Ueberschreibbar, damit sich die
|
||||||
|
#: Portsuche in einem Test wirklich ausfuehren laesst.
|
||||||
|
ETC_TESM="${TESM_ETC_DIR:-/etc/tesm}"
|
||||||
PORT=""
|
PORT=""
|
||||||
HTTP_PORT=""
|
HTTP_PORT=""
|
||||||
SKIP_PACKAGES=0
|
SKIP_PACKAGES=0
|
||||||
@@ -46,6 +49,62 @@ warn() { printf '%s !!%s %s\n' "$COLOR_WARN" "$COLOR_OFF" "$*"; }
|
|||||||
fail() { printf '%s !!%s %s\n' "$COLOR_ERR" "$COLOR_OFF" "$*" >&2; exit 1; }
|
fail() { printf '%s !!%s %s\n' "$COLOR_ERR" "$COLOR_OFF" "$*" >&2; exit 1; }
|
||||||
info() { printf ' %s\n' "$*"; }
|
info() { printf ' %s\n' "$*"; }
|
||||||
|
|
||||||
|
#: Merker fuer die EXIT-Falle. Zurueckgerollt wird nur, was auch gesichert ist.
|
||||||
|
SICHERUNG_STEHT=0
|
||||||
|
INSTALL_FERTIG=0
|
||||||
|
ZURUECKGEROLLT=0
|
||||||
|
|
||||||
|
zurueckrollen_install() {
|
||||||
|
[[ $SICHERUNG_STEHT -eq 1 && $ZURUECKGEROLLT -eq 0 ]] || return 0
|
||||||
|
# Kommt nur zum Zug, wenn die Sicherung steht -- dann sind diese Variablen
|
||||||
|
# gesetzt. Der Vollstaendigkeit halber trotzdem gepruefte Namen.
|
||||||
|
[[ -n "${BACKUP_DIR:-}" && -d "$BACKUP_DIR" ]] || { warn "Keine Sicherung gefunden -- nichts wiederhergestellt."; return 0; }
|
||||||
|
ZURUECKGEROLLT=1
|
||||||
|
warn "Alten Stand wiederherstellen."
|
||||||
|
systemctl stop "$UNIT" 2>/dev/null || true
|
||||||
|
[[ "$APP" == "tesm" ]] && systemctl stop "$MONITOR_UNIT" 2>/dev/null || true
|
||||||
|
rm -rf "$FAILED_DIR"
|
||||||
|
mv "$ROOT" "$FAILED_DIR" 2>/dev/null || true
|
||||||
|
cp -a "$BACKUP_DIR" "$ROOT"
|
||||||
|
systemctl start "$UNIT" 2>/dev/null || true
|
||||||
|
[[ "$APP" == "tesm" ]] && systemctl start "$MONITOR_UNIT" 2>/dev/null || true
|
||||||
|
warn "Wiederhergestellt aus $BACKUP_DIR"
|
||||||
|
warn "Fehlgeschlagener Stand liegt unter $FAILED_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Bei **jedem** Fehlschlag, nicht nur bei einem, an den ich gedacht habe:
|
||||||
|
# Systempakete, venv, sudoers, nginx, ein Ctrl-C. Vorher blieb der Dienst
|
||||||
|
# gestoppt und das Verzeichnis teils ersetzt zurueck.
|
||||||
|
bei_fehler() {
|
||||||
|
local code=$?
|
||||||
|
[[ $INSTALL_FERTIG -eq 1 || "$code" == "0" ]] && return "$code"
|
||||||
|
if [[ $SICHERUNG_STEHT -eq 1 ]]; then
|
||||||
|
zurueckrollen_install
|
||||||
|
else
|
||||||
|
# Neuinstallation: es gibt keinen alten Stand. Nichts loeschen -- im
|
||||||
|
# Zielverzeichnis kann eine Fassung 1.x liegen, deren Sicherung ein
|
||||||
|
# anderes Skript haelt (upgrade.sh).
|
||||||
|
# Vorsichtige Ersetzung: bricht es schon beim Auswerten der Argumente ab,
|
||||||
|
# gibt es $ROOT noch nicht -- und "set -u" wuerde die Falle selbst zum
|
||||||
|
# Fehler machen.
|
||||||
|
if [[ ${IS_UPDATE:-0} -eq 1 ]]; then
|
||||||
|
# Abbruch vor dem Sicherungsschritt: die Installation ist unangetastet.
|
||||||
|
warn "Abgebrochen (Code $code), bevor etwas geaendert wurde."
|
||||||
|
warn "Die bestehende Installation ist unberuehrt -- nichts zurueckzurollen."
|
||||||
|
else
|
||||||
|
warn "Abgebrochen (Code $code). Es gab keinen vorherigen Stand dieser Fassung,"
|
||||||
|
warn "also wurde nichts wiederhergestellt und nichts geloescht."
|
||||||
|
fi
|
||||||
|
# Nur sagen, was es wirklich gibt: bei einem Abbruch an der Rueckfrage
|
||||||
|
# existiert das Verzeichnis noch nicht, und die Zeile log dann.
|
||||||
|
if [[ -n "${ROOT:-}" && -d "${ROOT:-}" ]]; then
|
||||||
|
warn "Angelegt wurde bis hierhin: $ROOT (Daten unter data/ unberuehrt)."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return "$code"
|
||||||
|
}
|
||||||
|
trap bei_fehler EXIT
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'USAGE'
|
cat <<'USAGE'
|
||||||
Verwendung: install.sh --app <tesm|tesm-license> [Optionen]
|
Verwendung: install.sh --app <tesm|tesm-license> [Optionen]
|
||||||
@@ -112,13 +171,24 @@ MONITOR_UNIT="$NAME-monitor.service"
|
|||||||
NGINX_SITE="/etc/nginx/sites-available/$NAME"
|
NGINX_SITE="/etc/nginx/sites-available/$NAME"
|
||||||
ENV_PREFIX="TESM_"
|
ENV_PREFIX="TESM_"
|
||||||
[[ "$APP" == "tesm-license" ]] && ENV_PREFIX="TESM_LICENSE_"
|
[[ "$APP" == "tesm-license" ]] && ENV_PREFIX="TESM_LICENSE_"
|
||||||
|
|
||||||
|
# Ausgabe des Lizenzservers: die Master-Ausgabe bringt das Teilpaket
|
||||||
|
# tesm_license/master mit -- den einzigen Weg, auf dem eine Lizenzdatei die
|
||||||
|
# Rolle "master" bekommt. Das Release fuer Sub-Lizenzserver hat es nicht.
|
||||||
|
# Die Ausgabe haengt an der Anwesenheit des Verzeichnisses, nicht an einer
|
||||||
|
# Einstellung: eine Einstellung liesse sich vor Ort umstellen.
|
||||||
|
MASTER_TEILPAKET="apps/tesm-license/src/tesm_license/master"
|
||||||
|
EDITION="standard"
|
||||||
|
if [[ "$APP" == "tesm-license" ]]; then
|
||||||
|
if [[ -d "$REPO_DIR/$MASTER_TEILPAKET" ]]; then EDITION="master"; else EDITION="sub"; fi
|
||||||
|
fi
|
||||||
# Bei einer Aktualisierung gilt zuerst, was schon eingerichtet ist. Sonst
|
# Bei einer Aktualisierung gilt zuerst, was schon eingerichtet ist. Sonst
|
||||||
# verschiebt ein Update ohne Portangaben die Anwendung: eine benannte Instanz
|
# verschiebt ein Update ohne Portangaben die Anwendung: eine benannte Instanz
|
||||||
# faellt auf 8080/8443 zurueck, auch wenn sie seit der Installation auf Port 80
|
# faellt auf 8080/8443 zurueck, auch wenn sie seit der Installation auf Port 80
|
||||||
# lief -- und ein Reverse Proxy davor zeigt dann ins Leere. Dieselbe Regel wie
|
# lief -- und ein Reverse Proxy davor zeigt dann ins Leere. Dieselbe Regel wie
|
||||||
# beim Secure-Flag weiter unten: eine Aktualisierung nimmt nichts weg, was
|
# beim Secure-Flag weiter unten: eine Aktualisierung nimmt nichts weg, was
|
||||||
# nicht ausdruecklich neu gesetzt wird.
|
# nicht ausdruecklich neu gesetzt wird.
|
||||||
ENV_FILE="/etc/tesm/$NAME.env"
|
ENV_FILE="$ETC_TESM/$NAME.env"
|
||||||
if [[ -z "$PORT" && -f "$ENV_FILE" ]]; then
|
if [[ -z "$PORT" && -f "$ENV_FILE" ]]; then
|
||||||
PORT="$(sed -n "s/^${ENV_PREFIX}BIND=127\.0\.0\.1:\([0-9]\{1,5\}\)$/\1/p" "$ENV_FILE" | head -1)"
|
PORT="$(sed -n "s/^${ENV_PREFIX}BIND=127\.0\.0\.1:\([0-9]\{1,5\}\)$/\1/p" "$ENV_FILE" | head -1)"
|
||||||
fi
|
fi
|
||||||
@@ -127,10 +197,30 @@ if [[ -f "$NGINX_SITE" ]]; then
|
|||||||
[[ -z "$HTTPS_PORT" ]] && HTTPS_PORT="$(sed -n 's/^[[:space:]]*listen[[:space:]]\([0-9]\{1,5\}\)[[:space:]]\+ssl.*/\1/p' "$NGINX_SITE" | head -1)"
|
[[ -z "$HTTPS_PORT" ]] && HTTPS_PORT="$(sed -n 's/^[[:space:]]*listen[[:space:]]\([0-9]\{1,5\}\)[[:space:]]\+ssl.*/\1/p' "$NGINX_SITE" | head -1)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
port_belegt() {
|
||||||
|
local p="$1"
|
||||||
|
# Schon von einer anderen Installation vergeben? Die Umgebungsdateien sind
|
||||||
|
# die Wahrheit -- ein Dienst kann gerade gestoppt sein und den Port trotzdem
|
||||||
|
# beanspruchen.
|
||||||
|
grep -qs "BIND=127\.0\.0\.1:$p\$" "$ETC_TESM"/*.env 2>/dev/null &&
|
||||||
|
return 0
|
||||||
|
# Oder gerade in Benutzung, von was auch immer.
|
||||||
|
command -v ss >/dev/null 2>&1 && [[ -n "$(ss -Hltn "sport = :$p" 2>/dev/null)" ]] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if [[ -z "$PORT" ]]; then
|
if [[ -z "$PORT" ]]; then
|
||||||
PORT=5000
|
BASISPORT=5000
|
||||||
[[ "$APP" == "tesm-license" ]] && PORT=5001
|
[[ "$APP" == "tesm-license" ]] && BASISPORT=5001
|
||||||
[[ -n "$INSTANCE" ]] && PORT=$((PORT + 100))
|
if [[ -n "$INSTANCE" ]]; then
|
||||||
|
# Frueher bekam **jede** Instanz Basis+100. Die zweite startete nie
|
||||||
|
# ("Address already in use") -- und die Gesundheitspruefung merkte es
|
||||||
|
# nicht, weil die erste auf demselben Port antwortete.
|
||||||
|
PORT=$((BASISPORT + 100))
|
||||||
|
while port_belegt "$PORT"; do PORT=$((PORT + 1)); done
|
||||||
|
else
|
||||||
|
PORT=$BASISPORT
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ -z "$HTTP_PORT" ]]; then
|
if [[ -z "$HTTP_PORT" ]]; then
|
||||||
HTTP_PORT=80
|
HTTP_PORT=80
|
||||||
@@ -212,12 +302,24 @@ fi
|
|||||||
# Der Vorgaenger lief als root. Hier laeuft der Dienst unprivilegiert; Rootrechte
|
# Der Vorgaenger lief als root. Hier laeuft der Dienst unprivilegiert; Rootrechte
|
||||||
# gibt es ausschliesslich ueber den Helfer.
|
# gibt es ausschliesslich ueber den Helfer.
|
||||||
|
|
||||||
|
# Ein Sub-Paket auf eine Master-Installation waere ein stiller Rueckbau: der
|
||||||
|
# Abgleich unten laeuft mit --delete und wuerde das Teilpaket entfernen. Danach
|
||||||
|
# gaebe es auf dem Master keinen Weg mehr, eine Master-Lizenz auszustellen --
|
||||||
|
# und niemand haette es gesagt.
|
||||||
|
if [[ "$EDITION" == "sub" && -d "$ROOT/$MASTER_TEILPAKET" ]]; then
|
||||||
|
fail "Hier laeuft die Master-Ausgabe, dieses Paket ist die Ausgabe fuer Sub-Lizenzserver.
|
||||||
|
Eine Aktualisierung damit wuerde die Master-Ausgabe entfernen.
|
||||||
|
Passendes Paket: Repository tesm-license-master (privat).
|
||||||
|
Bewusster Rueckbau: $ROOT/$MASTER_TEILPAKET von Hand entfernen, dann erneut."
|
||||||
|
fi
|
||||||
|
|
||||||
# -- Backup ------------------------------------------------------------------
|
# -- Backup ------------------------------------------------------------------
|
||||||
if [[ $IS_UPDATE -eq 1 ]]; then
|
if [[ $IS_UPDATE -eq 1 ]]; then
|
||||||
step "Sicherung vor der Aktualisierung"
|
step "Sicherung vor der Aktualisierung"
|
||||||
systemctl stop "$UNIT" 2>/dev/null || true
|
systemctl stop "$UNIT" 2>/dev/null || true
|
||||||
[[ "$APP" == "tesm" ]] && systemctl stop "$MONITOR_UNIT" 2>/dev/null || true
|
[[ "$APP" == "tesm" ]] && systemctl stop "$MONITOR_UNIT" 2>/dev/null || true
|
||||||
cp -a "$ROOT" "$BACKUP_DIR"
|
cp -a "$ROOT" "$BACKUP_DIR"
|
||||||
|
SICHERUNG_STEHT=1
|
||||||
info "Sicherung: $BACKUP_DIR"
|
info "Sicherung: $BACKUP_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -347,8 +449,8 @@ step "Umgebung"
|
|||||||
# EnvironmentFile, die Kommandozeilenwerkzeuge ueber ihren Wrapper. Ohne das
|
# EnvironmentFile, die Kommandozeilenwerkzeuge ueber ihren Wrapper. Ohne das
|
||||||
# wuesste "tesm-admin" nicht, wo die Instanz liegt, und wuerde sich eine leere
|
# wuesste "tesm-admin" nicht, wo die Instanz liegt, und wuerde sich eine leere
|
||||||
# Datenbank im Arbeitsverzeichnis anlegen.
|
# Datenbank im Arbeitsverzeichnis anlegen.
|
||||||
install -d -o root -g root -m 0755 /etc/tesm
|
install -d -o root -g root -m 0755 "$ETC_TESM"
|
||||||
ENV_FILE="/etc/tesm/$NAME.env"
|
ENV_FILE="$ETC_TESM/$NAME.env"
|
||||||
TMP_ENV="$(mktemp)"
|
TMP_ENV="$(mktemp)"
|
||||||
{
|
{
|
||||||
echo "# Von install.sh erzeugt fuer $NAME -- nicht von Hand bearbeiten."
|
echo "# Von install.sh erzeugt fuer $NAME -- nicht von Hand bearbeiten."
|
||||||
@@ -491,7 +593,11 @@ fi
|
|||||||
step "Gesundheitspruefung"
|
step "Gesundheitspruefung"
|
||||||
HEALTHY=0
|
HEALTHY=0
|
||||||
for attempt in $(seq 1 20); do
|
for attempt in $(seq 1 20); do
|
||||||
if curl -fsS --max-time 3 "http://127.0.0.1:$PORT/gesundheit" >/dev/null 2>&1; then
|
# Beides: der Dienst muss laufen **und** antworten. Nur die Antwort zu
|
||||||
|
# pruefen ging schon einmal schief -- auf einem gemeinsamen Port antwortete
|
||||||
|
# eine andere Instanz, und ein toter Dienst galt als gesund.
|
||||||
|
if systemctl is-active --quiet "$UNIT" &&
|
||||||
|
curl -fsS --max-time 3 "http://127.0.0.1:$PORT/gesundheit" >/dev/null 2>&1; then
|
||||||
HEALTHY=1
|
HEALTHY=1
|
||||||
info "Antwort nach ${attempt}x"
|
info "Antwort nach ${attempt}x"
|
||||||
break
|
break
|
||||||
@@ -502,13 +608,9 @@ done
|
|||||||
if [[ $HEALTHY -eq 0 ]]; then
|
if [[ $HEALTHY -eq 0 ]]; then
|
||||||
warn "Die Anwendung antwortet nicht."
|
warn "Die Anwendung antwortet nicht."
|
||||||
if [[ $IS_UPDATE -eq 1 ]]; then
|
if [[ $IS_UPDATE -eq 1 ]]; then
|
||||||
warn "Rollback wird ausgefuehrt."
|
# Dieselbe Funktion wie die EXIT-Falle -- zwei Wege, die dasselbe tun
|
||||||
systemctl stop "$UNIT" 2>/dev/null || true
|
# sollen, laufen sonst irgendwann auseinander.
|
||||||
[[ "$APP" == "tesm" ]] && systemctl stop "$MONITOR_UNIT" 2>/dev/null || true
|
zurueckrollen_install
|
||||||
mv "$ROOT" "$FAILED_DIR"
|
|
||||||
cp -a "$BACKUP_DIR" "$ROOT"
|
|
||||||
systemctl start "$UNIT" || true
|
|
||||||
[[ "$APP" == "tesm" ]] && systemctl start "$MONITOR_UNIT" || true
|
|
||||||
fail "Aktualisierung fehlgeschlagen. Alter Stand wiederhergestellt.
|
fail "Aktualisierung fehlgeschlagen. Alter Stand wiederhergestellt.
|
||||||
Fehlgeschlagene Version : $FAILED_DIR
|
Fehlgeschlagene Version : $FAILED_DIR
|
||||||
Sicherung : $BACKUP_DIR
|
Sicherung : $BACKUP_DIR
|
||||||
@@ -568,12 +670,45 @@ if [[ $HTTPS -eq 1 ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# -- Lizenzserver einrichten -------------------------------------------------
|
||||||
|
# Bei einer Neuinstallation: Signaturschluessel anlegen (braucht jeder
|
||||||
|
# Lizenzserver, um Kundenlizenzen zu signieren) und in der Master-Ausgabe
|
||||||
|
# gleich die eigene Master-Lizenz. Bei einer Aktualisierung nichts davon --
|
||||||
|
# ein bestehender Schluessel darf nie angetastet werden.
|
||||||
|
if [[ "$APP" == "tesm-license" && $IS_UPDATE -eq 0 ]]; then
|
||||||
|
# Das Datenverzeichnis steht in der Umgebungsdatei -- es muss nicht
|
||||||
|
# $ROOT/data sein, wenn jemand es dort umgestellt hat.
|
||||||
|
DATEN_DIR="$(sed -n "s/^${ENV_PREFIX}DATA_DIR=//p" "$ENV_FILE" | tail -1)"
|
||||||
|
DATEN_DIR="${DATEN_DIR:-$ROOT/data}"
|
||||||
|
step "Signaturschluessel"
|
||||||
|
if sudo -u "$SERVICE_USER" "/usr/local/bin/$NAME-admin" init-key; then
|
||||||
|
info "Bitte die Datei $DATEN_DIR/master_signing_key.json SOFORT sichern."
|
||||||
|
else
|
||||||
|
fail "Der Signaturschluessel liess sich nicht erzeugen. Ohne ihn kann dieser Server
|
||||||
|
keine Lizenz ausstellen. Protokoll: journalctl -u $UNIT -n 50"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$EDITION" == "master" ]]; then
|
||||||
|
step "Eigene Master-Lizenz"
|
||||||
|
if [[ -s "$DATEN_DIR/license.json" ]]; then
|
||||||
|
info "Es liegt schon eine Lizenzdatei -- unangetastet."
|
||||||
|
elif sudo -u "$SERVICE_USER" "/usr/local/bin/$NAME-admin" bootstrap; then
|
||||||
|
systemctl restart "$UNIT"
|
||||||
|
info "Dienst neu gestartet, damit die Lizenz gelesen wird."
|
||||||
|
else
|
||||||
|
fail "Die Master-Lizenz liess sich nicht ausstellen.
|
||||||
|
Nachtraeglich: sudo -u $SERVICE_USER $NAME-admin bootstrap"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $IS_UPDATE -eq 1 ]]; then
|
if [[ $IS_UPDATE -eq 1 ]]; then
|
||||||
rm -rf "$BACKUP_DIR"
|
rm -rf "$BACKUP_DIR"
|
||||||
info "Sicherung entfernt (Aktualisierung erfolgreich)"
|
info "Sicherung entfernt (Aktualisierung erfolgreich)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -- Abschluss ---------------------------------------------------------------
|
# -- Abschluss ---------------------------------------------------------------
|
||||||
|
INSTALL_FERTIG=1
|
||||||
step "Fertig"
|
step "Fertig"
|
||||||
ADMIN_CMD="tesm-admin"
|
ADMIN_CMD="tesm-admin"
|
||||||
[[ "$APP" == "tesm-license" ]] && ADMIN_CMD="tesm-license-admin"
|
[[ "$APP" == "tesm-license" ]] && ADMIN_CMD="tesm-license-admin"
|
||||||
@@ -589,14 +724,24 @@ cat <<SUMMARY
|
|||||||
Naechste Schritte:
|
Naechste Schritte:
|
||||||
SUMMARY
|
SUMMARY
|
||||||
|
|
||||||
if [[ "$APP" == "tesm-license" ]]; then
|
if [[ "$APP" == "tesm-license" && "$EDITION" == "master" ]]; then
|
||||||
cat <<SUMMARY
|
cat <<SUMMARY
|
||||||
1. Signaturschluessel erzeugen und sofort sichern:
|
1. Administrator anlegen:
|
||||||
sudo -u $SERVICE_USER $NAME-admin init-key
|
|
||||||
2. Eigene Master-Lizenz ausstellen:
|
|
||||||
sudo -u $SERVICE_USER $NAME-admin bootstrap
|
|
||||||
3. Administrator anlegen:
|
|
||||||
sudo -u $SERVICE_USER $NAME-admin create-admin
|
sudo -u $SERVICE_USER $NAME-admin create-admin
|
||||||
|
2. Signaturschluessel sichern -- ohne ihn ist keine ausgestellte Lizenz
|
||||||
|
mehr pruefbar:
|
||||||
|
$ROOT/data/master_signing_key.json
|
||||||
|
SUMMARY
|
||||||
|
elif [[ "$APP" == "tesm-license" ]]; then
|
||||||
|
cat <<SUMMARY
|
||||||
|
1. Administrator anlegen:
|
||||||
|
sudo -u $SERVICE_USER $NAME-admin create-admin
|
||||||
|
2. Beim Master-Lizenzserver eine Lizenz fuer diesen Server anfordern und
|
||||||
|
unter "Eigene Lizenz" einspielen. Diese Ausgabe ist ein Sub-Lizenzserver
|
||||||
|
und stellt keine eigene Master-Lizenz aus.
|
||||||
|
3. Signaturschluessel sichern -- mit ihm signiert dieser Server seine
|
||||||
|
Kundenlizenzen:
|
||||||
|
$ROOT/data/master_signing_key.json
|
||||||
SUMMARY
|
SUMMARY
|
||||||
else
|
else
|
||||||
cat <<SUMMARY
|
cat <<SUMMARY
|
||||||
|
|||||||
+59
-6
@@ -11,6 +11,10 @@
|
|||||||
# Auf einer bestehenden Installation liegt sie schon bereit:
|
# Auf einer bestehenden Installation liegt sie schon bereit:
|
||||||
#
|
#
|
||||||
# sudo bash /srv/tesm/deploy/update.sh tesm
|
# sudo bash /srv/tesm/deploy/update.sh tesm
|
||||||
|
#
|
||||||
|
# Laeuft hier die Master-Ausgabe des Lizenzservers, kommt das Release aus dem
|
||||||
|
# privaten Repositorium tesm-license-master -- dafuer braucht es einen
|
||||||
|
# Zugriffstoken in TESM_RELEASE_TOKEN oder in /etc/tesm-release.token.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -31,14 +35,39 @@ EXTRA=("$@")
|
|||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Repositorium: die Master-Ausgabe des Lizenzservers liegt in einem eigenen,
|
||||||
|
# privaten Repositorium, heisst als Installation aber weiter "tesm-license".
|
||||||
|
# Erkannt wird sie an der Installation und nicht an einem Schalter -- wer
|
||||||
|
# aktualisiert, soll nicht erst wissen muessen, welche Ausgabe hier laeuft.
|
||||||
|
# Sonst holte der Aufruf die Ausgabe fuer Sub-Lizenzserver, und der Installer
|
||||||
|
# muesste sie abweisen.
|
||||||
|
REPO="$APP"
|
||||||
|
if [[ "$APP" == "tesm-license" ]]; then
|
||||||
|
NAME="$APP"
|
||||||
|
for ((i = 0; i < ${#EXTRA[@]}; i++)); do
|
||||||
|
[[ "${EXTRA[i]}" == "--instance" ]] && NAME="$APP-${EXTRA[i + 1]:-}"
|
||||||
|
done
|
||||||
|
# Die Wurzel ist ueberschreibbar, damit ein Test die Erkennung wirklich
|
||||||
|
# ausfuehren kann und nicht nur den Quelltext liest.
|
||||||
|
if [[ -d "${TESM_SRV_ROOT:-/srv}/$NAME/apps/tesm-license/src/tesm_license/master" ]]; then
|
||||||
|
REPO="tesm-license-master"
|
||||||
|
echo "==> Master-Ausgabe erkannt -- Release aus $REPO."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# --8<-- Bootstrap: Release holen --------------------------------------------
|
# --8<-- Bootstrap: Release holen --------------------------------------------
|
||||||
# Wortgleich in update.sh und upgrade.sh; ein Hygienetest vergleicht die beiden
|
# Wortgleich in update.sh und upgrade.sh; ein Hygienetest vergleicht die beiden
|
||||||
# Bloecke. Zwei eigenstaendige Skripte koennen sich keine Datei teilen, und
|
# Bloecke. Zwei eigenstaendige Skripte koennen sich keine Datei teilen, und
|
||||||
# beide sollen als **einzelne Datei** genuegen: auf das Zielsystem legen,
|
# beide sollen als **einzelne Datei** genuegen: auf das Zielsystem legen,
|
||||||
# ausfuehren, fertig -- auf einer 1.2.8 liegt nichts von diesem Projekt.
|
# ausfuehren, fertig -- auf einer 1.2.8 liegt nichts von diesem Projekt.
|
||||||
#
|
#
|
||||||
# Erwartet: $APP und $TAG. Setzt: $TAG (aufgeloest) und $SRC (entpacktes
|
# Erwartet: $APP und $TAG, wahlweise $REPO (Vorgabe: $APP). Setzt: $TAG
|
||||||
# Release). Raeumt sein Arbeitsverzeichnis beim Beenden auf.
|
# (aufgeloest) und $SRC (entpacktes Release). Raeumt sein Arbeitsverzeichnis
|
||||||
|
# beim Beenden auf.
|
||||||
|
#
|
||||||
|
# $REPO gibt es, weil Installationsname und Repositorium auseinanderfallen
|
||||||
|
# koennen: die Master-Ausgabe des Lizenzservers liegt in
|
||||||
|
# "tesm-license-master", die Installation heisst weiter "tesm-license".
|
||||||
#
|
#
|
||||||
# Zwei Fallen des Vorgaengers sind hier vermieden:
|
# Zwei Fallen des Vorgaengers sind hier vermieden:
|
||||||
# * Der Tag "latest" wird ueber die API aufgeloest, nie direkt in die
|
# * Der Tag "latest" wird ueber die API aufgeloest, nie direkt in die
|
||||||
@@ -48,6 +77,15 @@ EXTRA=("$@")
|
|||||||
BASE="${TESM_RELEASE_BASE:-https://gitea.int.eertmoed.net}"
|
BASE="${TESM_RELEASE_BASE:-https://gitea.int.eertmoed.net}"
|
||||||
OWNER="${TESM_RELEASE_OWNER:-alientim}"
|
OWNER="${TESM_RELEASE_OWNER:-alientim}"
|
||||||
|
|
||||||
|
# Ein privates Repositorium -- tesm-license-master -- gibt sein Release nur mit
|
||||||
|
# Zugriffstoken heraus. Reihenfolge: Umgebungsvariable, dann eine Datei, die
|
||||||
|
# der Betreiber selbst anlegt. Der Installer legt sie nie an: ob ein Token auf
|
||||||
|
# dem Server liegt, ist keine Entscheidung eines Skripts.
|
||||||
|
TOKEN="${TESM_RELEASE_TOKEN:-}"
|
||||||
|
if [[ -z "$TOKEN" && -r /etc/tesm-release.token ]]; then
|
||||||
|
TOKEN="$(tr -d '[:space:]' < /etc/tesm-release.token)"
|
||||||
|
fi
|
||||||
|
|
||||||
# Ein Wagenruecklauf im Paket macht jedes Shell-Skript auf dem Ziel unbrauchbar
|
# Ein Wagenruecklauf im Paket macht jedes Shell-Skript auf dem Ziel unbrauchbar
|
||||||
# ("bad interpreter"). Die Pruefung darf dabei nicht selbst von einem
|
# ("bad interpreter"). Die Pruefung darf dabei nicht selbst von einem
|
||||||
# Zusatzpaket abhaengen: mit "file" -- das auf einem schlanken Server fehlt --
|
# Zusatzpaket abhaengen: mit "file" -- das auf einem schlanken Server fehlt --
|
||||||
@@ -63,11 +101,24 @@ hat_crlf() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BOOTSTRAP_WORK=""
|
BOOTSTRAP_WORK=""
|
||||||
bootstrap_aufraeumen() { [[ -n "$BOOTSTRAP_WORK" ]] && rm -rf "$BOOTSTRAP_WORK"; }
|
bootstrap_aufraeumen() {
|
||||||
|
# Den Ausstiegscode zuerst festhalten: alles danach ueberschreibt ihn.
|
||||||
|
local code=$?
|
||||||
|
[[ -n "$BOOTSTRAP_WORK" ]] && rm -rf "$BOOTSTRAP_WORK"
|
||||||
|
# Wer einen eigenen Abschluss braucht -- etwa einen Rueckweg nach einem
|
||||||
|
# Fehlschlag --, definiert "beim_beenden". Ein zweiter "trap ... EXIT" waere
|
||||||
|
# der falsche Weg: er wuerde diesen hier ersetzen, und das entpackte Release
|
||||||
|
# bliebe unter /tmp liegen.
|
||||||
|
declare -F beim_beenden >/dev/null 2>&1 && beim_beenden "$code"
|
||||||
|
return "$code"
|
||||||
|
}
|
||||||
trap bootstrap_aufraeumen EXIT
|
trap bootstrap_aufraeumen EXIT
|
||||||
|
|
||||||
release_holen() {
|
release_holen() {
|
||||||
local repo="${TESM_RELEASE_REPO:-$APP}" asset
|
local repo="${TESM_RELEASE_REPO:-${REPO:-$APP}}" asset
|
||||||
|
# Leeres Array und "set -u" vertragen sich nur mit dieser Schreibweise.
|
||||||
|
local -a kopf=()
|
||||||
|
[[ -n "$TOKEN" ]] && kopf=(-H "Authorization: token $TOKEN")
|
||||||
for werkzeug in curl tar python3 find; do
|
for werkzeug in curl tar python3 find; do
|
||||||
command -v "$werkzeug" >/dev/null 2>&1 || {
|
command -v "$werkzeug" >/dev/null 2>&1 || {
|
||||||
echo "Auf diesem System fehlt $werkzeug -- ohne das geht es nicht." >&2
|
echo "Auf diesem System fehlt $werkzeug -- ohne das geht es nicht." >&2
|
||||||
@@ -78,9 +129,11 @@ release_holen() {
|
|||||||
|
|
||||||
if [[ "$TAG" == "latest" ]]; then
|
if [[ "$TAG" == "latest" ]]; then
|
||||||
echo "==> Ermittle aktuellen Release-Tag ..."
|
echo "==> Ermittle aktuellen Release-Tag ..."
|
||||||
TAG="$(curl -fsS "$BASE/api/v1/repos/$OWNER/$repo/releases/latest" \
|
TAG="$(curl -fsS ${kopf[0]+"${kopf[@]}"} "$BASE/api/v1/repos/$OWNER/$repo/releases/latest" \
|
||||||
| python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])')" || {
|
| python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])')" || {
|
||||||
echo "Kein Release gefunden unter $BASE/$OWNER/$repo." >&2
|
echo "Kein Release gefunden unter $BASE/$OWNER/$repo." >&2
|
||||||
|
[[ -z "$TOKEN" ]] &&
|
||||||
|
echo "Ist das Repositorium privat? Dann TESM_RELEASE_TOKEN setzen." >&2
|
||||||
echo "Andere Quelle: TESM_RELEASE_BASE, TESM_RELEASE_OWNER, TESM_RELEASE_REPO." >&2
|
echo "Andere Quelle: TESM_RELEASE_BASE, TESM_RELEASE_OWNER, TESM_RELEASE_REPO." >&2
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -89,7 +142,7 @@ release_holen() {
|
|||||||
|
|
||||||
asset="$repo-$TAG.tar.gz"
|
asset="$repo-$TAG.tar.gz"
|
||||||
echo "==> Lade $asset ..."
|
echo "==> Lade $asset ..."
|
||||||
curl -fsSL -o "$BOOTSTRAP_WORK/$asset" \
|
curl -fsSL ${kopf[0]+"${kopf[@]}"} -o "$BOOTSTRAP_WORK/$asset" \
|
||||||
"$BASE/$OWNER/$repo/releases/download/$TAG/$asset" || {
|
"$BASE/$OWNER/$repo/releases/download/$TAG/$asset" || {
|
||||||
echo "Download fehlgeschlagen: $BASE/$OWNER/$repo/releases/download/$TAG/$asset" >&2
|
echo "Download fehlgeschlagen: $BASE/$OWNER/$repo/releases/download/$TAG/$asset" >&2
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
+60
-13
@@ -53,6 +53,11 @@ STOP_OLD=1
|
|||||||
ADOPT_KEY=1
|
ADOPT_KEY=1
|
||||||
TAG="latest"
|
TAG="latest"
|
||||||
ROLLBACK=""
|
ROLLBACK=""
|
||||||
|
|
||||||
|
#: Steht die Sicherung schon, und ist der Lauf durch? Daran entscheidet
|
||||||
|
#: die EXIT-Falle, ob sie zurueckrollt.
|
||||||
|
SICHERUNG_STEHT=0
|
||||||
|
UPGRADE_FERTIG=0
|
||||||
#: Namenszusatz, unter dem ``rename.sh`` eine verdraengte Installation der
|
#: Namenszusatz, unter dem ``rename.sh`` eine verdraengte Installation der
|
||||||
#: Fassung 1.x ablegt. Beide Skripte muessen denselben Wert benutzen; ein
|
#: Fassung 1.x ablegt. Beide Skripte muessen denselben Wert benutzen; ein
|
||||||
#: Hygienetest vergleicht sie.
|
#: Hygienetest vergleicht sie.
|
||||||
@@ -252,6 +257,20 @@ zurueckrollen() {
|
|||||||
info "Zustand pruefen: systemctl status ${hinweis:-$APP.service}"
|
info "Zustand pruefen: systemctl status ${hinweis:-$APP.service}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Wird von der EXIT-Falle des Bootstraps aufgerufen -- mit dem Ausstiegscode.
|
||||||
|
# Damit gilt der Rueckweg fuer **jeden** Fehlschlag und nicht nur fuer den, an
|
||||||
|
# den ich gedacht habe: Export, Uebersetzen, Einspielen, nginx, ein Ctrl-C.
|
||||||
|
beim_beenden() {
|
||||||
|
local code="${1:-0}"
|
||||||
|
[[ "$code" == "0" ]] && return 0
|
||||||
|
[[ $UPGRADE_FERTIG -eq 1 ]] && return 0
|
||||||
|
if [[ $SICHERUNG_STEHT -eq 0 ]]; then
|
||||||
|
warn "Abgebrochen (Code $code), bevor eine Sicherung stand -- nichts zurueckzurollen."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
zurueckrollen "$BACKUP" "Abgebrochen (Code $code)."
|
||||||
|
}
|
||||||
|
|
||||||
neueste_sicherung() {
|
neueste_sicherung() {
|
||||||
local kandidat
|
local kandidat
|
||||||
kandidat="$(ls -1dt /srv/"$NAME"-upgrade-*/ 2>/dev/null | head -1)"
|
kandidat="$(ls -1dt /srv/"$NAME"-upgrade-*/ 2>/dev/null | head -1)"
|
||||||
@@ -286,8 +305,13 @@ fi
|
|||||||
# beide sollen als **einzelne Datei** genuegen: auf das Zielsystem legen,
|
# beide sollen als **einzelne Datei** genuegen: auf das Zielsystem legen,
|
||||||
# ausfuehren, fertig -- auf einer 1.2.8 liegt nichts von diesem Projekt.
|
# ausfuehren, fertig -- auf einer 1.2.8 liegt nichts von diesem Projekt.
|
||||||
#
|
#
|
||||||
# Erwartet: $APP und $TAG. Setzt: $TAG (aufgeloest) und $SRC (entpacktes
|
# Erwartet: $APP und $TAG, wahlweise $REPO (Vorgabe: $APP). Setzt: $TAG
|
||||||
# Release). Raeumt sein Arbeitsverzeichnis beim Beenden auf.
|
# (aufgeloest) und $SRC (entpacktes Release). Raeumt sein Arbeitsverzeichnis
|
||||||
|
# beim Beenden auf.
|
||||||
|
#
|
||||||
|
# $REPO gibt es, weil Installationsname und Repositorium auseinanderfallen
|
||||||
|
# koennen: die Master-Ausgabe des Lizenzservers liegt in
|
||||||
|
# "tesm-license-master", die Installation heisst weiter "tesm-license".
|
||||||
#
|
#
|
||||||
# Zwei Fallen des Vorgaengers sind hier vermieden:
|
# Zwei Fallen des Vorgaengers sind hier vermieden:
|
||||||
# * Der Tag "latest" wird ueber die API aufgeloest, nie direkt in die
|
# * Der Tag "latest" wird ueber die API aufgeloest, nie direkt in die
|
||||||
@@ -297,6 +321,15 @@ fi
|
|||||||
BASE="${TESM_RELEASE_BASE:-https://gitea.int.eertmoed.net}"
|
BASE="${TESM_RELEASE_BASE:-https://gitea.int.eertmoed.net}"
|
||||||
OWNER="${TESM_RELEASE_OWNER:-alientim}"
|
OWNER="${TESM_RELEASE_OWNER:-alientim}"
|
||||||
|
|
||||||
|
# Ein privates Repositorium -- tesm-license-master -- gibt sein Release nur mit
|
||||||
|
# Zugriffstoken heraus. Reihenfolge: Umgebungsvariable, dann eine Datei, die
|
||||||
|
# der Betreiber selbst anlegt. Der Installer legt sie nie an: ob ein Token auf
|
||||||
|
# dem Server liegt, ist keine Entscheidung eines Skripts.
|
||||||
|
TOKEN="${TESM_RELEASE_TOKEN:-}"
|
||||||
|
if [[ -z "$TOKEN" && -r /etc/tesm-release.token ]]; then
|
||||||
|
TOKEN="$(tr -d '[:space:]' < /etc/tesm-release.token)"
|
||||||
|
fi
|
||||||
|
|
||||||
# Ein Wagenruecklauf im Paket macht jedes Shell-Skript auf dem Ziel unbrauchbar
|
# Ein Wagenruecklauf im Paket macht jedes Shell-Skript auf dem Ziel unbrauchbar
|
||||||
# ("bad interpreter"). Die Pruefung darf dabei nicht selbst von einem
|
# ("bad interpreter"). Die Pruefung darf dabei nicht selbst von einem
|
||||||
# Zusatzpaket abhaengen: mit "file" -- das auf einem schlanken Server fehlt --
|
# Zusatzpaket abhaengen: mit "file" -- das auf einem schlanken Server fehlt --
|
||||||
@@ -312,11 +345,24 @@ hat_crlf() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BOOTSTRAP_WORK=""
|
BOOTSTRAP_WORK=""
|
||||||
bootstrap_aufraeumen() { [[ -n "$BOOTSTRAP_WORK" ]] && rm -rf "$BOOTSTRAP_WORK"; }
|
bootstrap_aufraeumen() {
|
||||||
|
# Den Ausstiegscode zuerst festhalten: alles danach ueberschreibt ihn.
|
||||||
|
local code=$?
|
||||||
|
[[ -n "$BOOTSTRAP_WORK" ]] && rm -rf "$BOOTSTRAP_WORK"
|
||||||
|
# Wer einen eigenen Abschluss braucht -- etwa einen Rueckweg nach einem
|
||||||
|
# Fehlschlag --, definiert "beim_beenden". Ein zweiter "trap ... EXIT" waere
|
||||||
|
# der falsche Weg: er wuerde diesen hier ersetzen, und das entpackte Release
|
||||||
|
# bliebe unter /tmp liegen.
|
||||||
|
declare -F beim_beenden >/dev/null 2>&1 && beim_beenden "$code"
|
||||||
|
return "$code"
|
||||||
|
}
|
||||||
trap bootstrap_aufraeumen EXIT
|
trap bootstrap_aufraeumen EXIT
|
||||||
|
|
||||||
release_holen() {
|
release_holen() {
|
||||||
local repo="${TESM_RELEASE_REPO:-$APP}" asset
|
local repo="${TESM_RELEASE_REPO:-${REPO:-$APP}}" asset
|
||||||
|
# Leeres Array und "set -u" vertragen sich nur mit dieser Schreibweise.
|
||||||
|
local -a kopf=()
|
||||||
|
[[ -n "$TOKEN" ]] && kopf=(-H "Authorization: token $TOKEN")
|
||||||
for werkzeug in curl tar python3 find; do
|
for werkzeug in curl tar python3 find; do
|
||||||
command -v "$werkzeug" >/dev/null 2>&1 || {
|
command -v "$werkzeug" >/dev/null 2>&1 || {
|
||||||
echo "Auf diesem System fehlt $werkzeug -- ohne das geht es nicht." >&2
|
echo "Auf diesem System fehlt $werkzeug -- ohne das geht es nicht." >&2
|
||||||
@@ -327,9 +373,11 @@ release_holen() {
|
|||||||
|
|
||||||
if [[ "$TAG" == "latest" ]]; then
|
if [[ "$TAG" == "latest" ]]; then
|
||||||
echo "==> Ermittle aktuellen Release-Tag ..."
|
echo "==> Ermittle aktuellen Release-Tag ..."
|
||||||
TAG="$(curl -fsS "$BASE/api/v1/repos/$OWNER/$repo/releases/latest" \
|
TAG="$(curl -fsS ${kopf[0]+"${kopf[@]}"} "$BASE/api/v1/repos/$OWNER/$repo/releases/latest" \
|
||||||
| python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])')" || {
|
| python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])')" || {
|
||||||
echo "Kein Release gefunden unter $BASE/$OWNER/$repo." >&2
|
echo "Kein Release gefunden unter $BASE/$OWNER/$repo." >&2
|
||||||
|
[[ -z "$TOKEN" ]] &&
|
||||||
|
echo "Ist das Repositorium privat? Dann TESM_RELEASE_TOKEN setzen." >&2
|
||||||
echo "Andere Quelle: TESM_RELEASE_BASE, TESM_RELEASE_OWNER, TESM_RELEASE_REPO." >&2
|
echo "Andere Quelle: TESM_RELEASE_BASE, TESM_RELEASE_OWNER, TESM_RELEASE_REPO." >&2
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -338,7 +386,7 @@ release_holen() {
|
|||||||
|
|
||||||
asset="$repo-$TAG.tar.gz"
|
asset="$repo-$TAG.tar.gz"
|
||||||
echo "==> Lade $asset ..."
|
echo "==> Lade $asset ..."
|
||||||
curl -fsSL -o "$BOOTSTRAP_WORK/$asset" \
|
curl -fsSL ${kopf[0]+"${kopf[@]}"} -o "$BOOTSTRAP_WORK/$asset" \
|
||||||
"$BASE/$OWNER/$repo/releases/download/$TAG/$asset" || {
|
"$BASE/$OWNER/$repo/releases/download/$TAG/$asset" || {
|
||||||
echo "Download fehlgeschlagen: $BASE/$OWNER/$repo/releases/download/$TAG/$asset" >&2
|
echo "Download fehlgeschlagen: $BASE/$OWNER/$repo/releases/download/$TAG/$asset" >&2
|
||||||
return 1
|
return 1
|
||||||
@@ -514,6 +562,7 @@ for unit in "${ALTE_UNITS[@]}"; do
|
|||||||
done
|
done
|
||||||
[[ -L "/etc/nginx/sites-enabled/$NAME" ]] && printf 'site-enabled %s\n' "$NAME" >> "$SYSTEM_KOPIE/manifest"
|
[[ -L "/etc/nginx/sites-enabled/$NAME" ]] && printf 'site-enabled %s\n' "$NAME" >> "$SYSTEM_KOPIE/manifest"
|
||||||
info "$(grep -c . "$SYSTEM_KOPIE/manifest") Systemdatei(en) und Zustaende vermerkt"
|
info "$(grep -c . "$SYSTEM_KOPIE/manifest") Systemdatei(en) und Zustaende vermerkt"
|
||||||
|
SICHERUNG_STEHT=1
|
||||||
|
|
||||||
# -- 2. Altes System abschalten ---------------------------------------------- #
|
# -- 2. Altes System abschalten ---------------------------------------------- #
|
||||||
if [[ $STOP_OLD -eq 1 ]]; then
|
if [[ $STOP_OLD -eq 1 ]]; then
|
||||||
@@ -532,15 +581,12 @@ fi
|
|||||||
step "Neu installieren"
|
step "Neu installieren"
|
||||||
INSTALL_ARGS=(--app "$APP" --yes)
|
INSTALL_ARGS=(--app "$APP" --yes)
|
||||||
[[ -n "$INSTANCE" ]] && INSTALL_ARGS+=(--instance "$INSTANCE")
|
[[ -n "$INSTANCE" ]] && INSTALL_ARGS+=(--instance "$INSTANCE")
|
||||||
if ! bash "$SRC/deploy/install.sh" "${INSTALL_ARGS[@]}" ${EXTRA[0]+"${EXTRA[@]}"}; then
|
# Kein eigener Fehlerzweig mehr: die EXIT-Falle rollt bei jedem Fehlschlag
|
||||||
# Hier stand das System bisher halb umgebaut da: alter Dienst aus, neuer
|
# zurueck -- hier, beim Export, beim Einspielen oder bei einem Ctrl-C.
|
||||||
# nicht vorhanden. Der Rueckweg bringt es in den Zustand von vorher, damit
|
bash "$SRC/deploy/install.sh" "${INSTALL_ARGS[@]}" ${EXTRA[0]+"${EXTRA[@]}"}
|
||||||
# ein zweiter Versuch nach der Ursache moeglich ist.
|
|
||||||
zurueckrollen "$BACKUP" "Die Installation ist fehlgeschlagen."
|
|
||||||
fail "Upgrade abgebrochen und zurueckgerollt. Ursache oben nachsehen, dann erneut versuchen."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $WITH_DATA -eq 0 ]]; then
|
if [[ $WITH_DATA -eq 0 ]]; then
|
||||||
|
UPGRADE_FERTIG=1
|
||||||
step "Fertig -- ohne Datenuebernahme"
|
step "Fertig -- ohne Datenuebernahme"
|
||||||
info "Sicherung der alten Installation: $ALT_KOPIE"
|
info "Sicherung der alten Installation: $ALT_KOPIE"
|
||||||
exit 0
|
exit 0
|
||||||
@@ -592,6 +638,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# -- Abschluss --------------------------------------------------------------- #
|
# -- Abschluss --------------------------------------------------------------- #
|
||||||
|
UPGRADE_FERTIG=1
|
||||||
step "Fertig"
|
step "Fertig"
|
||||||
info "Sicherung der alten Installation : $ALT_KOPIE"
|
info "Sicherung der alten Installation : $ALT_KOPIE"
|
||||||
if [[ "$APP" == "tesm" ]]; then
|
if [[ "$APP" == "tesm" ]]; then
|
||||||
|
|||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# Installation -- der kurze Weg.
|
||||||
|
#
|
||||||
|
# Diese Datei liegt oben im Paket und ruft den Installer unter deploy/ auf:
|
||||||
|
#
|
||||||
|
# tar xzf tesm-vX.Y.Z.tar.gz && cd tesm
|
||||||
|
# sudo bash install.sh --app tesm --https
|
||||||
|
#
|
||||||
|
# Sie genuegt aber auch **allein**: liegt kein Paket um sie herum, holt sie
|
||||||
|
# sich das aktuelle Release selbst.
|
||||||
|
#
|
||||||
|
# curl -fsSLO https://gitea.int.eertmoed.net/alientim/tesm/raw/branch/main/install.sh
|
||||||
|
# sudo bash install.sh --app tesm --https
|
||||||
|
#
|
||||||
|
# Die Master-Ausgabe des Lizenzservers liegt im privaten Repositorium
|
||||||
|
# tesm-license-master. Allein-Betrieb dafuer:
|
||||||
|
#
|
||||||
|
# export TESM_RELEASE_REPO=tesm-license-master
|
||||||
|
# export TESM_RELEASE_TOKEN=<Gitea-Token>
|
||||||
|
# sudo -E bash install.sh --app tesm-license --https
|
||||||
|
#
|
||||||
|
# Aus einem entpackten Paket braucht es das nicht: dort entscheidet, welches
|
||||||
|
# Paket es ist. Die Master-Ausgabe legt beim Installieren Signaturschluessel
|
||||||
|
# und eigene Master-Lizenz selbst an.
|
||||||
|
#
|
||||||
|
# Alle Argumente gehen unveraendert an deploy/install.sh weiter; dessen Hilfe:
|
||||||
|
#
|
||||||
|
# bash install.sh --help
|
||||||
|
# =============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# --app wird nur gelesen, nicht verbraucht: der Bootstrap braucht es fuer den
|
||||||
|
# Namen des Repositoriums, der Installer bekommt die Argumente unveraendert.
|
||||||
|
APP=""
|
||||||
|
TAG="latest"
|
||||||
|
ARGUMENTE=("$@")
|
||||||
|
for ((i = 0; i < $#; i++)); do
|
||||||
|
case "${ARGUMENTE[i]}" in
|
||||||
|
--app) APP="${ARGUMENTE[i + 1]:-}" ;;
|
||||||
|
--tag) TAG="${ARGUMENTE[i + 1]:-latest}" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Ohne Paket drumherum brauchen wir den Namen, um das richtige Release zu
|
||||||
|
# finden -- die beiden Anwendungen liegen in getrennten Repositorien.
|
||||||
|
if [[ ! -f "$SCRIPT_DIR/deploy/install.sh" ]]; then
|
||||||
|
[[ "$APP" == "tesm" || "$APP" == "tesm-license" ]] || {
|
||||||
|
echo "Diese Datei liegt allein hier und muss sich das Release holen --" >&2
|
||||||
|
echo "dafuer braucht sie --app <tesm|tesm-license>." >&2
|
||||||
|
echo "Verwendung: install.sh --app <tesm|tesm-license> [weitere Optionen]" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --8<-- Bootstrap: Release holen --------------------------------------------
|
||||||
|
# Wortgleich in update.sh und upgrade.sh; ein Hygienetest vergleicht die beiden
|
||||||
|
# Bloecke. Zwei eigenstaendige Skripte koennen sich keine Datei teilen, und
|
||||||
|
# beide sollen als **einzelne Datei** genuegen: auf das Zielsystem legen,
|
||||||
|
# ausfuehren, fertig -- auf einer 1.2.8 liegt nichts von diesem Projekt.
|
||||||
|
#
|
||||||
|
# Erwartet: $APP und $TAG, wahlweise $REPO (Vorgabe: $APP). Setzt: $TAG
|
||||||
|
# (aufgeloest) und $SRC (entpacktes Release). Raeumt sein Arbeitsverzeichnis
|
||||||
|
# beim Beenden auf.
|
||||||
|
#
|
||||||
|
# $REPO gibt es, weil Installationsname und Repositorium auseinanderfallen
|
||||||
|
# koennen: die Master-Ausgabe des Lizenzservers liegt in
|
||||||
|
# "tesm-license-master", die Installation heisst weiter "tesm-license".
|
||||||
|
#
|
||||||
|
# Zwei Fallen des Vorgaengers sind hier vermieden:
|
||||||
|
# * Der Tag "latest" wird ueber die API aufgeloest, nie direkt in die
|
||||||
|
# Download-URL geschrieben. Gitea liefert sonst mit HTTP 200 ein
|
||||||
|
# automatisch erzeugtes Quell-Snapshot statt des echten Release-Anhangs.
|
||||||
|
# * Nach dem Entpacken wird geprueft, dass ein install.sh existiert.
|
||||||
|
BASE="${TESM_RELEASE_BASE:-https://gitea.int.eertmoed.net}"
|
||||||
|
OWNER="${TESM_RELEASE_OWNER:-alientim}"
|
||||||
|
|
||||||
|
# Ein privates Repositorium -- tesm-license-master -- gibt sein Release nur mit
|
||||||
|
# Zugriffstoken heraus. Reihenfolge: Umgebungsvariable, dann eine Datei, die
|
||||||
|
# der Betreiber selbst anlegt. Der Installer legt sie nie an: ob ein Token auf
|
||||||
|
# dem Server liegt, ist keine Entscheidung eines Skripts.
|
||||||
|
TOKEN="${TESM_RELEASE_TOKEN:-}"
|
||||||
|
if [[ -z "$TOKEN" && -r /etc/tesm-release.token ]]; then
|
||||||
|
TOKEN="$(tr -d '[:space:]' < /etc/tesm-release.token)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ein Wagenruecklauf im Paket macht jedes Shell-Skript auf dem Ziel unbrauchbar
|
||||||
|
# ("bad interpreter"). Die Pruefung darf dabei nicht selbst von einem
|
||||||
|
# Zusatzpaket abhaengen: mit "file" -- das auf einem schlanken Server fehlt --
|
||||||
|
# endete sie in "command not found", grep bekam nichts zu sehen, und das Skript
|
||||||
|
# machte weiter, als waere alles in Ordnung.
|
||||||
|
hat_crlf() {
|
||||||
|
local cr
|
||||||
|
cr="$(printf '\r')"
|
||||||
|
# -U (binaer lesen) ist unter Linux ohne Wirkung, auf einer
|
||||||
|
# Windows-Buildmaschine aber entscheidend: dort entfernt grep den
|
||||||
|
# Wagenruecklauf beim Lesen und findet deshalb nie einen.
|
||||||
|
LC_ALL=C grep -qU "$cr" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOTSTRAP_WORK=""
|
||||||
|
bootstrap_aufraeumen() {
|
||||||
|
# Den Ausstiegscode zuerst festhalten: alles danach ueberschreibt ihn.
|
||||||
|
local code=$?
|
||||||
|
[[ -n "$BOOTSTRAP_WORK" ]] && rm -rf "$BOOTSTRAP_WORK"
|
||||||
|
# Wer einen eigenen Abschluss braucht -- etwa einen Rueckweg nach einem
|
||||||
|
# Fehlschlag --, definiert "beim_beenden". Ein zweiter "trap ... EXIT" waere
|
||||||
|
# der falsche Weg: er wuerde diesen hier ersetzen, und das entpackte Release
|
||||||
|
# bliebe unter /tmp liegen.
|
||||||
|
declare -F beim_beenden >/dev/null 2>&1 && beim_beenden "$code"
|
||||||
|
return "$code"
|
||||||
|
}
|
||||||
|
trap bootstrap_aufraeumen EXIT
|
||||||
|
|
||||||
|
release_holen() {
|
||||||
|
local repo="${TESM_RELEASE_REPO:-${REPO:-$APP}}" asset
|
||||||
|
# Leeres Array und "set -u" vertragen sich nur mit dieser Schreibweise.
|
||||||
|
local -a kopf=()
|
||||||
|
[[ -n "$TOKEN" ]] && kopf=(-H "Authorization: token $TOKEN")
|
||||||
|
for werkzeug in curl tar python3 find; do
|
||||||
|
command -v "$werkzeug" >/dev/null 2>&1 || {
|
||||||
|
echo "Auf diesem System fehlt $werkzeug -- ohne das geht es nicht." >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
BOOTSTRAP_WORK="$(mktemp -d /tmp/tesm-release-XXXXXX)"
|
||||||
|
|
||||||
|
if [[ "$TAG" == "latest" ]]; then
|
||||||
|
echo "==> Ermittle aktuellen Release-Tag ..."
|
||||||
|
TAG="$(curl -fsS ${kopf[0]+"${kopf[@]}"} "$BASE/api/v1/repos/$OWNER/$repo/releases/latest" \
|
||||||
|
| python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])')" || {
|
||||||
|
echo "Kein Release gefunden unter $BASE/$OWNER/$repo." >&2
|
||||||
|
[[ -z "$TOKEN" ]] &&
|
||||||
|
echo "Ist das Repositorium privat? Dann TESM_RELEASE_TOKEN setzen." >&2
|
||||||
|
echo "Andere Quelle: TESM_RELEASE_BASE, TESM_RELEASE_OWNER, TESM_RELEASE_REPO." >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
echo " $TAG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
asset="$repo-$TAG.tar.gz"
|
||||||
|
echo "==> Lade $asset ..."
|
||||||
|
curl -fsSL ${kopf[0]+"${kopf[@]}"} -o "$BOOTSTRAP_WORK/$asset" \
|
||||||
|
"$BASE/$OWNER/$repo/releases/download/$TAG/$asset" || {
|
||||||
|
echo "Download fehlgeschlagen: $BASE/$OWNER/$repo/releases/download/$TAG/$asset" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "==> Entpacke ..."
|
||||||
|
tar -xzf "$BOOTSTRAP_WORK/$asset" -C "$BOOTSTRAP_WORK"
|
||||||
|
# Drei Ebenen, nicht zwei: release.sh packt mit dem Praefix "<name>/", also
|
||||||
|
# liegt das Skript unter <name>/deploy/install.sh. Mit "-maxdepth 2" fand die
|
||||||
|
# Suche nie etwas -- und die Fehlermeldung schob es auf den Download.
|
||||||
|
SRC="$(find "$BOOTSTRAP_WORK" -maxdepth 3 -name install.sh -path '*/deploy/*' \
|
||||||
|
-printf '%h/..\n' | head -1)"
|
||||||
|
[[ -n "$SRC" ]] || {
|
||||||
|
echo "Im Paket fehlt deploy/install.sh -- Download unbrauchbar." >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
SRC="$(cd "$SRC" && pwd)"
|
||||||
|
|
||||||
|
echo "==> Pruefe auf CRLF (Windows-Buildmaschinen) ..."
|
||||||
|
if hat_crlf "$SRC/deploy/install.sh"; then
|
||||||
|
echo "Das Paket enthaelt CRLF-Zeilenenden. Beim Paketieren fehlte" >&2
|
||||||
|
echo "'git -c core.autocrlf=false archive'." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
# --8<-- Ende Bootstrap ------------------------------------------------------
|
||||||
|
|
||||||
|
if [[ -f "$SCRIPT_DIR/deploy/install.sh" ]]; then
|
||||||
|
# Im Paket: der Installer liegt daneben.
|
||||||
|
exec bash "$SCRIPT_DIR/deploy/install.sh" ${ARGUMENTE[0]+"${ARGUMENTE[@]}"}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Allein: Release holen und dessen Installer benutzen. Kein exec -- sonst
|
||||||
|
# laeuft die Aufraeumfalle nie und das entpackte Release bliebe unter /tmp.
|
||||||
|
release_holen || { echo "Das Release liess sich nicht holen." >&2; exit 1; }
|
||||||
|
bash "$SRC/deploy/install.sh" ${ARGUMENTE[0]+"${ARGUMENTE[@]}"}
|
||||||
Reference in new issue
Block a user