468 lines
23 KiB
Markdown
468 lines
23 KiB
Markdown
# FluxIP
|
||
|
||
FluxIP 是一个使用 Python、FastAPI 和 SQLite 开发的 AWS Lightsail Static IP
|
||
轮换控制器。它可以管理多台 Lightsail 实例,把每台实例与一个 Cloudflare A 记录
|
||
组成一个托管目标,再通过实例组统一设置轮换周期。A 记录默认使用 DNS only,也可按
|
||
实例选择橙云模式。
|
||
|
||
轮换过程中不会停止或重启 Lightsail。FluxIP 会分配新的 Static IP,解绑旧地址,
|
||
把新地址附加到仍在运行的实例,验证 SOCKS5 或 HTTP 代理与 Cloudflare DNS,等待释放宽限期,
|
||
最后从 AWS 账号释放旧 Static IP。
|
||
|
||
配置、账号资料、任务状态和历史记录保存在 SQLite。可以分别维护多套 AWS 与
|
||
Cloudflare 账号,每个托管实例独立选择要使用的两套账号。AWS 密钥与 Cloudflare
|
||
API Token 以及代理用户名、密码使用本机主密钥加密后存储,API 和 WebUI 不会返回密钥明文。
|
||
|
||
## 重要边界
|
||
|
||
- 每台 Lightsail 实例只能附加一个 Static IP。地址切换不是双 IP 并行切流。
|
||
- 解绑旧地址和附加新地址之间会有短暂切换窗口。实例进程不会退出,但已有
|
||
SOCKS5 TCP 连接会断开,需要客户端重连。
|
||
- DNS only 模式下 Cloudflare 只负责 DNS 解析。DNS 缓存仍可能在 TTL 内返回旧地址,
|
||
FluxIP 不能提供零中断或连接迁移。
|
||
- 普通 Cloudflare 橙云不能转发 SOCKS5、通用 HTTP CONNECT 或 10808 等任意 TCP
|
||
端口。只有已经单独配置 Cloudflare Spectrum 等 TCP 产品时才应开启橙云;FluxIP
|
||
只修改 DNS 记录的 `proxied` 状态,不会创建 Spectrum 应用。
|
||
- 橙云记录的 API 回读只能证明配置已经写入,不能证明所有 Cloudflare 边缘节点已经
|
||
切换源站;默认释放宽限期也不是 Spectrum 端到端可用性的保证。
|
||
- 旧 Static IP 一旦执行 `ReleaseStaticIp` 就不能找回。FluxIP 只会在新地址、DNS 和
|
||
可选代理认证与 CONNECT 检查通过后释放它。
|
||
- AWS 默认每个区域最多 5 个 Static IP,配额可以申请提高。安全轮换采用
|
||
“先分配新地址、后释放旧地址”,因此同一区域需要至少一个空余配额。
|
||
- 调度器运行在 Web 进程内,只支持一个 Uvicorn worker 和一个应用副本。
|
||
- 生产环境建议把控制器部署到独立的长期在线主机。虽然 Static IP 切换不会关闭
|
||
目标实例,但同机部署会使 WebUI 管理连接也受地址切换影响。
|
||
|
||
## 资源模型
|
||
|
||
一个托管实例包含:
|
||
|
||
- 显示名称;
|
||
- AWS 账号、从内置中文名称与图标目录选择的 Lightsail 区域,以及 Lightsail 实例名称;
|
||
- Cloudflare 账号、Zone ID、唯一的完整 A 记录名和橙云开关;
|
||
- SOCKS5/HTTP 代理链接、端口、健康检查开关和超时;
|
||
- 旧地址释放宽限期;
|
||
- 是否允许参与手动或分组轮换。
|
||
|
||
一个实例组包含若干托管实例、启用状态和轮换间隔。到期时,组内已启用实例按照
|
||
成员顺序逐台轮换。串行执行只额外占用一个 Static IP 配额,也避免同时切断多台
|
||
代理。下次执行时间从整个批次结束后重新计算,两个批次之间至少间隔一个完整周期。
|
||
当前系统全局只允许一个轮换批次或 DNS 同步任务执行。
|
||
|
||
AWS 与 Cloudflare 账号分别建档,同一账号可以由多个实例复用,不同实例也可以选择
|
||
完全独立的账号组合。数据库会阻止以下冲突:
|
||
|
||
- 同一个 AWS 账号下的同一区域和 Lightsail 实例被重复管理;
|
||
- 同一个 Cloudflare 账号下的同一记录被多个托管实例使用;
|
||
- 同一个实例同时加入多个实例组。
|
||
|
||
账号的新增、修改和删除与实例配置共用轮换写锁。轮换批次或 DNS 同步任务执行期间
|
||
通常不能写入账号;任务进入待处理或待清理后,只允许替换当前运行项所绑定账号的
|
||
恢复密钥,不能改名、切换认证模式或操作其他账号。仍被活动实例引用的账号不能删除,
|
||
必须先把实例改绑到其他账号或删除相关实例。
|
||
|
||
## 轮换流程
|
||
|
||
一次实例轮换按以下顺序执行:
|
||
|
||
1. **预检**:确认 Lightsail 正在运行且有公网 IPv4;读取当前附加的 Static IP;
|
||
确认 Cloudflare A 记录的 IP、代理模式和 TTL 与实例配置一致。
|
||
2. **分配**:使用确定性任务资源名调用 `AllocateStaticIp`,轮询 AWS operation,
|
||
再读取资源确认新 Static IP 已存在且属于正确区域。
|
||
3. **解绑**:如果实例原本已有 Static IP,调用 `DetachStaticIp` 并回读确认旧地址
|
||
已解绑。实例原本使用动态 IP 时,这一步用于首次 Static IP 纳管,不存在旧静态
|
||
资源需要删除。
|
||
4. **附加**:调用 `AttachStaticIp` 把新地址附加到目标实例,并同时核对 Static IP
|
||
的 `attachedTo`、实例 `publicIpAddress` 和 `isStaticIp`。
|
||
5. **健康检查**:直连新的 Static IP。SOCKS5 会完成方法协商、RFC 1929 用户密码认证
|
||
和外部 CONNECT;HTTP 代理会发送带 Basic 认证的 CONNECT。粘贴链接中的域名不会
|
||
用于 DNS 切换前的源站检查,避免误测到旧 IP。
|
||
6. **更新 DNS**:把 Cloudflare A 记录切换到新地址;灰云写入 `proxied=false`、TTL 60,
|
||
橙云写入 `proxied=true`、TTL Automatic,然后回读验证。
|
||
7. **释放宽限**:至少等待 60 秒,默认 75 秒。旧地址此时已解绑,宽限期不会让旧
|
||
客户端继续访问,但可以避免旧地址在 DNS 缓存期立即被 AWS 分配给其他用户。
|
||
8. **删除旧地址**:再次验证新地址仍附加、DNS 仍指向新地址,并按配置复查代理;
|
||
此时同时探测新 Static IP 和实际 A 记录域名,确保 DNS only 或 Spectrum 入口真正
|
||
可用后再调用 `ReleaseStaticIp`,以 `GetStaticIp` 返回不存在作为删除成功依据。
|
||
|
||
AWS 写操作是异步的。FluxIP 会保存任务阶段并轮询 `GetOperation`;进程意外退出后,
|
||
会根据 SQLite 状态和 AWS/Cloudflare 实际状态续跑,而不是仅根据上次 HTTP 响应猜测。
|
||
预检失败会直接结束任务;无法安全自动恢复的切换异常进入“待处理”;旧地址或回滚
|
||
新地址释放失败进入“待清理”。管理员应先核对云端状态,再更新凭据、继续或放弃任务。
|
||
|
||
如果旧 Static IP 仍存在、DNS 仍为旧地址且没有被外部修改,FluxIP 会在新地址附加、
|
||
健康检查或 DNS 更新失败后自动执行持久化回滚:解绑新地址、重新附加旧地址、验证旧
|
||
代理与 DNS 路由,再删除本次分配的新 Static IP。回滚过程中每一步都会写入 SQLite,
|
||
进程重启后可以幂等续跑。DNS 已经确认切到新地址时会继续向前验证,不会制造新旧 DNS
|
||
缓存分裂;DNS 指向第三方地址、记录缺失或代理模式被外部改变时则停止自动操作。首次从动态 IP
|
||
纳管没有可重新附加的旧静态资源,因此不能自动回滚,只能保留现场并进入待处理。
|
||
|
||
## 运行要求
|
||
|
||
- Python 3.11 或更高版本;
|
||
- 可访问 AWS Lightsail API 和 Cloudflare API;
|
||
- 目标 Lightsail 的 SOCKS5 或 HTTP 代理服务已经运行,端口和防火墙规则正确;
|
||
- Cloudflare 中使用独占的 A 记录;普通代理服务使用 DNS only,橙云需要已配置 Spectrum;
|
||
- AWS Static IP 配额具有轮换余量。
|
||
|
||
不允许使用多 worker:
|
||
|
||
```text
|
||
--workers 1
|
||
```
|
||
|
||
不要运行多个 Compose 副本、多个 systemd 实例或多个指向同一数据库的控制器。
|
||
|
||
## 直接运行源码
|
||
|
||
### Windows PowerShell
|
||
|
||
```powershell
|
||
cd "D:\project\S5-IP自动轮换"
|
||
py -3.11 -m venv .venv
|
||
.\.venv\Scripts\python.exe -m pip install --upgrade pip setuptools
|
||
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
|
||
if (-not (Test-Path .env)) { Copy-Item .env.example .env }
|
||
.\.venv\Scripts\python.exe -m app
|
||
```
|
||
|
||
打开 `http://127.0.0.1:8787`。关闭服务时在运行窗口按 `Ctrl+C`。
|
||
|
||
### Linux 或 macOS
|
||
|
||
```bash
|
||
cd /path/to/fluxip
|
||
python3 -m venv .venv
|
||
.venv/bin/python -m pip install --upgrade pip setuptools
|
||
.venv/bin/python -m pip install -r requirements.txt
|
||
[ -f .env ] || cp .env.example .env
|
||
.venv/bin/python -m app
|
||
```
|
||
|
||
`python -m app` 会读取 `.env` 中的 `ROTATOR_HOST`、`ROTATOR_PORT` 和日志配置,并固定
|
||
使用一个 worker。需要临时覆盖时也可以继续使用显式的 Uvicorn 命令。
|
||
|
||
默认数据文件为:
|
||
|
||
```text
|
||
data/fluxip.db
|
||
data/master.key
|
||
```
|
||
|
||
首次启动会创建数据库和主密钥。数据库存在而主密钥丢失时,程序会拒绝启动,避免
|
||
用新密钥覆盖后无法解密原凭据。
|
||
|
||
从只支持一套全局凭据的旧版本升级时,已配置的 AWS 和 Cloudflare 凭据会在启动迁移
|
||
后分别导入为 legacy 账号,旧实例会自动绑定对应账号。导入过程使用现有主密钥解密并
|
||
重新加密,不会把密钥明文写入数据库或日志。
|
||
|
||
## 管理员账号
|
||
|
||
FluxIP **没有默认管理员账号或默认密码**。
|
||
|
||
第一次打开 WebUI 时自行创建管理员,密码至少 10 位。未配置
|
||
`ROTATOR_BOOTSTRAP_TOKEN` 时,首次创建管理员只允许来自控制器回环地址;容器、远程
|
||
主机或公网反向代理部署应提前配置至少 32 字节高熵 Bootstrap Token。
|
||
|
||
生成示例:
|
||
|
||
```bash
|
||
python -c "import secrets; print(secrets.token_urlsafe(32))"
|
||
```
|
||
|
||
管理员创建完成后,可以从环境配置中删除 Bootstrap Token 并重启服务。不要把
|
||
Bootstrap Token、云端密钥或数据库主密钥提交到 Git。
|
||
|
||
## WebUI 首次配置
|
||
|
||
1. 创建管理员并登录。
|
||
2. 打开“账号”,新增 AWS 账号:选择默认凭据链,或填写专用 Access Key ID、Secret
|
||
Access Key;临时凭据还需要 Session Token。
|
||
3. 继续新增 Cloudflare 账号并填写 API Token。可以为不同 AWS 或 Cloudflare 租户创建
|
||
多个独立账号资料。
|
||
4. 新建托管实例,选择已保存的 AWS 账号和 Cloudflare 账号;从带图标和中文名称的
|
||
区域目录选择 AWS Region,再填写 Lightsail 实例名称、Zone ID 和完整 A 记录名。
|
||
代理测活链接可直接粘贴以下格式,协议、端口和认证会自动解析:
|
||
`socks5://用户:密码@域名:端口`、`http://用户:密码@域名:端口`、
|
||
`socks5://域名:端口@用户:密码`。用户名或密码包含 `@`、`:` 时应使用 URL 编码。
|
||
链接里的域名必须与该实例的 A 记录完整域名一致。
|
||
5. 执行实例连接测试,确认所选 AWS 账号可以读取实例公网地址,所选 Cloudflare 账号
|
||
可以读取目标记录。
|
||
6. 如 DNS 尚未指向实例当前地址,先执行 DNS 同步。
|
||
7. 对单个实例执行一次手动轮换,核对新 Static IP、代理 CONNECT 和 DNS。
|
||
8. 创建实例组、添加成员、设置轮换间隔并启用计划。
|
||
|
||
配置更新使用版本号进行并发检查。如果页面数据已被另一会话修改,刷新后再提交。
|
||
账号、实例和实例组的删除是软删除,不会自动删除 Lightsail 实例或 Cloudflare Zone。
|
||
|
||
轮换批次或 DNS 同步执行中禁止新增、删除账号,也禁止修改实例或实例组。任务因凭据
|
||
失效进入待处理或待清理后,可以只替换当前运行项所绑定账号的恢复密钥;账号名称和
|
||
认证模式仍被锁定。此时也可以为当前实例重新粘贴相同域名、协议和端口的代理链接,
|
||
只替换加密的代理用户名与密码;其他实例配置仍被锁定。审计日志不记录密钥内容。
|
||
|
||
## AWS 最小权限
|
||
|
||
建议使用专用 IAM 身份或角色,不要使用 AWS 根账号密钥。FluxIP 当前需要以下
|
||
Lightsail API:
|
||
|
||
- `GetInstance`
|
||
- `GetStaticIp`
|
||
- `GetStaticIps`
|
||
- `GetOperation`
|
||
- `AllocateStaticIp`
|
||
- `AttachStaticIp`
|
||
- `DetachStaticIp`
|
||
- `ReleaseStaticIp`
|
||
|
||
这些调用包含新资源分配、全区域静态地址查询和异步 operation 查询,不能全部稳定地
|
||
约束到一个预先存在的资源 ARN。下面策略使用精确 Action 列表和区域条件,避免授予
|
||
`lightsail:*`:
|
||
|
||
```json
|
||
{
|
||
"Version": "2012-10-17",
|
||
"Statement": [
|
||
{
|
||
"Sid": "FluxIPStaticIpRotation",
|
||
"Effect": "Allow",
|
||
"Action": [
|
||
"lightsail:GetInstance",
|
||
"lightsail:GetStaticIp",
|
||
"lightsail:GetStaticIps",
|
||
"lightsail:GetOperation",
|
||
"lightsail:AllocateStaticIp",
|
||
"lightsail:AttachStaticIp",
|
||
"lightsail:DetachStaticIp",
|
||
"lightsail:ReleaseStaticIp"
|
||
],
|
||
"Resource": "*",
|
||
"Condition": {
|
||
"StringEquals": {
|
||
"aws:RequestedRegion": [
|
||
"us-east-1",
|
||
"ap-southeast-1"
|
||
]
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
把区域列表改成实际使用的区域。单区域部署只保留一个值。组织若使用 SCP 或权限边界,
|
||
还需在相应层级允许这些 Action。权限不足时不要临时改成管理员权限,应根据错误信息
|
||
补充缺失 Action 或区域。
|
||
|
||
### Static IP 配额
|
||
|
||
AWS 默认每个账号、每个 Lightsail 区域最多 5 个 Static IP,配额可申请提高。假设同一
|
||
区域已有 `N` 台托管实例各占一个 Static IP,安全轮换至少需要 `N + 1` 的配额。实例组
|
||
逐台执行,因此通常只需要一个临时余量。
|
||
|
||
如果 5 个地址已经全部占用,FluxIP 不会先删除旧地址来腾配额。那样会失去恢复余地,
|
||
也可能重新获得相同地址。应先在 AWS 配额页面申请提高限制,或减少该区域的现用地址。
|
||
未附加的 Static IP 可能产生费用;待处理和待清理任务应尽快核对。
|
||
|
||
## Cloudflare 最小权限
|
||
|
||
创建专用 API Token,不要使用 Global API Key。Token 只包含托管目标所在 Zone,并授予:
|
||
|
||
- `Zone / Zone / Read`
|
||
- `Zone / DNS / Edit`
|
||
|
||
多个实例跨多个 Zone 时,把这些特定 Zone 都加入同一 Token,或改用覆盖所需 Zone 的
|
||
受控 Token。所有目标记录必须满足:
|
||
|
||
- 类型为 A;
|
||
- Proxy status 与实例里的橙云开关一致;
|
||
- 同名没有 CNAME、AAAA 或第二条 A 记录;
|
||
- 记录由 FluxIP 独占,不由其他自动化同时修改。
|
||
|
||
默认应使用 **DNS only(灰云)**。Cloudflare 普通橙云不代理任意 SOCKS5、通用
|
||
HTTP CONNECT 或 10808 等任意 TCP 端口。只有先在 Cloudflare 配置并开通 Spectrum
|
||
等对应 TCP 服务后才开启 FluxIP 的橙云开关;这个开关本身不会创建 Spectrum 应用。
|
||
|
||
FluxIP 写入前会核对当前记录,写入后再回读,但 Cloudflare 常规 DNS 接口不提供跨
|
||
AWS 与 DNS 的原子事务。需要人工接管时,先禁用相关实例组计划,并等待当前任务完成
|
||
或在任务详情明确处理异常现场。
|
||
|
||
## 环境变量
|
||
|
||
应用读取项目根目录 `.env`,变量统一使用 `ROTATOR_` 前缀。
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `ROTATOR_ENVIRONMENT` | `development` | `production` 会关闭 API 文档 |
|
||
| `ROTATOR_HOST` | `127.0.0.1` | 服务监听地址 |
|
||
| `ROTATOR_PORT` | `8787` | 服务端口 |
|
||
| `ROTATOR_DATABASE_PATH` | `data/fluxip.db` | SQLite 文件 |
|
||
| `ROTATOR_MASTER_KEY_FILE` | `data/master.key` | 凭据加密主密钥文件 |
|
||
| `ROTATOR_MASTER_KEY` | 空 | 外部注入的 URL-safe Base64 主密钥,优先于文件 |
|
||
| `ROTATOR_BOOTSTRAP_TOKEN` | 空 | 远程首次初始化令牌 |
|
||
| `ROTATOR_COOKIE_SECURE` | `false` | HTTPS 生产环境应设为 `true` |
|
||
| `ROTATOR_SESSION_DAYS` | `7` | 管理员会话有效期 |
|
||
| `ROTATOR_ALLOWED_ORIGINS` | 本机 8787 | 允许的完整 Origin,逗号分隔 |
|
||
| `ROTATOR_TRUSTED_HOSTS` | `localhost,127.0.0.1` | 接受的 Host,逗号分隔 |
|
||
| `ROTATOR_TRUSTED_PROXIES` | `127.0.0.1,::1` | 可提交转发头的代理 IP/CIDR |
|
||
| `ROTATOR_SCHEDULER_ENABLED` | `true` | 是否执行实例组到期扫描 |
|
||
| `ROTATOR_SCHEDULER_TICK_SECONDS` | `10` | 扫描间隔,2 到 60 秒 |
|
||
| `ROTATOR_LOG_LEVEL` | `INFO` | 日志级别 |
|
||
| `ROTATOR_EXTERNAL_TIMEOUT_SECONDS` | `20` | 外部请求基础超时,5 到 60 秒 |
|
||
|
||
生产示例见 `deploy/fluxip.env.example`。不要把生产密钥直接写进仓库内的示例文件。
|
||
|
||
## Docker Compose
|
||
|
||
Docker Compose 默认把 WebUI 发布到 `127.0.0.1:8787`,并使用项目目录下三个独立的
|
||
宿主机目录保存数据库、主密钥和备份:
|
||
|
||
```text
|
||
data/fluxip-data/
|
||
data/fluxip-key/
|
||
data/fluxip-backups/
|
||
```
|
||
|
||
部署需要 Docker Compose v2(使用 `docker compose` 命令)。启动时,一次性的
|
||
`fluxip-init` 服务会拒绝挂载目录中的符号链接或特殊文件,把目录权限收紧并交给
|
||
UID/GID `10001` 后退出;长期运行的 `fluxip` 服务仍是非 root 用户。已有数据库、
|
||
主密钥和备份不会被初始化服务删除或改写内容。
|
||
|
||
```bash
|
||
cp deploy/fluxip.env.example .env
|
||
# 编辑 .env,至少设置高熵 ROTATOR_BOOTSTRAP_TOKEN
|
||
docker compose up -d --build
|
||
docker compose ps -a
|
||
curl http://127.0.0.1:8787/readyz
|
||
```
|
||
|
||
Windows PowerShell:
|
||
|
||
```powershell
|
||
Copy-Item deploy\fluxip.env.example .env
|
||
docker compose up -d --build
|
||
Invoke-RestMethod http://127.0.0.1:8787/readyz
|
||
```
|
||
|
||
常用命令:
|
||
|
||
```bash
|
||
docker compose logs -f --tail=200 fluxip
|
||
docker compose restart fluxip
|
||
docker compose down
|
||
```
|
||
|
||
`docker compose down` 不删除上述宿主机目录。不要手工删除 `data/fluxip-key/master.key`;
|
||
数据库存在但主密钥丢失时,已保存的云端凭据将无法解密。
|
||
|
||
## Linux systemd 与 HTTPS
|
||
|
||
示例假定代码位于 `/opt/fluxip`:
|
||
|
||
```bash
|
||
sudo useradd --system --home /var/lib/fluxip --shell /usr/sbin/nologin fluxip
|
||
sudo install -d -o root -g root -m 0755 /opt/fluxip /etc/fluxip
|
||
sudo cp -a app migrations deploy requirements.txt pyproject.toml README.md /opt/fluxip/
|
||
sudo python3 -m venv /opt/fluxip/.venv
|
||
sudo /opt/fluxip/.venv/bin/python -m pip install -r /opt/fluxip/requirements.txt
|
||
sudo install -o root -g root -m 0600 deploy/fluxip.env.example /etc/fluxip/fluxip.env
|
||
sudo editor /etc/fluxip/fluxip.env
|
||
sudo install -o root -g root -m 0644 deploy/fluxip.service /etc/systemd/system/
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now fluxip.service
|
||
curl http://127.0.0.1:8787/readyz
|
||
```
|
||
|
||
查看日志:
|
||
|
||
```bash
|
||
sudo journalctl -u fluxip.service -n 200 --no-pager
|
||
```
|
||
|
||
`deploy/nginx-fluxip.conf.example` 是本机 Nginx HTTPS 反向代理示例。启用后设置:
|
||
|
||
```dotenv
|
||
ROTATOR_COOKIE_SECURE=true
|
||
ROTATOR_ALLOWED_ORIGINS=https://controller.example.com
|
||
ROTATOR_TRUSTED_HOSTS=controller.example.com,127.0.0.1
|
||
ROTATOR_TRUSTED_PROXIES=127.0.0.1,::1
|
||
```
|
||
|
||
若代理不在本机,只填写代理的精确 IP/CIDR,并同步限制 Uvicorn
|
||
`--forwarded-allow-ips` 和防火墙。不要信任 `0.0.0.0/0` 或 `::/0` 的转发头。
|
||
|
||
## 备份与恢复
|
||
|
||
数据库和主密钥必须成对恢复,但不应默认放进同一个未加密归档:
|
||
|
||
- 数据库包含管理员、实例、实例组、任务历史和加密凭据;
|
||
- `master.key` 可以解密云端凭据,必须单独保存在密码库或加密介质;
|
||
- 丢失主密钥无法恢复凭据,泄露主密钥会使数据库密文失去保护;
|
||
- 不要提交数据库、主密钥、`.env` 或备份包。
|
||
|
||
在线备份只打包 SQLite:
|
||
|
||
```bash
|
||
python3 deploy/backup.py \
|
||
--database /var/lib/fluxip/fluxip.db \
|
||
--output-dir /var/backups/fluxip \
|
||
--keep-last 30
|
||
```
|
||
|
||
Docker:
|
||
|
||
```bash
|
||
docker compose exec -T fluxip python /app/deploy/backup.py \
|
||
--database /app/data/fluxip.db \
|
||
--output-dir /app/backups \
|
||
--keep-last 30
|
||
docker compose cp fluxip:/app/backups/. ./fluxip-backups
|
||
```
|
||
|
||
systemd 定时备份示例为 `deploy/fluxip-backup.service` 和
|
||
`deploy/fluxip-backup.timer`。备份后应验证同名 `.sha256`,并定期在隔离环境实际恢复。
|
||
|
||
只有目标目录本身已经加密时,才使用 `--allow-plaintext-key-archive` 创建包含主密钥的
|
||
完整归档。恢复时先停止 FluxIP,同时恢复匹配的数据库和主密钥,再启动并验证
|
||
`/readyz`、管理员登录、账号资料状态、实例组计划和云端连接。
|
||
|
||
## 健康检查与排错
|
||
|
||
- `GET /healthz`:进程存活状态,返回 `status` 和应用版本;不探测 SQLite 或云端账号。
|
||
- `GET /readyz`:检查 SQLite 和已启用调度器的运行状态;就绪时返回 HTTP 200,数据库
|
||
异常或已启用调度器意外退出时返回 HTTP 503 和 `degraded`。响应中的 `scheduler`
|
||
会显示开关、运行状态、最近扫描/派发/异常时间;该接口不会调用 AWS 或 Cloudflare。
|
||
- **到期后没有自动轮换**:先检查 `/readyz` 的 `scheduler.enabled` 和 `running`。系统环境
|
||
变量优先于 `.env`;请删除意外继承的 `ROTATOR_SCHEDULER_ENABLED=false`,或明确设为
|
||
`true` 后重启。已逾期的实例组会在调度器启动后的首轮扫描立即执行。
|
||
- **没有默认管理员**:首次打开页面自行创建;远程初始化需 Bootstrap Token。
|
||
- **账号连接测试失败**:确认实例选中了正确类型的 AWS 与 Cloudflare 账号,账号密钥
|
||
完整,并且 Region、Zone ID 和记录名属于对应账号。
|
||
- **AWS AccessDenied**:核对所选 AWS 账号的上述 8 个 Lightsail Action 和目标区域,
|
||
不要直接授予管理员。
|
||
- **Static IP 配额不足**:提高该区域配额;不要先释放正在使用的旧地址。
|
||
- **找不到附加的旧地址**:确认实例、Static IP 和区域一致,避免同时在控制台人工修改。
|
||
- **代理检查失败**:确认服务监听公网接口、端口开放、链接协议与服务一致,并重新粘贴
|
||
正确的用户名、密码。探测会实际建立到 `1.1.1.1:443` 的 CONNECT。
|
||
- **DNS 冲突**:删除同名 CNAME、AAAA 或多余 A 记录,并让橙云状态与实例配置一致。
|
||
- **任务待处理**:先核对新旧 Static IP 的真实附加状态和 DNS,再更新恢复凭据或继续。
|
||
- **任务待清理**:新地址通常已经生效,但旧地址尚未成功释放,应尽快处理以避免费用。
|
||
- **客户端短暂掉线**:Static IP 替换会终止原 TCP 连接,这是当前架构的预期边界。
|
||
- **主密钥缺失**:恢复与数据库匹配的主密钥,不要删除数据库让程序生成新密钥。
|
||
- **Docker 启动提示 `/app/secrets/master.key` 无权限**:拉取最新代码后执行
|
||
`docker compose down`,再执行 `docker compose up -d --build --force-recreate`,确认
|
||
`docker compose ps -a` 中 `fluxip-init` 为 `Exited (0)`。这两个命令不会删除持久化
|
||
目录。旧版本可先停止服务,再将 `data/fluxip-data`、`data/fluxip-key`、
|
||
`data/fluxip-backups` 的所有者递归改为 `10001:10001`;不要删除或单独替换已有主密钥。
|
||
|
||
## 安全清单
|
||
|
||
- Uvicorn 始终使用单 worker、单副本。
|
||
- WebUI 只开放给受信网络,公网入口使用 HTTPS。
|
||
- AWS 使用专用最小权限身份,Cloudflare Token 只授权必要 Zone。
|
||
- 每个 Cloudflare A 记录由一个托管实例独占;普通 SOCKS5/HTTP CONNECT 使用 DNS only。
|
||
- 代理服务启用身份认证并限制入站来源。
|
||
- 为 Static IP 配额预留至少一个安全轮换余量。
|
||
- 定期检查待处理、待清理任务和未附加 Static IP 费用。
|
||
- SQLite 与主密钥成对备份,备份异机存放并加密。
|
||
- 不在日志、截图、工单、Git 或最终交付文档中发送真实密钥。
|