summaryrefslogtreecommitdiff
path: root/f3s
diff options
context:
space:
mode:
Diffstat (limited to 'f3s')
-rw-r--r--f3s/argocd-apps/services/shuriken.yaml28
-rw-r--r--f3s/shuriken/Justfile44
-rw-r--r--f3s/shuriken/README.md78
-rw-r--r--f3s/shuriken/docker-image/Justfile17
-rw-r--r--f3s/shuriken/helm-chart/Chart.yaml5
-rw-r--r--f3s/shuriken/helm-chart/templates/configmap.yaml50
-rw-r--r--f3s/shuriken/helm-chart/templates/cronjob.yaml91
-rw-r--r--f3s/shuriken/helm-chart/templates/persistent-volumes.yaml38
8 files changed, 351 insertions, 0 deletions
diff --git a/f3s/argocd-apps/services/shuriken.yaml b/f3s/argocd-apps/services/shuriken.yaml
new file mode 100644
index 0000000..35a3d89
--- /dev/null
+++ b/f3s/argocd-apps/services/shuriken.yaml
@@ -0,0 +1,28 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+ name: shuriken
+ namespace: cicd
+ finalizers:
+ - resources-finalizer.argocd.argoproj.io
+spec:
+ project: default
+ source:
+ repoURL: http://git-server.cicd.svc.cluster.local/conf.git
+ targetRevision: master
+ path: f3s/shuriken/helm-chart
+ destination:
+ server: https://kubernetes.default.svc
+ namespace: services
+ syncPolicy:
+ automated:
+ prune: true
+ selfHeal: true
+ syncOptions:
+ - CreateNamespace=false
+ retry:
+ limit: 3
+ backoff:
+ duration: 5s
+ factor: 2
+ maxDuration: 1m \ No newline at end of file
diff --git a/f3s/shuriken/Justfile b/f3s/shuriken/Justfile
new file mode 100644
index 0000000..87534e9
--- /dev/null
+++ b/f3s/shuriken/Justfile
@@ -0,0 +1,44 @@
+NAMESPACE := "services"
+APP_NAME := "shuriken"
+
+build:
+ just -f docker-image/Justfile build
+
+push:
+ just -f docker-image/Justfile push
+
+build-push: build push
+
+status:
+ @echo "=== CronJob ==="
+ @kubectl get cronjob -n {{NAMESPACE}} shuriken
+ @echo ""
+ @echo "=== Recent Jobs ==="
+ @kubectl get jobs -n {{NAMESPACE}} -l job-name | head -5 2>/dev/null || \
+ kubectl get jobs -n {{NAMESPACE}} | grep shuriken | head -5
+ @echo ""
+ @echo "=== PVC ==="
+ @kubectl get pvc -n {{NAMESPACE}} shuriken-data-pvc
+ @echo ""
+ @echo "=== ArgoCD Status ==="
+ @kubectl get application {{APP_NAME}} -n cicd -o jsonpath='Sync: {.status.sync.status}, Health: {.status.health.status}' 2>/dev/null && echo "" || echo "Not found"
+
+logs lines="100":
+ kubectl logs -n {{NAMESPACE}} -l job-name=shuriken --tail={{lines}} -f 2>/dev/null || \
+ kubectl logs -n {{NAMESPACE}} -l cronjob=shuriken --tail={{lines}} -f 2>/dev/null || \
+ echo "No running shuriken job pods; trigger one with: kubectl create job --from=cronjob/shuriken -n services shuriken-manual"
+
+# Trigger an immediate manual run (outside the daily schedule) and follow logs.
+run:
+ kubectl create job --from=cronjob/shuriken -n {{NAMESPACE}} shuriken-manual
+ @sleep 3
+ just logs
+
+sync:
+ @echo "Triggering ArgoCD sync..."
+ @kubectl annotate application {{APP_NAME}} -n cicd argocd.argoproj.io/refresh=normal --overwrite
+ @sleep 2
+ @kubectl get application {{APP_NAME}} -n cicd -o jsonpath='Sync: {.status.sync.status}, Health: {.status.health.status}' && echo ""
+
+argocd-status:
+ argocd app get {{APP_NAME}} --core \ No newline at end of file
diff --git a/f3s/shuriken/README.md b/f3s/shuriken/README.md
new file mode 100644
index 0000000..2025023
--- /dev/null
+++ b/f3s/shuriken/README.md
@@ -0,0 +1,78 @@
+# shuriken on f3s
+
+Nightly Kubernetes CronJob that regenerates the `irregular.ninja` and
+`alt.irregular.ninja` static photo albums with [shuriken.sh][shuriken] and
+writes them to the shared NFS export.
+
+[shuriken]: https://github.com/snonux/shuriken.sh
+
+## What it does
+
+Once a day (04:00 Europe/Sofia) a CronJob pod runs the shuriken Docker image.
+The image entrypoint iterates over `/configs/*.conf` and runs
+`shuriken --generate --config <conf>` for each, single-threaded by default
+(`--image-jobs 1`, overridable via `SHURIKEN_IMAGE_JOBS`). Both albums are
+generated sequentially in one run.
+
+## Image workflow
+
+Build and push to the private NodePort registry (run `just build` in the
+shuriken.sh repo first so `bin/shuriken` is current):
+
+```bash
+cd /home/paul/git/conf/f3s/shuriken
+just build-push
+```
+
+The image is pushed as `r0.lan.buetow.org:30001/shuriken:0.13.2` and the
+CronJob pulls `registry.lan.buetow.org:30001/shuriken:0.13.2`. Bump `TAG` in
+`docker-image/Justfile` and `appVersion`/the CronJob `image:` tag together
+when releasing a new shuriken version.
+
+## Storage
+
+- **PV/PVC** `shuriken-data-pv` / `shuriken-data-pvc`: hostPath
+ `/data/nfs/k3svolumes` mounted at `/data` (RWX, shared NFS). The whole tree
+ is mounted because the incoming dirs under
+ `/data/shuriken.sh/incoming/<site>` are relative symlinks into
+ `../../syncthing/...` that only resolve against the full layout. The job
+ writes only under `/data/shuriken.sh/<site>/dist`.
+- **NFS sentinel**: `shuriken.sh/.nfs-sentinel` on the NFS server; the
+ initContainer refuses to start if it is missing (NFS down / stale local-XFS
+ shadow).
+
+## Per-site config
+
+`helm-chart/templates/configmap.yaml` holds a `shuriken.conf` per site,
+mounted at `/configs`. Each points `INCOMING_DIR` at the readlink-resolved
+syncthing source and `DIST_DIR` at `/data/shuriken.sh/<site>/dist` (a regular
+subdirectory under the mount, so shuriken's staging atomic-swap rename works).
+No `SYNC_*` settings -- the container only generates; publishing to
+fishfinger/blowfish stays a separate concern.
+
+## Layout on the NFS server (f0)
+
+```
+/data/nfs/k3svolumes/shuriken.sh/
+ .nfs-sentinel
+ incoming/
+ irregular.ninja -> ../../syncthing/.../irregular.ninja
+ alt.irregular.ninja -> ../../syncthing/.../alt.irregular.ninja
+ irregular.ninja/dist/ # generated
+ alt.irregular.ninja/dist/ # generated
+```
+
+## Operate
+
+```bash
+just status # CronJob + PVC + ArgoCD status
+just run # trigger a manual run and tail logs
+just sync # refresh the ArgoCD app
+just argocd-status # argocd CLI view
+```
+
+## ArgoCD
+
+`argocd-apps/services/shuriken.yaml` points ArgoCD at
+`f3s/shuriken/helm-chart` in this repo (auto-sync, prune, self-heal), same
+pattern as the other service apps. \ No newline at end of file
diff --git a/f3s/shuriken/docker-image/Justfile b/f3s/shuriken/docker-image/Justfile
new file mode 100644
index 0000000..bd394fa
--- /dev/null
+++ b/f3s/shuriken/docker-image/Justfile
@@ -0,0 +1,17 @@
+REGISTRY := "r0.lan.buetow.org:30001"
+IMAGE := "shuriken"
+TAG := "0.13.2"
+# Source tree for the image build. The Dockerfile lives at the repo root and
+# copies bin/, share/templates, assets/site, src/shuriken.default.conf and
+# docker/entrypoint.sh, so build from the shuriken.sh checkout root. Run
+# `just build` in the shuriken.sh repo first so bin/shuriken is up to date.
+SRC := "/home/paul/git/shuriken.sh"
+
+build:
+ docker build -t {{IMAGE}}:{{TAG}} {{SRC}}
+
+push:
+ docker tag {{IMAGE}}:{{TAG}} {{REGISTRY}}/{{IMAGE}}:{{TAG}}
+ docker push {{REGISTRY}}/{{IMAGE}}:{{TAG}}
+
+all: build push \ No newline at end of file
diff --git a/f3s/shuriken/helm-chart/Chart.yaml b/f3s/shuriken/helm-chart/Chart.yaml
new file mode 100644
index 0000000..72ba81d
--- /dev/null
+++ b/f3s/shuriken/helm-chart/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v2
+name: shuriken
+description: Nightly shuriken photo-album generation CronJob for irregular.ninja and alt.irregular.ninja.
+version: 0.1.0
+appVersion: "0.13.2" \ No newline at end of file
diff --git a/f3s/shuriken/helm-chart/templates/configmap.yaml b/f3s/shuriken/helm-chart/templates/configmap.yaml
new file mode 100644
index 0000000..489e53e
--- /dev/null
+++ b/f3s/shuriken/helm-chart/templates/configmap.yaml
@@ -0,0 +1,50 @@
+# Per-site shuriken.conf files, mounted read-only at /configs inside the
+# CronJob container. The image entrypoint runs `shuriken --generate --config
+# <conf>` once per *.conf here (alphabetical), so both albums are regenerated
+# sequentially in a single daily run.
+#
+# Notes:
+# - INCOMING_DIR uses `readlink -f` on the symlink under /data/shuriken.sh/
+# incoming/ so shuriken's `find` descends into the real syncthing tree
+# (find does not follow a symlinked start directory). The resolved path
+# contains spaces; shuriken handles that (see the existing alt conf).
+# - DIST_DIR is a regular subdirectory under the NFS mount (NOT a mount
+# point), so shuriken's staging atomic-swap rename works.
+# - IMAGE_JOBS is set to 1 for documentation, but the container entrypoint
+# also passes --image-jobs 1 (overridable via SHURIKEN_IMAGE_JOBS), so a
+# single ImageMagick process runs at a time -- the N100 hosts are
+# passively cooled and this batch job has no latency budget.
+# - No SYNC_* settings: the container only generates the static site into
+# dist/. Publishing to fishfinger/blowfish stays a separate concern.
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: shuriken-config
+ namespace: services
+data:
+ irregular-ninja.conf: |
+ TITLE='Irregular Ninja'
+ THUMBHEIGHT=400
+ HEIGHT=1800
+ MAXPREVIEWS=40
+ IMAGE_JOBS=1
+ SHUFFLE=yes
+ SPLASH_PAGE=yes
+ STATS_PAGE=yes
+ INCOMING_DIR=$(readlink -f /data/shuriken.sh/incoming/irregular.ninja)
+ DIST_DIR=/data/shuriken.sh/irregular.ninja/dist
+ TEMPLATE_DIR=/usr/share/shuriken/templates/default
+ TARBALL_INCLUDE=no
+ alt-irregular-ninja.conf: |
+ TITLE='Alternative Irregular Ninja'
+ THUMBHEIGHT=400
+ HEIGHT=1800
+ MAXPREVIEWS=40
+ IMAGE_JOBS=1
+ SHUFFLE=yes
+ SPLASH_PAGE=yes
+ STATS_PAGE=yes
+ INCOMING_DIR=$(readlink -f /data/shuriken.sh/incoming/alt.irregular.ninja)
+ DIST_DIR=/data/shuriken.sh/alt.irregular.ninja/dist
+ TEMPLATE_DIR=/usr/share/shuriken/templates/default
+ TARBALL_INCLUDE=no \ No newline at end of file
diff --git a/f3s/shuriken/helm-chart/templates/cronjob.yaml b/f3s/shuriken/helm-chart/templates/cronjob.yaml
new file mode 100644
index 0000000..ddb97b8
--- /dev/null
+++ b/f3s/shuriken/helm-chart/templates/cronjob.yaml
@@ -0,0 +1,91 @@
+# shuriken: nightly regeneration of the irregular.ninja and alt.irregular.ninja
+# static photo albums. The container entrypoint runs `shuriken --generate
+# --config <conf>` once per /configs/*.conf (alphabetical), writing each site
+# into /data/shuriken.sh/<site>/dist on the shared NFS export.
+#
+# Design:
+# - concurrencyPolicy: Forbid -- a long generate run (large photo library,
+# single image job) must not stack a second worker on top of it; shuriken's
+# staging directories would race and the NFS write load would double.
+# - backoffLimit: 0 -- a failed nightly run surfaces in the Job history and
+# gets retried by the next night's schedule; spamming retries would just
+# re-burn ImageMagick CPU on a persistent failure (e.g. NFS down).
+# - activeDeadlineSeconds: 6h -- caps a runaway first-run backfill of a large
+# library; steady state is far shorter.
+# - runAsUser: 0 -- the syncthing source tree is mode 750 root:wheel on the
+# NFS server, and the dist output is written as root to match the existing
+# irregular.ninja layout.
+# - Single image job by default (entrypoint --image-jobs 1); raise via the
+# SHURIKEN_IMAGE_JOBS env if a faster one-off run is needed.
+apiVersion: batch/v1
+kind: CronJob
+metadata:
+ name: shuriken
+ namespace: services
+spec:
+ # 04:00 local daily -- off-peak, and doesn't collide with the beets-art
+ # noon sweep. timeZone is GA in k8s 1.27+; k3s 1.32 supports it.
+ schedule: "0 4 * * *"
+ timeZone: Europe/Sofia
+ concurrencyPolicy: Forbid
+ startingDeadlineSeconds: 300
+ successfulJobsHistoryLimit: 3
+ failedJobsHistoryLimit: 3
+ jobTemplate:
+ spec:
+ backoffLimit: 0
+ activeDeadlineSeconds: 21600
+ template:
+ spec:
+ restartPolicy: Never
+ initContainers:
+ - name: nfs-check
+ image: busybox:stable
+ command:
+ - sh
+ - -c
+ - |
+ test -f /mnt/shuriken.sh/.nfs-sentinel || (
+ echo "ERROR: NFS sentinel missing at /mnt/shuriken.sh/.nfs-sentinel"
+ echo "refusing to start; node likely has NFS unmounted"
+ echo "pod would otherwise write into a stale local-XFS shadow"
+ exit 1
+ )
+ volumeMounts:
+ - name: data
+ mountPath: /mnt
+ readOnly: true
+ containers:
+ - name: shuriken
+ image: registry.lan.buetow.org:30001/shuriken:0.13.2
+ imagePullPolicy: Always
+ env:
+ # Default 1 image job (single-threaded); override here if a
+ # faster one-off run is acceptable. The entrypoint passes this
+ # to shuriken as --image-jobs.
+ - name: SHURIKEN_IMAGE_JOBS
+ value: "1"
+ resources:
+ requests:
+ cpu: 100m
+ memory: 256Mi
+ limits:
+ cpu: "1"
+ memory: 1Gi
+ securityContext:
+ allowPrivilegeEscalation: false
+ runAsUser: 0
+ runAsGroup: 0
+ volumeMounts:
+ - name: data
+ mountPath: /data
+ - name: configs
+ mountPath: /configs
+ readOnly: true
+ volumes:
+ - name: data
+ persistentVolumeClaim:
+ claimName: shuriken-data-pvc
+ - name: configs
+ configMap:
+ name: shuriken-config \ No newline at end of file
diff --git a/f3s/shuriken/helm-chart/templates/persistent-volumes.yaml b/f3s/shuriken/helm-chart/templates/persistent-volumes.yaml
new file mode 100644
index 0000000..4906714
--- /dev/null
+++ b/f3s/shuriken/helm-chart/templates/persistent-volumes.yaml
@@ -0,0 +1,38 @@
+# shuriken-data: the shared NFS export (/data/nfs/k3svolumes) mounted into the
+# CronJob at /data. The whole tree is mounted (not just shuriken.sh/) because
+# the incoming directories under shuriken.sh/incoming/ are relative symlinks
+# into ../../syncthing/... -- they only resolve when /data exposes the full
+# k3svolumes layout. The job writes only under /data/shuriken.sh/<site>/dist;
+# everything else it reads.
+#
+# RWX (shared NFS) so the job may land on any r-node; the nfs-mount-monitor on
+# each r-node keeps the mount healthy and the initContainer sentinel check
+# refuses to start if NFS is down (avoids writing into a stale local-XFS
+# shadow). The sentinel lives at shuriken.sh/.nfs-sentinel on the NFS server.
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: shuriken-data-pv
+spec:
+ capacity:
+ storage: 20Gi
+ volumeMode: Filesystem
+ accessModes:
+ - ReadWriteMany
+ persistentVolumeReclaimPolicy: Retain
+ hostPath:
+ path: /data/nfs/k3svolumes
+ type: Directory
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: shuriken-data-pvc
+ namespace: services
+spec:
+ storageClassName: ""
+ accessModes:
+ - ReadWriteMany
+ resources:
+ requests:
+ storage: 20Gi \ No newline at end of file