diff --git a/README.md b/README.md index 28ebce1..5b13584 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,366 @@ -# lmvpn_server +# LMVPN -## 目录说明 +基于 WebSocket 隧道与 TUN 虚拟网卡的轻量级三层 VPN 系统,采用 Go + Vue 3 构建。服务端通过 WebSocket 与客户端建立控制通道,TUN 网卡与 WebSocket 之间双向搬运原始 IP 数据包,实现点对点与点对站点网络连接。 -- `data/` - 程序运行生成的数据(配置文件、数据库文件) -- `dist/` - 前端构建产物,由 Go 服务端托管提供静态资源 -- `frontend/` - 前端工程(Vue 3 + TypeScript + Vite) -- `internal/` - Go 后端源码 - - `config/` - 配置加载 - - `db/` - 数据库操作 - - `handler/` - HTTP 请求处理 - - `middleware/` - 中间件(认证等) - - `model/` - 数据模型 - - `vpn/` - VPN 相关逻辑(认证、隧道等) -- `pytest/` - Python 测试脚本 -- `main.go` - Go 服务端入口文件 -- `go.mod` / `go.sum` - Go 模块依赖管理 +> **平台说明**:服务端部署**目前仅在 Linux 下测试功能正常**(systemd + nftables/iptables NAT)。客户端协议规范见 [`docs/client-development.md`](docs/client-development.md)。 -## 安全配置 +--- -### JWT 密钥 +## 目录 -JWT 密钥按以下优先级加载: +- [环境要求](#环境要求) +- [一键部署(Linux,推荐)](#一键部署linux推荐) +- [首次登录与初始化](#首次登录与初始化) +- [手动部署](#手动部署) +- [配置说明](#配置说明) +- [反向代理(HTTPS/WSS)](#反向代理httpswss) +- [服务管理](#服务管理) +- [升级](#升级) +- [目录说明](#目录说明) +- [常见问题排查](#常见问题排查) +- [相关文档](#相关文档) -1. 环境变量 `LMVPN_JWT_SECRET` -2. 配置文件 `data/config.yml` 中的 `web.jwt_secret` -3. 首次启动时自动生成 32 字节随机密钥并写入配置文件 +--- -生产环境建议通过环境变量注入,避免密钥落盘。 +## 环境要求 -### 默认管理员 +- **操作系统**:Linux(已测试),需 root 或 sudo 权限 +- **Go**:≥ 1.26.4 +- **Node.js**:≥ 22.18(用于构建前端) +- **内核**:支持 TUN 设备(`/dev/net/tun`),开启 `ip_forward` +- **防火墙工具**:nftables(推荐)或 iptables +- **网络**:公网 IP,开放 Web 端口(或经反向代理) -首次启动时自动创建管理员账户,密码为随机生成的 16 位字符串。密码会: +--- -- 打印到 stdout(仅一次) -- 写入 `data/.initial_admin_password`(权限 0600) +## 一键部署(Linux,推荐) -请登录后立即修改密码,并删除 `data/.initial_admin_password` 文件。 +仓库自带 `install_linux.sh`,一条命令完成构建、部署、网络配置与 systemd 服务安装。 -### 登录限流 +```bash +git clone <仓库地址> lmvpn_server +cd lmvpn_server +sudo bash install_linux.sh +``` -`/api/login` 和 WebSocket 密码认证均限制每 IP 5 次/分钟。 +### 脚本执行流程 -### Unix Socket 权限 +1. 拉取最新代码(`git fetch origin && git reset --hard origin/main`) +2. 构建前端:`cd frontend && npm install && npm run build`(产物输出到 `../dist`) +3. 编译后端:`go build -o lmvpn .` +4. 创建无 shell 的系统用户 `lmvpn` +5. 停止旧服务,部署到 `/opt/lmvpn`(二进制 + `dist/`),属主改为 `lmvpn` +6. 开启内核 IP 转发(IPv4 / IPv6),写入 `/etc/sysctl.d/99-lmvpn.conf` +7. 配置 NAT 与转发规则:自动检测出口网卡,优先 nft,回退 iptables +8. 安装 systemd 服务 `/etc/systemd/system/lmvpn.service`,以 `lmvpn` 用户运行并授予 `CAP_NET_ADMIN` / `CAP_NET_RAW` +9. 启动服务并打印状态 -默认权限兼容反向代理(如 Caddy),可通过配置文件调整: +> ⚠️ 脚本中的 `git reset --hard origin/main` 会**丢弃所有本地未提交改动**。部署前请确保工作区干净,或先提交/暂存。 + +### 子网变量 + +脚本顶部有两个变量需与后台 VPN 设置保持一致: + +```bash +VPN_SUBNET="192.168.77.0/24" # IPv4 子网 +VPN_SUBNET6="fd00:dead:beef::/112" # IPv6 子网,留空则不配置 IPv6 NAT +``` + +若在管理后台修改了子网,需同步修改此处并重新执行脚本,或手动更新 NAT 规则,否则客户端无法上网。 + +--- + +## 首次登录与初始化 + +1. **获取初始密码**:首次启动时自动创建管理员 `admin`,密码为随机 16 位字符串,会 + - 打印到 stdout(仅一次) + - 写入 `/opt/lmvpn/data/.initial_admin_password`(权限 0600) + + ```bash + sudo cat /opt/lmvpn/data/.initial_admin_password + ``` + +2. **登录**:浏览器访问 `http://<服务器IP>:8080`,使用 `admin` + 初始密码登录。 + +3. **立即修改密码**,并删除初始密码文件: + + ```bash + sudo rm /opt/lmvpn/data/.initial_admin_password + ``` + +4. **启用 VPN**:进入「管理后台 → VPN 管理」,确认子网(默认 `192.168.77.0/24` + IPv6 `fd00:dead:beef::/112`),打开「启用」并保存。 + +5. **诊断检查**:VPN 管理页的「系统环境检测」面板(对应 `GET /api/admin/vpn/diag`)会检测 ip_forward、NAT、TUN 等,客户端无法上网时优先查看此处。 + +--- + +## 手动部署 + +如需自行控制部署细节,可参照以下步骤。 + +### 1. 构建产物 + +```bash +# 前端 +cd frontend +npm install +npm run build # 产物输出到 ../dist +cd .. + +# 后端 +go build -o lmvpn . +``` + +### 2. 部署文件 + +```bash +sudo useradd -r -s /bin/false lmvpn +sudo mkdir -p /opt/lmvpn +sudo cp lmvpn /opt/lmvpn/ +sudo cp -r dist /opt/lmvpn/ +sudo chown -R lmvpn:lmvpn /opt/lmvpn +``` + +### 3. 开启内核转发 + +```bash +sudo sysctl -w net.ipv4.ip_forward=1 +sudo sysctl -w net.ipv6.conf.all.forwarding=1 # IPv6 双栈时需要 + +cat <<'EOF' | sudo tee /etc/sysctl.d/99-lmvpn.conf +net.ipv4.ip_forward = 1 +net.ipv6.conf.all.forwarding = 1 +EOF +sudo sysctl -p /etc/sysctl.d/99-lmvpn.conf +``` + +### 4. 配置 NAT(nft 示例) + +将 `WAN_IFACE` 替换为出口网卡,`VPN_SUBNET` 替换为 VPN 子网: + +```bash +WAN_IFACE=eth0 +VPN_SUBNET=192.168.77.0/24 +VPN_SUBNET6=fd00:dead:beef::/112 + +sudo nft add table inet lmvpn_nat +sudo nft 'add chain inet lmvpn_nat postrouting { type nat hook postrouting priority 100 ; }' +sudo nft add rule inet lmvpn_nat postrouting oifname "$WAN_IFACE" ip saddr "$VPN_SUBNET" masquerade +sudo nft add rule inet lmvpn_nat postrouting oifname "$WAN_IFACE" ip6 saddr "$VPN_SUBNET6" masquerade +sudo nft 'add chain inet lmvpn_nat forward { type filter hook forward priority 0 ; policy accept ; }' +sudo nft add rule inet lmvpn_nat forward ip saddr "$VPN_SUBNET" accept +sudo nft add rule inet lmvpn_nat forward ip daddr "$VPN_SUBNET" accept +``` + +### 5. 安装 systemd 服务 + +```bash +sudo tee /etc/systemd/system/lmvpn.service >/dev/null <<'EOF' +[Unit] +Description=LMVPN Server +After=network.target + +[Service] +Type=simple +User=lmvpn +WorkingDirectory=/opt/lmvpn +ExecStart=/opt/lmvpn/lmvpn +Restart=on-failure +RestartSec=5 +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable --now lmvpn +``` + +--- + +## 配置说明 + +配置文件位于 `data/config.yml`(相对工作目录,即 `/opt/lmvpn/data/config.yml`),首次启动时自动生成。 ```yaml web: - sock: "/run/lmvpnweb.sock" - sock_mode: "0666" # socket 文件权限,默认 0666 - sock_group: "" # socket 文件 group,空=不修改 - sock_dir_mode: "0755" # socket 目录权限,默认 0755 + port: 8080 # TCP 监听端口,0 则不监听 TCP + sock: "/run/lmvpnweb.sock" # Unix Socket 路径,空则不监听 socket + sock_mode: "0666" # socket 文件权限 + sock_group: "" # socket 文件属组,空=不修改 + sock_dir_mode: "0755" # socket 目录权限 + jwt_secret: "" # JWT 密钥,留空则按下面规则生成 +database: + type: sqlite # sqlite 或 mysql + path: data/lmvpn.db # sqlite 文件路径 + dsn: "" # mysql DSN(type=mysql 时必填) ``` -多租户/高安全场景建议收紧: +> `web.port` 与 `web.sock` **至少配置一个**,两者都配则同时监听。生产环境建议仅留一个,由反向代理统一接入。 + +### JWT 密钥 + +按以下优先级加载: + +1. 环境变量 `LMVPN_JWT_SECRET` +2. 配置文件 `web.jwt_secret` +3. 首次启动自动生成 32 字节随机密钥并写入配置文件 + +生产环境建议通过环境变量注入,避免密钥落盘: + +```bash +# /etc/systemd/system/lmvpn.service 的 [Service] 段追加 +Environment=LMVPN_JWT_SECRET=<你的密钥> +``` + +### Unix Socket 权限收紧 + +多租户/高安全场景建议收紧 socket 权限(将 lmvpn 进程用户加入反代用户组): ```yaml web: sock: "/run/lmvpnweb.sock" sock_mode: "0660" - sock_group: "caddy" # 将 lmvpn 进程用户加入 caddy group + sock_group: "caddy" sock_dir_mode: "0750" ``` -### 生产部署 +> 若服务以非 root 用户运行且 socket 目录不可写,可将 `sock` 指向用户可写目录(如 `/opt/lmvpn/run/lmvpnweb.sock`),或设 `sock: ""` 仅用 TCP。 + +--- + +## 反向代理(HTTPS/WSS) + +生产环境**必须**通过反向代理提供 HTTPS/WSS。服务端默认监听 HTTP `:8080`,由反代终结 TLS。 + +### Caddy(自动 HTTPS) + +```caddyfile +lmvpn.example.com { + reverse_proxy 127.0.0.1:8080 +} +``` + +Caddy 的 `reverse_proxy` 自动处理 WebSocket 升级,无需额外配置。 + +若使用 Unix Socket 接入: + +```caddyfile +lmvpn.example.com { + reverse_proxy unix//run/lmvpnweb.sock +} +``` + +### Nginx + +```nginx +server { + listen 443 ssl http2; + server_name lmvpn.example.com; + + ssl_certificate /etc/ssl/certs/lmvpn.pem; + ssl_certificate_key /etc/ssl/private/lmvpn.key; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /ws { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + } +} +``` + +> 部署反代后,客户端应使用 `wss://lmvpn.example.com/ws` 连接。`/ws` 的长连接超时建议调大(如 3600s),避免空闲断连。 + +--- + +## 服务管理 + +```bash +sudo systemctl start lmvpn # 启动 +sudo systemctl stop lmvpn # 停止 +sudo systemctl restart lmvpn # 重启 +sudo systemctl status lmvpn # 状态 +sudo systemctl enable lmvpn # 开机自启 +``` + +查看实时日志: + +```bash +sudo journalctl -u lmvpn -f +``` + +--- + +## 升级 + +最简方式:重新执行一键脚本,它会自动拉取最新代码、重新构建并重启服务。 + +```bash +cd lmvpn_server +git pull +sudo bash install_linux.sh +``` + +> 脚本会 `git reset --hard origin/main`,本地改动会被覆盖。`/opt/lmvpn/data/` 下的配置与数据库不受影响。 + +--- + +## 目录说明 + +- `data/` — 运行时数据(`config.yml`、`lmvpn.db`、初始密码文件),gitignore +- `dist/` — 前端构建产物,由 Go 服务端托管,gitignore +- `frontend/` — 前端工程(Vue 3 + TypeScript + Vite) +- `internal/` — Go 后端源码 + - `config/` — 配置加载 + - `db/` — 数据库初始化 + - `handler/` — HTTP 请求处理 + - `middleware/` — 中间件(认证、限流) + - `model/` — 数据模型 + - `router/` — 路由 + - `vpn/` — VPN 核心(认证、隧道、TUN、包转发) +- `docs/` — 文档 +- `pytest/` — Python 测试脚本 +- `install_linux.sh` — Linux 一键部署脚本 +- `main.go` — 服务端入口 + +--- + +## 常见问题排查 + +| 现象 | 可能原因 | 排查建议 | +|------|----------|----------| +| 服务启动失败 | socket 目录不可写 / 端口占用 | `journalctl -u lmvpn`;设 `sock: ""` 或换可写目录 | +| 客户端连上但无法上网 | 未开 ip_forward / 未配 NAT / 子网不一致 | 管理后台诊断面板;核对脚本 `VPN_SUBNET` 与后台设置 | +| 登录提示「请求过于频繁」 | 触发限流(`/api/login` 5 次/分钟·IP) | 等待 1 分钟后重试 | +| 修改子网后客户端异常 | NAT 规则仍是旧子网 | 同步脚本变量并重跑 `install_linux.sh` | +| WebSocket 频繁断开 | 反代未透传 `Upgrade`/`Connection` 头 | 检查反代 `/ws` 配置;调大读超时 | +| 忘记管理员密码 | — | 删除 `data/lmvpn.db` 重新初始化(会丢失所有数据),或直接改库 | + +--- + +## 相关文档 + +- [客户端开发协议规范](docs/client-development.md) — WebSocket 隧道协议、认证、握手、数据面、TUN 配置等完整规范 + +--- + +## 安全提示 -- 生产环境必须通过反向代理(如 Caddy/Nginx)提供 HTTPS/WSS - 配置文件 `data/config.yml` 权限为 0600,仅限运行用户读写 -- WebSocket `/ws` 端点支持 JWT 认证(`?token=xxx`)和密码认证两种方式 +- `/api/login` 与 WebSocket 密码认证均限制每 IP 5 次/分钟 +- WebSocket `/ws` 支持 JWT(`?token=xxx`)与用户名/密码两种认证 +- 生产环境务必经反向代理启用 HTTPS/WSS