UAT environment
UAT is the environment the family actually plays on. It runs on the homelab machine with Docker Compose and is
reachable from the owner’s devices over WireGuard/LAN at 10.100.0.2. It runs the images of a uat- tag, and
production runs only a commit that ran here first: see Environments and releases,
ADR 0041 and
ADR 0010.
| What | URL |
|---|---|
Hub — the public home page at the root, the app at /play | http://10.100.0.2:4820 |
| Console | http://10.100.0.2:4821 — its own sign-in (Admin console) |
| Docs | http://10.100.0.2:4810 |
| Games origin | http://10.100.0.2:4830 (embedded by the hub) |
The API and PostgreSQL are not published; the hub proxies /api, the console only its own routes (/api/admin). Development uses the separate tohe compose
project on ports 47xx (Local development); the two never share containers, volumes or ports.
First use
Open the hub and choose Tạo tài khoản: an email, a name and a password, then the code that email receives (ADR 0024) — so UAT needs its relay configured (below), or nobody new can join. The account starts with one profile and plays at once. Accounts made before email sign-in keep their username and add an email the next time they sign in. Add the children’s profiles from Đổi người chơi → Thêm hồ sơ or Quản lý tài khoản (the first time it asks for the password and offers a 6-digit manage PIN, which opens it from then on — ADR 0025); an account holds up to five. A family shares one account on its devices; another adult may register their own. See Identity.
Deploy
From any checkout of the repository on the homelab:
deploy/uat/deploy.sh uat-YYYY-MM-DD.N # deploys the commit a uat- tag points atThe script also accepts a commit or nothing (origin/main), but agents and releases always deploy a tag, rolling back
included: a new uat- tag on the older commit (ADR 0041).
The script:
- creates
~/.config/tohe-uat/uat.envon first run (host, a random database password and a telemetry report token — keep this file; an older file gets the token added once); - exports the exact commit with
git archiveinto~/.local/share/tohe-uat/src— never a working copy; - pulls the four images CI published for that commit,
ghcr.io/vinh466/tohe-play/<api|hub|games|docs>:sha-<commit>(ADR 0021) — the same images production runs. CI publishes them after every check onmainpassed; until then the script stops and says so. A commit from before CI published images (a rollback that far) is built here instead, as deploys used to; - backs up the database if UAT is running (below);
- starts the
tohe-uatcompose project (database migrations run when the API starts), waits for/api/healthand prints the deployed commit.
What is running: curl -s http://10.100.0.2:4820/api/config → apiVersion is the first 12 characters of the deployed
commit. pnpm check:uat does that and the rest of the checks after a deploy
(it reads only), and pnpm check:uat --expect <tag> compares the version with a tag.
Deploy only commits that are on main with green CI. The agent tags the commit uat-… and deploys when the owner
says “deploy” (ADR 0041); the tag rules and
the checks to run afterwards are in Environments and releases.
The packages are public, so the homelab pulls without signing in. If a pull is refused, check the package’s
visibility on GitHub (Packages → tohe-play/api → Package settings), or sign in once with
docker login ghcr.io and a token that can read packages.
UAT sends no email until the relay’s settings are in ~/.config/tohe-uat/uat.env
(ADR 0022). For Resend (port 587, STARTTLS — the
username is the literal word resend, the password an API key). Tò He sends from toheplay.io.vn, the domain
verified in Resend for it, as production does; its key may send from that domain alone, so the sender must be on it:
TOHE_UAT_EMAIL_MODE=SmtpTOHE_UAT_SMTP_HOST=smtp.resend.comTOHE_UAT_SMTP_USERNAME=resendTOHE_UAT_SMTP_PASSWORD=re_…The next deploy picks them up. Resend’s free plan allows 100 emails a day for the whole account.
[email protected] in the same file makes the hub serve /.well-known/security.txt
(Configuration); without it that path answers 404.
Backups
deploy/uat/backup.sh dumps the database to ~/backups/tohe-uat/ and keeps the newest 20 dumps. Every deploy
runs it right before replacing the containers. Restore a dump:
gunzip -c ~/backups/tohe-uat/tohe-YYYYMMDD-HHMMSS.sql.gz \ | docker compose -p tohe-uat exec -T postgres psql -U tohe -d toheInspect
docker compose -p tohe-uat psdocker compose -p tohe-uat logs -f apidocker compose -p tohe-uat exec postgres psql -U tohe -d toheHow the games are doing on the family’s devices (Device telemetry); the operator
token is TOHE_UAT_TELEMETRY_TOKEN in ~/.config/tohe-uat/uat.env, added by the first deploy that needs it:
pnpm --filter @tohe/telemetry-report report -- --env uat --days 7The console, http://10.100.0.2:4821, shows the same and more (Admin console). It has a
sign-in of its own: the email, the password and a code from an authenticator app, which an account with a role adds in
the hub first (Quản lý tài khoản → Bảo mật tài khoản → Xác thực hai lớp). Being signed in to the hub does not sign in to
the console. Until someone owns the deployment, the API logs a setup token when it starts; the first owner gives it at
http://10.100.0.2:4821/setup, with the email and password of an account with a confirmed email:
docker compose -p tohe-uat logs api | grep "No platform owner"The command inside the API’s container grants roles too, and is the way back if every owner is lost:
docker compose -p tohe-uat exec api dotnet Tohe.Api.dll admin grant <email or old username> --role ownerdocker compose -p tohe-uat exec api dotnet Tohe.Api.dll admin listThen the authenticator, in the hub: an app on the phone, and recovery codes to keep. Without it the console’s sign-in refuses the account.
Limits (accepted for now)
- Plain HTTP inside WireGuard/LAN: no service worker or “Add to Home Screen” on phones (needs HTTPS), session
cookies are not marked
Secure. Production runs on HTTPS (ADR 0021). - One machine, no redundancy. Backups are local to the same disk.