- sing-box 单进程 bind 数据网卡 - mixed HTTP+SOCKS + 中文静态面板 - install.sh 支持 curl | sudo bash - 512MB 友好 MemoryMax 默认 96MB
249 lines
7.4 KiB
Bash
249 lines
7.4 KiB
Bash
#!/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 '...' | sudo bash -s -- --iface wwan0 --secret 'xxx' --user u --pass p
|
||
# sudo bash install.sh --iface usb0 --port 7890 --panel-port 9090
|
||
#
|
||
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}"
|
||
# 可用环境变量覆盖:CELLULAR_PROXY_REF=v1.0.0 或 commit sha
|
||
REF="${CELLULAR_PROXY_REF:-$BRANCH}"
|
||
|
||
INSTALL_DIR="${INSTALL_DIR:-/opt/cellular-proxy}"
|
||
WORK_DIR="${WORK_DIR:-/tmp/cellular-proxy-install-$$}"
|
||
|
||
# 可选 CLI 预填(也可装完再改 settings)
|
||
OPT_IFACE=""
|
||
OPT_SECRET=""
|
||
OPT_USER=""
|
||
OPT_PASS=""
|
||
OPT_PORT=""
|
||
OPT_PANEL_PORT=""
|
||
OPT_MEMORY=""
|
||
OPT_SKIP_START=0
|
||
OPT_LOCAL_DIR=""
|
||
|
||
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 一键安装
|
||
|
||
选项:
|
||
--iface NAME 数据网卡名(如 wwan0 / usb0)
|
||
--secret STR 面板 PANEL_SECRET
|
||
--user USER 代理账号 PROXY_USER
|
||
--pass PASS 代理密码 PROXY_PASS
|
||
--port N 代理 mixed 端口(默认 7890)
|
||
--panel-port N 面板端口(默认 9090)
|
||
--memory MB MemoryMax MB(默认 96;0=不限制)
|
||
--install-dir PATH 安装目录(默认 /opt/cellular-proxy)
|
||
--local DIR 使用本地源码目录,不从 Gitea 下载
|
||
--ref REF 分支/tag/commit(默认 main)
|
||
--skip-start 只安装不启动
|
||
-h, --help 帮助
|
||
|
||
环境变量:
|
||
GITEA_BASE REPO_OWNER REPO_NAME CELLULAR_PROXY_REF INSTALL_DIR
|
||
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 ;;
|
||
-h|--help) usage; exit 0 ;;
|
||
*) die "未知参数: $1(--help 查看用法)" ;;
|
||
esac
|
||
done
|
||
|
||
need_root() {
|
||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||
die "请用 root 运行:sudo bash install.sh ..."
|
||
fi
|
||
}
|
||
|
||
need_cmd() {
|
||
command -v "$1" >/dev/null 2>&1 || die "缺少命令: $1"
|
||
}
|
||
|
||
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"
|
||
elif command -v wget >/dev/null 2>&1; then
|
||
wget -O "$dest" "$url"
|
||
else
|
||
die "需要 curl 或 wget"
|
||
fi
|
||
}
|
||
|
||
set_kv() {
|
||
# set_kv file KEY value — 存在则替换行,否则追加
|
||
local file="$1" key="$2" val="$3"
|
||
local tmp
|
||
tmp="$(mktemp)"
|
||
if grep -qE "^${key}=" "$file" 2>/dev/null; then
|
||
# 用 awk 避免 sed 对特殊字符敏感
|
||
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 "下载失败。请检查网络或: git clone ${GITEA_BASE}/${REPO_OWNER}/${REPO_NAME}.git"
|
||
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"
|
||
[[ -f "$src/config/settings.conf.example" ]] || die "缺少 settings.conf.example"
|
||
cp "$src/config/settings.conf.example" "$conf"
|
||
|
||
if [[ -n "$OPT_IFACE" ]]; then
|
||
set_kv "$conf" CELLULAR_IFACE "$OPT_IFACE"
|
||
fi
|
||
if [[ -n "$OPT_SECRET" ]]; then
|
||
set_kv "$conf" PANEL_SECRET "$OPT_SECRET"
|
||
else
|
||
# 随机 secret,避免默认 please-change-me 裸奔
|
||
local gen
|
||
gen="$(openssl rand -hex 12 2>/dev/null || head -c 16 /dev/urandom | xxd -p)"
|
||
set_kv "$conf" PANEL_SECRET "$gen"
|
||
warn "已生成随机 PANEL_SECRET(见 $INSTALL_DIR/etc/settings.conf)"
|
||
fi
|
||
if [[ -n "$OPT_USER" ]]; then
|
||
set_kv "$conf" PROXY_USER "$OPT_USER"
|
||
fi
|
||
if [[ -n "$OPT_PASS" ]]; then
|
||
set_kv "$conf" PROXY_PASS "$OPT_PASS"
|
||
fi
|
||
if [[ -n "$OPT_PORT" ]]; then
|
||
set_kv "$conf" PROXY_MIXED_PORT "$OPT_PORT"
|
||
fi
|
||
if [[ -n "$OPT_PANEL_PORT" ]]; then
|
||
set_kv "$conf" PANEL_PORT "$OPT_PANEL_PORT"
|
||
fi
|
||
if [[ -n "$OPT_MEMORY" ]]; then
|
||
set_kv "$conf" MEMORY_MAX_MB "$OPT_MEMORY"
|
||
fi
|
||
set_kv "$conf" INSTALL_DIR "$INSTALL_DIR"
|
||
}
|
||
|
||
main() {
|
||
need_root
|
||
need_cmd tar
|
||
need_cmd awk
|
||
command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 || die "需要 curl 或 wget"
|
||
|
||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||
|
||
info "1/4 获取源码"
|
||
fetch_source
|
||
|
||
info "2/4 写入 settings.conf"
|
||
prepare_settings
|
||
|
||
info "3/4 调用官方 install.sh(下载 sing-box + systemd)"
|
||
# 临时把 install 行为调整:settings 已存在
|
||
export CONFIG_FILE="$WORK_DIR/src/config/settings.conf"
|
||
bash "$WORK_DIR/src/scripts/install.sh"
|
||
|
||
if [[ "$OPT_SKIP_START" -eq 1 ]]; then
|
||
systemctl stop cellular-proxy 2>/dev/null || true
|
||
info "已按 --skip-start 停止服务"
|
||
fi
|
||
|
||
info "4/4 完成"
|
||
local port panel secret
|
||
# shellcheck disable=SC1090
|
||
source "$INSTALL_DIR/etc/settings.conf" 2>/dev/null || true
|
||
port="${PROXY_MIXED_PORT:-7890}"
|
||
panel="${PANEL_PORT:-9090}"
|
||
secret="${PANEL_SECRET:-见 $INSTALL_DIR/etc/settings.conf}"
|
||
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
|
||
|
||
========== 安装完成 ==========
|
||
语义: 走代理 → 数据流量;不走代理 → 系统默认(WiFi)
|
||
|
||
代理: HTTP/SOCKS ${lan:-<LAN-IP>}:${port}
|
||
面板: http://${lan:-<LAN-IP>}:${panel}/
|
||
密钥: ${secret}
|
||
配置: $INSTALL_DIR/etc/settings.conf
|
||
|
||
验证:
|
||
cpxy verify
|
||
curl --socks5-hostname 127.0.0.1:${port} https://ifconfig.me
|
||
|
||
管理:
|
||
cpxy status | logs | generate | restart
|
||
EOM
|
||
}
|
||
|
||
main "$@"
|