163 lines
5.4 KiB
Markdown
163 lines
5.4 KiB
Markdown
# 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
|
|
|
|
```text
|
|
<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`.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```text
|
|
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`:
|
|
|
|
```text
|
|
<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:
|
|
|
|
```text
|
|
/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:
|
|
|
|
```bash
|
|
curl https://builds.example.com/health
|
|
```
|
|
|
|
Build:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```text
|
|
/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
|
|
|
|
```bash
|
|
python3 -m unittest -v tests.test_build_api
|
|
```
|