# VR1 Office1 -- as-built inventory and connection reference

**Purpose.** The single operational answer to "what is running, where, on what address, and how do I
reach it." Every value here was MEASURED on the live system, not planned or inferred. Update it in
the SAME change that brings a host or service online -- a stale connection table is worse than none.

**Scope.** VR1 Office1 only (the site headend). DC1/DC2 get their own sections when they exist.

**Authority note.** NetBox is the IPAM apex -- this table is an OPERATIONAL reference (how to reach
things, where credentials live), NOT the address authority. Where the two ever disagree, NetBox wins
and the divergence gets fixed here. **The D-115 carve IS now imported** into the Office1 sandbox
NetBox (2026-07-13) -- site `vr1-off1`, roles `office` + `edge`, and every prefix below. It has NOT
yet been fed back upstream to `netbox.baldurkeep.com`; that is an operator-gated step.

Last measured: 2026-07-13.

---

## 1. Networks

| Network | CIDR | Where it lives | Gateway | DHCP authority | Role (D-115) |
|---|---|---|---|---|---|
| `office1-wan` | `172.30.1.0/24` | libvirt NAT net on vcloud (`virbr11`) | `172.30.1.1` (vcloud) | none (static) | **Edge** (`172.30.0.0/16`) -- simulated ISP uplink |
| `office1-local` | `10.10.0.0/24` | libvirt bridge on vcloud (`virbr2`), addressless | `10.10.0.1` (the edge) | **Kea on the OPNsense edge** (pool `.100-.199`) | **Office** -- VR1 Office1 `10.10.0.0/22` |
| `lxdbr0` | `10.10.1.0/24` | INSIDE `voffice1` (LXD-managed, NAT) | `10.10.1.1` (voffice1) | **MAAS** (dynamic `.100-.200`; deployed machines get `.201+`) | **Office** -- same `/22`, compose net |

**The DHCP split is structural.** Two authorities, two physically separate L2s. `lxdbr0` is NEVER
bridged onto `office1-local`, so Kea and MAAS cannot see each other. LXD's own dnsmasq is disabled
(`ipv4.dhcp=false`, `dns.mode=none`, `raw.dnsmasq=port=0`) and binds **no** `:53` and **no** `:67`.

**IPv6:** Office1's carve is `2602:f3e2:f01:100::/56` with office subnet `2602:f3e2:f01:100::/64`
(D-115). **NOT YET DEPLOYED** -- pinned as the next step. Note IPv6 does NOT egress the lab
(measured: v6 gateway reachable, v6 internet 100% loss), so v6 here will be LAN-local only.

---

## 2. Hosts and services

| Host | Address | What runs on it | Level |
|---|---|---|---|
| **vcloud** | `10.17.11.248` (lab), `10.10.0.10` on `virbr2`, `172.30.1.1` on `virbr11` | libvirt/KVM hypervisor; OpenTofu runs from here | L1 (itself a KVM guest) |
| **office1-opnsense** | LAN `10.10.0.1` / WAN `172.30.1.2` | **OPNsense 26.7.1** (FreeBSD base **15.1**-RELEASE-p1; 26.7 -> 26.7.1 minor update 2026-07-23 via the REST API, no reboot needed) edge router: routing, NAT, **Kea DHCP**, firewall. Plugins: `os-git-backup` (UNCONFIGURED); `os-qemu-guest-agent` **FUNCTIONAL 2026-07-23** (D-129 qga channel retrofitted via outer tofu 0/1/0 apply -- the edge bounce WAS the ruled "next scheduled restart"; `guest-ping` answers, `domifaddr --source agent` reports both legs); `os-iperf` **INSTALLED 2026-07-23** (was refused on 26.7 -- the edge required the 26.7.1 update first). Evidence: `docs/audit/g13-close-20260723.txt`. See D-129 / gate G13 (CLOSED). | L2 |
| **voffice1** | `10.10.0.20` (Kea reservation) | **MAAS 3.7.2** region+rack + PostgreSQL 16.14; **LXD 5.21.5** (registered to MAAS as VM host `office1-lxd`) | L2 |
| **office1-netbox** | `10.10.1.10` | **NetBox 4.6** (netbox-docker; `/opt/netbox-docker`, `docker compose up -d`) on `:8000`; **`restart: unless-stopped`** on all 5 services (2026-07-17 -- survives instance reboot) | L3 (LXD VM) |
| **office1-tailscale** | `10.10.1.11` / tailnet **`100.64.0.53`** (`fd7a:115c:a1e0::35`) | **Tailscale 1.98.8**, subnet router advertising `10.10.0.0/22`. Joined the **SELF-HOSTED** control plane `https://tailscale.baldurkeep.com:443` | L3 (LXD VM) |
| ~~office1-gitbucket~~ | -- | **NOT BUILT -- AND WILL NOT BE (D-116).** `git.baldurkeep.com` remains the git service of record; new repos are created there as deployment tasks require. | -- |

`office1-netbox` and `office1-tailscale` are **MAAS-composed LXD VMs** (D-114) -- MAAS enlisted,
commissioned, deployed and powers them. The `voffice1` LXD host is `office1-lxd` (vm-host id 1).

**D-120 static-band re-IP (executed 2026-07-15).** Both services were moved from the MAAS
node band (`.201`/`.202`) into the static-services band: `office1-netbox .201 -> .10`,
`office1-tailscale .202 -> .11`. **MAAS-model drift, by design:** MAAS refuses to edit an
interface on a **Deployed** machine (the only lift is Release->redeploy, which would WIPE the
seeded sandbox), so MAAS's interface model still records `.201`/`.202` as `mode=auto`. The LIVE
address is set on each guest via `/etc/netplan/50-cloud-init.yaml` AND
`/etc/cloud/cloud.cfg.d/50-curtin-networking.cfg` (both edited, so it survives reboot). The drift
reconciles for free at the next teardown/redeploy, when MAAS re-renders from a corrected model.
MAAS auto-assigns nodes from `.201+` upward (observed: netbox/tailscale themselves landed at
`.201`/`.202`), so the low `.2-.49` band is off its allocation path -- no collision. (Being outside
the dynamic range is NOT itself the guarantee; the whole non-dynamic space is MAAS's static pool.)
Guest
control channel during the change was `lxc exec` (LXD socket), immune to the IP swap.

---

## 3. How to reach each thing

All access is via the Office1 service SSH key: **`~/vr1-office1-creds/office1_svc_ed25519`** on
vcloud (private half NEVER read; SEC-007 tracks its rotation).

`office1-local` (`10.10.0.0/24`) and `lxdbr0` (`10.10.1.0/24`) are **host-local to vcloud** -- not
routable from a workstation. Everything below therefore tunnels through vcloud.

| Target | From vcloud | From your workstation |
|---|---|---|
| **voffice1** (shell) | **durable (D-126):** `bash scripts/site-ssh-config.sh install --key <key>` -> `ssh voffice1`. Ad-hoc: `ssh -i <key> jessea123@10.10.0.20` | `ssh -J jessea123@10.17.11.248 jessea123@10.10.0.20` |
| **OPNsense GUI** | `https://10.10.0.1` | `ssh -L 8443:10.10.0.1:443 jessea123@10.17.11.248` -> `https://localhost:8443` |
| **OPNsense shell** | `ssh -i <key> root@10.10.0.1` | via vcloud. **root's shell is `tcsh`** -- feed commands to `sh -s`. |
| **MAAS UI** | `http://10.10.0.20:5240/MAAS` | `ssh -L 5240:10.10.0.20:5240 jessea123@10.17.11.248` -> `http://localhost:5240/MAAS` |
| **NetBox UI/API** | **durable (D-126, preferred):** `bash scripts/site-forward.sh install office1-netbox --key <key>` -> `http://127.0.0.1:8000`. Ad-hoc: `http://10.10.1.10:8000` needs the route below. | on tailnet: `http://10.10.1.10:8000` direct. Else `ssh -J jessea123@10.17.11.248 -L 8000:10.10.1.10:8000 jessea123@10.10.0.20` -> `http://localhost:8000` |
| **office1-netbox / office1-tailscale** (shell) | **durable (D-126):** `ssh office1-netbox` / `ssh office1-tailscale` (after `site-ssh-config.sh install`). Ad-hoc: `ssh -J jessea123@10.10.0.20 ubuntu@10.10.1.10` | chain both jumps |

**Routing to the compose net.** The OPNsense edge has a static route `10.10.1.0/24 -> 10.10.0.20`
(gateway object `OFFICE1_LXD_GW`, priority 255, NOT the default gateway). So any `office1-local`
client using the edge as its gateway reaches the compose net. **vcloud itself does NOT** -- its
default route is the lab, so it needs an explicit route (operator sudo):
`sudo ip route replace 10.10.1.0/24 via 10.10.0.20 dev virbr2`

**Durable vcloud->service-VM access (D-126, preferred over the sudo route above).** The route is
lost on reboot and, persisted via netplan, races libvirt (virbr2 is libvirt-managed). Instead,
`bash scripts/site-forward.sh install office1-netbox --key <key>` stands a ROOTLESS `systemctl --user`
unit holding an SSH local-forward through voffice1 -> `http://127.0.0.1:8000` reaches office1-netbox,
surviving reboot once `loginctl enable-linger jessea123` is set (one-time). Same tool + a `-J`
containment-VM hop generalizes to the DC service VMs (rows added once their IPs are MEASURED). The
NetBox API token is NOT carried by this forward -- it stays a host-local secret on office1-netbox
(the importers read it there); the forward carries only read/UI TCP.

**Tailscale front door (D-107: Office1 ONLY).** `office1-tailscale` is a subnet router on the
operator's **SELF-HOSTED** control plane (`https://tailscale.baldurkeep.com:443` -- NOT Tailscale's
public one). It is UP and online as `100.64.0.53`, advertising **`10.10.0.0/22`** -- exactly the
D-115 Office1 carve, so "Office1 only" is enforced by the CARVE, not by policy alone.

- **Brought up with:** `--login-server=https://tailscale.baldurkeep.com:443 --authkey=<file>
  --advertise-routes=10.10.0.0/22 --accept-routes --accept-dns=false --hostname=office1-tailscale`
- **`--accept-dns=false` is DELIBERATE** (a deviation from the operator's usual command): this is a
  MAAS-managed machine and MAAS's bind9 serves its internal names. Letting the tailnet take over DNS
  would break that resolution. Flip it if MagicDNS is wanted here.
- **ROUTE IS ENABLED** (operator, 2026-07-13). The `10.10.0.0/22` subnet route is live on the
  self-hosted control server and **verified from the workstation** -- the OPNsense GUI answers
  directly at `https://10.10.0.1` with **no SSH tunnel**. This SUPERSEDES the tunnel commands in the
  table above: from a tailnet-joined workstation, every Office1 address in the `/22` is reachable
  directly. The tunnels remain valid as a fallback when off-tailnet.
- The auth key was consumed by path from a `0600` file and **shredded from the node** after use --
  tailscaled holds its own node key now. It never entered an operator session or the repo.

Once the route is enabled, the whole Office1 carve -- edge GUI, MAAS UI, NetBox -- is reachable from
the tailnet with **no SSH tunnels at all**, which supersedes most of the tunnel commands above.

---

## 4. Where the credentials live (NEVER commit; never print)

| What | Location | Notes |
|---|---|---|
| Office1 service SSH key | `~/vr1-office1-creds/office1_svc_ed25519{,.pub}` (vcloud) | SEC-007 -- rotation obligation |
| OPNsense API key/secret | `~/vr1-office1-creds/opnsense-api.txt` (vcloud) | used by `scripts/opnsense-api.sh` |
| OPNsense root password | `~/vr1-office1-creds/opnsense-root-password` (vcloud) | GUI login |
| MAAS admin password + API key; DB password; LXD trust password | `/root/maas-secrets/` on **voffice1** (`0600`) | generated on the box |
| NetBox SECRET_KEY, admin password, API token | `/root/netbox-secrets/` on **office1-netbox** (`0600`) | SECRET_KEY rotated off netbox-docker's PUBLIC default |
| NetBox (upstream / v1-draft) token env | `~/vr1-office1-creds/vr1-netbox.env` (vcloud) | `NETBOX_URL`+`NETBOX_TOKEN` for `netbox.baldurkeep.com`. SEC-006 -- burned, revoke at completion. The SANDBOX token is NOT here -- it is on office1-netbox (`/root/netbox-secrets/`). |
| Office1 edge config env | `~/vr1-office1-creds/vr1-office1.env` (vcloud) | OPNsense WAN/LAN/DHCP/root-hash/SSH-key vars. Consolidated 2026-07-15 (SEC-009). |
| VR1 OpenTofu env | `~/vr1-office1-creds/vr1-stage1.env` (vcloud) | `TF_VAR_*` incl. `TF_VAR_maas_api_key`. Consolidated + chmod 600 2026-07-15 (was loose + 0664, SEC-009). TF_VAR pool-path names updated to D-119 (`vr1_dc0`/`vr1_dc1`). |
| Tailscale auth key (self-hosted tailnet) | `~/vr1-office1-creds/tailscale-authkey.txt` (vcloud, `0600`) | Operator-supplied, 48 chars. Consumed by PATH, never printed. The copy on the node was SHREDDED after `tailscale up`. Rotate/revoke on the control server at v1 close. |

**Credential consolidation (SEC-009, standing convention):** ALL sensitive + env/config files for a
site live in one `~/<site>-creds/` folder (0700; files 0600; public keys 0644) -- no loose env files
in `~`. When DC deployment starts, `~/vr1-dc0-creds/` and `~/vr1-dc1-creds/` follow the same pattern
from day one. See `docs/security-ledger.md` SEC-009.

**NetBox API token format (4.6):** the wire form is `nbt_<key>.<plaintext>`. The API's `token` field
alone is NOT usable -- present it raw and you get `403 Invalid v1 token`. See
`docs/archive/changelogs/changelog-20260713-office1-netbox-deployed.md`.

---

## 5. Standing traps for this site (the short list)

1. **DHCP on the edge is API-MANAGED.** A rendered `config.xml` push would CLOBBER it. That path is
   DELETED (D-113(a2)); configure via `scripts/opnsense-api.sh`.
2. **OPNsense gateways: `defaultgw` in the API output is DERIVED FROM `priority`** (lower = more
   preferred), not from what you send. Adding a LAN gateway with a lower priority than `WAN_GW`
   SILENTLY STEALS THE DEFAULT ROUTE and kills site egress -- this happened on 2026-07-13. Always
   add the gateway, READ THE CONFIG BACK, confirm `WAN_GW` is still default, and only THEN
   `reconfigure`. `WAN_GW` is now priority 1; `OFFICE1_LXD_GW` is 255.
3. **root's shell on the edge is `tcsh`** -- `$(...)` and `2>&1` fail (quietly, or with "Ambiguous
   output redirect"). Feed remote commands to `sh -s`.
4. **`lxc` reads config from stdin** -- in a piped script every `lxc` call needs `</dev/null` or it
   eats the rest of the script.
5. **MAAS's bind9 intermittently SERVFAILs a first-time external lookup** and succeeds on retry
   (seen twice: docker registry, tailscale.com). Retry before diagnosing.
6. **A `tofu apply` touching a `libvirt_domain`'s devices BOUNCES THE GUEST**, even when the plan
   says "updated in-place". Schedule it as an outage.

---

## 6. IPAM -- what NetBox now says about this site (D-115 carve, imported 2026-07-13)

The Office1 **sandbox** NetBox (`10.10.1.10:8000`) was seeded from the upstream draft (129 objects:
1 RIR, 23 roles, 5 regions, 10 sites, 90 prefixes) and then had the D-115 carve applied on top.
Site **`vr1-off1` ("VR1 Off1")**, region VR1.

| Prefix | Role | Status | What it is |
|---|---|---|---|
| `10.10.0.0/16` | office | container | The Office role's v4 block -- `/22` per office (D-115) |
| `10.10.0.0/22` | office | container | **VR1 Off1's office `/22`** (`.2`/`.3` spare) |
| `10.10.0.0/24` | office | active | `office1-local` LAN -- Kea DHCP on the edge |
| `10.10.1.0/24` | office | active | `lxdbr0` LXD compose net -- MAAS DHCP |
| `172.30.0.0/16` | **edge** | container | The NEW Edge role -- simulated ISP/WAN (mirrors v6 `2602:f3e2:fe::/48`) |
| `172.30.1.0/24` | edge | active | `office1-wan` -- the simulated ISP uplink |
| `2602:f3e2:f01:100::/56` | office | container | VR1 Off1's v6 `/56` (mirrors VR0 Off0's `e01:100::/56`) |
| `2602:f3e2:f01:100::/64` | office | active | Office subnet -- **NOT YET DEPLOYED** (v6 does not egress the lab) |

**Every one of these addresses was already running.** D-115 blesses what is deployed rather than
renumbering it -- the carve cost ZERO renumber. What it fixed is that `10.10.0.0/24`, `10.10.1.0/24`
and `172.30.1.0/24` were **SQUAT**: on the wire with no allocation behind them.

**Naming (D-117 + D-119):** VR1's datacenters are **vr1-dc0** (first) and **vr1-dc1** (second) -- the selector IS the NetBox apex slug. Bare `dcN` is RETIRED (it meant VR0's live cloud in `lib-net.sh`). The office KEEPS its number: **Office1** / `vr1-off1` (matching the apex, which already held both
sites) but **the office keeps its number** -- it is **Off1**. VR1 therefore reads DC0/DC1/**Off1**
while VR0 reads DC0/DC1/Off0. Accepted cosmetic asymmetry; the site is NEW upstream so nothing was
renamed and **no deployed object changed**.

**Not yet upstream.** The sandbox is a simulation. Feeding this carve back to
`netbox.baldurkeep.com` is a separate, operator-gated write (`netbox/d115-office-carve.py` refuses a
non-sandbox target unless given `--yes-write-upstream`).

**Reproduce the sandbox from scratch:**
```
python3 netbox/prod-draft-dump.py --out netbox/draft/vr1-draft.json     # on vcloud, READ-ONLY
# then on office1-netbox, with the sandbox token:
python3 sandbox-seed.py --draft draft/vr1-draft.json --commit
python3 d115-office-carve.py --commit
```
