Say what is true of this project, and link what is true of the machine

AGENTS.md had grown a description of the machine these sessions run on --
the two boxes, the qemu networking, the shared ~/repos mount, gitea and the
push permission, the Android SDK path, the shared emulator, how to detach a
server. None of that is about ai-app, and all of it is equally true in the
sibling repos, so each copy was a place for the truth to drift: the push
note in this one had been wrong since the bot key was authorized, and the
checkout path it named had moved.

It now lives once in ~/.claude/MACHINE.md, which every session already
reads. What stays here is only what the machine means for this project: that
ai-server belongs on the host because WireGuard terminates there, that the
tunnel can never terminate in the VM, that the claude CLI being VM-only
makes it a remote to the backend, and where this project's secrets live
instead of the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-28 02:33:55 -04:00
1 parent 5391fe3b34
commit a2bf744df9
1 file changed
+18 -50
+18 -50
View File
@@ -86,32 +86,18 @@ real-phone/WireGuard bring-up, which is operational rather than code.
## Where things run (host vs this VM) ## Where things run (host vs this VM)
Established 2026-08-25, and it decides more than it looks like: Established 2026-08-25, and it decides more than it looks like. The machine
itself — the two boxes, the shared `~/repos` mount, gitea, and why the VM is
treated as untrusted — is described once in `~/.claude/MACHINE.md`; what
follows is only what that means for **this** project.
- **The host (192.168.1.168) is the backend machine.** It runs - **`ai-server` belongs on the host in production.** That is where the LAN
dev-updater's server today and is where `ai-server` belongs in address the phone can reach is, and where WireGuard terminates.
production: it has the LAN address the phone can reach, and it's where `wg-setup-host.sh` sets that up (keys, `wg0.conf`, the phone's QR); run
WireGuard terminates. `wg-setup-host.sh` sets that up (keys, `wg0.conf`, it there with `sudo WG_ENDPOINT=<ddns name>`.
the phone's QR); run it there with `sudo WG_ENDPOINT=<ddns name>`. - **The tunnel and the real phone can never terminate in the VM**, because
- **This VM is a dev sandbox behind qemu user-mode networking** nothing outside can open a connection into it. Phone bring-up is host
(10.0.2.15, gateway 10.0.2.2) — outbound only. The host is reachable at work.
10.0.2.2, but **nothing outside can initiate a connection into the VM**,
so the tunnel and the real phone can never terminate here.
- **Code reaches the host through gitea, not the shared mount.** This VM's
checkout (`~/repos/ai-app`, a virtiofs mount the host also sees at
`~/stuff/vm/ai/repos/ai-app`) is a working copy only: its `origin` is
`git@git.arirex.me:iris/ai-app`, and the `iris-ai` bot key this VM holds
is authorized to push there — it was not until 2026-08-28, so anything
written before that saying a push fails with `Permission denied
(publickey)` is stale. The host still builds and runs from its own
separate clone outside the shared mount, so what keeps VM-authored code
off the host is the pull being a deliberate act there, not a filesystem
permission and no longer a push this VM cannot make.
- Consequence for building here: `server/target/` is shared with the host's
view of *this* checkout, so if anything on the host ever builds from the
shared path, the two `cargo build`s replace each other's binary and each
rebuilds from scratch. Building on the host from its own clone avoids it
entirely.
- `wg0` (10.66.0.1) now exists in this VM too, so the production path — - `wg0` (10.66.0.1) now exists in this VM too, so the production path —
`ai-server` with no `--bind` — is exercisable during development. It has `ai-server` with no `--bind` — is exercisable during development. It has
no reachable peer and doesn't need one; the interface existing is what no reachable peer and doesn't need one; the interface existing is what
@@ -122,14 +108,13 @@ Established 2026-08-25, and it decides more than it looks like:
network namespaces inside one machine and drives the server through it network namespaces inside one machine and drives the server through it
— a genuine handshake against 10.66.0.1 with pinned TLS, no router or — a genuine handshake against 10.66.0.1 with pinned TLS, no router or
phone involved. That's the way to verify the wg0-only posture. phone involved. That's the way to verify the wg0-only posture.
- **The `claude` CLI is installed in this VM only, not on the host.** So - **The `claude` CLI is only in the VM, so from the host it is a remote.**
the backend reaches it the same way it would any other machine: a The backend reaches it the way it would any other machine: a configured
configured host, and a session that names it. For the host to ssh in, host, and a session that names it. For the host to ssh in, the VM needs
the VM needs an inbound port forward in its launch configuration an inbound port forward it does not have by default.
(qemu `hostfwd`) — usermode networking has none by default. - **Nothing secret goes in the repo**, which is shared with the host and
- **Nothing secret goes in the repo.** The VM is treated as untrusted (see attacker-writable under this project's threat model (PLAN.md's security
PLAN.md's security section), and the repo is shared read-write with the section). So state lives outside it: `$XDG_CONFIG_HOME/ai-app/config.ron`
host, so state lives outside it: `$XDG_CONFIG_HOME/ai-app/config.ron`
and `certs/`, `$XDG_DATA_HOME/ai-app/sessions/`, owner-only. and `certs/`, `$XDG_DATA_HOME/ai-app/sessions/`, owner-only.
- Certificates are generated **by the server, on first start**, into - Certificates are generated **by the server, on first start**, into
`$XDG_CONFIG_HOME/ai-app/certs` (`--certs` overrides). The CA is created `$XDG_CONFIG_HOME/ai-app/certs` (`--certs` overrides). The CA is created
@@ -173,20 +158,3 @@ Established 2026-08-25, and it decides more than it looks like:
through `androidComponents.onVariants { it.sources.java?.addGenerated through `androidComponents.onVariants { it.sources.java?.addGenerated
SourceDirectory(task, Task::outputDir) }`, which also carries the task SourceDirectory(task, Task::outputDir) }`, which also carries the task
dependency. dependency.
## Environment notes (this machine, learned in dev-updater)
- Android SDK is at `~/Android/Sdk`, not the root-owned `/opt/android-sdk`
the ambient `$ANDROID_HOME` may point at; copy dev-updater's
`android-env.sh` override pattern.
- Each agent command runs in a fresh shell — exported environment does not
carry over. Chain: `cd app && . ./android-env.sh && ./gradlew …`. Never
pipe `source` into `head`/`grep` (subshell discards the exports).
- The shared emulator AVD is named `tdep` — one emulator across the Android
projects on this machine, not one per repo.
- Long-running servers launched from an agent must be fully detached
(`setsid nohup … & disown -h`, verify `PPID 1`), and every
`pgrep -f`/`pkill -f` pattern needs its first character bracketed
(`[a]i-server`) in **every** occurrence in the command, or the pattern
matches the shell running it. Full explanation in dev-updater's
`AGENTS.md` — it bites exactly the same way here.