Files
cellular-proxy/scripts/upgrade.sh
T
Hermes ccace51535 feat: 增量升级路径 --upgrade / --ui-only
- 已装机器默认自动走升级,保留 secret/网卡/账密
- 默认不重下 sing-box、不 rebind、不 verify
- scripts/upgrade.sh + install.sh 入口 + README
2026-07-21 12:48:00 +00:00

362 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 增量升级:保留 settings/密钥/网卡绑定,默认不重下 sing-box、不重探测、不强制 verify
# 用法:
# sudo ./scripts/upgrade.sh
# sudo ./scripts/upgrade.sh --ui-only
# sudo ./scripts/upgrade.sh --force-binary
# sudo ./scripts/upgrade.sh --rebind
# sudo ./scripts/upgrade.sh --verify
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=lib.sh
source "$ROOT_DIR/scripts/lib.sh"
UI_ONLY=false
FORCE_BINARY=false
DO_REBIND=false
DO_VERIFY=false
SKIP_START=false
usage() {
cat <<'H'
cellular-proxy 增量升级
保留: settings.conf / PANEL_SECRET / PROXY_* / CELLULAR_IFACE
默认: 不重新探测网卡、不重下 sing-box(已存在且可执行则跳过)、不强制出口验证
选项:
--ui-only 只更新 UI 面板
--force-binary 强制重下/重装 sing-box
--rebind 升级时重新探测数据网卡并写回配置
--verify 升级后跑出口验证
--skip-start 只落盘,不 restart 服务
-h, --help
H
}
while [[ $# -gt 0 ]]; do
case "$1" in
--ui-only) UI_ONLY=true; shift ;;
--force-binary) FORCE_BINARY=true; shift ;;
--rebind) DO_REBIND=true; shift ;;
--verify) DO_VERIFY=true; shift ;;
--skip-start) SKIP_START=true; shift ;;
-h|--help) usage; exit 0 ;;
*) die "未知参数: $1" ;;
esac
done
need_root
# 优先用已安装目录的配置
INST_CANDIDATES=(
"${INSTALL_DIR:-}"
"/opt/cellular-proxy"
"$ROOT_DIR"
)
EXISTING=""
for d in "${INST_CANDIDATES[@]}"; do
[[ -n "$d" ]] || continue
if [[ -f "$d/etc/settings.conf" ]]; then
EXISTING="$d"
break
fi
done
if [[ -z "$EXISTING" ]]; then
die "未检测到已安装实例(缺少 \$INSTALL_DIR/etc/settings.conf)。
请先全量安装:
curl -fsSL 'https://gitea.chickliu.fun/Hermes/cellular-proxy/raw/branch/main/install.sh' | sudo bash"
fi
export CONFIG_FILE="$EXISTING/etc/settings.conf"
load_config
INSTALL_DIR="${INSTALL_DIR:-$EXISTING}"
# 若 settings 里 INSTALL_DIR 与探测到的不一致,以配置为准;配置空则用探测目录
if [[ -z "${INSTALL_DIR:-}" || ! -d "$INSTALL_DIR" ]]; then
INSTALL_DIR="$EXISTING"
fi
export INSTALL_DIR
info "======== cellular-proxy 增量升级 ========"
info "安装目录: $INSTALL_DIR"
info "配置文件: $CONFIG_FILE(将保留密钥/网卡/账密)"
ensure_dirs
mkdir -p /var/lib/cellular-proxy "$INSTALL_DIR/bin" "$INSTALL_DIR/etc" "$INSTALL_DIR/ui" "$INSTALL_DIR/scripts" "$INSTALL_DIR/generated"
# --- UI only fast path ---
if [[ "$UI_ONLY" == "true" ]]; then
info "模式: 仅更新 UI"
if [[ ! -f "$ROOT_DIR/ui/index.html" ]]; then
die "源码缺少 ui/index.html"
fi
cp -a "$ROOT_DIR/ui/." "$INSTALL_DIR/ui/"
info "UI 已更新 -> $INSTALL_DIR/ui"
# 静态文件无需重启代理;admin 也不必
echo
info "完成(ui-only)。浏览器强刷: http://<LAN-IP>:${PANEL_PORT}/ui/"
exit 0
fi
# --- 可选 rebind ---
if [[ "$DO_REBIND" == "true" ]]; then
info "重新探测数据网卡…"
cell="$(detect_cellular_iface "" || true)"
[[ -n "$cell" ]] || die "未能探测数据网卡"
src="$(detect_source_ip "$cell" || true)"
persist_cellular_to_settings "$CONFIG_FILE" "$cell" "$src"
load_config
CELLULAR_IFACE="$cell"
CELLULAR_SOURCE_IP="${src:-}"
info "已 rebind: $CELLULAR_IFACE ${CELLULAR_SOURCE_IP:-}"
fi
cell="${CELLULAR_IFACE:-}"
if [[ -z "$cell" ]] || ! iface_exists "$cell"; then
warn "配置中的 CELLULAR_IFACE=${cell:-} 无效,尝试自动探测(仅本次)"
cell="$(detect_cellular_iface "" || true)"
if [[ -n "$cell" ]]; then
src="$(detect_source_ip "$cell" || true)"
persist_cellular_to_settings "$CONFIG_FILE" "$cell" "$src"
load_config
CELLULAR_IFACE="$cell"
CELLULAR_SOURCE_IP="${src:-$CELLULAR_SOURCE_IP}"
else
die "无有效数据网卡。请: cpxy rebind 或 --rebind / --iface"
fi
fi
src_ip="${CELLULAR_SOURCE_IP:-$(detect_source_ip "$cell" || true)}"
# --- binary ---
need_binary=true
if [[ "$FORCE_BINARY" != "true" && -x "$INSTALL_DIR/bin/sing-box" ]]; then
if "$INSTALL_DIR/bin/sing-box" version >/dev/null 2>&1; then
cur="$("$INSTALL_DIR/bin/sing-box" version 2>/dev/null | head -1 || true)"
info "复用已有 sing-box: $cur"
need_binary=false
fi
fi
if [[ "$need_binary" == "true" ]]; then
info "安装/更新 sing-box 二进制"
FORCE_DOWNLOAD="${FORCE_DOWNLOAD:-}"
if [[ "$FORCE_BINARY" == "true" ]]; then
export FORCE_DOWNLOAD=1
fi
# fetch 会同时拷 UI;后面还会再拷一遍 scripts/ui 没关系
export CONFIG_FILE
"$ROOT_DIR/scripts/fetch-binaries.sh"
else
info "跳过二进制下载(需要强制时加 --force-binary"
mkdir -p "$INSTALL_DIR/ui"
if [[ -f "$ROOT_DIR/ui/index.html" ]]; then
cp -a "$ROOT_DIR/ui/." "$INSTALL_DIR/ui/"
info "已更新 UI"
fi
fi
# --- scripts / cpxy / generate config ---
info "更新 scripts / 配置生成 / systemd"
# 同步新脚本到安装目录,但不要覆盖 etc/settings
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete \
--exclude 'settings.conf' \
"$ROOT_DIR/scripts/" "$INSTALL_DIR/scripts/"
else
mkdir -p "$INSTALL_DIR/scripts"
cp -a "$ROOT_DIR/scripts/." "$INSTALL_DIR/scripts/"
fi
chmod +x "$INSTALL_DIR/scripts/"*.sh "$INSTALL_DIR/scripts/"*.py 2>/dev/null || true
# 生成配置(用现有 settings,不改 secret
export CONFIG_FILE
"$ROOT_DIR/scripts/generate.sh" "$INSTALL_DIR/generated"
install -m 0644 "$INSTALL_DIR/generated/config.json" "$INSTALL_DIR/etc/config.json"
install -m 0644 "$INSTALL_DIR/generated/runtime.env" "$INSTALL_DIR/etc/runtime.env"
# 保留 settings:只确保 INSTALL_DIR 字段正确
if ! grep -qE '^INSTALL_DIR=' "$CONFIG_FILE" 2>/dev/null; then
printf 'INSTALL_DIR=%s\n' "$INSTALL_DIR" >> "$CONFIG_FILE"
fi
install -m 0644 "$CONFIG_FILE" "$INSTALL_DIR/etc/settings.conf"
if ! "$INSTALL_DIR/bin/sing-box" check -c "$INSTALL_DIR/etc/config.json"; then
die "sing-box 配置校验失败"
fi
# 校验 bind
if ! python3 - "$INSTALL_DIR/etc/config.json" "$cell" <<'PY'
import json,sys
c=json.load(open(sys.argv[1]))
cell=sys.argv[2]
ok=any(o.get("tag")=="cellular" and o.get("bind_interface")==cell for o in c.get("outbounds",[]))
sys.exit(0 if ok else 1)
PY
then
die "配置未正确绑定数据网卡 $cell"
fi
info "配置已确认 bind_interface=$cell"
# 安装 cpxy 包装器(与 install.sh 一致,含 upgrade 子命令)
install -m 0755 /dev/stdin "$INSTALL_DIR/bin/cpxy" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
BASE="$(cd "$(dirname "$0")/.." && pwd)"
export CONFIG_FILE="${CONFIG_FILE:-$BASE/etc/settings.conf}"
cmd="${1:-help}"
shift || true
case "$cmd" in
detect) exec "$BASE/scripts/detect.sh" "$@" ;;
generate)
# shellcheck source=/dev/null
source "$BASE/scripts/lib.sh"
load_config
if [[ -z "${CELLULAR_IFACE:-}" ]] || ! iface_exists "${CELLULAR_IFACE:-}"; then
cell="$(detect_cellular_iface "" || true)"
if [[ -n "$cell" ]]; then
src="$(detect_source_ip "$cell" || true)"
persist_cellular_to_settings "$CONFIG_FILE" "$cell" "$src"
CELLULAR_IFACE="$cell"
fi
fi
"$BASE/scripts/generate.sh" "$BASE/generated"
install -m 0644 "$BASE/generated/config.json" "$BASE/etc/config.json"
install -m 0644 "$BASE/generated/runtime.env" "$BASE/etc/runtime.env"
install -m 0644 "$CONFIG_FILE" "$BASE/etc/settings.conf" 2>/dev/null || true
"$BASE/bin/sing-box" check -c "$BASE/etc/config.json"
systemctl restart cellular-proxy 2>/dev/null || true
systemctl restart cellular-proxy-admin 2>/dev/null || true
;;
rebind)
# shellcheck source=/dev/null
source "$BASE/scripts/lib.sh"
load_config
cell="$(detect_cellular_iface "" || true)"
[[ -n "$cell" ]] || die "未能探测数据网卡"
src="$(detect_source_ip "$cell" || true)"
persist_cellular_to_settings "$CONFIG_FILE" "$cell" "$src"
info "已重新绑定 $cell"
exec "$0" generate
;;
upgrade)
# 拉取在线升级入口,或本地 scripts/upgrade
if [[ -x "$BASE/scripts/upgrade.sh" ]]; then
# 在线: 用户应使用 install.sh --upgrade;本地源码树内可直接
exec "$BASE/scripts/upgrade.sh" "$@"
fi
die "请使用: curl .../install.sh | sudo bash -s -- --upgrade"
;;
start) systemctl start cellular-proxy cellular-proxy-admin 2>/dev/null || systemctl start cellular-proxy ;;
stop) systemctl stop cellular-proxy-admin 2>/dev/null || true; systemctl stop cellular-proxy ;;
restart) systemctl restart cellular-proxy; systemctl restart cellular-proxy-admin 2>/dev/null || true ;;
status)
systemctl status cellular-proxy --no-pager || true
systemctl status cellular-proxy-admin --no-pager 2>/dev/null || true
;;
verify) exec "$BASE/scripts/verify.sh" "$@" ;;
auth)
exec "$BASE/scripts/apply-proxy-auth.sh" "$@"
;;
logs) journalctl -u cellular-proxy -n "${1:-80}" -f ;;
help|*)
cat <<H
cpxy — cellular-proxy
语义: 走代理的连接一律从数据网卡出口
cpxy detect | rebind | generate | start | stop | restart | status
cpxy verify | logs [N]
cpxy auth --user U --pass P | --clear | --show
在线增量升级:
curl -fsSL 'https://gitea.chickliu.fun/Hermes/cellular-proxy/raw/branch/main/install.sh' | sudo bash -s -- --upgrade
curl ... | sudo bash -s -- --upgrade --ui-only
curl ... | sudo bash -s -- --upgrade --force-binary
H
;;
esac
EOF
ln -sfn "$INSTALL_DIR/bin/cpxy" /usr/local/bin/cpxy
# systemd
MEM="${MEMORY_MAX_MB:-96}"
if [[ -f "$ROOT_DIR/systemd/cellular-proxy.service" ]]; then
install -m 0644 "$ROOT_DIR/systemd/cellular-proxy.service" /etc/systemd/system/cellular-proxy.service
sed -i "s|@INSTALL_DIR@|$INSTALL_DIR|g" /etc/systemd/system/cellular-proxy.service
sed -i "s|@LOG_DIR@|$LOG_DIR|g" /etc/systemd/system/cellular-proxy.service
if [[ "$MEM" != "0" && -n "$MEM" ]]; then
sed -i "s|@MEMORY_MAX@|${MEM}M|g" /etc/systemd/system/cellular-proxy.service
else
sed -i '/MemoryMax=@MEMORY_MAX@/d' /etc/systemd/system/cellular-proxy.service
fi
fi
if [[ -f "$ROOT_DIR/systemd/cellular-proxy-admin.service" ]]; then
install -m 0644 "$ROOT_DIR/systemd/cellular-proxy-admin.service" /etc/systemd/system/cellular-proxy-admin.service
sed -i "s|@INSTALL_DIR@|$INSTALL_DIR|g" /etc/systemd/system/cellular-proxy-admin.service
fi
systemctl daemon-reload
export SYSTEMD_PAGER=cat
export SYSTEMD_COLORS=0
if [[ "$SKIP_START" == "true" ]]; then
systemctl enable cellular-proxy.service 2>/dev/null || true
systemctl enable cellular-proxy-admin.service 2>/dev/null || true
info "已跳过 restart--skip-start"
else
systemctl enable cellular-proxy.service 2>/dev/null || true
systemctl enable cellular-proxy-admin.service 2>/dev/null || true
systemctl restart cellular-proxy.service
systemctl restart cellular-proxy-admin.service 2>/dev/null || true
ok=0
for _ in 1 2 3 4 5 6 7 8; do
if systemctl is-active --quiet cellular-proxy.service; then
ok=1
break
fi
sleep 1
done
active_state="$(systemctl is-active cellular-proxy.service 2>/dev/null || echo unknown)"
info "服务状态: $active_state"
if [[ "$ok" -ne 1 ]]; then
err "服务未 active"
journalctl -u cellular-proxy -n 30 --no-pager 2>/dev/null || true
die "升级后启动失败"
fi
fi
if [[ "$DO_VERIFY" == "true" && "$SKIP_START" != "true" ]]; then
info "出口验证…"
set +e
VERIFY_QUICK=1 EGRESS_TIMEOUT="${EGRESS_TIMEOUT:-8}" "$INSTALL_DIR/scripts/verify.sh"
vr=$?
set -e
if [[ "$vr" -eq 0 ]]; then
info "出口验证通过"
else
warn "验证未通过(exit=$vr),可稍后: cpxy verify"
fi
else
info "已跳过出口验证(需要时: --verify 或 cpxy verify"
fi
# shellcheck disable=SC1090
source "$INSTALL_DIR/etc/settings.conf" 2>/dev/null || true
lan="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}' || true)"
cat <<EOM
========== 增量升级完成 ==========
安装目录: $INSTALL_DIR
数据网卡: ${CELLULAR_IFACE:-$cell} (${src_ip:-})
保留: PANEL_SECRET / PROXY_USER / 端口等 settings
代理: ${lan:-127.0.0.1}:${PROXY_MIXED_PORT:-7890}
面板: http://${lan:-<LAN-IP>}:${PANEL_PORT:-9090}/ui/
管理: :9091(账密配置)
下次升级:
curl -fsSL 'https://gitea.chickliu.fun/Hermes/cellular-proxy/raw/branch/main/install.sh' | sudo bash -s -- --upgrade
# 只刷面板:
... | sudo bash -s -- --upgrade --ui-only
# 强制重下 sing-box:
... | sudo bash -s -- --upgrade --force-binary
EOM
exit 0