From 2662c65f572424c14770849fd04b87a92a59bdb9 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 6 Jun 2026 11:06:45 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog/index.md | 4 + docs/deployment/agent.md | 65 +++- docs/plan/20260606-interactive-installer.md | 85 +++++ scripts/install-agent.sh | 381 ++++++++++++++++++-- scripts/uninstall-agent-legacy.sh | 192 ---------- scripts/uninstall-agent.sh | 78 +++- 6 files changed, 577 insertions(+), 228 deletions(-) create mode 100644 docs/plan/20260606-interactive-installer.md delete mode 100644 scripts/uninstall-agent-legacy.sh diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 1bfa5823..d9d5cf84 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -20,12 +20,16 @@ sidebar: false ### 新增 +- 新增 Agent 交互式安装向导,支持选择本地安装和 Docker 运行模式;未传参数时自动进入交互菜单 +- 新增 Docker 运行模式的智能环境检查,检测到未安装 Docker 时支持一键在线安装,中国大陆环境支持多镜像源自动测速优选与加速器配置 +- 新增 Agent 交互式卸载向导,支持选择本地卸载和 Docker 容器卸载模式;未传参数时自动进入交互菜单 - 新增 Pages 静态托管使用指南(`pages-usage.md`),讲解 ZIP 上传、SPA Fallback 与 API 代理配置 - 新增 Uptime Kuma 监控同步集成指南(`uptime-kuma.md`),说明同步参数与专属标签隔离机制 - 完善 WAF IP 组订阅模式使用指南(`waf-usage.md`),补充 JSON 路径提取映射规则与同步参数说明 ### 变更 +- 重构 `install-agent.sh` 安装脚本与 `uninstall-agent.sh` 卸载脚本以兼容交互式导引、非交互式命令行参数及 Docker 部署/卸载参数(`--docker`/`--method docker`) - 重构 Go 包依赖结构为统一模块(Monorepo),模块命名为 `github.com/rain-kl/openflare` - 移除各子目录下独立的 `go.mod`/`go.sum` 文件,统一由根目录 `go.mod` 进行全局依赖管理与依赖版本锁定 - 替换全仓库 Go 源文件中的内部引用路径,由本地相对路径迁移为标准 GitHub 绝对导入路径 diff --git a/docs/deployment/agent.md b/docs/deployment/agent.md index c0d8143c..1bcf8214 100644 --- a/docs/deployment/agent.md +++ b/docs/deployment/agent.md @@ -20,7 +20,19 @@ OpenFlare Agent 运行在代理节点侧。它不会接收远程 shell 指令, ## 一键安装 -使用 `discovery_token`: +### 交互式安装 (推荐) + +如果在不传递任何参数的情况下运行安装脚本,脚本将进入交互模式。您将可以通过向导选择安装方式(本地运行 / Docker 容器运行),并配置 Server 地址与认证 Token(若选择 Docker 方式且本地没有 Docker,脚本还会询问并智能安装 Docker): + +```bash +curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash +``` + +### 自动化 (非交互式) 安装 + +如果在执行脚本时附加了任何参数,脚本将进入自动化安装模式,不需要任何交互。 + +使用 `discovery_token` 进行本地安装: ```bash curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \ @@ -28,7 +40,7 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst --discovery-token YOUR_DISCOVERY_TOKEN ``` -使用节点专属 `agent_token`: +使用节点专属 `agent_token` 进行本地安装: ```bash curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \ @@ -36,19 +48,30 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst --agent-token YOUR_AGENT_TOKEN ``` -安装脚本会下载最新 Agent,默认写入 `/opt/openflare-agent`,生成 `agent.json`,并在 Linux + systemd 环境创建 `openflare-agent.service`。 +使用 Docker 容器自动化安装: + +```bash +curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \ + --server-url http://your-server:3000 \ + --discovery-token YOUR_DISCOVERY_TOKEN \ + --docker +``` + +安装脚本在本地安装模式下会下载最新 Agent,默认写入 `/opt/openflare-agent`,生成 `agent.json`,并在 Linux + systemd 环境创建 `openflare-agent.service`。 支持参数: | 参数 | 说明 | | --- | --- | -| `--server-url` | Server 地址,必填 | +| `--server-url` | Server 地址 | | `--discovery-token` | 首次自动注册 Token | | `--agent-token` | 节点专属 Token | -| `--install-dir` | 安装目录,默认 `/opt/openflare-agent` | -| `--openresty-path` | OpenResty 二进制路径,未传时自动查找 `openresty` | +| `--install-dir` | 安装目录,默认 `/opt/openflare-agent`(仅本地安装生效) | +| `--openresty-path` | OpenResty 二进制路径,未传时自动查找 `openresty`(仅本地安装生效) | | `--repo` | 下载 Agent 的 GitHub 仓库,默认 `Rain-kl/OpenFlare` | -| `--no-service` | 不创建 systemd 服务 | +| `--no-service` | 不创建 systemd 服务(仅本地安装生效) | +| `--docker` | 使用 Docker 容器方式安装 | +| `--method` | 安装方式,可选 `local` 或 `docker`(默认 `local`) | ## 配置文件 @@ -150,20 +173,40 @@ export LOG_LEVEL='info' ## 卸载 -如需彻底卸载 Agent 并清空本地数据: +### 交互式卸载 (推荐) + +如果在不传递任何参数的情况下运行卸载脚本,脚本将进入交互模式。您可以通过提示菜单选择卸载方式(本地卸载 / Docker 容器卸载): ```bash curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash ``` +### 自动化 (非交互式) 卸载 + +使用命令行传参进行无人值守卸载。 + +本地卸载(默认): + +```bash +curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash -s -- --install-dir /opt/openflare-agent +``` + +Docker 容器卸载: + +```bash +curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash -s -- --docker +``` + 支持参数: | 参数 | 说明 | | --- | --- | -| `--install-dir` | 安装目录,默认 `/opt/openflare-agent` | -| `--service-name` | systemd 服务名,默认 `openflare-agent` | +| `--install-dir` | 安装目录,默认 `/opt/openflare-agent`(仅本地卸载生效) | +| `--service-name` | systemd 服务名,默认 `openflare-agent`(仅本地卸载生效) | +| `--docker` | 使用 Docker 容器方式卸载 | +| `--method` | 卸载方式,可选 `local` 或 `docker`(默认 `local`) | -卸载脚本只移除 Agent 服务、进程和安装目录,不会删除本机 OpenResty。 +本地卸载只会移除 Agent 服务、进程和安装目录,不会删除本机 OpenResty。Docker 卸载会停止并删除 `openflare-agent` 容器,交互模式下还可以选择是否清理对应的 Docker 镜像。 ## 常见问题 diff --git a/docs/plan/20260606-interactive-installer.md b/docs/plan/20260606-interactive-installer.md new file mode 100644 index 00000000..c4f3c11f --- /dev/null +++ b/docs/plan/20260606-interactive-installer.md @@ -0,0 +1,85 @@ +# 交互式智能安装脚本实现计划 + +本计划旨在拓展 OpenFlare Agent 安装脚本的功能,支持交互式选择本地安装或 Docker 容器安装,并提供智能检测/在线安装 Docker 环境的机制,同时保留通过命令行传参进行自动化安装的既有能力。 + +--- + +## 1. 目标与背景 (Goal & Context) +* **需求背景**:当前安装脚本 `install-agent.sh` 仅支持在宿主机直接下载二进制并配置为本地 systemd 服务运行。随着 Docker 部署方式的普及,需要让用户在一键安装时能根据需要交互式选择 Docker 或本地部署,从而提升部署体验。 +* **开发范围 (Scope)**: + * 支持交互式运行(未传参时):提示用户选择 Local 方式或 Docker 方式。 + * 当选择 Docker 方式时,检测本地是否存在 `docker` 命令。若不存在,提示并在线安装 Docker(支持国内镜像源及自动测速选择最低延迟源)。 + * 交互引导用户配置 `server_url` 和 `agent-token` 或 `discovery-token`。 + * 如果选择 Docker,则最终拉取 Agent 镜像并运行容器;如果选择 Local,则继续原有的本地二进制下载及配置发布逻辑。 + * 兼容非交互式模式:如果执行脚本时传递了任意参数,则跳过任何交互式提示,直接进行自动化安装(支持新参数 `--docker` / `--method docker` 来自动选用 Docker 部署)。 + +--- + +## 2. 设计与决策 (Design & Decisions) + +### 交互工作流 +1. 检查 `$#`(参数数量)。若 `$# -eq 0`,激活 `INTERACTIVE=true`。 +2. 在交互模式下: + * 引导用户选择安装方法(1: Local, 2: Docker)。 + * 若选择 Docker,调用 `Install_Docker` 检测并安装环境。 + * 引导用户输入 `SERVER_URL` 并进行非空校验。 + * 引导用户选择 Token 类型(1: Discovery Token, 2: Agent Token),并输入对应的 Token 值。 + * 若选择 Local 且未传 `--openresty-path`,如果 `openresty` 二进制未能在 $PATH 中找到,交互提示用户手动输入 OpenResty 路径。 +3. 非交互模式下: + * 解析命令行参数。 + * 支持通过 `--docker` 或 `--method docker` 指定 Docker 容器安装。 + * 依然根据传入的 `--server-url` 和 Token 自动执行安装,绝不进行任何交互。 + +### 数据流与架构图 +```mermaid +graph TD + Start[执行 install-agent.sh] --> CheckArgs{是否有命令行参数?} + + CheckArgs -- 是 (非交互模式) --> ParseArgs[解析参数] + ParseArgs --> IsDockerParam{是否指定 Docker?} + IsDockerParam -- 是 --> RunDocker[Docker 容器拉取与启动] + IsDockerParam -- 否 --> RunLocal[本地二进制下载与 systemd 服务创建] + + CheckArgs -- 否 (交互模式) --> PromptMethod[提示选择 Local 或 Docker] + PromptMethod --> MethodChosen{选择结果} + + MethodChosen -- Docker --> CheckDocker{本地有 Docker 吗?} + CheckDocker -- 否 --> PromptDockerInstall[询问是否安装 Docker?] + PromptDockerInstall -- 是 --> InstallDocker[在线安装 Docker + 配置国内镜像加速] + PromptDockerInstall -- 否 --> ExitScript[取消安装并退出] + CheckDocker -- 是 --> PromptServerUrl[提示输入 Server URL & Token] + InstallDocker --> PromptServerUrl + + MethodChosen -- Local --> PromptLocalConfig[检测 OpenResty 并提示输入 Server URL & Token] + + PromptServerUrl --> RunDocker + PromptLocalConfig --> RunLocal +``` + +--- + +## 3. 具体修改文件清单 (Proposed Changes) + +### 边缘 Agent 与部署脚本 +* #### [MODIFY] [install-agent.sh](file:///Users/ryan/DEV/Go/OpenFlare/scripts/install-agent.sh) + * 职责: + 1. 引入交互式选择逻辑和 `Install_Docker` / `configure_accelerator` 函数。 + 2. 新增 `--docker` 和 `--method` 命令行参数支持。 + 3. 支持用户交互输入配置项。 + 4. 增加 Docker 镜像拉取、停止旧容器并启动新容器的安装路径。 +* #### [MODIFY] [agent.md](file:///Users/ryan/DEV/Go/OpenFlare/docs/deployment/agent.md) + * 职责:更新一键安装说明文档,增加交互式模式的说明以及 `--docker` 参数的自动化 Docker 安装说明。 + +--- + +## 4. 验证计划 (Verification Plan) + +### 自动化与脚本测试 +* 在干净的测试环境运行脚本: + * `bash scripts/install-agent.sh` (测试交互模式 -> 选用 Local) + * `bash scripts/install-agent.sh` (测试交互模式 -> 选用 Docker) + * `bash scripts/install-agent.sh --server-url http://127.0.0.1:3000 --discovery-token mytoken` (测试自动 Local 安装) + * `bash scripts/install-agent.sh --server-url http://127.0.0.1:3000 --discovery-token mytoken --docker` (测试自动 Docker 安装) + +### 数据面生效验证 +* 通过 `docker ps` 和 `docker logs openflare-agent` 确认容器成功拉取并启动,环境参数注入正确。 diff --git a/scripts/install-agent.sh b/scripts/install-agent.sh index 1c9583d0..1500ebdd 100755 --- a/scripts/install-agent.sh +++ b/scripts/install-agent.sh @@ -15,6 +15,252 @@ AGENT_TOKEN="" CREATE_SERVICE="true" SERVICE_NAME="openflare-agent" OPENRESTY_PATH="" +INSTALL_METHOD="" + +CURRENT_DIR="$(pwd)" +LOG_FILE="${CURRENT_DIR}/install.log" + +# Translation text variables +TXT_DOCKER_RESTARTED="Docker 已启动/重启并运行中" +TXT_LOW_DOCKER_VERSION="Docker 版本较低,建议升级到 20.10 及以上版本" +TXT_INSTALL_DOCKER_CONFIRM="未检测到 Docker,是否现在安装?(y/n) [y]: " +TXT_DOCKER_INSTALL_ONLINE="开始在线安装 Docker..." +TXT_INSTALL_DOCKER_ONLINE="使用 opkg 在线安装 Docker..." +TXT_CHOOSE_LOWEST_LATENCY_SOURCE="选择延迟最低的镜像源:" +TXT_CHOOSE_LOWEST_LATENCY_DELAY="延迟为:" +TXT_TRY_NEXT_LINK="尝试使用链接:" +TXT_DOWNLOAD_DOCKER_SCRIPT="下载 Docker 安装脚本..." +TXT_DOWNLOAD_DOCKER_SCRIPT_SUCCESS="下载成功:" +TXT_SUCCESSFULLY_MESSAGE="开始执行安装..." +TXT_DOWNLOAD_FAIELD="下载失败:" +TXT_ALL_DOWNLOAD_ATTEMPTS_FAILED="所有 Docker 下载尝试均失败,请手动安装 Docker" +TXT_DOCKER_INSTALL_FAIL="Docker 安装失败" +TXT_DOCKER_INSTALL_SUCCESS="Docker 安装成功" +TXT_CANNOT_SELECT_SOURCE="无法选择合适的镜像源" +TXT_REGIONS_OTHER_THAN_CHINA="检测到非中国大陆地区,使用官方源安装 Docker..." +TXT_DOCKER_START_NOTICE="正在启动 Docker 服务..." +TXT_CANCEL_INSTALL_DOCKER="已取消 Docker 安装" +TXT_INVALID_YN_INPUT="无效的输入,请输入 y 或 n" + +log() { + echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +configure_accelerator() { + log "配置 Docker 国内镜像加速源..." + local docker_config_folder="/etc/docker" + if [[ ! -d "$docker_config_folder" ]]; then + mkdir -p "$docker_config_folder" + fi + cat > /etc/docker/daemon.json </dev/null; then + systemctl daemon-reload + systemctl restart docker + else + service dockerd restart + fi +} + +function Install_Docker(){ + if which docker >/dev/null 2>&1; then + docker_version=$(docker --version | grep -oE '[0-9]+\.[0-9]+' | head -n 1) + major_version=${docker_version%%.*} + minor_version=${docker_version##*.} + local service_cmd="service dockerd start && service dockerd status" + if command -v systemctl &>/dev/null; then + service_cmd="systemctl start docker && systemctl status docker" + fi + + set +e + local service_status=$($service_cmd 2>&1) + set -e + + if [[ $service_status == *running* ]]; then + log "$TXT_DOCKER_RESTARTED" + else + if [[ $major_version -lt 20 ]]; then + log "$TXT_LOW_DOCKER_VERSION" + fi + + set +e + local country_code=$(curl -s --max-time 5 ipinfo.io/country) + set -e + if [[ "$country_code" == "CN" ]]; then + configure_accelerator + fi + fi + else + while true; do + read -p "$TXT_INSTALL_DOCKER_CONFIRM" install_docker_choice + install_docker_choice=${install_docker_choice:-y} + case "$install_docker_choice" in + [yY]) + log "$TXT_DOCKER_INSTALL_ONLINE" + + if command -v opkg &>/dev/null; then + log "$TXT_INSTALL_DOCKER_ONLINE" + opkg update + opkg install luci-i18n-dockerman-zh-cn + opkg install zoneinfo-asia + service system restart + set +e + local country_code=$(curl -s --max-time 5 ipinfo.io/country) + set -e + if [[ "$country_code" == "CN" ]]; then + configure_accelerator + fi + else + set +e + local country_code=$(curl -s --max-time 5 ipinfo.io/country) + set -e + if [[ "$country_code" == "CN" ]]; then + sources=( + "https://mirrors.aliyun.com/docker-ce" + "https://mirrors.tencent.com/docker-ce" + "https://mirrors.163.com/docker-ce" + "https://mirrors.cernet.edu.cn/docker-ce" + ) + + docker_install_scripts=( + "https://get.docker.com" + "https://testingcf.jsdelivr.net/gh/docker/docker-install@master/install.sh" + "https://cdn.jsdelivr.net/gh/docker/docker-install@master/install.sh" + "https://fastly.jsdelivr.net/gh/docker/docker-install@master/install.sh" + "https://gcore.jsdelivr.net/gh/docker/docker-install@master/install.sh" + "https://raw.githubusercontent.com/docker/docker-install/master/install.sh" + ) + + get_average_delay() { + local source=$1 + local total_delay=0 + local iterations=2 + local timeout=2 + + for ((i = 0; i < iterations; i++)); do + delay=$(curl -o /dev/null -s -m $timeout -w "%{time_total}\n" "$source") + if [ $? -ne 0 ]; then + delay=$timeout + fi + total_delay=$(awk "BEGIN {print $total_delay + $delay}") + done + + average_delay=$(awk "BEGIN {print $total_delay / $iterations}") + echo "$average_delay" + } + + min_delay=99999999 + selected_source="" + + for source in "${sources[@]}"; do + average_delay=$(get_average_delay "$source" &) + + if (( $(awk 'BEGIN { print '"$average_delay"' < '"$min_delay"' }') )); then + min_delay=$average_delay + selected_source=$source + fi + done + wait + + if [ -n "$selected_source" ]; then + log "$TXT_CHOOSE_LOWEST_LATENCY_SOURCE $selected_source,$TXT_CHOOSE_LOWEST_LATENCY_DELAY $min_delay" + export DOWNLOAD_URL="$selected_source" + + for alt_source in "${docker_install_scripts[@]}"; do + log "$TXT_TRY_NEXT_LINK $alt_source $TXT_DOWNLOAD_DOCKER_SCRIPT" + if curl -fsSL --retry 2 --retry-delay 3 --connect-timeout 5 --max-time 10 "$alt_source" -o get-docker.sh; then + log "$TXT_DOWNLOAD_DOCKER_SCRIPT_SUCCESS $alt_source $TXT_SUCCESSFULLY_MESSAGE" + break + else + log "$TXT_DOWNLOAD_FAIELD $alt_source $TXT_TRY_NEXT_LINK" + fi + done + + if [ ! -f "get-docker.sh" ]; then + log "$TXT_ALL_DOWNLOAD_ATTEMPTS_FAILED" + log "bash <(curl -sSL https://linuxmirrors.cn/docker.sh)" + exit 1 + fi + + sh get-docker.sh 2>&1 | tee -a "${CURRENT_DIR}/install.log" + + docker_config_folder="/etc/docker" + if [[ ! -d "$docker_config_folder" ]]; then + mkdir -p "$docker_config_folder" + fi + + set +e + docker version >/dev/null 2>&1 + local docker_ver_res=$? + set -e + if [[ $docker_ver_res -ne 0 ]]; then + log "$TXT_DOCKER_INSTALL_FAIL" + exit 1 + else + log "$TXT_DOCKER_INSTALL_SUCCESS" + if command -v systemctl &>/dev/null; then + systemctl enable docker 2>&1 | tee -a "${LOG_FILE}" + fi + configure_accelerator + fi + else + log "$TXT_CANNOT_SELECT_SOURCE" + exit 1 + fi + else + log "$TXT_REGIONS_OTHER_THAN_CHINA" + export DOWNLOAD_URL="https://download.docker.com" + curl -fsSL "https://get.docker.com" -o get-docker.sh + sh get-docker.sh 2>&1 | tee -a "${LOG_FILE}" + + log "$TXT_DOCKER_START_NOTICE" + if command -v systemctl &>/dev/null; then + systemctl enable docker; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a "${LOG_FILE}" + else + service dockerd start 2>&1 | tee -a "${LOG_FILE}" + sleep 1 + fi + + docker_config_folder="/etc/docker" + if [[ ! -d "$docker_config_folder" ]]; then + mkdir -p "$docker_config_folder" + fi + + set +e + docker version >/dev/null 2>&1 + local docker_ver_res=$? + set -e + if [[ $docker_ver_res -ne 0 ]]; then + log "$TXT_DOCKER_INSTALL_FAIL" + exit 1 + else + log "$TXT_DOCKER_INSTALL_SUCCESS" + fi + fi + fi + + break + ;; + [nN]) + echo "$TXT_CANCEL_INSTALL_DOCKER" + exit 1 + ;; + *) + log "$TXT_INVALID_YN_INPUT" + continue + ;; + esac + done + fi +} usage() { cat </dev/null || true + + echo "启动 openflare-agent 容器..." + if [[ -n "$AGENT_TOKEN" ]]; then + docker run -d --name openflare-agent --restart unless-stopped \ + -p 80:80 -p 443:443/tcp -p 443:443/udp \ + -e OPENFLARE_SERVER_URL="${SERVER_URL}" \ + -e OPENFLARE_AGENT_TOKEN="${AGENT_TOKEN}" \ + ghcr.io/rain-kl/openflare-agent:latest + else + docker run -d --name openflare-agent --restart unless-stopped \ + -p 80:80 -p 443:443/tcp -p 443:443/udp \ + -e OPENFLARE_SERVER_URL="${SERVER_URL}" \ + -e OPENFLARE_DISCOVERY_TOKEN="${DISCOVERY_TOKEN}" \ + ghcr.io/rain-kl/openflare-agent:latest + fi + + echo "" + echo "OpenFlare Agent (Docker) 安装成功!" + echo "您可以执行以下命令查看运行日志:" + echo " docker logs -f openflare-agent" + exit 0 +fi + +# Local installation starts below +if [[ -z "$OPENRESTY_PATH" ]]; then + if command -v openresty >/dev/null 2>&1; then + OPENRESTY_PATH="$(command -v openresty)" + elif [[ "$HAS_ARGS" == "false" ]]; then + read -p "未在 PATH 中检测到 openresty,请输入 OpenResty 二进制路径: " OPENRESTY_PATH + if [[ -z "$OPENRESTY_PATH" ]]; then + echo "Error: OpenResty binary path is required for local installation." + exit 1 + fi + else + echo "Error: openresty was not found in PATH. Install OpenResty first or pass --openresty-path." + exit 1 + fi fi # Detect platform @@ -85,15 +429,6 @@ if [[ "$OS" != "linux" && "$OS" != "darwin" ]]; then exit 1 fi -if [[ -z "$OPENRESTY_PATH" ]]; then - if command -v openresty >/dev/null 2>&1; then - OPENRESTY_PATH="$(command -v openresty)" - else - echo "Error: openresty was not found in PATH. Install OpenResty first or pass --openresty-path." - exit 1 - fi -fi - if [[ ! -x "$OPENRESTY_PATH" ]]; then echo "Error: OpenResty binary is not executable: ${OPENRESTY_PATH}" exit 1 diff --git a/scripts/uninstall-agent-legacy.sh b/scripts/uninstall-agent-legacy.sh deleted file mode 100644 index 0da898d8..00000000 --- a/scripts/uninstall-agent-legacy.sh +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -INSTALL_DIR="/opt/openflare-agent" -SERVICE_NAME="openflare-agent" - -usage() { - cat </dev/null | head -n 1 || true) - if [[ -z "$match" ]]; then - return 0 - fi - - printf '%s\n' "$match" | sed -E 's/.*:[[:space:]]*"([^"]*)"/\1/' -} - -AGENT_BINARY="${INSTALL_DIR}/openflare-agent" -CONFIG_FILE="${INSTALL_DIR}/agent.json" -SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" - -OPENRESTY_PATH="" -OPENRESTY_CONTAINER_NAME="openflare-openresty" -OPENRESTY_DOCKER_IMAGE="openresty/openresty:alpine" -DOCKER_BINARY="docker" -OPENRESTY_MODE="unknown" - -if [[ -f "$CONFIG_FILE" ]]; then - OPENRESTY_PATH="$(json_get_string "$CONFIG_FILE" "openresty_path")" - OPENRESTY_CONTAINER_NAME="$(json_get_string "$CONFIG_FILE" "openresty_container_name")" - OPENRESTY_DOCKER_IMAGE="$(json_get_string "$CONFIG_FILE" "openresty_docker_image")" - DOCKER_BINARY="$(json_get_string "$CONFIG_FILE" "docker_binary")" - - if [[ -z "$OPENRESTY_CONTAINER_NAME" ]]; then - OPENRESTY_CONTAINER_NAME="openflare-openresty" - fi - if [[ -z "$OPENRESTY_DOCKER_IMAGE" ]]; then - OPENRESTY_DOCKER_IMAGE="openresty/openresty:alpine" - fi - if [[ -z "$DOCKER_BINARY" ]]; then - DOCKER_BINARY="docker" - fi - - if [[ -n "$OPENRESTY_PATH" ]]; then - OPENRESTY_MODE="local" - else - OPENRESTY_MODE="docker" - fi -fi - -SYSTEMCTL_AVAILABLE="false" -if command -v systemctl >/dev/null 2>&1; then - SYSTEMCTL_AVAILABLE="true" -fi - -echo "Uninstalling OpenFlare Agent from ${INSTALL_DIR}..." - -if [[ "$SYSTEMCTL_AVAILABLE" == "true" ]]; then - if systemctl is-active --quiet "$SERVICE_NAME"; then - echo "Stopping service: ${SERVICE_NAME}" - systemctl stop "$SERVICE_NAME" - fi - - if systemctl is-enabled --quiet "$SERVICE_NAME" >/dev/null 2>&1; then - echo "Disabling service: ${SERVICE_NAME}" - systemctl disable "$SERVICE_NAME" >/dev/null 2>&1 || true - fi -fi - -if command -v pgrep >/dev/null 2>&1; then - mapfile -t agent_pids < <(pgrep -f "$AGENT_BINARY" || true) - if (( ${#agent_pids[@]} > 0 )); then - echo "Stopping agent process: ${agent_pids[*]}" - kill "${agent_pids[@]}" || true - sleep 1 - - mapfile -t remaining_agent_pids < <(pgrep -f "$AGENT_BINARY" || true) - if (( ${#remaining_agent_pids[@]} > 0 )); then - echo "Force stopping remaining agent process: ${remaining_agent_pids[*]}" - kill -9 "${remaining_agent_pids[@]}" || true - fi - fi -fi - -if [[ -f "$SERVICE_FILE" ]]; then - echo "Removing service file: ${SERVICE_FILE}" - rm -f "$SERVICE_FILE" -fi - -if [[ "$SYSTEMCTL_AVAILABLE" == "true" ]]; then - systemctl daemon-reload || true - systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true -fi - -if [[ -d "$INSTALL_DIR" ]]; then - echo "Removing installation directory: ${INSTALL_DIR}" - rm -rf "$INSTALL_DIR" -else - echo "Installation directory not found, skipping: ${INSTALL_DIR}" -fi - -echo "Agent uninstall complete." -echo "" -echo "Checking OpenResty installation mode..." - -if [[ "$OPENRESTY_MODE" == "docker" ]]; then - echo "Detected Docker OpenResty mode." - - if ! command -v "$DOCKER_BINARY" >/dev/null 2>&1; then - echo "Docker binary '${DOCKER_BINARY}' was not found." - echo "Please remove container '${OPENRESTY_CONTAINER_NAME}' and image '${OPENRESTY_DOCKER_IMAGE}' manually." - exit 0 - fi - - if "$DOCKER_BINARY" inspect "$OPENRESTY_CONTAINER_NAME" >/dev/null 2>&1; then - if [[ -z "$OPENRESTY_DOCKER_IMAGE" ]]; then - OPENRESTY_DOCKER_IMAGE="$("$DOCKER_BINARY" inspect -f '{{.Config.Image}}' "$OPENRESTY_CONTAINER_NAME" 2>/dev/null || true)" - fi - - echo "Removing Docker container: ${OPENRESTY_CONTAINER_NAME}" - "$DOCKER_BINARY" rm -f "$OPENRESTY_CONTAINER_NAME" - else - echo "Docker container not found, skipping: ${OPENRESTY_CONTAINER_NAME}" - fi - - if [[ -n "$OPENRESTY_DOCKER_IMAGE" ]] && "$DOCKER_BINARY" image inspect "$OPENRESTY_DOCKER_IMAGE" >/dev/null 2>&1; then - other_container_ids="$("$DOCKER_BINARY" ps -a --filter "ancestor=${OPENRESTY_DOCKER_IMAGE}" --format '{{.ID}}' 2>/dev/null || true)" - if [[ -z "$other_container_ids" ]]; then - echo "Removing Docker image: ${OPENRESTY_DOCKER_IMAGE}" - if ! "$DOCKER_BINARY" image rm "$OPENRESTY_DOCKER_IMAGE"; then - echo "Image removal skipped because Docker reported it is still in use." - fi - else - echo "Docker image is still used by other containers, skipping image removal: ${OPENRESTY_DOCKER_IMAGE}" - fi - fi - - echo "Docker OpenResty cleanup complete." -elif [[ "$OPENRESTY_MODE" == "local" ]]; then - echo "Detected local OpenResty mode via openresty_path:" - echo " ${OPENRESTY_PATH}" - echo "Agent has been removed, but the local OpenResty installation was not modified." - echo "Please uninstall the local OpenResty manually if you no longer need it." -else - echo "OpenResty mode could not be determined because ${CONFIG_FILE} was not found before uninstall." - echo "If you were using Docker OpenResty, please remove its container and image manually if needed." -fi - -echo "" -echo "OpenFlare Agent uninstall finished." diff --git a/scripts/uninstall-agent.sh b/scripts/uninstall-agent.sh index 63577646..8cf50d6b 100644 --- a/scripts/uninstall-agent.sh +++ b/scripts/uninstall-agent.sh @@ -3,6 +3,7 @@ set -euo pipefail INSTALL_DIR="/opt/openflare-agent" SERVICE_NAME="openflare-agent" +INSTALL_METHOD="" usage() { cat </dev/null 2>&1; then + if docker ps -a --format '{{.Names}}' | grep -Eq "^openflare-agent$"; then + echo "停止并移除 openflare-agent 容器..." + docker stop openflare-agent || true + docker rm -f openflare-agent || true + echo "已成功移除 openflare-agent 容器。" + else + echo "未检测到 openflare-agent 容器。" + fi + + # In interactive mode, ask if user wants to remove docker image + if [[ "$HAS_ARGS" == "false" ]]; then + read -p "是否删除 OpenFlare Agent 镜像 (ghcr.io/rain-kl/openflare-agent:latest)? (y/n) [n]: " remove_image_choice + remove_image_choice=${remove_image_choice:-n} + case "$remove_image_choice" in + [yY]) + echo "正在删除 Docker 镜像..." + docker rmi ghcr.io/rain-kl/openflare-agent:latest || true + ;; + *) + echo "保留 Docker 镜像。" + ;; + esac + fi + else + echo "Error: 未检测到 Docker 运行环境,无法进行 Docker 方式的卸载。" + exit 1 + fi + + echo "OpenFlare Agent (Docker) 卸载完成。" + exit 0 +fi + if [[ -z "$INSTALL_DIR" || "$INSTALL_DIR" == "/" || "$INSTALL_DIR" == "." ]]; then echo "Refusing to remove unsafe install directory: '${INSTALL_DIR}'" exit 1