Merge pull request 'Auto-Update per Push-Webhook fuer Produktionsserver einrichten' (#37) from fds2610-bot/monitor:main into main
Reviewed-on: https://codeberg.org/fds2610/monitor/pulls/37
This commit is contained in:
commit
5471940389
6 changed files with 283 additions and 0 deletions
13
.env.example
13
.env.example
|
|
@ -39,3 +39,16 @@ MONITOR_SYNC_INTERVAL=600
|
||||||
# Der Endpunkt ist der einzige, den Codeberg von aussen erreichen muss - Proxy und Firewall
|
# Der Endpunkt ist der einzige, den Codeberg von aussen erreichen muss - Proxy und Firewall
|
||||||
# muessen POST /api/v1/webhooks/codeberg also aus dem Internet durchlassen.
|
# muessen POST /api/v1/webhooks/codeberg also aus dem Internet durchlassen.
|
||||||
CODEBERG_WEBHOOK_SECRET=
|
CODEBERG_WEBHOOK_SECRET=
|
||||||
|
|
||||||
|
# Auto-Update auf einem Produktionsserver (siehe README_UPDATE.md): deploy-webhook.py laeuft
|
||||||
|
# dort auf dem Host (nicht im Compose-Stack) und stoesst bei einem Push auf
|
||||||
|
# MONITOR_DEPLOY_WEBHOOK_BRANCH (z.B. nach einem manuell gemergten PR) update.sh an. Fuer die
|
||||||
|
# lokale Entwicklungsumgebung hier ohne Bedeutung.
|
||||||
|
# Secret des Deploy-Webhooks (Repo -> Einstellungen -> Webhooks -> Forgejo, Ziel-URL
|
||||||
|
# <deploy-webhook-url>, Event "Push"). Ohne gesetztes Secret weist deploy-webhook.py jede
|
||||||
|
# Anfrage mit 401 ab. Erzeugen z.B. mit: openssl rand -hex 32
|
||||||
|
MONITOR_DEPLOY_WEBHOOK_SECRET=
|
||||||
|
# Port, auf dem deploy-webhook.py lauscht.
|
||||||
|
MONITOR_DEPLOY_WEBHOOK_PORT=9099
|
||||||
|
# Branch, dessen Push ein Update ausloest.
|
||||||
|
MONITOR_DEPLOY_WEBHOOK_BRANCH=main
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,3 +2,5 @@ api/vendor/
|
||||||
api/data/
|
api/data/
|
||||||
.env
|
.env
|
||||||
node_modules/
|
node_modules/
|
||||||
|
__pycache__/
|
||||||
|
deploy-webhook.log
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ die dazugehörigen Klärungen in [Detailfragen.md](Detailfragen.md) und
|
||||||
Webhook eingerichtet ist, innerhalb von Sekunden nach jeder Issue-Änderung
|
Webhook eingerichtet ist, innerhalb von Sekunden nach jeder Issue-Änderung
|
||||||
(`POST /api/v1/webhooks/codeberg`, HMAC-signiert per `CODEBERG_WEBHOOK_SECRET`). Siehe
|
(`POST /api/v1/webhooks/codeberg`, HMAC-signiert per `CODEBERG_WEBHOOK_SECRET`). Siehe
|
||||||
[API.md](API.md) Abschnitt „Codeberg-Sync" — dort steht auch die Einrichtung im Codeberg-Repo.
|
[API.md](API.md) Abschnitt „Codeberg-Sync" — dort steht auch die Einrichtung im Codeberg-Repo.
|
||||||
|
- Auto-Update auf einem Produktionsserver: Sobald ein PR nach `main` gemergt wird, kann
|
||||||
|
`deploy-webhook.py` denselben `update.sh` anstoßen, den auch ein manuelles Update aufruft
|
||||||
|
(Push-Webhook, HMAC-signiert per `MONITOR_DEPLOY_WEBHOOK_SECRET`) — siehe
|
||||||
|
[README_UPDATE.md](README_UPDATE.md).
|
||||||
- Weiterhin: klassische Ping-Überwachung (`check_fds.sh`, `hosts.conf`, `mail_fds.py`) als
|
- Weiterhin: klassische Ping-Überwachung (`check_fds.sh`, `hosts.conf`, `mail_fds.py`) als
|
||||||
eine Datenquelle für den System-Monitor; die alte tabellarische Statusseite ist mit
|
eine Datenquelle für den System-Monitor; die alte tabellarische Statusseite ist mit
|
||||||
Phase 7 entfallen (F16), `status.txt` speist weiterhin den System-Monitor
|
Phase 7 entfallen (F16), `status.txt` speist weiterhin den System-Monitor
|
||||||
|
|
@ -46,6 +50,9 @@ die dazugehörigen Klärungen in [Detailfragen.md](Detailfragen.md) und
|
||||||
- `docker-compose.yml` — komplette Entwicklungsumgebung (siehe unten)
|
- `docker-compose.yml` — komplette Entwicklungsumgebung (siehe unten)
|
||||||
- `update.sh` — holt die neueste Version vom konfigurierten Git-Remote und aktualisiert die
|
- `update.sh` — holt die neueste Version vom konfigurierten Git-Remote und aktualisiert die
|
||||||
Docker-Compose-Umgebung (Abhängigkeiten, Images, Dienste, Tests)
|
Docker-Compose-Umgebung (Abhängigkeiten, Images, Dienste, Tests)
|
||||||
|
- `deploy-webhook.py`, `deploy/` — löst `update.sh` auf einem Produktionsserver automatisch
|
||||||
|
aus, sobald ein PR nach `main` gemergt wird (Push-Webhook); siehe
|
||||||
|
[README_UPDATE.md](README_UPDATE.md)
|
||||||
- `register-agent.sh` — Hilfsskript, um einen neuen Agenten per `POST /api/v1/agents` zu
|
- `register-agent.sh` — Hilfsskript, um einen neuen Agenten per `POST /api/v1/agents` zu
|
||||||
registrieren (siehe [API.md](API.md) Abschnitt "Agenten")
|
registrieren (siehe [API.md](API.md) Abschnitt "Agenten")
|
||||||
|
|
||||||
|
|
|
||||||
94
README_UPDATE.md
Normal file
94
README_UPDATE.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Auto-Update auf einem Produktionsserver
|
||||||
|
|
||||||
|
Dieses Dokument beschreibt, wie ein bereits laufendes `monitor` auf einem Produktionsserver
|
||||||
|
sich selbst aktualisiert, sobald der Repo-Besitzer einen Pull Request nach `main` mergt.
|
||||||
|
Anders als im parallelen [Agent-Projekt](https://codeberg.org/fds2610/Agent) gibt es hier
|
||||||
|
**keine** Trennung zwischen einem unveraenderlichen "Bootstrap"-Updater und dem eigentlichen
|
||||||
|
Projekt-Code - `update.sh` gehoert direkt zum Projekt und wird bei jedem Update mit
|
||||||
|
aktualisiert.
|
||||||
|
|
||||||
|
## Bausteine
|
||||||
|
|
||||||
|
- **`update.sh`** (Projekt-Wurzel) - holt die neueste Version des konfigurierten Branches per
|
||||||
|
`git pull --ff-only`, baut die Docker-Images neu, startet `api`, `web` und `sync` neu und
|
||||||
|
fuehrt (per Default) die Testsuiten aus. Kann jederzeit auch manuell aufgerufen werden, siehe
|
||||||
|
Kommentar am Kopf der Datei fuer die Umgebungsvariablen `UPDATE_REMOTE`, `UPDATE_BRANCH`,
|
||||||
|
`UPDATE_RUN_TESTS`.
|
||||||
|
- **`deploy-webhook.py`** (Projekt-Wurzel) - kleiner HTTP-Server (nur Python-Standardbibliothek,
|
||||||
|
keine Abhaengigkeiten) auf Basis von `http.server`. Prueft eingehende Codeberg-Webhooks per
|
||||||
|
HMAC-Signatur (`X-Forgejo-Signature`/`X-Gitea-Signature`, analog zum bestehenden
|
||||||
|
`POST /api/v1/webhooks/codeberg` aus [API.md](API.md)) und ruft bei einem Push auf den
|
||||||
|
konfigurierten Branch `update.sh` auf. Laeuft **auf dem Host**, nicht im Docker-Compose-Stack
|
||||||
|
- `update.sh` selbst ruft `docker compose` auf dem Host auf, das waere aus einem Container
|
||||||
|
heraus nur mit riskantem Zugriff auf den Docker-Socket moeglich.
|
||||||
|
- **`deploy/monitor-deploy-webhook.service`** - systemd-Unit-Vorlage, um `deploy-webhook.py`
|
||||||
|
dauerhaft im Hintergrund laufen zu lassen.
|
||||||
|
|
||||||
|
## Manuelle Vorarbeiten auf dem Server
|
||||||
|
|
||||||
|
Diese Schritte sind einmalig und **nicht** Teil dieses Repos (unterschiedlich je nach Server),
|
||||||
|
daher hier nur als Checkliste:
|
||||||
|
|
||||||
|
1. **Repo auskecken.** `git clone https://codeberg.org/fds2610/monitor.git` (oder per SSH,
|
||||||
|
falls Push-Zugangsdaten fuer `git pull` als Deploy-Key/SSH-Key eingerichtet werden) nach
|
||||||
|
z.B. `/opt/monitor`, Branch `main` ausgecheckt. Der Arbeitsbaum muss beim Update sauber
|
||||||
|
sein - `update.sh` bricht kontrolliert ab, wenn lokale Aenderungen vorhanden sind, statt sie
|
||||||
|
zu ueberschreiben.
|
||||||
|
2. **Docker + Docker Compose** installieren (`docker compose version` muss funktionieren).
|
||||||
|
Der Benutzer, unter dem `deploy-webhook.py` laeuft, braucht Mitgliedschaft in der
|
||||||
|
`docker`-Gruppe (oder aequivalente Rechte), um `docker compose` ohne `sudo` aufzurufen.
|
||||||
|
3. **Python 3** installieren (Standardbibliothek genuegt, keine weiteren Pakete noetig) -
|
||||||
|
`mail_fds.py` im selben Repo setzt das ohnehin schon voraus.
|
||||||
|
4. **`.env` anlegen** aus `.env.example`, insbesondere `MONITOR_ADMIN_API_KEY`,
|
||||||
|
`MONITOR_API_BASE` und die neuen Variablen `MONITOR_DEPLOY_WEBHOOK_SECRET`,
|
||||||
|
`MONITOR_DEPLOY_WEBHOOK_PORT`, `MONITOR_DEPLOY_WEBHOOK_BRANCH` (Details siehe dort). Das
|
||||||
|
Secret z.B. mit `openssl rand -hex 32` erzeugen.
|
||||||
|
5. **systemd-Unit einrichten:**
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/monitor-deploy-webhook.service /etc/systemd/system/
|
||||||
|
sudo $EDITOR /etc/systemd/system/monitor-deploy-webhook.service # Pfade/User/Group anpassen
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now monitor-deploy-webhook
|
||||||
|
sudo systemctl status monitor-deploy-webhook
|
||||||
|
```
|
||||||
|
6. **Port/Proxy freischalten.** `deploy-webhook.py` lauscht auf `MONITOR_DEPLOY_WEBHOOK_PORT`
|
||||||
|
(Default `9099`) auf allen Interfaces. Analog zum bestehenden Codeberg-Sync-Webhook muss
|
||||||
|
Proxy/Firewall eingehende Anfragen von Codeberg an diesen Port (bzw. an einen dahinter
|
||||||
|
liegenden Reverse-Proxy-Pfad) durchlassen - alle anderen Dienste (`api`, `web`, `sync`)
|
||||||
|
brauchen dafuer keine Aenderung.
|
||||||
|
7. **Webhook in Codeberg einrichten** (macht der Repo-Besitzer manuell, siehe Issue #36):
|
||||||
|
Repo-Einstellungen -> Webhooks -> Forgejo, Ziel-URL auf `deploy-webhook.py` (Port aus Schritt
|
||||||
|
6, ggf. per Proxy auf `https://...` gemappt), Content-Type `application/json`, Secret wie in
|
||||||
|
`.env` (`MONITOR_DEPLOY_WEBHOOK_SECRET`), Ereignis **nur** "Push". Ein Merge erzeugt immer
|
||||||
|
einen Push auf den Ziel-Branch, ein eigenes "Pull Request merged"-Event ist daher nicht
|
||||||
|
noetig.
|
||||||
|
|
||||||
|
## Testen
|
||||||
|
|
||||||
|
Zustellung testen (Codeberg schickt beim Anlegen automatisch ein `ping`-Event, sichtbar unter
|
||||||
|
"Letzte Zustellungen" im Webhook selbst). Manuell mit korrekter Signatur:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SECRET=<MONITOR_DEPLOY_WEBHOOK_SECRET aus .env>
|
||||||
|
BODY='{"ref":"refs/heads/main"}'
|
||||||
|
SIG=$(python3 -c "import hmac,hashlib,sys; print(hmac.new(sys.argv[1].encode(), sys.argv[2].encode(), hashlib.sha256).hexdigest())" "$SECRET" "$BODY")
|
||||||
|
curl -i -X POST "http://<server>:9099/" \
|
||||||
|
-H "X-Forgejo-Event: push" \
|
||||||
|
-H "X-Forgejo-Signature: $SIG" \
|
||||||
|
-d "$BODY"
|
||||||
|
```
|
||||||
|
|
||||||
|
Antwort `202 {"status": "update gestartet"}` bedeutet, dass `update.sh` im Hintergrund laeuft.
|
||||||
|
Der Verlauf landet in `MONITOR_DEPLOY_LOG_FILE` (Default: `deploy-webhook.log` neben
|
||||||
|
`deploy-webhook.py`) sowie im systemd-Journal (`journalctl -u monitor-deploy-webhook -f`).
|
||||||
|
|
||||||
|
## Sicherheit
|
||||||
|
|
||||||
|
Der Endpunkt ist wie `POST /api/v1/webhooks/codeberg` ohne Secret grundsaetzlich geschlossen
|
||||||
|
(401) und akzeptiert nur Anfragen mit gueltiger HMAC-Signatur. Er loest im Erfolgsfall
|
||||||
|
lediglich denselben `git pull --ff-only` + Rebuild aus, den ein Server-Betreiber auch manuell
|
||||||
|
per `./update.sh` anstossen wuerde - er bringt also keinen Code auf den Server, der nicht
|
||||||
|
bereits reviewt und **manuell** nach `main` gemergt wurde. Diese manuelle Merge-Hürde ist laut
|
||||||
|
Repo-Besitzer (Issue #36) als alleinige Sicherheitsmassnahme ausreichend; zusaetzliche
|
||||||
|
Branch-Protection-Einstellungen im Codeberg-Repo (z.B. kein direkter Push nach `main`) bleiben
|
||||||
|
trotzdem empfehlenswert, sind aber eine manuelle Repo-Einstellung und nicht Teil dieses Skripts.
|
||||||
145
deploy-webhook.py
Executable file
145
deploy-webhook.py
Executable file
|
|
@ -0,0 +1,145 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Empfaengt den Codeberg-Webhook fuer fds2610/monitor und stoesst bei einem Push auf den
|
||||||
|
konfigurierten Branch (Standard: main, d.h. nach jedem manuell gemergten PR) ein Update per
|
||||||
|
update.sh an. Laeuft direkt auf dem Host (nicht im Docker-Compose-Stack), weil update.sh
|
||||||
|
selbst "docker compose" auf dem Host aufruft.
|
||||||
|
|
||||||
|
Konfiguration ueber Umgebungsvariablen:
|
||||||
|
MONITOR_DEPLOY_WEBHOOK_SECRET - Pflicht. HMAC-Secret, dasselbe wie beim Einrichten des
|
||||||
|
Webhooks im Codeberg-Repo (Einstellungen -> Webhooks ->
|
||||||
|
Forgejo). Ohne gesetztes Secret weist der Endpunkt jede
|
||||||
|
Anfrage mit 401 ab.
|
||||||
|
MONITOR_DEPLOY_WEBHOOK_PORT - Port, auf dem gelauscht wird (Default: 9099).
|
||||||
|
MONITOR_DEPLOY_WEBHOOK_BRANCH - Branch, dessen Push ein Update ausloest (Default: main).
|
||||||
|
MONITOR_DEPLOY_UPDATE_SCRIPT - Pfad zu update.sh (Default: update.sh neben diesem Skript).
|
||||||
|
MONITOR_DEPLOY_LOG_FILE - Pfad, in den die update.sh-Ausgabe geschrieben wird
|
||||||
|
(Default: deploy-webhook.log neben diesem Skript).
|
||||||
|
|
||||||
|
Siehe README_UPDATE.md fuer die Einrichtung auf einem Produktionsserver.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from datetime import datetime
|
||||||
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO_DIR = Path(__file__).resolve().parent
|
||||||
|
SIGNATURE_HEADERS = ("X-Forgejo-Signature", "X-Gitea-Signature")
|
||||||
|
|
||||||
|
# Verhindert ueberlappende update.sh-Laeufe, falls mehrere Webhook-Zustellungen kurz
|
||||||
|
# hintereinander eintreffen (z.B. Codeberg-Retry). do_POST versucht den Lock nicht-blockierend
|
||||||
|
# zu erwerben, run_update() gibt ihn im finally-Block wieder frei.
|
||||||
|
update_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def verify_signature(secret: str, body: bytes, headers) -> bool:
|
||||||
|
expected = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
|
||||||
|
for name in SIGNATURE_HEADERS:
|
||||||
|
provided = headers.get(name)
|
||||||
|
if provided and hmac.compare_digest(expected, provided.strip().lower()):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def run_update(update_script: Path, log_file: Path) -> None:
|
||||||
|
try:
|
||||||
|
with open(log_file, "ab") as log:
|
||||||
|
log.write(
|
||||||
|
f"\n--- update.sh gestartet ({datetime.now().isoformat()}) ---\n".encode("utf-8")
|
||||||
|
)
|
||||||
|
log.flush()
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
[str(update_script)],
|
||||||
|
cwd=str(update_script.parent),
|
||||||
|
stdout=log,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
except OSError as exc:
|
||||||
|
log.write(f"--- update.sh konnte nicht gestartet werden: {exc} ---\n".encode("utf-8"))
|
||||||
|
finally:
|
||||||
|
update_lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
server_version = "monitor-deploy-webhook/1"
|
||||||
|
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
sys.stderr.write("[deploy-webhook] %s - %s\n" % (self.address_string(), fmt % args))
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
secret = os.environ.get("MONITOR_DEPLOY_WEBHOOK_SECRET", "")
|
||||||
|
length = int(self.headers.get("Content-Length", "0") or "0")
|
||||||
|
body = self.rfile.read(length) if length else b""
|
||||||
|
|
||||||
|
if not secret:
|
||||||
|
self._respond(401, {"error": "kein MONITOR_DEPLOY_WEBHOOK_SECRET konfiguriert"})
|
||||||
|
return
|
||||||
|
if not verify_signature(secret, body, self.headers):
|
||||||
|
self._respond(401, {"error": "Signatur fehlt oder ist ungueltig"})
|
||||||
|
return
|
||||||
|
|
||||||
|
event = self.headers.get("X-Forgejo-Event") or self.headers.get("X-Gitea-Event") or ""
|
||||||
|
if event == "ping":
|
||||||
|
self._respond(200, {"status": "pong"})
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.loads(body or b"{}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
branch = os.environ.get("MONITOR_DEPLOY_WEBHOOK_BRANCH", "main")
|
||||||
|
ref = payload.get("ref", "")
|
||||||
|
if event != "push" or ref != f"refs/heads/{branch}":
|
||||||
|
self._respond(200, {"status": "ignoriert"})
|
||||||
|
return
|
||||||
|
|
||||||
|
update_script = Path(
|
||||||
|
os.environ.get("MONITOR_DEPLOY_UPDATE_SCRIPT", str(REPO_DIR / "update.sh"))
|
||||||
|
)
|
||||||
|
log_file = Path(
|
||||||
|
os.environ.get("MONITOR_DEPLOY_LOG_FILE", str(REPO_DIR / "deploy-webhook.log"))
|
||||||
|
)
|
||||||
|
|
||||||
|
if not update_lock.acquire(blocking=False):
|
||||||
|
self._respond(202, {"status": "update laeuft bereits"})
|
||||||
|
return
|
||||||
|
|
||||||
|
threading.Thread(target=run_update, args=(update_script, log_file), daemon=True).start()
|
||||||
|
self._respond(202, {"status": "update gestartet"})
|
||||||
|
|
||||||
|
def _respond(self, status: int, payload: dict) -> None:
|
||||||
|
body = json.dumps(payload).encode("utf-8")
|
||||||
|
self.send_response(status)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if not os.environ.get("MONITOR_DEPLOY_WEBHOOK_SECRET"):
|
||||||
|
print(
|
||||||
|
"[deploy-webhook] WARNUNG: MONITOR_DEPLOY_WEBHOOK_SECRET ist nicht gesetzt - "
|
||||||
|
"der Endpunkt weist jede Anfrage mit 401 ab.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
port = int(os.environ.get("MONITOR_DEPLOY_WEBHOOK_PORT", "9099"))
|
||||||
|
server = ThreadingHTTPServer(("0.0.0.0", port), Handler)
|
||||||
|
print(f"[deploy-webhook] lausche auf Port {port}", file=sys.stderr)
|
||||||
|
server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
22
deploy/monitor-deploy-webhook.service
Normal file
22
deploy/monitor-deploy-webhook.service
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
[Unit]
|
||||||
|
Description=monitor Deploy-Webhook (loest update.sh bei Push auf main aus)
|
||||||
|
After=network.target docker.service
|
||||||
|
Wants=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
# Anpassen: Checkout-Pfad des monitor-Repos auf diesem Server.
|
||||||
|
WorkingDirectory=/opt/monitor
|
||||||
|
EnvironmentFile=/opt/monitor/.env
|
||||||
|
ExecStart=/usr/bin/python3 /opt/monitor/deploy-webhook.py
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# Anpassen: Benutzer/Gruppe mit Schreibrechten auf das Repo, git-Zugangsdaten (SSH-Key oder
|
||||||
|
# gespeicherte HTTPS-Credentials fuer "git pull") und Mitgliedschaft in der docker-Gruppe
|
||||||
|
# (fuer "docker compose"). Siehe README_UPDATE.md.
|
||||||
|
User=monitor
|
||||||
|
Group=monitor
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Loading…
Add table
Reference in a new issue