#!/bin/bash INSTALL_PATH="/data/bin/act_runner" SYSTEM_PATH="/usr/local/bin/act_runner" RUNNER_INDEX_URL="${RUNNER_INDEX_URL:-https://dl.gitea.com/act_runner/}" print_usage() { cat <<'EOF' Usage: /data/upgrade.sh [options] Upgrade act_runner without re-registering existing runners. Options: --version Upgrade to a specific version instead of auto-detecting latest --arch Override detected architecture: amd64, arm64, arm-7 --runner Restart only the specified runner (can be used multiple times) -y, --yes Skip confirmation prompt -h, --help Show this help message EOF } log() { echo "$@" } fail() { echo "✗ $*" >&2 exit 1 } extract_version_number() { sed -nE 's/.*version[[:space:]]+([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -n 1 } extract_versions_from_listing() { grep -oE '/act_runner/[0-9]+\.[0-9]+\.[0-9]+/' | \ sed -E 's#^/act_runner/([0-9]+\.[0-9]+\.[0-9]+)/$#\1#' | \ awk '!seen[$0]++' } pick_latest_version() { awk 'NF' | sort -V | tail -n 1 } fetch_latest_version() { curl -fsSL "${RUNNER_INDEX_URL}" | extract_versions_from_listing | pick_latest_version } resolve_latest_version_or_fallback() { local fallback_version=$1 local fetcher_cmd=${2:-fetch_latest_version} local detected_version="" if command -v "${fetcher_cmd}" >/dev/null 2>&1; then detected_version=$("${fetcher_cmd}" 2>/dev/null || true) fi if [ -n "${detected_version}" ] && validate_version "${detected_version}"; then echo "${detected_version}" else echo "${fallback_version}" fi } validate_version() { local version=$1 [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] } detect_runner_arch() { local arch arch=$(uname -m) case "${arch}" in x86_64) echo "amd64" ;; aarch64|arm64) echo "arm64" ;; armv7l) echo "arm-7" ;; *) fail "Unsupported architecture: ${arch}" ;; esac } get_installed_version() { local output="" if [ -x "${INSTALL_PATH}" ]; then output=$("${INSTALL_PATH}" --version 2>/dev/null || true) elif command -v act_runner >/dev/null 2>&1; then output=$(act_runner --version 2>/dev/null || true) fi if [ -n "${output}" ]; then extract_version_number <<<"${output}" fi } list_registered_runners() { local runner_dir if [ ! -d "/data/runners" ]; then return 0 fi for runner_dir in /data/runners/*/; do if [ -d "${runner_dir}" ] && [ -f "${runner_dir}/.runner" ]; then basename "${runner_dir}" fi done } build_download_url() { local version=$1 local arch=$2 echo "https://dl.gitea.com/act_runner/${version}/act_runner-${version}-linux-${arch}" } confirm_or_exit() { local auto_confirm=$1 if [ "${auto_confirm}" = "true" ]; then return 0 fi read -p "Proceed with upgrade? (y/N): " -n 1 -r echo if [[ ! "${REPLY}" =~ ^[Yy]$ ]]; then echo "Upgrade cancelled." exit 0 fi } download_and_install() { local version=$1 local arch=$2 local url local tmp_path local downloaded_version mkdir -p "$(dirname "${INSTALL_PATH}")" url=$(build_download_url "${version}" "${arch}") tmp_path=$(mktemp "${INSTALL_PATH}.tmp.XXXXXX") trap 'rm -f "${tmp_path}"' EXIT log "Downloading ${url}" curl -fL "${url}" -o "${tmp_path}" chmod +x "${tmp_path}" downloaded_version=$("${tmp_path}" --version 2>/dev/null | extract_version_number) if [ "${downloaded_version}" != "${version}" ]; then fail "Downloaded binary version mismatch: expected ${version}, got ${downloaded_version:-unknown}" fi mv "${tmp_path}" "${INSTALL_PATH}" ln -sf "${INSTALL_PATH}" "${SYSTEM_PATH}" trap - EXIT } restart_runner_process() { local runner_name=$1 if ! command -v supervisorctl >/dev/null 2>&1; then log "⚠ supervisorctl not found, please restart the container manually." return 1 fi log "Restarting runner '${runner_name}'..." supervisorctl restart "runner-${runner_name}" } restart_selected_runners() { local requested_runners=("$@") local runner_names=() local runner_name if [ "${#requested_runners[@]}" -gt 0 ]; then runner_names=("${requested_runners[@]}") else while IFS= read -r runner_name; do [ -n "${runner_name}" ] && runner_names+=("${runner_name}") done < <(list_registered_runners) fi if [ "${#runner_names[@]}" -eq 0 ]; then log "No registered runners found. Binary upgraded, no process restart needed." return 0 fi for runner_name in "${runner_names[@]}"; do restart_runner_process "${runner_name}" done } main() { local target_version="" local target_arch="" local auto_confirm="false" local current_version="" local runner_names=() while [ $# -gt 0 ]; do case "$1" in --version) [ $# -ge 2 ] || fail "--version requires a value" target_version=$2 shift 2 ;; --arch) [ $# -ge 2 ] || fail "--arch requires a value" target_arch=$2 shift 2 ;; --runner) [ $# -ge 2 ] || fail "--runner requires a value" runner_names+=("$2") shift 2 ;; -y|--yes) auto_confirm="true" shift ;; -h|--help) print_usage exit 0 ;; *) fail "Unknown option: $1" ;; esac done current_version=$(get_installed_version || true) if [ -z "${target_version}" ]; then log "Fetching latest act_runner version from ${RUNNER_INDEX_URL}" target_version=$(fetch_latest_version) [ -n "${target_version}" ] || fail "Unable to detect the latest act_runner version" fi validate_version "${target_version}" || fail "Invalid version: ${target_version}" if [ -z "${target_arch}" ]; then target_arch=$(detect_runner_arch) fi if [ -n "${current_version}" ] && [ "${current_version}" = "${target_version}" ]; then log "act_runner is already at the latest version: ${current_version}" exit 0 fi log "Upgrade summary:" log " Current version: ${current_version:-not installed}" log " Target version: ${target_version}" log " Architecture: ${target_arch}" log " Install path: ${INSTALL_PATH}" if [ "${#runner_names[@]}" -gt 0 ]; then log " Restart target: ${runner_names[*]}" else log " Restart target: all registered runners" fi echo confirm_or_exit "${auto_confirm}" download_and_install "${target_version}" "${target_arch}" restart_selected_runners "${runner_names[@]}" log log "✓ act_runner upgraded successfully" log " Installed version: $(get_installed_version || echo unknown)" } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then set -euo pipefail main "$@" fi