feat: add one-click deploy script + project optimization
- deploy.sh: one-click deployment (venv + pip deps + .env + gunicorn + systemd + firewall) - gunicorn_config.py: unified gunicorn config with env var overrides - .env.example: full env var template (admin, security, port, session, backup, etc.) - app.py: optional dotenv .env file loading - requirements.txt: add python-dotenv
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# SOCKS Manager - 环境变量示例
|
||||
# 复制为 .env 并按需修改:cp .env.example .env
|
||||
|
||||
# ── 管理员账号 ──────────────────────────────────
|
||||
SM_ADMIN_USER=admin
|
||||
# 生产环境务必修改!首次启动如未设置会自动使用 admin123
|
||||
SM_ADMIN_PASSWORD=admin123
|
||||
|
||||
# ── Flask 安全 ──────────────────────────────────
|
||||
# 生成: python3 -c "import secrets; print(secrets.token_urlsafe(48))"
|
||||
SM_SECRET_KEY=change-me-in-production
|
||||
|
||||
# ── Web 面板监听 ────────────────────────────────
|
||||
SM_HOST=0.0.0.0
|
||||
SM_PORT=5000
|
||||
SM_WORKERS=1
|
||||
SM_TIMEOUT=30
|
||||
|
||||
# ── 会话 Cookie ─────────────────────────────────
|
||||
# 跨域/用域名访问时设置为你的域名或 IP
|
||||
# SM_SESSION_DOMAIN=
|
||||
# HTTPS 部署时设为 true
|
||||
SM_COOKIE_SECURE=false
|
||||
SM_SESSION_NAME=sm_session
|
||||
|
||||
# ── 数据库 ─────────────────────────────────────
|
||||
SM_DB_URI=sqlite:///socks_manager.db
|
||||
|
||||
# ── 备份 ───────────────────────────────────────
|
||||
SM_BACKUP_DIR=./backups/
|
||||
|
||||
# ── 日志 ───────────────────────────────────────
|
||||
SM_LOG_LEVEL=INFO
|
||||
|
||||
# ── SOCKS5 引擎 ────────────────────────────────
|
||||
# 是否启用后台 sync 线程(探活死掉的 SOCKS5 实例)
|
||||
SM_SYNC_LOOP_ENABLE=true
|
||||
SM_SYNC_INTERVAL=30
|
||||
@@ -5,6 +5,16 @@ import logging
|
||||
import threading
|
||||
import time
|
||||
from flask import Flask
|
||||
|
||||
# 可选: 加载 .env 文件(生产部署推荐用 systemd EnvironmentFile)
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
_env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
|
||||
if os.path.isfile(_env_path):
|
||||
load_dotenv(_env_path)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from config import Config
|
||||
from database import db
|
||||
from models import Instance # 触发表创建
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# SOCKS Manager - 一键部署脚本
|
||||
# =============================================================================
|
||||
# 功能:
|
||||
# 1. 安装系统依赖 (python3, pip, gcc, libffi, etc.)
|
||||
# 2. 创建 Python 虚拟环境并安装 pip 依赖
|
||||
# 3. 生成 .env 配置(随机 SECRET_KEY、管理员密码)
|
||||
# 4. 初始化数据库 & 后台线程
|
||||
# 5. 生成 gunicorn + systemd 服务文件并启动
|
||||
# 6. 配置防火墙(firewalld / ufw 自动识别)
|
||||
#
|
||||
# 用法:
|
||||
# bash deploy.sh # 全自动部署 (默认 /opt/socks-manager)
|
||||
# INSTALL_DIR=/opt/sm bash deploy.sh
|
||||
# bash deploy.sh --no-start # 不启动服务
|
||||
# ADMIN_PASS=mypassword bash deploy.sh
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# ---- 配置 -------------------------------------------------------------------
|
||||
INSTALL_DIR="${INSTALL_DIR:-/opt/socks-manager}"
|
||||
SERVICE_NAME="${SERVICE_NAME:-socks-manager}"
|
||||
SM_HOST="${SM_HOST:-0.0.0.0}"
|
||||
SM_PORT="${SM_PORT:-5000}"
|
||||
SM_WORKERS="${SM_WORKERS:-1}"
|
||||
ADMIN_USER="${ADMIN_USER:-admin}"
|
||||
ADMIN_PASS="${ADMIN_PASS:-admin123}"
|
||||
|
||||
START_SERVICE=true
|
||||
SKIP_EXISTING=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--no-start) START_SERVICE=false ;;
|
||||
--skip-existing) SKIP_EXISTING=true ;;
|
||||
-h|--help)
|
||||
sed -n '2,25p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $arg" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---- 工具函数 ---------------------------------------------------------------
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
|
||||
must_run_as_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
error "此脚本必须以 root 运行 (sudo $0)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
detect_os() {
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
. /etc/os-release
|
||||
OS_ID="$ID"
|
||||
OS_VER="$VERSION_ID"
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
OS_ID="debian"
|
||||
elif command -v yum &>/dev/null; then
|
||||
OS_ID="centos"
|
||||
else
|
||||
error "无法识别操作系统"
|
||||
exit 1
|
||||
fi
|
||||
info "操作系统: $OS_ID $OS_VER"
|
||||
}
|
||||
|
||||
pkg_install() {
|
||||
info "安装系统包: $*"
|
||||
case "$OS_ID" in
|
||||
debian|ubuntu)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq "$@"
|
||||
;;
|
||||
centos|rhel|rocky|almalinux|ol)
|
||||
yum install -y -q "$@"
|
||||
;;
|
||||
*)
|
||||
error "不支持的包管理器: $OS_ID"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---- 1. 前置检查 ------------------------------------------------------------
|
||||
must_run_as_root
|
||||
detect_os
|
||||
|
||||
info "安装目录: $INSTALL_DIR"
|
||||
info "服务名: $SERVICE_NAME"
|
||||
info "监听: $SM_HOST:$SM_PORT"
|
||||
info "管理员: $ADMIN_USER"
|
||||
|
||||
# ---- 2. 安装系统依赖 --------------------------------------------------------
|
||||
SYS_PACKAGES=(
|
||||
python3 python3-pip python3-venv python3-dev
|
||||
gcc libffi-dev libssl-dev
|
||||
curl
|
||||
)
|
||||
|
||||
pkg_install "${SYS_PACKAGES[@]}"
|
||||
|
||||
PYTHON_BIN="$(command -v python3)"
|
||||
PIP_BIN="$(command -v pip3 || command -v pip)"
|
||||
info "Python: $PYTHON_BIN"
|
||||
info "Pip: $PIP_BIN"
|
||||
|
||||
# ---- 3. 部署项目文件 --------------------------------------------------------
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
if [[ "$SCRIPT_DIR" != "$INSTALL_DIR" ]]; then
|
||||
if [[ -d "$INSTALL_DIR" ]] && $SKIP_EXISTING; then
|
||||
warn "安装目录已存在且 --skip-existing,跳过文件复制"
|
||||
else
|
||||
info "复制项目文件到 $INSTALL_DIR"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
rsync -a --delete \
|
||||
--exclude='.git/' \
|
||||
--exclude='__pycache__/' \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='*.db' \
|
||||
--exclude='backups/' \
|
||||
--exclude='.env' \
|
||||
--exclude='gunicorn.pid' \
|
||||
"$SCRIPT_DIR/" "$INSTALL_DIR/"
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$INSTALL_DIR"
|
||||
|
||||
# 确保运行时目录
|
||||
mkdir -p backups
|
||||
chmod 700 backups
|
||||
|
||||
# ---- 4. 虚拟环境 + pip 依赖 ------------------------------------------------
|
||||
VENV_DIR="$INSTALL_DIR/.venv"
|
||||
|
||||
if [[ -d "$VENV_DIR" ]] && $SKIP_EXISTING; then
|
||||
warn "虚拟环境已存在且 --skip-existing,跳过依赖安装"
|
||||
else
|
||||
info "创建 Python 虚拟环境: $VENV_DIR"
|
||||
$PYTHON_BIN -m venv "$VENV_DIR"
|
||||
|
||||
info "升级 pip + 安装 Python 依赖"
|
||||
"$VENV_DIR/bin/pip" install --upgrade pip setuptools wheel
|
||||
"$VENV_DIR/bin/pip" install -r requirements.txt
|
||||
fi
|
||||
|
||||
PYTHON="$VENV_DIR/bin/python"
|
||||
GUNICORN="$VENV_DIR/bin/gunicorn"
|
||||
|
||||
# ---- 5. 生成 .env 配置 ------------------------------------------------------
|
||||
ENV_FILE="$INSTALL_DIR/.env"
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]] || ! $SKIP_EXISTING; then
|
||||
info "生成 .env 配置文件"
|
||||
SECRET_KEY="$($PYTHON -c "import secrets; print(secrets.token_urlsafe(48))")"
|
||||
cat > "$ENV_FILE" <<EOF
|
||||
# SOCKS Manager - 自动生成于 $(date -Iseconds)
|
||||
# 所有变量可参考 .env.example
|
||||
SM_ADMIN_USER=$ADMIN_USER
|
||||
SM_ADMIN_PASSWORD=$ADMIN_PASS
|
||||
SM_SECRET_KEY=$SECRET_KEY
|
||||
SM_HOST=$SM_HOST
|
||||
SM_PORT=$SM_PORT
|
||||
SM_WORKERS=$SM_WORKERS
|
||||
SM_TIMEOUT=30
|
||||
SM_LOG_LEVEL=INFO
|
||||
SM_SYNC_LOOP_ENABLE=true
|
||||
SM_SYNC_INTERVAL=30
|
||||
EOF
|
||||
chmod 600 "$ENV_FILE"
|
||||
info "SECRET_KEY 已随机生成,管理员密码: $ADMIN_PASS"
|
||||
fi
|
||||
|
||||
# ---- 6. 初始化数据库 --------------------------------------------------------
|
||||
info "初始化数据库"
|
||||
$PYTHON -c "
|
||||
import os, sys
|
||||
os.environ['SM_ADMIN_PASSWORD'] = '$ADMIN_PASS'
|
||||
os.environ['SM_ADMIN_USER'] = '$ADMIN_USER'
|
||||
sys.path.insert(0, '$INSTALL_DIR')
|
||||
from app import create_app
|
||||
app = create_app()
|
||||
print('[init] database ready, instances synced')
|
||||
"
|
||||
|
||||
# ---- 7. 生成 systemd 服务文件 ----------------------------------------------
|
||||
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
|
||||
info "生成 systemd 服务文件: $SERVICE_FILE"
|
||||
cat > "$SERVICE_FILE" <<EOF
|
||||
[Unit]
|
||||
Description=SOCKS5 Proxy Manager
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=$INSTALL_DIR
|
||||
EnvironmentFile=$ENV_FILE
|
||||
ExecStart=$GUNICORN -c gunicorn_config.py run:app
|
||||
ExecReload=/bin/kill -s HUP \$MAINPID
|
||||
ExecStop=/bin/kill -s TERM \$MAINPID
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=60
|
||||
# 资源限制
|
||||
MemoryMax=512M
|
||||
MemoryHigh=384M
|
||||
LimitNOFILE=65535
|
||||
# 安全加固
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
ReadWritePaths=$INSTALL_DIR
|
||||
PrivateTmp=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
info "systemd 服务已注册并设为开机自启"
|
||||
|
||||
# ---- 8. 防火墙配置 ---------------------------------------------------------
|
||||
_configure_firewall() {
|
||||
local port="$1"
|
||||
if command -v ufw &>/dev/null && ufw status | grep -q "Status: active" 2>/dev/null; then
|
||||
if ! ufw status | grep -q "${port}/tcp"; then
|
||||
ufw allow "${port}/tcp" comment "socks-manager web" >/dev/null
|
||||
info "ufw 已放行 ${port}/tcp (Web 面板)"
|
||||
fi
|
||||
elif command -v firewall-cmd &>/dev/null && firewall-cmd --state &>/dev/null; then
|
||||
if ! firewall-cmd --list-ports | grep -q "${port}/tcp"; then
|
||||
firewall-cmd --permanent --add-port="${port}/tcp" >/dev/null
|
||||
firewall-cmd --reload >/dev/null
|
||||
info "firewalld 已放行 ${port}/tcp (Web 面板)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
_configure_firewall "$SM_PORT"
|
||||
|
||||
# ---- 9. 启动服务 -----------------------------------------------------------
|
||||
if $START_SERVICE; then
|
||||
info "启动 $SERVICE_NAME 服务"
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
sleep 3
|
||||
|
||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
info "$SERVICE_NAME 服务运行正常 ✓"
|
||||
else
|
||||
error "$SERVICE_NAME 服务启动失败!"
|
||||
echo "---------- journalctl 最后 40 行 ----------"
|
||||
journalctl -u "$SERVICE_NAME" --no-pager -n 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 健康检查
|
||||
if curl -sf -o /dev/null "http://127.0.0.1:${SM_PORT}/login"; then
|
||||
info "HTTP 健康检查通过 ✓ (http://127.0.0.1:${SM_PORT}/login)"
|
||||
else
|
||||
warn "HTTP 健康检查失败,请检查防火墙或端口配置"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- 10. 输出汇总 -----------------------------------------------------------
|
||||
echo ""
|
||||
echo "================================================================"
|
||||
echo " SOCKS Manager 部署完成!"
|
||||
echo "================================================================"
|
||||
echo " Web UI: http://<服务器IP>:${SM_PORT}"
|
||||
echo " 账号: ${ADMIN_USER} / ${ADMIN_PASS}"
|
||||
echo " 安装目录: ${INSTALL_DIR}"
|
||||
echo " 服务名: ${SERVICE_NAME}"
|
||||
echo " 配置文件: ${ENV_FILE}"
|
||||
echo ""
|
||||
echo " 常用命令:"
|
||||
echo " systemctl status $SERVICE_NAME # 查看状态"
|
||||
echo " systemctl restart $SERVICE_NAME # 重启服务"
|
||||
echo " journalctl -u $SERVICE_NAME -f # 实时日志"
|
||||
echo ""
|
||||
echo " 提示:"
|
||||
echo " - SOCKS5 代理实例在 Web 面板中创建和管理"
|
||||
echo " - 代理端口需要手动在防火墙放行(面板会提示)"
|
||||
echo " - 登录后请立即修改默认管理员密码"
|
||||
echo "================================================================"
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Gunicorn configuration for SOCKS Manager.
|
||||
|
||||
Usage:
|
||||
gunicorn -c gunicorn_config.py run:app
|
||||
|
||||
All values can be overridden via environment variables:
|
||||
SM_HOST bind address (default: 0.0.0.0)
|
||||
SM_PORT bind port (default: 5000)
|
||||
SM_WORKERS worker count (default: 1 — keep 1 for SOCKS5 thread singleton)
|
||||
SM_TIMEOUT worker timeout (default: 30)
|
||||
"""
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
_host = os.environ.get("SM_HOST", "0.0.0.0")
|
||||
_port = os.environ.get("SM_PORT", "5000")
|
||||
_workers = int(os.environ.get("SM_WORKERS", 1))
|
||||
_timeout = int(os.environ.get("SM_TIMEOUT", 30))
|
||||
|
||||
bind = f"{_host}:{_port}"
|
||||
workers = _workers
|
||||
timeout = _timeout
|
||||
graceful_timeout = 30
|
||||
worker_class = "sync"
|
||||
|
||||
# Periodic worker recycling to prevent memory leaks
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 100
|
||||
|
||||
# Request size limits
|
||||
limit_request_line = 8190
|
||||
limit_request_fields = 100
|
||||
limit_request_field_size = 8190
|
||||
|
||||
# Logging → journald
|
||||
accesslog = "-"
|
||||
errorlog = "-"
|
||||
loglevel = os.environ.get("SM_LOG_LEVEL", "INFO").lower()
|
||||
|
||||
# Preload app (faster fork, fails fast). Keep True when workers=1.
|
||||
preload_app = True
|
||||
|
||||
# PID file
|
||||
pidfile = os.path.join(os.path.dirname(os.path.abspath(__file__)), "gunicorn.pid")
|
||||
@@ -3,3 +3,4 @@ Flask-SQLAlchemy>=3.1
|
||||
psutil>=5.9
|
||||
bcrypt>=4.0
|
||||
gunicorn>=21.2
|
||||
python-dotenv>=1.0.0
|
||||
|
||||
Reference in New Issue
Block a user