Skip to content

Commit eead319

Browse files
committed
showcase: add public landing bundle at www.mdapi.ch
Curated public showcase (nginx static) listing only public-facing services and hosted sites, as a personalised-free public front door distinct from the internal app launcher. flame keeps the mdapi.ch apex until the per-user portal (start.mdapi.ch) is live, then the apex cuts over and flame retires. DNS-01 issuer + per-namespace TSIG (matching the other bundles); image pinned to nginxinc/nginx-unprivileged:1.27-alpine + IfNotPresent, kept current by Renovate Lane-B (renovate.json) rather than Keel.
1 parent 81817f1 commit eead319

8 files changed

Lines changed: 298 additions & 0 deletions

File tree

renovate.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@
7878
"enabled": true,
7979
"sourceUrl": "https://github.com/keel-hq/keel",
8080
"minimumReleaseAge": "3 days"
81+
},
82+
{
83+
"description": "Lane-B upstream stock: nginxinc/nginx-unprivileged (showcase + cert-host static serving). Pinned tag, Renovate-owned (not Keel). Manual review + 3-day soak.",
84+
"matchManagers": ["kubernetes"],
85+
"matchPackageNames": ["nginxinc/nginx-unprivileged"],
86+
"enabled": true,
87+
"minimumReleaseAge": "3 days"
8188
}
8289
],
8390
"hostRules": [

showcase/fleet.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defaultNamespace: showcase
2+
namespace: showcase
3+
namespaceLabels:
4+
cd.mdapi.ch: fleet
5+
cdenv.mdapi.ch: prod
6+
dependsOn:
7+
- name: mdapi-prod-cert-manager
8+
helm:
9+
maxHistory: 25
10+
11+
# VPA (mode=Auto) writes resources.requests/limits onto running pods.
12+
# Strip the resources block from both sides before diff so Fleet stops
13+
# flagging the bundle as Modified.
14+
diff:
15+
comparePatches:
16+
- apiVersion: apps/v1
17+
kind: Deployment
18+
name: showcase
19+
namespace: showcase
20+
operations:
21+
- {"op": "remove", "path": "/spec/template/spec/containers/0/resources"}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
apiVersion: cert-manager.io/v1
2+
kind: Issuer
3+
metadata:
4+
name: letsencrypt-prod
5+
namespace: showcase
6+
spec:
7+
acme:
8+
email: tillo@tillo.ch
9+
privateKeySecretRef:
10+
name: letsencrypt-prod
11+
server: https://acme-v02.api.letsencrypt.org/directory
12+
solvers:
13+
- dns01:
14+
rfc2136:
15+
nameserver: 31.3.128.59:53
16+
tsigKeyName: mdapi
17+
tsigAlgorithm: HMACSHA256
18+
tsigSecretSecretRef:
19+
name: tsig-secret
20+
key: tsig-secret-key
21+
selector:
22+
dnsZones:
23+
- 'mdapi.ch'
24+
- http01:
25+
ingress:
26+
class: nginx

showcase/showcase-cm.yml

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: showcase-html
5+
namespace: showcase
6+
data:
7+
index.html: |
8+
<!DOCTYPE html>
9+
<html lang="en">
10+
<head>
11+
<meta charset="utf-8">
12+
<meta name="viewport" content="width=device-width, initial-scale=1">
13+
<title>mdapi.ch — a self-hosted Kubernetes homelab</title>
14+
<meta name="description" content="mdapi.ch is a self-hosted, GitOps-driven Kubernetes homelab. Public showcase of the platform, the sites it hosts, and the services members can request.">
15+
<style>
16+
:root{
17+
--bg:#0f1115; --panel:#171a21; --line:#262b36;
18+
--ink:#EAEEF7; --muted:#9aa6bd; --accent:#7FA5F9; --accent-2:#a7f3d0;
19+
}
20+
*{box-sizing:border-box}
21+
html,body{margin:0;padding:0}
22+
body{background:var(--bg);color:var(--ink);
23+
font:16px/1.6 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
24+
-webkit-font-smoothing:antialiased}
25+
a{color:var(--accent);text-decoration:none}
26+
a:hover{text-decoration:underline}
27+
.wrap{max-width:980px;margin:0 auto;padding:0 20px}
28+
header.hero{padding:72px 0 40px;border-bottom:1px solid var(--line)}
29+
.badge{display:inline-block;font-size:13px;color:var(--accent-2);
30+
border:1px solid var(--line);border-radius:999px;padding:4px 12px;margin-bottom:18px}
31+
h1{font-size:clamp(28px,5vw,44px);line-height:1.15;margin:.1em 0 .3em;letter-spacing:-.02em}
32+
h1 .dot{color:var(--accent)}
33+
.lede{font-size:clamp(16px,2.4vw,19px);color:var(--muted);max-width:64ch;margin:0 0 28px}
34+
.cta{display:flex;gap:12px;flex-wrap:wrap}
35+
.btn{display:inline-block;padding:11px 18px;border-radius:10px;font-weight:600}
36+
.btn.primary{background:var(--accent);color:#0b1020}
37+
.btn.ghost{border:1px solid var(--line);color:var(--ink)}
38+
.btn:hover{text-decoration:none;filter:brightness(1.08)}
39+
section{padding:44px 0;border-bottom:1px solid var(--line)}
40+
h2{font-size:22px;margin:0 0 6px;letter-spacing:-.01em}
41+
.sub{color:var(--muted);margin:0 0 22px;max-width:62ch}
42+
.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
43+
.card{display:block;background:var(--panel);border:1px solid var(--line);
44+
border-radius:12px;padding:16px 16px 14px;transition:border-color .15s,transform .15s}
45+
.card:hover{border-color:var(--accent);transform:translateY(-2px);text-decoration:none}
46+
.card .t{font-weight:650;color:var(--ink);margin:0 0 2px}
47+
.card .d{font-size:13.5px;color:var(--muted);margin:0}
48+
.card .host{font-size:12px;color:var(--accent);margin-top:8px;display:block}
49+
.members{background:linear-gradient(180deg,rgba(127,165,249,.08),transparent)}
50+
.pill{font-size:12px;color:var(--muted);border:1px solid var(--line);border-radius:999px;
51+
padding:3px 10px;margin:0 6px 8px 0;display:inline-block}
52+
footer{padding:34px 0 60px;color:var(--muted);font-size:13.5px}
53+
footer a{color:var(--muted);text-decoration:underline}
54+
</style>
55+
</head>
56+
<body>
57+
<header class="hero"><div class="wrap">
58+
<span class="badge">self-hosted &middot; GitOps &middot; Kubernetes</span>
59+
<h1>mdapi<span class="dot">.</span>ch</h1>
60+
<p class="lede">A self-hosted homelab running a production-grade Kubernetes stack at home —
61+
identity, mail, storage, automation, home-automation and the websites it hosts, all
62+
declared in public GitOps. This is the front door; the deep dive lives in the docs.</p>
63+
<div class="cta">
64+
<a class="btn primary" href="https://docs.mdapi.ch">Explore the docs &rarr;</a>
65+
<a class="btn ghost" href="https://github.com/MDAPI-Repos">View the GitOps on GitHub</a>
66+
</div>
67+
</div></header>
68+
69+
<section><div class="wrap">
70+
<h2>Explore the platform</h2>
71+
<p class="sub">Everything below is documented, end to end — architecture, security model,
72+
storage, networking and operations. Start here.</p>
73+
<div class="grid">
74+
<a class="card" href="https://docs.mdapi.ch">
75+
<p class="t">Documentation</p><p class="d">Architecture, security, storage, networking, operations — the full write-up.</p>
76+
<span class="host">docs.mdapi.ch</span></a>
77+
<a class="card" href="https://gitlab.mdapi.ch">
78+
<p class="t">GitLab</p><p class="d">Self-hosted GitLab EE: the source, registry and CI behind the platform.</p>
79+
<span class="host">gitlab.mdapi.ch</span></a>
80+
<a class="card" href="https://webmail.mdapi.ch">
81+
<p class="t">Webmail</p><p class="d">Roundcube webmail for accounts hosted on the platform's mail server.</p>
82+
<span class="host">webmail.mdapi.ch</span></a>
83+
</div>
84+
</div></section>
85+
86+
<section><div class="wrap">
87+
<h2>Sites hosted here</h2>
88+
<p class="sub">Public websites served from the cluster — each its own GitOps-managed deployment.</p>
89+
<div class="grid">
90+
<a class="card" href="https://tillo.ch"><p class="t">Tillo</p><p class="d">Personal site.</p><span class="host">tillo.ch</span></a>
91+
<a class="card" href="https://dellambrogio.ch"><p class="t">Dell'Ambrogio</p><p class="d">Family / professional site.</p><span class="host">dellambrogio.ch</span></a>
92+
<a class="card" href="https://ivodellambrogio.ch"><p class="t">Ivo Dell'Ambrogio</p><p class="d">Personal portfolio.</p><span class="host">ivodellambrogio.ch</span></a>
93+
<a class="card" href="https://coiffuredreams.ch"><p class="t">Coiffure Dreams</p><p class="d">Salon site.</p><span class="host">coiffuredreams.ch</span></a>
94+
<a class="card" href="https://spider3.ch"><p class="t">Spider3</p><p class="d">Project site.</p><span class="host">spider3.ch</span></a>
95+
</div>
96+
</div></section>
97+
98+
<section class="members"><div class="wrap">
99+
<h2>For members</h2>
100+
<p class="sub">Family &amp; friends get a personalised portal — your apps, plus a marketplace
101+
where you can request your own services. Sign in to see what's yours and what you can ask for.</p>
102+
<div style="margin-bottom:18px">
103+
<span class="pill">📬 a personal @name mailbox</span>
104+
<span class="pill">🌐 your own website</span>
105+
<span class="pill">📁 file / SFTP space</span>
106+
<span class="pill">🔗 a subdomain + certificate</span>
107+
</div>
108+
<a class="btn primary" href="https://start.mdapi.ch">Sign in to your portal &rarr;</a>
109+
</div></section>
110+
111+
<footer><div class="wrap">
112+
Built on RKE2 · Rancher · Fleet · Longhorn · Ceph · cert-manager.
113+
GitOps is public at <a href="https://github.com/MDAPI-Repos">github.com/MDAPI-Repos</a>.
114+
Neighbours of the En&nbsp;Vuassu association: <a href="https://www.envuassu.ch">envuassu.ch</a>.
115+
</div></footer>
116+
</body>
117+
</html>

showcase/showcase-deploy.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: showcase
5+
namespace: showcase
6+
annotations:
7+
reloader.stakater.com/auto: "true"
8+
spec:
9+
selector:
10+
matchLabels:
11+
app: showcase
12+
replicas: 2
13+
strategy:
14+
type: RollingUpdate
15+
rollingUpdate:
16+
maxUnavailable: 0
17+
maxSurge: 1
18+
template:
19+
metadata:
20+
labels:
21+
app: showcase
22+
spec:
23+
affinity:
24+
podAntiAffinity:
25+
preferredDuringSchedulingIgnoredDuringExecution:
26+
- weight: 100
27+
podAffinityTerm:
28+
labelSelector:
29+
matchLabels:
30+
app: showcase
31+
topologyKey: kubernetes.io/hostname
32+
containers:
33+
- name: nginx
34+
image: nginxinc/nginx-unprivileged:1.27-alpine
35+
imagePullPolicy: IfNotPresent
36+
ports:
37+
- containerPort: 8080
38+
resources:
39+
requests:
40+
cpu: 10m
41+
memory: 24Mi
42+
limits:
43+
memory: 64Mi
44+
volumeMounts:
45+
- name: html
46+
mountPath: /usr/share/nginx/html
47+
readOnly: true
48+
readinessProbe:
49+
httpGet:
50+
path: /
51+
port: 8080
52+
initialDelaySeconds: 3
53+
periodSeconds: 10
54+
volumes:
55+
- name: html
56+
configMap:
57+
name: showcase-html

showcase/showcase-ing.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
name: showcase
5+
namespace: showcase
6+
annotations:
7+
cert-manager.io/issuer: letsencrypt-prod
8+
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
9+
nginx.ingress.kubernetes.io/modsecurity-transaction-id: "$request_id"
10+
nginx.ingress.kubernetes.io/modsecurity-snippet: |
11+
SecRuleEngine On
12+
SecAuditEngine RelevantOnly
13+
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
14+
SecAuditLogParts ABIJDEFHZ
15+
SecAuditLogType Serial
16+
SecAuditLogFormat JSON
17+
SecAuditLog /dev/stdout
18+
SecDebugLog /tmp/modsec_debug.log
19+
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
20+
SecRuleRemoveById 911100
21+
spec:
22+
ingressClassName: nginx
23+
rules:
24+
- host: www.mdapi.ch
25+
http:
26+
paths:
27+
- backend:
28+
service:
29+
name: showcase
30+
port:
31+
number: 80
32+
path: /
33+
pathType: Prefix
34+
tls:
35+
- hosts:
36+
- www.mdapi.ch
37+
secretName: showcase-cert

showcase/showcase-svc.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: showcase
5+
namespace: showcase
6+
spec:
7+
selector:
8+
app: showcase
9+
ports:
10+
- name: http
11+
port: 80
12+
protocol: TCP
13+
targetPort: 8080
14+
type: ClusterIP

showcase/tsig-es.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: external-secrets.io/v1
2+
kind: ExternalSecret
3+
metadata:
4+
name: tsig-secret
5+
spec:
6+
refreshInterval: 1m0s
7+
8+
secretStoreRef:
9+
kind: ClusterSecretStore
10+
name: cm-akeyless
11+
12+
target:
13+
name: tsig-secret
14+
creationPolicy: Owner
15+
16+
data:
17+
- remoteRef:
18+
key: /mdapi/showcase/tsig-secret/tsig-secret-key
19+
secretKey: tsig-secret-key

0 commit comments

Comments
 (0)