diff --git a/apps/gitea.yaml b/apps/gitea.yaml new file mode 100644 index 0000000..714c1ed --- /dev/null +++ b/apps/gitea.yaml @@ -0,0 +1,194 @@ +--- +# Namespace (trust zone; default-deny + policies apply) +apiVersion: v1 +kind: Namespace +metadata: + name: gitea + labels: + homelab.keithjackson.dev/app-namespace: 'true' + gateway-access: shared + annotations: + homelab.keithjackson.dev/backup-policy: daily-offcluster +--- +# Default-deny baseline (R-NET-01): deny all, allow only DNS +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny + namespace: gitea + labels: + homelab.keithjackson.dev/baseline: default-deny +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: [] + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 +--- +# PVC gitea-data (R-STATE) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: gitea-data + namespace: gitea + labels: + app.kubernetes.io/name: gitea +spec: + accessModes: + - ReadWriteOnce + storageClassName: longhorn + resources: + requests: + storage: 10Gi +--- +# Deployment (R-PLAT-02) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gitea + namespace: gitea + labels: + app.kubernetes.io/name: gitea +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: gitea + template: + metadata: + labels: + app.kubernetes.io/name: gitea + homelab.keithjackson.dev/egress-class: internet + homelab.keithjackson.dev/auth-mode: oidc + annotations: + homelab.keithjackson.dev/auth-mode: oidc + homelab.keithjackson.dev/auth-groups: platform-admins + spec: + containers: + - name: gitea + image: gitea/gitea:1.24.3 + ports: + - containerPort: 3000 + volumeMounts: + - name: data + mountPath: /data/data + volumes: + - name: data + persistentVolumeClaim: + claimName: gitea-data +--- +# Service +apiVersion: v1 +kind: Service +metadata: + name: gitea + namespace: gitea + labels: + app.kubernetes.io/name: gitea +spec: + selector: + app.kubernetes.io/name: gitea + ports: + - name: port-3000 + port: 3000 + targetPort: 3000 +--- +# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): gitea-allow-gateway-ingress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: gitea-allow-gateway-ingress + namespace: gitea + labels: + app.kubernetes.io/name: gitea +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: gitea + ingress: + - fromEntities: + - ingress + toPorts: + - ports: + - port: '3000' + protocol: TCP +--- +# HTTPRoute gitea-3000 (R-TLS/R-DNS) +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: gitea-3000 + namespace: gitea + labels: + app.kubernetes.io/name: gitea + annotations: + homelab.keithjackson.dev/auth-mode: oidc + homelab.keithjackson.dev/auth-groups: platform-admins +spec: + parentRefs: + - name: shared + namespace: gateway + sectionName: https + hostnames: + - gitea.home.keithjackson.dev + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: gitea + port: 3000 +--- +# ServiceMonitor (R-OBS-01) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: gitea + namespace: gitea + labels: + release: kube-prometheus-stack + app.kubernetes.io/name: gitea +spec: + selector: + matchLabels: + app.kubernetes.io/name: gitea + namespaceSelector: + matchNames: + - gitea + endpoints: + - port: metrics + interval: 30s +--- +# PrometheusRule SLI alerts (R-OBS-01) +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: gitea-slis + namespace: gitea + labels: + release: kube-prometheus-stack + app.kubernetes.io/name: gitea +spec: + groups: + - name: gitea.slis + rules: + - alert: gitea-down + expr: up{job="gitea"} == 0 + for: 5m + labels: + severity: critical + service: gitea + annotations: + summary: gitea is down + description: 'gitea has been unreachable for 5m (SLI: up).' diff --git a/apps/kustomization.yaml b/apps/kustomization.yaml index d370026..e991ff0 100644 --- a/apps/kustomization.yaml +++ b/apps/kustomization.yaml @@ -7,3 +7,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - pihole.yaml + - gitea.yaml diff --git a/catalog/gitea.yaml b/catalog/gitea.yaml new file mode 100644 index 0000000..ddb9fea --- /dev/null +++ b/catalog/gitea.yaml @@ -0,0 +1,42 @@ +# ServiceDefinition: gitea +# Source of truth: spec docs/13-service-catalog.md row 12; docs/15-identity-and-access.md; docs/05. +# public git hosting | egress internet | repos+DB on Longhorn (irreplaceable, DR-critical) | auth oidc. +# Bootstrapping-critical: self-hosts the platform's GitOps source + CI/CD, onboarded EARLY (docs/13 +# "Gitea is bootstrapping-critical", WS-APPS step 2, right after PiHole). A cluster loss would take its +# own git host with it, so repos/DB are backed up off-cluster + mirrored (R-STATE-03). +# Single-container Gitea with the built-in SQLite datastore is sufficient at home scale; the repos and +# DB live on one Longhorn PVC (/data), so no external PostgreSQL is required for this onboarding. +# Validates against schemas/servicedefinition.schema.json. +name: gitea +kind: container +source: + image: gitea/gitea:1.24.3 # pinned recent stable tag (R-UPD-01); update.range governs advancement +exposure: public # edge-forwarded git host at gitea.home.keithjackson.dev (docs/09; R-DNS/R-NET) +egress: internet # normal WAN egress (docs/03; R-NET-05) +storage: + - name: data + size: 10Gi + class: longhorn # repos + SQLite DB on 3-replica Longhorn; irreplaceable (docs/04; R-STATE-02) +dependencies: [] # default-deny: reaches only DNS; git clients reach it north-south via the Gateway (R-NET-01/02) +ingressRoutes: + - host: gitea.home.keithjackson.dev + port: 3000 # Gitea HTTP web/API; TLS terminates at the shared Gateway (docs/08, docs/09) +auth: + mode: oidc # native Keycloak OIDC client (docs/13 SSO row, docs/15 "Native OIDC" set; R-IDP-02) + groups: + - platform-admins # access scoped to an authorized group (R-IDP-06) + # NOTE (OIDC follow-up + break-glass): Gitea keeps a LOCAL admin as the break-glass path so an IdP + # outage never locks the operator out of recovery tools (docs/15 "Availability caveat"). At initial + # deploy Gitea comes up on that local admin; the Keycloak OIDC client (+ SOPS client secret) is wired + # once Keycloak is live. This declaration records the target integration; it does not embed a secret. +update: + channel: stable # R-UPD-01 + range: ">=1.22.0 <2.0.0" # stay on the 1.x stable line + cadence: "0 4 * * *" + autoRollbackOnFailedHealth: true # R-UPD-03 +observability: + metrics: true + slis: + - up # git host down is a critical alert (docs/10; R-OBS-01) +backup: + policy: daily-offcluster # DR-critical: repos/DB backed up off-cluster + mirrored (docs/13, docs/04; R-STATE-03)