Files

114 lines
4.5 KiB
YAML

variant: flatcar
version: 1.0.0
# Shared by every Flatcar VM: main.tf passes this file as each VM's inline
# butane snippet. Loaded with file(), not templatefile(), so it is not a
# template -- ${...} below reaches the VM as written.
#
# Keeps /etc/versions.env in sync with versions.env at the root of
# AtlasHorizon/compose, which holds the image tag of every container on the
# Flatcar VMs. A version bump merged to the branch below reaches each VM
# within ~10 minutes with no plan or rebuild; see fetch-versions.sh for how
# the affected units are found and restarted.
#
# To opt a container unit in, in its butane file:
# [Unit] After=... versions-fetch.service
# Wants=versions-fetch.service
# [Service] EnvironmentFile=/etc/versions.env
# and use the variable, in braces, wherever the image goes (the docker pull
# and docker run arguments). In a .tftpl file the braces need a doubled
# dollar sign so templatefile() leaves them alone.
systemd:
units:
# Not enabled itself: the container units pull it in at boot (Wants= and
# After=), so they start on the freshest copy, and the timer re-runs it.
# A failed fetch keeps the last good copy, so a Gitea outage only delays
# updates -- except on a brand-new VM, whose units cannot start until the
# first fetch succeeds. Restart= keeps retrying that first fetch.
- name: versions-fetch.service
contents: |
[Unit]
Description=Fetch image versions from AtlasHorizon/compose
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
# Override with a drop-in to test unreleased versions on one VM.
Environment=VERSIONS_BRANCH=main
ExecStart=/opt/versions/fetch-versions.sh
Restart=on-failure
RestartSec=30s
- name: versions-fetch.timer
enabled: true
contents: |
[Unit]
Description=Periodically re-fetch image versions
[Timer]
OnBootSec=10min
OnUnitActiveSec=10min
RandomizedDelaySec=1min
[Install]
WantedBy=timers.target
storage:
files:
- path: /opt/versions/fetch-versions.sh
mode: 0755
contents:
inline: |
#!/usr/bin/bash
# Fetches versions.env and restarts the units that use a variable
# whose value changed. A unit "uses" a variable when its unit file
# contains it in braces (the docker pull/run image arguments), so
# adding a container needs no change here.
set -euo pipefail
url="https://git.atlashorizon.net/AtlasHorizon/compose/raw/branch/$VERSIONS_BRANCH/versions.env"
dest=/etc/versions.env
tmp=$(mktemp /etc/.versions.env.XXXXXX)
trap 'rm -f "$tmp"' EXIT
curl -fsS --max-time 30 -o "$tmp" "$url"
# Only comments, blank lines and VAR=value lines are accepted, and
# at least one *_IMAGE line must be present, so an error page or a
# truncated download never replaces the last good copy.
if grep -qvE '^(#.*|[A-Z0-9_]+=[^[:space:]]+|)$' "$tmp" ||
! grep -qE '^[A-Z0-9_]+_IMAGE=' "$tmp"; then
echo "refusing malformed versions.env from $url" >&2
exit 1
fi
if [ -f "$dest" ] && cmp -s "$tmp" "$dest"; then
exit 0
fi
# With no previous copy this is a brand-new VM: its units are waiting
# on (or already failed without) this file, so start them outright.
# Otherwise only units that are running get restarted, so a unit
# stopped by hand stays stopped.
if [ -f "$dest" ]; then
action=try-restart
changed=$(diff <(grep -E '^[A-Z0-9_]+=' "$dest" | sort) \
<(grep -E '^[A-Z0-9_]+=' "$tmp" | sort) |
sed -nE 's/^[<>] ([A-Z0-9_]+)=.*/\1/p' | sort -u || true)
else
action=restart
changed=$(sed -nE 's/^([A-Z0-9_]+)=.*/\1/p' "$tmp")
fi
chmod 0644 "$tmp"
mv -f "$tmp" "$dest"
for var in $changed; do
echo "$var changed"
pattern=$(printf '${%s}' "$var")
# --no-block: the units are ordered after this one, so waiting on
# their jobs from inside it would deadlock at boot.
for unit in $(grep -lF "$pattern" /etc/systemd/system/*.service || true); do
systemctl --no-block "$action" "$(basename "$unit")"
done
done