feat: 一条 curl 自动装/升(未装安装、已装升级)

This commit is contained in:
Hermes
2026-07-21 14:23:31 +00:00
commit b2a7332f86
20 changed files with 3459 additions and 0 deletions
+357
View File
@@ -0,0 +1,357 @@
#!/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)
# 一条命令在线升级:拉最新 install.sh 再 --upgrade
if [[ -x "$BASE/scripts/online-upgrade.sh" ]]; then
exec "$BASE/scripts/online-upgrade.sh" "$@"
fi
url="${CELLULAR_PROXY_INSTALL_URL:-https://gitea.chickliu.fun/Hermes/cellular-proxy/raw/branch/main/install.sh}"
exec bash -c 'curl -fsSL "$1" | bash -s -- --upgrade --install-dir "$2" "${@:3}"' _ "$url" "$BASE" "$@"
;;
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
cpxy upgrade # 一条命令在线增量升级
cpxy upgrade --ui-only | --force-binary | --verify
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(账密配置)
下次升级:
sudo cpxy upgrade
sudo cpxy upgrade --ui-only
sudo cpxy upgrade --force-binary
EOM
exit 0