ci: pull-based deploy to the Pi via rolling dev release
Build (Dev) / build (push) Failing after 16s
CI/CD / lint-and-typecheck (push) Successful in 9m28s
CI/CD / test (push) Successful in 9m27s
CI/CD / build (push) Failing after 4m49s
CI/CD / deploy (push) Has been skipped

The Pi is on a closed travel-router LAN, so push-based deploy from a
runner can't reach it. Switch to pull: the runner builds + publishes,
the Pi fetches.

- build-dev.yaml: after the arm64 build, publish the binary + sha256 +
  version.txt to a rolling "dev" Gitea release (replaces the
  upload-artifact + repository_dispatch -> deploy-dev hop)
- remove deploy-dev.yaml (push/scp-based deploy no longer used)
- scripts/pi-update.sh: poll the dev release, verify sha256, install via
  deploy.sh (backup/restart/rollback); only updates when version changes
- scripts/remoterig-update.{service,timer}: run the updater every 5 min
- setup-pi.sh: install deploy.sh + pi-update.sh + update.env template +
  the updater timer; summary now reflects the pull flow
- README: document the pull-based CI/CD; fix stale GOARM=6 (Zero 2 W is
  arm64 on 64-bit OS / arm GOARM=7 on 32-bit)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Joshua King
2026-06-05 08:00:48 -04:00
parent f261fa0f55
commit c2a05f9b7c
7 changed files with 197 additions and 166 deletions
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# RemoteRig — Pi-side pull updater
# ================================
# Polls the rolling "dev" release on Gitea and, when the published version
# differs from what's installed, downloads + verifies (sha256) + deploys it
# via the existing rollback-capable deploy.sh. Run on a timer (see
# remoterig-update.timer). The Pi pulls; nothing pushes into the closed net.
#
# Config (env, or /opt/remoterig/update.env):
# GITEA_BASE default https://code.cubecraftcreations.com
# REPO default CubeCraft-Creations/remote-rig
# GITEA_TOKEN read token (required only if the repo is private)
# DEPLOY_PATH default /opt/remoterig/remoterig
# SERVICE default remoterig
set -euo pipefail
ENV_FILE="${ENV_FILE:-/opt/remoterig/update.env}"
# shellcheck disable=SC1090
[ -f "$ENV_FILE" ] && . "$ENV_FILE"
GITEA_BASE="${GITEA_BASE:-https://code.cubecraftcreations.com}"
REPO="${REPO:-CubeCraft-Creations/remote-rig}"
DEPLOY_DIR="/opt/remoterig"
DEPLOY_PATH="${DEPLOY_PATH:-$DEPLOY_DIR/remoterig}"
SERVICE="${SERVICE:-remoterig}"
TAG="dev"
DL="$GITEA_BASE/$REPO/releases/download/$TAG"
VERSION_FILE="$DEPLOY_DIR/VERSION"
AUTH=()
[ -n "${GITEA_TOKEN:-}" ] && AUTH=(-H "Authorization: token $GITEA_TOKEN")
log() { echo "[$(date -Is)] $*"; }
# 1. What version is published?
REMOTE_VER="$(curl -fsSL "${AUTH[@]}" "$DL/version.txt" | tr -d '[:space:]')" || {
log "could not reach $DL/version.txt — skipping"; exit 0; }
[ -n "$REMOTE_VER" ] || { log "empty remote version — skipping"; exit 0; }
LOCAL_VER="$(cat "$VERSION_FILE" 2>/dev/null || echo none)"
if [ "$REMOTE_VER" = "$LOCAL_VER" ]; then
log "up to date ($LOCAL_VER)"; exit 0
fi
log "update available: $LOCAL_VER -> $REMOTE_VER"
# 2. Download + verify checksum
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
curl -fsSL "${AUTH[@]}" "$DL/remoterig" -o "$TMP/remoterig"
curl -fsSL "${AUTH[@]}" "$DL/remoterig.sha256" -o "$TMP/remoterig.sha256"
( cd "$TMP" && echo "$(cat remoterig.sha256) remoterig" | sha256sum -c - ) || {
log "checksum FAILED — aborting update"; exit 1; }
# 3. Deploy via the existing backup/restart/rollback logic
chmod +x "$TMP/remoterig"
"$DEPLOY_DIR/deploy.sh" "$TMP/remoterig" "$DEPLOY_PATH" "$SERVICE"
# 4. Record the installed version
echo "$REMOTE_VER" > "$VERSION_FILE"
log "updated to $REMOTE_VER"
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=RemoteRig pull updater (checks Gitea dev release)
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/opt/remoterig/pi-update.sh
# Updater needs root to write the binary and restart the service
User=root
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=Periodically check for RemoteRig updates (Gitea dev release)
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Persistent=true
[Install]
WantedBy=timers.target
+61 -12
View File
@@ -204,6 +204,54 @@ else
fi
fi
# ---------------------------------------------------------------------------
# 6b. Install pull updater (Pi polls the Gitea dev release and self-updates)
# ---------------------------------------------------------------------------
info "Installing pull updater..."
# deploy.sh + pi-update.sh live in the deploy dir (the updater calls them)
for f in deploy.sh pi-update.sh; do
if [ -f "${SCRIPT_DIR}/${f}" ]; then
cp "${SCRIPT_DIR}/${f}" "${DEPLOY_DIR}/${f}"
chmod +x "${DEPLOY_DIR}/${f}"
ok "Installed ${DEPLOY_DIR}/${f}"
else
warn "${SCRIPT_DIR}/${f} not found — skipping"
fi
done
# update.env template (don't clobber an existing one that may hold a token)
UPDATE_ENV="${DEPLOY_DIR}/update.env"
if [ -f "${UPDATE_ENV}" ]; then
skip "${UPDATE_ENV} already exists (not overwriting)"
else
cat > "${UPDATE_ENV}" <<'ENVEOF'
# RemoteRig updater config
GITEA_BASE=https://code.cubecraftcreations.com
REPO=CubeCraft-Creations/remote-rig
# Read token — required only if the repo is private:
GITEA_TOKEN=
ENVEOF
chmod 600 "${UPDATE_ENV}"
ok "Wrote ${UPDATE_ENV} (set GITEA_TOKEN if the repo is private)"
fi
# Updater service + timer
for unit in remoterig-update.service remoterig-update.timer; do
if [ -f "${SCRIPT_DIR}/${unit}" ]; then
cp "${SCRIPT_DIR}/${unit}" "/etc/systemd/system/${unit}"
ok "Installed ${unit}"
else
warn "${SCRIPT_DIR}/${unit} not found — skipping"
fi
done
systemctl daemon-reload
if systemctl enable --now remoterig-update.timer 2>/dev/null; then
ok "remoterig-update.timer enabled and started"
else
warn "Could not enable remoterig-update.timer"
fi
# ---------------------------------------------------------------------------
# 7. Set static IP on wlan0
# ---------------------------------------------------------------------------
@@ -317,20 +365,21 @@ echo " Setup complete!"
echo "=============================================="
echo " Mosquitto: $(systemctl is-active mosquitto 2>/dev/null || echo 'unknown')"
echo " Service: ${SERVICE_NAME} (systemctl status ${SERVICE_NAME})"
echo " Updater: remoterig-update.timer (systemctl status remoterig-update.timer)"
echo " Deploy dir: ${DEPLOY_DIR}"
echo " Static IP: ${STATIC_IP} on wlan0"
echo ""
echo " Next steps:"
echo " 1. Build the remoterig binary for ARM64:"
echo " GOOS=linux GOARCH=arm64 go build -o remoterig ./cmd/server"
echo " 2. Copy binary to Pi:"
echo " scp remoterig pi@192.168.8.56:/opt/remoterig/"
echo " 3. Copy config if needed:"
echo " scp config.yaml pi@192.168.8.56:/opt/remoterig/"
echo " 4. Start the service:"
echo " sudo systemctl start remoterig"
echo " 5. Check health:"
echo " curl http://192.168.8.56:8080/health"
echo " Deploys are pull-based: push to 'dev' on Gitea -> CI builds the"
echo " arm64 binary -> the Pi's timer pulls + installs it automatically."
echo ""
echo " To deploy updates, use: scripts/deploy.sh"
echo " Next steps:"
echo " 1. If the repo is private, set a read token:"
echo " sudo sed -i 's/^GITEA_TOKEN=.*/GITEA_TOKEN=<token>/' ${DEPLOY_DIR}/update.env"
echo " 2. Trigger / wait for an update check:"
echo " sudo systemctl start remoterig-update.service"
echo " journalctl -u remoterig-update.service -n 30"
echo " 3. Check health once deployed:"
echo " curl http://${STATIC_IP%/*}:8080/health"
echo ""
echo " Manual one-off deploy (local binary) still works: scripts/deploy.sh"
echo "=============================================="