commit 71883b0c271923168de3e72fd5fea194eb94e991 Author: slh Date: Fri Jul 17 01:40:25 2026 +0000 bootstrap infra source of truth diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98dfd73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Runtime state and credentials never enter Git. +.env +.env.* +!*.example +gitea/ +postgres/data/ +postgres/secrets/ +postgres/migration/ +postgres/.infra-postgres-state +litellm/chatgpt/ +litellm/xai_oauth/ +litellm/migration/ +litellm/.infra-litellm-state +*/migration/ +*.snapshot diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7b87a6 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Infra Compose + +## Gitea 真相源 + +- 本目录的最终代码与部署历史以 Gitea `slh/infra` 的 `main` 分支为唯一真相源。 +- 生产部署只能来自 Gitea;GitHub 只能作为云端开发工作区,不能覆盖 Gitea Main。 +- Git 仅跟踪 Compose、README、无凭据服务配置和幂等初始化脚本。数据库、OAuth、Secrets、日志、迁移快照及运行数据永不入库。 +- Compose 的唯一入口是 `docker-compose.yaml`。 + +## Compose 真相源规范 + +- 每个项目唯一、最终的 Compose 真相文件必须命名为 `docker-compose.yaml`。 +- `compose.yaml`、`docker-compose.yml`、服务分片和迁移候选只能临时存在;任务完成前必须合并回 `docker-compose.yaml`。 +- 同一项目不得保留第二个可执行生产入口。历史材料只能进入明确的 `migration/` 或 `rollback/` 目录,并使用不会被 Docker Compose 自动识别的快照文件名。 +- 收尾必须执行 `docker compose -f docker-compose.yaml config -q`,并核对 Compose project、service、容器名和运行归属。 + +共享基础设施的唯一 Compose 入口。迁移按服务逐个进行;尚未迁移的服务继续由原项目管理。 + +当前服务: + +- `gitea`:NAS 内部唯一 Git 真相源,Web/SSH 继续只监听宿主机 `127.0.0.1:13000/2223`,状态固定在 `/volume2/docker/infra/gitea`。 +- `litellm`:继续通过外部 `n8n_default` 网络向现有 Agent 提供 `http://litellm:4000`,迁移稳定后再统一共享网络。 + +唯一 Compose 入口是 `/volume2/docker/infra/compose.yaml`。所有单实例服务显式设置 `container_name`。 + +Infra 版 LiteLLM 只提供官方订阅 OAuth 路由:OpenAI ChatGPT OAuth 与 xAI OAuth,不包含 GitHub Copilot。旧 NewAPI、DeepSeek、Kuaipao、MINIMAX、Elysia 和其他 API Key 不迁移;`litellm-config.yaml` 不包含 secret。 + +2026-07-16 对当前 Plus 账号真实请求模型目录并逐一 smoke test 后,ChatGPT 路由只保留:`gpt-5.6-sol`、`gpt-5.5`、`gpt-5.4`、`gpt-5.4-mini`、`codex-auto-review`。这些结论来自账号运行态,不来自静态文档目录。 + +`frpc` 是独立项目,不属于本 Compose。 + +Gitea 从 Codex Compose 迁移使用 `/volume2/docker/ops/migrate-gitea-to-infra.sh`。如果旧 `codex_local_git` 仍存在,脚本会停止并重命名它;如果容器已经不存在,则直接冷接管离线状态。脚本会复制 `/volume2/docker/codex/gitea` 到 `/volume2/docker/infra/gitea`;新容器健康后,它会从 Codex Compose 精确移除旧 Gitea 服务,并把原文件保存在新状态目录的 `migration/legacy-codex-compose.yaml`。旧数据始终保留为回滚点,确认仓库、登录、SSH 和数据完整前不得删除。 + +LiteLLM 迁移使用 `/volume2/docker/ops/migrate-litellm-to-infra.sh`。脚本只复制已有 ChatGPT/xAI OAuth 状态,不读取或生成 API Key 环境文件。成功前不得删除旧 `litellm` 容器和 `/volume2/docker/n8n/litellm`。 + +单实例服务必须显式设置 `container_name`。Infra LiteLLM 固定使用 `litellm`;首次按新命名重建前,如果旧回滚容器仍占用该名称,先将旧容器改名为 `litellm-legacy-rollback`,不得直接删除未确认的回滚状态。 diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..cd0a9ff --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,131 @@ +name: infra + +services: + postgres: + container_name: postgres + group_add: ["65534"] + image: pgvector/pgvector:0.8.2-pg18 + restart: unless-stopped + stop_grace_period: 60s + shm_size: 256mb + environment: + TZ: Asia/Shanghai + POSTGRES_USER: postgres + POSTGRES_DB: postgres + POSTGRES_PASSWORD_FILE: /run/secrets/postgres_superuser_password + POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256 + PGDATA: /var/lib/postgresql/18/docker + secrets: + - postgres_superuser_password + - gitea_db_password + volumes: + - ./postgres/data:/var/lib/postgresql + - ./postgres/init:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] + interval: 5s + timeout: 5s + retries: 20 + start_period: 20s + networks: + - git_forge + + gitea: + container_name: gitea + group_add: ["65534"] + image: ${GITEA_IMAGE:-gitea/gitea:1.23} + restart: unless-stopped + stop_grace_period: 30s + environment: + TZ: Asia/Shanghai + USER_UID: 1000 + USER_GID: 1000 + GITEA__database__DB_TYPE: postgres + GITEA__database__HOST: postgres:5432 + GITEA__database__NAME: gitea + GITEA__database__USER: gitea + GITEA__database__PASSWD__FILE: /run/secrets/gitea_db_password + GITEA__database__SSL_MODE: disable + GITEA__server__DOMAIN: localhost + GITEA__server__ROOT_URL: http://localhost:13000/ + GITEA__server__SSH_DOMAIN: localhost + GITEA__server__SSH_PORT: 2223 + GITEA__service__REQUIRE_SIGNIN_VIEW: "true" + GITEA__service__DISABLE_REGISTRATION: "true" + GITEA__security__INSTALL_LOCK: "true" + depends_on: + postgres: + condition: service_healthy + secrets: + - gitea_db_password + volumes: + - ./gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "127.0.0.1:13000:3000" + - "127.0.0.1:2223:22" + healthcheck: + test: ["CMD-SHELL", "grep -Eq '^INSTALL_LOCK[[:space:]]*=[[:space:]]*true' /data/gitea/conf/app.ini && grep -Eq '^DB_TYPE[[:space:]]*=[[:space:]]*postgres' /data/gitea/conf/app.ini && wget -q --spider http://127.0.0.1:3000/api/healthz"] + interval: 5s + timeout: 3s + retries: 24 + start_period: 20s + networks: + - git_forge + + litellm: + container_name: litellm + image: ghcr.io/berriai/litellm:main-latest + restart: unless-stopped + user: root + environment: + TZ: Asia/Shanghai + XAI_OAUTH_TOKEN_DIR: /app/config/xai_oauth + CHATGPT_TOKEN_DIR: /app/config/chatgpt + HTTP_PROXY: http://192.168.0.4:7890 + HTTPS_PROXY: http://192.168.0.4:7890 + ALL_PROXY: http://192.168.0.4:7890 + http_proxy: http://192.168.0.4:7890 + https_proxy: http://192.168.0.4:7890 + all_proxy: http://192.168.0.4:7890 + NO_PROXY: localhost,127.0.0.1,::1,host.docker.internal,n8n,postgres,qdrant,litellm,10.,172.16.,172.17.,172.18.,172.19.,172.20.,172.21.,172.22.,172.23.,172.24.,172.25.,172.26.,172.27.,172.28.,172.29.,172.30.,172.31.,192.168. + no_proxy: localhost,127.0.0.1,::1,host.docker.internal,n8n,postgres,qdrant,litellm,10.,172.16.,172.17.,172.18.,172.19.,172.20.,172.21.,172.22.,172.23.,172.24.,172.25.,172.26.,172.27.,172.28.,172.29.,172.30.,172.31.,192.168. + ports: + - "4000:4000" + volumes: + - ./litellm/config.yaml:/app/config/config.yaml:ro + - ./litellm/xai_oauth:/app/config/xai_oauth + - ./litellm/chatgpt:/app/config/chatgpt + command: + - --config + - /app/config/config.yaml + - --host + - 0.0.0.0 + - --port + - "4000" + healthcheck: + test: + - CMD + - /app/.venv/bin/python + - -c + - import urllib.request; urllib.request.urlopen("http://127.0.0.1:4000/health/liveliness", timeout=3).read() + interval: 30s + timeout: 5s + retries: 5 + start_period: 20s + networks: + - n8n_default + +networks: + git_forge: + name: git_forge + n8n_default: + external: true + name: n8n_default + +secrets: + postgres_superuser_password: + file: ./postgres/secrets/postgres_superuser_password + gitea_db_password: + file: ./postgres/secrets/gitea_db_password diff --git a/litellm/config.yaml b/litellm/config.yaml new file mode 100644 index 0000000..cc43a45 --- /dev/null +++ b/litellm/config.yaml @@ -0,0 +1,77 @@ +model_list: + - model_name: gpt-5.6-sol + model_info: + mode: responses + context_window: 272000 + litellm_params: + model: chatgpt/gpt-5.6-sol + + - model_name: gpt-5.5 + model_info: + mode: responses + context_window: 272000 + litellm_params: + model: chatgpt/gpt-5.5 + + - model_name: gpt-5.4 + model_info: + mode: responses + context_window: 272000 + litellm_params: + model: chatgpt/gpt-5.4 + + - model_name: gpt-5.4-mini + model_info: + mode: responses + context_window: 272000 + litellm_params: + model: chatgpt/gpt-5.4-mini + + - model_name: codex-auto-review + model_info: + mode: responses + context_window: 272000 + litellm_params: + model: chatgpt/codex-auto-review + + - model_name: grok-official + model_info: + mode: chat + litellm_params: + model: xai/grok-4.5 + use_xai_oauth: true + + - model_name: grok-fast + model_info: + mode: chat + context_window: 1000000 + litellm_params: + model: xai/grok-4.20-0309-non-reasoning + use_xai_oauth: true + + - model_name: grok-reasoning + model_info: + mode: chat + context_window: 1000000 + litellm_params: + model: xai/grok-4.20-0309-reasoning + use_xai_oauth: true + + - model_name: grok-research + model_info: + mode: responses + context_window: 1000000 + litellm_params: + model: xai/grok-4.20-multi-agent-0309 + use_xai_oauth: true + + - model_name: grok-build + model_info: + mode: chat + litellm_params: + model: xai/grok-build-0.1 + use_xai_oauth: true + +litellm_settings: + aiohttp_trust_env: true + drop_params: true diff --git a/postgres/init/10-create-gitea.sh b/postgres/init/10-create-gitea.sh new file mode 100755 index 0000000..ba26ff0 --- /dev/null +++ b/postgres/init/10-create-gitea.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh +set -eu + +gitea_password=$(cat /run/secrets/gitea_db_password) + +psql --set=ON_ERROR_STOP=1 \ + --username "$POSTGRES_USER" \ + --dbname "$POSTGRES_DB" \ + --set=gitea_password="$gitea_password" <<'SQL' +SELECT format('CREATE ROLE gitea LOGIN PASSWORD %L', :'gitea_password') +WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'gitea') \gexec +ALTER ROLE gitea WITH LOGIN PASSWORD :'gitea_password'; + +SELECT 'CREATE DATABASE gitea OWNER gitea' +WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'gitea') \gexec + +REVOKE ALL ON DATABASE gitea FROM PUBLIC; +GRANT CONNECT, TEMPORARY ON DATABASE gitea TO gitea; +SQL