Let a server service carry helper scripts and timers, starting with UseSend's cert refresh

This commit is contained in:
Gabriel Brown committed 2026-10-30 13:44:29 -04:00
1 parent a987cbab66
commit 95fa480469
9 files changed
+304 -59

No files matched your search

+65 -15
View File
@@ -4,8 +4,10 @@
#
# The repository carries the catalog: server/vps/<Name>/ holds a
# compose.yml, a podman-<name>.service user unit, and an .env.example naming
# what the service needs told. The machine carries the rest: ~/Server/<Name>/
# is a real directory owning the .env (secrets never sit inside the checkout,
# what the service needs told. A service may also carry helper units (any
# other *.service or *.timer, e.g. a cert refresh) and helper files (scripts,
# a README) beside them. The machine carries the rest: ~/Server/<Name>/ is a
# real directory owning the .env (secrets never sit inside the checkout,
# gitignored or not) and the bind-mounted data. Only the tracked files are
# symlinked, so a `git clean` in the repo can never reach a database.
#
@@ -83,12 +85,12 @@ require_service() {
fi
}
# The one unit file a service directory carries. Its filename is its identity
# -- podman-<name>.service, exactly what lands in the systemd user directory --
# so nothing here invents a name that could drift from the file's.
# The one compose unit a service directory carries. Its filename is its
# identity -- podman-<name>.service, exactly what lands in the systemd user
# directory -- so nothing here invents a name that could drift from the file's.
unit_path_for() {
local name="$1" unit
for unit in "$SERVER_DIR/$name"/*.service; do
for unit in "$SERVER_DIR/$name"/podman-*.service; do
[[ -e "$unit" ]] || continue
printf '%s' "$unit"
return 0
@@ -96,6 +98,44 @@ unit_path_for() {
return 1
}
# Every other unit in the directory: helper services and the timers that fire
# them. They are linked beside the compose unit, and each timer is enabled.
helper_units_for() {
local name="$1" unit
for unit in "$SERVER_DIR/$name"/*.service "$SERVER_DIR/$name"/*.timer; do
[[ -e "$unit" ]] || continue
[[ "$(basename "$unit")" == podman-*.service ]] && continue
printf '%s\n' "$unit"
done
}
# The files linked into ~/Server/<Name>/: every visible top-level file that is
# not a unit. That is compose.yml plus any helper script or README; dotfiles
# are skipped so .env.example (and a stray .env) never become links.
live_files_for() {
local name="$1" file
for file in "$SERVER_DIR/$name"/*; do
[[ -f "$file" ]] || continue
case "$file" in *.service|*.timer) continue ;; esac
printf '%s\n' "$file"
done
}
link_service_files() {
local name="$1" file unit
mkdir -p "$TARGET_DIR/$name" "$UNIT_DIR"
while IFS= read -r file; do
link_file "$file" "$TARGET_DIR/$name/$(basename "$file")"
done < <(live_files_for "$name")
while IFS= read -r unit; do
link_file "$unit" "$UNIT_DIR/$(basename "$unit")"
done < <(helper_units_for "$name")
}
helper_timers_for() {
helper_units_for "$1" | grep '\.timer$' | xargs -r -n1 basename
}
unit_name_for() {
local unit
unit="$(unit_path_for "$1")" || return 1
@@ -111,10 +151,12 @@ unit_installed() {
[[ -L "$UNIT_DIR/$unit_name" ]]
}
# Everything this machine links for the service, so a change to a helper
# script or timer is reported by relink just like a compose change.
definition_hash() {
local name="$1" unit
unit="$(unit_path_for "$name")" || unit=/dev/null
cat "$SERVER_DIR/$name/compose.yml" "$unit" 2>/dev/null | sha256sum | cut -d' ' -f1
local name="$1"
{ live_files_for "$name"; unit_path_for "$name" && echo; helper_units_for "$name"; } \
| sort | xargs -r -d '\n' cat 2>/dev/null | sha256sum | cut -d' ' -f1
}
recorded_hash() {
@@ -198,6 +240,7 @@ cmd_enable() {
fi
mkdir -p "$UNIT_DIR"
link_service_files "$name"
link_file "$unit_path" "$UNIT_DIR/$unit_name"
systemctl --user daemon-reload
if systemctl --user enable --now "$unit_name"; then
@@ -207,6 +250,10 @@ cmd_enable() {
err "$unit_name did not start; see: systemctl --user status $unit_name"
exit 1
fi
local timer
for timer in $(helper_timers_for "$name"); do
systemctl --user enable --now "$timer" && ok "Enabled $timer"
done
}
cmd_disable() {
@@ -216,11 +263,15 @@ cmd_disable() {
local unit_name
unit_name="$(unit_name_for "$name")" || { err "$name has no unit file in the catalog"; exit 1; }
local timer unit
for timer in $(helper_timers_for "$name"); do
systemctl --user disable --now "$timer" 2>/dev/null || true
done
systemctl --user disable --now "$unit_name" 2>/dev/null || true
if [[ -L "$UNIT_DIR/$unit_name" ]]; then
rm "$UNIT_DIR/$unit_name"
systemctl --user daemon-reload
fi
for unit in "$UNIT_DIR/$unit_name" $(helper_units_for "$name" | xargs -r -n1 basename | sed "s|^|$UNIT_DIR/|"); do
[[ -L "$unit" ]] && rm "$unit"
done
systemctl --user daemon-reload
ok "$name disabled. ~/Server/$name, its .env and its data were not touched."
}
@@ -267,8 +318,7 @@ cmd_relink() {
unit_installed "$name" || continue
link_file "$unit_path" "$UNIT_DIR/$unit_name"
mkdir -p "$TARGET_DIR/$name"
link_file "$SERVER_DIR/$name/compose.yml" "$TARGET_DIR/$name/compose.yml"
link_service_files "$name"
refreshed=$((refreshed + 1))
# Changed since the last time this machine looked, which is what makes a