第四部分:Web管理系统

11. Python环境准备

11.1 Python版本检查

11.1.1 检查系统Python版本

查看Python版本:

# 检查Python 3版本
python3 --version

# 检查pip版本
pip3 --version

# 查看Python安装路径
which python3

# 查看详细信息
python3 -c "import sys; print(sys.version)"

预期输出:

Python 3.10.12
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)
/usr/bin/python3
3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]

11.1.2 版本要求

最低要求:

Python: 3.8+
pip: 20.0+
setuptools: 45.0+

推荐版本:

Python: 3.10.x (本项目使用)
pip: 22.0+
setuptools: 59.0+

⚠️ 重要:Python 3.7及以下版本不支持某些现代语法特性,可能导致兼容性问题。

11.1.3 升级Python(如需要)

Ubuntu系统升级Python:

# 添加deadsnakes PPA(提供最新Python版本)
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update

# 安装Python 3.10
sudo apt install python3.10 python3.10-venv python3.10-dev

# 更新alternatives(切换默认Python版本)
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1

# 验证版本
python3 --version

升级pip:

# 升级pip到最新版本
python3 -m pip install --upgrade pip

# 验证pip版本
pip3 --version

11.2 pip安装

11.2.1 安装pip包管理器

Ubuntu/Debian系统:

# 更新软件源
sudo apt update

# 安装pip
sudo apt install python3-pip

# 验证安装
pip3 --version

CentOS/RHEL系统:

# 安装EPEL仓库
sudo yum install epel-release

# 安装pip
sudo yum install python3-pip

# 验证安装
pip3 --version

11.2.2 pip配置优化

配置国内镜像源(加速下载):

# 创建pip配置目录
mkdir -p ~/.pip

# 创建配置文件
nano ~/.pip/pip.conf

配置内容:

[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
trusted-host = pypi.tuna.tsinghua.edu.cn

[install]
timeout = 60

常用国内镜像源:

清华大学:https://pypi.tuna.tsinghua.edu.cn/simple
阿里云:https://mirrors.aliyun.com/pypi/simple/
中科大:https://pypi.mirrors.ustc.edu.cn/simple/
豆瓣:https://pypi.douban.com/simple/

临时使用镜像源:

pip3 install Flask -i https://pypi.tuna.tsinghua.edu.cn/simple

11.2.3 pip常用命令

安装软件包:

# 安装指定版本
pip3 install Flask==2.3.3

# 安装最新版本
pip3 install Flask

# 从requirements.txt安装
pip3 install -r requirements.txt

# 安装本地包
pip3 install /path/to/package.tar.gz

查询软件包:

# 列出已安装的包
pip3 list

# 查看包详细信息
pip3 show Flask

# 搜索包
pip3 search flask  # 注:该功能已被PyPI禁用

# 检查可更新的包
pip3 list --outdated

卸载软件包:

# 卸载单个包
pip3 uninstall Flask

# 卸载多个包
pip3 uninstall Flask requests -y

# 卸载所有包(危险操作)
pip3 freeze | xargs pip3 uninstall -y

导出依赖列表:

# 导出当前环境所有包
pip3 freeze > requirements.txt

# 仅导出项目使用的包(推荐使用pipreqs)
pip3 install pipreqs
pipreqs /path/to/project

11.3 依赖包安装命令

11.3.1 创建requirements.txt

项目依赖清单:

# 创建依赖文件
cat > /home/vpn-demo/requirements.txt << 'EOF'
Flask==2.3.3
Werkzeug==2.3.7
click==8.1.7
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
EOF

📝 说明:以上是Flask及其依赖的精确版本,确保环境一致性。

11.3.2 安装依赖包

方法1:使用requirements.txt(推荐)

# 进入项目目录
cd /home/vpn-demo

# 安装所有依赖
sudo pip3 install -r requirements.txt

# 验证安装
pip3 list | grep Flask

方法2:直接安装Flask

# 安装指定版本
sudo pip3 install Flask==2.3.3

# 或安装最新版本
sudo pip3 install Flask

11.3.3 验证依赖安装

检查Flask安装:

# 方法1:使用pip
pip3 show Flask

# 方法2:使用Python
python3 -c "import flask; print(flask.__version__)"

# 方法3:查看安装路径
python3 -c "import flask; print(flask.__file__)"

预期输出:

Name: Flask
Version: 2.3.3
Summary: A simple framework for building complex web applications.
Home-page: https://palletsprojects.com/p/flask
Author: Armin Ronacher
License: BSD-3-Clause
Location: /usr/local/lib/python3.10/dist-packages
Requires: Werkzeug, Jinja2, itsdangerous, click
Required-by: 

检查所有依赖:

# 检查Flask及其依赖树
pip3 show Flask Werkzeug Jinja2 click itsdangerous MarkupSafe

# 或使用pipdeptree(需要先安装)
pip3 install pipdeptree
pipdeptree -p Flask

11.3.4 处理依赖冲突

常见问题:版本冲突

# 问题示例
ERROR: pip's dependency resolver does not currently take into account 
all the packages that are installed. This behaviour is the source of 
the following dependency conflicts.

解决方案:

# 方法1:强制安装指定版本
pip3 install --force-reinstall Flask==2.3.3

# 方法2:忽略依赖检查(不推荐)
pip3 install --no-deps Flask==2.3.3

# 方法3:使用虚拟环境(推荐)
python3 -m venv /home/vpn-demo/venv
source /home/vpn-demo/venv/bin/activate
pip3 install -r requirements.txt

11.4 虚拟环境配置(可选)

11.4.1 为什么使用虚拟环境

优点:

缺点:

💡 建议:生产环境推荐使用虚拟环境;简单测试可以直接安装到系统。

11.4.2 创建虚拟环境

使用venv(Python内置):

# 创建虚拟环境
python3 -m venv /home/vpn-demo/venv

# 激活虚拟环境
source /home/vpn-demo/venv/bin/activate

# 验证环境(提示符会变化)
which python3
# 输出:/home/vpn-demo/venv/bin/python3

# 升级pip
pip3 install --upgrade pip

使用virtualenv(第三方工具):

# 安装virtualenv
sudo pip3 install virtualenv

# 创建虚拟环境
virtualenv /home/vpn-demo/venv

# 激活虚拟环境
source /home/vpn-demo/venv/bin/activate

11.4.3 在虚拟环境中安装依赖

激活环境后安装:

# 确保虚拟环境已激活(提示符显示(venv))
(venv) user@host:~$ 

# 安装依赖
pip3 install -r /home/vpn-demo/requirements.txt

# 验证安装
pip3 list

11.4.4 退出和删除虚拟环境

退出虚拟环境:

# 退出命令
deactivate

# 提示符恢复正常
user@host:~$

删除虚拟环境:

# 直接删除目录
rm -rf /home/vpn-demo/venv

# 重新创建(如需要)
python3 -m venv /home/vpn-demo/venv

11.4.5 systemd服务中使用虚拟环境

修改启动脚本:

#!/bin/bash
# 激活虚拟环境
source /home/vpn-demo/venv/bin/activate

# 启动Flask应用
python3 /home/vpn-demo/vpn_api.py

systemd服务文件:

[Unit]
Description=VPN Management API
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/home/vpn-demo
ExecStart=/home/vpn-demo/venv/bin/python3 /home/vpn-demo/vpn_api.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

12. Flask后端部署

12.1 vpn_api.py代码

12.1.1 完整代码清单

创建Flask应用文件:

# 创建项目目录
sudo mkdir -p /home/vpn-demo
cd /home/vpn-demo

# 创建API文件
sudo nano vpn_api.py

完整代码(vpn_api.py):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
WireGuard VPN 管理 API
提供VPN服务的启动、停止、状态查询和性能测试功能
"""

from flask import Flask, jsonify, request
import subprocess
import json
import re
import time

app = Flask(__name__)

# ==================== 工具函数 ====================

def run_command(cmd, shell=False):
    """
    执行系统命令并返回结果
    
    Args:
        cmd: 命令字符串或列表
        shell: 是否使用shell模式
        
    Returns:
        dict: {
            'success': bool,
            'output': str,
            'error': str,
            'returncode': int
        }
    """
    try:
        if shell:
            result = subprocess.run(
                cmd,
                shell=True,
                capture_output=True,
                text=True,
                timeout=30
            )
        else:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=30
            )
        
        return {
            'success': result.returncode == 0,
            'output': result.stdout.strip(),
            'error': result.stderr.strip(),
            'returncode': result.returncode
        }
    except subprocess.TimeoutExpired:
        return {
            'success': False,
            'output': '',
            'error': 'Command timeout',
            'returncode': -1
        }
    except Exception as e:
        return {
            'success': False,
            'output': '',
            'error': str(e),
            'returncode': -1
        }

# ==================== API路由 ====================

@app.route('/api/vpn/status', methods=['GET'])
def get_vpn_status():
    """
    查询VPN运行状态
    
    Returns:
        JSON: {
            'status': 'running' | 'stopped',
            'interface': 'wg0',
            'peers': int,
            'details': str
        }
    """
    result = run_command(['sudo', 'wg', 'show'])
    
    if result['success'] and result['output']:
        # 解析peer数量
        peers = len([line for line in result['output'].split('\n') 
                     if line.startswith('peer:')])
        
        return jsonify({
            'status': 'running',
            'interface': 'wg0',
            'peers': peers,
            'details': result['output']
        })
    else:
        return jsonify({
            'status': 'stopped',
            'interface': 'wg0',
            'peers': 0,
            'details': 'WireGuard is not running'
        })

@app.route('/api/vpn/start', methods=['POST'])
def start_vpn():
    """
    启动VPN服务
    
    Returns:
        JSON: {
            'success': bool,
            'message': str,
            'details': str
        }
    """
    # 检查是否已经运行
    check = run_command(['sudo', 'wg', 'show'])
    if check['success'] and check['output']:
        return jsonify({
            'success': False,
            'message': 'VPN is already running',
            'details': check['output']
        }), 400
    
    # 启动WireGuard
    result = run_command(['sudo', 'wg-quick', 'up', 'wg0'])
    
    if result['success']:
        # 等待接口完全启动
        time.sleep(2)
        
        # 验证启动状态
        verify = run_command(['sudo', 'wg', 'show'])
        
        return jsonify({
            'success': True,
            'message': 'VPN started successfully',
            'details': verify['output']
        })
    else:
        return jsonify({
            'success': False,
            'message': 'Failed to start VPN',
            'details': result['error']
        }), 500

@app.route('/api/vpn/stop', methods=['POST'])
def stop_vpn():
    """
    停止VPN服务
    
    Returns:
        JSON: {
            'success': bool,
            'message': str,
            'details': str
        }
    """
    # 检查是否在运行
    check = run_command(['sudo', 'wg', 'show'])
    if not check['success'] or not check['output']:
        return jsonify({
            'success': False,
            'message': 'VPN is not running',
            'details': 'WireGuard interface not found'
        }), 400
    
    # 停止WireGuard
    result = run_command(['sudo', 'wg-quick', 'down', 'wg0'])
    
    if result['success']:
        return jsonify({
            'success': True,
            'message': 'VPN stopped successfully',
            'details': result['output']
        })
    else:
        return jsonify({
            'success': False,
            'message': 'Failed to stop VPN',
            'details': result['error']
        }), 500

@app.route('/api/vpn/test/latency', methods=['GET'])
def test_latency():
    """
    测试VPN延迟
    使用ping命令测试10.8.0.2(客户端)的延迟
    
    Returns:
        JSON: {
            'success': bool,
            'target': str,
            'packets_sent': int,
            'packets_received': int,
            'packet_loss': float,
            'rtt_min': float,
            'rtt_avg': float,
            'rtt_max': float,
            'rtt_mdev': float
        }
    """
    target = '10.8.0.2'
    count = 10
    
    # 执行ping测试
    result = run_command(['ping', '-c', str(count), target])
    
    if not result['success']:
        return jsonify({
            'success': False,
            'message': 'Ping test failed',
            'details': result['error']
        }), 500
    
    # 解析ping结果
    output = result['output']
    
    # 提取统计信息
    # 示例:10 packets transmitted, 10 received, 0% packet loss
    stats_match = re.search(
        r'(\d+) packets transmitted, (\d+) received, ([\d.]+)% packet loss',
        output
    )
    
    # 提取RTT信息
    # 示例:rtt min/avg/max/mdev = 0.032/0.065/0.173/0.020 ms
    rtt_match = re.search(
        r'rtt min/avg/max/mdev = ([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+) ms',
        output
    )
    
    if stats_match and rtt_match:
        return jsonify({
            'success': True,
            'target': target,
            'packets_sent': int(stats_match.group(1)),
            'packets_received': int(stats_match.group(2)),
            'packet_loss': float(stats_match.group(3)),
            'rtt_min': float(rtt_match.group(1)),
            'rtt_avg': float(rtt_match.group(2)),
            'rtt_max': float(rtt_match.group(3)),
            'rtt_mdev': float(rtt_match.group(4)),
            'unit': 'ms'
        })
    else:
        return jsonify({
            'success': False,
            'message': 'Failed to parse ping results',
            'details': output
        }), 500

@app.route('/api/vpn/test/bandwidth', methods=['GET'])
def test_bandwidth():
    """
    测试VPN带宽
    通过WireGuard传输统计估算带宽
    
    Returns:
        JSON: {
            'success': bool,
            'rx_bytes': int,
            'tx_bytes': int,
            'rx_mbps': float,
            'tx_mbps': float
        }
    """
    # 获取传输统计
    result = run_command(['sudo', 'wg', 'show', 'wg0', 'transfer'])
    
    if not result['success']:
        return jsonify({
            'success': False,
            'message': 'Failed to get transfer statistics',
            'details': result['error']
        }), 500
    
    # 解析输出
    # 示例:peer_pubkey    12345678    87654321
    lines = result['output'].strip().split('\n')
    
    if len(lines) == 0:
        return jsonify({
            'success': False,
            'message': 'No peer data available',
            'details': 'No connected peers'
        }), 404
    
    # 取第一个peer的数据
    parts = lines[0].split()
    if len(parts) >= 3:
        rx_bytes = int(parts[1])
        tx_bytes = int(parts[2])
        
        # 简单估算:假设数据在60秒内传输
        time_window = 60
        rx_mbps = (rx_bytes * 8) / (time_window * 1000000)
        tx_mbps = (tx_bytes * 8) / (time_window * 1000000)
        
        return jsonify({
            'success': True,
            'rx_bytes': rx_bytes,
            'tx_bytes': tx_bytes,
            'rx_mbps': round(rx_mbps, 2),
            'tx_mbps': round(tx_mbps, 2),
            'note': 'Bandwidth estimation based on transfer statistics'
        })
    else:
        return jsonify({
            'success': False,
            'message': 'Failed to parse transfer data',
            'details': result['output']
        }), 500

@app.route('/api/system/info', methods=['GET'])
def get_system_info():
    """
    获取系统信息
    
    Returns:
        JSON: {
            'hostname': str,
            'kernel': str,
            'uptime': str,
            'load_average': str
        }
    """
    # 获取主机名
    hostname = run_command(['hostname'])['output']
    
    # 获取内核版本
    kernel = run_command(['uname', '-r'])['output']
    
    # 获取系统运行时间
    uptime = run_command(['uptime', '-p'])['output']
    
    # 获取负载
    load = run_command(['cat', '/proc/loadavg'])['output'].split()[:3]
    
    return jsonify({
        'hostname': hostname,
        'kernel': kernel,
        'uptime': uptime,
        'load_average': ' '.join(load),
        'timestamp': int(time.time())
    })

# ==================== 健康检查 ====================

@app.route('/health', methods=['GET'])
def health_check():
    """API健康检查"""
    return jsonify({
        'status': 'healthy',
        'service': 'VPN Management API',
        'version': '1.0'
    })

# ==================== 错误处理 ====================

@app.errorhandler(404)
def not_found(error):
    """404错误处理"""
    return jsonify({
        'error': 'Not Found',
        'message': 'The requested endpoint does not exist'
    }), 404

@app.errorhandler(500)
def internal_error(error):
    """500错误处理"""
    return jsonify({
        'error': 'Internal Server Error',
        'message': 'An unexpected error occurred'
    }), 500

# ==================== 主程序 ====================

if __name__ == '__main__':
    # 生产环境配置
    app.run(
        host='0.0.0.0',  # 监听所有接口
        port=5000,       # 端口
        debug=False,     # 关闭调试模式
        threaded=True    # 启用多线程
    )

12.1.2 代码结构说明

模块组织:

vpn_api.py
├── 导入模块 (Flask, subprocess, etc.)
├── Flask应用初始化
├── 工具函数
│   └── run_command()  # 执行系统命令
├── API路由
│   ├── GET  /api/vpn/status        # 查询状态
│   ├── POST /api/vpn/start         # 启动VPN
│   ├── POST /api/vpn/stop          # 停止VPN
│   ├── GET  /api/vpn/test/latency  # 延迟测试
│   ├── GET  /api/vpn/test/bandwidth# 带宽测试
│   └── GET  /api/system/info       # 系统信息
├── 健康检查
│   └── GET  /health                # 健康状态
├── 错误处理
│   ├── 404处理
│   └── 500处理
└── 主程序入口

12.1.3 代码关键点解析

1. subprocess安全执行:

# 使用列表形式传递命令(防止shell注入)
result = subprocess.run(
    ['sudo', 'wg', 'show'],  # 不使用shell=True
    capture_output=True,
    text=True,
    timeout=30  # 设置超时防止hang
)

2. 错误处理:

try:
    result = subprocess.run(...)
    return {
        'success': result.returncode == 0,
        'output': result.stdout.strip(),
        'error': result.stderr.strip()
    }
except subprocess.TimeoutExpired:
    # 超时处理
except Exception as e:
    # 其他异常处理

3. 正则表达式解析:

# 解析ping输出
stats_match = re.search(
    r'(\d+) packets transmitted, (\d+) received, ([\d.]+)% packet loss',
    output
)

if stats_match:
    packets_sent = int(stats_match.group(1))
    packets_received = int(stats_match.group(2))
    packet_loss = float(stats_match.group(3))

4. JSON响应格式:

return jsonify({
    'success': True,
    'message': 'Operation completed',
    'details': data
})

12.2 API接口说明

12.2.1 接口列表

方法路径功能权限
GET/api/vpn/status查询VPN状态读取
POST/api/vpn/start启动VPN服务root
POST/api/vpn/stop停止VPN服务root
GET/api/vpn/test/latency延迟测试读取
GET/api/vpn/test/bandwidth带宽测试读取
GET/api/system/info系统信息读取
GET/health健康检查

12.2.2 接口详细说明

1. GET /api/vpn/status

查询WireGuard VPN当前运行状态。

请求示例:

curl http://localhost:5000/api/vpn/status

响应示例(运行中):

{
  "status": "running",
  "interface": "wg0",
  "peers": 1,
  "details": "interface: wg0\n  public key: rUEB9pR7DO7qgkbo6Ylerrh/Fvl1XA8RJuALBdlkFkk=\n  private key: (hidden)\n  listening port: 51820\n\npeer: QcDIGh0MyyLJoovsb7hMccZGwMha7aqfa/3cyFtfhE4=\n  endpoint: 192.168.1.100:54231\n  allowed ips: 10.8.0.2/32\n  latest handshake: 45 seconds ago\n  transfer: 1.23 MiB received, 856.78 KiB sent"
}

响应示例(已停止):

{
  "status": "stopped",
  "interface": "wg0",
  "peers": 0,
  "details": "WireGuard is not running"
}

2. POST /api/vpn/start

启动WireGuard VPN服务。

请求示例:

curl -X POST http://localhost:5000/api/vpn/start

成功响应:

{
  "success": true,
  "message": "VPN started successfully",
  "details": "interface: wg0\n  public key: rUEB9pR7DO7qgkbo6Ylerrh/Fvl1XA8RJuALBdlkFkk=\n  listening port: 51820"
}

失败响应(已在运行):

{
  "success": false,
  "message": "VPN is already running",
  "details": "..."
}

3. POST /api/vpn/stop

停止WireGuard VPN服务。

请求示例:

curl -X POST http://localhost:5000/api/vpn/stop

成功响应:

{
  "success": true,
  "message": "VPN stopped successfully",
  "details": ""
}

失败响应(未在运行):

{
  "success": false,
  "message": "VPN is not running",
  "details": "WireGuard interface not found"
}

4. GET /api/vpn/test/latency

测试VPN隧道延迟(ping 10.8.0.2)。

请求示例:

curl http://localhost:5000/api/vpn/test/latency

成功响应:

{
  "success": true,
  "target": "10.8.0.2",
  "packets_sent": 10,
  "packets_received": 10,
  "packet_loss": 0.0,
  "rtt_min": 0.032,
  "rtt_avg": 0.065,
  "rtt_max": 0.173,
  "rtt_mdev": 0.020,
  "unit": "ms"
}

失败响应:

{
  "success": false,
  "message": "Ping test failed",
  "details": "Destination Host Unreachable"
}

5. GET /api/vpn/test/bandwidth

测试VPN带宽(基于传输统计估算)。

请求示例:

curl http://localhost:5000/api/vpn/test/bandwidth

成功响应:

{
  "success": true,
  "rx_bytes": 1290240,
  "tx_bytes": 877568,
  "rx_mbps": 0.17,
  "tx_mbps": 0.12,
  "note": "Bandwidth estimation based on transfer statistics"
}

6. GET /api/system/info

获取系统基本信息。

请求示例:

curl http://localhost:5000/api/system/info

响应示例:

{
  "hostname": "eve-ng",
  "kernel": "5.15.0-91-generic",
  "uptime": "up 2 days, 5 hours, 32 minutes",
  "load_average": "0.15 0.18 0.12",
  "timestamp": 1711180800
}

7. GET /health

API健康检查。

请求示例:

curl http://localhost:5000/health

响应示例:

{
  "status": "healthy",
  "service": "VPN Management API",
  "version": "1.0"
}

12.3 权限配置

12.3.1 sudo权限配置

Flask应用需要以root权限执行WireGuard命令,配置sudo免密执行。

方法1:配置sudoers(推荐)

# 编辑sudoers文件
sudo visudo

在文件末尾添加:

# 允许www-data用户免密执行WireGuard命令
www-data ALL=(ALL) NOPASSWD: /usr/bin/wg
www-data ALL=(ALL) NOPASSWD: /usr/bin/wg-quick
www-data ALL=(ALL) NOPASSWD: /bin/ping

# 或允许所有用户(不推荐)
ALL ALL=(ALL) NOPASSWD: /usr/bin/wg, /usr/bin/wg-quick, /bin/ping

保存并退出(Ctrl+X, Y, Enter)。

验证配置:

# 测试sudo免密
sudo -u www-data sudo wg show

# 应该能正常执行,不要求密码

⚠️ 安全提示:仅授权必要的命令,不要使用NOPASSWD: ALL

方法2:使用root用户运行(简单但不推荐)

# 直接以root用户运行Flask应用
sudo python3 vpn_api.py

💡 说明:本项目使用此方法,简单直接,但生产环境不推荐。

12.3.2 文件权限设置

设置vpn_api.py权限:

# 设置所有者
sudo chown root:root /home/vpn-demo/vpn_api.py

# 设置可执行权限
sudo chmod 755 /home/vpn-demo/vpn_api.py

# 验证权限
ls -l /home/vpn-demo/vpn_api.py

预期输出:

-rwxr-xr-x 1 root root 12345 Mar 23 10:00 /home/vpn-demo/vpn_api.py

设置日志目录权限:

# 创建日志目录
sudo mkdir -p /var/log/vpn-demo

# 设置权限
sudo chown -R root:root /var/log/vpn-demo
sudo chmod 755 /var/log/vpn-demo

12.3.3 防火墙权限

限制API访问(推荐):

# 仅允许本机访问API
sudo ufw deny 5000/tcp
sudo ufw allow from 127.0.0.1 to any port 5000

# 或允许内网访问
sudo ufw allow from 192.168.1.0/24 to any port 5000

# 重新加载防火墙
sudo ufw reload

临时开放(测试用):

# 临时允许所有IP访问
sudo ufw allow 5000/tcp

# 测试完成后关闭
sudo ufw delete allow 5000/tcp

12.4 启动命令

12.4.1 前台启动(测试)

直接运行:

# 进入项目目录
cd /home/vpn-demo

# 以root权限启动
sudo python3 vpn_api.py

输出示例:

 * Serving Flask app 'vpn_api'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://192.168.1.66:5000
Press CTRL+C to quit

💡 提示:按Ctrl+C停止服务。

测试API:

# 打开新终端,测试健康检查
curl http://localhost:5000/health

# 测试VPN状态
curl http://localhost:5000/api/vpn/status

12.4.2 后台启动

方法1:使用nohup

# 后台运行,输出到日志文件
sudo nohup python3 /home/vpn-demo/vpn_api.py > /var/log/vpn-demo/api.log 2>&1 &

# 查看进程
ps aux | grep vpn_api.py

# 查看日志
tail -f /var/log/vpn-demo/api.log

停止服务:

# 查找PID
ps aux | grep vpn_api.py | grep -v grep

# 杀死进程
sudo kill -9 <PID>

# 或使用pkill
sudo pkill -f vpn_api.py

方法2:使用screen(推荐)

# 安装screen
sudo apt install screen

# 创建新会话
sudo screen -S vpn-api

# 在screen中启动Flask
cd /home/vpn-demo
sudo python3 vpn_api.py

# 分离会话(Ctrl+A, D)

# 重新连接会话
sudo screen -r vpn-api

# 查看所有会话
screen -ls

方法3:使用systemd(最推荐)

参见12.5节”进程管理”。

12.4.3 开机自启动

使用systemd服务:

# 创建服务文件
sudo nano /etc/systemd/system/vpn-api.service

服务配置:

[Unit]
Description=VPN Management API
After=network.target [email protected]
Wants[email protected]

[Service]
Type=simple
User=root
WorkingDirectory=/home/vpn-demo
ExecStart=/usr/bin/python3 /home/vpn-demo/vpn_api.py
Restart=on-failure
RestartSec=10
StandardOutput=append:/var/log/vpn-demo/api.log
StandardError=append:/var/log/vpn-demo/api-error.log

[Install]
WantedBy=multi-user.target

启用服务:

# 重新加载systemd
sudo systemctl daemon-reload

# 启动服务
sudo systemctl start vpn-api

# 设置开机自启
sudo systemctl enable vpn-api

# 查看状态
sudo systemctl status vpn-api

12.5 进程管理

12.5.1 systemd服务管理

基本命令:

# 启动服务
sudo systemctl start vpn-api

# 停止服务
sudo systemctl stop vpn-api

# 重启服务
sudo systemctl restart vpn-api

# 查看状态
sudo systemctl status vpn-api

# 查看日志
sudo journalctl -u vpn-api -f

# 查看最近100行日志
sudo journalctl -u vpn-api -n 100

自启动管理:

# 启用开机自启
sudo systemctl enable vpn-api

# 禁用开机自启
sudo systemctl disable vpn-api

# 检查是否已启用
sudo systemctl is-enabled vpn-api

服务状态示例:

● vpn-api.service - VPN Management API
     Loaded: loaded (/etc/systemd/system/vpn-api.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2025-03-23 10:30:15 UTC; 2h 15min ago
   Main PID: 1234 (python3)
      Tasks: 1 (limit: 4638)
     Memory: 25.3M
        CPU: 1.234s
     CGroup: /system.slice/vpn-api.service
             └─1234 /usr/bin/python3 /home/vpn-demo/vpn_api.py

12.5.2 进程监控

查看进程:

# 方法1:使用ps
ps aux | grep vpn_api

# 方法2:使用pgrep
pgrep -f vpn_api.py

# 方法3:使用top
top -p $(pgrep -f vpn_api.py)

# 方法4:使用htop(更友好)
sudo apt install htop
htop -p $(pgrep -f vpn_api.py)

资源使用监控:

# CPU和内存使用
ps -p $(pgrep -f vpn_api.py) -o %cpu,%mem,cmd

# 详细信息
pidstat -p $(pgrep -f vpn_api.py) 1

网络连接监控:

# 查看监听端口
sudo netstat -tulnp | grep :5000

# 查看连接数
sudo netstat -an | grep :5000 | wc -l

# 使用ss命令
sudo ss -tulnp | grep :5000

12.5.3 日志管理

实时查看日志:

# systemd日志
sudo journalctl -u vpn-api -f

# 自定义日志文件
tail -f /var/log/vpn-demo/api.log

日志过滤:

# 查看错误日志
sudo journalctl -u vpn-api -p err

# 查看今天的日志
sudo journalctl -u vpn-api --since today

# 查看最近1小时的日志
sudo journalctl -u vpn-api --since "1 hour ago"

# 查看指定时间范围的日志
sudo journalctl -u vpn-api --since "2025-03-23 10:00" --until "2025-03-23 12:00"

日志轮转配置:

# 创建logrotate配置
sudo nano /etc/logrotate.d/vpn-demo

配置内容:

/var/log/vpn-demo/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 0640 root root
    sharedscripts
    postrotate
        systemctl reload vpn-api > /dev/null 2>&1 || true
    endscript
}

测试配置:

# 测试logrotate配置
sudo logrotate -d /etc/logrotate.d/vpn-demo

# 强制轮转
sudo logrotate -f /etc/logrotate.d/vpn-demo

12.5.4 进程保护

配置服务自动重启:

/etc/systemd/system/vpn-api.service中已配置:

[Service]
Restart=on-failure
RestartSec=10

配置说明:

限制重启次数:

[Service]
StartLimitInterval=600
StartLimitBurst=5

说明:600秒内最多重启5次,超过则停止重启。

测试自动重启:

# 杀死进程测试
sudo kill -9 $(pgrep -f vpn_api.py)

# 等待10秒后检查
sleep 10
sudo systemctl status vpn-api

# 应该显示进程已自动重启

13. 前端页面部署

13.1 index.html代码

13.1.1 完整HTML代码

创建前端文件:

# 确保在项目目录
cd /home/vpn-demo

# 创建HTML文件
sudo nano index.html

完整代码(index.html):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>VPN 管理系统</title>
    
    <!-- Tailwind CSS CDN -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <style>
        /* 自定义样式 */
        .status-indicator {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            display: inline-block;
            margin-right: 8px;
        }
        .status-running {
            background-color: #10b981;
            box-shadow: 0 0 8px #10b981;
        }
        .status-stopped {
            background-color: #ef4444;
        }
        .loading {
            border: 3px solid #f3f3f3;
            border-top: 3px solid #3498db;
            border-radius: 50%;
            width: 24px;
            height: 24px;
            animation: spin 1s linear infinite;
            display: inline-block;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body class="bg-gray-100">
    <!-- 导航栏 -->
    <nav class="bg-blue-600 text-white shadow-lg">
        <div class="container mx-auto px-4 py-4">
            <div class="flex justify-between items-center">
                <h1 class="text-2xl font-bold">WireGuard VPN 管理系统</h1>
                <div class="flex items-center space-x-4">
                    <span id="clock" class="text-sm"></span>
                    <span class="text-sm">谢北然 | 42006606</span>
                </div>
            </div>
        </div>
    </nav>

    <!-- 主内容区 -->
    <div class="container mx-auto px-4 py-8">
        <!-- 左侧按钮 -->
        <div class="fixed left-4 top-1/2 transform -translate-y-1/2 z-10">
            <a href="https://www.retourne-toi.org" target="_blank" 
               class="block bg-purple-600 hover:bg-purple-700 text-white px-4 py-3 rounded-lg shadow-lg transition-all">
                📄 技术文档
            </a>
        </div>

        <!-- 右侧按钮 -->
        <div class="fixed right-4 top-1/2 transform -translate-y-1/2 z-10">
            <a href="https://www.retourne-toi.org" target="_blank" 
               class="block bg-green-600 hover:bg-green-700 text-white px-4 py-3 rounded-lg shadow-lg transition-all">
                🎓 答辩资料
            </a>
        </div>

        <!-- 状态卡片区 -->
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
            <!-- VPN状态卡片 -->
            <div class="bg-white rounded-lg shadow-lg p-6">
                <div class="flex items-center justify-between mb-4">
                    <h3 class="text-gray-600 text-sm font-medium">VPN 状态</h3>
                    <span class="status-indicator" id="status-indicator"></span>
                </div>
                <div class="text-3xl font-bold" id="vpn-status">加载中...</div>
            </div>

            <!-- 在线用户卡片 -->
            <div class="bg-white rounded-lg shadow-lg p-6">
                <div class="flex items-center justify-between mb-4">
                    <h3 class="text-gray-600 text-sm font-medium">在线用户</h3>
                    <svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                              d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
                    </svg>
                </div>
                <div class="text-3xl font-bold" id="peer-count">0</div>
            </div>

            <!-- 延迟卡片 -->
            <div class="bg-white rounded-lg shadow-lg p-6">
                <div class="flex items-center justify-between mb-4">
                    <h3 class="text-gray-600 text-sm font-medium">平均延迟</h3>
                    <svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                              d="M13 10V3L4 14h7v7l9-11h-7z" />
                    </svg>
                </div>
                <div class="text-3xl font-bold" id="latency">-- ms</div>
            </div>

            <!-- 带宽卡片 -->
            <div class="bg-white rounded-lg shadow-lg p-6">
                <div class="flex items-center justify-between mb-4">
                    <h3 class="text-gray-600 text-sm font-medium">传输速率</h3>
                    <svg class="w-6 h-6 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                              d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" />
                    </svg>
                </div>
                <div class="text-3xl font-bold" id="bandwidth">-- Mbps</div>
            </div>
        </div>

        <!-- 控制面板 -->
        <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
            <!-- 控制按钮区 -->
            <div class="bg-white rounded-lg shadow-lg p-6">
                <h3 class="text-xl font-bold mb-6">服务控制</h3>
                <div class="space-y-4">
                    <button onclick="startVPN()" 
                            class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg transition-all flex items-center justify-center">
                        <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                                  d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                                  d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                        </svg>
                        启动 VPN
                    </button>

                    <button onclick="stopVPN()" 
                            class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg transition-all flex items-center justify-center">
                        <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                                  d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                                  d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
                        </svg>
                        停止 VPN
                    </button>

                    <button onclick="runFullTest()" 
                            class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition-all flex items-center justify-center">
                        <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
                                  d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
                        </svg>
                        运行性能测试
                    </button>
                </div>
            </div>

            <!-- 系统信息区 -->
            <div class="bg-white rounded-lg shadow-lg p-6">
                <h3 class="text-xl font-bold mb-6">系统信息</h3>
                <div class="space-y-3">
                    <div class="flex justify-between items-center py-2 border-b">
                        <span class="text-gray-600">主机名</span>
                        <span class="font-medium" id="sys-hostname">--</span>
                    </div>
                    <div class="flex justify-between items-center py-2 border-b">
                        <span class="text-gray-600">内核版本</span>
                        <span class="font-medium" id="sys-kernel">--</span>
                    </div>
                    <div class="flex justify-between items-center py-2 border-b">
                        <span class="text-gray-600">运行时间</span>
                        <span class="font-medium" id="sys-uptime">--</span>
                    </div>
                    <div class="flex justify-between items-center py-2">
                        <span class="text-gray-600">负载</span>
                        <span class="font-medium" id="sys-load">--</span>
                    </div>
                </div>
            </div>
        </div>

        <!-- 日志输出区 -->
        <div class="bg-white rounded-lg shadow-lg p-6">
            <div class="flex justify-between items-center mb-4">
                <h3 class="text-xl font-bold">系统日志</h3>
                <button onclick="clearLogs()" 
                        class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded text-sm">
                    清空日志
                </button>
            </div>
            <div id="log-output" 
                 class="bg-gray-900 text-green-400 p-4 rounded font-mono text-sm h-64 overflow-y-auto">
                <div class="text-gray-500">等待操作...</div>
            </div>
        </div>
    </div>

    <!-- 页脚 -->
    <footer class="bg-gray-800 text-white mt-12 py-6">
        <div class="container mx-auto px-4 text-center">
            <p>基于WireGuard的VPN安全远程接入系统 | 西南财经大学天府学院</p>
            <p class="text-sm text-gray-400 mt-2">作者:谢北然 (42006606) | 指导教师:裴剑辉</p>
        </div>
    </footer>

    <!-- JavaScript代码 -->
    <script>
        // API基础URL
        const API_BASE = 'http://192.168.1.66:5000';

        // 更新时钟
        function updateClock() {
            const now = new Date();
            const timeString = now.toLocaleString('zh-CN', {
                year: 'numeric',
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit',
                second: '2-digit'
            });
            document.getElementById('clock').textContent = timeString;
        }
        setInterval(updateClock, 1000);
        updateClock();

        // 添加日志
        function addLog(message, type = 'info') {
            const logOutput = document.getElementById('log-output');
            const timestamp = new Date().toLocaleTimeString('zh-CN');
            const colors = {
                'info': 'text-green-400',
                'error': 'text-red-400',
                'warning': 'text-yellow-400',
                'success': 'text-blue-400'
            };
            const color = colors[type] || colors['info'];
            
            logOutput.innerHTML += `<div class="${color}">[${timestamp}] ${message}</div>`;
            logOutput.scrollTop = logOutput.scrollHeight;
        }

        // 清空日志
        function clearLogs() {
            document.getElementById('log-output').innerHTML = '<div class="text-gray-500">日志已清空</div>';
        }

        // 获取VPN状态
        async function getStatus() {
            try {
                const response = await fetch(`${API_BASE}/api/vpn/status`);
                const data = await response.json();
                
                if (data.status === 'running') {
                    document.getElementById('vpn-status').textContent = '运行中';
                    document.getElementById('status-indicator').className = 'status-indicator status-running';
                    document.getElementById('peer-count').textContent = data.peers;
                } else {
                    document.getElementById('vpn-status').textContent = '已停止';
                    document.getElementById('status-indicator').className = 'status-indicator status-stopped';
                    document.getElementById('peer-count').textContent = '0';
                }
            } catch (error) {
                console.error('Error fetching status:', error);
                document.getElementById('vpn-status').textContent = '错误';
            }
        }

        // 启动VPN
        async function startVPN() {
            addLog('正在启动VPN服务...', 'info');
            try {
                const response = await fetch(`${API_BASE}/api/vpn/start`, {
                    method: 'POST'
                });
                const data = await response.json();
                
                if (data.success) {
                    addLog('VPN启动成功!', 'success');
                    getStatus();
                } else {
                    addLog(`VPN启动失败: ${data.message}`, 'error');
                }
            } catch (error) {
                addLog(`错误: ${error.message}`, 'error');
            }
        }

        // 停止VPN
        async function stopVPN() {
            if (!confirm('确定要停止VPN服务吗?')) return;
            
            addLog('正在停止VPN服务...', 'info');
            try {
                const response = await fetch(`${API_BASE}/api/vpn/stop`, {
                    method: 'POST'
                });
                const data = await response.json();
                
                if (data.success) {
                    addLog('VPN已停止', 'warning');
                    getStatus();
                } else {
                    addLog(`VPN停止失败: ${data.message}`, 'error');
                }
            } catch (error) {
                addLog(`错误: ${error.message}`, 'error');
            }
        }

        // 延迟测试
        async function testLatency() {
            addLog('正在进行延迟测试...', 'info');
            try {
                const response = await fetch(`${API_BASE}/api/vpn/test/latency`);
                const data = await response.json();
                
                if (data.success) {
                    document.getElementById('latency').textContent = `${data.rtt_avg} ms`;
                    addLog(`延迟测试完成: 平均 ${data.rtt_avg}ms, 最小 ${data.rtt_min}ms, 最大 ${data.rtt_max}ms, 丢包率 ${data.packet_loss}%`, 'success');
                } else {
                    addLog(`延迟测试失败: ${data.message}`, 'error');
                }
            } catch (error) {
                addLog(`错误: ${error.message}`, 'error');
            }
        }

        // 带宽测试
        async function testBandwidth() {
            addLog('正在进行带宽测试...', 'info');
            try {
                const response = await fetch(`${API_BASE}/api/vpn/test/bandwidth`);
                const data = await response.json();
                
                if (data.success) {
                    document.getElementById('bandwidth').textContent = `↓${data.rx_mbps} ↑${data.tx_mbps}`;
                    addLog(`带宽测试完成: 下行 ${data.rx_mbps} Mbps, 上行 ${data.tx_mbps} Mbps`, 'success');
                } else {
                    addLog(`带宽测试失败: ${data.message}`, 'error');
                }
            } catch (error) {
                addLog(`错误: ${error.message}`, 'error');
            }
        }

        // 完整性能测试
        async function runFullTest() {
            addLog('========== 开始完整性能测试 ==========', 'info');
            await testLatency();
            await new Promise(resolve => setTimeout(resolve, 1000));
            await testBandwidth();
            addLog('========== 性能测试完成 ==========', 'info');
        }

        // 获取系统信息
        async function getSystemInfo() {
            try {
                const response = await fetch(`${API_BASE}/api/system/info`);
                const data = await response.json();
                
                document.getElementById('sys-hostname').textContent = data.hostname;
                document.getElementById('sys-kernel').textContent = data.kernel;
                document.getElementById('sys-uptime').textContent = data.uptime;
                document.getElementById('sys-load').textContent = data.load_average;
            } catch (error) {
                console.error('Error fetching system info:', error);
            }
        }

        // 初始化
        window.onload = function() {
            addLog('系统初始化...', 'info');
            getStatus();
            getSystemInfo();
            
            // 定时刷新状态(每5秒)
            setInterval(() => {
                getStatus();
                getSystemInfo();
            }, 5000);
            
            addLog('系统就绪,等待操作...', 'success');
        };
    </script>
</body>
</html>

保存文件(Ctrl+X, Y, Enter)。

13.2 HTTP服务启动

13.2.1 使用Python内置HTTP服务器

Python http.server模块:

Python 3自带的http.server模块可以快速启动一个简单的HTTP服务器,用于提供静态文件服务。

基本用法:

# 在指定目录启动HTTP服务器
cd /home/vpn-demo
python3 -m http.server 8000

预期输出:

Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

指定监听地址:

# 仅监听本地(安全)
python3 -m http.server 8000 --bind 127.0.0.1

# 监听所有接口(本项目使用)
python3 -m http.server 8000 --bind 0.0.0.0

# 指定目录
python3 -m http.server 8000 --directory /home/vpn-demo

后台运行:

# 使用nohup后台运行
nohup python3 -m http.server 8000 > /var/log/vpn-demo/http.log 2>&1 &

# 查看进程
ps aux | grep "http.server"

# 停止服务
pkill -f "http.server"

13.2.2 处理端口占用问题

检查端口占用:

# 方法1:netstat
sudo netstat -tulnp | grep :8000

# 方法2:lsof
sudo lsof -i :8000

# 方法3:ss
sudo ss -tulnp | grep :8000

输出示例(端口被占用):

tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      1234/python3

解决端口占用:

# 方法1:找到PID并杀死进程
sudo lsof -i :8000
# 输出:python3   1234  root    3u  IPv4  12345      0t0  TCP *:8000 (LISTEN)
sudo kill -9 1234

# 方法2:使用fuser直接杀死
sudo fuser -k 8000/tcp

# 方法3:使用pkill
sudo pkill -f "python3 -m http.server"

验证端口已释放:

# 再次检查,应该没有输出
sudo lsof -i :8000

13.2.3 配置防火墙

开放8000端口:

# 方法1:ufw防火墙
sudo ufw allow 8000/tcp

# 方法2:仅允许内网访问
sudo ufw allow from 192.168.1.0/24 to any port 8000

# 方法3:iptables
sudo iptables -A INPUT -p tcp --dport 8000 -j ACCEPT

限制访问源:

# 仅允许指定IP访问
sudo ufw allow from 192.168.1.100 to any port 8000

# 允许IP段访问
sudo ufw allow from 192.168.1.0/24 to any port 8000

# 拒绝其他所有访问
sudo ufw deny 8000/tcp

查看防火墙规则:

# ufw规则
sudo ufw status numbered

# iptables规则
sudo iptables -L INPUT -n --line-numbers | grep 8000

13.2.4 测试HTTP服务

本地测试:

# 使用curl测试
curl http://localhost:8000

# 查看HTTP头
curl -I http://localhost:8000

# 下载index.html
curl -O http://localhost:8000/index.html

远程测试:

# 从另一台机器测试
curl http://192.168.1.66:8000

# Windows PowerShell测试
Invoke-WebRequest -Uri http://192.168.1.66:8000

浏览器测试:

打开浏览器,访问:
http://192.168.1.66:8000

预期结果:
显示VPN管理系统界面

13.2.5 HTTP服务日志

查看访问日志:

# http.server输出到终端
# 示例:
192.168.1.100 - - [23/Mar/2025 10:30:15] "GET / HTTP/1.1" 200 -
192.168.1.100 - - [23/Mar/2025 10:30:16] "GET /index.html HTTP/1.1" 200 -

重定向日志到文件:

# 启动时指定日志文件
python3 -m http.server 8000 > /var/log/vpn-demo/http.log 2>&1 &

# 实时查看日志
tail -f /var/log/vpn-demo/http.log

日志格式说明:

192.168.1.100        # 客户端IP
-                    # 远程用户(通常为-)
-                    # 认证用户(通常为-)
[23/Mar/2025 10:30:15] # 时间戳
"GET / HTTP/1.1"     # 请求方法和路径
200                  # HTTP状态码
-                    # 响应大小(字节)

13.3 访问地址配置

13.3.1 服务访问地址

完整的访问地址清单:

服务地址端口协议用途
Web管理界面http://192.168.1.66:80008000HTTP前端控制面板
Flask APIhttp://192.168.1.66:50005000HTTP后端API
WireGuard VPN192.168.1.66:5182051820UDPVPN隧道
内网演示服务http://10.8.0.1:80008000HTTP通过VPN访问

13.3.2 主机名配置(可选)

配置域名解析(本地):

如果不想记IP地址,可以配置本地hosts:

Linux/macOS:

# 编辑hosts文件
sudo nano /etc/hosts

# 添加以下行
192.168.1.66  vpn.local
192.168.1.66  vpn-api.local

# 保存后测试
ping vpn.local
curl http://vpn.local:8000

Windows:

# 以管理员身份运行记事本
notepad C:\Windows\System32\drivers\etc\hosts

# 添加以下行
192.168.1.66  vpn.local
192.168.1.66  vpn-api.local

# 保存后测试
ping vpn.local

访问地址变更为:

Web界面:http://vpn.local:8000
API:http://vpn-api.local:5000

13.3.3 CORS配置(跨域访问)

如果前端和后端部署在不同端口或域名,需要配置CORS。

安装flask-cors:

sudo pip3 install flask-cors

修改vpn_api.py:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# 允许所有来源(开发环境)
CORS(app)

# 或限制特定来源(生产环境)
# CORS(app, resources={r"/api/*": {"origins": "http://192.168.1.66:8000"}})

测试CORS:

# 发送跨域请求
curl -H "Origin: http://example.com" \
     -H "Access-Control-Request-Method: POST" \
     -X OPTIONS http://192.168.1.66:5000/api/vpn/start

13.3.4 HTTPS配置(可选)

使用Nginx反向代理+Let’s Encrypt:

安装Nginx:

sudo apt install nginx

配置反向代理:

sudo nano /etc/nginx/sites-available/vpn-mgmt

配置内容:

server {
    listen 80;
    server_name vpn.example.com;

    # 前端静态文件
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # API接口
    location /api/ {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

启用配置:

sudo ln -s /etc/nginx/sites-available/vpn-mgmt /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

安装SSL证书(Let’s Encrypt):

# 安装certbot
sudo apt install certbot python3-certbot-nginx

# 获取证书
sudo certbot --nginx -d vpn.example.com

# 自动续期
sudo certbot renew --dry-run

访问地址变更为:

HTTPS:https://vpn.example.com

💡 提示:本项目为内网环境,使用HTTP即可;公网环境建议配置HTTPS。

13.3.5 移动端访问

同一局域网访问:

确保移动设备连接到同一WiFi网络(192.168.1.0/24),然后访问:

http://192.168.1.66:8000

通过VPN访问:

移动设备连接VPN后,可以使用内网地址:

http://10.8.0.1:8000

二维码快速访问:

生成二维码方便移动设备扫描:

# 安装qrencode
sudo apt install qrencode

# 生成二维码(终端显示)
qrencode -t ANSI "http://192.168.1.66:8000"

# 生成二维码图片
qrencode -o vpn-qrcode.png "http://192.168.1.66:8000"

14. 统一启动脚本

14.1 start-api.sh脚本内容

14.1.1 完整启动脚本

创建启动脚本:

# 进入项目目录
cd /home/vpn-demo

# 创建脚本文件
sudo nano start-api.sh

完整脚本内容:

#!/bin/bash

#######################################
# WireGuard VPN 管理系统启动脚本
# 
# 功能:
#   1. 停止旧进程
#   2. 启动Flask API后端
#   3. 启动HTTP前端服务
#   4. 显示服务状态
#
# 作者:谢北然 (42006606)
# 日期:2025-03-23
#######################################

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 配置变量
PROJECT_DIR="/home/vpn-demo"
API_PORT=5000
WEB_PORT=8000
LOG_DIR="/var/log/vpn-demo"
API_LOG="${LOG_DIR}/api.log"
WEB_LOG="${LOG_DIR}/web.log"

# 打印带颜色的消息
print_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 打印分隔线
print_separator() {
    echo "=========================================="
}

# 检查是否以root运行
check_root() {
    if [ "$EUID" -ne 0 ]; then
        print_error "请使用root权限运行此脚本"
        echo "使用方法: sudo $0"
        exit 1
    fi
}

# 创建日志目录
create_log_dir() {
    if [ ! -d "$LOG_DIR" ]; then
        print_info "创建日志目录: $LOG_DIR"
        mkdir -p "$LOG_DIR"
    fi
}

# 停止旧进程
stop_old_processes() {
    print_info "正在停止旧服务..."
    
    # 停止Flask API
    API_PID=$(pgrep -f "vpn_api.py")
    if [ -n "$API_PID" ]; then
        print_warning "发现Flask API进程 (PID: $API_PID),正在停止..."
        pkill -f vpn_api.py
        sleep 1
    fi
    
    # 停止HTTP服务器
    WEB_PID=$(pgrep -f "python3 -m http.server.*${WEB_PORT}")
    if [ -n "$WEB_PID" ]; then
        print_warning "发现HTTP服务进程 (PID: $WEB_PID),正在停止..."
        pkill -f "python3 -m http.server.*${WEB_PORT}"
        sleep 1
    fi
    
    # 强制释放端口
    fuser -k ${API_PORT}/tcp 2>/dev/null
    fuser -k ${WEB_PORT}/tcp 2>/dev/null
    
    print_success "旧服务已停止"
}

# 检查端口占用
check_port() {
    local port=$1
    local service=$2
    
    if lsof -Pi :${port} -sTCP:LISTEN -t >/dev/null 2>&1 ; then
        print_error "${service}端口 ${port} 仍被占用"
        print_info "占用进程信息:"
        lsof -i :${port}
        return 1
    else
        print_success "${service}端口 ${port} 可用"
        return 0
    fi
}

# 启动Flask API
start_flask_api() {
    print_info "正在启动Flask API (端口: ${API_PORT})..."
    
    cd "$PROJECT_DIR"
    
    # 后台启动Flask
    nohup python3 vpn_api.py > "$API_LOG" 2>&1 &
    
    # 等待服务启动
    sleep 3
    
    # 检查是否启动成功
    if pgrep -f "vpn_api.py" > /dev/null; then
        API_PID=$(pgrep -f "vpn_api.py")
        print_success "Flask API 启动成功 (PID: ${API_PID})"
        
        # 测试API健康检查
        if curl -s http://localhost:${API_PORT}/health > /dev/null 2>&1; then
            print_success "API健康检查通过"
        else
            print_warning "API健康检查失败,请检查日志"
        fi
    else
        print_error "Flask API 启动失败"
        print_info "查看日志: tail -f $API_LOG"
        return 1
    fi
}

# 启动HTTP服务器
start_http_server() {
    print_info "正在启动HTTP服务器 (端口: ${WEB_PORT})..."
    
    cd "$PROJECT_DIR"
    
    # 后台启动HTTP服务器
    nohup python3 -m http.server ${WEB_PORT} > "$WEB_LOG" 2>&1 &
    
    # 等待服务启动
    sleep 2
    
    # 检查是否启动成功
    if pgrep -f "python3 -m http.server.*${WEB_PORT}" > /dev/null; then
        WEB_PID=$(pgrep -f "python3 -m http.server.*${WEB_PORT}")
        print_success "HTTP服务器启动成功 (PID: ${WEB_PID})"
        
        # 测试HTTP访问
        if curl -s http://localhost:${WEB_PORT} > /dev/null 2>&1; then
            print_success "HTTP服务测试通过"
        else
            print_warning "HTTP服务测试失败,请检查日志"
        fi
    else
        print_error "HTTP服务器启动失败"
        print_info "查看日志: tail -f $WEB_LOG"
        return 1
    fi
}

# 显示服务状态
show_status() {
    print_separator
    print_info "服务状态:"
    print_separator
    
    # Flask API状态
    if pgrep -f "vpn_api.py" > /dev/null; then
        API_PID=$(pgrep -f "vpn_api.py")
        print_success "Flask API: 运行中 (PID: ${API_PID})"
    else
        print_error "Flask API: 已停止"
    fi
    
    # HTTP服务器状态
    if pgrep -f "python3 -m http.server.*${WEB_PORT}" > /dev/null; then
        WEB_PID=$(pgrep -f "python3 -m http.server.*${WEB_PORT}")
        print_success "HTTP服务器: 运行中 (PID: ${WEB_PID})"
    else
        print_error "HTTP服务器: 已停止"
    fi
    
    print_separator
}

# 显示访问信息
show_access_info() {
    # 获取本机IP
    LOCAL_IP=$(hostname -I | awk '{print $1}')
    
    print_separator
    print_info "访问地址:"
    print_separator
    echo ""
    echo -e "  ${GREEN}Web管理界面:${NC}"
    echo -e "    http://localhost:${WEB_PORT}"
    echo -e "    http://${LOCAL_IP}:${WEB_PORT}"
    echo ""
    echo -e "  ${GREEN}API接口:${NC}"
    echo -e "    http://localhost:${API_PORT}"
    echo -e "    http://${LOCAL_IP}:${API_PORT}"
    echo ""
    echo -e "  ${GREEN}API文档:${NC}"
    echo -e "    健康检查: http://${LOCAL_IP}:${API_PORT}/health"
    echo -e "    VPN状态:  http://${LOCAL_IP}:${API_PORT}/api/vpn/status"
    echo ""
    print_separator
    print_info "日志文件:"
    print_separator
    echo "  API日志: tail -f $API_LOG"
    echo "  Web日志: tail -f $WEB_LOG"
    echo ""
    print_separator
    print_info "停止服务:"
    print_separator
    echo "  停止所有: sudo pkill -f vpn_api.py && sudo pkill -f 'python3 -m http.server'"
    echo "  停止API:  sudo pkill -f vpn_api.py"
    echo "  停止Web:  sudo pkill -f 'python3 -m http.server'"
    echo ""
    print_separator
}

# 主函数
main() {
    clear
    print_separator
    echo -e "${BLUE}WireGuard VPN 管理系统启动脚本${NC}"
    print_separator
    echo ""
    
    # 检查root权限
    check_root
    
    # 创建日志目录
    create_log_dir
    
    # 停止旧进程
    stop_old_processes
    
    # 等待端口释放
    sleep 2
    
    # 检查端口
    check_port $API_PORT "Flask API" || exit 1
    check_port $WEB_PORT "HTTP服务器" || exit 1
    
    echo ""
    
    # 启动服务
    start_flask_api || exit 1
    echo ""
    start_http_server || exit 1
    
    echo ""
    
    # 显示状态
    show_status
    
    # 显示访问信息
    show_access_info
    
    print_success "所有服务启动完成!"
    echo ""
}

# 执行主函数
main

保存文件(Ctrl+X, Y, Enter)。

14.1.2 脚本功能说明

脚本主要功能:

  1. 环境检查

    • 检查root权限
    • 创建日志目录
    • 检查端口占用
  2. 停止旧服务

    • 查找并停止Flask API进程
    • 查找并停止HTTP服务器进程
    • 强制释放端口
  3. 启动新服务

    • 后台启动Flask API
    • 后台启动HTTP服务器
    • 验证服务健康状态
  4. 状态显示

    • 显示进程PID
    • 显示访问地址
    • 显示日志路径
    • 显示停止命令

脚本特点:

14.2 脚本权限设置

14.2.1 设置可执行权限

添加执行权限:

# 设置所有者为root
sudo chown root:root /home/vpn-demo/start-api.sh

# 添加执行权限
sudo chmod +x /home/vpn-demo/start-api.sh

# 验证权限
ls -l /home/vpn-demo/start-api.sh

预期输出:

-rwxr-xr-x 1 root root 8192 Mar 23 10:00 /home/vpn-demo/start-api.sh

权限说明:

14.2.2 创建符号链接(可选)

方便全局调用:

# 创建符号链接到/usr/local/bin
sudo ln -s /home/vpn-demo/start-api.sh /usr/local/bin/vpn-start

# 验证链接
ls -l /usr/local/bin/vpn-start

# 现在可以直接运行
sudo vpn-start

14.2.3 设置别名(可选)

添加bash别名:

# 编辑.bashrc
nano ~/.bashrc

# 添加别名
alias vpn-start='sudo /home/vpn-demo/start-api.sh'
alias vpn-stop='sudo pkill -f vpn_api.py && sudo pkill -f "python3 -m http.server"'
alias vpn-status='sudo systemctl status vpn-api'
alias vpn-logs='sudo tail -f /var/log/vpn-demo/api.log'

# 重新加载配置
source ~/.bashrc

# 使用别名
vpn-start

14.3 启动方法

14.3.1 手动启动

基本启动:

# 进入目录
cd /home/vpn-demo

# 以root权限执行
sudo ./start-api.sh

从任意位置启动:

# 使用绝对路径
sudo /home/vpn-demo/start-api.sh

# 或使用符号链接
sudo vpn-start

# 或使用别名
vpn-start

14.3.2 启动输出示例

==========================================
WireGuard VPN 管理系统启动脚本
==========================================

[INFO] 正在停止旧服务...
[SUCCESS] 旧服务已停止
[SUCCESS] Flask API端口 5000 可用
[SUCCESS] HTTP服务器端口 8000 可用

[INFO] 正在启动Flask API (端口: 5000)...
[SUCCESS] Flask API 启动成功 (PID: 1234)
[SUCCESS] API健康检查通过

[INFO] 正在启动HTTP服务器 (端口: 8000)...
[SUCCESS] HTTP服务器启动成功 (PID: 5678)
[SUCCESS] HTTP服务测试通过

==========================================
[INFO] 服务状态:
==========================================
[SUCCESS] Flask API: 运行中 (PID: 1234)
[SUCCESS] HTTP服务器: 运行中 (PID: 5678)
==========================================

==========================================
[INFO] 访问地址:
==========================================

  Web管理界面:
    http://localhost:8000
    http://192.168.1.66:8000

  API接口:
    http://localhost:5000
    http://192.168.1.66:5000

  API文档:
    健康检查: http://192.168.1.66:5000/health
    VPN状态:  http://192.168.1.66:5000/api/vpn/status

==========================================
[INFO] 日志文件:
==========================================
  API日志: tail -f /var/log/vpn-demo/api.log
  Web日志: tail -f /var/log/vpn-demo/web.log

==========================================
[INFO] 停止服务:
==========================================
  停止所有: sudo pkill -f vpn_api.py && sudo pkill -f 'python3 -m http.server'
  停止API:  sudo pkill -f vpn_api.py
  停止Web:  sudo pkill -f 'python3 -m http.server'

==========================================

[SUCCESS] 所有服务启动完成!

14.3.3 开机自启动

方法1:使用crontab

# 编辑root的crontab
sudo crontab -e

# 添加以下行(开机时执行)
@reboot /home/vpn-demo/start-api.sh

# 保存退出后查看
sudo crontab -l

方法2:使用rc.local(传统方法)

# 编辑rc.local
sudo nano /etc/rc.local

# 在exit 0之前添加
/home/vpn-demo/start-api.sh

# 确保rc.local可执行
sudo chmod +x /etc/rc.local

# 启用rc-local服务
sudo systemctl enable rc-local

方法3:使用systemd(最推荐)

创建systemd服务文件:

sudo nano /etc/systemd/system/vpn-mgmt.service

服务配置:

[Unit]
Description=VPN Management System (API + Web)
After=network.target [email protected]
Wants[email protected]

[Service]
Type=forking
ExecStart=/home/vpn-demo/start-api.sh
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

启用服务:

# 重新加载systemd
sudo systemctl daemon-reload

# 启用开机自启
sudo systemctl enable vpn-mgmt

# 立即启动
sudo systemctl start vpn-mgmt

# 查看状态
sudo systemctl status vpn-mgmt

14.4 停止方法

14.4.1 手动停止

停止所有服务:

# 停止Flask API
sudo pkill -f vpn_api.py

# 停止HTTP服务器
sudo pkill -f "python3 -m http.server"

# 一行命令停止所有
sudo pkill -f vpn_api.py && sudo pkill -f "python3 -m http.server"

# 强制释放端口
sudo fuser -k 5000/tcp
sudo fuser -k 8000/tcp

使用别名停止:

# 如果配置了别名
vpn-stop

验证停止:

# 检查进程
ps aux | grep vpn_api.py
ps aux | grep "http.server"

# 检查端口
sudo lsof -i :5000
sudo lsof -i :8000

# 都应该没有输出,表示服务已停止

14.4.2 使用systemd停止

如果使用systemd管理:

# 停止服务
sudo systemctl stop vpn-mgmt

# 禁用开机自启
sudo systemctl disable vpn-mgmt

# 查看状态
sudo systemctl status vpn-mgmt

14.4.3 紧急停止

强制杀死所有Python进程(危险):

# 查看所有Python进程
ps aux | grep python3

# 杀死特定PID
sudo kill -9 <PID>

# 杀死所有相关进程(谨慎使用)
sudo killall -9 python3

⚠️ 警告killall python3会杀死所有Python进程,可能影响其他服务。

14.5 状态检查

14.5.1 检查服务运行状态

查看进程:

# 检查Flask API
ps aux | grep vpn_api.py | grep -v grep

# 检查HTTP服务器
ps aux | grep "http.server" | grep -v grep

# 检查所有相关进程
ps aux | grep -E "vpn_api|http.server" | grep -v grep

输出示例:

root      1234  0.1  0.5  50000 10000 ?  S  10:00  0:05 python3 vpn_api.py
root      5678  0.0  0.3  40000  7000 ?  S  10:00  0:02 python3 -m http.server 8000

14.5.2 检查端口监听

查看监听端口:

# 方法1:netstat
sudo netstat -tulnp | grep -E "5000|8000"

# 方法2:ss
sudo ss -tulnp | grep -E "5000|8000"

# 方法3:lsof
sudo lsof -i :5000
sudo lsof -i :8000

输出示例:

tcp  0  0 0.0.0.0:5000  0.0.0.0:*  LISTEN  1234/python3
tcp  0  0 0.0.0.0:8000  0.0.0.0:*  LISTEN  5678/python3

14.5.3 API健康检查

测试API接口:

# 健康检查
curl http://localhost:5000/health

# 预期输出
# {"status":"healthy","service":"VPN Management API","version":"1.0"}

# VPN状态查询
curl http://localhost:5000/api/vpn/status

# 系统信息
curl http://localhost:5000/api/system/info

14.5.4 Web服务检查

测试Web界面:

# 访问首页
curl -I http://localhost:8000

# 预期输出
# HTTP/1.0 200 OK
# Server: SimpleHTTP/0.6 Python/3.10.12
# Date: ...
# Content-type: text/html

# 下载index.html
curl -s http://localhost:8000/index.html | head -n 5

14.5.5 查看实时日志

查看API日志:

# 实时查看
tail -f /var/log/vpn-demo/api.log

# 查看最近100行
tail -n 100 /var/log/vpn-demo/api.log

# 查看错误日志
grep -i error /var/log/vpn-demo/api.log

查看Web日志:

# 实时查看
tail -f /var/log/vpn-demo/web.log

# 查看访问记录
tail -n 50 /var/log/vpn-demo/web.log | grep "GET"

systemd日志(如果使用systemd):

# 查看服务日志
sudo journalctl -u vpn-mgmt -f

# 查看最近100行
sudo journalctl -u vpn-mgmt -n 100

# 查看错误日志
sudo journalctl -u vpn-mgmt -p err

# 查看今天的日志
sudo journalctl -u vpn-mgmt --since today

14.5.6 性能监控

监控资源使用:

# CPU和内存使用
ps -p $(pgrep -f vpn_api.py) -o %cpu,%mem,cmd

# 使用top监控
top -p $(pgrep -f vpn_api.py)

# 使用htop(更友好)
htop -p $(pgrep -f vpn_api.py),$(pgrep -f "http.server")

网络连接统计:

# 查看连接数
sudo netstat -an | grep :5000 | grep ESTABLISHED | wc -l
sudo netstat -an | grep :8000 | grep ESTABLISHED | wc -l

# 查看详细连接
sudo netstat -anp | grep -E "5000|8000"

XBR

© 2026 XBR

bilibili bluesky discord