4.9 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 toartifacts/in the repository.BUILD_API_PROJECT_SCRIPT: defaults tofrontend/tools/new_project.py.BUILD_API_PYTHON: Python used to invoke the builder.BUILD_API_HOST/BUILD_API_PORT: defaults to127.0.0.1:8081.BUILD_API_TIMEOUT: synchronous build timeout in seconds, default900.
Install API dependencies, then run from the repository root:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
export BUILD_API_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(48))')"
export BUILD_API_ARTIFACT_ROOT=/srv/coruna-artifacts
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> ...
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"]
}'
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
- Deploy
build_api+ artifact root + reverse proxy / static site via your panel. - Set Laravel
CORUNA_BUILD_SERVICE_*and static-site domains. - Create a new channel from Admin and verify support/daily URLs on the static host.
- Keep legacy
public/webandpublic/syncread-only until old channels are rebuilt or retired. - Device validation (ops): capture that the implant requests
/channel/<id>/sync/daily.htmlthen/channel/<id>/sync/<wire>.
Tests
python3 -m unittest -v tests.test_build_api