mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-12 16:04:25 +00:00
156 lines
7.5 KiB
Bash
Executable File
156 lines
7.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# Everything CI should know about the fleet join script.
|
|
#
|
|
# The script is served verbatim from the backend at GET /join.sh and is what a
|
|
# stranger pipes into a root shell to add a machine, which makes it the
|
|
# highest-consequence file in the repo that is not Go. Nothing else covered it,
|
|
# and that is how a systemd unit that could never start, an env file with a
|
|
# stray JSON fragment in it, and a state directory the node could not write all
|
|
# reached the branch at once.
|
|
#
|
|
# What is checked:
|
|
# - POSIX parse under dash, which is /bin/sh on Debian and Ubuntu
|
|
# - shellcheck, in sh mode
|
|
# - --help exits 0 and says something
|
|
# - the generated systemd unit is ONE ExecStart line with the image as a
|
|
# systemd variable, not a command substitution systemd would never expand
|
|
# - the mount list always includes the agent directory
|
|
# - a relative BLOB_FS_ROOT is refused rather than mounted
|
|
set -eu
|
|
|
|
SCRIPT="internal/api/handler/nodescript/join.sh"
|
|
fail() { printf 'check-join-script: %s\n' "$*" >&2; exit 1; }
|
|
ok() { printf ' ok %s\n' "$*"; }
|
|
|
|
[ -f "$SCRIPT" ] || fail "$SCRIPT not found (run from the repository root)"
|
|
|
|
# POSIX parse. sh -n under a non-POSIX shell proves nothing about dash, which
|
|
# is /bin/sh on Debian and Ubuntu, so dash is used when it is there.
|
|
parse_check() {
|
|
if command -v dash >/dev/null 2>&1; then
|
|
dash -n "$1" || fail "dash -n failed on $1"
|
|
else
|
|
sh -n "$1" || fail "sh -n failed on $1"
|
|
fi
|
|
}
|
|
|
|
parse_check "$SCRIPT"
|
|
# The checker too: its own shellcheck-disable directives are load-bearing.
|
|
parse_check "$0"
|
|
if command -v dash >/dev/null 2>&1; then
|
|
ok "dash -n (script and checker)"
|
|
else
|
|
printf ' -- dash not installed; parsed with sh -n instead\n'
|
|
fi
|
|
|
|
if command -v shellcheck >/dev/null 2>&1; then
|
|
shellcheck -s sh "$SCRIPT" || fail "shellcheck failed on $SCRIPT"
|
|
shellcheck -s sh "$0" || fail "shellcheck failed on $0"
|
|
ok "shellcheck -s sh (script and checker)"
|
|
else
|
|
printf ' -- shellcheck not installed; skipped\n'
|
|
fi
|
|
|
|
out=$(sh "$SCRIPT" --help) || fail "--help exited non-zero"
|
|
printf '%s' "$out" | grep -q -- "--token" || fail "--help does not document --token"
|
|
ok "--help"
|
|
|
|
# Everything below asserts on what the script RENDERS, never on its source
|
|
# text. A previous version of this file checked a heredoc copied in here, which
|
|
# meant putting the original `$(cat ...)` bug back left it passing green.
|
|
# The render variants, defined once. Both loops below read this, so adding a
|
|
# third here extends every per-render assertion rather than only the first.
|
|
RENDER_VARIANTS='
|
|
BLOB_PROVIDER=fs
|
|
BLOB_FS_ROOT=/var/lib/warmbly/blobs'
|
|
|
|
unit=$(sh "$SCRIPT" --print-unit) || fail "--print-unit failed"
|
|
|
|
# shellcheck disable=SC2016 # the pattern is literal on purpose; it must not expand
|
|
printf '%s\n' "$unit" | grep -q 'ExecStart=.*\${WARMBLY_IMAGE_REF}$' \
|
|
|| fail "ExecStart must end with the systemd variable \${WARMBLY_IMAGE_REF}"
|
|
# shellcheck disable=SC2016 # literal on purpose
|
|
if printf '%s\n' "$unit" | grep -q 'ExecStart=.*\$('; then
|
|
fail "ExecStart contains a command substitution; systemd never expands one"
|
|
fi
|
|
[ "$(printf '%s\n' "$unit" | grep -c '^ExecStart=')" = "1" ] \
|
|
|| fail "ExecStart must be exactly one line"
|
|
printf '%s\n' "$unit" | grep -q '^EnvironmentFile=/var/lib/warmbly/image-ref$' \
|
|
|| fail "image-ref must stay in the root-owned state dir; the node must not be able to rewrite it"
|
|
printf '%s\n' "$unit" | grep -q 'ExecStart=.*-v /var/lib/warmbly/node:/var/lib/warmbly/node' \
|
|
|| fail "the agent directory must always be mounted, or auto-update stops silently"
|
|
ok "rendered unit (no blob mount)"
|
|
|
|
# With local blobs the root has to be mounted too, and the line must still be
|
|
# one line: a multi-line mount list is how the continuation collapsed before.
|
|
unit=$(NODE_ENV="$RENDER_VARIANTS" sh "$SCRIPT" --print-unit) || fail "--print-unit with blobs failed"
|
|
printf '%s\n' "$unit" | grep -q 'ExecStart=.*-v /var/lib/warmbly/blobs:/var/lib/warmbly/blobs' \
|
|
|| fail "BLOB_FS_ROOT must be mounted (the fs alias counts as filesystem)"
|
|
[ "$(printf '%s\n' "$unit" | grep -c '^ExecStart=')" = "1" ] \
|
|
|| fail "ExecStart must stay one line when a blob mount is added"
|
|
ok "rendered unit (fs alias + blob mount)"
|
|
|
|
# A relative root is refused rather than rendered into a mount docker rejects.
|
|
if NODE_ENV="BLOB_PROVIDER=filesystem
|
|
BLOB_FS_ROOT=data/blobs" sh "$SCRIPT" --print-unit >/dev/null 2>&1; then
|
|
fail "a relative BLOB_FS_ROOT must be refused, not mounted"
|
|
fi
|
|
ok "relative BLOB_FS_ROOT refused"
|
|
|
|
# NO EnvironmentFile may point into the node-writable mount. Asserting only
|
|
# that the right one exists is not enough: an extra one under AGENT_DIR would
|
|
# let the container choose the image root's `docker run --network host` runs.
|
|
# Checked in every render, not just the default one. EnvironmentFile does not
|
|
# vary with NODE_ENV today, but the point of this assertion is what someone
|
|
# changes tomorrow, and "today it is redundant" is exactly the reasoning that
|
|
# already dropped this guard once. One extra subshell is a fair price.
|
|
for variant_env in "" "$RENDER_VARIANTS"; do
|
|
v_unit=$(NODE_ENV="$variant_env" sh "$SCRIPT" --print-unit) || fail "--print-unit failed"
|
|
if printf '%s\n' "$v_unit" | grep '^EnvironmentFile=' | grep -q '/var/lib/warmbly/node'; then
|
|
fail "an EnvironmentFile points into the node-writable mount; the node could choose the image root runs"
|
|
fi
|
|
done
|
|
ok "no EnvironmentFile is node-writable (every render)"
|
|
|
|
# Two invariants that leave no trace in the rendered unit and so cannot be
|
|
# caught above: both were real defects, so they are asserted at their call
|
|
# sites. Comments are stripped and the call is matched in command position, so
|
|
# a commented-out call fails while reformatting does not.
|
|
# body_of prints one function's body. Tolerant about the definition's spacing,
|
|
# and loud when the function is not found: an empty body would otherwise fail
|
|
# every assertion below with a message about the wrong thing.
|
|
body_of() {
|
|
out=$(awk -v fn="$1" '
|
|
$0 ~ "^" fn "[ \t]*\\([ \t]*\\)[ \t]*{" { inside = 1 }
|
|
inside { print }
|
|
inside && $0 == "}" { exit }' "$SCRIPT")
|
|
[ -n "$out" ] || fail "function $1() not found in $SCRIPT"
|
|
printf '%s\n' "$out"
|
|
}
|
|
|
|
# Match the call lines themselves, not any line mentioning the word: "enrol"
|
|
# also appears inside the word "enrolment" in a comment.
|
|
# Captured before asserting: body_of fails when the function is missing, and a
|
|
# pipeline would run it in a subshell where that failure is lost and the
|
|
# misleading assertion message wins.
|
|
main_body=$(body_of main)
|
|
printf '%s\n' "$main_body" | awk '
|
|
$1 == "enrol" { e = NR }
|
|
$1 == "validate_blob_root" { v = NR }
|
|
$1 == "write_config" { w = NR }
|
|
END { exit !(e && v && w && e < v && v < w) }' \
|
|
|| fail "main must call validate_blob_root between enrol and write_config"
|
|
ok "config is validated before anything is written"
|
|
|
|
# Field match on the first word, which cannot be fooled by the name appearing
|
|
# inside a string or a comment. That does mean the call has to stay a
|
|
# standalone statement; join.sh says so at the call site. A looser regex was
|
|
# tried and was satisfied by `warn "... ensure_blob_root ..."`, which is a much
|
|
# worse failure than a reformat that reports itself clearly.
|
|
install_body=$(body_of install_units)
|
|
printf '%s\n' "$install_body" | awk '$1 == "ensure_blob_root" { found = 1 } END { exit !found }' \
|
|
|| fail "install_units must call ensure_blob_root as a standalone statement, or a filesystem-blob node restart-loops"
|
|
ok "blob root is prepared before the unit is installed"
|
|
|
|
printf 'check-join-script: all checks passed\n'
|