# shuriken-sync: a SEPARATE CronJob that publishes the generated # /data/shuriken.sh//dist trees to the public web servers (fishfinger + # blowfish) over the rsync DAEMON protocol (no SSH, no key). The frontends run # rsyncd via inetd with `hosts allow = *.wg0.wan.buetow.org,*.wg0,localhost`; the # k3s pods run on r-nodes that have .wg0 (WireGuard) connectivity, so they match # and can push over the mesh. The writable modules `irregular-ninja` and # `alt-irregular-ninja` are declared in frontends/etc/rsyncd.conf.tpl (deploy # with `rex -f frontends/Rexfile rsync`). # # It only publishes when a generation has COMPLETED since the last sync: # shuriken deletes dist/status.json at the start of a run and writes it last on # success, so status.json's presence+freshness is the "completed" signal. Most # ticks are no-ops (one stat); a publish fires once after each successful daily # generation. The shuriken `--sync` over SSH stays available as an option; this # cron job just uses the rsync protocol instead. # # The nfs-check initContainer refuses to start if NFS is down on the node, so # rsync --delete can never run against an empty/stale source and wipe the live # public site. apiVersion: batch/v1 kind: CronJob metadata: name: shuriken-sync namespace: services spec: # Every 30 min -- most ticks skip (no fresh generation); a publish fires soon # after the daily 04:00 generation completes. Cheap: one stat per tick. schedule: "*/30 * * * *" timeZone: Europe/Sofia concurrencyPolicy: Forbid startingDeadlineSeconds: 300 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: spec: backoffLimit: 0 activeDeadlineSeconds: 3600 template: spec: restartPolicy: Never hostAliases: # The frontends are reached over the WireGuard mesh (.wg0); cluster # DNS does not resolve *.wg0 names, so pin them to the mesh IPs. - ip: 192.168.2.111 hostnames: - fishfinger.wg0 - fishfinger.wg0.wan.buetow.org - ip: 192.168.2.110 hostnames: - blowfish.wg0 - blowfish.wg0.wan.buetow.org 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 publish; NFS likely unmounted on this node" echo "rsync --delete against an empty/stale source would wipe the live site" exit 1 ) volumeMounts: - name: data mountPath: /mnt readOnly: true containers: - name: shuriken-sync image: registry.lan.buetow.org:30001/shuriken:0.14.0 imagePullPolicy: Always command: ["/bin/bash", "-c"] args: - | set -euo pipefail D=/data/shuriken.sh # Mutually exclude with the generation CronJob: don't publish # while a generation is running. Non-blocking -- if the lock is # held (generation in progress), skip this tick and let the next # one publish once generation finishes. flock auto-releases if # the generation pod dies, so a crash never wedges the sync. exec 9>"$D/.lock" if ! flock -n 9; then echo "shuriken-sync: generation in progress (lock held); skipping" exit 0 fi # Only publish when a generation has completed since the last # sync. status.json is deleted at the start of a run and # written last on success, so its presence means "completed" # and its freshness vs .last-sync means "not yet published". gen=0 for f in \ "$D/irregular.ninja/dist/status.json" \ "$D/alt.irregular.ninja/dist/status.json"; do if [ ! -f "$f" ]; then echo "shuriken-sync: $f absent (generation in progress or failed); skipping" exit 0 fi m=$(stat -c %Y "$f") [ "$m" -gt "$gen" ] && gen=$m done last=0 [ -f "$D/.last-sync" ] && last=$(stat -c %Y "$D/.last-sync") if [ "$gen" -le "$last" ]; then echo "shuriken-sync: no completed generation since last sync (gen=$gen last=$last); skipping" exit 0 fi echo "shuriken-sync: generation completed at $gen (last sync $last); publishing" for pair in irregular.ninja:irregular-ninja alt.irregular.ninja:alt-irregular-ninja; do site=${pair%%:*} mod=${pair##*:} for srv in fishfinger.wg0 blowfish.wg0; do echo "shuriken-sync: $site -> rsync://$srv/$mod/" rsync -a --delete --info=stats1 \ "$D/$site/dist/" "rsync://$srv/$mod/" done done : > "$D/.last-sync" echo "shuriken-sync: done" resources: requests: cpu: 50m memory: 64Mi limits: cpu: "1" memory: 512Mi securityContext: allowPrivilegeEscalation: false runAsUser: 0 runAsGroup: 0 volumeMounts: - name: data mountPath: /data - name: tmp mountPath: /tmp volumes: - name: data persistentVolumeClaim: claimName: shuriken-data-pvc - name: tmp emptyDir: sizeLimit: 256Mi