summaryrefslogtreecommitdiff
path: root/f3s/shuriken/helm-chart/templates/sync-cronjob.yaml
blob: 66109e72aa6d74c9b4a48b0c4c7a37604c4c5fa1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# shuriken-sync: a SEPARATE CronJob that publishes the generated
# /data/shuriken.sh/<site>/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.13.2
              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