diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e904c89 --- /dev/null +++ b/.env.example @@ -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 diff --git a/app.py b/app.py index 3a73943..45b8a80 100644 --- a/app.py +++ b/app.py @@ -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 # 触发表创建 diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..d1702ce --- /dev/null +++ b/deploy.sh @@ -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" < "$SERVICE_FILE" </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 "================================================================" diff --git a/gunicorn_config.py b/gunicorn_config.py new file mode 100644 index 0000000..9a98af7 --- /dev/null +++ b/gunicorn_config.py @@ -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") diff --git a/requirements.txt b/requirements.txt index 25758dd..9843c31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ Flask-SQLAlchemy>=3.1 psutil>=5.9 bcrypt>=4.0 gunicorn>=21.2 +python-dotenv>=1.0.0