diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 81832c2..cfc9a29 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -31,7 +31,7 @@ jobs: --exclude '__pycache__' \ ./ deploy@pbt:/opt/pbt/ - - name: Abhängigkeiten installieren & Service neu starten + - name: Abhängigkeiten installieren, Haltestellen importieren & Service neu starten run: | ssh deploy@pbt ' set -e @@ -40,4 +40,7 @@ jobs: source venv/bin/activate pip install -q -r requirements.txt sudo /usr/bin/systemctl restart pbt + # oneshot unit: reads /etc/pbt/pbt.env for PBT_DATABASE_URL, + # runs "flask import-stops" (idempotent upsert of data/stops_at.csv.gz) + sudo /usr/bin/systemctl start pbt-import-stops.service ' diff --git a/.gitignore b/.gitignore index e813d97..a2796e5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ venv/ __pycache__/ *.pyc .env +pbt.env instance/ diff --git a/README.md b/README.md index ec9f51f..14fc15a 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,9 @@ Nach dem Anlegen der Tabellen den Snapshot in die DB laden: flask import-stops # FLASK_APP=app.py bzw. im Projektverzeichnis ``` -Der Import ist idempotent (Upsert) und braucht kein Internet. Beim Deploy per -systemd einmalig ausführen, danach nur wenn der Snapshot aktualisiert wurde. +Der Import ist idempotent (Upsert) und braucht kein Internet. Im Produktivbetrieb +übernimmt das die Unit `pbt-import-stops.service` (wird bei jedem Deploy +angestoßen, s. u.). Snapshot neu von OpenStreetMap holen (dauert 1–2 min, braucht Netzugang): @@ -44,15 +45,29 @@ flask import-stops ## Produktivbetrieb mit systemd + Gunicorn 1. Projekt nach `/opt/pbt` kopieren, virtuelle Umgebung dort anlegen (s. oben). -2. `pbt.service` nach `/etc/systemd/system/pbt.service` kopieren, DB-URL und - Secret-Key darin anpassen. -3. Aktivieren: +2. Secrets anlegen (nicht im Repo): ```bash - systemctl daemon-reload - systemctl enable --now pbt + sudo install -d -m 750 -o root -g root /etc/pbt + sudo cp /opt/pbt/pbt.env.example /etc/pbt/pbt.env + sudo chmod 640 /etc/pbt/pbt.env + sudo $EDITOR /etc/pbt/pbt.env # echte PBT_DATABASE_URL + PBT_SECRET_KEY + ``` +3. Beide Units installieren (lesen `/etc/pbt/pbt.env`): + ```bash + sudo cp /opt/pbt/pbt.service /opt/pbt/pbt-import-stops.service /etc/systemd/system/ + sudo systemctl daemon-reload + sudo systemctl enable --now pbt + sudo systemctl start pbt-import-stops.service # Haltestellen laden systemctl status pbt ``` Die App lauscht dann intern auf `127.0.0.1:8000`. +4. Damit der Deploy-User (`deploy`) beim CI-Deploy neu starten und den + Haltestellen-Import anstoßen darf, in `/etc/sudoers.d/pbt-deploy`: + ``` + deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart pbt, /usr/bin/systemctl start pbt-import-stops.service + ``` + Der Import selbst läuft als `www-data` und bekommt die DB-URL aus + `/etc/pbt/pbt.env` – die Credentials liegen nie beim Deploy-User. ## Caddy-Eintrag diff --git a/pbt-import-stops.service b/pbt-import-stops.service new file mode 100644 index 0000000..0e2f437 --- /dev/null +++ b/pbt-import-stops.service @@ -0,0 +1,14 @@ +[Unit] +Description=PBT: import the Austrian stop list into the database +# Postgres runs on a separate host, so just wait for the network. +Wants=network-online.target +After=network-online.target + +[Service] +Type=oneshot +User=www-data +WorkingDirectory=/opt/pbt +# Same secrets file as pbt.service - gives the import command PBT_DATABASE_URL. +EnvironmentFile=/etc/pbt/pbt.env +Environment=FLASK_APP=app.py +ExecStart=/opt/pbt/venv/bin/flask import-stops diff --git a/pbt.env.example b/pbt.env.example new file mode 100644 index 0000000..bfff790 --- /dev/null +++ b/pbt.env.example @@ -0,0 +1,6 @@ +# Copy to /etc/pbt/pbt.env on the host (root:root, chmod 640) and fill in. +# Referenced by pbt.service and pbt-import-stops.service via EnvironmentFile=. +# systemd parses this itself: KEY=VALUE per line, no quotes, no `export`. + +PBT_DATABASE_URL=postgresql://pbt_app:CHANGE_ME@postgres/pbt +PBT_SECRET_KEY=CHANGE_ME_long_random_string diff --git a/pbt.service b/pbt.service index f27aeb0..af3c70a 100644 --- a/pbt.service +++ b/pbt.service @@ -5,8 +5,9 @@ After=network.target [Service] User=www-data WorkingDirectory=/opt/pbt -Environment="PBT_DATABASE_URL=postgresql://pbt_app:changeme@postgres/pbt" -Environment="PBT_SECRET_KEY=bitte-aendern" +# Secrets live in a root-owned file on the host, not in this tracked unit. +# Copy pbt.env.example to /etc/pbt/pbt.env and fill in the real values. +EnvironmentFile=/etc/pbt/pbt.env ExecStart=/opt/pbt/venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 app:app Restart=always