mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +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),
|
seo: z.number().min(0).max(100),
|
||||||
})
|
})
|
||||||
.optional(),
|
.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
|
// Score history for trend visualization
|
||||||
history: z
|
history: z
|
||||||
.array(
|
.array(
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,15 @@ scores:
|
||||||
documentation: 98
|
documentation: 98
|
||||||
security: 92
|
security: 92
|
||||||
ux: 95
|
ux: 95
|
||||||
|
dependencies:
|
||||||
|
total: 42
|
||||||
|
outdated: 9
|
||||||
|
vulnerabilities:
|
||||||
|
critical: 0
|
||||||
|
high: 0
|
||||||
|
moderate: 0
|
||||||
|
low: 0
|
||||||
|
lastChecked: '2026-03-24'
|
||||||
lighthouse:
|
lighthouse:
|
||||||
performance: 92
|
performance: 92
|
||||||
accessibility: 95
|
accessibility: 95
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,15 @@ scores:
|
||||||
documentation: 95
|
documentation: 95
|
||||||
security: 90
|
security: 90
|
||||||
ux: 94
|
ux: 94
|
||||||
|
dependencies:
|
||||||
|
total: 38
|
||||||
|
outdated: 9
|
||||||
|
vulnerabilities:
|
||||||
|
critical: 0
|
||||||
|
high: 0
|
||||||
|
moderate: 0
|
||||||
|
low: 0
|
||||||
|
lastChecked: '2026-03-24'
|
||||||
lighthouse:
|
lighthouse:
|
||||||
performance: 90
|
performance: 90
|
||||||
accessibility: 93
|
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 */}
|
{/* Stats */}
|
||||||
{
|
{
|
||||||
audit.data.stats && (
|
audit.data.stats && (
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,46 @@ const statuses = [...new Set(sortedAudits.map((a) => a.data.status))];
|
||||||
))}
|
))}
|
||||||
</div>
|
</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 */}
|
{/* Lighthouse scores */}
|
||||||
{data.lighthouse && (
|
{data.lighthouse && (
|
||||||
<div class="mt-2 flex items-center gap-3">
|
<div class="mt-2 flex items-center gap-3">
|
||||||
|
|
|
||||||
|
|
@ -114,5 +114,27 @@ ingress:
|
||||||
- hostname: glitchtip.mana.how
|
- hostname: glitchtip.mana.how
|
||||||
service: http://localhost:8020
|
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
|
# Catch-all
|
||||||
- service: http_status:404
|
- service: http_status:404
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,25 @@ services:
|
||||||
done
|
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:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: mana-infra-redis
|
container_name: mana-infra-redis
|
||||||
|
|
@ -871,9 +890,10 @@ services:
|
||||||
- "4080:80"
|
- "4080:80"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/"]
|
||||||
interval: 120s
|
interval: 180s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
matrix-web:
|
matrix-web:
|
||||||
build:
|
build:
|
||||||
|
|
@ -893,9 +913,10 @@ services:
|
||||||
- "4090:5180"
|
- "4090:5180"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5180/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5180/health"]
|
||||||
interval: 120s
|
interval: 180s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
# Matrix Bots (Ports 4010-4029)
|
# Matrix Bots (Ports 4010-4029)
|
||||||
matrix-mana-bot:
|
matrix-mana-bot:
|
||||||
|
|
@ -932,7 +953,7 @@ services:
|
||||||
- "4010:4010"
|
- "4010:4010"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4010/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4010/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -961,7 +982,7 @@ services:
|
||||||
- "4011:4011"
|
- "4011:4011"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4011/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4011/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1002,7 +1023,7 @@ services:
|
||||||
- "4012:4012"
|
- "4012:4012"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4012/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4012/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1035,7 +1056,7 @@ services:
|
||||||
- "4013:4013"
|
- "4013:4013"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4013/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4013/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1071,7 +1092,7 @@ services:
|
||||||
- "4014:4014"
|
- "4014:4014"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4014/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4014/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1105,7 +1126,7 @@ services:
|
||||||
- "4015:4015"
|
- "4015:4015"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4015/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4015/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1139,7 +1160,7 @@ services:
|
||||||
- "4016:4016"
|
- "4016:4016"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4016/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4016/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1169,7 +1190,7 @@ services:
|
||||||
- "4017:4017"
|
- "4017:4017"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4017/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4017/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1210,7 +1231,7 @@ services:
|
||||||
- "4018:4018"
|
- "4018:4018"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4018/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4018/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1244,7 +1265,7 @@ services:
|
||||||
- "4019:4019"
|
- "4019:4019"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4019/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4019/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1277,7 +1298,7 @@ services:
|
||||||
- "4021:4021"
|
- "4021:4021"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4021/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4021/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1314,7 +1335,7 @@ services:
|
||||||
- "4020:4020"
|
- "4020:4020"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4020/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4020/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1354,7 +1375,7 @@ services:
|
||||||
- "4022:4022"
|
- "4022:4022"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4022/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4022/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1907,9 +1928,10 @@ services:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8000/api/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8000/api/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
umami:
|
umami:
|
||||||
image: ghcr.io/umami-software/umami:postgresql-latest
|
image: ghcr.io/umami-software/umami:postgresql-latest
|
||||||
|
|
@ -1927,7 +1949,7 @@ services:
|
||||||
- "8010:3000"
|
- "8010:3000"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/heartbeat"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/heartbeat"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
@ -1956,9 +1978,10 @@ services:
|
||||||
- "9090:9090"
|
- "9090:9090"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9090/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9090/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
pushgateway:
|
pushgateway:
|
||||||
image: prom/pushgateway:v1.7.0
|
image: prom/pushgateway:v1.7.0
|
||||||
|
|
@ -1968,9 +1991,10 @@ services:
|
||||||
- "9091:9091"
|
- "9091:9091"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9091/-/healthy"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9091/-/healthy"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
cadvisor:
|
cadvisor:
|
||||||
image: gcr.io/cadvisor/cadvisor:v0.49.1
|
image: gcr.io/cadvisor/cadvisor:v0.49.1
|
||||||
|
|
@ -1987,9 +2011,10 @@ services:
|
||||||
- "9110:8080"
|
- "9110:8080"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/healthz"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/healthz"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
postgres-exporter:
|
postgres-exporter:
|
||||||
image: prometheuscommunity/postgres-exporter:v0.15.0
|
image: prometheuscommunity/postgres-exporter:v0.15.0
|
||||||
|
|
@ -2036,9 +2061,10 @@ services:
|
||||||
- "9100:9100"
|
- "9100:9100"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9100/metrics"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9100/metrics"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Alerting Stack (Ports 9093-9095)
|
# Alerting Stack (Ports 9093-9095)
|
||||||
|
|
@ -2067,9 +2093,10 @@ services:
|
||||||
- "8880:8880"
|
- "8880:8880"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8880/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8880/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: prom/alertmanager:v0.27.0
|
image: prom/alertmanager:v0.27.0
|
||||||
|
|
@ -2089,9 +2116,10 @@ services:
|
||||||
- "9093:9093"
|
- "9093:9093"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9093/-/healthy"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9093/-/healthy"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
alert-notifier:
|
alert-notifier:
|
||||||
build:
|
build:
|
||||||
|
|
@ -2109,10 +2137,10 @@ services:
|
||||||
- "9095:8080"
|
- "9095:8080"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/health"]
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/health"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 5s
|
start_period: 10s
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Auto-Update (Watchtower)
|
# Auto-Update (Watchtower)
|
||||||
|
|
@ -2162,9 +2190,10 @@ services:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8020/_health/')"]
|
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8020/_health/')"]
|
||||||
interval: 120s
|
interval: 300s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
glitchtip-worker:
|
glitchtip-worker:
|
||||||
image: glitchtip/glitchtip:latest
|
image: glitchtip/glitchtip:latest
|
||||||
|
|
@ -2203,9 +2232,10 @@ services:
|
||||||
- "5100:5100"
|
- "5100:5100"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5100/"]
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5100/"]
|
||||||
interval: 120s
|
interval: 180s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
redis_data:
|
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