ipgate — 基于 IP 证书的 HTTPS 端口映射网关
把本机任意 HTTP 服务(127.0.0.1:8080、0.0.0.0:3000 …)一键发布成公网 HTTPS
端口,证书是 Let's Encrypt 直接签给 IP 地址本身的短期证书,不需要域名。
https://115.190.165.92:8443 -> 127.0.0.1:8080
https://115.190.165.92:9444 -> 0.0.0.0:3000
https://115.190.165.92:10443 -> /var/www/docs (静态目录)
Caddy 负责 TLS 终止与反向代理,Python 负责证书生命周期、路由管理、Web 面板和 CLI。 纯 stdlib,无 pip 依赖。
架构
公网
│ https://IP:8443, :9444, :443 …
▼
┌──────────────┐ admin API (127.0.0.1:2019) ┌────────────────┐
│ Caddy │ ◀───────────────────────────── │ ipgate (py) │
│ TLS + 反代 │ │ 面板 + 续期 │
└──────┬───────┘ └───────┬────────┘
│ 读证书文件 │ 调用
▼ ▼
<pyproxy>/tls/{fullchain,key}.pem ◀── acme.sh (tls-alpn-01)
▲
└── 同一份证书,pyproxy 也在用
为什么不让 Caddy 自己签证书
Let's Encrypt 的 IP 证书只在 shortlived profile(约 6.5 天)下签发,
Caddy 至今没有实现这条路径(caddyserver/caddy#7399),会直接报
subject '<ip>' cannot have public IP certificate。
所以签发继续交给 acme.sh,Caddy 只负责加载证书文件。配置里全程
automatic_https.disable = true,避免它徒劳地尝试再报错。
和 pyproxy 的共存:443 端口之争
同机的 pyproxy 用 --alpn(tls-alpn-01)续期,需要独占 443。
Caddy 常驻 443 会让它签不下来,而证书 6 天就过期 —— 直接把 VPN 的 HTTPS 弄挂。
ipgate 的做法是成为唯一真正动手续期的一方,且不改 pyproxy 一行代码:
抢在 acme.sh 自己计划的时刻之前把证书换掉,它跑到时就只会打印
Skipping. Next renewal time is ... 然后退出,全程不碰 443。
关键是怎么知道它计划什么时候动手。不能靠「剩余天数」猜 —— acme.sh 走
RFC 9773 ARI,续期时刻是在 Let's Encrypt 下发的 suggestedWindow 里
随机取点,命令行上的 --days 会被整个覆盖:
# acme.sh 内部
_ari_offset=$(_math "$(_time)" % "$_ari_window")
Le_NextRenewTime=$(_math "$_ari_start_t_new" + "$_ari_offset")
实测这台机器上 Le_NextRenewTime = notAfter − 3.35 天,而不是 --days 3
应该给出的 notAfter − 3 天。
所以 ipgate 直接读它写在共享账本
(<acme_home>/<IP>_ecc/<IP>.conf 里的 Le_NextRenewTime)中的计划时刻,
永远比它早 6 小时动手(renew_lead 可调)。ARI 随机到哪都成立,
LE 以后改窗口形状也不用跟着调参数。读不到账本时才退回「剩余不足 4 天」的兜底判断。
即便万一让 pyproxy 抢先跑了一次,后果也只是它 bind 443 失败、在自己的
status.json 里记一条错误 —— 证书是共用的,ipgate 这边照常续上,不会真的断服务。
ipgate 调 acme.sh 时带 --force(按 acme.sh 自己的账本还没到期),这绕过了它
内建的节流,因此另有一道 min_issue_interval(默认 12 小时)兜底,防止逻辑
出错时反复冲击 Let's Encrypt 的配额。
续期发生时,如果确实有映射占着 443,ipgate 会通过 admin API 临时把这一个 监听器摘掉,签完立刻挂回来 —— 其余端口全程不受影响,443 的中断窗口约 10 秒, 约每 2.5 天一次。
安装
./install.sh # 幂等,可反复执行
ipgate totp # 强烈建议:绑定两步验证
不用 install.sh:pip 安装
pip install pyipgate
pip install 只装 Python 代码,不像 install.sh 一条命令包办一切,但装完之后
剩下的都能在网页 http://<IP>:9500 上点完:
- 跑一次
ipgate-server,打开面板。「日志」页如果显示 Caddy 未安装,会出现一张 "安装 Caddy"卡片——点一下会下载对应架构的二进制、注册caddy.service并启动, 跟install.sh那段是同一份逻辑,只是触发方式从命令行改成了网页; 命令行等价操作是ipgate caddy install - 「证书」页如果没有可用证书,会出现"初始化证书"卡片:先探测同机是否有 pyproxy
可复用,探测不到就下载 acme.sh 并签发第一张证书(正式/staging 二选一);
命令行等价操作是
ipgate cert init(--dry-run先看会做什么,--staging先在测试环境跑通流程,--acme-sh <路径>复用已有的 acme.sh) - 想要开机自启 / 崩溃自动重启
ipgate-server本身(Caddy 那份已经由上面第 1 步 注册好了),照抄install.sh里ipgate.service那段 systemd 单元自己写一份—— 这是目前唯一还没搬上网页的部分,因为 ipgate 没法从自己进程里注册"重启自己"的 服务
状态目录(config.json / routes.json / caddy.json / cert-status.json /
acme.sh 本体 / 签发出的证书)默认在 /etc/ipgate/(root 权限),可用
IPGATE_HOME 环境变量改到别处。
简单说:install.sh 是「自动挡」,一条命令连 Caddy 和 systemd 一起装好;
pip install pyipgate 是「半自动挡」,Caddy 和证书这两步网页/CLI 都能触发,
只有 ipgate-server 自身的开机自启还需要手写一份 systemd 单元。
首次设置密码
服务启动时若还没有密码,会开一个首次设置窗口(setup_window,默认 12 小时),
直接打开面板就能在浏览器里设定,不必 SSH。窗口只存在内存里:
- 设定完成即关闭;
- 超时未设定则锁死,
systemctl restart ipgate可重新开窗; - 已经设过密码的实例重启不会再开窗。
不想用窗口的话,ipgate passwd 随时可以在命令行设定/修改。
⚠️ 窗口期内面板对全网是真的开放的:任何人扫到
:9500都能抢先设定密码、 接管这台网关。窗口越长风险越大 —— 12 小时意味着装完就该尽快去把密码设掉, 别装完就撂着过夜。改短:config.json里的setup_window(秒)。
安装脚本会下载 Caddy、部署代码(路径由 IPGATE_DIR 决定)、自动探测同机 pyproxy
的证书目录、注册两个 systemd 单元(caddy.service / ipgate.service),
并把 CLI 链接为 /usr/local/bin/ipgate。
pyproxy 的安装路径各机器不一:已见过
/root/vpn/pyproxy和/root/main/vpn/pyproxy。探测按「证书文件最新」挑选,多个备份目录也不会认错。 探测不到时在config.json里手工填cert_dir/acme_home/acme_sh即可。
CLI
ipgate status # 总览:IP / 证书 / Caddy / 所有映射
ipgate ls
ipgate add web 8443 127.0.0.1:8080 # 新增映射
ipgate add api 9444 0.0.0.0:3000 --note "内部 API"
ipgate add sec 10443 127.0.0.1:9443 --tls # 后端本身是 HTTPS(默认跳过证书校验)
ipgate add-static docs 11443 /var/www/docs # 直接发布一个目录
ipgate edit web --upstream 127.0.0.1:8081
ipgate disable web / enable web / rm web
ipgate test web # 探测后端连通性
ipgate reload # 重新生成配置并热加载
ipgate cert # 证书详情(JSON)
ipgate cert renew # 按需续期
ipgate cert renew --force # 强制重签(注意每周配额)
ipgate cert init # 首次初始化:探测同机 pyproxy,探测不到就下载 acme.sh 并签发
ipgate cert init --dry-run # 只打印会做什么,不实际下载/签发/写配置
ipgate cert init --staging # 先在 LE 测试环境跑通流程,不消耗正式配额
ipgate cert init --acme-sh /path/to/acme.sh # 已有 acme.sh 就不用重新下载
ipgate passwd | ipgate totp | ipgate token
ipgate caddy start|stop|restart|status|config
ipgate caddy install # 下载二进制、注册 systemd 单元、启动(幂等)
ipgate caddy install --force # 已装过也强制重新下载覆盖
ipgate bundle # 打包一份可拿去别处部署的 zip
ipgate bundle --with-caddy # 连 caddy 二进制一起打(约 17 MB)
部署到别的机器
管理界面「设置」页有下载按钮,命令行用 ipgate bundle。包里是纯代码:
管理密码、API token、两步验证密钥、端口映射都不在里面(用白名单挑文件,
不是排除法 —— 漏掉排除项就是泄密)。
目标机器上解压后 bash install.sh 即可,脚本会自动探测那台机器上 pyproxy 的
证书目录。
--with-caddy版把 caddy 二进制一起打进去,install.sh 会直接用、不联网。 国内机器直连 GitHub 拉 caddy 常常只有几十 KB/s,这一步能省十几分钟。
所有写操作都会立刻通过 Caddy admin API 热加载,不断开已有连接。 Caddy 若拒绝新配置,改动会自动回滚。
管理面板
http://<IP>:9500 —— 端口映射的增删改查、证书状态与手动续期、acme.sh
实时输出、两步验证绑定(扫码)、systemd 日志查看。
登录需要密码 且(启用后)动态验证码,登录失败 5 分钟内 8 次即锁定。
面板按需求直接监听公网 HTTP 端口,密码在链路上是明文的。 想让面板自己也走 HTTPS,加一条指向它的映射即可:
ipgate add panel 443 127.0.0.1:9500,之后用https://<IP>/访问。
文件
| 路径 | 说明 |
|---|---|
config.json |
面板端口、证书路径、续期阈值、首次设置窗口、密码散列、API token(0600) |
routes.json |
端口映射定义 |
caddy.json |
生成的 Caddy 配置,供 systemd 冷启动使用 |
cert-status.json |
最近一次续期的状态与 acme.sh 输出 |
totp.json |
两步验证密钥(0600) |
注意
- 端口一对一:一个对外端口只属于一条映射,新增时会先试探 bind,端口被别的 进程占用会当场拒绝,而不是等 Caddy 起不来。
0.0.0.0:port会自动改写成127.0.0.1:port—— 从ss -tlnp抄来的地址 可以直接粘。- WebSocket 无需额外配置,Caddy 的
reverse_proxy原生支持协议升级。 - 不要用别的工具再给这个 IP 签证书:同一 SAN 的重复证书 Let's Encrypt 每周只给 5 张,两个客户端各自续期很容易撞上限。
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyipgate-1.0.0.tar.gz.
File metadata
- Download URL: pyipgate-1.0.0.tar.gz
- Upload date:
- Size: 66.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e5fa9121cb8890f44bc3b9857d069815c162c49dec59d7f9212b9c005770fffd
|
|
| MD5 |
02cfe45ed19586d4cc076573e46ad505
|
|
| BLAKE2b-256 |
11114977f1b5eb2ebe3a696932cd72bdec92ef2e1cae15d2f2e545432423c5eb
|
File details
Details for the file pyipgate-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pyipgate-1.0.0-py3-none-any.whl
- Upload date:
- Size: 68.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0216da20aa0931426780a926394934bb83beddc9dc50f2284b2dbf1280b3a318
|
|
| MD5 |
92ce11aa4b517b0e3ccd88ea3f257d3b
|
|
| BLAKE2b-256 |
7424d2f0793127342a7a6722bca3effdd50c2cefa89e690e00ff013bd11e8411
|