CampgroundTickets/scripts/deploy-runner.sh
Hank 3583c2e15f CI: cache Android SDK/NDK, Gradle, and npm across APK builds
Persist three Docker volumes into the build job (android-sdk, gradle home, npm
cache) and enable the Gradle build cache, so repeat builds skip the ~2GB NDK
download, Maven dependency resolution, and unchanged native/Kotlin compilation
— cutting builds from ~1h to a few minutes after the first run.

The runner must whitelist these volumes via config.yaml (container.valid_volumes);
deploy-runner.sh now writes that config, pre-creates the volumes, and starts the
daemon with --config. Requires re-running deploy-runner.sh on the runner host
before the next tag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 16:26:06 +00:00

143 lines
4.7 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Deploy the Camp Scan Forgejo Actions runner on a remote Ubuntu host.
# Self-contained: installs Docker if missing, writes the runner compose + env,
# registers against git.mowden.top, and starts the runner.
#
# Usage:
# sudo bash deploy-runner.sh <REGISTRATION_TOKEN> [INSTALL_DIR]
#
# REGISTRATION_TOKEN From Forgejo -> Beartaria/CampgroundTickets ->
# Settings -> Actions -> Runners -> Create new runner.
# INSTALL_DIR Where to place the runner (default /opt/camptickets-runner).
#
# Re-running is safe: it updates files and restarts. The token is only consumed
# on the first registration (stored afterwards in <INSTALL_DIR>/data/.runner).
set -euo pipefail
FORGEJO_INSTANCE="https://git.mowden.top"
RUNNER_IMAGE="code.forgejo.org/forgejo/runner:6.3.1"
RUNNER_NAME="${RUNNER_NAME:-$(hostname)-campscan}"
JOB_LABEL="docker:docker://node:22-bookworm"
TOKEN="${1:-${REGISTRATION_TOKEN:-}}"
INSTALL_DIR="${2:-/opt/camptickets-runner}"
log() { printf '\033[1;32m==>\033[0m %s\n' "$*"; }
err() { printf '\033[1;31mERROR:\033[0m %s\n' "$*" >&2; }
if [ "$(id -u)" -ne 0 ]; then
err "Run with sudo/root (needs to install packages and access the Docker socket)."
exit 1
fi
if [ -z "$TOKEN" ] && [ ! -f "$INSTALL_DIR/data/.runner" ]; then
err "No registration token given and no existing registration found."
echo "Usage: sudo bash deploy-runner.sh <REGISTRATION_TOKEN> [INSTALL_DIR]" >&2
exit 1
fi
# --- 1. Docker ---------------------------------------------------------------
if ! command -v docker >/dev/null 2>&1; then
log "Installing Docker (get.docker.com)..."
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
else
log "Docker already installed: $(docker --version)"
fi
if ! docker compose version >/dev/null 2>&1; then
log "Installing docker compose plugin..."
apt-get update -y
apt-get install -y docker-compose-plugin
fi
DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)"
log "Docker socket group id: $DOCKER_GID"
# --- 2. Files ----------------------------------------------------------------
log "Writing runner files to $INSTALL_DIR"
mkdir -p "$INSTALL_DIR/data"
# The runner image runs as uid/gid 1000 and must own its config dir.
chown -R 1000:1000 "$INSTALL_DIR/data"
# Runner config: allow the build workflow to mount the persistent cache volumes
# (Android SDK/NDK, Gradle home, npm cache) so APK builds don't redownload and
# recompile everything each run. Without valid_volumes listed, the runner
# rejects the workflow's `volumes:` and the job fails.
cat > "$INSTALL_DIR/config.yaml" <<'CONFIG'
container:
valid_volumes:
- camptickets-android-sdk
- camptickets-gradle
- camptickets-npm
CONFIG
chown 1000:1000 "$INSTALL_DIR/config.yaml"
# Pre-create the named cache volumes (idempotent).
docker volume create camptickets-android-sdk >/dev/null
docker volume create camptickets-gradle >/dev/null
docker volume create camptickets-npm >/dev/null
cat > "$INSTALL_DIR/docker-compose.yml" <<COMPOSE
services:
runner:
image: ${RUNNER_IMAGE}
container_name: camptickets-runner
restart: unless-stopped
working_dir: /data
group_add:
- "\${DOCKER_GID}"
volumes:
- ./data:/data
- ./config.yaml:/data/config.yaml:ro
- /var/run/docker.sock:/var/run/docker.sock
env_file: .env
command:
- sh
- -c
- |
if [ ! -f /data/.runner ]; then
echo "Registering runner with ${FORGEJO_INSTANCE} ..."
forgejo-runner register --no-interactive \\
--instance "${FORGEJO_INSTANCE}" \\
--token "\${REGISTRATION_TOKEN}" \\
--name "\${RUNNER_NAME}" \\
--labels "${JOB_LABEL}"
fi
exec forgejo-runner daemon --config /data/config.yaml
COMPOSE
# Preserve an existing token line if re-running without a new token.
cat > "$INSTALL_DIR/.env" <<ENV
REGISTRATION_TOKEN=${TOKEN}
RUNNER_NAME=${RUNNER_NAME}
DOCKER_GID=${DOCKER_GID}
ENV
chmod 600 "$INSTALL_DIR/.env"
# --- 3. Start ----------------------------------------------------------------
log "Starting runner..."
cd "$INSTALL_DIR"
docker compose up -d
sleep 6
echo
log "Status:"
docker compose ps --format 'table {{.Name}}\t{{.Status}}'
echo
log "Recent logs (look for 'declared successfully' then 'poller launched'):"
docker compose logs --no-log-prefix 2>&1 | tail -8
cat <<DONE
------------------------------------------------------------------
Runner deployed to $INSTALL_DIR
Logs: cd $INSTALL_DIR && docker compose logs -f
Restart: cd $INSTALL_DIR && docker compose restart
Stop: cd $INSTALL_DIR && docker compose down
Next: add the ANDROID_* secrets in Forgejo, then push a tag (e.g. v0.0.1)
to trigger the APK build. It will run on THIS host.
------------------------------------------------------------------
DONE