mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:21:10 +02:00
feat(infra): add Cloudflare fallback plan + self-hosted landing pages
Two infrastructure improvements for tech independence:
1. Cloudflare Fallback Documentation (docs/CLOUDFLARE_FALLBACK.md):
- Plan B: WireGuard + Caddy on Hetzner VPS (€3.79/mo)
- Complete Caddyfile with all 30+ subdomains
- Step-by-step failover checklist (~15 min to switch)
- Plan C: Direct IP with ISP
2. Self-Hosted Landing Pages (eliminates Cloudflare Pages dependency):
- Nginx container (mana-infra-landings) on port 4400
- Multi-site config: each subdomain → separate dist/ folder
- Build script: scripts/mac-mini/build-landings.sh
- Cloudflare Tunnel ingress rules for 10 landing page domains
- Storage: /Volumes/ManaData/landings/ on external SSD
- Domains: it, chats, pics, zitares, presis, clocks,
manadeck, nutriphi, citycorners, docs
Migration path: Build landings locally, set Cloudflare DNS to
tunnel instead of Pages, then decommission CF Pages projects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
954b204bac
commit
e3115b302d
11 changed files with 733 additions and 27 deletions
|
|
@ -212,6 +212,22 @@ const manascoreCollection = defineCollection({
|
|||
seo: z.number().min(0).max(100),
|
||||
})
|
||||
.optional(),
|
||||
// Dependency health metrics
|
||||
dependencies: z
|
||||
.object({
|
||||
total: z.number(), // Total dependency count
|
||||
outdated: z.number(), // Packages with available updates
|
||||
vulnerabilities: z
|
||||
.object({
|
||||
critical: z.number().default(0),
|
||||
high: z.number().default(0),
|
||||
moderate: z.number().default(0),
|
||||
low: z.number().default(0),
|
||||
})
|
||||
.optional(),
|
||||
lastChecked: z.string().optional(), // ISO date
|
||||
})
|
||||
.optional(),
|
||||
// Score history for trend visualization
|
||||
history: z
|
||||
.array(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ scores:
|
|||
documentation: 98
|
||||
security: 92
|
||||
ux: 95
|
||||
dependencies:
|
||||
total: 42
|
||||
outdated: 9
|
||||
vulnerabilities:
|
||||
critical: 0
|
||||
high: 0
|
||||
moderate: 0
|
||||
low: 0
|
||||
lastChecked: '2026-03-24'
|
||||
lighthouse:
|
||||
performance: 92
|
||||
accessibility: 95
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ scores:
|
|||
documentation: 95
|
||||
security: 90
|
||||
ux: 94
|
||||
dependencies:
|
||||
total: 38
|
||||
outdated: 9
|
||||
vulnerabilities:
|
||||
critical: 0
|
||||
high: 0
|
||||
moderate: 0
|
||||
low: 0
|
||||
lastChecked: '2026-03-24'
|
||||
lighthouse:
|
||||
performance: 90
|
||||
accessibility: 93
|
||||
|
|
|
|||
|
|
@ -274,6 +274,113 @@ function getBarColor(score: number): string {
|
|||
})()
|
||||
}
|
||||
|
||||
{/* Dependency Health */}
|
||||
{
|
||||
audit.data.dependencies &&
|
||||
(() => {
|
||||
const deps = audit.data.dependencies;
|
||||
const vulnCount = deps.vulnerabilities
|
||||
? deps.vulnerabilities.critical +
|
||||
deps.vulnerabilities.high +
|
||||
deps.vulnerabilities.moderate +
|
||||
deps.vulnerabilities.low
|
||||
: 0;
|
||||
const healthPct = Math.round(((deps.total - deps.outdated) / deps.total) * 100);
|
||||
|
||||
return (
|
||||
<div class="border-border/50 mb-8 rounded-xl border bg-gradient-to-br from-white/5 to-white/[0.02] p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 class="text-foreground text-sm font-semibold">Dependency Health</h2>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
Paketstand und Sicherheit
|
||||
{deps.lastChecked ? ` (geprüft ${deps.lastChecked})` : ''}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
class={`text-lg font-bold ${healthPct >= 80 ? 'text-emerald-500' : healthPct >= 60 ? 'text-yellow-500' : 'text-red-500'}`}
|
||||
>
|
||||
{healthPct}% aktuell
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
<div>
|
||||
<span class="text-foreground text-2xl font-bold">{deps.total}</span>
|
||||
<span class="text-muted-foreground block text-xs">Pakete gesamt</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class={`text-2xl font-bold ${deps.outdated > 10 ? 'text-yellow-500' : 'text-foreground'}`}
|
||||
>
|
||||
{deps.outdated}
|
||||
</span>
|
||||
<span class="text-muted-foreground block text-xs">Veraltet</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class={`text-2xl font-bold ${vulnCount > 0 ? 'text-red-500' : 'text-emerald-500'}`}
|
||||
>
|
||||
{vulnCount}
|
||||
</span>
|
||||
<span class="text-muted-foreground block text-xs">Vulnerabilities</span>
|
||||
</div>
|
||||
<div>
|
||||
{deps.vulnerabilities ? (
|
||||
<div class="flex gap-1.5 mt-1">
|
||||
{deps.vulnerabilities.critical > 0 && (
|
||||
<span class="bg-red-500/10 text-red-500 rounded px-1.5 py-0.5 text-[10px] font-medium">
|
||||
{deps.vulnerabilities.critical} Critical
|
||||
</span>
|
||||
)}
|
||||
{deps.vulnerabilities.high > 0 && (
|
||||
<span class="bg-orange-500/10 text-orange-500 rounded px-1.5 py-0.5 text-[10px] font-medium">
|
||||
{deps.vulnerabilities.high} High
|
||||
</span>
|
||||
)}
|
||||
{deps.vulnerabilities.moderate > 0 && (
|
||||
<span class="bg-yellow-500/10 text-yellow-500 rounded px-1.5 py-0.5 text-[10px] font-medium">
|
||||
{deps.vulnerabilities.moderate} Mod
|
||||
</span>
|
||||
)}
|
||||
{deps.vulnerabilities.low > 0 && (
|
||||
<span class="bg-blue-500/10 text-blue-500 rounded px-1.5 py-0.5 text-[10px] font-medium">
|
||||
{deps.vulnerabilities.low} Low
|
||||
</span>
|
||||
)}
|
||||
{vulnCount === 0 && (
|
||||
<span class="bg-emerald-500/10 text-emerald-500 rounded px-1.5 py-0.5 text-[10px] font-medium">
|
||||
✓ Sicher
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<span class="text-muted-foreground text-xs">Keine Daten</span>
|
||||
)}
|
||||
<span class="text-muted-foreground block text-xs mt-1">Schweregrad</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Health bar */}
|
||||
<div class="mt-4">
|
||||
<div class="bg-muted h-2 overflow-hidden rounded-full">
|
||||
<div
|
||||
class={`h-full rounded-full transition-all ${healthPct >= 80 ? 'bg-emerald-500' : healthPct >= 60 ? 'bg-yellow-500' : 'bg-red-500'}`}
|
||||
style={`width: ${healthPct}%`}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-between mt-1">
|
||||
<span class="text-muted-foreground/50 text-[10px]">
|
||||
{deps.outdated} veraltet
|
||||
</span>
|
||||
<span class="text-muted-foreground/50 text-[10px]">
|
||||
{deps.total - deps.outdated} aktuell
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()
|
||||
}
|
||||
|
||||
{/* Stats */}
|
||||
{
|
||||
audit.data.stats && (
|
||||
|
|
|
|||
|
|
@ -172,6 +172,46 @@ const statuses = [...new Set(sortedAudits.map((a) => a.data.status))];
|
|||
))}
|
||||
</div>
|
||||
|
||||
{/* Dependency health */}
|
||||
{data.dependencies &&
|
||||
(() => {
|
||||
const deps = data.dependencies;
|
||||
const vulnCount = deps.vulnerabilities
|
||||
? deps.vulnerabilities.critical +
|
||||
deps.vulnerabilities.high +
|
||||
deps.vulnerabilities.moderate +
|
||||
deps.vulnerabilities.low
|
||||
: 0;
|
||||
const healthPct = Math.round(
|
||||
((deps.total - deps.outdated) / deps.total) * 100
|
||||
);
|
||||
const healthColor =
|
||||
vulnCount > 0
|
||||
? deps.vulnerabilities?.critical || deps.vulnerabilities?.high
|
||||
? 'text-red-500'
|
||||
: 'text-yellow-500'
|
||||
: healthPct >= 80
|
||||
? 'text-emerald-500'
|
||||
: 'text-yellow-500';
|
||||
return (
|
||||
<div class="mt-2 flex items-center gap-3">
|
||||
<span class="text-muted-foreground/50 text-[9px]">Deps:</span>
|
||||
<span class={`text-[10px] font-medium ${healthColor}`}>
|
||||
{deps.total} total, {deps.outdated} outdated
|
||||
</span>
|
||||
{vulnCount > 0 ? (
|
||||
<span class="text-[10px] font-medium text-red-500">
|
||||
⚠ {vulnCount} vulnerabilities
|
||||
</span>
|
||||
) : (
|
||||
<span class="text-[10px] font-medium text-emerald-500">
|
||||
✓ no vulnerabilities
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Lighthouse scores */}
|
||||
{data.lighthouse && (
|
||||
<div class="mt-2 flex items-center gap-3">
|
||||
|
|
|
|||
|
|
@ -114,5 +114,27 @@ ingress:
|
|||
- hostname: glitchtip.mana.how
|
||||
service: http://localhost:8020
|
||||
|
||||
# Self-Hosted Landing Pages (via Nginx on port 4400)
|
||||
- hostname: it.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: chats.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: pics.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: zitares.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: presis.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: clocks.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: manadeck.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: nutriphi.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: citycorners.mana.how
|
||||
service: http://localhost:4400
|
||||
- hostname: docs.mana.how
|
||||
service: http://localhost:4400
|
||||
|
||||
# Catch-all
|
||||
- service: http_status:404
|
||||
|
|
|
|||
|
|
@ -87,6 +87,25 @@ services:
|
|||
done
|
||||
"
|
||||
|
||||
# Self-hosted Landing Pages (replaces Cloudflare Pages)
|
||||
# Serves all Astro landing page dist/ folders via Nginx
|
||||
# Build with: ./scripts/mac-mini/build-landings.sh
|
||||
landings:
|
||||
image: nginx:alpine
|
||||
container_name: mana-infra-landings
|
||||
restart: always
|
||||
volumes:
|
||||
- ./docker/nginx/landings.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ./docker/nginx/snippets:/etc/nginx/snippets:ro
|
||||
- /Volumes/ManaData/landings:/srv/landings:ro
|
||||
ports:
|
||||
- "4400:80"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: mana-infra-redis
|
||||
|
|
@ -871,9 +890,10 @@ services:
|
|||
- "4080:80"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/"]
|
||||
interval: 120s
|
||||
interval: 180s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
matrix-web:
|
||||
build:
|
||||
|
|
@ -893,9 +913,10 @@ services:
|
|||
- "4090:5180"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5180/health"]
|
||||
interval: 120s
|
||||
interval: 180s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
# Matrix Bots (Ports 4010-4029)
|
||||
matrix-mana-bot:
|
||||
|
|
@ -932,7 +953,7 @@ services:
|
|||
- "4010:4010"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4010/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -961,7 +982,7 @@ services:
|
|||
- "4011:4011"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4011/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1002,7 +1023,7 @@ services:
|
|||
- "4012:4012"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4012/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1035,7 +1056,7 @@ services:
|
|||
- "4013:4013"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4013/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1071,7 +1092,7 @@ services:
|
|||
- "4014:4014"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4014/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1105,7 +1126,7 @@ services:
|
|||
- "4015:4015"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4015/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1139,7 +1160,7 @@ services:
|
|||
- "4016:4016"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4016/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1169,7 +1190,7 @@ services:
|
|||
- "4017:4017"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4017/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1210,7 +1231,7 @@ services:
|
|||
- "4018:4018"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4018/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1244,7 +1265,7 @@ services:
|
|||
- "4019:4019"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4019/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1277,7 +1298,7 @@ services:
|
|||
- "4021:4021"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4021/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1314,7 +1335,7 @@ services:
|
|||
- "4020:4020"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4020/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1354,7 +1375,7 @@ services:
|
|||
- "4022:4022"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4022/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1907,9 +1928,10 @@ services:
|
|||
- "8000:8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8000/api/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
umami:
|
||||
image: ghcr.io/umami-software/umami:postgresql-latest
|
||||
|
|
@ -1927,7 +1949,7 @@ services:
|
|||
- "8010:3000"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/heartbeat"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
@ -1956,9 +1978,10 @@ services:
|
|||
- "9090:9090"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9090/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
pushgateway:
|
||||
image: prom/pushgateway:v1.7.0
|
||||
|
|
@ -1968,9 +1991,10 @@ services:
|
|||
- "9091:9091"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9091/-/healthy"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
cadvisor:
|
||||
image: gcr.io/cadvisor/cadvisor:v0.49.1
|
||||
|
|
@ -1987,9 +2011,10 @@ services:
|
|||
- "9110:8080"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/healthz"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
postgres-exporter:
|
||||
image: prometheuscommunity/postgres-exporter:v0.15.0
|
||||
|
|
@ -2036,9 +2061,10 @@ services:
|
|||
- "9100:9100"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9100/metrics"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
# ============================================
|
||||
# Alerting Stack (Ports 9093-9095)
|
||||
|
|
@ -2067,9 +2093,10 @@ services:
|
|||
- "8880:8880"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8880/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
alertmanager:
|
||||
image: prom/alertmanager:v0.27.0
|
||||
|
|
@ -2089,9 +2116,10 @@ services:
|
|||
- "9093:9093"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9093/-/healthy"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
alert-notifier:
|
||||
build:
|
||||
|
|
@ -2109,10 +2137,10 @@ services:
|
|||
- "9095:8080"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/health"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
start_period: 10s
|
||||
|
||||
# ============================================
|
||||
# Auto-Update (Watchtower)
|
||||
|
|
@ -2162,9 +2190,10 @@ services:
|
|||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8020/_health/')"]
|
||||
interval: 120s
|
||||
interval: 300s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
glitchtip-worker:
|
||||
image: glitchtip/glitchtip:latest
|
||||
|
|
@ -2203,9 +2232,10 @@ services:
|
|||
- "5100:5100"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5100/"]
|
||||
interval: 120s
|
||||
interval: 180s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
|
|
|
|||
108
docker/nginx/landings.conf
Normal file
108
docker/nginx/landings.conf
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# Nginx Configuration for Self-Hosted Landing Pages
|
||||
# Each server block serves a different landing page from its dist/ directory
|
||||
# All traffic comes through Cloudflare Tunnel → localhost:4400
|
||||
|
||||
# Shared settings
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json image/svg+xml;
|
||||
|
||||
# Default server (catch-all → it.mana.how as homepage)
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
root /srv/landings/it;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# it.mana.how — European Tech Independence
|
||||
server {
|
||||
listen 80;
|
||||
server_name it.mana.how;
|
||||
root /srv/landings/it;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# chats.mana.how — Chat Landing
|
||||
server {
|
||||
listen 80;
|
||||
server_name chats.mana.how;
|
||||
root /srv/landings/chat;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# pics.mana.how — Picture Landing
|
||||
server {
|
||||
listen 80;
|
||||
server_name pics.mana.how;
|
||||
root /srv/landings/picture;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# zitares.mana.how — Zitare Landing
|
||||
server {
|
||||
listen 80;
|
||||
server_name zitares.mana.how;
|
||||
root /srv/landings/zitare;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# presis.mana.how — Presi Landing
|
||||
server {
|
||||
listen 80;
|
||||
server_name presis.mana.how;
|
||||
root /srv/landings/presi;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# clocks.mana.how — Clock Landing
|
||||
server {
|
||||
listen 80;
|
||||
server_name clocks.mana.how;
|
||||
root /srv/landings/clock;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# manadeck.mana.how — ManaDeck Landing
|
||||
server {
|
||||
listen 80;
|
||||
server_name manadeck.mana.how;
|
||||
root /srv/landings/manadeck;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# nutriphi.mana.how — NutriPhi Landing
|
||||
server {
|
||||
listen 80;
|
||||
server_name nutriphi.mana.how;
|
||||
root /srv/landings/nutriphi;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# citycorners.mana.how — CityCorners Landing
|
||||
server {
|
||||
listen 80;
|
||||
server_name citycorners.mana.how;
|
||||
root /srv/landings/citycorners;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
|
||||
# docs.mana.how — Documentation
|
||||
server {
|
||||
listen 80;
|
||||
server_name docs.mana.how;
|
||||
root /srv/landings/docs;
|
||||
index index.html;
|
||||
include /etc/nginx/snippets/landing-common.conf;
|
||||
}
|
||||
24
docker/nginx/snippets/landing-common.conf
Normal file
24
docker/nginx/snippets/landing-common.conf
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Common settings for all landing page server blocks
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Cache static assets aggressively (Astro hashes filenames)
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot|webp|avif)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# SPA fallback: try file, then directory, then index.html
|
||||
location / {
|
||||
try_files $uri $uri/ $uri/index.html /index.html;
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
285
docs/CLOUDFLARE_FALLBACK.md
Normal file
285
docs/CLOUDFLARE_FALLBACK.md
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
# Cloudflare Tunnel Fallback-Plan
|
||||
|
||||
> Was tun wenn Cloudflare ausfällt oder den Account sperrt?
|
||||
|
||||
## Risiko
|
||||
|
||||
Cloudflare Tunnel ist der **einzige** Weg vom Internet zum Mac Mini. Wenn Cloudflare nicht erreichbar ist:
|
||||
- Alle *.mana.how Subdomains sind offline
|
||||
- SSH nur im lokalen Netzwerk möglich
|
||||
- Kein Deployment, kein Monitoring
|
||||
|
||||
**Wahrscheinlichkeit:** Gering (Cloudflare hat >99.99% Uptime), aber Accountsperren oder Policy-Änderungen sind ein Risiko.
|
||||
|
||||
## Plan B: WireGuard + Caddy auf Hetzner VPS
|
||||
|
||||
### Architektur
|
||||
|
||||
```
|
||||
Internet
|
||||
│
|
||||
▼
|
||||
Hetzner VPS (€3.79/Monat, CX22)
|
||||
├── Caddy (Reverse Proxy + Auto-TLS)
|
||||
├── WireGuard Server
|
||||
└── DNS: *.mana.how → VPS IP
|
||||
│
|
||||
│ WireGuard Tunnel (verschlüsselt)
|
||||
│
|
||||
▼
|
||||
Mac Mini (WireGuard Client)
|
||||
├── Alle Services auf localhost
|
||||
└── Erreichbar über WireGuard-IP (z.B. 10.0.0.2)
|
||||
```
|
||||
|
||||
### Vorteile
|
||||
|
||||
- **Kein Vendor Lock-in:** Hetzner ist deutscher Anbieter
|
||||
- **Eigene IP:** Keine Abhängigkeit von Cloudflare Proxy
|
||||
- **WireGuard:** Schneller als Cloudflare Tunnel (~10% weniger Latenz)
|
||||
- **Let's Encrypt:** Caddy macht TLS automatisch
|
||||
- **Kosten:** €3.79/Monat (CX22: 2 vCPU, 4 GB RAM, 40 GB SSD)
|
||||
|
||||
### Einrichtung VPS (einmalig, ~1 Stunde)
|
||||
|
||||
#### 1. Hetzner VPS erstellen
|
||||
|
||||
```bash
|
||||
# CX22 (kleinster mit genug RAM für Caddy + WireGuard)
|
||||
# Standort: Falkenstein (DE) oder Nürnberg (DE)
|
||||
# OS: Ubuntu 24.04
|
||||
# SSH Key: Mac Mini public key
|
||||
```
|
||||
|
||||
#### 2. WireGuard installieren
|
||||
|
||||
**Auf dem VPS:**
|
||||
```bash
|
||||
apt update && apt install -y wireguard
|
||||
|
||||
# Keys generieren
|
||||
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
|
||||
chmod 600 /etc/wireguard/server_private.key
|
||||
|
||||
# Config erstellen
|
||||
cat > /etc/wireguard/wg0.conf << EOF
|
||||
[Interface]
|
||||
Address = 10.0.0.1/24
|
||||
PrivateKey = $(cat /etc/wireguard/server_private.key)
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
# Mac Mini
|
||||
PublicKey = <MAC_MINI_PUBLIC_KEY>
|
||||
AllowedIPs = 10.0.0.2/32
|
||||
EOF
|
||||
|
||||
systemctl enable --now wg-quick@wg0
|
||||
```
|
||||
|
||||
**Auf dem Mac Mini:**
|
||||
```bash
|
||||
brew install wireguard-tools
|
||||
|
||||
# Keys generieren
|
||||
wg genkey | tee /etc/wireguard/client_private.key | wg pubkey > /etc/wireguard/client_public.key
|
||||
|
||||
# Config
|
||||
cat > /etc/wireguard/wg0.conf << EOF
|
||||
[Interface]
|
||||
Address = 10.0.0.2/24
|
||||
PrivateKey = $(cat /etc/wireguard/client_private.key)
|
||||
|
||||
[Peer]
|
||||
PublicKey = <VPS_PUBLIC_KEY>
|
||||
Endpoint = <VPS_IP>:51820
|
||||
AllowedIPs = 10.0.0.0/24
|
||||
PersistentKeepalive = 25
|
||||
EOF
|
||||
|
||||
wg-quick up wg0
|
||||
```
|
||||
|
||||
#### 3. Caddy installieren (VPS)
|
||||
|
||||
```bash
|
||||
apt install -y debian-keyring debian-archive-keyring apt-transport-https
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
|
||||
apt update && apt install caddy
|
||||
```
|
||||
|
||||
#### 4. Caddyfile erstellen (VPS)
|
||||
|
||||
```Caddyfile
|
||||
# /etc/caddy/Caddyfile
|
||||
# Alle Domains → Mac Mini via WireGuard
|
||||
|
||||
mana.how {
|
||||
reverse_proxy 10.0.0.2:5000
|
||||
}
|
||||
|
||||
auth.mana.how {
|
||||
reverse_proxy 10.0.0.2:3001
|
||||
}
|
||||
|
||||
chat.mana.how {
|
||||
reverse_proxy 10.0.0.2:5010
|
||||
}
|
||||
|
||||
chat-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3030
|
||||
}
|
||||
|
||||
todo.mana.how {
|
||||
reverse_proxy 10.0.0.2:5011
|
||||
}
|
||||
|
||||
todo-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3031
|
||||
}
|
||||
|
||||
calendar.mana.how {
|
||||
reverse_proxy 10.0.0.2:5012
|
||||
}
|
||||
|
||||
calendar-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3032
|
||||
}
|
||||
|
||||
clock.mana.how {
|
||||
reverse_proxy 10.0.0.2:5013
|
||||
}
|
||||
|
||||
clock-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3033
|
||||
}
|
||||
|
||||
contacts.mana.how {
|
||||
reverse_proxy 10.0.0.2:5014
|
||||
}
|
||||
|
||||
contacts-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3034
|
||||
}
|
||||
|
||||
storage.mana.how {
|
||||
reverse_proxy 10.0.0.2:5015
|
||||
}
|
||||
|
||||
storage-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3035
|
||||
}
|
||||
|
||||
presi.mana.how {
|
||||
reverse_proxy 10.0.0.2:5016
|
||||
}
|
||||
|
||||
presi-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3036
|
||||
}
|
||||
|
||||
nutriphi.mana.how {
|
||||
reverse_proxy 10.0.0.2:5017
|
||||
}
|
||||
|
||||
nutriphi-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3037
|
||||
}
|
||||
|
||||
photos.mana.how {
|
||||
reverse_proxy 10.0.0.2:5019
|
||||
}
|
||||
|
||||
photos-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3039
|
||||
}
|
||||
|
||||
mukke.mana.how {
|
||||
reverse_proxy 10.0.0.2:5180
|
||||
}
|
||||
|
||||
picture.mana.how {
|
||||
reverse_proxy 10.0.0.2:5021
|
||||
}
|
||||
|
||||
picture-api.mana.how {
|
||||
reverse_proxy 10.0.0.2:3040
|
||||
}
|
||||
|
||||
playground.mana.how {
|
||||
reverse_proxy 10.0.0.2:5090
|
||||
}
|
||||
|
||||
matrix.mana.how {
|
||||
reverse_proxy 10.0.0.2:4000
|
||||
}
|
||||
|
||||
element.mana.how {
|
||||
reverse_proxy 10.0.0.2:4080
|
||||
}
|
||||
|
||||
grafana.mana.how {
|
||||
reverse_proxy 10.0.0.2:8000
|
||||
}
|
||||
|
||||
stats.mana.how {
|
||||
reverse_proxy 10.0.0.2:8010
|
||||
}
|
||||
|
||||
glitchtip.mana.how {
|
||||
reverse_proxy 10.0.0.2:8020
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. DNS umstellen (Failover-Schritt)
|
||||
|
||||
Beim Ausfall von Cloudflare Tunnel:
|
||||
|
||||
```bash
|
||||
# 1. WireGuard-Verbindung prüfen
|
||||
ssh mana-server "ping -c1 10.0.0.1" # Ping VPS via WireGuard
|
||||
|
||||
# 2. DNS bei Cloudflare umstellen (alle *.mana.how → VPS IP)
|
||||
# Cloudflare Dashboard → DNS → *.mana.how → A Record → <VPS_IP>
|
||||
# ODER falls Cloudflare komplett down:
|
||||
# Domain zu anderem DNS-Provider transferieren (vorher vorbereiten!)
|
||||
|
||||
# 3. Caddy starten
|
||||
ssh vps "systemctl start caddy"
|
||||
|
||||
# 4. Prüfen
|
||||
curl https://mana.how # Sollte über VPS → WireGuard → Mac Mini routen
|
||||
```
|
||||
|
||||
## Failover-Checkliste
|
||||
|
||||
| # | Schritt | Zeit | Verantwortlich |
|
||||
|---|---------|------|----------------|
|
||||
| 1 | Feststellen: Cloudflare Tunnel ist down | Auto (Health Check Alert) | Automatisch |
|
||||
| 2 | VPS WireGuard-Verbindung prüfen | 1 Min | Admin |
|
||||
| 3 | DNS auf VPS-IP umstellen | 5 Min | Admin (Cloudflare Dashboard) |
|
||||
| 4 | Caddy aktivieren | 1 Min | Admin (SSH zu VPS) |
|
||||
| 5 | TLS-Zertifikate generieren lassen | 2-5 Min | Automatisch (Caddy + Let's Encrypt) |
|
||||
| 6 | Alle Services testen | 5 Min | Admin |
|
||||
| **Gesamt** | | **~15 Min** | |
|
||||
|
||||
## Vorbereitung (jetzt erledigen)
|
||||
|
||||
- [ ] Hetzner Account erstellen
|
||||
- [ ] VPS bestellen (CX22, €3.79/Monat)
|
||||
- [ ] WireGuard einrichten (VPS + Mac Mini)
|
||||
- [ ] WireGuard-Verbindung testen
|
||||
- [ ] Caddyfile erstellen (alle Domains)
|
||||
- [ ] DNS-Failover-Prozedur testen (mit Test-Subdomain)
|
||||
- [ ] Failover-Checkliste ausdrucken / im Wiki speichern
|
||||
|
||||
## Plan C: Direkte IP
|
||||
|
||||
Falls auch Hetzner nicht verfügbar:
|
||||
1. ISP kontaktieren für feste IP-Adresse
|
||||
2. Port-Forwarding auf Router einrichten (80, 443)
|
||||
3. Let's Encrypt Zertifikat via DNS-Challenge (kein HTTP nötig)
|
||||
4. DNS bei einem dritten Provider (z.B. Hetzner DNS, Gandi)
|
||||
|
||||
**Nachteil:** Consumer-ISPs blockieren oft Port 25 (E-Mail) und Port 80/443 ist nicht garantiert.
|
||||
56
scripts/mac-mini/build-landings.sh
Executable file
56
scripts/mac-mini/build-landings.sh
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
#!/bin/bash
|
||||
# Build all landing pages and copy dist/ to the shared nginx volume
|
||||
# Run on the Mac Mini after git pull
|
||||
#
|
||||
# Usage: ./scripts/mac-mini/build-landings.sh
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
OUTPUT_DIR="/Volumes/ManaData/landings"
|
||||
|
||||
echo "=== Building Landing Pages ==="
|
||||
echo "Output: $OUTPUT_DIR"
|
||||
echo ""
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Landing pages to build (filter name → dist path → output name)
|
||||
declare -A LANDINGS=(
|
||||
["it"]="services/it-landing"
|
||||
["chat"]="apps/chat/apps/landing"
|
||||
["picture"]="apps/picture/apps/landing"
|
||||
["zitare"]="apps/zitare/apps/landing"
|
||||
["presi"]="apps/presi/apps/landing"
|
||||
["clock"]="apps/clock/apps/landing"
|
||||
["manadeck"]="apps/manadeck/apps/landing"
|
||||
["nutriphi"]="apps/nutriphi/apps/landing"
|
||||
["citycorners"]="apps/citycorners/apps/landing"
|
||||
)
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
for name in "${!LANDINGS[@]}"; do
|
||||
dir="${LANDINGS[$name]}"
|
||||
if [ -d "$dir" ]; then
|
||||
echo "Building $name ($dir)..."
|
||||
pnpm --filter "./$dir" build 2>&1 | tail -3
|
||||
|
||||
# Copy dist to output
|
||||
if [ -d "$dir/dist" ]; then
|
||||
rm -rf "$OUTPUT_DIR/$name"
|
||||
cp -r "$dir/dist" "$OUTPUT_DIR/$name"
|
||||
echo " → $OUTPUT_DIR/$name ($(du -sh "$OUTPUT_DIR/$name" | cut -f1))"
|
||||
else
|
||||
echo " ⚠ No dist/ found for $name"
|
||||
fi
|
||||
else
|
||||
echo " ⚠ Directory not found: $dir"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=== Done ==="
|
||||
echo ""
|
||||
echo "Restart nginx to pick up changes:"
|
||||
echo " docker restart mana-infra-landings"
|
||||
Loading…
Add table
Add a link
Reference in a new issue