scripts/update-from-gha.sh
Rama master · réplica de lectura del repositorio original.
#!/bin/bash
#
# Update a StartOS server from a GitHub Actions build, without building locally.
#
# This is `make start-os-update-squashfs` with the squashfs fetched from CI
# instead of results/: prune, upload to /media/startos/images/next.rootfs, and
# run the checksummed upgrade. The server uses the new image after its next reboot.
#
# Usage: scripts/update-from-gha.sh [options] <remote>
#
# <remote> ssh destination of the server, e.g. root@server-name.local
#
# Options:
# --run <id|url> GitHub Actions run to take the image from. Accepts a run
# id or a full run URL.
# --branch <name> Use the latest successful startos-iso run on this branch
# instead of --run. Default: master.
# --platform <name> Image platform (x86_64, aarch64, raspberrypi, ...).
# Default: whatever the server reports.
# --keep <dir> Download into <dir> and leave it in place. Default: a
# temp dir, removed on exit.
# --yes Skip the confirmation prompt.
# -h, --help Show this help.
#
# Environment variables:
# REPO GitHub repo to pull runs from (default: Start9Labs/start-technologies)
# SSHPASS ssh password, matching the Makefile's REMOTE/SSHPASS convention
set -euo pipefail
REPO="${REPO:-Start9Labs/start-technologies}"
WORKFLOW="startos-iso.yaml"
REMOTE=""
RUN_ID=""
BRANCH=""
PLATFORM=""
KEEP_DIR=""
ASSUME_YES=0
# Print the header comment block (from line 3 to the first non-comment line).
usage() {
awk 'NR > 2 { if (!/^#/) exit; sub(/^# ?/, ""); print }' "$0"
exit "${1:-0}"
}
while [ $# -gt 0 ]; do
case "$1" in
--run) RUN_ID="$2"; shift 2 ;;
--branch) BRANCH="$2"; shift 2 ;;
--platform) PLATFORM="$2"; shift 2 ;;
--keep) KEEP_DIR="$2"; shift 2 ;;
--yes | -y) ASSUME_YES=1; shift ;;
-h | --help) usage 0 ;;
-*) >&2 echo "Unknown option: $1"; usage 2 ;;
*)
if [ -n "$REMOTE" ]; then
>&2 echo "Unexpected argument: $1"
usage 2
fi
REMOTE="$1"; shift
;;
esac
done
if [ -z "$REMOTE" ]; then
>&2 echo "Must specify the server to update (e.g. root@server-name.local)"
usage 2
fi
for bin in gh b3sum; do
if ! command -v "$bin" > /dev/null; then
>&2 echo "Missing required tool: $bin"
exit 1
fi
done
if [ -n "${SSHPASS:-}" ]; then
if ! command -v sshpass > /dev/null; then
>&2 echo "SSHPASS is set but sshpass is not installed"
exit 1
fi
remote_sh() { sshpass -p "$SSHPASS" ssh "$REMOTE" "$@"; }
else
remote_sh() { ssh "$REMOTE" "$@"; }
fi
# --- Resolve the run -------------------------------------------------------
if [ -n "$RUN_ID" ]; then
# Accept a run URL as well as a bare id, matching manage-release.sh.
if [[ "$RUN_ID" =~ /actions/runs/([0-9]+) ]]; then
RUN_ID="${BASH_REMATCH[1]}"
fi
else
BRANCH="${BRANCH:-master}"
echo "Finding the latest successful $WORKFLOW run on $BRANCH..."
RUN_ID=$(gh run list -R "$REPO" -w "$WORKFLOW" -b "$BRANCH" -s success \
-L 1 --json databaseId -q '.[0].databaseId')
if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
>&2 echo "No successful $WORKFLOW run found on $BRANCH"
exit 1
fi
fi
read -r RUN_SHA RUN_BRANCH RUN_DATE < <(
gh run view -R "$REPO" "$RUN_ID" \
--json headSha,headBranch,createdAt \
-q '[.headSha, .headBranch, .createdAt] | @tsv'
)
# --- Resolve the platform --------------------------------------------------
if [ -z "$PLATFORM" ]; then
echo "Reading the platform from $REMOTE..."
PLATFORM=$(remote_sh 'cat /usr/lib/startos/PLATFORM.txt' | tr -d '[:space:]')
if [ -z "$PLATFORM" ]; then
>&2 echo "Could not read /usr/lib/startos/PLATFORM.txt — pass --platform"
exit 1
fi
fi
CURRENT_VERSION=$(remote_sh 'cat /usr/lib/startos/VERSION.txt' 2> /dev/null | tr -d '[:space:]' || true)
CURRENT_HASH=$(remote_sh 'cat /usr/lib/startos/GIT_HASH.txt' 2> /dev/null | tr -d '[:space:]' || true)
cat << EOF
Server: $REMOTE
Installed: ${CURRENT_VERSION:-unknown} (${CURRENT_HASH:-unknown})
Platform: $PLATFORM
Run: $RUN_ID — $RUN_BRANCH @ ${RUN_SHA:0:7}, $RUN_DATE
Repo: $REPO
This stages the server's OS image for its next reboot.
EOF
if [ "$ASSUME_YES" -ne 1 ]; then
read -rp "Continue? [y/N] " reply
case "$reply" in
y | Y | yes | YES) ;;
*) echo "Aborted."; exit 1 ;;
esac
fi
# --- Fetch the image -------------------------------------------------------
if [ -n "$KEEP_DIR" ]; then
mkdir -p "$KEEP_DIR"
DOWNLOAD_DIR="$KEEP_DIR"
else
DOWNLOAD_DIR="$(mktemp -d)"
trap 'rm -rf "$DOWNLOAD_DIR"' EXIT
fi
echo "Downloading ${PLATFORM}.squashfs from run $RUN_ID..."
gh run download -R "$REPO" "$RUN_ID" -n "${PLATFORM}.squashfs" -D "$DOWNLOAD_DIR"
SQUASHFS=$(find "$DOWNLOAD_DIR" -name '*.squashfs' -type f | head -n1)
if [ -z "$SQUASHFS" ]; then
>&2 echo "Run $RUN_ID has no ${PLATFORM}.squashfs artifact (expired, or that platform was not built)"
exit 1
fi
# The upgrade script compares against the first half of the blake3 hash, the
# same truncation start-os-update-squashfs passes.
SQFS_SUM=$(b3sum --no-names "$SQUASHFS" | head -c 32)
SQFS_SIZE=$(stat -c %s "$SQUASHFS")
echo "Image: $(basename "$SQUASHFS") (${SQFS_SIZE} bytes, blake3 ${SQFS_SUM})"
# --- Install ---------------------------------------------------------------
echo "Pruning old images to make room..."
remote_sh "sudo /usr/lib/startos/scripts/prune-images $SQFS_SIZE"
remote_sh 'sudo /usr/lib/startos/scripts/prune-boot'
echo "Uploading the image..."
# Streamed through tee rather than scp'd: /media/startos/images is root-owned,
# and this needs no writable staging area on a server that was just pruned for
# exactly this file's worth of space.
remote_sh 'sudo tee /media/startos/images/next.rootfs > /dev/null' < "$SQUASHFS"
echo "Installing the incoming updater..."
remote_sh 'sudo unsquashfs -n -f -d / /media/startos/images/next.rootfs /usr/lib/startos/scripts/upgrade'
echo "Upgrading..."
# The checksum is passed twice on purpose. Servers running an older image carry
# an `upgrade` that only compares when a second positional argument is present,
# so CHECKSUM alone would be silently ignored there; the current script keys off
# CHECKSUM and ignores $2. Passing both verifies on either.
remote_sh "sudo CHECKSUM=$SQFS_SUM /usr/lib/startos/scripts/upgrade /media/startos/images/next.rootfs $SQFS_SUM"
echo
echo "Done — $REMOTE will run ${RUN_SHA:0:7} after it reboots."