Appearance
Lab SafeCall (safecall-lab.sourcectl.com)
Agent-deployable lab host for Ariadne / Theseus Internal pairing. Not Keryx {public_base}.
This packet is the how-to for OA-AT-lab-host (#161) and the agent bun run lab:deploy loop. Human portal/DNS/Caddy/sudoers steps stay operator-only. After that one-time host setup, bun run lab:deploy is an agent step.
Do not put passwords, JWTs, mobile keys, keystores, or a filled main.config.json in git. Do not commit floor rasters or mar.json. Do not reuse this hostname as the Keryx store privacy URL unless a later decision says so.
Locked values
| Item | Value |
|---|---|
| Public URL | https://safecall-lab.sourcectl.com |
| SSH | con.ssh |
| Site root | /home/thomas/websites/safecall-lab.sourcectl.com |
| systemd | safecall-lab.service (unit file in scripts/deploy/safecall-lab.service) |
| Listen | 127.0.0.1 on an unused port. Prefer 8788 if free (Pythia is 3738, Wattistas 4747) |
| JWT issuer / audience | Brand defaults https://auth.wantok.cz (must match the --release SPA; not the lab hostname) |
server_id | LAB001 (must not collide with TST000 or a customer box) |
| ntfy | https://notify.wantok.cz (may stay; lab Ariadne POC does not need MQTT) |
| Deploy | bun run lab:deploy → scripts/deploy.ts |
Theseus stays Wantok-internal (Play Internal + TestFlight only). Never claim cz.wantok.ariadne. Do not start Play Production or App Review.
Who does what
| Step | Who |
|---|---|
DNS A/AAAA, first Caddy site, systemd unit, sudoers NOPASSWD restart, first remote main.config.json | Operator (OA-AT-lab-host) |
| Discover a free port on the host (suggest 8788) | Operator, once |
Local web --release + embeds + rsync + remote bun install + systemd restart | Agent (bun run lab:deploy) |
| CEDR rasters + Hansel import over SSH | Agent (bun scripts/deploy/import-cedr.ts) after host health |
| Apple / Play app records, Transporter, keytool | Operator (Ariadne / Theseus store chapters) |
| Phone walk (heading + position) | Operator (OA-AT-maps) |
Rsync safety
bun run lab:deploy copies only what bun server/src/server.ts needs:
| Sent | Not sent |
|---|---|
config/ (except main.config.json) | ariadne/, keryx/, theseus/ |
lib/ | docs/, website/, bug-collector/ |
server/ (except db/, maps/, tests/, backups/) | Playwright, root node_modules/ |
web/build/ (release assets; embeds already generated locally) | web/src/ except lib/contracts.ts |
web/src/lib/contracts.ts (server Valibot imports) | release scripts, Flutter trees |
scripts/deploy/safecall-lab.service |
It never overwrites lab SQLite, floor rasters, or remote config (--exclude + --filter protect in scripts/deploy/sync.ts):
config/main.config.jsonserver/db/server/maps/.env
After rsync, deploy removes leftover whole-repo trees on the host (ariadne/, docs/, tmp/, web/src/admin, …) if an earlier fat sync put them there. Remote bun install runs in lib/ and server/ only, then symlinks web/node_modules/valibot to server/node_modules/valibot so web/src/lib/contracts.ts can resolve it.
Copy scripts/deploy/deploy.env.example to gitignored scripts/deploy/deploy.env only if you need to override the locked host/root.
Dry run:
bash
RSYNC_DRY_RUN=true bun run lab:deploy -- --sync-only(--sync-only skips the local web build.)
One-time operator host setup (OA-AT-lab-host)
Run on con.ssh as a user with sudo. Exact clicks/commands are also in the Ariadne close-out chapter. Do not skip the port check.
1. Port
bash
ss -ltnp | grep -E ':8788|:3738|:4747'If 8788 is free, use it. If it is taken, pick another unused port and write that number on NAS evidence/ariadne-theseus/ (not git). Caddy, systemd does not set the port — server_http_port in remote config does.
2. Site directory
bash
mkdir -p /home/thomas/websites/safecall-lab.sourcectl.com
chown thomas:thomas /home/thomas/websites/safecall-lab.sourcectl.com3. systemd
Install the unit from the repo copy scripts/deploy/safecall-lab.service to /etc/systemd/system/safecall-lab.service.
bash
sudo cp /home/thomas/websites/safecall-lab.sourcectl.com/scripts/deploy/safecall-lab.service \
/etc/systemd/system/safecall-lab.service
sudo systemctl daemon-reload
sudo systemctl enable safecall-lab.serviceThe first enable may happen before the first deploy; start waits until config exists.
4. Caddy
At the registrar, add A and AAAA for safecall-lab.sourcectl.com to the same addresses as pythia.sourcectl.com / wattistas.sourcectl.com (the con.ssh public IPs). Then on the host, add a Caddy site:
safecall-lab.sourcectl.com {
reverse_proxy 127.0.0.1:8788
}Reload Caddy. TLS is automatic. Do not expose 8788 on the public internet; the process binds 127.0.0.1 via server_http_hostname.
If 8788 was not free, change the reverse_proxy port to the NAS-recorded value.
5. sudoers
visudo -f /etc/sudoers.d/safecall-lab:
thomas ALL=(root) NOPASSWD: /bin/systemctl restart safecall-lab.service
thomas ALL=(root) NOPASSWD: /bin/systemctl status safecall-lab.serviceVerify as thomas:
bash
sudo -l6. Remote config (once, on the host, never git)
Write /home/thomas/websites/safecall-lab.sourcectl.com/config/main.config.json on the server only. Do not copy brand defaults. Missing keys are filled from config/defaults.ts on first start, and those defaults include "bridge" — that is what polls http://localhost:8088/lld/gateways.
Replace the two placeholders with new Apple Passwords items created for this host. Do not reuse Theseus, ntfy, Keryx, or keystore items.
| Config key | Apple Passwords item | What it is |
|---|---|---|
server_credentials_default_password | SafeCall lab admin @ safecall-lab.sourcectl.com (username admin@sourcectl.com) | Web login for the lab admin UI. Generate a new password. |
server_mobile_api_key | SafeCall lab mobile API key @ safecall-lab.sourcectl.com | Random string Ariadne puts in the Mobile Access QR. Not a login. Generate a new password-length secret. |
Do not use Wantok staff passphrase @ Theseus, NTFY / safecall-ops-subscriber, Keryx publish tokens, or keystore export passwords here.
Do not commit this file.
json
{
"server_id": "LAB001",
"server_features": ["navigation", "rtls"],
"server_mode": "prod",
"server_http_port": 8788,
"server_http_hostname": "127.0.0.1",
"server_db_path": "db/safecall.db",
"server_credentials_default_email": "admin@sourcectl.com",
"server_credentials_default_password": "replace-from-apple-passwords",
"server_auth_jwt_issuer": "https://auth.wantok.cz",
"server_auth_jwt_audience": "https://auth.wantok.cz",
"server_mobile_api_key": "replace-from-apple-passwords",
"server_ntfy_server_url": "https://notify.wantok.cz",
"server_ntfy_ops_enabled": false,
"server_bug_tracker_enabled": false
}| Must | Must not |
|---|---|
"navigation" and "rtls" (Ariadne pairing + position) | "bridge" (needs a live LLD on :8088) |
server_http_hostname 127.0.0.1, port 8788 (or the free port you recorded) | "safecall" unless you also want sensors/KISS |
JWT issuer/audience = https://auth.wantok.cz (release SPA bake) | Lab hostname as JWT issuer (login will bounce) |
If this file already exists, edit server_features in place (the loader already wrote ["bridge", "safecall"] once). The JWT issuer must stay https://auth.wantok.cz so it matches the release web bundle; using the lab hostname makes login succeed and then bounce back to /login. Changing server_credentials_default_password does not reset an admin user that already exists in SQLite.
MQTT stays at the default mqtt://localhost:1883. With no Mosquitto, the client reconnects quietly; Ariadne POC does not need a broker.
Rsync excludes this file. --delete cannot remove it because of protect. Restart after editing:
bash
sudo systemctl restart safecall-lab.service7. First deploy + health
From the safecall repo on GLaDOS (agent after DNS/Caddy/sudoers exist):
bash
bun run lab:deployHealth:
bash
curl -sS -o /dev/null -w "%{http_code}\n" https://safecall-lab.sourcectl.com/
curl -sS -o /dev/null -w "%{http_code}\n" https://safecall-lab.sourcectl.com/loginExpect 200 on / (SPA) and 200 on GET /login (login page). POST /login with a wrong password is not the health check.
CEDR maps import (agent, OA-AT-lab-maps-import)
Not the phone walk. Archive stays on disk.
bash
bun scripts/deploy/import-cedr.tsThat rsyncs CEDR_4NP_deployment-map.png and Map_CEDR_4NP.png into remote server/maps/, copies project.json, and runs server/scripts/import_hansel_project.ts against remote server/db/safecall.db.
Then attach maps in the lab admin UI (or sqlite) per POC replay. Do not commit rasters.