# Office1 service re-IP -- move NetBox + Tailscale into the D-120 static band

**EXECUTED 2026-07-15** (D-120 ADOPTED, option (a)). Both services re-IP'd, no wipe, verified on the
wire. The PENDING-VERIFICATION markers below are now RESOLVED with the measured method; this is the
reference procedure the DCs reuse for their own compose /24s. It remains a gated mutation on running
services -- present each step, justify it as minimal, get individual approval -- if ever re-run.

## Target (EXECUTED)

    office1-netbox     10.10.1.201 -> 10.10.1.10   (static, band .2-.49)
    office1-tailscale  10.10.1.202 -> 10.10.1.11   (static, band .2-.49)

Both VMs are MAAS-COMPOSED LXD VMs on `voffice1`; their addresses come from MAAS. The edge's static
route `10.10.1.0/24 -> 10.10.0.20` is /24-wide and is UNAFFECTED by the change.

## Preconditions (READ-ONLY)

1. D-120 ADOPTED; target addresses confirmed.
2. Coordinate with sandbox/C2 work -- the NetBox re-IP briefly interrupts the sandbox NetBox.
3. Record current state: `office1-netbox 10.10.1.201`, `office1-tailscale 10.10.1.202` (as-built).
4. Access: `ssh -J jessea123@10.10.0.20 ubuntu@10.10.1.201` (and `.202`); MAAS on `voffice1`
   (`10.10.0.20`), secrets in `/root/maas-secrets/` on the box (operator-held).

## Step 0 -- Confirm the recovery backstop FIRST (READ-ONLY, blocking)

Before severing any network path, prove `lxc exec`/`console` reaches each guest independent of guest
networking -- it rides the LXD socket, so it survives the re-IP and is the recovery path if the new
address does not come up. On `voffice1`:

    sudo -n lxc list                                   # instance names (office1-netbox / -tailscale)
    sudo -n lxc exec office1-netbox -- hostname        # must return -- proves the backstop

## Step 1 -- MAAS: NOT AVAILABLE on a Deployed machine (finding)

**MAAS refuses interface edits on a Deployed machine:** `interface unlink-subnet ...` returns
"Cannot unlink subnet interface because the machine is not New, Ready, Allocated, or Broken." The
only way to make it Ready is Release, which **WIPES the OS** -- forbidden (it destroys the seeded
sandbox). So the MAAS-model update is skipped and MAAS-model drift is ACCEPTED (MAAS keeps showing
`.201`/`.202` `mode=auto`). Drift reconciles for free at the next teardown/redeploy. `.10`/`.11` are
off MAAS's auto-assign path: MAAS allocates nodes from `.201+` upward (observed -- the two services
themselves landed at `.201`/`.202`), so the low `.2-.49` band is never auto-handed out. (NB: the
whole non-dynamic space is MAAS's static pool, so "outside the dynamic range" is NOT the guarantee --
the allocation DIRECTION is.)

> **DC replication:** a DC building fresh should static-assign `.2-.49` in MAAS BEFORE deploy (a
> Ready machine DOES accept `interface link-subnet <sid> <iface> subnet=<id> mode=STATIC
> ip_address=<addr>`), so there is no drift. The Deployed-block only bit Office1 because its services
> were already deployed in the node band.

## Step 2 -- Set the address on the GUEST (both files, via lxc exec) [MUTATION: gated]

Drive this over `lxc exec` from `voffice1`, NOT SSH-to-the-guest-IP: the LXD socket is immune to the
`netplan apply` that swaps the address, so your control channel never drops. Edit BOTH files (netplan
takes effect now; the curtin cfg makes it survive a reboot / cloud-init re-render), then apply:

    for f in /etc/netplan/50-cloud-init.yaml /etc/cloud/cloud.cfg.d/50-curtin-networking.cfg; do
      sudo -n lxc exec office1-netbox -- cp "$f" "$f.bak-reip201"
      sudo -n lxc exec office1-netbox -- sed -i 's#10.10.1.201/24#10.10.1.10/24#' "$f"
    done
    sudo -n lxc exec office1-netbox -- netplan apply     # gateway4-deprecated warnings are benign
    sudo -n lxc exec office1-netbox -- ip -4 addr show enp5s0 | grep inet   # verify .10, do not assume

(Same for `office1-tailscale`: `.202/24 -> .11/24`, backups `.bak-reip202`.)

## Step 3 -- Each SERVICE's own address config [MEASURED]

- **NetBox (`office1-netbox`, netbox-docker):** binds `0.0.0.0:8000`, follows the host IP.
  `ALLOWED_HOSTS` is **`*`** (config default in `/opt/netbox-docker/configuration/configuration.py`;
  NOT overridden in `env/netbox.env`), so NO change and NO restart was needed -- it served at `.10`
  immediately (HTTP 200 at `http://10.10.1.10:8000/login/`).
- **Tailscale (`office1-tailscale`):** re-asserted automatically after the interface IP change --
  `BackendState=Running`, `PrimaryRoutes=["10.10.0.0/22"]` unchanged, tailnet IP `100.64.0.53`
  unchanged. NO `tailscale up` re-assert was needed. (If a future run shows the route dropped,
  re-assert with `tailscale up --advertise-routes=10.10.0.0/22`; node identity/key is unchanged.)

## Step 4 -- Verify on the wire (READ-ONLY)

    ssh -J jessea123@10.10.0.20 ubuntu@10.10.1.10 'ip -4 addr'      # netbox at .10
    curl -s -o /dev/null -w "%{http_code}\n" http://10.10.1.10:8000/api/    # 403/200 = up
    ssh -J jessea123@10.10.0.20 ubuntu@10.10.1.11 'tailscale status'        # tailscale at .11

## Step 5 -- Update the repo references + the importer allowlist [DONE 2026-07-15]

`10.10.1.201 -> 10.10.1.10` and `10.10.1.202 -> 10.10.1.11` applied across:

- **CODE (done):** `SANDBOX_HOSTS` `.201 -> .10` in `netbox/roles-aggregates-import.py`,
  `netbox/dc-dc-prefixes-import.py`, and `netbox/d115-office-carve.py`;
  `tests/dc-dc-prefixes-import/test_logic.py` (both sandbox-URL hosts).
  `tests/roles-aggregates-import/run-tests.sh` only greps for the literal `SANDBOX_HOSTS` token and
  uses `localhost` (still in the allowlist) -- no address change needed there.
- **Docs (done):** `docs/vr1-office1-as-built.md`, `docs/session-ledger.md`,
  `docs/dc-dc-netbox-buildout-scope.md`, `docs/dc-dc-deployment-workflow.md`,
  `runbooks/dc-dc-phase1-office1-standup.md`, and D-120 itself. Dated changelogs LEFT as historical
  snapshots (they record the deploy-time `.201`/`.202`).
- Gauntlet: `bash scripts/run-tests-all.sh` + `bash scripts/repo-lint.sh` re-run green.

## Step 6 -- Record the D-120 carve in NetBox (via the sandbox loop)

Register `10.10.1.0/24`'s child ranges (static `.2-.49` / dynamic `.100-.200` / node `.201-.254`) and
the two service IP assignments in the sandbox NetBox, then feed upstream with C2. NetBox is the IPAM
authority for this; do not leave the layout recorded only in prose.

## Rollback

Restore each guest's saved netplan + curtin backups (`.bak-reip201` / `.bak-reip202`) via `lxc exec`
and `netplan apply`, then `git revert` the Step-5 repo change. MAAS needs no rollback (its model was
never changed -- see Step 1). Nothing
here is destructive -- the VMs and their data are untouched; only the L3 address changes.

## DC replication note

When a DC's compose /24 is built, its services get D-120 static-band addresses FROM DAY ONE via this
same procedure -- there is no "assign in the node band then re-IP later" for the DCs. This runbook is
the reference.
