#!/bin/sh # BackupX Agent 一键安装脚本(由 Master 动态渲染) # Magic: BACKUPX_AGENT_INSTALL_V1 —— 若 `head -3 脚本` 看不到此行,说明反向代理或 CDN 改写了响应 # 模式: {{.Mode}} | 架构: {{.Arch}} | 版本: {{.AgentVersion}} set -eu umask 077 # 文件执行模式下优先使用 bash;管道模式继续使用 POSIX sh。 if [ -z "${BASH_VERSION:-}" ] && command -v bash >/dev/null 2>&1 && [ -f "$0" ]; then exec bash "$0" "$@" fi MASTER_URL="{{.MasterURL}}" AGENT_TOKEN="{{.AgentToken}}" AGENT_VERSION="{{.AgentVersion}}" DOWNLOAD_BASE="{{.DownloadBase}}" INSTALL_PREFIX="{{.InstallPrefix}}" ARCH="{{.Arch}}" PROXY_URL="{{.ProxyURL}}" CA_CERT_FILE="{{.CACertFile}}" CONFIG_DIR="/etc/backupx-agent" CONFIG_FILE="${CONFIG_DIR}/config.yaml" TOKEN_FILE="${CONFIG_DIR}/agent.token" download_file() { source_url="$1" destination="$2" if command -v curl >/dev/null 2>&1; then if [ -n "$PROXY_URL" ]; then curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 --proxy "$PROXY_URL" "$source_url" -o "$destination" else curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 15 "$source_url" -o "$destination" fi else wget -q -T 30 -O "$destination" "$source_url" fi } agent_is_online() { if command -v curl >/dev/null 2>&1; then if [ -n "$PROXY_URL" ] && [ -n "$CA_CERT_FILE" ]; then response=$(curl -fsS --max-time 5 --proxy "$PROXY_URL" --cacert "$CA_CERT_FILE" -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 elif [ -n "$PROXY_URL" ]; then response=$(curl -fsS --max-time 5 --proxy "$PROXY_URL" -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 elif [ -n "$CA_CERT_FILE" ]; then response=$(curl -fsS --max-time 5 --cacert "$CA_CERT_FILE" -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 else response=$(curl -fsS --max-time 5 -H "X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 fi else response=$(wget -q -T 5 --max-redirect=0 -O - --header="X-Agent-Token: ${AGENT_TOKEN}" "${MASTER_URL}/api/v1/agent/self" 2>/dev/null) || return 1 fi printf '%s' "$response" | grep -q '"status":"online"' } # 1. 前置检查 [ "$(id -u)" -eq 0 ] || { echo "请使用 root 或 sudo 执行" >&2; exit 1; } command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 \ || { echo "需要 curl 或 wget" >&2; exit 1; } command -v grep >/dev/null 2>&1 || { echo "需要 grep" >&2; exit 1; } if { [ -n "$PROXY_URL" ] || [ -n "$CA_CERT_FILE" ]; } && ! command -v curl >/dev/null 2>&1; then echo "显式代理或自定义 CA 场景需要 curl" >&2 exit 1 fi {{if eq .Mode "systemd"}}command -v systemctl >/dev/null 2>&1 || { echo "当前系统不支持 systemd" >&2; exit 1; } {{end}}{{if eq .Mode "docker"}}command -v docker >/dev/null 2>&1 || { echo "需要先安装 Docker" >&2; exit 1; } {{end}} # 2. 架构检测 if [ "$ARCH" = "auto" ]; then case "$(uname -m)" in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; *) echo "不支持的架构: $(uname -m)" >&2; exit 1 ;; esac fi # 3. 安全写入 Agent 配置。systemd unit 与容器元数据中不保存节点 Token。 install -d -m 0700 "$CONFIG_DIR" /var/lib/backupx-agent /var/lib/backupx-agent/tmp printf '%s\n' "$AGENT_TOKEN" > "$TOKEN_FILE" chmod 0600 "$TOKEN_FILE" if [ -n "$CA_CERT_FILE" ]; then [ -r "$CA_CERT_FILE" ] || { echo "无法读取 CA 证书: $CA_CERT_FILE" >&2; exit 1; } if [ "$CA_CERT_FILE" != "$CONFIG_DIR/ca.pem" ]; then install -m 0644 "$CA_CERT_FILE" "$CONFIG_DIR/ca.pem" fi CA_CERT_FILE="$CONFIG_DIR/ca.pem" fi cat > "$CONFIG_FILE" </dev/null; then if command -v sha256sum >/dev/null 2>&1; then (cd "$TMPDIR" && sha256sum -c "$ARCHIVE.sha256") elif command -v shasum >/dev/null 2>&1; then (cd "$TMPDIR" && shasum -a 256 -c "$ARCHIVE.sha256") else echo "已下载校验文件,但系统缺少 sha256sum 或 shasum,拒绝未校验安装" >&2 exit 1 fi else echo "[WARN] 当前版本未提供 SHA-256 校验文件,继续兼容安装" >&2 fi tar xzf "$TMPDIR/$ARCHIVE" -C "$TMPDIR" echo "[2/4] 安装到 ${INSTALL_PREFIX}" install -d -m 0755 "$INSTALL_PREFIX" install -m 0755 "$TMPDIR/backupx-${AGENT_VERSION}-linux-${ARCH}/backupx" "$INSTALL_PREFIX/backupx.new" mv -f "$INSTALL_PREFIX/backupx.new" "$INSTALL_PREFIX/backupx" {{end}} {{if eq .Mode "systemd"}} # 5. systemd unit echo "[3/4] 配置 systemd" cat > /etc/systemd/system/backupx-agent.service <&2 systemctl status backupx-agent --no-pager >&2 || true journalctl -u backupx-agent -n 100 --no-pager >&2 || true exit 1 fi # 6. 等待上线 echo "[4/4] 等待节点上线" i=0 while [ "$i" -lt 15 ]; do sleep 2 if agent_is_online; then echo "[OK] 节点已上线" exit 0 fi i=$((i + 1)) done echo "[WARN] 30 秒内未收到上线心跳,请检查网络、代理或 SSH 隧道" >&2 echo "排查命令: systemctl status backupx-agent" >&2 echo "排查命令: journalctl -u backupx-agent -n 100 --no-pager" >&2 exit 2 {{end}} {{if eq .Mode "foreground"}} # 5. 前台运行 echo "[3/3] 前台启动 Agent(Ctrl+C 退出)" exec "${INSTALL_PREFIX}/backupx" agent --config "$CONFIG_FILE" {{end}} {{if eq .Mode "docker"}} # Docker 模式:配置文件只读挂载,避免 Token 出现在 docker inspect 环境变量中。 echo "[1/2] 拉取镜像 awuqing/backupx:${AGENT_VERSION}" docker pull "awuqing/backupx:${AGENT_VERSION}" echo "[2/2] 启动容器 backupx-agent" docker rm -f backupx-agent >/dev/null 2>&1 || true docker run -d --name backupx-agent --restart=unless-stopped --user 0:0 \ --security-opt no-new-privileges:true \ -v /etc/backupx-agent:/etc/backupx-agent:ro \ -v /var/lib/backupx-agent:/var/lib/backupx-agent \ "awuqing/backupx:${AGENT_VERSION}" agent --config /etc/backupx-agent/config.yaml echo "提示: Docker Agent 只能访问显式挂载的目录;备份宿主机路径前请按文档添加只读 -v 挂载。" echo "容器已启动,等待节点上线" i=0 while [ "$i" -lt 15 ]; do sleep 2 if agent_is_online; then echo "[OK] 节点已上线" exit 0 fi i=$((i + 1)) done echo "[WARN] 30 秒内未收到上线心跳,请检查容器、代理、隧道与 Master 地址" >&2 echo "排查命令: docker ps -a --filter name=backupx-agent" >&2 echo "排查命令: docker logs --tail=100 backupx-agent" >&2 exit 2 {{end}}