diff options
| author | Paul Buetow <paul@buetow.org> | 2026-08-01 09:43:07 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-08-01 09:43:07 +0300 |
| commit | 38b01c385a59e6f9bd71ebe54a24fcf74b8966ab (patch) | |
| tree | a15f0773a310967fce8dda671b82370b2a5353d3 /f3s | |
| parent | 0bcc97a819d6578d41754512376b31fff66b1bce (diff) | |
Add shuriken nightly CronJob (irregular.ninja + alt.irregular.ninja)
ArgoCD-managed CronJob that regenerates the irregular.ninja and
alt.irregular.ninja photo albums with the shuriken.sh Docker image,
writing them to /data/nfs/k3svolumes/shuriken.sh/<site>/dist on the
shared NFS export. Single image job by default (passively cooled N100
hosts). 04:00 Europe/Sofia daily, Forbid stacking, 6h deadline.
Diffstat (limited to 'f3s')
| -rw-r--r-- | f3s/argocd-apps/services/shuriken.yaml | 28 | ||||
| -rw-r--r-- | f3s/shuriken/Justfile | 44 | ||||
| -rw-r--r-- | f3s/shuriken/README.md | 78 | ||||
| -rw-r--r-- | f3s/shuriken/docker-image/Justfile | 17 | ||||
| -rw-r--r-- | f3s/shuriken/helm-chart/Chart.yaml | 5 | ||||
| -rw-r--r-- | f3s/shuriken/helm-chart/templates/configmap.yaml | 50 | ||||
| -rw-r--r-- | f3s/shuriken/helm-chart/templates/cronjob.yaml | 91 | ||||
| -rw-r--r-- | f3s/shuriken/helm-chart/templates/persistent-volumes.yaml | 38 |
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 |
