Skip to main content

ipgate — 基于 IP 证书的 HTTPS 端口映射网关

把本机任意 HTTP 服务(127.0.0.1:80800.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 上点完:

  1. 跑一次 ipgate-server,打开面板。「日志」页如果显示 Caddy 未安装,会出现一张 "安装 Caddy"卡片——点一下会下载对应架构的二进制、注册 caddy.service 并启动, 跟 install.sh 那段是同一份逻辑,只是触发方式从命令行改成了网页; 命令行等价操作是 ipgate caddy install
  2. 「证书」页如果没有可用证书,会出现"初始化证书"卡片:先探测同机是否有 pyproxy 可复用,探测不到就下载 acme.sh 并签发第一张证书(正式/staging 二选一); 命令行等价操作是 ipgate cert init--dry-run 先看会做什么,--staging 先在测试环境跑通流程,--acme-sh <路径> 复用已有的 acme.sh)
  3. 想要开机自启 / 崩溃自动重启 ipgate-server 本身(Caddy 那份已经由上面第 1 步 注册好了),照抄 install.shipgate.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

pyipgate-1.0.0.tar.gz (66.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyipgate-1.0.0-py3-none-any.whl (68.6 kB view details)

Uploaded Python 3

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

Hashes for pyipgate-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e5fa9121cb8890f44bc3b9857d069815c162c49dec59d7f9212b9c005770fffd
MD5 02cfe45ed19586d4cc076573e46ad505
BLAKE2b-256 11114977f1b5eb2ebe3a696932cd72bdec92ef2e1cae15d2f2e545432423c5eb

See more details on using hashes here.

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

Hashes for pyipgate-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0216da20aa0931426780a926394934bb83beddc9dc50f2284b2dbf1280b3a318
MD5 92ce11aa4b517b0e3ccd88ea3f257d3b
BLAKE2b-256 7424d2f0793127342a7a6722bca3effdd50c2cefa89e690e00ff013bd11e8411

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page