diff --git a/docs/BUILD-HOST-RUNBOOK.md b/docs/BUILD-HOST-RUNBOOK.md new file mode 100644 index 000000000..141de5df9 --- /dev/null +++ b/docs/BUILD-HOST-RUNBOOK.md @@ -0,0 +1,91 @@ +# Runtime Image Build Host Runbook + +> Purpose: build API/Worker runtime image artifacts outside the production host. + +## Why + +Production host currently runs Gitea, Gitea Actions runner, Web/API/Worker/Postgres/Redis. It has about 2 CPU and 1.7GiB memory. Building API/Worker images there can make SSH/Web/API unstable, so production must only load prebuilt images and restart containers. + +## Allowed build hosts + +Use one of: + +1. Dedicated Gitea runner on a separate machine. +2. Local developer machine with Docker installed. +3. Temporary cloud build VM that is destroyed after artifact upload. + +Do not use the production host unless an emergency exception is explicitly approved with `ALLOW_SHARED_PRODUCTION_BUILD_HOST=true`. + +## Build steps + +From a clean checkout of the target release commit: + +```bash +git checkout develop +git pull --ff-only origin develop +scripts/build_release_images.sh v0.1.5 +``` + +The script writes: + +```text +dist/release-images/xiaoxia-runtime-images-v0.1.5.tar +``` + +## Upload artifact + +Upload to production: + +```bash +scp dist/release-images/xiaoxia-runtime-images-v0.1.5.tar \ + xiaoxia-server:/var/lib/xiaoxia-saas-production/runtime-images-v0.1.5.tar +``` + +## Release tag + +After the runtime image tar exists on production: + +```bash +git tag -a v0.1.5 -m "Release v0.1.5" +git push origin v0.1.5 +``` + +The Gitea production deploy passes `RELEASE_VERSION=v0.1.5` to `infra/docker/deploy-production.sh`. The deploy must fail if the runtime image tar is missing. + +## Manual production deploy fallback + +If tag deploy is unavailable but the runtime image tar has been uploaded: + +```bash +ssh xiaoxia-server \ + "HOST_PREFIX= RELEASE_VERSION=v0.1.5 \ + RUNTIME_IMAGE_TAR=/var/lib/xiaoxia-saas-production/runtime-images-v0.1.5.tar \ + /var/lib/xiaoxia-saas-production/repo/infra/docker/deploy-production.sh" +``` + +## Verification + +Run from local repo after deploy: + +```bash +python scripts/smoke_public_auth_flow.py +python scripts/smoke_public_upload_flow.py +``` + +Then verify project detail endpoint: + +```bash +curl -fsS https://saas.xiaoxiajianji.com/api/v1/projects/ +``` + +## Required release notes + +Record: + +- tag +- commit +- runtime image tar path +- public auth smoke result +- public upload smoke result +- production API image tag +- production Worker image tag diff --git a/tests/unit/test_release_scripts.py b/tests/unit/test_release_scripts.py index e1bae9d36..5e044be28 100644 --- a/tests/unit/test_release_scripts.py +++ b/tests/unit/test_release_scripts.py @@ -154,6 +154,16 @@ def test_subscription_api_is_not_wired_to_ui_until_backend_exists(): assert importers == [] +def test_build_host_runbook_requires_off_production_runtime_builds(): + runbook = Path("docs/BUILD-HOST-RUNBOOK.md").read_text(encoding="utf-8") + + assert "Production host currently runs Gitea" in runbook + assert "scripts/build_release_images.sh v0.1.5" in runbook + assert "runtime-images-v0.1.5.tar" in runbook + assert "The deploy must fail if the runtime image tar is missing" in runbook + assert "python scripts/smoke_public_upload_flow.py" in runbook + + def test_deployment_docs_forbid_production_runtime_builds(): docs = Path("docs/DEPLOYMENT.md").read_text(encoding="utf-8")