341 lines
24 KiB
HTML
341 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Server-Struktur & Infrastruktur-Estate</title>
|
||
<style>
|
||
@page {
|
||
size: A4;
|
||
margin: 22mm 16mm 20mm 16mm;
|
||
@bottom-center { content: counter(page) " / " counter(pages); font-size: 9px; color: #9a958c; }
|
||
@bottom-left { content: "HiveGrid – Server-Struktur (Hetzner Estate)"; font-size: 9px; color: #9a958c; }
|
||
}
|
||
@page :first { margin: 0; @bottom-center { content: ""; } @bottom-left { content: ""; } }
|
||
* { box-sizing: border-box; }
|
||
html { font-size: 10.6px; }
|
||
body { font-family: -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif; color: #231F20; line-height: 1.48; margin: 0; }
|
||
|
||
.cover { height: 297mm; padding: 40mm 24mm 0 24mm; background: linear-gradient(158deg, #2b2521 0%, #1c1813 60%, #14110d 100%); color: #fff; page-break-after: always; }
|
||
.cover .logo { width: 250px; margin-bottom: 44mm; }
|
||
.cover .kicker { font-size: 13px; letter-spacing: 3px; text-transform: uppercase; color: #F1B729; font-weight: 600; }
|
||
.cover h1 { font-size: 38px; line-height: 1.1; margin: 14px 0 10px 0; font-weight: 700; color: #fff; }
|
||
.cover h1 .accent { color: #F1B729; }
|
||
.cover h2 { font-size: 16px; font-weight: 400; color: #d8d3c8; margin: 0 0 32px 0; }
|
||
.cover .meta { font-size: 12px; color: #ddd8cd; line-height: 1.9; border-top: 2px solid #F1B729; padding-top: 18px; max-width: 122mm; }
|
||
.cover .meta strong { color: #F1B729; }
|
||
.cover .badge { display:inline-block; background: rgba(241,183,41,.12); border:1px solid rgba(241,183,41,.55); color:#F1B729; border-radius: 20px; padding: 4px 13px; font-size: 11px; margin: 2px 4px 2px 0; }
|
||
|
||
.toc { page-break-after: always; }
|
||
.toc .logo-dark { width: 150px; display:block; margin: 4mm 0 8mm 0; }
|
||
.toc h2 { border:none; }
|
||
.toc ol { list-style: none; padding-left: 0; counter-reset: toc; column-count: 2; column-gap: 14mm; }
|
||
.toc > ol > li { counter-increment: toc; padding: 5px 0; border-bottom: 1px dotted #ddd6c5; font-weight: 600; color:#231F20; font-size: 11.5px; break-inside: avoid; }
|
||
.toc > ol > li::before { content: counter(toc) ". "; color:#c69412; font-weight:700; }
|
||
|
||
h2.section { font-size: 18px; color: #231F20; border-bottom: 3px solid #F1B729; padding-bottom: 5px; margin-top: 26px; break-after: avoid; }
|
||
h3 { font-size: 13.5px; color: #9a720b; margin-top: 16px; break-after: avoid; }
|
||
p { margin: 6px 0; }
|
||
ul, ol { margin: 6px 0; padding-left: 18px; }
|
||
li { margin: 3px 0; }
|
||
code { font-family: "SF Mono", "Menlo", Consolas, monospace; background: #fbf1d4; padding: 1px 5px; border-radius: 3px; font-size: 9.2px; color: #82610a; }
|
||
strong { color: #231F20; }
|
||
.lead { font-size: 12.5px; color: #5a544a; }
|
||
|
||
table { width: 100%; border-collapse: collapse; margin: 9px 0; font-size: 9.1px; break-inside: avoid; }
|
||
th { background: #231F20; color: #fff; text-align: left; padding: 5px 7px; font-weight: 600; border-bottom: 2px solid #F1B729; }
|
||
td { border-bottom: 1px solid #e7e1d2; padding: 4px 7px; vertical-align: top; }
|
||
tr:nth-child(even) td { background: #faf6ec; }
|
||
|
||
.callout { border-left: 4px solid #F1B729; background: #fdf6e2; padding: 9px 14px; margin: 12px 0; border-radius: 0 5px 5px 0; break-inside: avoid; }
|
||
.callout.warn { border-left-color: #c2410c; background: #fdf0e8; }
|
||
.callout.ok { border-left-color: #15803d; background: #edf8ef; }
|
||
.callout .t { font-weight: 700; display:block; margin-bottom: 2px; color:#231F20; }
|
||
|
||
.mono-box { font-family: "SF Mono","Menlo",Consolas,monospace; font-size: 8.6px; background:#fbf7ea; border:1px solid #e7e1d2; border-left: 4px solid #F1B729; border-radius:6px; padding:10px 12px; white-space:pre; line-height:1.35; color:#3a3326; break-inside: avoid; }
|
||
.pill { display:inline-block; font-size:8px; font-weight:700; padding:1px 6px; border-radius:10px; vertical-align:middle; }
|
||
.pill.live { background:#dcf3e1; color:#166534; }
|
||
.pill.plan { background:#fbe9b0; color:#82610a; }
|
||
.small { font-size: 9px; color:#6b665c; }
|
||
hr { border:none; border-top:1px solid #e7e1d2; margin: 16px 0; }
|
||
|
||
section.sw { break-inside: avoid; }
|
||
.group { break-inside: avoid; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="cover">
|
||
<img class="logo" src="assets/hivegrid-logo-white.svg" alt="HiveGrid">
|
||
<div class="kicker">Infrastruktur-Dokumentation</div>
|
||
<h1>Server-Struktur<br>& <span class="accent">Hetzner-Estate</span></h1>
|
||
<h2>Komplette Bestandsaufnahme: Hosts, Netzwerk, Dienste, Deploy</h2>
|
||
<div>
|
||
<span class="badge">6 Hosts</span>
|
||
<span class="badge">Hetzner (EU)</span>
|
||
<span class="badge">Coolify Control-Plane</span>
|
||
<span class="badge">WireGuard-Mesh</span>
|
||
<span class="badge">Zentrale Dienste</span>
|
||
</div>
|
||
<div class="meta">
|
||
<strong>Zweck:</strong> Onboarding- & Betriebsreferenz für die gesamte Server-Landschaft.<br>
|
||
<strong>Quelle der Wahrheit:</strong> Repo <code>infra/</code> (README, STANDARDS, ADRs, Runbooks).<br>
|
||
<strong>Stand:</strong> 14. Juni 2026<br>
|
||
<strong>Modell:</strong> per-Produkt getrennte Workloads, zentral gesteuert über Coolify,
|
||
zentrale Hilfsdienste über ein privates WireGuard-Mesh.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toc">
|
||
<img class="logo-dark" src="assets/hivegrid-logo.svg" alt="HiveGrid">
|
||
<h2 class="section">Inhalt</h2>
|
||
<ol>
|
||
<li>Überblick & Architekturmodell</li>
|
||
<li>Server-Inventar (alle Hosts)</li>
|
||
<li>Hetzner-Projekt-Struktur</li>
|
||
<li>Netzwerk & WireGuard-Mesh</li>
|
||
<li>Orchestrierung (Coolify, Traefik)</li>
|
||
<li>Zentrale Dienste (node1)</li>
|
||
<li>Observability & Logging</li>
|
||
<li>Aufteilung pro Produkt</li>
|
||
<li>Deploy-Kette</li>
|
||
<li>DNS & Domains</li>
|
||
<li>Secrets & Zugänge</li>
|
||
<li>Backups & Disaster Recovery</li>
|
||
<li>Standards & Konventionen</li>
|
||
<li>Offene Punkte</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<!-- 1 -->
|
||
<section class="sw">
|
||
<h2 class="section">1. Überblick & Architekturmodell</h2>
|
||
<p class="lead">Schlankes, per-Produkt getrenntes SaaS-Infra-Modell mit zentraler Steuerung – alles auf Hetzner (EU).</p>
|
||
<ul>
|
||
<li><strong>Compute:</strong> Mehrere Produkt-Stagings teilen sich einen Host (<code>staging-1</code>); Produktion bekommt pro Produkt ein eigenes Hetzner-Projekt/VM (ADR-009).</li>
|
||
<li><strong>Control-Plane:</strong> Eine zentrale Coolify-Instanz (<code>control</code>) verwaltet alle Hosts und Deployments.</li>
|
||
<li><strong>Hilfsdienste:</strong> Einmal zentral auf dem dedizierten <code>node1</code> (Sentry, Loki, Meilisearch, ClamAV, mCaptcha, NSFW-API), erreichbar über ein privates WireGuard-Mesh.</li>
|
||
<li><strong>CI/CD:</strong> Build-once in der Gitea-Registry, Deploy über Coolify (Staging automatisch, Prod manuell/Promote).</li>
|
||
<li><strong>Governance:</strong> <code>infra/</code>-Repo mit STANDARDS, nummerierten ADRs und Runbooks ist die Quelle der Wahrheit.</li>
|
||
</ul>
|
||
<div class="mono-box"> ┌──────────────────────────────┐
|
||
│ control (Coolify-Plane) │
|
||
│ Prometheus + Grafana │
|
||
│ WireGuard-Server 10.42.0.1 │
|
||
└───────────────┬──────────────┘
|
||
SSH-Deploy / Mesh │
|
||
┌───────────────┬──────────────┬───────┴───────┬───────────────┐
|
||
▼ ▼ ▼ ▼ ▼
|
||
homeserver staging-1 marketing node1 (EX44) mitapp-prod
|
||
10.42.0.2 10.42.0.3 10.42.0.5 10.42.0.4 10.42.0.6
|
||
Gitea/CI/ alle SaaS- hivegrid.de / ZENTRALE mITapp
|
||
Authentik Stagings tool-ctrl.de DIENSTE (Prod)</div>
|
||
</section>
|
||
|
||
<!-- 2 -->
|
||
<section class="sw">
|
||
<h2 class="section">2. Server-Inventar</h2>
|
||
<table>
|
||
<tr><th>Host</th><th>Provider / Typ</th><th>Spec</th><th>Public-IP</th><th>Mesh-IP</th><th>Rolle</th></tr>
|
||
<tr><td><code>control</code></td><td>Hetzner Cloud · cx33</td><td>4C / 8 GB / 80 GB</td><td>195.201.133.26</td><td>10.42.0.1</td><td>Coolify-Control-Plane, Prometheus + Grafana, WireGuard-Server</td></tr>
|
||
<tr><td><code>staging-1</code></td><td>Hetzner Cloud · cpx42</td><td>8C / 15 GB / 75 GB</td><td>46.225.5.93</td><td>10.42.0.3</td><td>Gemeinsamer SaaS-Staging-Host (boilerplate, mITapp, künftig HiveGrid)</td></tr>
|
||
<tr><td><code>node1</code> (EX44)</td><td>Hetzner Dedicated (Robot)</td><td>20C / 62 GB / 436 GB</td><td>176.9.111.179</td><td>10.42.0.4</td><td>Zentrale Dienste: Sentry, Loki, Meilisearch, ClamAV, mCaptcha, NSFW-API</td></tr>
|
||
<tr><td><code>marketing</code></td><td>Hetzner Cloud · cx33</td><td>4C / 8 GB / 80 GB</td><td>128.140.109.153</td><td>10.42.0.5</td><td>Marketing-Sites (hivegrid.de, tool-ctrl.de)</td></tr>
|
||
<tr><td><code>mitapp-prod</code></td><td>Hetzner Cloud · cpx22</td><td>2C / 4 GB / 80 GB</td><td>178.104.213.13</td><td>10.42.0.6</td><td>mITapp Produktion</td></tr>
|
||
<tr><td><code>homeserver</code></td><td>Self-hosted (DDNS)</td><td>16C / 30 GB / 935 GB</td><td>DDNS</td><td>10.42.0.2</td><td>Gitea, CI-Runner, Authentik, Vaultwarden, interne Tools</td></tr>
|
||
</table>
|
||
<p class="small">Alle Cloud-Hosts in Hetzner-Regionen nbg1/fsn1 (Deutschland). <code>node1</code> im RZ FSN1-DC8. SSH per Schlüssel (<code>~/.ssh/node1</code> für alle Hetzner-Hosts).</p>
|
||
</section>
|
||
|
||
<!-- 3 -->
|
||
<section class="sw">
|
||
<h2 class="section">3. Hetzner-Projekt-Struktur</h2>
|
||
<p>Workloads sind in getrennte Hetzner-Cloud-Projekte aufgeteilt (Abrechnung/Isolation). Produktion erhält pro Produkt ein eigenes Projekt (ADR-009).</p>
|
||
<table>
|
||
<tr><th>Projekt</th><th>Zweck</th><th>VM(s)</th><th>Status</th></tr>
|
||
<tr><td>DevOps</td><td>Coolify-Control-Plane</td><td><code>control</code> (cx33)</td><td><span class="pill live">live</span></td></tr>
|
||
<tr><td>Staging</td><td>SaaS-Staging-Pool</td><td><code>staging-1</code> (cpx42)</td><td><span class="pill live">live</span></td></tr>
|
||
<tr><td>Marketing</td><td>Marketing-Workload</td><td><code>marketing</code> (cx33)</td><td><span class="pill live">live</span></td></tr>
|
||
<tr><td>probono</td><td>mITapp-Produktion</td><td><code>mitapp-prod</code> (cpx22)</td><td><span class="pill live">live</span></td></tr>
|
||
<tr><td>HiveGrid</td><td>HiveGrid-Cloud-Prod (geplant, ADR-009)</td><td>—</td><td><span class="pill plan">geplant</span></td></tr>
|
||
<tr><td>Boilerplate</td><td>Boilerplate-Prod (geplant, ADR-009)</td><td>—</td><td><span class="pill plan">geplant</span></td></tr>
|
||
</table>
|
||
<p class="small">Dazu: 1 Dedicated-Server (<code>node1</code>, Hetzner Robot) für zentrale Dienste und der self-hosted <code>homeserver</code> für CI/SSO.</p>
|
||
</section>
|
||
|
||
<!-- 4 -->
|
||
<section class="sw">
|
||
<h2 class="section">4. Netzwerk & WireGuard-Mesh</h2>
|
||
<p>Alle Hosts hängen in einem privaten WireGuard-Mesh <code>10.42.0.0/24</code> mit Stern-Topologie: <code>control</code> ist Server (UDP 51820), alle anderen sind Peers. Inter-Host-Verkehr (Metrics-Scrapes, zentrale Dienste) läuft ausschließlich über Mesh-IPs – nicht über öffentliche Endpunkte.</p>
|
||
<table>
|
||
<tr><th>Host</th><th>Interface</th><th>Mesh-IP</th><th>Rolle im Mesh</th></tr>
|
||
<tr><td>control</td><td>wg0</td><td>10.42.0.1/24</td><td>Server</td></tr>
|
||
<tr><td>homeserver</td><td>wg1</td><td>10.42.0.2/24</td><td>Peer (eigenes wg0 bleibt Heim-VPN)</td></tr>
|
||
<tr><td>staging-1</td><td>wg0</td><td>10.42.0.3/24</td><td>Peer</td></tr>
|
||
<tr><td>node1</td><td>wg1</td><td>10.42.0.4/24</td><td>Peer (eigenes wg0 separates VPN)</td></tr>
|
||
<tr><td>marketing</td><td>wg0</td><td>10.42.0.5/24</td><td>Peer</td></tr>
|
||
<tr><td>mitapp-prod</td><td>wg0</td><td>10.42.0.6/24</td><td>Peer</td></tr>
|
||
</table>
|
||
</section>
|
||
|
||
<!-- 5 -->
|
||
<section class="sw">
|
||
<h2 class="section">5. Orchestrierung (Coolify, Traefik)</h2>
|
||
<p><strong>Coolify v4</strong> auf <code>control</code> (Dashboard <code>https://saas-ctrl.com</code>) ist die zentrale Steuerung. Jeder Ziel-Host ist als Server registriert; pro Produkt gibt es ein Projekt, pro Stufe ein Environment, pro Dienst eine Resource.</p>
|
||
<ul>
|
||
<li><strong>Resource-Modus <code>dockerimage</code>:</strong> Coolify baut nicht selbst, sondern zieht vorgefertigte Images aus der Gitea-Registry.</li>
|
||
<li><strong>Reverse-Proxy:</strong> Traefik (<code>coolify-proxy</code>) je Host, TLS via Let's Encrypt (ACME). Auf <code>node1</code> hat Traefik seit der ADR-013-Migration das frühere Host-Caddy ersetzt.</li>
|
||
<li><strong>Konvention:</strong> Projekt = Produkt · Environment = Stufe (staging/production) · Resource = Dienst.</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<!-- 6 -->
|
||
<section class="sw">
|
||
<h2 class="section">6. Zentrale Dienste (node1)</h2>
|
||
<p>Einmal zentral betrieben, von allen Produkten über das Mesh genutzt. Index-/Key-Konventionen pro Produkt sorgen für Trennung.</p>
|
||
<table>
|
||
<tr><th>Dienst</th><th>Erreichbar</th><th>Zweck / Hinweise</th></tr>
|
||
<tr><td>Sentry</td><td><code>sentry.saas-ctrl.com</code></td><td>Error-Tracking. 1 Org, 1 Team pro Produkt, 1 Projekt pro Codebasis; Staging/Prod über Environment-Tags.</td></tr>
|
||
<tr><td>Loki</td><td><code>10.42.0.4:3100</code> (mesh)</td><td>Zentrale Logs; Promtail-Collector auf jedem Host; 7 Tage Retention.</td></tr>
|
||
<tr><td>Meilisearch</td><td><code>10.42.0.4:7700</code> (mesh)</td><td>Volltextsuche; pro Produkt ein Scoped-Key, Index-Präfix <code><produkt>-*</code>.</td></tr>
|
||
<tr><td>ClamAV</td><td><code>10.42.0.4:3310</code> (mesh)</td><td>Virenscan per TCP-INSTREAM; Default fail-closed für Uploads.</td></tr>
|
||
<tr><td>NSFW-API</td><td><code>10.42.0.4:3000</code> (mesh)</td><td>Bild-Klassifikation; Default fail-open.</td></tr>
|
||
<tr><td>mCaptcha</td><td><code>captcha.saas-ctrl.com</code></td><td>Captcha-Service (Sitekey-verwaltet).</td></tr>
|
||
</table>
|
||
</section>
|
||
|
||
<!-- 7 -->
|
||
<section class="sw">
|
||
<h2 class="section">7. Observability & Logging</h2>
|
||
<p>Zentral auf <code>control</code>: <strong>Prometheus</strong> scrapt jeden Host über das Mesh (alle 30 s), <strong>Grafana</strong> (<code>grafana.saas-ctrl.com</code>) visualisiert. Logs zentral in <strong>Loki</strong>, Errors in <strong>Sentry</strong>.</p>
|
||
<ul>
|
||
<li><strong>Scrape-Targets je Host:</strong> <code>node-exporter</code> (:9100) und <code>docker-stats-exporter</code> (:9487) auf der jeweiligen Mesh-IP.</li>
|
||
<li><strong>Labels:</strong> <code>project</code>, <code>environment</code>, <code>server</code>, <code>service</code> – damit lassen sich Dashboards pro Produkt/Host filtern.</li>
|
||
<li><strong>Dashboards:</strong> <code>estate-overview</code> (flottenweit) plus je Host/Produkt eines (<code>project-staging</code>, <code>project-hetzner-ex44</code>, <code>project-mitapp</code>, …).</li>
|
||
<li><strong>Offen:</strong> AlertManager-/Alert-Routing noch nicht aktiv; Dashboards aktuell nicht versioniert.</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<!-- 8 -->
|
||
<section class="sw">
|
||
<h2 class="section">8. Aufteilung pro Produkt</h2>
|
||
<div class="group">
|
||
<h3>HiveGrid Cloud (SaaS) <span class="pill plan">Prod geplant</span></h3>
|
||
<p>SaaS-App in aktiver Entwicklung (Git-Downstream des Boilerplates). Noch nicht deployt. Folgt bei Reife dem Estate-Modell: Staging auf <code>staging-1</code> (oder eigenes Projekt), Prod als eigenes Hetzner-Projekt „HiveGrid" gemäß ADR-009; zentrale Dienste auf <code>node1</code>. Nicht verwechseln mit der Marketing-Site <code>hivegrid.de</code>.</p>
|
||
</div>
|
||
<div class="group">
|
||
<h3>HiveGrid Marketing-Site <span class="pill live">live</span></h3>
|
||
<p>Statische/Next.js-Site auf <code>marketing</code>, Domain <code>hivegrid.de</code> (Cloudflare-proxied), TLS über Traefik. Leads lokal in SQLite-Volume.</p>
|
||
</div>
|
||
<div class="group">
|
||
<h3>Boilerplate SaaS <span class="pill live">Staging live</span></h3>
|
||
<p><strong>Staging</strong> auf <code>staging-1</code> (Coolify-Projekt „boilerplate" / „validation"): web + api + 5 Worker, Postgres (Coolify-managed) + PgBouncer, Authentik unter <code>auth.boiler.saas-ctrl.com</code>; Suche/Virenscan/Errors zentral auf <code>node1</code>. <strong>Prod</strong> laut ADR-009 als eigenes Projekt geplant, noch nicht live.</p>
|
||
</div>
|
||
<div class="group">
|
||
<h3>mITapp <span class="pill live">live</span></h3>
|
||
<p><strong>Prod</strong> auf <code>mitapp-prod</code> (cpx22): App + Worker (Image-Tag <code>:prod</code>), Uploads-Sidecar (nginx), Postgres 16 + Redis 7 (Coolify-managed). Domains <code>mitapp.de</code> (kanonisch) + <code>api.mitapp.de</code> (mobil) sowie Legacy <code>mitapp.mitup.de</code> (muss <code>/api/*</code> + <code>/.well-known/*</code> für ausgelieferte Mobile-Apps bedienen). <strong>Staging</strong> auf <code>staging-1</code> (<code>staging.mitapp.de</code>, Basic-Auth). CI/CD nach Modell „Build-once / Promote".</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 9 -->
|
||
<section class="sw">
|
||
<h2 class="section">9. Deploy-Kette</h2>
|
||
<div class="mono-box">git push origin main
|
||
│
|
||
▼ Gitea Actions (Runner auf homeserver, Label "toolctrl-ci")
|
||
│ build image → push git.tool-ctrl.de/<org>/<repo>:latest + :sha-<short>
|
||
│
|
||
▼ Coolify (control) — Resource-Modus "dockerimage"
|
||
│ pull pre-built image → per SSH auf Ziel-Host → Container-Rolling-Restart (~25-30s)
|
||
│
|
||
▼ Traefik (Ziel-Host) → TLS-Termination (Let's Encrypt)
|
||
│
|
||
▼ Cloudflare DNS (+ Proxy bei user-facing Domains) → live</div>
|
||
<ul>
|
||
<li><strong>Staging:</strong> automatisch bei Push auf <code>main</code> (Tag <code>:latest</code> + <code>:sha-…</code>).</li>
|
||
<li><strong>Produktion (Modell „Promote"):</strong> manueller <code>promote.yml</code>-Dispatch retaggt ein bereits gebautes SHA-Image auf <code>:prod</code> (kein Rebuild) und deployt es. Rollback = altes SHA neu deployen.</li>
|
||
<li><strong>Registry-Auth:</strong> pro Host <code>docker login</code> gegen die Gitea-Registry.</li>
|
||
</ul>
|
||
</section>
|
||
|
||
<!-- 10 -->
|
||
<section class="sw">
|
||
<h2 class="section">10. DNS & Domains (Cloudflare)</h2>
|
||
<p>DNS zentral in Cloudflare. User-facing-Domains laufen über den Cloudflare-Proxy; Origins, die selbst Let's-Encrypt-Zertifikate ausstellen, stehen auf „DNS-only". Transaktionsmails über Mailjet (DKIM/SPF je Zone), eingehende Mail über mailbox.org.</p>
|
||
<table>
|
||
<tr><th>Domain / Subdomain</th><th>Ziel-Host</th><th>Zweck</th></tr>
|
||
<tr><td><code>saas-ctrl.com</code></td><td>control</td><td>Coolify-Dashboard</td></tr>
|
||
<tr><td><code>grafana.saas-ctrl.com</code></td><td>control</td><td>Monitoring</td></tr>
|
||
<tr><td><code>sentry.saas-ctrl.com</code> · <code>captcha.saas-ctrl.com</code></td><td>node1</td><td>Zentrale Dienste</td></tr>
|
||
<tr><td><code>boiler.saas-ctrl.com</code> · <code>auth.boiler…</code></td><td>staging-1</td><td>Boilerplate-Staging + Admin-SSO</td></tr>
|
||
<tr><td><code>hivegrid.de</code></td><td>marketing</td><td>HiveGrid-Marketing-Site</td></tr>
|
||
<tr><td><code>tool-ctrl.de</code> (+ <code>git</code>,<code>auth</code>,…)</td><td>marketing / homeserver</td><td>Marketing + interne Tools (DDNS)</td></tr>
|
||
<tr><td><code>mitapp.de</code> · <code>api.mitapp.de</code> · <code>staging.mitapp.de</code></td><td>mitapp-prod / staging-1</td><td>mITapp Prod/API/Staging</td></tr>
|
||
</table>
|
||
<p class="small">Aktive Zonen u. a.: <code>saas-ctrl.com</code>, <code>hivegrid.de</code>, <code>mitapp.de</code>, <code>mitup.de</code>, <code>tool-ctrl.de</code>, <code>power-linked.de</code> (reserviert). TLS-Modus durchgehend „Strict".</p>
|
||
</section>
|
||
|
||
<!-- 11 -->
|
||
<section class="sw">
|
||
<h2 class="section">11. Secrets & Zugänge</h2>
|
||
<ul>
|
||
<li><strong>App- & Service-Secrets:</strong> zentral über <strong>Coolify</strong> verwaltet – als Environment-Variablen je Resource, nie im Repo.</li>
|
||
<li><strong>Admin-/CLI-Zugänge</strong> (Hetzner-Cloud-API, Coolify-API, Cloudflare) liegen lokal unter <code>~/.cli-tokens/</code> (chmod 600).</li>
|
||
<li><strong>Inter-Host:</strong> Coolify→Host per dediziertem SSH-Key; Apps→zentrale Dienste über das private Mesh (mesh-only).</li>
|
||
<li><strong>Admin-SSO & Passwortverwaltung:</strong> Authentik + Vaultwarden auf dem <code>homeserver</code>.</li>
|
||
</ul>
|
||
<p class="small">Grundsatz aus STANDARDS: keine Secrets im Repo, keine Klartext-Weitergabe in Chats. (Konkrete Schlüsselwerte und Dateipfade sind in diesem Dokument bewusst ausgespart.)</p>
|
||
</section>
|
||
|
||
<!-- 12 -->
|
||
<section class="sw">
|
||
<h2 class="section">12. Backups & Disaster Recovery</h2>
|
||
<div class="callout warn"><span class="t">Status: in Arbeit</span>Eine vollständige, automatisierte Backup-Strategie ist noch nicht abgeschlossen – wichtig, bevor eine DB zum alleinigen Speicher von Kundendaten wird.</div>
|
||
<table>
|
||
<tr><th>Komponente</th><th>Disposabel?</th><th>Backup / Recovery</th></tr>
|
||
<tr><td>Coolify-State (<code>/data/coolify</code> auf control)</td><td>nein (alle Deploy-Configs)</td><td>nur lokaler Teil-Snapshot; automatisiertes S3-Export geplant</td></tr>
|
||
<tr><td>Produkt-DBs (Postgres, Prod/Staging)</td><td>nein (Kundendaten)</td><td>Coolify-Auto-Backup zu aktivieren/dokumentieren</td></tr>
|
||
<tr><td>Sentry / Loki / ClamAV / mCaptcha (node1)</td><td>ja (nur Historie/Sig-DB)</td><td>kein Backup – werden bei Bedarf neu aufgesetzt</td></tr>
|
||
<tr><td>Meilisearch-Index</td><td>teilweise</td><td>aus Postgres reindizierbar; S3-Snapshots geplant</td></tr>
|
||
</table>
|
||
<p class="small">Geplant: Synology-NAS zieht per Hyper Backup von <code>control</code> und <code>homeserver</code>; Off-Site-/Objekt-Speicher über <strong>Hetzner Object Storage (S3, EU)</strong>.</p>
|
||
<p><strong>Recovery-Kurzform:</strong> <code>node1</code>-Ausfall → Dienste neu deployen (Daten verworfen). <code>control</code>-Ausfall → <code>/data/coolify</code> wiederherstellen, Coolify starten, Apps neu deployen. Produkt-DB → aus Postgres-Backup wiederherstellen (sobald aktiviert).</p>
|
||
</section>
|
||
|
||
<!-- 13 -->
|
||
<section class="sw">
|
||
<h2 class="section">13. Standards & Konventionen</h2>
|
||
<p>Verbindlich aus <code>infra/STANDARDS.md</code> (Kurzfassung):</p>
|
||
<ol>
|
||
<li>Errors → zentrale Sentry (1 Team/Produkt, 1 Projekt/Codebasis, Environment-Tags).</li>
|
||
<li>Jeder Host liefert Metrics (node-exporter + docker-stats) und Logs (Promtail → Loki).</li>
|
||
<li>Jedes SaaS bekommt ein Grafana-Dashboard (gefiltert nach <code>project</code>/<code>server</code>).</li>
|
||
<li>Volltextsuche über zentrale Meilisearch (Index-Präfix, Scoped-Key).</li>
|
||
<li>Admin-SSO über zentrale Authentik.</li>
|
||
<li>Repos in Gitea, privat per Default; Dockerfile Pflicht.</li>
|
||
<li>Coolify ist das Deploy-Ziel: Projekt = Produkt, Environment = Stufe, Resource = Dienst.</li>
|
||
<li>Domains in Cloudflare (proxied für user-facing, DNS-only für LE-Origins).</li>
|
||
<li>Secrets außerhalb des Repos (<code>~/.cli-tokens/</code>, chmod 600, keine Chat-Pastes).</li>
|
||
<li>Backup dokumentieren, bevor etwas „live" geht.</li>
|
||
<li>ADR für nicht-triviale Entscheidungen (<code>decisions/</code>, nummeriert).</li>
|
||
<li>Uploads: Virenscan über zentrale ClamAV (fail-closed), Bildprüfung über NSFW-API (fail-open).</li>
|
||
</ol>
|
||
</section>
|
||
|
||
<!-- 14 -->
|
||
<section class="sw">
|
||
<h2 class="section">14. Offene Punkte</h2>
|
||
<p>Neutral zusammengefasst aus README-/LESSONS-To-dos (Stand Juni 2026):</p>
|
||
<ul>
|
||
<li><strong>Release:</strong> erster mITapp-<code>:prod</code>-Promote noch ausstehend; Legacy-Compose-Stack nach Bewährungsfenster abbauen.</li>
|
||
<li><strong>Prod-Topologie:</strong> eigene Hetzner-Projekte für Boilerplate- und HiveGrid-Cloud-Prod gemäß ADR-009 anlegen.</li>
|
||
<li><strong>Backups:</strong> Coolify-State-Export, Produkt-DB-Backups und NAS-/Off-Site-Ziel finalisieren.</li>
|
||
<li><strong>Sicherheit:</strong> Secret-Rotation, 2FA auf Cloudflare, Hetzner-Cloud-Firewall (SSH auf bekannte IPs), Cloudflare-Token auf „scoped" reduzieren.</li>
|
||
<li><strong>Observability:</strong> Alert-Routing aktivieren, Grafana-Dashboards versionieren.</li>
|
||
</ul>
|
||
<hr>
|
||
<p class="small">Stand 14. Juni 2026. Quelle der Wahrheit ist das <code>infra/</code>-Repository (<code>README.md</code>, <code>STANDARDS.md</code>, <code>LESSONS.md</code>, <code>decisions/</code>, <code>runbooks/</code>). Bei Abweichungen gilt das Repo. Konkrete Geheimnisse/Schlüssel sind hier bewusst ausgespart.</p>
|
||
</section>
|
||
|
||
</body>
|
||
</html>
|