proxy —— Ubuntu 全局代理管理工具

43 阅读11分钟

🛰️ proxy —— Ubuntu 全局代理管理工具

一个 Shell 脚本,解决 Ubuntu 代理设置散乱、切换繁琐的痛点。proxy on 一条命令同时搞定 Shell、apt、git、Docker 四处代理,proxy off 真正清除所有层级。

背景:为什么需要这个工具?

在 Ubuntu 上使用代理一直是一件「能用但麻烦」的事。你可能遇到过这些场景:

  • 每次开机都要手动 export http_proxy=...,关掉终端就失效了
  • apt update 走不了代理,要单独配置 /etc/apt/apt.conf.d/
  • git clone 慢如蜗牛,git 有自己独立的代理设置
  • docker pull 超时,Docker daemon 根本不读 shell 环境变量

功能速览

命令说明
proxy config 10.x.x.x:7890配置代理地址(支持交互式向导)
proxy on开启全局代理(Shell + RC + apt + git + Docker)
proxy off关闭并清除所有层级代理(保留配置地址)
proxy ping测试连通性(自动判断走代理还是直连)
proxy ping <url>测试指定地址
proxy status查看五层代理状态
proxy -h帮助信息

安装

前置依赖

依赖说明
bashUbuntu 默认已安装
curl用于 proxy ping 连通性测试(sudo apt install curl
git可选,proxy on 时自动配置 git 全局代理
docker可选,proxy on 时自动配置 Docker daemon 代理
sudo 权限写入 apt / docker 配置及 systemd reload 时需要

一键安装

proxyinstall_proxy.sh 两个文件放在同一目录,执行:

chmod +x proxy install_proxy.sh
sudo bash install_proxy.sh

安装脚本会自动完成:

  1. 检查 curl / bash 依赖
  2. 将主脚本安装为 /usr/local/bin/_proxy_bin
  3. ~/.bashrc(以及 ~/.zshrc,如果存在)注入 proxy shell function
  4. 引导完成首次代理地址配置(可回车跳过)

安装完成后执行一次让当前终端生效:

source ~/.bashrc

✅ 后续新开的终端无需再次 source,function 会随 bashrc 自动加载。

使用指南

第一步:配置代理地址

# 一次性设置 host 和 port(推荐)
proxy config 10.60.60.39:7890

# 单独修改 host 或 port
proxy config -host 10.60.60.39
proxy config -port 7890

# 交互式向导
proxy config

配置文件存储在 ~/.config/proxy-manager/config,权限自动设为 600。配置地址与代理开关状态相互独立——proxy off 不会删除配置,随时可以重新 proxy on


第二步:开启代理

proxy on

一条命令同时配置五个层级:

层级写入位置生效范围
Shell 环境变量当前进程(eval)当前终端窗口,立即生效
Shell RC 文件~/.bashrc / ~/.zshrc新开终端自动加载
apt 代理/etc/apt/apt.conf.d/95proxyapt install / apt update
git 全局代理~/.gitconfig所有 git 操作
Docker daemon/etc/systemd/system/docker.service.d/proxy.confdocker pull / docker build

执行后的输出示例:

✔  代理已开启: http://10.60.60.39:7890

  ✔  Shell 环境变量   → 当前窗口立即生效
  ✔  ~/.bashrc        → 新终端自动加载
  ✔  apt 代理         → /etc/apt/apt.conf.d/95proxy
  ✔  git 全局代理     → http.proxy / https.proxy
  ✔  docker 代理      → /etc/systemd/system/docker.service.d/proxy.conf

⚠️ Docker 代理生效需要重启 daemon,脚本会自动执行 systemctl restart docker。如果 Docker 当前未运行,配置写入后启动时自动生效。


关闭代理

proxy off

同步清除所有层级,保留配置地址

✔  代理已关闭

  ✔  Shell 环境变量   已清除(当前窗口)
  ✔  ~/.bashrc        已清除代理块
  ✔  apt 代理         已移除
  ✔  git 全局代理     已移除
  ✔  docker 代理      已移除

💡 proxy off 是幂等的,重复执行不会报错。


测试连通性

proxy ping 根据当前窗口的代理状态自动决定测试方式:

proxy ping                          # 默认测试 https://www.google.com
proxy ping https://www.youtube.com  # 测试指定地址
  • 代理已开启:经由代理测试,验证代理是否正常工作
  • 代理未开启:直连测试,验证目标地址是否可达
# 代理已开启时
ℹ  代理已开启,经由 http://10.60.60.39:7890 测试: https://www.google.com
✔  连通性: 正常  HTTP 200  耗时 342ms

# 代理未开启时
ℹ  代理未开启,直连测试: https://www.google.com
✔  连通性: 正常  HTTP 200  耗时 89ms

查看状态

proxy status
═══ 代理状态检测 ═══

  配置地址   ✔  10.60.60.39:7890  (~/.config/proxy-manager/config)
  当前窗口   ✔  http://10.60.60.39:7890
  Shell RC   ✔  代理已写入 ~/.bashrc(新终端自动生效)
  apt 代理   ✔  /etc/apt/apt.conf.d/95proxy
  git 代理   ✔  http://10.60.60.39:7890
  docker     ✔  /etc/systemd/system/docker.service.d/proxy.conf

设计说明

为什么各工具需要单独配置?

Linux 下不同工具有各自独立的代理机制,仅设置 shell 环境变量是不够的:

工具代理机制
curl / wget读取 http_proxy 环境变量
apt只认 /etc/apt/apt.conf.d/ 配置文件
git使用 git config http.proxy,独立于环境变量
Docker daemon以 systemd service 运行,需通过 drop-in 文件注入环境变量给 dockerd 进程

proxy on 针对这四种机制分别写入,一条命令全部搞定。

Docker 代理的特殊性

Docker 有两个不同的代理配置场景,本工具解决的是 daemon 代理

  • daemon 代理(本工具配置):控制 dockerd 进程拉取镜像时走代理,通过 systemd drop-in 文件 /etc/systemd/system/docker.service.d/proxy.conf 配置,需要重启 daemon 生效
  • 容器代理:控制容器内部的网络请求,通过 ~/.docker/config.json 配置,不在本工具范围内

配置地址与开关状态分离

proxy off 仅清除代理的激活状态,不删除 ~/.config/proxy-manager/config 中保存的地址。临时关闭代理后,随时可以用 proxy on 重新启用,无需重新输入地址。


完整操作示例

# 1. 首次配置代理地址
proxy config 10.60.60.39:7890
# ✔  代理已配置: 10.60.60.39:7890

# 2. 开启全局代理(含 Docker)
proxy on
# ✔  代理已开启: http://10.60.60.39:7890
# ✔  五层全部配置完毕,Docker daemon 已重启

# 3. 测试连通性(走代理)
proxy ping
# ✔  连通性: 正常  HTTP 200  耗时 312ms

# 4. 拉取 Docker 镜像(走代理)
docker pull ubuntu:24.04

# 5. 查看当前状态
proxy status
# 五层全部显示 ✔

# 6. 临时关闭代理
proxy off
# ✔  代理已关闭(五层全部清除,配置地址保留)

# 7. 修改代理地址后重新开启
proxy config -host 10.60.60.100
proxy on

获取脚本

完整源码包含两个文件:

  • proxy — 主脚本(安装为 /usr/local/bin/_proxy_bin
  • install_proxy.sh — 安装脚本,需要 sudo 运行

💡 脚本逻辑简单透明,欢迎按需修改。


如果这个工具对你有帮助,欢迎分享给同样在 Ubuntu 上被代理折磨的朋友 🙂

proxy

#!/usr/bin/env bash
# =============================================================================
# _proxy_bin - proxy 工具真实执行脚本(通过 shell function 包装调用)
# 安装: sudo bash install_proxy.sh
# =============================================================================
#
# ── 设计说明 ──────────────────────────────────────────────────────────────────
# shell 子进程无法修改父进程的环境变量,因此 proxy on/off 对当前窗口的
# 环境变量操作必须在父 shell 中执行。
#
# 解决方案:
#   1. 本脚本(_proxy_bin)负责所有文件操作(rc/apt/git/docker)和用户输出
#   2. 对需要影响当前 shell 的操作,在 stdout 末尾输出 __EVAL__:<commands>
#   3. install_proxy.sh 在 ~/.bashrc 注入一个名为 proxy 的 shell function
#   4. 该 function 调用 _proxy_bin,捕获 __EVAL__ 行并在当前 shell 中 eval
# =============================================================================

set -euo pipefail

# ── 常量 ─────────────────────────────────────────────────────────────────────
CONFIG_FILE="$HOME/.config/proxy-manager/config"
RC_MARKER_BEGIN="# >>> proxy-manager global <<<"
RC_MARKER_END="# <<< proxy-manager global <<<"
BASHRC_FILE="$HOME/.bashrc"
ZSHRC_FILE="$HOME/.zshrc"
APT_PROXY_FILE="/etc/apt/apt.conf.d/95proxy"
DOCKER_PROXY_DIR="/etc/systemd/system/docker.service.d"
DOCKER_PROXY_FILE="${DOCKER_PROXY_DIR}/proxy.conf"

# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'

# ── 工具函数 ──────────────────────────────────────────────────────────────────
info()    { echo -e "${BLUE}${NC}  $*"; }
success() { echo -e "${GREEN}${NC}  $*"; }
warn()    { echo -e "${YELLOW}${NC}  $*"; }
error()   { echo -e "${RED}${NC}  $*" >&2; }
die()     { error "$*"; exit 1; }
bold()    { echo -e "${BOLD}$*${NC}"; }

# 向父 shell 发送需要 eval 的命令(由 proxy function 捕获执行)
emit_eval() { echo "__EVAL__:$*"; }

# ── 配置读写 ──────────────────────────────────────────────────────────────────
load_config() {
    [[ -f "$CONFIG_FILE" ]] || return 1
    # shellcheck source=/dev/null
    source "$CONFIG_FILE"
}

save_config() {
    local host="$1" port="$2"
    mkdir -p "$(dirname "$CONFIG_FILE")"
    cat > "$CONFIG_FILE" <<EOF
# proxy-manager 配置文件 — 请勿手动修改格式
PROXY_HOST="${host}"
PROXY_PORT="${port}"
EOF
    chmod 600 "$CONFIG_FILE"
}

get_proxy_url() {
    load_config || die "尚未配置代理,请先运行: proxy config <host>:<port>"
    echo "http://${PROXY_HOST}:${PROXY_PORT}"
}

# ── 格式校验 ──────────────────────────────────────────────────────────────────
validate_host() {
    local host="$1"
    [[ "$host" =~ ^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]] \
    || [[ "$host" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] \
    || [[ "$host" == "localhost" ]]
}

validate_port() {
    local port="$1"
    [[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 ))
}

# ── 环境变量块 ────────────────────────────────────────────────────────────────
_export_cmds() {
    local url="$1"
    echo "export http_proxy='${url}' https_proxy='${url}' HTTP_PROXY='${url}' HTTPS_PROXY='${url}' ftp_proxy='${url}' FTP_PROXY='${url}' no_proxy='localhost,127.0.0.1,::1' NO_PROXY='localhost,127.0.0.1,::1'"
}

_unset_cmds() {
    echo "unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ftp_proxy FTP_PROXY no_proxy NO_PROXY"
}

_rc_export_block() {
    local url="$1"
    cat <<EOF
export http_proxy="${url}"
export https_proxy="${url}"
export HTTP_PROXY="${url}"
export HTTPS_PROXY="${url}"
export ftp_proxy="${url}"
export FTP_PROXY="${url}"
export no_proxy="localhost,127.0.0.1,::1"
export NO_PROXY="localhost,127.0.0.1,::1"
EOF
}

# ── rc 文件操作 ───────────────────────────────────────────────────────────────
_write_rc() {
    local url="$1" file="$2"
    [[ -f "$file" ]] || return 0
    sed -i "/# >>> proxy-manager global <<</,/# <<< proxy-manager global <<</d" "$file"
    cat >> "$file" <<EOF

${RC_MARKER_BEGIN}
$(_rc_export_block "$url")
${RC_MARKER_END}
EOF
}

_remove_rc() {
    local file="$1"
    [[ -f "$file" ]] || return 0
    sed -i "/# >>> proxy-manager global <<</,/# <<< proxy-manager global <<</d" "$file"
}

_rc_has_proxy() {
    grep -q "proxy-manager global" "$BASHRC_FILE" 2>/dev/null
}

# ── apt 代理 ──────────────────────────────────────────────────────────────────
_write_apt() {
    local host="$1" port="$2"
    sudo tee "$APT_PROXY_FILE" > /dev/null <<EOF
# proxy-manager — apt 代理设置
Acquire::http::Proxy "http://${host}:${port}";
Acquire::https::Proxy "http://${host}:${port}";
EOF
    sudo chmod 644 "$APT_PROXY_FILE"
}

_remove_apt() {
    [[ -f "$APT_PROXY_FILE" ]] && sudo rm -f "$APT_PROXY_FILE" || true
}

_apt_has_proxy() { [[ -f "$APT_PROXY_FILE" ]]; }

# ── git 代理 ──────────────────────────────────────────────────────────────────
_write_git() {
    local url="$1"
    git config --global http.proxy  "$url"
    git config --global https.proxy "$url"
}

_remove_git() {
    git config --global --unset http.proxy  2>/dev/null || true
    git config --global --unset https.proxy 2>/dev/null || true
}

_git_has_proxy() {
    command -v git &>/dev/null && git config --global --get http.proxy &>/dev/null
}

# ── docker 代理 ───────────────────────────────────────────────────────────────
# Docker daemon 拉取镜像走的是 dockerd 进程,不读取 shell 环境变量,
# 需要通过 systemd drop-in 文件配置 HTTP_PROXY 传给 dockerd,
# 并 reload systemd + restart docker 使其生效。
_write_docker() {
    local url="$1"
    if ! command -v docker &>/dev/null; then
        warn "未检测到 docker,跳过 docker 代理配置"
        return 0
    fi
    sudo mkdir -p "$DOCKER_PROXY_DIR"
    sudo tee "$DOCKER_PROXY_FILE" > /dev/null <<EOF
# proxy-manager — docker daemon 代理设置
[Service]
Environment="HTTP_PROXY=${url}"
Environment="HTTPS_PROXY=${url}"
Environment="NO_PROXY=localhost,127.0.0.1"
EOF
    sudo chmod 644 "$DOCKER_PROXY_FILE"
    # reload systemd 配置并重启 docker daemon
    sudo systemctl daemon-reload
    if sudo systemctl is-active --quiet docker; then
        sudo systemctl restart docker
        success "Docker daemon 已重启,代理生效"
    else
        info "Docker daemon 当前未运行,代理配置已写入,启动时自动生效"
    fi
}

_remove_docker() {
    if [[ -f "$DOCKER_PROXY_FILE" ]]; then
        sudo rm -f "$DOCKER_PROXY_FILE"
        sudo systemctl daemon-reload
        if sudo systemctl is-active --quiet docker 2>/dev/null; then
            sudo systemctl restart docker
        fi
    fi
}

_docker_has_proxy() {
    [[ -f "$DOCKER_PROXY_FILE" ]]
}

# ── 子命令:on ────────────────────────────────────────────────────────────────
cmd_on() {
    local proxy_url
    proxy_url="$(get_proxy_url)"
    load_config || die "尚未配置代理"

    # 1. rc 文件
    _write_rc "$proxy_url" "$BASHRC_FILE"
    [[ -f "$ZSHRC_FILE" ]] && _write_rc "$proxy_url" "$ZSHRC_FILE"

    # 2. apt 代理
    if sudo -n true 2>/dev/null; then
        _write_apt "$PROXY_HOST" "$PROXY_PORT"
    else
        warn "配置系统代理需要 sudo 权限,请输入密码:"
        _write_apt "$PROXY_HOST" "$PROXY_PORT"
    fi

    # 3. git 全局代理
    if command -v git &>/dev/null; then
        _write_git "$proxy_url"
    else
        warn "未检测到 git,跳过 git 代理配置"
    fi

    # 4. docker daemon 代理
    _write_docker "$proxy_url"

    # 5. 通知父 shell 导出环境变量
    emit_eval "$(_export_cmds "$proxy_url")"

    echo ""
    success "代理已开启: ${CYAN}${proxy_url}${NC}"
    echo ""
    echo -e "  ${GREEN}${NC}  Shell 环境变量   → 当前窗口立即生效"
    echo -e "  ${GREEN}${NC}  ~/.bashrc        → 新终端自动加载"
    [[ -f "$ZSHRC_FILE" ]] && \
    echo -e "  ${GREEN}${NC}  ~/.zshrc         → 新终端自动加载"
    echo -e "  ${GREEN}${NC}  apt 代理         → ${APT_PROXY_FILE}"
    command -v git &>/dev/null && \
    echo -e "  ${GREEN}${NC}  git 全局代理     → http.proxy / https.proxy"
    command -v docker &>/dev/null && \
    echo -e "  ${GREEN}${NC}  docker 代理      → ${DOCKER_PROXY_FILE}"
    echo ""
}

# ── 子命令:off ───────────────────────────────────────────────────────────────
cmd_off() {
    # 1. rc 文件
    _remove_rc "$BASHRC_FILE"
    [[ -f "$ZSHRC_FILE" ]] && _remove_rc "$ZSHRC_FILE"

    # 2. apt 代理
    _remove_apt

    # 3. git 代理
    if command -v git &>/dev/null; then
        _remove_git
    fi

    # 4. docker 代理
    _remove_docker

    # 5. 通知父 shell 清除环境变量
    emit_eval "$(_unset_cmds)"

    echo ""
    success "代理已关闭"
    echo ""
    echo -e "  ${GREEN}${NC}  Shell 环境变量   已清除(当前窗口)"
    echo -e "  ${GREEN}${NC}  ~/.bashrc        已清除代理块"
    [[ -f "$ZSHRC_FILE" ]] && \
    echo -e "  ${GREEN}${NC}  ~/.zshrc         已清除代理块"
    echo -e "  ${GREEN}${NC}  apt 代理         已移除"
    command -v git &>/dev/null && \
    echo -e "  ${GREEN}${NC}  git 全局代理     已移除"
    command -v docker &>/dev/null && \
    echo -e "  ${GREEN}${NC}  docker 代理      已移除"
    echo ""
}

# ── 子命令:ping ──────────────────────────────────────────────────────────────
cmd_ping() {
    local target="${1:-https://www.google.com}"
    local proxy_url="${_PROXY_CURRENT_URL:-}"
    local curl_opts=()

    if [[ -n "$proxy_url" ]]; then
        info "代理已开启,经由 ${CYAN}${proxy_url}${NC} 测试: ${BOLD}${target}${NC}"
        curl_opts=(--proxy "$proxy_url")
    else
        info "代理未开启,直连测试: ${BOLD}${target}${NC}"
    fi
    echo ""

    local start end elapsed http_code
    start=$(date +%s%3N)
    http_code=$(curl -s -o /dev/null -w "%{http_code}" \
        "${curl_opts[@]}" \
        --connect-timeout 10 \
        --max-time 15 \
        "$target" 2>&1) || true
    end=$(date +%s%3N)
    elapsed=$(( end - start ))

    if [[ "$http_code" =~ ^[23] ]]; then
        success "连通性: ${GREEN}正常${NC}  HTTP ${http_code}  耗时 ${elapsed}ms"
    elif [[ "$http_code" == "000" ]]; then
        error "连通性: 失败(连接超时或目标不可达)"
        exit 1
    else
        warn "连通性: 可到达但返回 HTTP ${http_code}  耗时 ${elapsed}ms"
    fi
}

# ── 子命令:status ────────────────────────────────────────────────────────────
cmd_status() {
    local current_url="${_PROXY_CURRENT_URL:-}"

    echo ""
    bold "═══ 代理状态检测 ═══"
    echo ""

    # 配置文件
    if load_config 2>/dev/null; then
        echo -e "  配置地址   ${GREEN}${NC}  ${CYAN}${PROXY_HOST}:${PROXY_PORT}${NC}  (${DIM}${CONFIG_FILE}${NC})"
    else
        echo -e "  配置地址   ${RED}${NC}  未配置(运行 proxy config <host>:<port>)"
    fi

    # 当前窗口
    if [[ -n "$current_url" ]]; then
        echo -e "  当前窗口   ${GREEN}${NC}  ${CYAN}${current_url}${NC}"
    else
        echo -e "  当前窗口   ${DIM}${NC}  未开启"
    fi

    # rc 文件
    if _rc_has_proxy; then
        echo -e "  Shell RC   ${GREEN}${NC}  代理已写入 ~/.bashrc(新终端自动生效)"
    else
        echo -e "  Shell RC   ${DIM}${NC}  未设置"
    fi

    # apt
    if _apt_has_proxy; then
        echo -e "  apt 代理   ${GREEN}${NC}  ${DIM}${APT_PROXY_FILE}${NC}"
    else
        echo -e "  apt 代理   ${DIM}${NC}  未设置"
    fi

    # git
    if _git_has_proxy; then
        local gp
        gp=$(git config --global --get http.proxy 2>/dev/null || echo "")
        echo -e "  git 代理   ${GREEN}${NC}  ${CYAN}${gp}${NC}"
    else
        echo -e "  git 代理   ${DIM}${NC}  未设置"
    fi

    # docker
    if _docker_has_proxy; then
        echo -e "  docker     ${GREEN}${NC}  ${DIM}${DOCKER_PROXY_FILE}${NC}"
    else
        echo -e "  docker     ${DIM}${NC}  未设置"
    fi

    echo ""
}

# ── 子命令:config ────────────────────────────────────────────────────────────
cmd_config() {
    local host="" port=""

    load_config 2>/dev/null && {
        host="${PROXY_HOST:-}"
        port="${PROXY_PORT:-}"
    } || true

    if [[ $# -eq 0 ]]; then
        echo ""
        bold "═══ 代理配置向导 ═══"
        echo ""
        read -rp "  代理地址 (host:port,如 10.60.60.39:7890): " raw
        _parse_and_save "$raw"
        return
    fi

    case "$1" in
        -host|--host)
            [[ $# -ge 2 ]] || die "请提供 host 值: proxy config -host <host>"
            host="$2"
            validate_host "$host" || die "主机格式无效: $host"
            [[ -n "$port" ]] || die "尚未设置 port,请先运行: proxy config <host>:<port>"
            save_config "$host" "$port"
            success "host 已更新: ${CYAN}${host}:${port}${NC}"
            ;;
        -port|--port)
            [[ $# -ge 2 ]] || die "请提供 port 值: proxy config -port <port>"
            port="$2"
            validate_port "$port" || die "端口无效(1-65535): $port"
            [[ -n "$host" ]] || die "尚未设置 host,请先运行: proxy config <host>:<port>"
            save_config "$host" "$port"
            success "port 已更新: ${CYAN}${host}:${port}${NC}"
            ;;
        *)
            _parse_and_save "$1"
            ;;
    esac
}

_parse_and_save() {
    local raw="$1"
    raw="${raw#http://}"; raw="${raw#https://}"; raw="${raw%/}"

    if [[ "$raw" =~ ^(.+):([0-9]+)$ ]]; then
        local h="${BASH_REMATCH[1]}" p="${BASH_REMATCH[2]}"
        validate_host "$h" || die "主机格式无效: $h"
        validate_port "$p" || die "端口无效(1-65535): $p"
        save_config "$h" "$p"
        success "代理已配置: ${CYAN}${h}:${p}${NC}"
        info "运行 ${BOLD}proxy on${NC} 以启用代理"
    else
        die "格式错误,请使用 host:port,如: 10.60.60.39:7890"
    fi
}

# ── 子命令:help ──────────────────────────────────────────────────────────────
cmd_help() {
    echo ""
    bold "  proxy — Ubuntu 代理管理工具"
    echo -e "  ${DIM}版本 4.0  配置文件: ${CONFIG_FILE}${NC}"
    echo ""
    echo -e "  ${BOLD}用法${NC}"
    echo    "    proxy <命令> [选项]"
    echo ""
    echo -e "  ${BOLD}命令${NC}"
    echo ""
    echo -e "    ${CYAN}on${NC}                       开启全局代理(Shell RC + apt + git + docker)"
    echo ""
    echo -e "    ${CYAN}off${NC}                      关闭全局代理(清除所有层级,保留配置地址)"
    echo ""
    echo -e "    ${CYAN}ping${NC}  [url]              测试连通性(根据当前代理状态决定是否走代理)"
    echo -e "      ${DIM}proxy ping                          (默认 https://www.google.com)${NC}"
    echo -e "      ${DIM}proxy ping https://www.youtube.com  (指定测试地址)${NC}"
    echo ""
    echo -e "    ${CYAN}status${NC}                   显示代理状态(窗口 / RC / apt / git / docker)"
    echo ""
    echo -e "    ${CYAN}config${NC}  <host:port>      配置代理地址(不影响当前开启/关闭状态)"
    echo -e "      ${DIM}proxy config 10.60.60.39:7890       (一次设置 host 和 port)${NC}"
    echo -e "      ${DIM}proxy config -host 10.60.60.39      (单独更新 host)${NC}"
    echo -e "      ${DIM}proxy config -port 7890             (单独更新 port)${NC}"
    echo -e "      ${DIM}proxy config                        (交互式向导)${NC}"
    echo ""
    echo -e "    ${CYAN}help${NC}  [-h|--help]        显示此帮助"
    echo ""
    echo -e "  ${BOLD}快速开始${NC}"
    echo -e "    ${DIM}1. proxy config 10.60.60.39:7890${NC}"
    echo -e "    ${DIM}2. proxy on${NC}"
    echo -e "    ${DIM}3. proxy ping${NC}"
    echo -e "    ${DIM}4. proxy off${NC}"
    echo ""
}

# ── 入口 ──────────────────────────────────────────────────────────────────────
main() {
    local cmd="${1:-help}"
    shift || true

    case "$cmd" in
        on)              cmd_on "$@" ;;
        off)             cmd_off ;;
        ping)            cmd_ping "$@" ;;
        status)          cmd_status ;;
        config)          cmd_config "$@" ;;
        help|-h|--help)  cmd_help ;;
        *)
            error "未知命令: $cmd"
            cmd_help
            exit 1
            ;;
    esac
}

main "$@"

install_proxy.sh

#!/usr/bin/env bash
# =============================================================================
# install_proxy.sh — proxy 工具安装脚本
# 用法: sudo bash install_proxy.sh
# =============================================================================

set -euo pipefail

BIN_PATH="/usr/local/bin/_proxy_bin"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROXY_SCRIPT="$SCRIPT_DIR/proxy"

FUNC_MARKER_BEGIN="# >>> proxy-manager function <<<"
FUNC_MARKER_END="# <<< proxy-manager function <<<"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'

info()    { echo -e "\033[0;34mℹ\033[0m  $*"; }
success() { echo -e "${GREEN}${NC}  $*"; }
warn()    { echo -e "${YELLOW}${NC}  $*"; }
error()   { echo -e "${RED}${NC}  $*" >&2; }
die()     { error "$*"; exit 1; }

# ── 检查 root ─────────────────────────────────────────────────────────────────
if [[ $EUID -ne 0 ]]; then
    die "请使用 sudo 运行安装脚本: sudo bash install_proxy.sh"
fi

REAL_USER="${SUDO_USER:-$USER}"
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
BASHRC="$REAL_HOME/.bashrc"
ZSHRC="$REAL_HOME/.zshrc"

echo ""
echo -e "${BOLD}  proxy 安装程序 v3.0${NC}"
echo -e "  ${DIM}正在检查依赖...${NC}"
echo ""

# ── 检查依赖 ──────────────────────────────────────────────────────────────────
for dep in curl bash; do
    if command -v "$dep" &>/dev/null; then
        success "依赖 ${CYAN}${dep}${NC} 已安装"
    else
        die "缺少依赖: $dep  (请先安装: sudo apt install $dep)"
    fi
done

# ── 安装二进制 ────────────────────────────────────────────────────────────────
echo ""
info "安装到 ${CYAN}${BIN_PATH}${NC} ..."

[[ -f "$PROXY_SCRIPT" ]] || die "未找到 proxy 脚本: $PROXY_SCRIPT"

cp "$PROXY_SCRIPT" "$BIN_PATH"
chmod 755 "$BIN_PATH"
success "脚本已安装: ${CYAN}${BIN_PATH}${NC}"

# ── 注入 proxy shell function ─────────────────────────────────────────────────
# proxy function 的职责:
#   1. 将当前 shell 的代理状态(http_proxy)通过环境变量传给 _proxy_bin
#   2. 调用 _proxy_bin,捕获输出中的 __EVAL__: 行
#   3. 在当前 shell 中 eval 这些命令,实现环境变量的真正修改

FUNC_BLOCK=$(cat <<'FUNCEOF'

# >>> proxy-manager function <<<
proxy() {
    local _output _line _eval_cmds=""
    # 将当前窗口代理状态传入子进程,供 ping/status 读取
    export _PROXY_CURRENT_URL="${http_proxy:-}"
    # 捕获 _proxy_bin 的全部输出
    _output=$(_proxy_bin "$@" 2>&1)
    local _exit_code=$?
    # 逐行处理:__EVAL__: 开头的行在当前 shell 执行,其余直接打印
    while IFS= read -r _line; do
        if [[ "$_line" == __EVAL__:* ]]; then
            _eval_cmds="${_line#__EVAL__:}"
        else
            echo -e "$_line"
        fi
    done <<< "$_output"
    # 在当前 shell 中执行环境变量操作
    if [[ -n "$_eval_cmds" ]]; then
        eval "$_eval_cmds"
        # 同步更新传递给子进程的状态变量
        export _PROXY_CURRENT_URL="${http_proxy:-}"
    fi
    unset _PROXY_CURRENT_URL
    return $_exit_code
}
# <<< proxy-manager function <<<
FUNCEOF
)

_inject_function() {
    local rcfile="$1"
    [[ -f "$rcfile" ]] || return 0
    # 清除旧版注入
    sed -i "/# >>> proxy-manager function <<</,/# <<< proxy-manager function <<</d" "$rcfile"
    # 同时清除旧版 v1/v2 的 short 标记(兼容旧安装)
    sed -i "/# >>> proxy-manager short <<</,/# <<< proxy-manager short <<</d" "$rcfile"
    sed -i "/# >>> proxy-manager global <<</,/# <<< proxy-manager global <<</d" "$rcfile"
    echo "$FUNC_BLOCK" >> "$rcfile"
    chown "$REAL_USER:$(id -gn "$REAL_USER")" "$rcfile"
}

echo ""
info "注入 proxy shell function ..."

_inject_function "$BASHRC"
success "已写入 ${CYAN}${BASHRC}${NC}"

if [[ -f "$ZSHRC" ]]; then
    _inject_function "$ZSHRC"
    success "已写入 ${CYAN}${ZSHRC}${NC}"
fi

# ── 首次配置向导 ──────────────────────────────────────────────────────────────
echo ""
echo -e "  ${BOLD}首次配置${NC}"
echo ""

CONFIG_FILE="$REAL_HOME/.config/proxy-manager/config"

if [[ -f "$CONFIG_FILE" ]]; then
    warn "检测到已有配置文件,跳过首次配置向导"
    info "如需修改代理地址,请运行: proxy config <host>:<port>"
else
    read -rp "  请输入代理地址 (host:port,如 10.60.60.39:7890,回车跳过): " raw
    if [[ -n "$raw" ]]; then
        sudo -u "$REAL_USER" "$BIN_PATH" config "$raw" || \
            warn "配置写入失败,请安装后手动运行: proxy config <host>:<port>"
    else
        warn "跳过配置,请稍后手动运行: proxy config <host>:<port>"
    fi
fi

# ── 完成 ──────────────────────────────────────────────────────────────────────
echo ""
echo -e "  ${GREEN}${BOLD}安装完成!${NC}"
echo ""
echo -e "  ${BOLD}⚠  重要:${NC}请执行以下命令使 proxy function 在当前终端生效:"
echo -e "    ${CYAN}source ~/.bashrc${NC}"
echo ""
echo -e "  ${BOLD}快速上手${NC}"
echo -e "    ${DIM}proxy config 10.60.60.39:7890  # 配置代理地址${NC}"
echo -e "    ${DIM}proxy on                       # 开启全局代理${NC}"
echo -e "    ${DIM}proxy ping                     # 测试连通性${NC}"
echo -e "    ${DIM}proxy status                   # 查看状态${NC}"
echo -e "    ${DIM}proxy off                      # 关闭代理${NC}"
echo -e "    ${DIM}proxy -h                       # 查看帮助${NC}"
echo ""