Skip to content

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 ​

ItemValue
Public URLhttps://safecall-lab.sourcectl.com
SSHcon.ssh
Site root/home/thomas/websites/safecall-lab.sourcectl.com
systemdsafecall-lab.service (unit file in scripts/deploy/safecall-lab.service)
Listen127.0.0.1 on an unused port. Prefer 8788 if free (Pythia is 3738, Wattistas 4747)
JWT issuer / audienceBrand defaults https://auth.wantok.cz (must match the --release SPA; not the lab hostname)
server_idLAB001 (must not collide with TST000 or a customer box)
ntfyhttps://notify.wantok.cz (may stay; lab Ariadne POC does not need MQTT)
Deploybun 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 ​

StepWho
DNS A/AAAA, first Caddy site, systemd unit, sudoers NOPASSWD restart, first remote main.config.jsonOperator (OA-AT-lab-host)
Discover a free port on the host (suggest 8788)Operator, once
Local web --release + embeds + rsync + remote bun install + systemd restartAgent (bun run lab:deploy)
CEDR rasters + Hansel import over SSHAgent (bun scripts/deploy/import-cedr.ts) after host health
Apple / Play app records, Transporter, keytoolOperator (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:

SentNot 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.json
  • server/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.com

3. 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.service

The 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.service

Verify as thomas:

bash
sudo -l

6. 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 keyApple Passwords itemWhat it is
server_credentials_default_passwordSafeCall lab admin @ safecall-lab.sourcectl.com (username admin@sourcectl.com)Web login for the lab admin UI. Generate a new password.
server_mobile_api_keySafeCall lab mobile API key @ safecall-lab.sourcectl.comRandom 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
}
MustMust 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.service

7. First deploy + health ​

From the safecall repo on GLaDOS (agent after DNS/Caddy/sudoers exist):

bash
bun run lab:deploy

Health:

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/login

Expect 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.ts

That 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.