Files
felhom.eu/manifests/webpage.yaml
T
admin bee6848458
gates / gates (push) Successful in 8s
installer v1.23.0 — publishing becomes an act, not a side-effect (R-110, R-183)
Two channels moved off main in the same change, because either one left behind
makes the other cosmetic.

Channel 1 — the served script. webpage.yaml git-synced /scripts/ from
--branch=main every 30s and nginx served that tree, so pushing this file WAS
publishing it: within half a minute it was what every new machine downloaded and
ran as root, with no staging and no rollback but another push. The sync is now
SPLIT: the website keeps tracking main at the same cadence (a copy edit must
never need a release) and /scripts/ tracks the tag installer-v<SCRIPT_VERSION>.

PROVEN before the manifest was touched: git-sync v4.4.0 follows a tag AND
notices a MOVED one — measured on a throwaway sync against this repo,
"update required ... local:<old> remote:<new>" -> "updated successfully",
within one period. The moved-tag half is what the publish model rests on.

Channel 2 — the sixteen files fetched at run time. fetch_raw pulled from
$AGENT_REPO/raw/branch/main; it now pulls raw/tag/v$ART_AGENT_VER. That is a
correctness fix, not only a channel one (R-183): a fresh install fetched the
vouched agent BINARY while taking its unit file, sudoers and guarded wrappers
from whatever main held. Two refs, one install, nothing compared them. Their
correct ref was never SCRIPT_VERSION — they do not live in this repo.

No fallback to a branch: a vouched version whose tag is missing fails loudly
rather than quietly serving main.

Channel 3 — the URL — needed no change, recorded rather than left silent:
https://felhom.eu/scripts/felhom-host-install.sh never carried a ref, so both
producers follow the tag with no edit. No hub change, no hub version bump.

Gate 6 in hostinstall_gates.py pins all three structurally with no network, so
it stays in --fast and runs in CI. It deliberately does NOT assert "a tag exists
for the current SCRIPT_VERSION": that would go red on the very push that bumps
the version, before publishing — and publishing being separate is the ruling.
2026-08-03 12:08:37 +02:00

452 lines
13 KiB
YAML

# 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.23.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.23.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