Deploy Homepage (gethomepage) as the apex landing dashboard aggregating links to cluster services, in its OWN non-gating post-platform Flux Kustomization (clusters/base/homepage.yaml, dependsOn platform-config, wait:false) — mirrors headlamp/oauth2-proxy so a leaf UI can never gate the platform chain. - platform/homepage/: plain manifests (ns, config ConfigMap, Deployment, Service, default-deny+Cilium ingress NP, apex HTTPRoute). Non-root pod, HOMEPAGE_ALLOWED_HOSTS, bundled mdi-* icons (DNS-only egress). - gateway.yaml: add https-apex + http-apex-redirect listeners for the bare apex (the *.home wildcard listener does NOT match it). Apex is already a SAN on the wildcard cert — no new Certificate. - Forward-auth annotated (auth-mode=forward-auth) but NOT enforced: the Gateway ext-authz mechanism is an unbuilt Phase-6 WS-NET TODO (same status as all other forward-auth apps). Flagged as PM follow-up. Gate: lint/l0/render-golden(10/10)/l1 green; conformance skipped (no cluster). Co-Authored-By: Claude Opus 4.8 <[email protected]>
102 lines
4.4 KiB
YAML
102 lines
4.4 KiB
YAML
---
|
|
# Homepage (gethomepage) — the apex landing dashboard at https://home.keithjackson.dev. Plain
|
|
# manifests (not a HelmRelease): the whole app is a single stateless Deployment + a config ConfigMap,
|
|
# and hand-authoring the ConfigMap gives exact control over the service links without guessing a
|
|
# chart's values schema (same plain-manifest style as apps/vms/unifi). Rendered by its OWN non-gating
|
|
# post-platform Flux Kustomization (clusters/base/homepage.yaml, dependsOn platform-config,
|
|
# wait:false) — a leaf UI must never sit in the gating `platform` tier (DECISIONS-LOG: "Headlamp
|
|
# gating stall").
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: homepage
|
|
namespace: homepage
|
|
labels:
|
|
app.kubernetes.io/name: homepage
|
|
spec:
|
|
replicas: 1
|
|
revisionHistoryLimit: 3
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: homepage
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: homepage
|
|
annotations:
|
|
# Re-roll the pod when the config changes (ConfigMap subPath mounts do NOT hot-reload).
|
|
checksum/config: config-v1
|
|
spec:
|
|
automountServiceAccountToken: false
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
fsGroup: 1000
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
containers:
|
|
- name: homepage
|
|
# Renovate maintains this pin (ghcr.io/gethomepage/homepage). Config lives in /app/config.
|
|
image: ghcr.io/gethomepage/homepage:v2.1.2
|
|
imagePullPolicy: IfNotPresent
|
|
ports:
|
|
- name: http
|
|
containerPort: 3000
|
|
protocol: TCP
|
|
env:
|
|
# Referenced by HOMEPAGE_ALLOWED_HOSTS below so the in-pod liveness/readiness probes
|
|
# (which hit the pod IP, not the public host) are accepted by Homepage's host guard.
|
|
- name: MY_POD_IP
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: status.podIP
|
|
# REQUIRED by Homepage >= 1.0 — requests whose Host header is not in this list get 403.
|
|
# The public apex host (served via the Gateway) + the pod IP (for kubelet probes).
|
|
- name: HOMEPAGE_ALLOWED_HOSTS
|
|
value: "home.keithjackson.dev,$(MY_POD_IP):3000"
|
|
resources:
|
|
requests:
|
|
cpu: 25m
|
|
memory: 128Mi
|
|
limits:
|
|
memory: 256Mi
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
# NOTE: readOnlyRootFilesystem intentionally left unset — Homepage (Next.js) writes a
|
|
# runtime build/cache under /app/.next at boot. Same deferred-hardening note as headlamp.
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /api/healthcheck
|
|
port: http
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 15
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /api/healthcheck
|
|
port: http
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 15
|
|
volumeMounts:
|
|
# Homepage expects each config file present; mount them read-only by subPath so the rest
|
|
# of /app/config stays writable (it writes ./logs there at runtime).
|
|
- { name: homepage-config, mountPath: /app/config/settings.yaml, subPath: settings.yaml, readOnly: true }
|
|
- { name: homepage-config, mountPath: /app/config/services.yaml, subPath: services.yaml, readOnly: true }
|
|
- { name: homepage-config, mountPath: /app/config/bookmarks.yaml, subPath: bookmarks.yaml, readOnly: true }
|
|
- { name: homepage-config, mountPath: /app/config/widgets.yaml, subPath: widgets.yaml, readOnly: true }
|
|
- { name: homepage-config, mountPath: /app/config/docker.yaml, subPath: docker.yaml, readOnly: true }
|
|
- { name: homepage-config, mountPath: /app/config/kubernetes.yaml, subPath: kubernetes.yaml, readOnly: true }
|
|
- { name: homepage-config, mountPath: /app/config/custom.css, subPath: custom.css, readOnly: true }
|
|
- { name: homepage-config, mountPath: /app/config/custom.js, subPath: custom.js, readOnly: true }
|
|
# Writable scratch for Homepage's runtime logs (it writes /app/config/logs).
|
|
- { name: logs, mountPath: /app/config/logs }
|
|
volumes:
|
|
- name: homepage-config
|
|
configMap:
|
|
name: homepage
|
|
- name: logs
|
|
emptyDir: {}
|