cnbugs a5519c2f6c Feature: per-instance public_host for .ovpn
When creating/editing an instance, add '公网地址' (public_host) field.
This is the domain/IP clients will use to reach the OpenVPN server.
When downloading a .ovpn, this field is used first, falling back to:
  1. ?host= query parameter (one-time override)
  2. Request Host header
  3. 'vpn.example.com' default

This eliminates the need to manually edit .ovpn files after download
when the server's public address differs from its internal IP.

Backend changes:
  model.Instance: add PublicHost string field
  service.GenerateOVPN: priority PublicHost > remoteHost > default
  service.UpdateInstance: PublicHost persisted (already via JSON tag)

Frontend changes:
  Instances.vue: new '公网地址' input in create/edit dialog
  Instances.vue: new column in list table (shows '未配置' when empty)
  Users.vue: hint text updated to '可选: 覆盖实例公网地址'

E2E verified:
  public_host='vpn.yunwei.blog'  → .ovpn has 'remote vpn.yunwei.blog 1194'
  public_host='' + host=X        → .ovpn has 'remote X 1194'
  public_host set + no host      → public_host wins
2026-08-10 00:35:30 +08:00
2026-08-09 21:45:41 +08:00
2026-08-09 20:32:37 +08:00

OpenVPN Manager

一个开箱即用的 OpenVPN Web 管理控制台 —— 多实例托管、客户端证书一键签发、流量审计、证书到期提醒、自动备份与恢复。 基于 Go (Gin) + Vue 3 + Element Plus + ECharts,单二进制部署。

dashboard Vue License


目录


功能特性

模块 说明
多实例管理 同一台机器上跑多个 OpenVPN 实例,每个独立端口/协议/子网/PKI
客户端证书 一键签发,自动生成 .ovpn(内嵌 CA/Cert/Key/TLS-Auth),无需额外文件
固定 IP 通过 CCD (client-config-dir) 为指定用户分配固定 VPN IP
访问控制(白名单) 实例/用户两层 allow_networks,合并生效;服务端 iptables 强制隔离
多账号管理 db.json 中存 bcrypt 哈希,可创建多个 admin/operator 账号,首登强制改密
启停控制 Web 一键启动/停止实例,显示 PID 与状态
流量审计 解析 status-version 3 输出,记录上下行字节/连接时长
证书到期提醒 仪表盘统计 30 天内到期的证书,单独证书管理页查看完整清单
自动备份 一键打包 PKI + 实例配置 + 客户端配置为 tar.gz
一键恢复 上传/选择已有备份,直接覆盖还原
操作审计 所有写操作(创建/修改/删除/吊销/备份)持久化,记录操作者/IP/结果
JWT 鉴权 登录后 12 小时有效 token,无状态可水平扩展
内网转发 文档化 IP 转发 + NAT 一键配置,客户端可访问服务端 LAN

架构概览

   ┌──────────────────────────┐
   │  浏览器 (Vue 3 SPA)      │
   │  Element Plus + ECharts  │
   └──────────────┬───────────┘
                  │ HTTPS/HTTP (8089)
                  ▼
   ┌──────────────────────────┐         ┌────────────────────────┐
   │  Go HTTP 服务 (Gin)      │ ◄──┐    │ systemd:               │
   │  - JWT 鉴权              │    │    │  openvpn-manager.service│
   │  - REST API              │    │    └────────────────────────┘
   │  - OpenVPN 进程管理       │    │
   │  - OpenSSL 证书签发/吊销   │    │    ┌────────────────────────┐
   └──────────────┬───────────┘    │    │ /usr/sbin/openvpn       │
                  │                ├───►│  --config <conf>       │
                  │                │    │  --cd <dir>            │
                  ▼                │    │  → status.log          │
   ┌──────────────────────────┐    │    └────────────────────────┘
   │ <DataDir>                │    │
   │   pki/                   │    │
   │   instances/<name>/...   │    │
   │   clients/<inst>/<user>/ │    │
   │   backups/               │    │
   │   db.json                │    │
   └──────────────────────────┘    │
                                   │
                  ┌────────────────┘
                  │
                  ▼
        (所有写操作) → audit_logs[]

一、服务端部署

系统要求

  • 操作系统:Ubuntu 20.04+ / Debian 11+ / CentOS Stream 9+ / Rocky 9+
  • CPU:1 核即可
  • 内存:最低 512 MB
  • 磁盘:1 GB(证书 + 用户越多占用越大)
  • 网络:开放 8089 端口(管理界面) + 每个 OpenVPN 实例一个 UDP/TCP 端口(默认 1194)
  • 权限:必须以 root 启动(OpenVPN 需要 TUN/TAP 设备权限)
  • 依赖工具(脚本自动安装):
    • openvpn ≥ 2.5
    • openssl ≥ 1.1
    • nodejs ≥ 18
    • npm
    • golang ≥ 1.21

一键安装

git clone ssh://git@git.cnbugs.com:10022/AI-Agent/openvpn-manager.git
cd openvpn-manager
chmod +x scripts/install.sh
sudo ./scripts/install.sh

脚本会做这些事:

  1. 识别发行版,自动选 apt/dnf/yum
  2. 安装系统依赖
  3. npm install + npm run build 构建前端
  4. go build 编译后端二进制
  5. 拷贝到 /opt/openvpn-manager
  6. 注册 systemd 单元并启动
  7. 健康检查 /api/health
  8. 打印访问地址和默认账号

成功后会看到:

================================================================
 OpenVPN Manager 安装完成
================================================================
  访问地址  : http://<服务器IP>:8089
  用户名    : admin
  密码      : admin123
  安装目录  : /opt/openvpn-manager
  数据目录  : /opt/openvpn-manager/data
  配置单元  : /etc/systemd/system/openvpn-manager.service
================================================================

安装参数

sudo ./scripts/install.sh \
    --port 9090 \
    --user admin \
    --pass 'StrongPass!2026' \
    --dir /opt/openvpn-manager
参数 默认值 说明
--port 8089 Web 管理端口
--user admin 初始管理员用户名
--pass admin123 初始管理员密码,生产环境必须改
--dir /opt/openvpn-manager 安装目录
-u, --uninstall - 卸载(停服务、删除安装目录、清理 systemd)

JWT 签名密钥会在安装时自动生成 64 位随机十六进制串写入 /etc/systemd/system/openvpn-manager.serviceOVPNMGR_JWT_SECRET 环境变量,不要复制生产环境的这个文件

手动安装

适合不想用 systemd 的场景(如 Docker):

# 1. 装依赖
apt-get install -y openvpn openssl nodejs npm golang-go git  # Debian/Ubuntu
# 或
dnf install -y openvpn openssl nodejs npm golang git        # RHEL/Fedora

# 2. 编译
git clone <this-repo> && cd openvpn-manager
cd frontend && npm install --include=dev && npm run build && cd ..
cd backend && go build -o ../bin/openvpn-manager ./cmd/server && cd ..

# 3. 运行
mkdir -p data
OVPNMGR_PORT=8089 \
OVPNMGR_ADMIN_USER=admin \
OVPNMGR_ADMIN_PASS='your-password' \
OVPNMGR_JWT_SECRET=$(openssl rand -hex 32) \
./bin/openvpn-manager --data ./data --dist ./dist

目录结构

/opt/openvpn-manager/
├── bin/openvpn-manager         # Go 编译后的二进制 (~21MB)
├── dist/                       # Vue 构建产物 (index.html + assets/)
├── data/                       # 数据目录,定期备份
│   ├── db.json                 # 元数据(JSON):实例/用户/审计
│   ├── pki/
│   │   ├── ca.crt              # CA 证书(全局共享)
│   │   ├── ca.key              # CA 私钥(权限 0600)
│   │   ├── dh.pem              # DH 参数
│   │   └── ta.key              # TLS-Auth 共享密钥
│   ├── instances/
│   │   └── <instance-name>/
│   │       ├── server.conf     # OpenVPN 服务端配置
│   │       ├── status.log      # OpenVPN 状态文件(10s 周期)
│   │       ├── ipp.txt         # IP 池持久化
│   │       ├── ccd/            # 客户端配置目录(固定 IP)
│   │       ├── logs/openvpn.log
│   │       └── pki/
│   │           ├── issued/<cn>.crt
│   │           └── private/<cn>.key
│   ├── clients/
│   │   └── <instance>/<user>.ovpn
│   └── backups/                # 自动备份目录
└── .env                        # 环境变量(权限 0600)

systemd 管理

systemctl status openvpn-manager      # 状态
systemctl restart openvpn-manager     # 重启
systemctl stop openvpn-manager        # 停止
journalctl -u openvpn-manager -f      # 跟踪日志(ctrl+c 退出)
journalctl -u openvpn-manager -n 200  # 最近 200 行

修改配置后需要:

systemctl edit openvpn-manager        # 改环境变量(创建 override.conf)
systemctl daemon-reload
systemctl restart openvpn-manager

或者直接编辑主单元:

systemctl edit --full openvpn-manager

升级

cd /path/to/openvpn-manager
git pull
sudo ./scripts/install.sh

install.sh 会:

  • 重新编译并覆盖二进制
  • 保留 data/backups/dist/(原压缩产物)
  • 重启服务

重要:升级前请先在 Web 界面"备份与恢复"页面手动做一次备份,以防万一。

卸载

sudo ./scripts/install.sh -u

此命令会:停服务、禁用自启、删除 /etc/systemd/system/openvpn-manager.service、删除 /opt/openvpn-manager

源码目录不会被删除,如需彻底清理请手动 rm -rf


二、首次配置

登录 Web 控制台

浏览器访问 http://<服务器IP>:8089/

默认账号:

  • 用户名:admin
  • 密码:admin123

修改默认密码

⚠️ 生产环境第一步

首次登录强制改密

首次访问用默认账号 admin/admin123 登录后,系统强制弹出"修改初始密码"对话框, 必须改成 ≥6 位的强密码才能继续使用,无法跳过。这一步密码修改后会写入 data/db.json, 之后不再需要重启服务,密码以 bcrypt 哈希持久化。

通过 Web 修改自己的密码

右上角 admin 下拉 → "修改密码" → 填写当前密码 + 新密码 → 保存,立即生效。

创建/管理其他账号

Web → 左侧"管理员"菜单(admin role 才可见):

操作 说明
新建账号 设置用户名、密码、角色(admin / operator)
重置密码 不知道旧密码也能强制改密,新密码立即生效
启用/禁用 禁用后该账号无法登录(已登录的 token 仍可短期使用)
删除 不能删除自己、不能删除最后一个 admin

所有账号操作都进入审计日志。

通过 systemd 环境变量重置(忘记密码时)

如果你把所有 admin 都禁用/删除了,或者忘了所有密码,可以临时用环境变量密码 覆盖(只用于"首次登录然后改密",不持久):

systemctl edit --full openvpn-manager
# 找到 OVPNMGR_ADMIN_PASS 一行,改成你临时用的密码
systemctl daemon-reload
systemctl restart openvpn-manager
# 用新密码登录,系统会触发 SeedDefaultAdminIfEmpty
# 注:此路径只在 db.json 中无任何 admin 时生效;否则会校验 db.json 中的账号

更可靠的做法:直接编辑 data/db.json,把 password_hash 替换为 bcrypt 哈希 (用 htpasswd -bnBC 10 "" yourpass | tr -d ':\n' 生成)后重启服务。

环境变量里的 OVPNMGR_ADMIN_USER / OVPNMGR_ADMIN_PASS 仅在数据库为空时作为初始 seed 使用。

创建第一个实例

Web → "实例管理" → "新建实例":

字段 推荐值 说明
名称 prod 字母数字下划线,作为目录名,不可重复
端口 1194 OpenVPN 监听端口,不能与已有服务冲突
协议 udp udp 性能好,tcp 穿透性强
设备 tun tun 路由模式(常用),tap 桥接模式
子网 10.8.0.0/24 给客户端分配的 VPN 内网网段
加密 AES-256-GCM AES-128-GCM 更快,CHACHA20-POLY1305 适合 ARM
摘要 SHA256
推送 DNS dhcp-option DNS 1.1.1.1
dhcp-option DNS 8.8.8.8
一行一条
推送路由 192.168.1.0 255.255.255.0 让客户端能访问内网,每行一条 CIDR

点"保存"会自动:

  • 创建实例目录
  • 用全局 CA 签发服务端证书
  • 生成 server.conf

然后点列表里的"启动"按钮即可。如果失败,看 journalctl -u openvpn-manager -n 50

创建客户端用户并下载配置

Web → "用户管理":

  1. 顶部下拉框选择实例
  2. "新建用户":
    • 用户名(CN):alice(字母数字下划线,作为证书 CN)
    • 备注:Alice 张三
    • 邮箱:alice@example.com
    • 固定 IP:留空为动态;若填 10.8.0.10,Alice 每次连上都是这个 VPN IP
  3. 保存后表格出现 alice,点"下载 .ovpn"

下载的文件约 4-5 KB,里面已经内嵌了:

  • CA 证书(<ca>...</ca>)
  • 客户端证书(<cert>...</cert>)
  • 客户端私钥(<key>...</key>)
  • TLS-Auth 密钥(<tls-auth>...</tls-auth>,key-direction 1)

直接发给用户即可,无需额外的 ca.crt 等文件。

IP 转发与内网访问(客户端访问服务端 LAN)

场景: 客户端连上 VPN 后,想访问服务端所在的局域网设备(如 172.16.2.0/24 网段的服务器、打印机、NAS 等)。

前提: 客户端能 ping 通 VPN 网关(10.8.0.1),但访问不了 LAN(如 ping 172.16.2.30 失败)。

OpenVPN 默认只让客户端访问 VPN 子网本身,不能直接访问服务端 LAN。要打通,服务端需要做两件事:

  1. 开启 IP 转发(net.ipv4.ip_forward=1),让内核把 VPN 客户端的包从 tun 设备转到 LAN 网卡。
  2. 配置 NAT(MASQUERADE),让 VPN 客户端的源 IP 在离开服务端时被替换成 LAN 网卡 IP,这样 LAN 设备知道怎么回包。

一键启用(推荐)

如果服务端只有一个网卡接 LAN(典型场景),执行:

# 1. 立即启用 IP 转发
sudo sysctl -w net.ipv4.ip_forward=1

# 2. 持久化(重启不丢)
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-openvpn-manager.conf

# 3. 添加 NAT 规则
#    eth0 是服务端接 LAN 的网卡名(可能是 ens192/eno1/enp0s3 等,用 ip a 查看)
LAN_IF="eth0"   # ← 改成实际网卡名
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o "$LAN_IF" -j MASQUERADE
sudo iptables -A FORWARD -i "$LAN_IF" -o tun+ -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i tun+ -o "$LAN_IF" -j ACCEPT

# 4. 持久化 iptables(重启不丢)
sudo apt-get install -y iptables-persistent   # Debian/Ubuntu
sudo netfilter-persistent save                # 保存当前规则
# 或: sudo service iptables save             # CentOS/RHEL

为什么是 tun+?: OpenVPN 用 tun0/tun1... 设备,+ 通配匹配所有 tun 设备。多个实例并存也能用。

验证

# 服务端视角: 应该看到一条 MASQUERADE 规则
sudo iptables -t nat -L POSTROUTING -n -v | grep MASQUERADE

# 客户端视角: VPN 连接后
ping 10.8.0.1                   # 必通(网关)
ping <服务端 LAN 上的某个 IP>   # 通了说明成功
curl ifconfig.me                # 显示服务端公网 IP,说明 NAT 生效

找对 LAN 网卡

ip -br a                       # 简略列出所有网卡和 IP
ip route | grep default        # 看默认路由走哪块网卡 — 通常就是 LAN 网卡

例:

default via 172.16.2.254 dev ens192   →  LAN 网卡是 ens192

push 路由(让客户端知道去 LAN 怎么走)

光在服务端做 NAT 不够,还得让客户端知道 "要去 172.16.2.0/24 就走 VPN"。两种做法:

方法 A:在实例里 push 路由(推荐)

编辑实例的 server.conf,添加:

push "route 172.16.2.0 255.255.255.0"

然后在 Web 面板重启实例。

方法 B:在 Web 面板"实例管理"里配置

把 LAN 网段加到实例的 AccessMode 白名单或 push 路由列表。白名单模式下 allow_networks 会自动生成对应的 push 路由。

多网卡/复杂路由场景

如果服务端有多块 LAN 网卡(如同时接公司网和家庭网),MASQUERADE 规则要分别添加:

for IF in ens192 ens224; do
  sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o "$IF" -j MASQUERADE
done

或者直接用 -o eth+ 一次性匹配所有以太网卡(不太安全,慎用):

sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -j MASQUERADE

排障

症状 排查
客户端能 ping 通 10.8.0.1,ping 不通 LAN 没开 IP 转发,或没加 MASQUERADE 规则
能 ping 通 LAN IP,但 SSH/服务不通 LAN 设备防火墙拒绝 VPN 子网;在 LAN 设备上 iptables -I INPUT -s 10.8.0.0/24 -j ACCEPT
重启后规则丢失 没装 iptables-persistent 或没保存;参考上文持久化步骤
ping 得通但访问慢/丢包 MTU 问题;在 server.conf 加 tun-mtu 1400 mssfix 1360
ip_forward 已开但客户端仍上不了网 push 路由缺失;客户端路由表里没有目标 LAN 网段

检查清单

# 1. 转发开关
cat /proc/sys/net/ipv4/ip_forward         # 必须输出 1

# 2. NAT 规则
sudo iptables -t nat -L POSTROUTING -n    # 应该有 10.8.0.0/24 的 MASQUERADE

# 3. FORWARD 策略(默认 ACCEPT 还是 DROP?)
sudo iptables -L FORWARD                  # 看到 policy DROP 就需要显式 ACCEPT tun+ ↔ LAN 的包

# 4. 客户端路由
#    VPN 连接后,在客户端执行:
ip route                                  # 应该看到 "10.8.0.0/24 dev tun0" 和 "172.16.2.0/24 via 10.8.0.1 dev tun0"

防火墙与公网暴露

  1. Web 管理端口(默认 8089)不建议直接暴露公网,建议:

    • 用防火墙只允许公司/家庭 IP 访问
    • 或反代 + HTTPS + Basic Auth
    • 或 SSH 端口转发 ssh -L 8089:127.0.0.1:8089 user@server
  2. OpenVPN 实例端口必须开放给需要连入的客户端。UDP 优先(性能好):

# ufw
ufw allow 1194/udp
ufw allow 1194/tcp   # 如果实例用 TCP

# firewalld
firewall-cmd --permanent --add-port=1194/udp
firewall-cmd --reload
  1. 若服务器在 NAT 后(如家用宽带),需要在路由器做端口映射 UDP 1194 → 服务器内网 IP。

三、访问控制(白名单模式)

核心需求:VPN 用户登录后,只能访问你明确允许的内网网段,默认与所有内网隔离。

工作原理

每个实例有一个 access_mode 字段:

  • open(默认):不限制,客户端可访问所有可达网段
  • whitelist:仅允许访问白名单中列出的内网网段

access_mode=whitelist 时:

  1. 实例配置 allow_networks:该实例下所有用户共享的允许网段
  2. 用户配置 allow_networks:单个用户的额外允许网段(在实例基础上叠加)
  3. 实际生效 = 实例白名单 ∪ 用户白名单(去重)
  4. 服务端强制:
    • OpenVPN 自动生成 client-connect.sh / client-disconnect.sh 脚本
    • 每个用户连接时,服务端的 FORWARD 链插入 iptables ACCEPT 规则,只放行到白名单网段的流量
    • 其他内网流量在服务端被 REJECT(客户端看到的是"无法连接",而非"超时")
  5. 客户端推送:
    • 自动 push "redirect-gateway def1 bypass-dhcp" 让客户端把所有流量都走 VPN(否则白名单没意义)
    • 自动按白名单 push route ... 让客户端知道这些网段在 VPN 后

双重防护:即使客户端操作系统被绕过,iptables 仍会拒绝非法流量。

使用方法

Web 端

  1. 实例管理 → 新建/编辑实例

    • "访问控制"下拉选 "白名单(whitelist)"
    • "允许网段"标签输入:每行一个 CIDR,如 192.168.1.0/2410.0.0.0/8
    • 保存
  2. 用户管理 → 编辑用户(可选)

    • "额外允许网段":这个用户特有的、实例未列出的网段
    • 保存
  3. 启动实例 —— 实例启动时,会执行一次 iptables -A FORWARD -i tunX -j REJECT(仅白名单模式)作为兜底

命令行(API)

# 创建白名单实例
curl -X POST http://localhost:8089/api/instances \
  -H "Authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "name": "internal",
    "port": 1194,
    "subnet": "10.8.0.0/24",
    "access_mode": "whitelist",
    "allow_networks": ["192.168.1.0/24", "10.0.0.0/8"]
  }'

# 创建用户,额外允许一个网段
curl -X POST http://localhost:8089/api/instances/$IID/users \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"username":"alice","allow_networks":["172.16.0.0/16"]}'

验证白名单生效

实例启动后,登录客户端:

# 1. 客户端连上 VPN,确认 VPN IP 拿到
ip addr show | grep 10.8.0

# 2. 测允许的网段 - 应通
ping 192.168.1.1

# 3. 测未允许的网段 - 应 REJECT(ICMP net unreachable)
ping 192.168.50.1   # 不在白名单,应该不通

服务端验证 iptables 规则:

iptables -L FORWARD -n --line-numbers
# 应看到 -i tun0 -j REJECT 在底部,前面若干 -s <vpn-ip> -d <allowed-net> -j ACCEPT

限制与注意

说明
仅 IPv4 IPv6 白名单需要扩展 client-connect.sh 使用 route-ipv6,目前未实现
需要 root + iptables 若运行在容器内/无 root,白名单模式会以"open"模式退化运行
端口转发必须由客户端发起 客户端连入后,服务端只允许它主动访问白名单中的目标;不能用 VPN 当跳板从外部进入内网
修改 allow_networks 后 已有用户需重新连接一次才能拿到新的 push route;新用户即时生效
删除/吊销用户 会清掉 iptables 中对应的 ACCEPT 规则,不影响其他用户

常见误用

错误 后果
客户端关掉 VPN 网关 服务端 iptables 仍会拒绝非白名单流量,客户端访问不到
客户端把 allowed 网段路由改成另一条 服务端 iptables 在 FORWARD 链过滤,客户端改路由无效
忘记添加 DNS 服务器 客户端没法解析域名 —— 在"允许网段"加上 DNS 服务器的 IP(如 8.8.8.8/32)

三、客户端使用

配置文件说明

下载的 alice.ovpn 是单一文件,内容大致为:

client
dev tun
proto udp
remote vpn.example.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
cipher AES-256-GCM
auth SHA256
remote-cert-tls server
verb 3

<ca>
-----BEGIN CERTIFICATE-----
... CA 证书内容 ...
-----END CERTIFICATE-----
</ca>

<cert>
-----BEGIN CERTIFICATE-----
... 客户端证书 ...
-----END CERTIFICATE-----
</cert>

<key>
-----BEGIN PRIVATE KEY-----
... 客户端私钥(请勿泄露)...
-----END PRIVATE KEY-----
</key>

<tls-auth>
-----BEGIN OpenVPN Static key V1-----
... TLS-Auth 共享密钥 ...
-----END OpenVPN Static key V1-----
</tls-auth>
key-direction 1

注意 remote 行是客户端实际连接的服务器地址,默认是 vpn.example.com,需要改成你自己的服务器公网域名/IP

修改方法:

  • 在 Web 界面"用户管理"页面,顶部"客户端连接的远端域名/IP"输入框填入你的服务器地址(如 vpn.your-domain.com1.2.3.4),再点"下载 .ovpn"
  • 或下载后用文本编辑器手动改 remote

Windows

推荐:OpenVPN 官方 GUI 客户端

  1. 下载:https://openvpn.net/community-downloads/ → 选择 "Windows 64-bit MSI installer"
  2. 安装(一路下一步,会安装一个虚拟网卡驱动,需要管理员权限)
  3. alice.ovpn 放到 C:\Users\<你>\OpenVPN\config\
  4. 启动 "OpenVPN GUI"(开始菜单里),右下角会出现托盘图标
  5. 右键托盘图标 → Connect
  6. 第一次会弹窗请求管理员权限(用于配置路由)
  7. 连接成功后托盘变绿,会分配一个 10.8.0.x 的 VPN IP

验证:

ipconfig /all
# 看到 "10.8.0.x" 的 Tap adapter IPv4 地址即成功

ping 10.8.0.1
# 应该通(10.8.0.1 是 OpenVPN 服务端在子网里的网关)

macOS

选项 1:Tunnelblick(免费开源,推荐)

  1. 下载:https://tunnelblick.net/
  2. 安装,会自动安装 tun 驱动
  3. 双击 alice.ovpn,Tunnelblick 会问你"是否为所有用户安装",选"仅我"即可
  4. 菜单栏点 Tunnelblick 图标 → Connect alice
  5. 状态变绿即成功

选项 2:OpenVPN Connect(官方)

从 Mac App Store 搜索 "OpenVPN" 安装。

Linux

命令行 (systemd 服务)

# Debian/Ubuntu
sudo apt-get install -y openvpn

# RHEL/Fedora
sudo dnf install -y openvpn

# 连接
sudo openvpn --config alice.ovpn --daemon
# 或前台运行(能看到日志)
sudo openvpn --config alice.ovpn

NetworkManager 图形客户端

sudo apt-get install -y network-manager-openvpn network-manager-openvpn-gnome  # Debian/Ubuntu
sudo dnf install -y NetworkManager-openvpn NetworkManager-openvpn-gnome      # Fedora

设置 → 网络 → + VPN → "从文件导入 VPN" → 选 alice.ovpn → 保存 → 连接。

Android

  1. 安装"OpenVPN Connect"(Google Play / F-Droid 都有)
  2. alice.ovpn 通过 USB / 邮件 / 网盘传到手机
  3. 用文件管理器打开 .ovpn,系统会询问"用 OpenVPN 打开"
  4. 点右上角"连接"
  5. 首次会提示接受 VPN 配置,点确定
  6. 通知栏出现钥匙图标即连接成功

iOS

  1. App Store 搜索"OpenVPN"安装
  2. 用"文件"App 把 alice.ovpn 传到手机(隔空投送也行)
  3. 在"文件"App 里点击 .ovpn,选择"用 OpenVPN 打开"
  4. 点"ADD"导入 → 点右上角开关连接
  5. 系统会弹窗请求添加 VPN 配置,允许
  6. 设置 → 通用 → VPN 里可以看到状态

验证连接

无论哪个平台,连接成功后都可以这样验证:

  1. VPN IP:看分配的 IP 是否在配置的子网里(如 10.8.0.x)
  2. Ping 服务端:从客户端 ping 10.8.0.1 应该通
  3. 公网出口:从客户端 curl ifconfig.me 应显示服务器公网 IP
  4. DNS 解析:如果推送了 DNS,客户端 /etc/resolv.conf(Linux)应看到推送的 DNS

在 Web 控制台:

  • "仪表盘"会实时显示当前在线客户端数(10 秒刷新)
  • "实例管理" → 点实例 → 不会直接显示在线,但可以看 status.log
  • "连接日志"显示历史连接记录

排障

症状 可能原因
连接后立刻断开 客户端证书与 CA 不匹配;服务端证书过期
拿到 IP 但 ping 不通服务端 防火墙没允许 UDP 1194;服务端没启用 IP 转发(见 IP 转发与内网访问)
拿到 IP 但访问不了互联网 没推送 DNS,或客户端没把 VPN 设为默认网关
拿到 IP 能 ping 通网关,访问不了 LAN 没做服务端 NAT — 见 IP 转发与内网访问
Android 连不上 服务器在 NAT 后,检查运营商是否屏蔽 UDP
iOS 连不上 看 OpenVPN 日志(应用内 OpenVPN → Settings → Log)

五、API 参考

所有 /api 路径(除 /login/health)都需要 Authorization: Bearer <token>

完整列表见 docs/API.md 或启动后查看 internal/api/router.go

简单列几个常用的:

方法 路径 用途
POST /api/login 登录,返回 {token, username}
GET /api/dashboard 仪表盘汇总
GET/POST /api/instances 列出/创建实例
GET/PUT/DELETE /api/instances/:id 单实例 CRUD
POST /api/instances/:id/start / /stop 启停
GET/POST /api/instances/:id/users 用户列表/新建
POST /api/instances/:id/users/:uid/revoke 吊销
DELETE /api/instances/:id/users/:uid 删除
GET /api/instances/:id/users/:uid/ovpn?host=X 下载 .ovpn
GET /api/certs 证书到期清单
GET /api/connlogs?instance=X 连接日志
GET/POST/DELETE /api/backups / /api/backups/:id 备份管理
POST /api/backups/:id/restore 恢复
GET /api/audits 审计日志

示例:

# 登录
TOKEN=$(curl -s -X POST http://localhost:8089/api/login \
    -H 'content-type: application/json' \
    -d '{"username":"admin","password":"admin123"}' | jq -r .token)

# 创建实例
curl -X POST http://localhost:8089/api/instances \
    -H "Authorization: Bearer $TOKEN" \
    -H 'content-type: application/json' \
    -d '{"name":"prod","port":1194,"subnet":"10.8.0.0/24"}'

六、常见问题

Q: 服务启动后访问 8089 提示"无法连接"?

排查顺序:

systemctl status openvpn-manager    # 进程在不在?
journalctl -u openvpn-manager -n 50 # 启动报错?
ss -tlnp | grep 8089                # 端口在听吗?
curl http://127.0.0.1:8089/api/health # 本机能访问吗?

常见原因:go/nodejs/openvpn 没装或版本太低。

Q: 在实例列表点"启动"提示成功,但状态一直 stopped?

OpenVPN 需要 root 启动。如果你的 systemd 单元不是 root 运行,启动会失败。检查:

ps aux | grep openvpn-manager  # 确认主进程是 root
cat /etc/systemd/system/openvpn-manager.service | grep User
# 应该 User=root

Q: 下载的 .ovpn 客户端连不上?

  1. 确认服务器防火墙/路由器开放了对应端口(UDP 1194 等)
  2. 客户端能 ping 通服务器公网 IP 吗?
  3. 服务端 status.log 有没有客户端连接尝试?

Q: 怎么从备份恢复?

Web → "备份与恢复" → 选中备份 → "恢复"。会覆盖现有 pki/instances/clients/,操作前先做一次新的备份以防万一。

Q: 能用 Nginx 反代 + HTTPS 吗?

可以,示例 Nginx 配置:

server {
    listen 443 ssl http2;
    server_name vpn-admin.example.com;

    ssl_certificate     /etc/letsencrypt/live/vpn-admin.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vpn-admin.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8089;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

注意 OpenVPN 自身的 UDP/TCP 端口(1194 等)不能走 Nginx,要单独放行。

Q: 支持 IPv6 吗?

服务端支持(把 proto 改成 udp6tcp6,子网用 IPv6 CIDR),但本项目目前 UI 主要按 IPv4 写,IPv6 子网需要手动编辑 server.conf(用"自定义"字段)。


七、开发

# 后端
cd backend
go run ./cmd/server --data ../data --dist ../dist

# 前端(开发热重载,会自动代理 /api 到 :8089)
cd frontend
npm install
npm run dev
# 浏览器打开 http://localhost:3000

调试模式日志:

OVPNMGR_LOG_LEVEL=debug ./bin/openvpn-manager --data ./data --dist ./dist

跑测试:

cd backend && go test ./...
cd frontend && npm run build   # 顺便当 type/lint 校验

构建发布版:

# 前端
cd frontend && npm run build && cd ..

# 后端(静态链接、剥离调试符号)
cd backend
CGO_ENABLED=0 go build -ldflags "-s -w" -trimpath -o ../bin/openvpn-manager ./cmd/server

许可证

MIT


致谢

本项目使用了以下开源软件:

S
Description
No description provided
Readme MIT 386 KiB
2026-08-10 00:37:04 +08:00
Languages
Go 61.9%
Vue 27.3%
Shell 6.1%
JavaScript 3.9%
CSS 0.6%
Other 0.2%