# FileBrowser + Webpage deployment for felhom.eu
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: filebrowser-files
  namespace: felhom-system
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: longhorn
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: filebrowser-db
  namespace: felhom-system
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 100Mi
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebrowser-config
  namespace: felhom-system
data:
  .filebrowser.json: |
    {
      "port": 8080,
      "baseURL": "",
      "address": "0.0.0.0",
      "log": "stdout",
      "database": "/database/filebrowser.db",
      "root": "/srv"
    }
---
# ===================
# NGINX CONFIG FOR CLEAN URLs
# ===================
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
  namespace: felhom-system
data:
  default.conf: |
    server {
        listen 80;
        server_name _;
        root /usr/share/nginx/html/current/website;
        index index.html;

        # Enable clean URLs - serve .html files without extension
        location / {
            try_files $uri $uri.html $uri/ =404;
        }

        location = /sitemap.xml {
            types { application/xml xml; }
            default_type application/xml;
        }

        # Host-install script. It lives at the repo's /scripts (outside the website doc-root),
        # synced into .../current/scripts by git-sync (see the sparse-checkout ConfigMap). Served
        # as text/plain so operators can inspect it in a browser before download-then-run.
        # R-110: served from the INSTALLER TAG's tree, not the website's. The URL is unchanged
        # (https://felhom.eu/scripts/felhom-host-install.sh) — it never carried a ref, so every
        # producer of it (the bootstrap script, the hub's day-0 command) follows the tag with no
        # edit. What changed is which tree this root points at.
        location /scripts/ {
            root /usr/share/nginx/scripts/current;
            default_type text/plain;
        }

        # Cache static assets
        location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
            expires 7d;
            add_header Cache-Control "public, immutable";
        }

        # Security headers
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;

        # Error pages
        error_page 404 /404.html;
        error_page 500 502 503 504 /50x.html;
    }
---
# ===================
# FILEBROWSER
# ===================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: filebrowser
  namespace: felhom-system
  labels:
    app: filebrowser
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: filebrowser
  template:
    metadata:
      labels:
        app: filebrowser
    spec:
      # filebrowser v2.63.13 (debian default) runs as a non-root UID by default
      # and can't write to PVC files left by the previous v2-alpine image (which
      # ran as root). Force root explicitly so the existing PVC contents are
      # readable + writable. (The alternative -- chown the PVC then drop perms --
      # needs a one-shot initContainer; not worth the moving parts here.)
      securityContext:
        runAsUser: 0
        runAsGroup: 0
      containers:
        - name: filebrowser
          image: filebrowser/filebrowser:v2.63.13
          # v2.63.x default config path is `/config/settings.json`; our ConfigMap
          # is mounted at `/.filebrowser.json`. Tell filebrowser to read it
          # explicitly so it picks up port 8080 (else it falls back to port 80
          # and the readiness probe on 8080 fails).
          args: ["-c", "/.filebrowser.json"]
          ports:
            - containerPort: 8080
          volumeMounts:
            - name: files
              mountPath: /srv
            - name: database
              mountPath: /database
            - name: config
              mountPath: /.filebrowser.json
              subPath: .filebrowser.json
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
      volumes:
        - name: files
          persistentVolumeClaim:
            claimName: filebrowser-files
        - name: database
          persistentVolumeClaim:
            claimName: filebrowser-db
        - name: config
          configMap:
            name: filebrowser-config
---
apiVersion: v1
kind: Service
metadata:
  name: filebrowser
  namespace: felhom-system
spec:
  selector:
    app: filebrowser
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: filebrowser
  namespace: felhom-system
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
spec:
  ingressClassName: nginx-internal
  tls:
    - hosts:
        - files.felhom.eu
      secretName: filebrowser-tls
  rules:
    - host: files.felhom.eu
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: filebrowser
                port:
                  number: 80
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: git-sync-sparse-checkout
  namespace: felhom-system
data:
  # R-110: TWO sparse-checkouts, because there are now two syncs with two different refs.
  # The website tracks `main` (a copy edit must never need a release); /scripts/ tracks the
  # installer TAG (pushing the installer must never publish it).
  sparse-checkout: |
    /website/
  sparse-checkout-scripts: |
    /scripts/
---
# ===================
# WEBPAGE (nginx)
# ===================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: felhom-webpage
  namespace: felhom-system
  labels:
    app: felhom-webpage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: felhom-webpage
  template:
    metadata:
      labels:
        app: felhom-webpage
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80
          volumeMounts:
            - name: git-data
              mountPath: /usr/share/nginx/html
              readOnly: true
            - name: git-data-scripts
              mountPath: /usr/share/nginx/scripts
              readOnly: true
            - name: nginx-config
              mountPath: /etc/nginx/conf.d/default.conf
              subPath: default.conf
          resources:
            requests:
              memory: "32Mi"
              cpu: "10m"
            limits:
              memory: "128Mi"
              cpu: "200m"
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 3
            periodSeconds: 10

        # ── The WEBSITE sync — tracks `main`, unchanged cadence ──────────────────────────────
        # Deliberately still a branch: the site is content, and a typo fix must reach felhom.eu in
        # thirty seconds without cutting a release. Only /scripts/ moved to a tag (R-110).
        - name: git-sync
          image: registry.k8s.io/git-sync/git-sync:v4.4.0
          args:
            - --repo=https://gitea.dooplex.hu/admin/felhom.eu.git
            - --ref=main
            - --root=/git
            - --link=current
            - --period=30s
            # Only sync the website subdirectory
            - --sparse-checkout-file=/etc/git-sync/sparse-checkout
          volumeMounts:
            - name: git-data
              mountPath: /git
            - name: sparse-checkout
              mountPath: /etc/git-sync
          resources:
            requests:
              memory: "32Mi"
              cpu: "10m"
            limits:
              memory: "128Mi"
              cpu: "100m"
          securityContext:
            runAsUser: 65534  # nobody

        # ── The INSTALLER sync — tracks a TAG (R-110, operator ruling 2026-08-03) ─────────────
        # felhom-host-install.sh runs as root on a virgin machine. Before this it was served
        # straight from `main`, so pushing it WAS publishing it: within thirty seconds it was what
        # every new machine downloaded and ran, with no staging and no rollback but another push.
        #
        # Publishing is now moving this tag; rolling back is moving it back. PROVEN, not assumed:
        # git-sync v4.4.0 follows a tag AND notices a moved one — measured 2026-08-03 on a
        # throwaway sync against this very repo (`update required … local:<old> remote:<new>` →
        # `updated successfully`, one period, ~20 s).
        #
        # Bump this ref when the installer's published version changes. `hostinstall_gates.py`
        # gate 6 fails if this sync stops naming an `installer-v…` tag.
        - name: git-sync-scripts
          image: registry.k8s.io/git-sync/git-sync:v4.4.0
          args:
            - --repo=https://gitea.dooplex.hu/admin/felhom.eu.git
            - --ref=installer-v1.24.0
            - --root=/git-scripts
            - --link=current
            - --period=30s
            - --sparse-checkout-file=/etc/git-sync-scripts/sparse-checkout
          volumeMounts:
            - name: git-data-scripts
              mountPath: /git-scripts
            - name: sparse-checkout-scripts
              mountPath: /etc/git-sync-scripts
          resources:
            requests:
              memory: "32Mi"
              cpu: "10m"
            limits:
              memory: "128Mi"
              cpu: "100m"
          securityContext:
            runAsUser: 65534  # nobody

      # Init container: wait for first sync before nginx starts
      initContainers:
        # BOTH trees are seeded before nginx accepts traffic. The second one is why /scripts/ has
        # no 404 window across this change: a fresh pod does not become ready until the installer
        # tag has been checked out, exactly as the website already worked.
        - name: git-sync-init
          image: registry.k8s.io/git-sync/git-sync:v4.4.0
          args:
            - --repo=https://gitea.dooplex.hu/admin/felhom.eu.git
            - --ref=main
            - --root=/git
            - --link=current
            - --one-time
            - --sparse-checkout-file=/etc/git-sync/sparse-checkout
          volumeMounts:
            - name: git-data
              mountPath: /git
            - name: sparse-checkout
              mountPath: /etc/git-sync
          securityContext:
            runAsUser: 65534
        - name: git-sync-scripts-init
          image: registry.k8s.io/git-sync/git-sync:v4.4.0
          args:
            - --repo=https://gitea.dooplex.hu/admin/felhom.eu.git
            - --ref=installer-v1.24.0
            - --root=/git-scripts
            - --link=current
            - --one-time
            - --sparse-checkout-file=/etc/git-sync-scripts/sparse-checkout
          volumeMounts:
            - name: git-data-scripts
              mountPath: /git-scripts
            - name: sparse-checkout-scripts
              mountPath: /etc/git-sync-scripts
          securityContext:
            runAsUser: 65534

      volumes:
        - name: git-data
          emptyDir: {}
        - name: git-data-scripts
          emptyDir: {}
        - name: nginx-config
          configMap:
            name: nginx-config
        - name: sparse-checkout
          configMap:
            name: git-sync-sparse-checkout
            items:
              - key: sparse-checkout
                path: sparse-checkout
        - name: sparse-checkout-scripts
          configMap:
            name: git-sync-sparse-checkout
            items:
              - key: sparse-checkout-scripts
                path: sparse-checkout
---
apiVersion: v1
kind: Service
metadata:
  name: felhom-webpage
  namespace: felhom-system
spec:
  selector:
    app: felhom-webpage
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: felhom-webpage
  namespace: felhom-system
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx-internal
  tls:
    - hosts:
        - felhom.eu
        - www.felhom.eu
      secretName: felhom-webpage-tls
  rules:
    - host: felhom.eu
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: felhom-webpage
                port:
                  number: 80
    - host: www.felhom.eu
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: felhom-webpage
                port:
                  number: 80