johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
#!/usr/bin/env bash
# Builds knotless on the runner host from this checkout and (re)starts its
# knotless-run there.
#
# deploy/runner-host/deploy.sh <ssh destination>
# KNOTLESS_RUNNER_HOST=root@host deploy/runner-host/deploy.sh
#
# or with that in deploy/local.env. Run setup.sh on the host once first.
#
# KNOTLESS_RUNNER_ENV names a file read instead of deploy/local.env, for
# a second runner on a host, as deploy/ci.env is for the trusted runner
# (README.md, The trusted runner), so none of the first's settings reach
# it: its KNOTLESS_RUNNER_HOST and KNOTLESS_RUNNER_* settings, and
# KNOTLESS_RUNNER_NAME, _ETC and _DATA, its compose project, its directory
# for run.env, the secrets and the pause list (/etc/knotless), and its
# work (/var/lib/knotless-run).
#
# From deploy/local.env, or the environment:
#
# - optional: KNOTLESS_RUNNER_REPOS, repositories it watches besides
# those whose records name its account, which it finds (--discover), as
# `alice.example.com/tally bob.example.com/notes`;
# - KNOTLESS_RUNNER_IDENTIFIER and KNOTLESS_RUNNER_PASSWORD, its account
# and an app password for it, needed once: they are kept on the host in
# /etc/knotless/run.env;
# - optional: KNOTLESS_RUNNER_DOMAIN and KNOTLESS_RUNNER_DID, the name
# Caddy answers the runner's bot login at (`runner.knotless.dev`, an
# A/AAAA record to this host) and the runner's account, the bot that may
# log in there: both turn on the `bots` profile, for private
# repositories (README.md, Private repositories);
# - optional: KNOTLESS_RUNNER_ARGS, more of its command line (`--queue`);
# KNOTLESS_RUNNER_JOBS (4), _CPUS (2), _MEMORY (3, GiB), _TIMEOUT
# (3600 s), _ALLOWANCE (2000) and _PRIVATE_ALLOWANCE (500), as
# docker-compose.yml passes them; KNOTLESS_LOG (info);
# - optional: KNOTLESS_RUNNER_IMAGE_FROM, an SSH destination whose
# knotless:current to run rather than building here, copied only when
# its runner's build differs: main's deploy sets it to the main host,
# so both run the same knotless (.knotless/workflows/deploy.yml).
#
# The image is built on the host, from this checkout's HEAD as `git
# archive` gives it (so commit first): a build driven over SSH by a local
# docker client times out resolving base images, and Docker Desktop's
# builder ignores DOCKER_HOST and builds locally.
set -euo pipefail
cd "$(dirname "$0")/../.."
if [ -n "${KNOTLESS_RUNNER_ENV:-}" ]; then
set -a; . "$KNOTLESS_RUNNER_ENV"; set +a
elif [ -f deploy/local.env ]; then
set -a; . deploy/local.env; set +a
fi
NAME="${KNOTLESS_RUNNER_NAME:-knotless-runner}"
ETC="${KNOTLESS_RUNNER_ETC:-/etc/knotless}"
DATA="${KNOTLESS_RUNNER_DATA:-/var/lib/knotless-run}"
DEST="${1:-${KNOTLESS_RUNNER_HOST:-}}"
[ -n "$DEST" ] || { sed -n '5,6p' "$0" >&2; exit 2; }
REPOS="${KNOTLESS_RUNNER_REPOS:-}"
SSH="ssh -o StrictHostKeyChecking=accept-new $DEST"
COMMIT="$(git rev-parse HEAD 2>/dev/null || echo local)"
if [ -n "${KNOTLESS_RUNNER_IMAGE_FROM:-}" ]; then
# A box too small to build knotless, or a deploy of main, takes the image
# another host built, unless the runner here is that build already: as
# the main host's deploy does (deploy/deploy.sh), a runner whose build
# is the same is left running, since a restart cancels its runs.
FROM="ssh -o StrictHostKeyChecking=accept-new $KNOTLESS_RUNNER_IMAGE_FROM"
PRINT='docker run --rm --entrypoint cat knotless:current /etc/knotless-run.fingerprint'
NEW=$($FROM "$PRINT" 2>/dev/null || true)
OLD=$($SSH "$PRINT" 2>/dev/null || true)
if [[ $NEW =~ ^[0-9a-f]{64}$ ]] && [ "$NEW" = "$OLD" ]; then
echo "==> its runner is ${NEW:0:12} on $KNOTLESS_RUNNER_IMAGE_FROM too: keeping its image"
else
echo "==> copying knotless:current (${NEW:0:12}, here ${OLD:0:12}) from $KNOTLESS_RUNNER_IMAGE_FROM to $DEST"
$FROM 'docker save knotless:current | gzip -1' | $SSH 'gunzip | docker load -q'
fi
else
echo "==> building knotless at ${COMMIT:0:12} on $DEST"
git archive --format=tar HEAD |
$SSH "docker build -q --build-arg KNOTLESS_COMMIT=$COMMIT -t knotless:current -"
fi
if [ -n "${KNOTLESS_RUNNER_IDENTIFIER:-}" ] && [ -n "${KNOTLESS_RUNNER_PASSWORD:-}" ]; then
printf 'KNOTLESS_IDENTIFIER=%s\nKNOTLESS_PASSWORD=%s\n' "$KNOTLESS_RUNNER_IDENTIFIER" "$KNOTLESS_RUNNER_PASSWORD" |
$SSH "umask 077; mkdir -p '$ETC' && cat > '$ETC/run.env'"
fi
$SSH "[ -f '$ETC/run.env' ]" || {
echo "the runner needs KNOTLESS_RUNNER_IDENTIFIER and KNOTLESS_RUNNER_PASSWORD once" >&2
exit 2
}
{
echo "KNOTLESS_RUNNER_NAME=$NAME"
echo "KNOTLESS_RUNNER_ETC=$ETC"
echo "KNOTLESS_RUNNER_DATA=$DATA"
echo "KNOTLESS_RUN_REPOS=$REPOS"
[ -z "${KNOTLESS_RUNNER_ARGS:-}" ] || echo "KNOTLESS_RUN_ARGS=$KNOTLESS_RUNNER_ARGS"
# KNOTLESS_RUNNER_DISCOVER set empty watches only KNOTLESS_RUNNER_REPOS.
[ -z "${KNOTLESS_RUNNER_DISCOVER+set}" ] || echo "KNOTLESS_RUN_DISCOVER=$KNOTLESS_RUNNER_DISCOVER"
for v in JOBS OWNER_JOBS CPUS MEMORY TIMEOUT ALLOWANCE PRIVATE_ALLOWANCE; do
from="KNOTLESS_RUNNER_$v"
[ -z "${!from:-}" ] || echo "KNOTLESS_RUN_$v=${!from}"
done
[ -z "${KNOTLESS_LOG:-}" ] || echo "KNOTLESS_LOG=$KNOTLESS_LOG"
if [ -n "${KNOTLESS_RUNNER_DOMAIN:-}" ]; then
echo "COMPOSE_PROFILES=bots"
echo "KNOTLESS_RUNNER_DOMAIN=$KNOTLESS_RUNNER_DOMAIN"
echo "KNOTLESS_BOT_DIDS=${KNOTLESS_RUNNER_DID:?KNOTLESS_RUNNER_DOMAIN takes KNOTLESS_RUNNER_DID, the bot that logs in there}"
fi
} | $SSH "umask 077; cat > '$ETC/.env'"
scp -q -o StrictHostKeyChecking=accept-new deploy/runner-host/docker-compose.yml \
deploy/runner-host/Caddyfile "$DEST:$ETC/"
echo "==> starting"
$SSH "
set -e
cd '$ETC'
install -d -m 0700 '$DATA'
[ -f paused.json ] || echo '{}' > paused.json
[ -f run-secrets.json ] || (umask 077; echo '{}' > run-secrets.json)
mkdir -p '$DATA/bots/sessions' /var/lib/knotless-serve /var/lib/caddy
# bots runs as uid 1000, the node user the image runs as, as the main
# host's serve does; run, as root, reads and refreshes there too.
chown 1000:1000 /var/lib/knotless-serve '$DATA/bots' '$DATA/bots/sessions'
docker compose config --quiet
docker compose up -d --remove-orphans
docker compose ps
"