fix: Admin-Dienste hinter dem VPN wieder korrekt prüfen
Seit der VPN-Absicherung (Phase 2) antworten n8n, die beiden Nginx Proxy Manager, Grafana und CloudPanel auf ihren öffentlichen Domains nur noch mit 403. Der Checker wertete das als "down" — die Statusseite stand deshalb dauerhaft auf "down", ohne dass ein Dienst tatsächlich gestört war. Der Checker läuft per Cron auf hetzner, das selbst WireGuard-Peer 10.10.0.14 ist. Diese Dienste werden jetzt über das neue Feld "check_url" intern geprüft, Grafana und CloudPanel laufen auf hetzner selbst und werden über localhost angesprochen. "url" bleibt die öffentliche Adresse für die Anzeige; "check_url" wird vor dem Schreiben aus status.json entfernt, damit die interne Netztopologie nicht auf einer öffentlichen Seite landet. Uptime Kuma zeigte bisher nur einen 302 auf der gesperrten Root-URL. Geprüft wird jetzt die öffentliche Status-Page, die tatsächlich beweist, dass Kuma antwortet. Nebenbei korrigiert: ng.vanityontour.de war als "CloudPanel" gefuehrt, ist aber laut NPM-Datenbank der Nginx Proxy Manager auf VoTServer. CloudPanel selbst (cp.blog.vanityontour.de) wurde dadurch nie geprüft und ist jetzt ergaenzt. Der GitHub-Actions-Workflow entfaellt: er lief ohnehin nur noch manuell, von GitHub aus sind die internen Adressen unerreichbar, und ein Start haette ein falsches "alles down" committet und per FTP über die Live-Seite geschoben. README beschreibt jetzt den tatsaechlichen Weg (Cron auf hetzner + SCP).
This commit is contained in:
parent
d725139a05
commit
db2c6c70a0
3 changed files with 67 additions and 62 deletions
43
.github/workflows/update-status.yml
vendored
43
.github/workflows/update-status.yml
vendored
|
|
@ -1,43 +0,0 @@
|
||||||
name: Update Status Page
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch: # manual trigger only — status updates run via Hetzner cron + SCP
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Run status checks
|
|
||||||
run: python3 scripts/check_status.py
|
|
||||||
|
|
||||||
- name: Commit updated status.json
|
|
||||||
run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add public/status.json
|
|
||||||
git diff --cached --quiet && echo "No changes" && exit 0
|
|
||||||
git commit -m "chore: status update $(date -u '+%Y-%m-%d %H:%M UTC')"
|
|
||||||
git push
|
|
||||||
|
|
||||||
- name: Deploy to Hostinger via FTP
|
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
|
||||||
with:
|
|
||||||
server: ${{ secrets.FTP_SERVER }}
|
|
||||||
username: ${{ secrets.FTP_USERNAME }}
|
|
||||||
password: ${{ secrets.FTP_PASSWORD }}
|
|
||||||
local-dir: ./public/
|
|
||||||
server-dir: /public_html/
|
|
||||||
exclude: |
|
|
||||||
**/.git*
|
|
||||||
**/.git*/**
|
|
||||||
56
README.md
56
README.md
|
|
@ -12,20 +12,51 @@ Automated status dashboard for all VanityOnTour services, hosted on Hostinger at
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
GitHub Actions runs every 5 minutes:
|
Ein Cron auf **hetzner** (`88.99.209.207`) läuft alle 5 Minuten:
|
||||||
1. `scripts/check_status.py` checks all services and writes `public/status.json`
|
|
||||||
2. Commits the updated `status.json` to the repo
|
|
||||||
3. Deploys `public/` to Hostinger via FTP
|
|
||||||
|
|
||||||
## Setup: GitHub Secrets required
|
```
|
||||||
|
*/5 * * * * /opt/run_status_check.sh >> /var/log/status_check.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
Go to **Settings → Secrets → Actions** and add:
|
1. `/opt/check_status.py` prüft alle Dienste und schreibt `/opt/public/status.json`
|
||||||
|
2. `run_status_check.sh` kopiert die Datei per SCP (Port 65002) nach
|
||||||
|
`/home/u982551092/domains/status.vanityontour.de/public_html/status.json`
|
||||||
|
|
||||||
| Secret | Value |
|
Die statischen Dateien unter `public/` (HTML, CSS, Icons) liegen unverändert auf
|
||||||
|--------|-------|
|
Hostinger; nur `status.json` wird zyklisch überschrieben.
|
||||||
| `FTP_SERVER` | FTP hostname from Hostinger hPanel |
|
|
||||||
| `FTP_USERNAME` | `u982551092` |
|
> Der frühere Weg über GitHub Actions + FTP-Deploy wird **nicht** mehr benutzt.
|
||||||
| `FTP_PASSWORD` | FTP password from Hostinger hPanel |
|
> Das committete `public/status.json` ist deshalb nur ein Platzhalter — der
|
||||||
|
> Live-Stand steht ausschließlich auf Hostinger.
|
||||||
|
|
||||||
|
### Deployment einer Skript-Änderung
|
||||||
|
|
||||||
|
`scripts/check_status.py` wird **nicht** automatisch ausgerollt. Nach einer
|
||||||
|
Änderung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp scripts/check_status.py hetzner:/opt/check_status.py
|
||||||
|
ssh hetzner '/opt/run_status_check.sh' # einmal testweise ausführen
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dienste hinter dem Management-VPN
|
||||||
|
|
||||||
|
Seit der VPN-Absicherung antworten die Admin-Oberflächen öffentlich nur noch mit
|
||||||
|
`403`. Weil der Checker auf hetzner läuft und hetzner WireGuard-Peer
|
||||||
|
`10.10.0.14` ist, werden diese Dienste über das Feld `check_url` intern geprüft:
|
||||||
|
|
||||||
|
| Dienst | Anzeige (`url`) | Prüfung (`check_url`) |
|
||||||
|
|---|---|---|
|
||||||
|
| N8N Automation | `n8n.vanityontour.de` | `http://10.10.0.13:5678` |
|
||||||
|
| Nginx Proxy Manager | `nginx.vanityontour.de` | `http://10.10.0.13:81` |
|
||||||
|
| Nginx Proxy Mgr (VoT) | `ng.vanityontour.de` | `http://10.10.0.12:81` |
|
||||||
|
| Statistiken (Grafana) | `stats.vanityontour.de` | `http://127.0.0.1:3000` |
|
||||||
|
| CloudPanel | `cp.blog.vanityontour.de` | `https://127.0.0.1:8443` |
|
||||||
|
| Uptime Kuma | `server.vanityontour.de` | `…/status/vanity` (öffentlich) |
|
||||||
|
|
||||||
|
`check_url` wird vor dem Schreiben aus `status.json` entfernt — die interne
|
||||||
|
Netztopologie gehört nicht auf eine öffentliche Seite. Die Statusseite zeigt
|
||||||
|
weiterhin nur den Hostnamen aus `url` an.
|
||||||
|
|
||||||
## Local test
|
## Local test
|
||||||
|
|
||||||
|
|
@ -33,3 +64,6 @@ Go to **Settings → Secrets → Actions** and add:
|
||||||
python3 scripts/check_status.py
|
python3 scripts/check_status.py
|
||||||
# → writes public/status.json
|
# → writes public/status.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Achtung: Lokal (ohne VPN-Route zu `10.10.0.0/24`) melden die intern geprüften
|
||||||
|
Dienste zwangsläufig „down". Aussagekräftig ist der Lauf nur auf hetzner.
|
||||||
|
|
|
||||||
|
|
@ -14,18 +14,28 @@ from datetime import datetime, timezone
|
||||||
|
|
||||||
OUTPUT_FILE = "public/status.json"
|
OUTPUT_FILE = "public/status.json"
|
||||||
|
|
||||||
|
# Seit der VPN-Absicherung (Phase 2) antworten die Admin-Oberflächen auf ihren
|
||||||
|
# öffentlichen Domains nur noch mit 403. Der Checker läuft per Cron auf hetzner,
|
||||||
|
# das selbst WireGuard-Peer 10.10.0.14 ist — diese Dienste werden deshalb über
|
||||||
|
# "check_url" intern geprüft. "url" bleibt die öffentliche Adresse: die Statusseite
|
||||||
|
# zeigt daraus nur den Hostnamen an, interne Adressen gehören nicht ins status.json.
|
||||||
WEBSITES = [
|
WEBSITES = [
|
||||||
{"name": "VanityOnTour", "url": "https://vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
{"name": "VanityOnTour", "url": "https://vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
||||||
{"name": "News Portal", "url": "https://news.vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
{"name": "News Portal", "url": "https://news.vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
||||||
{"name": "Wiki", "url": "https://wiki.vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
{"name": "Wiki", "url": "https://wiki.vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
||||||
{"name": "StaySense", "url": "https://staysense.vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
{"name": "StaySense", "url": "https://staysense.vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
||||||
{"name": "StaySense Landing", "url": "https://landing.staysense.vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
{"name": "StaySense Landing", "url": "https://landing.staysense.vanityontour.de", "group": "websites", "expect": [200, 301, 302]},
|
||||||
{"name": "N8N Automation", "url": "https://n8n.vanityontour.de", "group": "tools", "expect": [200, 301, 302]},
|
{"name": "N8N Automation", "url": "https://n8n.vanityontour.de", "group": "tools", "expect": [200, 301, 302], "check_url": "http://10.10.0.13:5678"},
|
||||||
{"name": "Nginx Proxy Manager", "url": "https://nginx.vanityontour.de", "group": "tools", "expect": [200, 301, 302]},
|
{"name": "Nginx Proxy Manager", "url": "https://nginx.vanityontour.de", "group": "tools", "expect": [200, 301, 302], "check_url": "http://10.10.0.13:81"},
|
||||||
{"name": "Uptime Kuma", "url": "https://server.vanityontour.de", "group": "tools", "expect": [200, 301, 302]},
|
{"name": "Nginx Proxy Mgr (VoT)", "url": "https://ng.vanityontour.de", "group": "tools", "expect": [200, 301, 302], "check_url": "http://10.10.0.12:81"},
|
||||||
{"name": "Statistiken", "url": "https://stats.vanityontour.de", "group": "tools", "expect": [200, 301, 302]},
|
# Root liefert öffentlich zwar 302, aber /dashboard ist gesperrt — die echte
|
||||||
|
# öffentliche Status-Page beweist dagegen, dass Kuma wirklich antwortet.
|
||||||
|
{"name": "Uptime Kuma", "url": "https://server.vanityontour.de", "group": "tools", "expect": [200], "check_url": "https://server.vanityontour.de/status/vanity"},
|
||||||
|
{"name": "Statistiken", "url": "https://stats.vanityontour.de", "group": "tools", "expect": [200, 301, 302], "check_url": "http://127.0.0.1:3000"},
|
||||||
{"name": "App Backend", "url": "https://app.vanityontour.de", "group": "tools", "expect": [200, 301, 302]},
|
{"name": "App Backend", "url": "https://app.vanityontour.de", "group": "tools", "expect": [200, 301, 302]},
|
||||||
{"name": "CloudPanel", "url": "https://ng.vanityontour.de", "group": "tools", "expect": [200, 301, 302]},
|
# Grafana und CloudPanel laufen auf hetzner selbst — localhost statt 10.10.0.14,
|
||||||
|
# damit die Prüfung auch bei liegendem Tunnel noch stimmt.
|
||||||
|
{"name": "CloudPanel", "url": "https://cp.blog.vanityontour.de", "group": "tools", "expect": [200, 301, 302], "check_url": "https://127.0.0.1:8443"},
|
||||||
{"name": "RSS News API", "url": "https://news.vanityontour.de/health", "group": "apis", "expect": [200]},
|
{"name": "RSS News API", "url": "https://news.vanityontour.de/health", "group": "apis", "expect": [200]},
|
||||||
{"name": "StaySense API", "url": "https://staysense.vanityontour.de/api/health", "group": "apis", "expect": [200]},
|
{"name": "StaySense API", "url": "https://staysense.vanityontour.de/api/health", "group": "apis", "expect": [200]},
|
||||||
]
|
]
|
||||||
|
|
@ -119,10 +129,14 @@ def main():
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for site in WEBSITES:
|
for site in WEBSITES:
|
||||||
r = check_http(site["url"], site["expect"])
|
target = site.get("check_url", site["url"])
|
||||||
results.append({**site, **r})
|
r = check_http(target, site["expect"])
|
||||||
|
# check_url beschreibt die interne Netztopologie und wird nicht veröffentlicht
|
||||||
|
public = {k: v for k, v in site.items() if k != "check_url"}
|
||||||
|
results.append({**public, **r})
|
||||||
sym = "✓" if r["status"] == "up" else "✗"
|
sym = "✓" if r["status"] == "up" else "✗"
|
||||||
print(f" {sym} {site['name']:30s} {r['status']:8s} {r.get('status_code') or '---'} {r.get('response_time_ms') or '---'}ms")
|
via = " via " + target if "check_url" in site else ""
|
||||||
|
print(f" {sym} {site['name']:30s} {r['status']:8s} {r.get('status_code') or '---'} {r.get('response_time_ms') or '---'}ms{via}")
|
||||||
|
|
||||||
print("Checking SSL certificates...")
|
print("Checking SSL certificates...")
|
||||||
ssl_results = {}
|
ssl_results = {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue