mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-07 00:05:49 +00:00
ci: complete release pipeline
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt,clippy
|
||||
|
||||
- name: Cache Rust
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend -> target
|
||||
|
||||
- name: Rust format
|
||||
run: cargo fmt --manifest-path backend/Cargo.toml --check
|
||||
|
||||
- name: Rust clippy
|
||||
run: cargo clippy --manifest-path backend/Cargo.toml --all-targets --all-features --locked -- -D warnings
|
||||
|
||||
- name: Rust tests
|
||||
run: cargo test --manifest-path backend/Cargo.toml --test main --locked
|
||||
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 11.7.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: frontend/pnpm-lock.yaml
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm --dir frontend install --frozen-lockfile
|
||||
|
||||
- name: Frontend build
|
||||
run: pnpm --dir frontend build
|
||||
@@ -4,6 +4,12 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to release, for example v0.1.0'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -19,88 +25,97 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
|
||||
|
||||
- name: Derive version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "build_time=${BUILD_TIME}" >> "$GITHUB_OUTPUT"
|
||||
echo "git_sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Write VERSION file
|
||||
shell: bash
|
||||
run: printf '%s\n' '${{ steps.version.outputs.version }}' > backend/build/VERSION
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Enable pnpm
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare pnpm@11.7.0 --activate
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm --dir frontend install --frozen-lockfile
|
||||
|
||||
- name: Build frontend
|
||||
run: pnpm --dir frontend build
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache Rust
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend -> target
|
||||
|
||||
- name: Build release binaries
|
||||
env:
|
||||
CPR_VERSION: ${{ steps.version.outputs.version }}
|
||||
CPR_GIT_SHA: ${{ steps.version.outputs.git_sha }}
|
||||
CPR_BUILD_TIME: ${{ steps.version.outputs.build_time }}
|
||||
CPR_BUILD_TYPE: release
|
||||
run: cargo build --manifest-path backend/Cargo.toml --release --locked --bin codex-proxy-rs
|
||||
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
|
||||
run: |
|
||||
VERSION="${RELEASE_TAG#v}"
|
||||
BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
GIT_SHA="$(git rev-parse HEAD)"
|
||||
{
|
||||
echo "tag=${RELEASE_TAG}"
|
||||
echo "version=${VERSION}"
|
||||
echo "build_time=${BUILD_TIME}"
|
||||
echo "git_sha=${GIT_SHA}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify Cargo package version
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
PACKAGE_VERSION="$(python3 - <<'PY'
|
||||
import pathlib
|
||||
import tomllib
|
||||
|
||||
manifest = tomllib.loads(pathlib.Path("backend/Cargo.toml").read_text())
|
||||
print(manifest["package"]["version"])
|
||||
PY
|
||||
)"
|
||||
|
||||
if [ "${PACKAGE_VERSION}" != "${RELEASE_VERSION}" ]; then
|
||||
echo "Tag version ${RELEASE_VERSION} does not match backend/Cargo.toml package version ${PACKAGE_VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Package release assets
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
PACKAGE_DIR="dist-package/codex-proxy-rs"
|
||||
mkdir -p "${PACKAGE_DIR}/web"
|
||||
cp backend/target/release/codex-proxy-rs "${PACKAGE_DIR}/"
|
||||
cp -r frontend/dist "${PACKAGE_DIR}/web/dist"
|
||||
tar -czf "codex-proxy-rs_${VERSION}_linux_x86_64.tar.gz" -C dist-package codex-proxy-rs
|
||||
sha256sum "codex-proxy-rs_${VERSION}_linux_x86_64.tar.gz" > checksums.txt
|
||||
rm -rf dist-package checksums.txt
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
docker buildx build \
|
||||
--file deploy/Dockerfile \
|
||||
--target release-asset \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--output type=local,dest=dist-package \
|
||||
--cache-from type=gha,scope=release \
|
||||
--cache-to type=gha,mode=max,scope=release \
|
||||
--build-arg CPR_VERSION="${VERSION}" \
|
||||
--build-arg CPR_GIT_SHA="${{ steps.version.outputs.git_sha }}" \
|
||||
--build-arg CPR_BUILD_TIME="${{ steps.version.outputs.build_time }}" \
|
||||
--build-arg CPR_BUILD_TYPE=release \
|
||||
.
|
||||
|
||||
find dist-package -type f -name 'codex-proxy-rs_*.tar.gz' -exec cp {} . \;
|
||||
test -f "codex-proxy-rs_${VERSION}_linux_amd64.tar.gz"
|
||||
test -f "codex-proxy-rs_${VERSION}_linux_arm64.tar.gz"
|
||||
sha256sum "codex-proxy-rs_${VERSION}_linux_amd64.tar.gz" \
|
||||
"codex-proxy-rs_${VERSION}_linux_arm64.tar.gz" > checksums.txt
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push app image
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: deploy/Dockerfile
|
||||
target: runtime
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
CPR_VERSION=${{ steps.version.outputs.version }}
|
||||
CPR_GIT_SHA=${{ steps.version.outputs.git_sha }}
|
||||
CPR_BUILD_TIME=${{ steps.version.outputs.build_time }}
|
||||
CPR_BUILD_TYPE=release
|
||||
cache-from: type=gha,scope=release
|
||||
cache-to: type=gha,mode=max,scope=release
|
||||
labels: |
|
||||
org.opencontainers.image.title=Codex Proxy RS
|
||||
org.opencontainers.image.version=${{ steps.version.outputs.version }}
|
||||
@@ -109,12 +124,30 @@ jobs:
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ steps.version.outputs.git_sha }}
|
||||
|
||||
- name: Publish GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
generate_release_notes: true
|
||||
body: |
|
||||
## Installation
|
||||
|
||||
**Docker:**
|
||||
```bash
|
||||
# GitHub Container Registry
|
||||
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
||||
```
|
||||
|
||||
**Manual download:**
|
||||
Download the appropriate archive for your platform from the assets below.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [GitHub Repository](https://github.com/${{ github.repository }})
|
||||
- [Installation Guide](https://github.com/${{ github.repository }}/blob/main/deploy/README.md)
|
||||
files: |
|
||||
codex-proxy-rs_${{ steps.version.outputs.version }}_linux_x86_64.tar.gz
|
||||
codex-proxy-rs_${{ steps.version.outputs.version }}_linux_amd64.tar.gz
|
||||
codex-proxy-rs_${{ steps.version.outputs.version }}_linux_arm64.tar.gz
|
||||
checksums.txt
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 3 * * 1'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: security-scan-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
backend-security:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache Rust
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: backend -> target
|
||||
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit --locked
|
||||
|
||||
- name: Run cargo audit
|
||||
working-directory: backend
|
||||
run: cargo audit
|
||||
|
||||
frontend-security:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 11.7.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
cache-dependency-path: frontend/pnpm-lock.yaml
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm --dir frontend install --frozen-lockfile
|
||||
|
||||
- name: Run pnpm audit
|
||||
run: pnpm --dir frontend audit --prod --audit-level=high
|
||||
@@ -103,24 +103,7 @@ logging:
|
||||
|
||||
## 架构
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Client["客户端 / SDK"] -->|OpenAI API| Proxy["/v1/*"]
|
||||
Browser["浏览器"] -->|Web| Admin["/api/admin/*"]
|
||||
|
||||
subgraph Gateway["codex-proxy-rs"]
|
||||
Proxy --> Auth["API Key 鉴权"]
|
||||
Auth --> Dispatch["调度层<br/>选账号 · 限并发 · 亲和性"]
|
||||
Dispatch --> Transport["上游传输<br/>HTTP SSE / WebSocket"]
|
||||
Dispatch --> Pool["账号池<br/>内存状态 + SQLite"]
|
||||
Admin --> SQLite[("SQLite")]
|
||||
Pool --> SQLite
|
||||
end
|
||||
|
||||
Transport --> Upstream["ChatGPT / Codex API"]
|
||||
Proxy -.->|静态资源| SPA["Vue 3 管理端"]
|
||||
Admin -.-> SPA
|
||||
```
|
||||
Codex Proxy RS 是单主服务架构:Rust 后端同时承载 OpenAI 兼容代理、管理端 API、SQLite 持久化、前端静态资源托管和在线更新。详细模块边界、请求链路、发布和更新流程见 [架构说明](docs/architecture.md)。
|
||||
|
||||
## 项目结构
|
||||
|
||||
@@ -131,7 +114,7 @@ backend/
|
||||
build/ 构建脚本
|
||||
frontend/ Vue 3 管理控制台
|
||||
deploy/ Dockerfile、Compose、示例配置
|
||||
docs/ 设计与迁移文档
|
||||
docs/ 架构与维护文档
|
||||
```
|
||||
|
||||
## 开发
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
0.1.0
|
||||
+4
-16
@@ -1,19 +1,14 @@
|
||||
use std::{env, fs};
|
||||
|
||||
const VERSION_FILE: &str = "build/VERSION";
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed={VERSION_FILE}");
|
||||
println!("cargo:rerun-if-env-changed=CPR_VERSION");
|
||||
println!("cargo:rerun-if-env-changed=CPR_GIT_SHA");
|
||||
println!("cargo:rerun-if-env-changed=CPR_BUILD_TIME");
|
||||
println!("cargo:rerun-if-env-changed=CPR_BUILD_TYPE");
|
||||
|
||||
let version = version_env_value("CPR_VERSION")
|
||||
.or_else(version_file)
|
||||
.unwrap_or_else(|| {
|
||||
env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0-dev".to_string())
|
||||
});
|
||||
let version = version_env_value("CPR_VERSION").unwrap_or_else(|| {
|
||||
env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0-dev".to_string())
|
||||
});
|
||||
let git_sha = env_value("CPR_GIT_SHA").unwrap_or_else(|| "unknown".to_string());
|
||||
let build_time = env_value("CPR_BUILD_TIME").unwrap_or_else(|| "unknown".to_string());
|
||||
let build_type = env_value("CPR_BUILD_TYPE").unwrap_or_else(|| "source".to_string());
|
||||
@@ -34,10 +29,3 @@ fn env_value(key: &str) -> Option<String> {
|
||||
fn version_env_value(key: &str) -> Option<String> {
|
||||
env_value(key).map(|value| value.trim_start_matches('v').to_string())
|
||||
}
|
||||
|
||||
fn version_file() -> Option<String> {
|
||||
fs::read_to_string(VERSION_FILE)
|
||||
.ok()
|
||||
.map(|value| value.trim().trim_start_matches('v').to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
@@ -614,65 +614,6 @@ fn health_tone(bucket: UsageWindow, success_rate: f64, is_future: bool) -> char
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{health_tone, UsageWindow};
|
||||
|
||||
#[test]
|
||||
fn health_tone_should_classify_availability_levels() {
|
||||
assert_eq!(health_tone(UsageWindow::default(), 0.0, true), '0');
|
||||
assert_eq!(health_tone(UsageWindow::default(), 0.0, false), '1');
|
||||
assert_eq!(
|
||||
health_tone(
|
||||
UsageWindow {
|
||||
requests: 2,
|
||||
errors: 2,
|
||||
..UsageWindow::default()
|
||||
},
|
||||
0.0,
|
||||
false,
|
||||
),
|
||||
'2'
|
||||
);
|
||||
assert_eq!(
|
||||
health_tone(
|
||||
UsageWindow {
|
||||
requests: 10,
|
||||
errors: 2,
|
||||
..UsageWindow::default()
|
||||
},
|
||||
80.0,
|
||||
false,
|
||||
),
|
||||
'3'
|
||||
);
|
||||
assert_eq!(
|
||||
health_tone(
|
||||
UsageWindow {
|
||||
requests: 2,
|
||||
errors: 0,
|
||||
..UsageWindow::default()
|
||||
},
|
||||
100.0,
|
||||
false,
|
||||
),
|
||||
'4'
|
||||
);
|
||||
assert_eq!(
|
||||
health_tone(
|
||||
UsageWindow {
|
||||
requests: 3,
|
||||
errors: 0,
|
||||
..UsageWindow::default()
|
||||
},
|
||||
100.0,
|
||||
false,
|
||||
),
|
||||
'5'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn trend_summary(
|
||||
kind: DashboardTrendKind,
|
||||
points: &[DashboardTrendPointData],
|
||||
|
||||
@@ -815,8 +815,8 @@ fn select_release_archive<'a>(
|
||||
release: &'a GitHubRelease,
|
||||
version: &str,
|
||||
) -> Result<&'a GitHubAsset, AdminError> {
|
||||
let os = env::consts::OS;
|
||||
let arch = env::consts::ARCH;
|
||||
let os_aliases = platform_os_aliases();
|
||||
let arch_aliases = platform_arch_aliases();
|
||||
let normalized = normalize_version_tag(version);
|
||||
release
|
||||
.assets
|
||||
@@ -825,23 +825,47 @@ fn select_release_archive<'a>(
|
||||
let name = asset.name.as_str();
|
||||
name.contains(APP_BINARY_NAME)
|
||||
&& name.contains(&normalized)
|
||||
&& name.contains(os)
|
||||
&& name.contains(arch)
|
||||
&& asset_matches_current_platform(name, os_aliases, arch_aliases)
|
||||
&& !name.ends_with(".txt")
|
||||
})
|
||||
.or_else(|| {
|
||||
release.assets.iter().find(|asset| {
|
||||
let name = asset.name.as_str();
|
||||
name.contains(os) && name.contains(arch) && !name.ends_with(".txt")
|
||||
asset_matches_current_platform(name, os_aliases, arch_aliases)
|
||||
&& !name.ends_with(".txt")
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
AdminError::conflict(format!(
|
||||
"No compatible release archive found for {os}/{arch}"
|
||||
"No compatible release archive found for {}/{}",
|
||||
env::consts::OS,
|
||||
env::consts::ARCH
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn platform_os_aliases() -> &'static [&'static str] {
|
||||
match env::consts::OS {
|
||||
"macos" => &["macos", "darwin"],
|
||||
"windows" => &["windows", "win32"],
|
||||
"linux" => &["linux"],
|
||||
_ => &[env::consts::OS],
|
||||
}
|
||||
}
|
||||
|
||||
fn platform_arch_aliases() -> &'static [&'static str] {
|
||||
match env::consts::ARCH {
|
||||
"x86_64" => &["x86_64", "amd64"],
|
||||
"aarch64" => &["aarch64", "arm64"],
|
||||
_ => &[env::consts::ARCH],
|
||||
}
|
||||
}
|
||||
|
||||
fn asset_matches_current_platform(name: &str, os_aliases: &[&str], arch_aliases: &[&str]) -> bool {
|
||||
os_aliases.iter().any(|os| name.contains(os))
|
||||
&& arch_aliases.iter().any(|arch| name.contains(arch))
|
||||
}
|
||||
|
||||
async fn download_file(
|
||||
url: &str,
|
||||
dest: &Path,
|
||||
|
||||
@@ -364,7 +364,7 @@ async fn mount_latest_release_with_archive(
|
||||
let archive_name = format!(
|
||||
"codex-proxy-rs_{version}_{}_{}.tar.gz",
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
release_arch()
|
||||
);
|
||||
let archive = release_archive(binary, index);
|
||||
let checksum = format!(
|
||||
@@ -406,6 +406,14 @@ async fn mount_latest_release_with_archive(
|
||||
.await;
|
||||
}
|
||||
|
||||
fn release_arch() -> &'static str {
|
||||
match std::env::consts::ARCH {
|
||||
"x86_64" => "amd64",
|
||||
"aarch64" => "arm64",
|
||||
arch => arch,
|
||||
}
|
||||
}
|
||||
|
||||
fn release_archive(binary: &str, index: &str) -> Vec<u8> {
|
||||
let mut data = Vec::new();
|
||||
{
|
||||
|
||||
+19
-1
@@ -11,10 +11,12 @@ RUN pnpm build
|
||||
FROM rust:1.95-bookworm AS backend-builder
|
||||
|
||||
WORKDIR /app/backend
|
||||
ARG TARGETARCH
|
||||
ARG CPR_VERSION=
|
||||
ARG CPR_GIT_SHA=unknown
|
||||
ARG CPR_BUILD_TIME=unknown
|
||||
ARG CPR_BUILD_TYPE=release
|
||||
ENV CARGO_TARGET_DIR=/app/backend/target/${TARGETARCH}
|
||||
ENV CPR_VERSION=${CPR_VERSION}
|
||||
ENV CPR_GIT_SHA=${CPR_GIT_SHA}
|
||||
ENV CPR_BUILD_TIME=${CPR_BUILD_TIME}
|
||||
@@ -27,7 +29,23 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
--mount=type=cache,target=/app/backend/target \
|
||||
cargo build --release --locked --bin codex-proxy-rs \
|
||||
&& cp /app/backend/target/release/codex-proxy-rs /tmp/codex-proxy-rs
|
||||
&& cp "${CARGO_TARGET_DIR}/release/codex-proxy-rs" /tmp/codex-proxy-rs
|
||||
|
||||
FROM backend-builder AS release-asset-builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /out
|
||||
RUN mkdir -p /tmp/release/codex-proxy-rs/web \
|
||||
&& cp /tmp/codex-proxy-rs /tmp/release/codex-proxy-rs/ \
|
||||
&& cp -r /app/backend/web/dist /tmp/release/codex-proxy-rs/web/dist \
|
||||
&& tar -czf "/out/codex-proxy-rs_${CPR_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" \
|
||||
-C /tmp/release codex-proxy-rs
|
||||
|
||||
FROM scratch AS release-asset
|
||||
|
||||
COPY --from=release-asset-builder /out /
|
||||
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
# Codex Proxy RS 架构说明
|
||||
|
||||
## 总览
|
||||
|
||||
Codex Proxy RS 是一个单主服务应用:Rust 后端提供 OpenAI 兼容代理、管理端 API、SQLite 持久化、静态前端托管和在线更新能力;Vue 管理端作为 SPA 由同一个后端进程托管。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Client["客户端 / SDK"] -->|"OpenAI API /v1/*"| Http["Axum HTTP Router"]
|
||||
Browser["浏览器"] -->|"管理端 SPA"| Web["web 静态资源托管"]
|
||||
Browser -->|"/api/admin/*"| Http
|
||||
|
||||
subgraph App["codex-proxy-rs 单主进程"]
|
||||
Http --> Proxy["proxy<br/>API Key 鉴权<br/>OpenAI 兼容协议"]
|
||||
Http --> Admin["admin<br/>账号 / Key / 设置 / 监控 / 系统更新"]
|
||||
Http --> Web
|
||||
|
||||
Proxy --> Dispatch["dispatch<br/>账号选择 / 限流 / 亲和性 / 失败回退"]
|
||||
Dispatch --> Protocol["upstream protocol<br/>请求转换 / SSE 转换"]
|
||||
Protocol --> Transport["upstream transport<br/>HTTP SSE / WebSocket"]
|
||||
|
||||
Admin --> Runtime["runtime services<br/>账号池 / 后台任务 / 更新状态"]
|
||||
Dispatch --> Runtime
|
||||
Runtime --> Store[("SQLite<br/>账号 / 会话 / 使用记录 / 设置")]
|
||||
Admin --> Store
|
||||
end
|
||||
|
||||
Transport --> Upstream["ChatGPT / Codex Upstream"]
|
||||
Web --> Frontend["frontend/dist<br/>Vue 3 管理端"]
|
||||
|
||||
Release["GitHub Release<br/>tar.gz + checksums"] --> Update["在线更新<br/>下载 / 校验 / 替换 / 重启"]
|
||||
Update --> Admin
|
||||
GHCR["GHCR<br/>linux/amd64 + linux/arm64"] --> Docker["Docker Compose<br/>restart: unless-stopped"]
|
||||
```
|
||||
|
||||
## 仓库边界
|
||||
|
||||
- `backend/`:Rust/Axum 后端、SQLite schema、运行时服务、集成测试和 Cargo 构建脚本。
|
||||
- `frontend/`:Vue 3 管理端,使用 Vite、Tailwind v4、Pinia、Vue Router 和 ECharts。
|
||||
- `deploy/`:Dockerfile、Compose 文件、Docker ignore 文件和部署样例配置。
|
||||
- `docs/`:长期维护文档,只记录当前真实架构和约定,不保留迁移过程作为开发约束。
|
||||
- `skills/`:项目本地 Codex skill。
|
||||
|
||||
## 后端结构
|
||||
|
||||
后端入口是 `backend/src/main.rs`,公共库入口是 `backend/src/lib.rs`。运行时由 `runtime::bootstrap` 初始化,服务集合在 `runtime::services` 构造,并通过 `AppState` 注入到路由。
|
||||
|
||||
主要模块:
|
||||
|
||||
- `admin/`:管理端 API,包括认证、账号、API Key、监控、设置和系统更新。
|
||||
- `proxy/`:OpenAI 兼容代理入口、鉴权、请求转换和账号调度。
|
||||
- `upstream/`:ChatGPT/Codex 上游协议、传输层、账号模型、token 刷新、quota 刷新和 fingerprint。
|
||||
- `config/`:配置加载、运行时设置和配置类型。
|
||||
- `infra/`:数据库、日志、时间、格式化、JSON 和 schema。
|
||||
- `http/`:HTTP router、中间件和统一请求上下文。
|
||||
- `web/`:前端静态资源和 SPA fallback 托管。
|
||||
|
||||
测试只放在 `backend/tests/`,不要在 `backend/src/` 中新增测试模块。
|
||||
|
||||
## 数据和运行时
|
||||
|
||||
后端以 SQLite 作为主要持久化存储。默认运行时数据位于 `.runtime/`,Docker 部署时通过 Compose 挂载到 `/app/data` 和 `/app/logs`。
|
||||
|
||||
运行时状态分两类:
|
||||
|
||||
- 持久化状态:账号、cookie、API Key、session、使用记录、时间桶、fingerprint、运行时设置等。
|
||||
- 内存状态:账号池、调度状态、运行中任务、SSE 更新日志广播、缓存的 release 检查结果等。
|
||||
|
||||
运行时设置通过管理端 API 修改后写入数据库,并同步到内存服务。
|
||||
|
||||
## 代理请求链路
|
||||
|
||||
客户端请求进入 `/v1/*`:
|
||||
|
||||
1. `proxy/auth` 校验客户端 API Key。
|
||||
2. `proxy/openai` 解析 OpenAI 兼容请求。
|
||||
3. `proxy/dispatch` 选择账号、处理限流、亲和性和失败回退。
|
||||
4. `upstream/protocol` 转换 Codex/ChatGPT 上游协议。
|
||||
5. `upstream/transport` 使用 HTTP SSE 或 WebSocket 访问上游。
|
||||
6. 响应被转换回 OpenAI 兼容格式,并记录使用数据。
|
||||
|
||||
账号运行时状态由 SQLite 快照恢复,并在请求、刷新、quota 检查和失败回退时更新。
|
||||
|
||||
## 管理端
|
||||
|
||||
管理端 API 位于 `/api/admin/*`,使用 session cookie 保护。前端入口由后端托管,不单独部署 Node 服务。
|
||||
|
||||
前端结构:
|
||||
|
||||
- `frontend/src/api/modules`:管理端 API 客户端。
|
||||
- `frontend/src/components/base`:基础 UI 组件。
|
||||
- `frontend/src/layout`:主布局、侧边栏、系统更新入口和更新弹窗。
|
||||
- `frontend/src/views`:各业务页面。
|
||||
- `frontend/src/stores`:Pinia 状态。
|
||||
- `frontend/src/styles/tokens.css`:亮色/暗色主题 token。
|
||||
|
||||
前端只展示后端返回的版本、状态和更新日志,不在前端做状态文案映射。
|
||||
|
||||
## 版本和发布
|
||||
|
||||
版本只有两个来源层级:
|
||||
|
||||
- 源码默认版本:`backend/Cargo.toml` 的 `[package].version`。
|
||||
- 发布版本:Git tag 去掉 `v` 前缀后得到的版本。
|
||||
|
||||
发布 workflow 会校验 tag 版本必须等于 `backend/Cargo.toml` 的 package version。发布构建时通过 `CPR_VERSION`、`CPR_GIT_SHA`、`CPR_BUILD_TIME` 和 `CPR_BUILD_TYPE=release` 注入编译期元数据。
|
||||
|
||||
`backend/build/build.rs` 负责把这些值写入二进制。没有 `CPR_VERSION` 时,版本回落到 Cargo 的 `CARGO_PKG_VERSION`。不要重新引入独立 `VERSION` 文件。
|
||||
|
||||
GitHub Release 产物:
|
||||
|
||||
```text
|
||||
codex-proxy-rs_<version>_linux_amd64.tar.gz
|
||||
codex-proxy-rs_<version>_linux_arm64.tar.gz
|
||||
checksums.txt
|
||||
```
|
||||
|
||||
GHCR 镜像发布为 `linux/amd64` 和 `linux/arm64` 多平台 manifest。
|
||||
|
||||
## 在线更新
|
||||
|
||||
在线更新由主服务自己处理,不依赖独立 updater sidecar。核心接口:
|
||||
|
||||
- `GET /api/admin/system/version`
|
||||
- `GET /api/admin/system/check-updates`
|
||||
- `GET /api/admin/system/update-events`
|
||||
- `GET /api/admin/system/update-status`
|
||||
- `POST /api/admin/system/update`
|
||||
- `POST /api/admin/system/restart`
|
||||
|
||||
更新流程:
|
||||
|
||||
1. 后端检查 GitHub latest release。
|
||||
2. 按当前 OS/arch 选择匹配 asset,兼容 `amd64/x86_64` 和 `arm64/aarch64` 命名。
|
||||
3. 下载 release tar 包并用 `checksums.txt` 校验。
|
||||
4. 解压二进制和 `web/dist`。
|
||||
5. 替换当前二进制和前端静态资源。
|
||||
6. 前端通过 SSE 实时展示后端推送的中文更新日志。
|
||||
7. Docker 部署依靠 `restart: unless-stopped` 在自重启后拉起新进程。
|
||||
|
||||
替换逻辑必须支持跨文件系统 fallback,因为容器文件系统里 `rename` 可能遇到 `Invalid cross-device link`。
|
||||
|
||||
## Docker 部署
|
||||
|
||||
Docker 构建入口是 `deploy/Dockerfile`,上下文保持仓库根目录。
|
||||
|
||||
构建阶段:
|
||||
|
||||
- `frontend-builder`:构建 Vue SPA。
|
||||
- `backend-builder`:构建 Rust release 二进制,并复制前端 dist。
|
||||
- `release-asset`:导出 GitHub Release tar 包。
|
||||
- `runtime`:最小运行镜像,基于 `debian:bookworm-slim`,只包含 CA、后端二进制和 `web/dist`。
|
||||
|
||||
Compose 文件是 `deploy/docker-compose.yml`。示例配置只保留 `deploy/config.example.yaml`,用户复制为 `deploy/config.yaml` 后部署。
|
||||
|
||||
## CI 和安全扫描
|
||||
|
||||
常规 CI 位于 `.github/workflows/ci.yml`:
|
||||
|
||||
- Rust fmt、clippy、集成测试。
|
||||
- 前端依赖安装和 build。
|
||||
- Compose 配置校验。
|
||||
|
||||
安全扫描位于 `.github/workflows/security-scan.yml`:
|
||||
|
||||
- `cargo audit` 扫描 Rust 依赖。
|
||||
- `pnpm audit --prod --audit-level=high` 扫描前端生产依赖。
|
||||
- 每周定时运行,同时覆盖 push 和 pull request。
|
||||
|
||||
发布流程位于 `.github/workflows/release.yml`,只由 `v*` tag 或手动指定 tag 触发。
|
||||
@@ -1,237 +0,0 @@
|
||||
# Project Layout Restructure
|
||||
|
||||
## 背景
|
||||
|
||||
项目目录已按 Sub2API 的边界收敛:
|
||||
|
||||
- `backend/` 承载后端应用、后端构建元信息和后端测试。
|
||||
- `frontend/` 承载前端应用。
|
||||
- `deploy/` 承载 Docker、Compose、部署脚本和部署示例配置。
|
||||
|
||||
根目录只保留仓库级入口和文档,不再承载后端源码、前端源码或具体部署实现。
|
||||
|
||||
## 完成后结构
|
||||
|
||||
```text
|
||||
codex-proxy-rs/
|
||||
├── backend/
|
||||
│ ├── Cargo.toml
|
||||
│ ├── Cargo.lock
|
||||
│ ├── build/
|
||||
│ │ ├── build.rs
|
||||
│ │ └── VERSION
|
||||
│ ├── rust-toolchain.toml
|
||||
│ ├── src/
|
||||
│ └── tests/
|
||||
├── frontend/
|
||||
│ ├── package.json
|
||||
│ ├── pnpm-lock.yaml
|
||||
│ ├── pnpm-workspace.yaml
|
||||
│ ├── vite.config.ts
|
||||
│ └── src/
|
||||
├── deploy/
|
||||
│ ├── Dockerfile
|
||||
│ ├── docker-compose.yml
|
||||
│ ├── config.example.yaml
|
||||
│ └── README.md
|
||||
├── docs/
|
||||
├── README.md
|
||||
└── .github/
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `backend/build/VERSION` 是唯一的源码默认版本文件。它对应 Sub2API 的 `backend/cmd/server/VERSION`,但本项目的 Rust 入口仍保留在 `backend/src/main.rs`,所以版本元信息归入 `backend/build/`。
|
||||
- `backend/build/build.rs` 读取 `CPR_VERSION`,否则读取 `backend/build/VERSION`,最后才回落到 `CARGO_PKG_VERSION`。
|
||||
- `frontend/` 替代原 `web/`。前端只负责展示后端返回的版本、构建类型、部署类型和更新进度。
|
||||
- `deploy/` 替代根目录里的部署文件归属。根目录只保留仓库级入口和文档,不承载具体部署实现。
|
||||
- 测试代码继续放在测试目录,禁止放入 `backend/src`。
|
||||
|
||||
## 路径映射
|
||||
|
||||
| 原路径 | 新路径 | 备注 |
|
||||
| --- | --- | --- |
|
||||
| `src/` | `backend/src/` | Rust 后端源码。 |
|
||||
| `tests/` | `backend/tests/` | Rust 集成测试,仍在测试目录,不进入源码目录。 |
|
||||
| `Cargo.toml` | `backend/Cargo.toml` | 后端 package manifest。 |
|
||||
| `Cargo.lock` | `backend/Cargo.lock` | 后端锁文件,随后端移动。 |
|
||||
| `build.rs` | `backend/build/build.rs` | 后端构建脚本。 |
|
||||
| `VERSION` | `backend/build/VERSION` | 后端应用版本源。 |
|
||||
| `rust-toolchain.toml` | `backend/rust-toolchain.toml` | 后端工具链配置。 |
|
||||
| `web/` | `frontend/` | 前端应用。 |
|
||||
| `Dockerfile` | `deploy/Dockerfile` | 生产镜像构建入口。 |
|
||||
| `docker-compose.yml` | `deploy/docker-compose.yml` | Docker 部署入口。 |
|
||||
| `config.yaml` | `deploy/config.example.yaml` | 根目录不保留本地运行配置。 |
|
||||
|
||||
## Cargo 组织方式
|
||||
|
||||
优先采用 Sub2API 风格:后端是自包含子项目。
|
||||
|
||||
```text
|
||||
backend/
|
||||
├── Cargo.toml
|
||||
├── Cargo.lock
|
||||
├── build/
|
||||
│ ├── build.rs
|
||||
│ └── VERSION
|
||||
├── src/
|
||||
└── tests/
|
||||
```
|
||||
|
||||
后续命令从:
|
||||
|
||||
```bash
|
||||
cargo test --test main admin::system --locked
|
||||
cargo clippy --all-targets --all-features --locked -- -D warnings
|
||||
```
|
||||
|
||||
调整为:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path backend/Cargo.toml --test main admin::system --locked
|
||||
cargo clippy --manifest-path backend/Cargo.toml --all-targets --all-features --locked -- -D warnings
|
||||
```
|
||||
|
||||
如果后续需要根目录统一调度,可以再加一个很薄的 root workspace `Cargo.toml`,但不要让根目录重新成为后端版本源。
|
||||
|
||||
根目录 `target/` 不是项目构建目标目录。迁移后后端构建产物位于 `backend/target/`;如果本地编辑器或 rust-analyzer 在根目录生成 `target/flycheck*`,它只是本地检查临时产物,可以直接删除,且已被 `.gitignore` 忽略。
|
||||
|
||||
## 前端迁移点
|
||||
|
||||
`web/` 已迁移到 `frontend/`,同步点:
|
||||
|
||||
- `.github/workflows/release.yml` 中所有 `pnpm --dir web ...` 改为 `pnpm --dir frontend ...`。
|
||||
- Dockerfile 中所有 `web/package.json`、`web/pnpm-lock.yaml`、`web/dist` 改为 `frontend/...`。
|
||||
- README、部署文档和开发命令里的 `web` 改为 `frontend`。
|
||||
- 前端 API 类型和更新弹窗不做业务重写,只做路径迁移。
|
||||
|
||||
## 部署迁移点
|
||||
|
||||
`deploy/` 负责部署相关文件:
|
||||
|
||||
- `deploy/Dockerfile`
|
||||
- `deploy/docker-compose.yml`
|
||||
- `deploy/config.example.yaml`
|
||||
- `deploy/README.md`
|
||||
|
||||
Docker 构建上下文仍建议使用仓库根目录:
|
||||
|
||||
```bash
|
||||
docker build -f deploy/Dockerfile -t codex-proxy-rs .
|
||||
```
|
||||
|
||||
这样 Dockerfile 可以同时访问 `backend/` 和 `frontend/`,避免把构建上下文拆散。
|
||||
|
||||
Compose 文件迁移到 `deploy/` 后,默认挂载路径随 Compose 文件落在 `deploy/` 目录,保持部署文件和部署状态集中;需要接入外部配置或数据目录时,通过环境变量覆盖:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
codex-proxy-rs:
|
||||
volumes:
|
||||
- ${CPR_CONFIG_FILE:-./config.yaml}:/app/config.yaml:ro
|
||||
- ${CPR_DATA_DIR:-./data}:/app/data
|
||||
- ${CPR_LOG_DIR:-./logs}:/app/logs
|
||||
```
|
||||
|
||||
使用 `deploy/config.example.yaml` 作为样例,运行时配置由用户复制为 `deploy/config.yaml` 或通过 `CPR_CONFIG_FILE` 挂载提供。
|
||||
|
||||
## 发布链路调整
|
||||
|
||||
发布版本必须只有一个来源:
|
||||
|
||||
1. Git tag,例如 `v0.1.1`。
|
||||
2. CI 去掉前缀 `v`,得到 `0.1.1`。
|
||||
3. CI 写入 `backend/build/VERSION`。
|
||||
4. CI 构建后端时传入:
|
||||
- `CPR_VERSION=0.1.1`
|
||||
- `CPR_GIT_SHA=<sha>`
|
||||
- `CPR_BUILD_TIME=<utc>`
|
||||
- `CPR_BUILD_TYPE=release`
|
||||
5. `backend/build/build.rs` 将这些值注入二进制。
|
||||
6. 后端 `/api/admin/system/version` 和 `/api/admin/system/check-updates` 使用二进制内的版本值。
|
||||
7. 前端只展示后端返回的数据,不做版本映射。
|
||||
|
||||
需要同步修改:
|
||||
|
||||
- `.github/workflows/release.yml`
|
||||
- `deploy/Dockerfile`
|
||||
- `backend/build/build.rs`
|
||||
- `backend/src/admin/system/routes.rs`
|
||||
- `backend/tests/admin/system/mod.rs`
|
||||
|
||||
## Dockerfile 调整
|
||||
|
||||
目标 Dockerfile 阶段:
|
||||
|
||||
```dockerfile
|
||||
FROM node:24-bookworm-slim AS frontend-builder
|
||||
WORKDIR /app/frontend
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml frontend/pnpm-workspace.yaml ./
|
||||
RUN corepack enable && pnpm install --frozen-lockfile
|
||||
COPY frontend ./
|
||||
RUN pnpm build
|
||||
|
||||
FROM rust:1.95-bookworm AS backend-builder
|
||||
WORKDIR /app/backend
|
||||
COPY backend/Cargo.toml backend/Cargo.lock ./
|
||||
COPY backend/build ./build
|
||||
COPY backend/src ./src
|
||||
COPY --from=frontend-builder /app/frontend/dist ./web/dist
|
||||
RUN cargo build --release --locked --bin codex-proxy-rs
|
||||
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
WORKDIR /app
|
||||
ENV CPR_DEPLOYMENT_MODE=docker
|
||||
COPY --from=backend-builder /app/backend/target/release/codex-proxy-rs /usr/local/bin/codex-proxy-rs
|
||||
COPY --from=frontend-builder /app/frontend/dist ./web/dist
|
||||
CMD ["codex-proxy-rs"]
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- Docker runtime 镜像不设置 `CPR_VERSION`,避免运行时环境变量成为第二版本源。
|
||||
- Docker runtime 仍只包含主服务,不恢复 updater sidecar。
|
||||
- `deploy/Dockerfile` 的构建上下文应保持仓库根目录。
|
||||
|
||||
## 迁移记录
|
||||
|
||||
本次迁移执行项:
|
||||
|
||||
1. 新建 `backend/`,用 `git mv` 移动 `src/`、`tests/`、`Cargo.toml`、`Cargo.lock`、`rust-toolchain.toml`,并将 `build.rs`、`VERSION` 归入 `backend/build/`。
|
||||
2. 新建 `frontend/`,用 `git mv web frontend`。
|
||||
3. 新建 `deploy/`,用 `git mv Dockerfile deploy/Dockerfile`、`git mv docker-compose.yml deploy/docker-compose.yml`,并移动部署样例配置。
|
||||
4. 修改 `.github/workflows/release.yml` 的工作目录、构建路径、打包路径和 Dockerfile 路径。
|
||||
5. 修改 `deploy/Dockerfile` 的 `COPY` 路径。
|
||||
6. 修改 `.dockerignore` 和 `.gitignore`,使用 `frontend/node_modules`、`frontend/dist`、`backend/target`。
|
||||
7. 修改文档和 README 中的开发、测试、构建命令。
|
||||
|
||||
## 验收命令
|
||||
|
||||
迁移完成后至少运行:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path backend/Cargo.toml --test main admin::system --locked
|
||||
cargo clippy --manifest-path backend/Cargo.toml --all-targets --all-features --locked -- -D warnings
|
||||
pnpm --dir frontend build
|
||||
docker build -f deploy/Dockerfile -t codex-proxy-rs:latest .
|
||||
docker compose -f deploy/docker-compose.yml config
|
||||
git diff --check
|
||||
```
|
||||
|
||||
如果保留 root workspace,则还需要确认:
|
||||
|
||||
```bash
|
||||
cargo metadata --format-version 1
|
||||
```
|
||||
|
||||
输出中不能重新出现旧的 updater binary,也不能把根 `VERSION` 当成版本源。
|
||||
|
||||
## 完成标准
|
||||
|
||||
- 顶层不再有 `src/` 和 `web/`。
|
||||
- 后端源码、测试和版本源都归属 `backend/`。
|
||||
- 前端应用归属 `frontend/`。
|
||||
- Dockerfile、Compose 和部署样例归属 `deploy/`。
|
||||
- `backend/build/VERSION` 是唯一源码默认版本文件。
|
||||
- 发布 tag、release asset、Docker image label、后端 `/version`、前端更新弹窗显示同一个版本。
|
||||
- Docker 部署仍是单主服务模式,不恢复 updater sidecar。
|
||||
@@ -14,11 +14,11 @@
|
||||
## 仓库结构
|
||||
|
||||
- `backend/`:Rust 1.95 Axum 后端,包含 SQLite 存储、代理调度、管理端 API、web 静态资源托管、在线更新逻辑。
|
||||
- `backend/build/`:构建脚本和 `VERSION`;release workflow 会写入 `backend/build/VERSION`。
|
||||
- `backend/build/`:Cargo 构建脚本,负责把版本、Git SHA、构建时间写入编译期环境变量。
|
||||
- `backend/tests/`:集成测试和 fixtures。测试代码只放这里,禁止放进 `backend/src`。
|
||||
- `frontend/`:Vue 3 管理端 SPA,使用 Vite 8、Tailwind v4、Pinia、Vue Router、Axios、lucide icons、ECharts。
|
||||
- `deploy/`:Dockerfile、Dockerfile 专用 ignore 文件、Compose 文件、`config.example.yaml`。
|
||||
- `docs/`:只在用户要求时放设计说明或迁移文档。
|
||||
- `docs/`:只放长期维护文档,例如架构说明;不要保留过程型迁移文档作为开发约束。
|
||||
- `skills/`:项目本地 Codex skills。
|
||||
|
||||
## 后端规范
|
||||
@@ -89,13 +89,15 @@ docker compose -f deploy/docker-compose.yml up -d
|
||||
|
||||
- Release workflow:`.github/workflows/release.yml`。
|
||||
- `v*` tag 触发发布。
|
||||
- release 时版本来源是去掉 `v` 前缀的 Git tag;workflow 会写入 `backend/build/VERSION`。
|
||||
- release 时版本来源是去掉 `v` 前缀的 Git tag;workflow 通过 `CPR_VERSION` build arg 写入编译期元数据。
|
||||
- 源码默认版本使用 `backend/Cargo.toml` 的 `[package].version`;不要重新引入独立 `VERSION` 文件。
|
||||
- 运行时版本接口展示编译期元数据:`CPR_VERSION`、`CPR_GIT_SHA`、`CPR_BUILD_TIME`。
|
||||
- 当前发布的 Docker 平台是 `linux/amd64`,因为 release asset 目前只有 `linux_x86_64`。
|
||||
- 当前发布的 Docker 镜像平台是 `linux/amd64` 和 `linux/arm64`。
|
||||
- Release asset 命名必须匹配在线更新选择器:
|
||||
|
||||
```text
|
||||
codex-proxy-rs_<version>_linux_x86_64.tar.gz
|
||||
codex-proxy-rs_<version>_linux_amd64.tar.gz
|
||||
codex-proxy-rs_<version>_linux_arm64.tar.gz
|
||||
checksums.txt
|
||||
```
|
||||
|
||||
@@ -173,4 +175,4 @@ docker compose -f deploy/docker-compose.yml config
|
||||
- README、docs、发布和更新说明要和真实命令、真实行为一致。
|
||||
- UI 保持当前安静、偏运维控制台的风格;避免营销式布局、卡片套卡片、图标过大、信息重复。
|
||||
- 版本统一从发布 tag 到编译期元数据再到前端显示;不要引入第二个运行时版本源。
|
||||
- 历史迁移文档可能包含阶段性方案;实现和 README、workflow、Dockerfile 不一致时,以当前代码和真实命令为准。
|
||||
- 架构说明、README、workflow、Dockerfile 需要和当前代码和真实命令保持一致。
|
||||
|
||||
Reference in New Issue
Block a user