# Dedicated Gitea Runtime Builder Runner

> Goal: build API/Worker runtime image tar on a machine that does not host production services.

## Required machine

Minimum recommended spec:

- 2 vCPU minimum, 4 vCPU preferred.
- 4GiB RAM minimum, 8GiB preferred.
- 40GiB disk minimum.
- Docker Engine installed.
- Network access to Gitea and production SSH.

Do not install this runner on the current production host.

## Runner label

The production runtime image workflow targets:

```yaml
runs-on: runtime-builder
```

Register the new runner with a label named `runtime-builder`. Keep the existing production/staging runner separate.

## Required Gitea secrets

The workflow can use either Gitea secrets or the dedicated runner-local upload key.

Preferred repository or organization secrets:

- `PRODUCTION_SSH_HOST`
- `PRODUCTION_SSH_USER`
- `PRODUCTION_SSH_KEY`

If these secrets are absent, the workflow falls back to:

- host: `47.98.113.167`
- user: `root`
- key path on the dedicated builder: `/root/.ssh/xiaoxia_runtime_builder`

The key must allow uploading to:

```text
/var/lib/xiaoxia-saas-production/runtime-images-<tag>.tar
```

Do not store SSH private keys in the repository.

## Workflow behavior

On `v*` tag push:

1. `build-production-runtime-images` runs on `runtime-builder`.
2. Checkout downloads the repository archive using the explicit `${{ github.token }}` environment binding; runner shell environments must not assume `GITHUB_TOKEN` is implicitly set.
3. It executes `scripts/build_release_images.sh <tag>`.
4. It builds `apps/web/dist` in a Node Docker container, not on production.
5. It packages `release-<tag>.tar.gz` with the prebuilt Web dist.
6. It uploads both `runtime-images-<tag>.tar` and `release-<tag>.tar.gz` to production.
7. `deploy-production` runs after the build job succeeds, on `runtime-builder`, and drives production over SSH.
8. `deploy-production` extracts the prebuilt release artifact on production, loads `/var/lib/xiaoxia-saas-production/runtime-images-<tag>.tar`, and restarts API/Worker/Web.

If either the release tar or runtime image tar is missing, production deploy must fail. The production deploy job intentionally also runs on `runtime-builder` because Gitea `needs` scheduling previously skipped the downstream `ubuntu-latest` deploy job after a successful cross-runner build.

## Preflight checks on runner

Run on the new runner before registration:

```bash
docker version
docker ps --format '{{.Names}}' | grep -Eq '^(xiaoxia-(api|web|worker|postgres|redis)-production|gitea)$' && exit 1 || true
```

The second command must not find production containers.

## Validation release

After runner registration:

1. Push a test tag only after `develop` is green.
2. Confirm runtime image build job runs on the dedicated runner.
3. Confirm production deploy waits for the image job.
4. Run:

```bash
python scripts/smoke_public_auth_flow.py
python scripts/smoke_public_upload_flow.py
```

5. Record release result in the Phase progress document.
