Files
hashbro 9153a4f557 admin
2026-08-09 02:42:45 +08:00

5.4 KiB

Synchronous build API

build_api is the HTTP front of coruna-lab-web. It runs frontend/tools/new_project.py synchronously and publishes completed releases for a web server to serve directly from the local filesystem. Laravel lives in the sibling coruna-lab project and calls this API over the network.

Storage layout

<artifact-root>/
├── channel/<channel-id>/       # published web/, sync/, out/, manifest.json
├── staging/<request-id>/       # private build workspaces
└── locks/<channel-id>.lock     # nonblocking advisory locks

Staging and published releases are children of the same artifact root and are checked to be on the same filesystem. A successful build is renamed into channel/<channel-id> only after its required output and absence of symlinks have been verified. Builder failures remove staging and leave an existing release untouched.

Every release gets a manifest.json containing the normalized build input, its SHA-256 digest, request ID, build timestamp, and a digest of builder stdout. An identical input returns the existing manifest without running the builder. Different input for an existing channel returns 409; "force": true replaces it after a new build succeeds.

Configuration

The service reads:

  • BUILD_API_TOKEN (required): exact Bearer token.
  • BUILD_API_ARTIFACT_ROOT: defaults to artifacts/ in the repository.
  • BUILD_API_PROJECT_SCRIPT: defaults to frontend/tools/new_project.py.
  • BUILD_API_PYTHON: Python used to invoke the builder.
  • BUILD_API_HOST / BUILD_API_PORT: defaults to 127.0.0.1:8081.
  • BUILD_API_TIMEOUT: synchronous build timeout in seconds, default 900.

Install API dependencies, then run from the repository root. On panel hosts (宝塔), put variables in a project-root .env (see .env.example); python -m build_api loads it automatically when the process environment does not already define them. Optional: BUILD_API_ENV_FILE=/path/to/file.

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

cp .env.example .env
# edit .env — at least set BUILD_API_TOKEN
python3 -m build_api

The HTTP layer is FastAPI served by Uvicorn. Route contracts, status codes, and Bearer auth are unchanged for Laravel clients.

The builder contract is:

python new_project.py \
  --root <artifact-root>/staging/<request-id> \
  --channel-id <id> \
  --deployment-domains <domain> ... \
  --reporting-domains <domain> ... \
  --support-template test|blank

support_template selects the web/support.html page:

  • test (default): current source campaign page with lab HUD
  • blank: loader scripts only, no HUD UI (white blank page)

It must create a channel-scoped tree below --root:

<root>/
├── web/support.html
├── sync/daily.html
├── out/
└── manifest.json   # includes channel_id, support_path, daily_path

The API always passes --force because it pre-creates the staging directory. It never writes into the Laravel public/ directory. Published static URLs are:

/channel/<id>/web/support.html
/channel/<id>/sync/daily.html

Run the API as a long-lived process (for example via a process manager in 宝塔) and put a reverse proxy in front of BUILD_API_HOST/BUILD_API_PORT. Point the public static site document root at the artifact root so only channel/<id>/web/ and channel/<id>/sync/ are reachable; keep staging/, locks/, manifests, and out/ private. The web server user needs read access to published releases; it should not have write access.

API

Health is public:

curl https://builds.example.com/health

Build:

curl -i -X POST \
  -H "Authorization: Bearer $BUILD_API_TOKEN" \
  -H "Content-Type: application/json" \
  https://builds.example.com/v1/channels/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/build \
  -d '{
    "deployment_domains": ["deploy.example"],
    "reporting_domains": ["report.example"],
    "support_template": "test"
  }'

support_template is optional (test by default). Allowed values: test, blank.

The first successful build returns 201; the same normalized input returns 200. A busy channel or conflicting existing release returns 409, and an invalid channel, JSON body, domain, or field returns 422.

Status and deletion:

curl -H "Authorization: Bearer $BUILD_API_TOKEN" \
  https://builds.example.com/v1/channels/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

curl -i -X DELETE -H "Authorization: Bearer $BUILD_API_TOKEN" \
  https://builds.example.com/v1/channels/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Deletion returns 204 whether or not the release exists. It rejects symbolic links and takes the same per-channel lock as builds.

Public static URLs:

/channel/<channel-id>/web/support.html
/channel/<channel-id>/sync/daily.html

Only web/ and sync/ under each channel should be exposed; manifests, out/, staging, and locks stay private.

Migration checklist

  1. Deploy build_api + artifact root + reverse proxy / static site via your panel.
  2. Set Laravel CORUNA_BUILD_SERVICE_* and static-site domains.
  3. Create a new channel from Admin and verify support/daily URLs on the static host.
  4. Keep legacy public/web and public/sync read-only until old channels are rebuilt or retired.
  5. Device validation (ops): capture that the implant requests /channel/<id>/sync/daily.html then /channel/<id>/sync/<wire>.

Tests

python3 -m unittest -v tests.test_build_api