Files
cellular-proxy/install.sh
T

405 lines
13 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
# cellular-proxy 全自动一键安装 / 增量升级
# 语义:走本代理的连接一律从数据网卡(流量)出口;系统默认仍可走 WiFi
#
# 全新安装:
# curl -fsSL 'https://gitea.chickliu.fun/Hermes/cellular-proxy/raw/branch/main/install.sh' | sudo bash
#
# 增量升级(推荐,已装机器):
# curl -fsSL 'https://gitea.chickliu.fun/Hermes/cellular-proxy/raw/branch/main/install.sh' | sudo bash -s -- --upgrade
# ... | sudo bash -s -- --upgrade --ui-only
# ... | sudo bash -s -- --upgrade --force-binary
#
# 可选参数:
# ... | sudo bash -s -- --iface wwan0 --secret xxx --user u --pass p
#
set -euo pipefail
REPO_OWNER="${REPO_OWNER:-Hermes}"
REPO_NAME="${REPO_NAME:-cellular-proxy}"
GITEA_BASE="${GITEA_BASE:-https://gitea.chickliu.fun}"
BRANCH="${BRANCH:-main}"
REF="${CELLULAR_PROXY_REF:-$BRANCH}"
INSTALL_DIR="${INSTALL_DIR:-/opt/cellular-proxy}"
WORK_DIR="${WORK_DIR:-/tmp/cellular-proxy-install-$$}"
OPT_IFACE=""
OPT_SECRET=""
OPT_USER=""
OPT_PASS=""
OPT_PORT=""
OPT_PANEL_PORT=""
OPT_MEMORY=""
OPT_SKIP_START=0
OPT_SKIP_VERIFY=0
OPT_LOCAL_DIR=""
OPT_NO_AUTO_DETECT=0
OPT_UPGRADE=0
OPT_UI_ONLY=0
OPT_FORCE_BINARY=0
OPT_REBIND=0
OPT_FORCE_INSTALL=0
OPT_DO_VERIFY=0
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" >&2; }
info() { log "INFO $*"; }
warn() { log "WARN $*"; }
err() { log "ERROR $*"; }
die() { err "$*"; exit 1; }
usage() {
cat <<'H'
cellular-proxy 全自动一键安装 / 增量升级
全新安装(无参数):
自动探测数据网卡 → 绑定出口 → 装 sing-box → 启动 → 验证
增量升级(已装机器推荐):
保留 settings / 密钥 / 网卡绑定;默认不重下二进制、不重探测
--upgrade 增量升级
--ui-only 仅更新面板(需配合 --upgrade,或自动识别已安装)
--force-binary 升级时强制重下 sing-box
--rebind 升级时重新探测数据网卡
--verify 升级后跑出口验证(升级默认跳过验证以加快)
--force-install 即使已安装也走全量安装(会尽量沿用已有 settings)
其它选项(全可选):
--iface NAME 强制指定数据网卡
--secret STR 面板 PANEL_SECRET(默认随机;升级时不改已有)
--user USER 代理账号(仅全量/显式传入时写入)
--pass PASS 代理密码
--port N 代理端口(默认 7890)
--panel-port N 面板端口(默认 9090)
--memory MB MemoryMax(默认 96
--install-dir PATH 安装目录
--local DIR 本地源码,不拉 Gitea
--ref REF 分支/tag
--skip-start 不启动服务
--skip-verify 跳过出口验证
--no-auto-detect 禁止自动探测(必须 --iface)
-h, --help
H
}
while [[ $# -gt 0 ]]; do
case "$1" in
--iface) OPT_IFACE="${2:-}"; shift 2 ;;
--secret) OPT_SECRET="${2:-}"; shift 2 ;;
--user) OPT_USER="${2:-}"; shift 2 ;;
--pass) OPT_PASS="${2:-}"; shift 2 ;;
--port) OPT_PORT="${2:-}"; shift 2 ;;
--panel-port) OPT_PANEL_PORT="${2:-}"; shift 2 ;;
--memory) OPT_MEMORY="${2:-}"; shift 2 ;;
--install-dir) INSTALL_DIR="${2:-}"; shift 2 ;;
--local) OPT_LOCAL_DIR="${2:-}"; shift 2 ;;
--ref) REF="${2:-}"; shift 2 ;;
--skip-start) OPT_SKIP_START=1; shift ;;
--skip-verify) OPT_SKIP_VERIFY=1; shift ;;
--no-auto-detect) OPT_NO_AUTO_DETECT=1; shift ;;
--upgrade) OPT_UPGRADE=1; shift ;;
--ui-only) OPT_UI_ONLY=1; shift ;;
--force-binary) OPT_FORCE_BINARY=1; shift ;;
--rebind) OPT_REBIND=1; shift ;;
--force-install) OPT_FORCE_INSTALL=1; shift ;;
--verify) OPT_DO_VERIFY=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "未知参数: $1--help" ;;
esac
done
need_root() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
die "请用 rootcurl -fsSL '...' | sudo bash"
fi
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "缺少命令: $1"
}
is_installed() {
local d="${1:-$INSTALL_DIR}"
[[ -f "$d/etc/settings.conf" && -x "$d/bin/sing-box" ]] || [[ -f "$d/etc/settings.conf" ]]
}
ensure_deps() {
local missing=()
for c in tar awk ip curl; do
command -v "$c" >/dev/null 2>&1 || missing+=("$c")
done
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
missing+=("curl")
fi
if [[ ${#missing[@]} -eq 0 ]]; then
return 0
fi
info "尝试自动安装依赖: ${missing[*]}"
if command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq ca-certificates curl tar iproute2 openssl coreutils findutils 2>/dev/null \
|| apt-get install -y ca-certificates curl tar iproute2 openssl
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache ca-certificates curl tar iproute2 openssl
else
die "缺少依赖 ${missing[*]},且无法自动安装(请先装 curl/tar/iproute2"
fi
}
download() {
local url="$1" dest="$2"
if command -v curl >/dev/null 2>&1; then
curl -fL --retry 3 --connect-timeout 20 -o "$dest" "$url"
else
wget -O "$dest" "$url"
fi
}
set_kv() {
local file="$1" key="$2" val="$3"
local tmp
tmp="$(mktemp)"
if grep -qE "^${key}=" "$file" 2>/dev/null; then
awk -v k="$key" -v v="$val" '
BEGIN { done=0 }
index($0, k "=")==1 { print k "=" v; done=1; next }
{ print }
END { if (!done) print k "=" v }
' "$file" > "$tmp"
mv "$tmp" "$file"
else
printf '%s=%s\n' "$key" "$val" >> "$file"
rm -f "$tmp"
fi
}
fetch_source() {
mkdir -p "$WORK_DIR"
if [[ -n "$OPT_LOCAL_DIR" ]]; then
[[ -d "$OPT_LOCAL_DIR" ]] || die "本地目录不存在: $OPT_LOCAL_DIR"
info "使用本地源码: $OPT_LOCAL_DIR"
mkdir -p "$WORK_DIR/src"
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete \
--exclude '.git' --exclude 'generated' --exclude 'config/settings.conf' \
"$OPT_LOCAL_DIR"/ "$WORK_DIR/src"/
else
cp -a "$OPT_LOCAL_DIR"/. "$WORK_DIR/src"/
rm -rf "$WORK_DIR/src/.git" "$WORK_DIR/src/generated" "$WORK_DIR/src/config/settings.conf" 2>/dev/null || true
fi
return
fi
local tarball="$WORK_DIR/src.tgz"
local url_archive="${GITEA_BASE}/${REPO_OWNER}/${REPO_NAME}/archive/${REF}.tar.gz"
info "下载源码: $url_archive"
if ! download "$url_archive" "$tarball"; then
die "下载失败: $url_archive"
fi
mkdir -p "$WORK_DIR/extract"
tar -xzf "$tarball" -C "$WORK_DIR/extract"
local top
top="$(find "$WORK_DIR/extract" -mindepth 1 -maxdepth 1 -type d | head -1)"
[[ -n "$top" ]] || die "压缩包内容异常"
mkdir -p "$WORK_DIR/src"
if command -v rsync >/dev/null 2>&1; then
rsync -a "$top"/ "$WORK_DIR/src"/
else
cp -a "$top"/. "$WORK_DIR/src"/
fi
}
prepare_settings() {
local src="$WORK_DIR/src"
local conf="$src/config/settings.conf"
local live="${INSTALL_DIR}/etc/settings.conf"
[[ -f "$src/config/settings.conf.example" ]] || die "缺少 settings.conf.example"
# 已有安装:以 live settings 为底,不重置密钥/网卡
if [[ -f "$live" ]]; then
info "沿用已有配置: $live"
cp "$live" "$conf"
else
cp "$src/config/settings.conf.example" "$conf"
fi
# shellcheck source=/dev/null
source "$src/scripts/lib.sh"
CELLULAR_IFACE_PATTERNS="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,usb,enx,ppp,cdc,rmnet,ccmni,mbim,qmi}"
REQUIRE_CELLULAR_IFACE=false
local cell=""
if [[ -n "$OPT_IFACE" ]]; then
cell="$OPT_IFACE"
info "使用 --iface $cell"
elif [[ -f "$live" ]] && grep -qE '^CELLULAR_IFACE=.' "$live" 2>/dev/null; then
# shellcheck disable=SC1090
cell="$(awk -F= '/^CELLULAR_IFACE=/{print substr($0,index($0,"=")+1); exit}' "$live")"
if [[ -n "$cell" ]] && iface_exists "$cell"; then
info "沿用已绑定网卡: $cell"
else
cell=""
fi
fi
if [[ -z "$cell" ]]; then
if [[ "$OPT_NO_AUTO_DETECT" -eq 1 ]]; then
die "--no-auto-detect 时必须提供 --iface"
fi
info "自动探测数据网卡…"
cell="$(detect_cellular_iface "" || true)"
fi
if [[ -z "$cell" ]]; then
die "自动探测失败:未找到数据网卡。
请确认模组/拨号已 up,或指定:
... | sudo bash -s -- --iface <网卡名>
可先手动: ip -br a"
fi
if ! iface_exists "$cell"; then
die "网卡不存在: $cell"
fi
local src_ip
src_ip="$(detect_source_ip "$cell" || true)"
set_kv "$conf" CELLULAR_IFACE "$cell"
if [[ -n "$src_ip" ]]; then
set_kv "$conf" CELLULAR_SOURCE_IP "$src_ip"
fi
info "已写入 CELLULAR_IFACE=$cell ip=${src_ip:-none}"
# secret:仅全量且未指定且当前是占位时生成;有 live 则不覆盖
if [[ -n "$OPT_SECRET" ]]; then
set_kv "$conf" PANEL_SECRET "$OPT_SECRET"
elif [[ ! -f "$live" ]]; then
local cur
cur="$(awk -F= '/^PANEL_SECRET=/{print substr($0,index($0,"=")+1); exit}' "$conf" 2>/dev/null || true)"
if [[ -z "$cur" || "$cur" == "please-change-me" || "$cur" == "change-this-secret" ]]; then
local gen
gen="$(openssl rand -hex 12 2>/dev/null || head -c 16 /dev/urandom | xxd -p | tr -d '\n')"
set_kv "$conf" PANEL_SECRET "$gen"
info "已自动生成 PANEL_SECRET"
fi
fi
[[ -n "$OPT_USER" ]] && set_kv "$conf" PROXY_USER "$OPT_USER"
[[ -n "$OPT_PASS" ]] && set_kv "$conf" PROXY_PASS "$OPT_PASS"
[[ -n "$OPT_PORT" ]] && set_kv "$conf" PROXY_MIXED_PORT "$OPT_PORT"
[[ -n "$OPT_PANEL_PORT" ]] && set_kv "$conf" PANEL_PORT "$OPT_PANEL_PORT"
[[ -n "$OPT_MEMORY" ]] && set_kv "$conf" MEMORY_MAX_MB "$OPT_MEMORY"
set_kv "$conf" INSTALL_DIR "$INSTALL_DIR"
set_kv "$conf" REQUIRE_CELLULAR_IFACE "true"
export DETECTED_CELL="$cell"
export DETECTED_SRC_IP="$src_ip"
}
run_upgrade() {
info "======== cellular-proxy 增量升级入口 ========"
fetch_source
local args=()
[[ "$OPT_UI_ONLY" -eq 1 ]] && args+=(--ui-only)
[[ "$OPT_FORCE_BINARY" -eq 1 ]] && args+=(--force-binary)
[[ "$OPT_REBIND" -eq 1 ]] && args+=(--rebind)
# 升级默认不 verify(加快);需要时: --verify 或 UPGRADE_VERIFY=1
if [[ "${OPT_DO_VERIFY:-0}" -eq 1 ]] || [[ "${UPGRADE_VERIFY:-}" == "1" ]]; then
args+=(--verify)
fi
# 用户传了 skip-start
if [[ "$OPT_SKIP_START" -eq 1 ]]; then
args+=(--skip-start)
fi
# 把 --iface 映射为 rebind 场景下的强制:简单做法:若给了 --iface,写入 live 再 upgrade
if [[ -n "$OPT_IFACE" && -f "$INSTALL_DIR/etc/settings.conf" ]]; then
set_kv "$INSTALL_DIR/etc/settings.conf" CELLULAR_IFACE "$OPT_IFACE"
info "已写入 --iface $OPT_IFACE 到现有 settings"
fi
export INSTALL_DIR
export CONFIG_FILE="$INSTALL_DIR/etc/settings.conf"
bash "$WORK_DIR/src/scripts/upgrade.sh" "${args[@]}"
}
main() {
need_root
ensure_deps
need_cmd tar
need_cmd awk
need_cmd ip
trap 'rm -rf "$WORK_DIR"' EXIT
# --ui-only 暗示升级
if [[ "$OPT_UI_ONLY" -eq 1 ]]; then
OPT_UPGRADE=1
fi
# 已安装且未强制全量 → 自动走升级(除非显式 --force-install
if [[ "$OPT_FORCE_INSTALL" -eq 0 && "$OPT_UPGRADE" -eq 0 ]]; then
if is_installed "$INSTALL_DIR"; then
warn "检测到已安装: $INSTALL_DIR"
warn "自动切换为增量升级(保留密钥/网卡)。全量重装请加: --force-install"
OPT_UPGRADE=1
fi
fi
if [[ "$OPT_UPGRADE" -eq 1 ]]; then
if ! is_installed "$INSTALL_DIR" && [[ ! -f "$INSTALL_DIR/etc/settings.conf" ]]; then
die "未找到已安装实例 ($INSTALL_DIR)。请先无参安装,或检查 --install-dir"
fi
run_upgrade
return 0
fi
info "======== cellular-proxy 全自动安装 ========"
info "1/3 获取源码"
fetch_source
info "2/3 自动探测数据网卡并写配置"
prepare_settings
info "3/3 安装 sing-box、绑定出口、启动"
export CONFIG_FILE="$WORK_DIR/src/config/settings.conf"
export AUTO_DETECT=false
export AUTO_VERIFY=true
if [[ "$OPT_SKIP_VERIFY" -eq 1 ]]; then
export AUTO_VERIFY=false
fi
if [[ "$OPT_SKIP_START" -eq 1 ]]; then
export SKIP_START=true
else
export SKIP_START=false
fi
bash "$WORK_DIR/src/scripts/install.sh"
# shellcheck disable=SC1090
source "$INSTALL_DIR/etc/settings.conf" 2>/dev/null || true
local port="${PROXY_MIXED_PORT:-7890}"
local panel="${PANEL_PORT:-9090}"
local secret="${PANEL_SECRET:-}"
local cell="${CELLULAR_IFACE:-$DETECTED_CELL}"
local lan
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
========== 全自动安装完成 ==========
数据网卡(已绑定): ${cell}
语义: 走代理 → 数据流量;不走代理 → WiFi/默认
代理: HTTP/SOCKS ${lan:-127.0.0.1}:${port}
面板: http://${lan:-<LAN-IP>}:${panel}/ui/
密钥: ${secret}
配置: $INSTALL_DIR/etc/settings.conf
验证: cpxy verify
重绑: cpxy rebind
升级: sudo cpxy upgrade
日志: cpxy logs
EOM
}
main "$@"