From ae370382d384cd96e8ebc55e38288dca9566bc3a Mon Sep 17 00:00:00 2001 From: sagit <36596628+Sagit-chu@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:02:52 +0800 Subject: [PATCH] feat(agent): support Alpine installation (#534) Add Alpine bootstrap and OpenRC lifecycle support to the agent installer. Closes #527 --- doc/install.md | 11 +- install.sh | 287 +++++++++++++++++++++++++++++----- test-install-scripts-proxy.sh | 121 +++++++++++++- 3 files changed, 377 insertions(+), 42 deletions(-) diff --git a/doc/install.md b/doc/install.md index 3067103..a1101d2 100644 --- a/doc/install.md +++ b/doc/install.md @@ -64,6 +64,14 @@ curl -L https://raw.githubusercontent.com/Sagit-chu/flux-panel/main/panel_instal curl -L https://raw.githubusercontent.com/Sagit-chu/flux-panel/main/install.sh -o install.sh && chmod +x install.sh && ./install.sh ``` +Alpine Linux 最小化安装若未包含 `curl`,可使用系统自带的 `wget` 下载: + +```bash +wget -O install.sh https://raw.githubusercontent.com/Sagit-chu/flux-panel/main/install.sh && chmod +x install.sh && ./install.sh +``` + +脚本会在 Alpine 上自动安装 Bash,并使用 OpenRC 注册、启动和管理 `flux_agent` 服务;其他受支持的 Linux 发行版继续使用 systemd。 + **安装过程中会提示输入:** - **服务器地址**: 面板端的通信地址(通常是 `http://<面板IP>:<后端端口>`,例如 `http://1.2.3.4:6365`)。 - **密钥**: 刚才在面板中获取的节点密钥。 @@ -77,7 +85,8 @@ curl -L https://raw.githubusercontent.com/Sagit-chu/flux-panel/main/install.sh - ### 3. 验证安装 安装完成后,服务会自动启动。 -- 查看状态: `systemctl status flux_agent` +- systemd 查看状态: `systemctl status flux_agent` +- Alpine/OpenRC 查看状态: `rc-service flux_agent status` - 回到面板 **节点管理** 页面,该节点状态应显示为 **在线**。 --- diff --git a/install.sh b/install.sh index 7304159..839b4bf 100644 --- a/install.sh +++ b/install.sh @@ -1,4 +1,31 @@ -#!/bin/bash +#!/bin/sh +# shellcheck shell=bash + +# Alpine 默认不带 Bash。先用系统自带的 /bin/sh 安装/切换到 Bash, +# 后续主体继续使用 Bash 语法,避免要求用户手动准备运行环境。 +if [ -z "${BASH_VERSION:-}" ]; then + if command -v bash >/dev/null 2>&1; then + exec bash "$0" "$@" + fi + + if [ -f /etc/alpine-release ] && command -v apk >/dev/null 2>&1; then + if [ "$(id -u)" -eq 0 ]; then + apk add --no-cache bash + elif command -v sudo >/dev/null 2>&1; then + sudo apk add --no-cache bash + elif command -v doas >/dev/null 2>&1; then + doas apk add --no-cache bash + else + echo "❌ Alpine 安装需要 root 权限,或已配置 sudo/doas。" >&2 + exit 1 + fi + + exec bash "$0" "$@" + fi + + echo "❌ 此安装脚本需要 Bash。" >&2 + exit 1 +fi # GitHub repo used for release downloads REPO="Sagit-chu/flux-panel" @@ -24,11 +51,14 @@ get_architecture() { # 安装目录 INSTALL_DIR="/etc/flux_agent" +FLUX_AGENT_SYSTEMD_SERVICE_FILE="/etc/systemd/system/flux_agent.service" +FLUX_AGENT_OPENRC_SERVICE_FILE="/etc/init.d/flux_agent" LEGACY_GOST_BINARY="/usr/local/bin/gost" LEGACY_GOST_CONFIG_DIR="/etc/gost" LEGACY_GOST_SERVICE_FILE_ETC="/etc/systemd/system/gost.service" LEGACY_GOST_SERVICE_FILE_LIB="/lib/systemd/system/gost.service" LEGACY_GOST_SERVICE_FILE_USR_LIB="/usr/lib/systemd/system/gost.service" +SERVICE_MANAGER="${SERVICE_MANAGER:-}" # 镜像加速配置(可由面板传入或交互式询问) PROXY_ENABLED="${PROXY_ENABLED:-}" @@ -256,6 +286,199 @@ write_flux_agent_config() { "$(json_escape "$SECRET")" > "$path" } +ensure_service_manager() { + if [[ -n "$SERVICE_MANAGER" ]]; then + case "$SERVICE_MANAGER" in + systemd|openrc) + return 0 + ;; + *) + echo "❌ 不支持的服务管理器: $SERVICE_MANAGER" >&2 + return 1 + ;; + esac + fi + + if command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then + SERVICE_MANAGER="systemd" + return 0 + fi + if command -v rc-service >/dev/null 2>&1 && command -v rc-update >/dev/null 2>&1; then + SERVICE_MANAGER="openrc" + return 0 + fi + + echo "❌ 未检测到受支持的服务管理器(systemd 或 OpenRC)。" >&2 + return 1 +} + +flux_agent_service_exists() { + ensure_service_manager || return 1 + + case "$SERVICE_MANAGER" in + systemd) + [[ -f "$FLUX_AGENT_SYSTEMD_SERVICE_FILE" ]] || \ + systemctl list-units --full -all 2>/dev/null | grep -Fq "flux_agent.service" + ;; + openrc) + [[ -f "$FLUX_AGENT_OPENRC_SERVICE_FILE" ]] + ;; + esac +} + +stop_flux_agent_service() { + ensure_service_manager || return 1 + + case "$SERVICE_MANAGER" in + systemd) + systemctl stop flux_agent 2>/dev/null || true + ;; + openrc) + rc-service flux_agent stop 2>/dev/null || true + ;; + esac +} + +disable_flux_agent_service() { + ensure_service_manager || return 1 + + case "$SERVICE_MANAGER" in + systemd) + systemctl disable flux_agent 2>/dev/null || true + ;; + openrc) + rc-update del flux_agent default 2>/dev/null || true + ;; + esac +} + +write_flux_agent_service() { + ensure_service_manager || return 1 + + case "$SERVICE_MANAGER" in + systemd) + mkdir -p "$(dirname "$FLUX_AGENT_SYSTEMD_SERVICE_FILE")" + cat > "$FLUX_AGENT_SYSTEMD_SERVICE_FILE" < "$FLUX_AGENT_OPENRC_SERVICE_FILE" </dev/null 2>&1 + ;; + esac +} + +flux_agent_service_status() { + ensure_service_manager || return 1 + + case "$SERVICE_MANAGER" in + systemd) + systemctl is-active flux_agent 2>/dev/null || true + ;; + openrc) + rc-service flux_agent status 2>/dev/null || true + ;; + esac +} + +remove_flux_agent_service() { + ensure_service_manager || return 1 + + case "$SERVICE_MANAGER" in + systemd) + rm -f "$FLUX_AGENT_SYSTEMD_SERVICE_FILE" + systemctl daemon-reload 2>/dev/null || true + ;; + openrc) + rm -f "$FLUX_AGENT_OPENRC_SERVICE_FILE" + ;; + esac +} + +flux_agent_service_status_hint() { + ensure_service_manager || return 1 + + case "$SERVICE_MANAGER" in + systemd) + echo "systemctl status flux_agent --no-pager" + ;; + openrc) + echo "rc-service flux_agent status" + ;; + esac +} + cleanup_legacy_gost_installation() { local matched_service_files=() local service_file="" @@ -341,19 +564,21 @@ install_flux_agent() { get_config_params - # 检查并安装 tcpkill + # 检查并安装 tcpkill check_and_install_tcpkill - + ensure_service_manager || exit 1 mkdir -p "$INSTALL_DIR" local tmp_binary="$INSTALL_DIR/flux_agent.new" # 停止并禁用已有服务 - if systemctl list-units --full -all | grep -Fq "flux_agent.service"; then + if flux_agent_service_exists; then echo "🔍 检测到已存在的flux_agent服务" - systemctl stop flux_agent 2>/dev/null && echo "🛑 停止服务" - systemctl disable flux_agent 2>/dev/null && echo "🚫 禁用自启" + stop_flux_agent_service + echo "🛑 停止服务" + disable_flux_agent_service + echo "🚫 禁用自启" fi # 下载 flux_agent @@ -392,38 +617,21 @@ EOF # 加强权限 chmod 600 "$INSTALL_DIR"/*.json - # 创建 systemd 服务 - SERVICE_FILE="/etc/systemd/system/flux_agent.service" - cat > "$SERVICE_FILE" </dev/null - systemctl disable flux_agent 2>/dev/null + stop_flux_agent_service + disable_flux_agent_service fi # 删除服务文件 - if [[ -f "/etc/systemd/system/flux_agent.service" ]]; then - rm -f "/etc/systemd/system/flux_agent.service" + if [[ -f "$FLUX_AGENT_SYSTEMD_SERVICE_FILE" || -f "$FLUX_AGENT_OPENRC_SERVICE_FILE" ]]; then + remove_flux_agent_service echo "🧹 删除服务文件" fi @@ -503,9 +713,6 @@ uninstall_flux_agent() { echo "🧹 删除安装目录: $INSTALL_DIR" fi - # 重载 systemd - systemctl daemon-reload - echo "✅ 卸载完成" } diff --git a/test-install-scripts-proxy.sh b/test-install-scripts-proxy.sh index 5d49a0b..efd724a 100644 --- a/test-install-scripts-proxy.sh +++ b/test-install-scripts-proxy.sh @@ -90,6 +90,7 @@ test_update_flux_agent_asks_for_proxy_config() ( set -euo pipefail load_script_without_main "$ROOT_DIR/install.sh" + SERVICE_MANAGER="systemd" INSTALL_DIR=$(mktemp -d) cat > "$INSTALL_DIR/flux_agent" <<'EOF' #!/bin/bash @@ -165,6 +166,7 @@ test_install_flux_agent_preserves_legacy_gost_when_download_fails() ( set -euo pipefail load_script_without_main "$ROOT_DIR/install.sh" + SERVICE_MANAGER="systemd" INSTALL_DIR=$(mktemp -d) cat > "$INSTALL_DIR/flux_agent" <<'EOF' #!/bin/bash @@ -199,6 +201,7 @@ test_update_flux_agent_preserves_legacy_gost_when_download_fails() ( set -euo pipefail load_script_without_main "$ROOT_DIR/install.sh" + SERVICE_MANAGER="systemd" INSTALL_DIR=$(mktemp -d) cat > "$INSTALL_DIR/flux_agent" <<'EOF' #!/bin/bash @@ -236,7 +239,9 @@ test_install_flux_agent_writes_json_safe_config() ( set -euo pipefail load_script_without_main "$ROOT_DIR/install.sh" + SERVICE_MANAGER="systemd" INSTALL_DIR=$(mktemp -d) + FLUX_AGENT_SYSTEMD_SERVICE_FILE="$INSTALL_DIR/flux_agent.service" SERVER_ADDR='panel"addr' SECRET='sec\ret"1' DOWNLOAD_URL="https://example.com/gost" @@ -274,6 +279,117 @@ EOF assert_equals "$expected" "$actual" "install_flux_agent should JSON-escape config values" ) +test_install_script_bootstraps_bash_for_alpine() ( + set -euo pipefail + + local shebang + shebang=$(head -n 1 "$ROOT_DIR/install.sh") + assert_equals "#!/bin/sh" "$shebang" "install.sh should start with Alpine's default shell" + + grep -Fq 'apk add --no-cache bash' "$ROOT_DIR/install.sh" || \ + fail "install.sh should bootstrap Bash through apk on Alpine" +) + +test_install_flux_agent_uses_openrc() ( + set -euo pipefail + load_script_without_main "$ROOT_DIR/install.sh" + + local temp_root + temp_root=$(mktemp -d) + INSTALL_DIR="$temp_root/flux_agent" + FLUX_AGENT_OPENRC_SERVICE_FILE="$temp_root/init.d/flux_agent" + SERVICE_MANAGER="openrc" + SERVER_ADDR="panel.example.com:443" + SECRET="secret" + DOWNLOAD_URL="https://example.com/gost" + + local rc_service_calls="" + local rc_update_calls="" + + ask_proxy_config() { :; } + ensure_download_url_initialized() { :; } + get_config_params() { :; } + check_and_install_tcpkill() { :; } + cleanup_legacy_gost_installation() { :; } + curl() { + local output="" + while [[ $# -gt 0 ]]; do + if [[ "$1" == "-o" ]]; then + output="$2" + shift 2 + continue + fi + shift + done + + cat > "$output" <<'EOF' +#!/bin/sh +echo "new version" +EOF + chmod +x "$output" + } + rc-service() { + rc_service_calls+=$'\n'"$*" + if [[ "$2" == "status" ]]; then + echo "status: started" + fi + return 0 + } + rc-update() { + rc_update_calls+=$'\n'"$*" + return 0 + } + + install_flux_agent >/dev/null + + [[ -x "$FLUX_AGENT_OPENRC_SERVICE_FILE" ]] || fail "OpenRC service file should be executable" + grep -Fq '#!/sbin/openrc-run' "$FLUX_AGENT_OPENRC_SERVICE_FILE" || \ + fail "OpenRC service should use openrc-run" + grep -Fq "command=\"$INSTALL_DIR/flux_agent\"" "$FLUX_AGENT_OPENRC_SERVICE_FILE" || \ + fail "OpenRC service should launch the installed flux_agent binary" + grep -Fq 'command_background="yes"' "$FLUX_AGENT_OPENRC_SERVICE_FILE" || \ + fail "OpenRC service should run flux_agent in the background" + if command -v openrc-run >/dev/null 2>&1; then + "$FLUX_AGENT_OPENRC_SERVICE_FILE" describe >/dev/null 2>&1 + fi + [[ "$rc_update_calls" == *"add flux_agent default"* ]] || \ + fail "OpenRC install should enable flux_agent in the default runlevel" + [[ "$rc_service_calls" == *"start"* ]] || fail "OpenRC install should start flux_agent" + [[ "$rc_service_calls" == *"status"* ]] || fail "OpenRC install should verify flux_agent status" +) + +test_remove_flux_agent_service_uses_openrc() ( + set -euo pipefail + load_script_without_main "$ROOT_DIR/install.sh" + + local temp_root + temp_root=$(mktemp -d) + SERVICE_MANAGER="openrc" + FLUX_AGENT_OPENRC_SERVICE_FILE="$temp_root/init.d/flux_agent" + mkdir -p "$(dirname "$FLUX_AGENT_OPENRC_SERVICE_FILE")" + : > "$FLUX_AGENT_OPENRC_SERVICE_FILE" + + local rc_service_calls="" + local rc_update_calls="" + rc-service() { + rc_service_calls+=$'\n'"$*" + return 0 + } + rc-update() { + rc_update_calls+=$'\n'"$*" + return 0 + } + + stop_flux_agent_service + disable_flux_agent_service + remove_flux_agent_service + + [[ "$rc_service_calls" == *"stop"* ]] || fail "OpenRC uninstall should stop flux_agent" + [[ "$rc_update_calls" == *"del flux_agent default"* ]] || \ + fail "OpenRC uninstall should remove flux_agent from the default runlevel" + [[ ! -e "$FLUX_AGENT_OPENRC_SERVICE_FILE" ]] || fail "OpenRC uninstall should remove its service file" +) + test_cleanup_legacy_gost_installation_removes_service_and_binary() ( set -euo pipefail load_script_without_main "$ROOT_DIR/install.sh" @@ -504,6 +620,9 @@ test_update_flux_agent_skips_proxy_prompt_when_not_installed test_install_flux_agent_preserves_legacy_gost_when_download_fails test_update_flux_agent_preserves_legacy_gost_when_download_fails test_install_flux_agent_writes_json_safe_config +test_install_script_bootstraps_bash_for_alpine +test_install_flux_agent_uses_openrc +test_remove_flux_agent_service_uses_openrc test_cleanup_legacy_gost_installation_removes_service_and_binary test_cleanup_legacy_gost_installation_preserves_unrelated_gost test_install_script_accepts_proxy_url_env_without_prompt @@ -514,4 +633,4 @@ test_panel_install_script_uses_default_proxy test_panel_install_script_accepts_proxy_url_env_without_prompt test_panel_install_script_defaults_proxy_on_eof -echo "install script proxy tests passed" +echo "install script tests passed"