From 9d4c4a1f7341f1ecbf663cf2a03c3434fc6ee314 Mon Sep 17 00:00:00 2001 From: csh Date: Thu, 7 May 2026 12:08:46 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E=20act=5Frunner?= =?UTF-8?q?=20=E8=87=AA=E5=8A=A8=E5=8D=87=E7=BA=A7=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=B9=B6=E5=90=8C=E6=AD=A5=E6=9C=80=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=8E=A2=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEPLOYMENT.md | 28 +- docker-runner/common/check_crlf.sh | 1 + docker-runner/common/setup.sh | 29 +- docker-runner/common/upgrade.sh | 284 ++++++++++++++++++ .../buildx-archlinux/docker-compose.yml | 1 + .../buildx-ubuntu-22/docker-compose.yml | 1 + .../standard-ubuntu-22/docker-compose.yml | 1 + tests/upgrade_test.sh | 87 ++++++ 8 files changed, 425 insertions(+), 7 deletions(-) create mode 100644 docker-runner/common/upgrade.sh create mode 100644 tests/upgrade_test.sh diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index d77de1a..3e72174 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -29,6 +29,7 @@ docker-runner/ │ ├── check_crlf.sh # Windows 换行符检查工具 │ ├── entrypoint.sh # 容器启动脚本 │ ├── setup.sh # Runner 安装脚本 +│ ├── upgrade.sh # Runner 升级脚本 │ ├── register.sh # Runner 注册脚本 │ └── manage.sh # Runner 管理脚本 │ @@ -128,7 +129,7 @@ docker-compose up -d docker-compose exec gitea-runner /data/setup.sh ``` -按照提示选择版本(默认 0.2.13)和架构(自动检测)。 +脚本会自动探测最新 `act_runner` 版本作为默认值,然后提示你确认架构。 #### 5. 注册 Runner @@ -250,6 +251,21 @@ docker-compose exec gitea-runner /data/manage.sh restart docker-compose exec gitea-runner /data/manage.sh delete ``` +### 升级 act_runner + +```bash +# 自动检查最新稳定版本,下载后重启所有已注册 runners +docker-compose exec gitea-runner /data/upgrade.sh + +# 指定版本 +docker-compose exec gitea-runner /data/upgrade.sh --version 0.6.1 + +# 仅重启指定 runner +docker-compose exec gitea-runner /data/upgrade.sh --runner +``` + +`upgrade.sh` 只替换 `/data/bin/act_runner` 并重启 runner 进程,不会重新注册,也不需要重新输入 token。 + ### 添加多个 Runners ```bash @@ -389,8 +405,14 @@ CMD ["/app"] ### Q1: 如何更新 Runner 版本? ```bash -docker-compose exec gitea-runner /data/setup.sh -# 脚本会检测到已安装并询问是否升级 +docker-compose exec gitea-runner /data/upgrade.sh +# 脚本会自动从官方目录页检测最新 act_runner 版本 +``` + +如需固定版本: + +```bash +docker-compose exec gitea-runner /data/upgrade.sh --version 0.6.1 ``` ### Q2: 如何添加多个 Runners? diff --git a/docker-runner/common/check_crlf.sh b/docker-runner/common/check_crlf.sh index c5f91c8..1a2272f 100644 --- a/docker-runner/common/check_crlf.sh +++ b/docker-runner/common/check_crlf.sh @@ -15,6 +15,7 @@ NC='\033[0m' SCRIPT_FILES=( "entrypoint.sh" "setup.sh" + "upgrade.sh" "register.sh" "manage.sh" ) diff --git a/docker-runner/common/setup.sh b/docker-runner/common/setup.sh index e2180fc..a742734 100644 --- a/docker-runner/common/setup.sh +++ b/docker-runner/common/setup.sh @@ -1,5 +1,15 @@ #!/bin/bash +# 共享升级脚本中的版本探测逻辑 +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +UPGRADE_HELPER="${SCRIPT_DIR}/upgrade.sh" +DEFAULT_RUNNER_VERSION_FALLBACK="${DEFAULT_RUNNER_VERSION_FALLBACK:-0.2.13}" + +if [ -f "${UPGRADE_HELPER}" ]; then + # shellcheck source=/dev/null + source "${UPGRADE_HELPER}" +fi + echo "==========================================" echo " Gitea Runner Installation Script " echo "==========================================" @@ -30,8 +40,20 @@ fi # 获取要安装的版本 echo "Available versions: https://dl.gitea.com/act_runner/" echo "" -read -p "Enter version to install (default: 0.2.13): " RUNNER_VERSION -RUNNER_VERSION=${RUNNER_VERSION:-0.2.13} + +DEFAULT_RUNNER_VERSION="${DEFAULT_RUNNER_VERSION_FALLBACK}" +if declare -F resolve_latest_version_or_fallback >/dev/null 2>&1; then + DEFAULT_RUNNER_VERSION=$(resolve_latest_version_or_fallback "${DEFAULT_RUNNER_VERSION_FALLBACK}") +fi + +echo "Default install version: ${DEFAULT_RUNNER_VERSION}" +read -p "Enter version to install (default: ${DEFAULT_RUNNER_VERSION}): " RUNNER_VERSION +RUNNER_VERSION=${RUNNER_VERSION:-$DEFAULT_RUNNER_VERSION} + +if declare -F validate_version >/dev/null 2>&1 && ! validate_version "${RUNNER_VERSION}"; then + echo "✗ Invalid version format: ${RUNNER_VERSION}" + exit 1 +fi ARCH=$(uname -m) case "$ARCH" in @@ -114,8 +136,7 @@ if curl -L "$DOWNLOAD_URL" -o "$INSTALL_PATH"; then echo "1. Register the runner:" echo " docker-compose exec gitea-runner /data/register.sh" echo "" - echo "2. Restart the container:" - echo " docker-compose restart" + echo "2. Start using the runner after registration." echo "" echo "Note: act_runner is saved in persistent storage" echo " and will survive container restarts." diff --git a/docker-runner/common/upgrade.sh b/docker-runner/common/upgrade.sh new file mode 100644 index 0000000..ae7f4f7 --- /dev/null +++ b/docker-runner/common/upgrade.sh @@ -0,0 +1,284 @@ +#!/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 diff --git a/docker-runner/presets/buildx-archlinux/docker-compose.yml b/docker-runner/presets/buildx-archlinux/docker-compose.yml index bfb92bc..e1bb553 100644 --- a/docker-runner/presets/buildx-archlinux/docker-compose.yml +++ b/docker-runner/presets/buildx-archlinux/docker-compose.yml @@ -8,6 +8,7 @@ services: volumes: - ./runner-data:/data - ../../common/setup.sh:/data/setup.sh:ro + - ../../common/upgrade.sh:/data/upgrade.sh:ro - ../../common/register.sh:/data/register.sh:ro - ../../common/manage.sh:/data/manage.sh:ro - ../../common/check_crlf.sh:/data/check_crlf.sh:ro diff --git a/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml b/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml index d8aae51..5fd12a4 100644 --- a/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml +++ b/docker-runner/presets/buildx-ubuntu-22/docker-compose.yml @@ -8,6 +8,7 @@ services: volumes: - ./runner-data:/data - ../../common/setup.sh:/data/setup.sh:ro + - ../../common/upgrade.sh:/data/upgrade.sh:ro - ../../common/register.sh:/data/register.sh:ro - ../../common/manage.sh:/data/manage.sh:ro - ../../common/check_crlf.sh:/data/check_crlf.sh:ro diff --git a/docker-runner/presets/standard-ubuntu-22/docker-compose.yml b/docker-runner/presets/standard-ubuntu-22/docker-compose.yml index e562c8e..7fefb47 100644 --- a/docker-runner/presets/standard-ubuntu-22/docker-compose.yml +++ b/docker-runner/presets/standard-ubuntu-22/docker-compose.yml @@ -7,6 +7,7 @@ services: volumes: - ./runner-data:/data - ../../common/setup.sh:/data/setup.sh:ro + - ../../common/upgrade.sh:/data/upgrade.sh:ro - ../../common/register.sh:/data/register.sh:ro - ../../common/manage.sh:/data/manage.sh:ro - ../../common/entrypoint.sh:/data/entrypoint.sh:ro diff --git a/tests/upgrade_test.sh b/tests/upgrade_test.sh new file mode 100644 index 0000000..57eeb75 --- /dev/null +++ b/tests/upgrade_test.sh @@ -0,0 +1,87 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +REPO_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd) + +# shellcheck source=/dev/null +source "${REPO_ROOT}/docker-runner/common/upgrade.sh" + +assert_eq() { + local expected=$1 + local actual=$2 + local message=$3 + + if [ "${expected}" != "${actual}" ]; then + echo "FAIL: ${message}" >&2 + echo " expected: ${expected}" >&2 + echo " actual: ${actual}" >&2 + exit 1 + fi +} + +test_extract_versions_from_listing() { + local html + html=$(cat <<'EOF' + + 0.6.1 + + + 0.5.0 + + + 0.4.1 + +EOF +) + + local actual + actual=$(extract_versions_from_listing <<<"${html}") + + assert_eq $'0.6.1\n0.5.0\n0.4.1' "${actual}" \ + "extract_versions_from_listing should return every version in listing order" +} + +test_pick_latest_version() { + local actual + actual=$(pick_latest_version <<'EOF' +0.5.0 +0.6.1 +0.4.9 +EOF +) + + assert_eq "0.6.1" "${actual}" \ + "pick_latest_version should return the highest semantic version" +} + +fake_latest_version() { + echo "0.6.2" +} + +fake_invalid_version() { + echo "latest" +} + +test_resolve_latest_version_or_fallback_prefers_latest() { + local actual + actual=$(resolve_latest_version_or_fallback "0.2.13" fake_latest_version) + + assert_eq "0.6.2" "${actual}" \ + "resolve_latest_version_or_fallback should prefer the detected latest version" +} + +test_resolve_latest_version_or_fallback_uses_fallback() { + local actual + actual=$(resolve_latest_version_or_fallback "0.2.13" fake_invalid_version) + + assert_eq "0.2.13" "${actual}" \ + "resolve_latest_version_or_fallback should fall back when detected version is invalid" +} + +test_extract_versions_from_listing +test_pick_latest_version +test_resolve_latest_version_or_fallback_prefers_latest +test_resolve_latest_version_or_fallback_uses_fallback + +echo "upgrade_test.sh: PASS"