feat: harden deployment and CI pipeline

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
zyycn
2026-07-11 17:18:17 +08:00
co-authored by Codex
parent 49120d0efb
commit 4dcd9dc31d
19 changed files with 884 additions and 246 deletions
+4
View File
@@ -0,0 +1,4 @@
# Docker deployment secrets. Copy this file to deploy/.env and set every value.
CPR_ADMIN_DEFAULT_PASSWORD=''
CPR_POSTGRES_PASSWORD=''
CPR_REDIS_PASSWORD=''
+8 -5
View File
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7
# syntax=docker/dockerfile:1.7@sha256:a57df69d0ea827fb7266491f2813635de6f17269be881f696fbfdf2d83dda33e
FROM node:24-bookworm-slim AS frontend-builder
FROM node:24-bookworm-slim@sha256:cb4e8f7c443347358b7875e717c29e27bf9befc8f5a26cf18af3c3dec80e58c5 AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package.json frontend/pnpm-lock.yaml frontend/pnpm-workspace.yaml ./
@@ -8,7 +8,7 @@ RUN corepack enable && pnpm install --frozen-lockfile
COPY frontend ./
RUN pnpm build
FROM rust:1.95-bookworm AS backend-builder
FROM rust:1.95-bookworm@sha256:6258907abe69656e41cd992e0b705cdcfabcbbe3db374f92ed2d47121282d4a1 AS backend-builder
WORKDIR /app/backend
ARG TARGETARCH
@@ -38,7 +38,10 @@ ARG TARGETARCH
WORKDIR /out
COPY --from=frontend-builder /app/frontend/dist /tmp/release/codex-proxy-rs/web/dist
RUN cp /tmp/codex-proxy-rs /tmp/release/codex-proxy-rs/ \
RUN test -n "${CPR_VERSION}" \
&& test -n "${TARGETOS}" \
&& test -n "${TARGETARCH}" \
&& cp /tmp/codex-proxy-rs /tmp/release/codex-proxy-rs/ \
&& tar -czf "/out/codex-proxy-rs_${CPR_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" \
-C /tmp/release codex-proxy-rs
@@ -46,7 +49,7 @@ FROM scratch AS release-asset
COPY --from=release-asset-builder /out /
FROM debian:bookworm-slim AS runtime-base
FROM debian:bookworm-slim@sha256:60eac759739651111db372c07be67863818726f754804b8707c90979bda511df AS runtime-base
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
+20 -10
View File
@@ -9,6 +9,12 @@
```bash
mkdir -p .runtime/data .runtime/logs
cp deploy/config.example.yaml .runtime/config.yaml
sudo chown -R "$(id -u):10001" .runtime/data .runtime/logs
chmod 0770 .runtime/data .runtime/logs
sudo chown "$(id -u):10001" .runtime/config.yaml
chmod 0640 .runtime/config.yaml
cp deploy/.env.example deploy/.env
chmod 0600 deploy/.env
```
`deploy/config.example.yaml` 是 Docker 部署模板。Compose 默认使用仓库根目录的 `.runtime/`:
@@ -20,20 +26,24 @@ cp deploy/config.example.yaml .runtime/config.yaml
应用日志同时写入 `docker logs` 与 `.runtime/logs`。Compose 对应用、PostgreSQL、Redis
统一启用 `json-file` 的 `10m × 5` 轮转;应用文件日志还受配置中的自然日、单文件大小和文件总数限制。
复制后必须设置 `admin.default_password`。空值、长度不足 12 位或常见弱口令会被后端拒绝。
`deploy/.env` 是 Docker 部署的密钥配置文件,已被 Git 忽略且必须保持 mode `0600`。其中
`CPR_ADMIN_DEFAULT_PASSWORD`、`CPR_POSTGRES_PASSWORD`、`CPR_REDIS_PASSWORD` 三项均为必填,
留空时 Compose 会在启动前失败。管理员密码必须至少 12 位且不能是常见弱口令。
Redis 默认密码为 `codex_proxy`,与 `deploy/config.example.yaml` 的连接串一致。生产部署通过
`CPR_REDIS_PASSWORD` 覆盖后,必须同步更新 `.runtime/config.yaml` 中 URL 的密码(特殊字符需做 URL 编码)。
本地 Rust 集成测试若复用该 Compose Redis,设置
`CPR_TEST_REDIS_URL=redis://:codex_proxy@127.0.0.1:6379/`。
Compose 会把 PostgreSQL、Redis 密码的同一个值同时传给服务端和应用;应用使用 URL API 覆盖连接串
密码,并用管理员密码覆盖 `admin.default_password`。因此无需修改 `.runtime/config.yaml` 中的密码,
特殊字符也无需手工编码。本地 Rust 集成测试若复用该 Compose 服务,应使用与 `deploy/.env` 一致的
测试连接串。
编辑密码时保留 `deploy/.env` 中的单引号,使 `#`、`$` 等 Compose 语法字符按字面值传递。
如果前面还有 Nginx、Caddy、Cloudflare Tunnel 等反向代理,确保反代正常传递 `CF-Connecting-IP`、`X-Real-IP` 或 `X-Forwarded-For`。后端会按这组头自动解析真实客户端 IP;没有这些头时回落到直连 peer IP。
构建并启动:
```bash
docker compose -f deploy/docker-compose.yml build
docker compose -f deploy/docker-compose.yml up -d
# 先编辑 deploy/.env,设置全部三项密码。
docker compose --env-file deploy/.env -f deploy/docker-compose.yml build
docker compose --env-file deploy/.env -f deploy/docker-compose.yml up -d
```
可以通过环境变量覆盖宿主机路径。容器内始终通过 `CPR_CONFIG_FILE=/app/config.yaml` 读取挂载后的配置:
@@ -42,7 +52,7 @@ docker compose -f deploy/docker-compose.yml up -d
CPR_CONFIG_FILE=/path/to/config.yaml \
CPR_DATA_DIR=/path/to/data \
CPR_LOG_DIR=/path/to/logs \
docker compose -f deploy/docker-compose.yml up -d
docker compose --env-file deploy/.env -f deploy/docker-compose.yml up -d
```
## 构建
@@ -56,7 +66,7 @@ docker build -f deploy/Dockerfile -t codex-proxy-rs:latest .
也可以用 Compose 构建:
```bash
docker compose -f deploy/docker-compose.yml build
docker compose --env-file deploy/.env -f deploy/docker-compose.yml build
```
需要写入版本元数据时:
@@ -65,5 +75,5 @@ docker compose -f deploy/docker-compose.yml build
CPR_VERSION="$(ruby -ryaml -e 'puts YAML.load_file("release/version.yaml").fetch("version").delete_prefix("v")')" \
CPR_GIT_SHA="$(git rev-parse HEAD)" \
CPR_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
docker compose -f deploy/docker-compose.yml build
docker compose --env-file deploy/.env -f deploy/docker-compose.yml build
```
+1 -1
View File
@@ -84,7 +84,7 @@ fingerprint:
admin:
# 管理端登录会话有效期,单位分钟。
session_ttl_minutes: 1440
# 首次启动创建的默认管理员账号。default_password 必须改成至少 12 位的非默认密码。
# 首次启动创建的默认管理员账号。Docker 部署通过 CPR_ADMIN_DEFAULT_PASSWORD 注入密码。
default_username: admin@cpr.local
default_password: ''
+11
View File
@@ -0,0 +1,11 @@
services:
postgres:
ports: !reset []
redis:
ports: !reset []
codex-proxy-rs:
container_name: ${COMPOSE_PROJECT_NAME}-app
ports: !override
- "127.0.0.1:${CPR_SMOKE_PORT:-18080}:8080"
+8 -5
View File
@@ -8,12 +8,12 @@ x-json-logging: &json-logging
services:
postgres:
image: postgres:18-alpine
image: postgres:18-alpine@sha256:9a8afca54e7861fd90fab5fdf4c42477a6b1cb7d293595148e674e0a3181de15
restart: unless-stopped
environment:
POSTGRES_DB: codex_proxy
POSTGRES_USER: codex_proxy
POSTGRES_PASSWORD: ${CPR_POSTGRES_PASSWORD:-codex_proxy}
POSTGRES_PASSWORD: ${CPR_POSTGRES_PASSWORD:?CPR_POSTGRES_PASSWORD must be set in deploy/.env}
volumes:
- postgres-data:/var/lib/postgresql
ports:
@@ -28,7 +28,7 @@ services:
logging: *json-logging
redis:
image: redis:8-alpine
image: redis:8-alpine@sha256:9d317178eceac8454a2284a9e6df2466b93c745529947f0cd42a0fa9609d7005
restart: unless-stopped
command:
[
@@ -36,7 +36,7 @@ services:
"--appendonly",
"yes",
"--requirepass",
"${CPR_REDIS_PASSWORD:-codex_proxy}",
"${CPR_REDIS_PASSWORD:?CPR_REDIS_PASSWORD must be set in deploy/.env}",
]
volumes:
- redis-data:/data
@@ -49,7 +49,7 @@ services:
"redis-cli",
"--no-auth-warning",
"-a",
"${CPR_REDIS_PASSWORD:-codex_proxy}",
"${CPR_REDIS_PASSWORD:?CPR_REDIS_PASSWORD must be set in deploy/.env}",
"ping",
]
interval: 5s
@@ -105,6 +105,9 @@ services:
HOME: /app
XDG_DATA_HOME: /app/data
CPR_CONFIG_FILE: /app/config.yaml
CPR_ADMIN_DEFAULT_PASSWORD: ${CPR_ADMIN_DEFAULT_PASSWORD:?CPR_ADMIN_DEFAULT_PASSWORD must be set in deploy/.env}
CPR_POSTGRES_PASSWORD: ${CPR_POSTGRES_PASSWORD:?CPR_POSTGRES_PASSWORD must be set in deploy/.env}
CPR_REDIS_PASSWORD: ${CPR_REDIS_PASSWORD:?CPR_REDIS_PASSWORD must be set in deploy/.env}
CPR_DEPLOYMENT_MODE: docker
CPR_UPDATE_REPOSITORY: zyycn/codex-proxy-rs
CPR_UPDATE_CHANNEL: stable