refactor(gateway): 统一 AETHER_GATEWAY_BIND 为 APP_PORT,新增 API Key 前缀配置和启动自举管理员

- 绑定地址固定 0.0.0.0,仅通过 APP_PORT 控制端口,简化 CLI/Docker/systemd/dev.sh/前端代理全链路
- 新增 API_KEY_PREFIX 环境变量,抽取 handlers/shared/api_keys.rs 消除 admin/public 重复逻辑
- 新增 bootstrap_admin.rs,启动时通过 ADMIN_* 环境变量在无管理员时自动创建首个本地管理员
- 前端密码输入改用 type=password,API Key 占位符改为动态前缀
- 删除过时的 pyproject.toml/uv.lock 和旧部署文档
- 更新 .env.example/README 反映新配置项
This commit is contained in:
fawney19
2026-04-11 17:39:02 +08:00
parent a570a77cca
commit 801e16c988
30 changed files with 867 additions and 3929 deletions

View File

@@ -1,241 +0,0 @@
# Aether Logging Guide
目标:
- 明确 `stdout`、文件日志和 `journald` 的职责边界
-`aether-gateway` / `aether-proxy` 提供统一的查看与排障手册
- 避免重复轮转、重复采集和“日志写了但不知道去哪找”的问题
## 1. 组合策略
推荐组合不是“二选一”,而是按运行方式选一套固定策略:
| 场景 | 推荐配置 | 说明 |
|------|----------|------|
| 本地开发 | `stdout` | 直接看终端输出,最简单 |
| Docker Compose 开发 | `stdout` | 交给容器日志驱动,避免和文件日志重复 |
| systemd 单机生产 | `both` | `journald` 负责最近事件,文件日志负责留痕和 grep |
| 裸机临时排障 | `file``both` | 需要长时间保留时用文件日志 |
规则:
- `stdout` 进入容器日志驱动或 `journald`
- `file` 由应用自己轮转和清理
- `both` 适合宿主机生产
- 不要再给同一目录额外叠 `logrotate`
## 2. 关键配置
### aether-gateway
- `AETHER_LOG_DESTINATION=stdout|file|both`
- `AETHER_LOG_FORMAT=pretty|json`
- `AETHER_LOG_DIR=/var/log/aether`
- `AETHER_LOG_ROTATION=hourly|daily`
- `AETHER_LOG_RETENTION_DAYS=7`
- `AETHER_LOG_MAX_FILES=30`
### aether-proxy
- `AETHER_PROXY_LOG_DESTINATION=stdout|file|both`
- `AETHER_PROXY_LOG_JSON=false|true`
- `AETHER_PROXY_LOG_DIR=/var/log/aether-proxy`
- `AETHER_PROXY_LOG_ROTATION=hourly|daily`
- `AETHER_PROXY_LOG_RETENTION_DAYS=7`
- `AETHER_PROXY_LOG_MAX_FILES=30`
注意:
- `file` / `both` 必须同时给出 `*_LOG_DIR`
- Docker Compose 默认建议 `stdout`
- systemd 默认建议 `both`
## 3. 日志落点
### systemd
`aether-gateway`
- `journalctl -u aether-gateway`
- 文件日志目录默认建议 `/var/log/aether`
`aether-proxy`
- `journalctl -u aether-proxy`
- 文件日志目录默认建议 `/var/log/aether-proxy`
### Docker Compose
- `docker compose logs -f gateway`
- `docker compose logs -f aether-proxy`
- 如果显式启用了 `file/both`,再去看容器内挂载出来的日志目录
## 4. 常用排障命令
### gateway
```bash
sudo systemctl status aether-gateway --no-pager
sudo journalctl -u aether-gateway -n 200 --no-pager
sudo tail -n 200 /var/log/aether/aether-gateway.*.log
sudo rg "trace_id|request_id|error" /var/log/aether/aether-gateway.*.log
```
### proxy
```bash
sudo systemctl status aether-proxy --no-pager
sudo journalctl -u aether-proxy -n 200 --no-pager
sudo tail -n 200 /var/log/aether-proxy/aether-proxy.*.log
sudo rg "node_id|server|error" /var/log/aether-proxy/aether-proxy.*.log
```
### Docker
```bash
docker compose logs -f gateway
docker compose logs -f aether-proxy
```
## 5. 故障定位顺序
1. 先看 `systemctl status`,判断是不是进程本身没起来
2. 再看 `journalctl`,确认启动报错、权限报错、配置报错
3. 如果启用了 `both`,再查文件日志做结构化检索
4.`trace_id` / `request_id` 串联同一请求
5. 对长时间问题看文件日志,对最近故障先看 `journald`
## 6. 常见问题
### 没有生成文件日志
优先检查:
- `*_LOG_DESTINATION` 是否是 `file``both`
- `*_LOG_DIR` 是否已设置
- systemd 的 `LogsDirectory=` 是否生效
- 进程用户对日志目录是否有写权限
### 日志重复
通常是以下原因之一:
- 容器里开了 `both`,同时又在看 `docker logs`
- 宿主机上既开了应用文件日志,又配了外部 `logrotate` / 采集器重复收集同一路径
处理方式:
- Docker 开发环境保持 `stdout`
- systemd 生产环境用 `both`
- 同一文件目录不要再叠第二套轮转器
### 磁盘持续增长
先看:
- `*_LOG_RETENTION_DAYS`
- `*_LOG_MAX_FILES`
- 是否误开 `hourly` 且保留天数过大
- 是否有旧目录不再被应用清理
### grep 不到请求
优先用这些字段:
- `trace_id`
- `request_id`
- `node_id`
- `route_class`
- `execution_path`
如果 `stdout` 噪声太大,就切到文件日志查。
## 7. 脱敏边界
常规日志不应该直接出现:
- `Authorization`
- `x-api-key`
- `x-goog-api-key`
- 完整请求体
- 完整响应体
- 完整 `report_context`
当前约定是:
- body 只记录摘要,例如字节数和 hash
- 真正的原文只允许进入显式调试通道,且必须先过 redaction
## 8. 关键事件名
当前先固定这批事件名,后续新增事件优先复用同一命名风格:
| event_name | log_type | 用途 |
|------------|----------|------|
| `http_request_started` | `access` | 请求进入 gateway |
| `http_request_completed` | `access` | 请求正常完成 |
| `http_request_failed` | `access` | 请求以 5xx 结束 |
| `local_sync_candidate_retry_scheduled` | `event` | 本地 sync 候选命中可重试结果,调度下一个候选 |
| `local_stream_candidate_retry_scheduled` | `event` | 本地 stream 候选命中可重试状态,调度下一个候选 |
| `local_openai_chat_candidates_exhausted` | `event` | 本地 OpenAI Chat 路径耗尽全部候选 |
| `local_core_finalize_fallback_raw_response_body` | `event` | 本地核心 finalize 无法映射成结构化响应,退回原始 body |
| `local_core_finalize_missing_error_report_mapping` | `event` | 本地核心 finalize 缺少错误上报映射 |
| `local_core_finalize_missing_success_report_mapping` | `event` | 本地核心 finalize 缺少成功上报映射 |
| `usage_terminal_settlement_failed` | `event` | usage 终态直写后结算失败 |
| `admin_billing_preset_applied` | `audit` | 管理员应用计费预设 |
| `admin_system_settings_updated` | `audit` | 管理员更新系统设置 |
| `admin_system_config_updated` | `audit` | 管理员更新系统配置项 |
| `admin_system_config_deleted` | `audit` | 管理员删除系统配置项 |
| `admin_wallet_balance_adjusted` | `audit` | 管理员手动调整钱包余额 |
| `admin_wallet_manual_recharge_created` | `audit` | 管理员创建手动充值 |
| `admin_wallet_refund_processed` | `audit` | 管理员开始处理退款 |
| `admin_wallet_refund_completed` | `audit` | 管理员完成退款 |
| `admin_wallet_refund_failed` | `audit` | 管理员标记退款失败 |
| `video_task_status_updated` | `event` | 异步视频任务轮询后状态发生更新 |
| `video_task_finalize_settlement_failed` | `event` | 异步视频任务终态结算失败 |
| `maintenance_worker_failed` | `ops` | 定时任务启动、tick 或调度查找失败 |
| `usage_cleanup_completed` | `ops` | usage 清理批次完成 |
| `provider_checkin_completed` | `ops` | provider checkin 批次完成 |
| `log_retention_cleanup_failed` | `ops` | 日志保留清理失败,但不阻断服务启动 |
| `execution_runtime_stream_flush_skipped` | `debug` | 下游断开,跳过 stream flush |
| `execution_runtime_stream_report_skipped` | `debug` | 下游断开,跳过 stream report |
命名规则:
- 统一小写蛇形
- 前缀先写模块,再写动作,再写结果
- `access``event``debug``ops``audit` 分层不要混用
- 管理员高风险写操作优先落 `audit`
- 定时任务批次结果和失败统一落 `ops`
## 9. 字段字典
这些字段应该优先保持稳定,方便 grep、告警和日志采集
| 字段 | 含义 |
|------|------|
| `event_name` | 机器可依赖的稳定事件名 |
| `log_type` | 日志类别,例如 `access` / `event` / `debug` / `ops` / `audit` |
| `service` | 服务名,例如 `aether-gateway` / `aether-proxy` |
| `node_role` | 节点角色,例如 `gateway` / `proxy` |
| `instance_id` | 进程或节点实例标识 |
| `trace_id` | 跨链路关联键 |
| `request_id` | 业务请求标识 |
| `route_class` | 路由大类,例如 `passthrough` / `control` |
| `execution_path` | 实际执行路径,例如 `local` / `proxy` / `passthrough` |
| `status` | 事件状态,例如 `started` / `completed` / `failed` |
| `status_code` | HTTP 或上游状态码 |
| `elapsed_ms` | 整体耗时 |
| `error` | 错误摘要,不放原始敏感内容 |
| `candidate_id` | 候选执行槽位标识 |
| `candidate_count` | 候选数量或耗尽数量 |
| `provider_id` | 供应商标识 |
| `endpoint_id` | endpoint 标识 |
| `key_id` | provider key 标识 |
| `task_id` | 异步任务标识 |
约束:
- 没有稳定含义的临时字段不要进入长期事件模板
- 敏感原文不进入 `error`
- body 相关字段默认只允许摘要,不允许原文

View File

@@ -1,198 +0,0 @@
# Aether Gateway Systemd 部署
目标形态:
- `aether-gateway` 作为宿主机上的 `systemd` 服务运行
- Docker 只保留 `Postgres``Redis`
- 前端静态资源由 `aether-gateway` 直接服务
适用场景:
- 单机/单节点部署
- 允许短暂停机更新
- 未来需要网页一键更新
## 目录约定
安装脚本默认使用以下路径:
- Gateway release: `/opt/aether/releases/<release-id>`
- 当前版本软链接: `/opt/aether/current`
- systemd env 文件: `/etc/aether/aether-gateway.env`
- 数据栈 compose: `/opt/aether/shared/docker-compose.data.yml`
## 1. 构建二进制与前端
在目标机器或 CI 产物目录中准备:
```bash
cargo build --release -p aether-gateway
(cd frontend && npm ci && npm run build)
```
构建完成后需要存在:
- `target/release/aether-gateway`
- `frontend/dist`
## 2. 准备环境变量
复制示例文件并修改:
```bash
sudo mkdir -p /etc/aether
sudo cp deploy/systemd/aether-gateway.env.example /etc/aether/aether-gateway.env
sudo chmod 600 /etc/aether/aether-gateway.env
sudo vim /etc/aether/aether-gateway.env
```
注意:
- `systemd``EnvironmentFile` 只适合简单 `KEY=VALUE`
- 不要写 `export`
- 不要写 `${VAR}`、命令替换、shell 函数等复杂语法
- `DATABASE_URL` / `REDIS_URL` 请直接写完整值
- 文件日志建议直接配 `AETHER_LOG_DESTINATION=both``AETHER_LOG_DIR=/var/log/aether`
- 现在支持 `AETHER_GATEWAY_DEPLOYMENT_TOPOLOGY=single-node|multi-node`
- 现在支持 `AETHER_GATEWAY_NODE_ROLE=all|frontdoor|background`
- 如果未来准备跑多实例,先把它切成 `multi-node`,让启动期校验替你拦掉单机残留配置
- 安装脚本会拒绝示例占位值,例如 `change-me-*``change-this-*`
三种推荐运行档位:
- `single-node + all + Postgres/Redis`:完整单机,功能最全
- `single-node + all + 无 Postgres/Redis`:轻量单机,本地文件/内存兜底,适合最小化运行
- `multi-node + frontdoor/background + Postgres/Redis`:集群预备形态
## 3. 启动数据服务
这套 compose 只负责 `Postgres``Redis`
```bash
docker compose \
--env-file /etc/aether/aether-gateway.env \
-f deploy/docker-compose.data.yml \
up -d
```
其中:
- `Postgres` 数据目录走 Docker volume
- `Redis` 默认开启 `AOF + everysec`,避免 usage stream、分布式限流和锁状态在容器重启时全部丢失
- 端口默认只绑定到 `127.0.0.1`
- 如果给 gateway 打开 `AETHER_LOG_DESTINATION=file|both`,建议把 `AETHER_LOG_DIR` 指到持久目录
如果你已经执行过安装脚本,也可以使用安装后的固定路径:
```bash
docker compose \
--env-file /etc/aether/aether-gateway.env \
-f /opt/aether/shared/docker-compose.data.yml \
up -d
```
## 4. 安装 systemd 服务
```bash
sudo deploy/systemd/install-systemd.sh --env-file /etc/aether/aether-gateway.env
```
这个脚本会完成:
- 创建 `aether` 系统用户/组
- 复制 release 到 `/opt/aether/releases/<release-id>`
- 更新 `/opt/aether/current` 软链接
- 安装 `aether-gateway.service`
- 安装数据栈 compose 到 `/opt/aether/shared/docker-compose.data.yml`
- 校验 env 文件语法、拓扑模式和关键密钥占位值
- 重载并启动 `aether-gateway`
日志约定:
- 默认 stdout 仍进入 `journald`
- 如果设置 `AETHER_LOG_DESTINATION=both`gateway 会同时把日志写到 `AETHER_LOG_DIR`
- 文件日志目前支持 `daily/hourly` 轮转
- 文件日志会按 `AETHER_LOG_RETENTION_DAYS``AETHER_LOG_MAX_FILES` 自动清理
- 不建议再叠加外部 `logrotate` 对同一目录做二次轮转
- 更完整的组合策略和排障命令见 `docs/deploy/logging.md`
## 5. 验证
```bash
sudo systemctl status aether-gateway --no-pager
sudo journalctl -u aether-gateway -n 100 --no-pager
sudo ls -lah /var/log/aether
sudo tail -n 100 /var/log/aether/aether-gateway.*.log
curl -fsS http://127.0.0.1:8084/_gateway/health
curl -fsS http://127.0.0.1:8084/readyz
curl -fsS http://127.0.0.1:8084/.well-known/aether/frontdoor.json
```
## 6. 升级前备份
如果你使用仓库内置的数据栈 compose升级前至少备份 `Postgres`
```bash
docker compose \
--env-file /etc/aether/aether-gateway.env \
-f /opt/aether/shared/docker-compose.data.yml \
exec -T postgres pg_dump -U postgres aether | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
```
如果你改过 `DB_USER` / `DB_NAME`,把命令里的 `postgres` / `aether` 替换成实际值。
## 7. 更新流程
重新构建新版本后,重复执行安装脚本即可:
```bash
cargo build --release -p aether-gateway
(cd frontend && npm ci && npm run build)
sudo deploy/systemd/install-systemd.sh \
--env-file /etc/aether/aether-gateway.env \
--release-id "$(date +%Y%m%d%H%M%S)"
```
这会:
- 安装新 release
- 切换 `/opt/aether/current`
- 重启 `aether-gateway`
## 8. 回滚
列出历史 release
```bash
ls -1 /opt/aether/releases
```
把软链接切回旧版本并重启:
```bash
sudo ln -sfn /opt/aether/releases/<old-release-id> /opt/aether/current
sudo systemctl restart aether-gateway
```
如果这次更新包含数据库结构变更,单纯切回旧 release 不一定够,还需要把 `Postgres` 恢复到升级前备份。当前这套 Rust 迁移链路不应该再按老的 `alembic downgrade` 方式处理。
## 9. 集群演进注意点
这套方式适合单机,但不会阻塞以后做集群。要提前避免的坑:
- 不要把长期业务状态继续写进本地文件
- `AETHER_GATEWAY_VIDEO_TASK_STORE_PATH` 只适合单机临时方案
- 共享状态优先放 `Postgres` / `Redis`
- 以后扩成多实例时,替换的是上层托管者,不是 `aether-gateway` 二进制形态本身
建议先把单机 env 收敛成未来可迁移的基线:
- 保留 `DATABASE_URL``REDIS_URL`,不要跑“无 Redis 的单机特例”
- 默认就使用 `AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=rust-authoritative`
- 只有单机临时排障时才启用 `AETHER_GATEWAY_VIDEO_TASK_STORE_PATH`
- 如果未来要接 `aether-proxy` / tunnel owner relay多实例下要给每个节点单独设置 `AETHER_GATEWAY_INSTANCE_ID`
- 如果未来要跨节点转发 tunnel owner 请求,还要给每个节点设置外部可达的 `AETHER_TUNNEL_RELAY_BASE_URL`
- `AETHER_GATEWAY_DISTRIBUTED_REQUEST_LIMIT` 是可选项,但要开的话现在已经可以直接复用 `REDIS_URL`,不必再维护第二份 Redis 地址
- 多实例下不要再用 `AETHER_GATEWAY_NODE_ROLE=all`;前台节点用 `frontdoor`,后台节点用 `background`
- 多实例下前台限流不会再退回本地内存计数;单机模式才允许这种兜底

View File

@@ -1,28 +0,0 @@
### 1. 创建集群项目
### 2. 添加两个数据库服务
- redis (复制 Redis Connection String)
- postgresql (复制 Connection String)
### 3. 添加Docker容器镜像
1. 镜像: ghcr.io/fawney19/aether:latest
2. 环境变量
```
DATABASE_URL=(postgresql复制的内容)
REDIS_URL=(redis复制的内容)
JWT_SECRET_KEY=change-this-to-a-secure-random-string
ENCRYPTION_KEY=change-this-to-another-secure-random-string
ADMIN_EMAIL=admin@example.com
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123456
```
JWT_SECRET_KEY、ENCRYPTION_KEY: 可以运行项目中的python脚本自动生成 python generate_keys.py
ADMIN_EMAIL、ADMIN_USERNAME、ADMIN_PASSWORD: 管理员初始信息必须修改
3. 端口: 80