# Slice B + C: Add fips and c-relay to the n-OS-tr ISO

## Status: **Ready for implementation**

This document specifies the work required to enable the [fips](../includes/fips)
mesh-routing daemon and the [c-relay](../includes/c-relay) Nostr relay on the
live ISO built by [`build.sh`](../build.sh), replacing the current Slice A
(ginxsom + nginx only) with the full three-service stack planned in
[`iso_architecture.md`](./iso_architecture.md).

---

## 1. Design Decisions (captured from user)

| # | Topic | Decision |
|---|---|---|
| 1 | Source of c-relay binary | User-provided, already downloaded into [`includes/c-relay/`](../includes/c-relay) |
| 2 | Source of fips binaries | Build locally via `cargo deb` during `build.sh` |
| 3 | c-relay admin nsec strategy | **Ephemeral** — c-relay self-provisions per boot, admin nsec captured from journald |
| 4 | fips identity | **Ephemeral** — new npub per boot, no persistent key management for this slice |
| 5 | fips peer config | Connect to the same public bootstrap node used elsewhere in the repo: `npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98` at `217.77.8.91:2121` (UDP), per [`examples/sidecar-nostr-relay/.env`](../includes/fips/examples/sidecar-nostr-relay/.env:9) et al. |
| 6 | fips transport | **Ethernet** enabled, plus UDP for the static peer |
| 7 | fips-gateway | Disabled (default) |
| 8 | c-relay exposure | nginx reverse-proxies `wss://host/relay` → `ws://127.0.0.1:8888/`; nginx reverse-proxies `/admin/` → `http://127.0.0.1:8888/api/` |
| 9 | Scope | Full Slice B+C — services, nginx wiring, package deps, smoke test, MOTD, README updates |
| 10 | Key management / identity continuity | Out of scope for this slice — revisit in a later pass |

---

## 2. What Already Exists (Slice A baseline)

Confirmed present and working, per source inspection:

- [`iso/auto/config`](../iso/auto/config) — live-build config
- [`iso/config/package-lists/base.list.chroot`](../iso/config/package-lists/base.list.chroot) — nginx-light, spawn-fcgi, ca-certificates, curl, jq, vim, htop
- [`iso/config/includes.chroot/etc/systemd/system/ginxsom.service`](../iso/config/includes.chroot/etc/systemd/system/ginxsom.service)
- [`iso/config/includes.chroot/etc/systemd/system/n-os-tr-firstboot.service`](../iso/config/includes.chroot/etc/systemd/system/n-os-tr-firstboot.service)
- [`iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf`](../iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf) — `:443` TLS, Blossom routing, static landing page
- [`iso/config/includes.chroot/usr/local/sbin/n-os-tr-firstboot`](../iso/config/includes.chroot/usr/local/sbin/n-os-tr-firstboot) — generates ginxsom nsec + self-signed TLS
- [`iso/config/includes.chroot/usr/local/bin/n-os-tr-smoketest`](../iso/config/includes.chroot/usr/local/bin/n-os-tr-smoketest) — 5 PASS + 8 SKIP placeholder
- [`iso/config/includes.chroot/usr/local/bin/nak`](../iso/config/includes.chroot/usr/local/bin/nak) — Nostr CLI helper
- [`iso/config/includes.chroot/usr/local/bin/ginxsom/ginxsom-fcgi`](../iso/config/includes.chroot/usr/local/bin/ginxsom/ginxsom-fcgi) — Blossom FastCGI binary
- [`iso/config/hooks/live/0020-enable-services.hook.chroot`](../iso/config/hooks/live/0020-enable-services.hook.chroot) — enables nginx, ginxsom, firstboot

Slice A smoke test passes `5 PASS, 0 FAIL, 8 SKIP` on a freshly-booted USB.

---

## 3. What Needs to Change (target state)

```mermaid
flowchart LR
    subgraph Build [Build-time build.sh]
        BS[build.sh]
        CDB[cargo deb in<br/>includes/fips]
        FDeb[iso/local-artifacts/fips_0.3.0_amd64.deb]
        CRX[includes/c-relay/c_relay_x86]
        CRC[iso/local-artifacts/c_relay_x86]
        BS --> CDB --> FDeb
        BS --> CRC
    end
    subgraph Chroot [live-build chroot phase]
        H1[0010-fetch-artifacts.hook.chroot]
        FDeb --> H1
        CRC --> H1
        H1 -->|dpkg -i| FInst[/usr/bin/fips etc]
        H1 -->|install -m 0755| CInst[/opt/c-relay/c_relay_x86]
        H1 -->|adduser --system| CUsr[c-relay system user]
        H1 -->|write fips.yaml| FCfg[/etc/fips/fips.yaml]
    end
    subgraph Boot [Runtime at boot]
        Fb[n-os-tr-firstboot]
        Fips[fips.service]
        FDns[fips-dns.service]
        CRel[c-relay.service]
        Gx[ginxsom.service]
        Ng[nginx.service]
        Fb --> Gx
        Fips --> FDns
        Ng -->|wss /relay| CRel
        Ng -->|http /admin/| CRel
    end
```

### Summary of changes

| Area | Change |
|---|---|
| Build system | [`build.sh`](../build.sh) gains a pre-build step: build fips `.deb` via `cargo deb` and stage artifacts into [`iso/local-artifacts/`](../iso/local-artifacts) |
| Artifact layout | New directory [`iso/local-artifacts/`](../iso/local-artifacts) holds `fips_*_amd64.deb` and `c_relay_x86` — gitignored |
| Chroot hook (new) | [`iso/config/hooks/live/0010-fetch-artifacts.hook.chroot`](../iso/config/hooks/live/0010-fetch-artifacts.hook.chroot) installs those two artifacts into the chroot |
| Package list | [`iso/config/package-lists/base.list.chroot`](../iso/config/package-lists/base.list.chroot) adds runtime deps: `libdbus-1-3`, `iproute2`, `nftables` (optional for gateway mode later) |
| fips config | New [`iso/config/includes.chroot/etc/fips/fips.yaml`](../iso/config/includes.chroot/etc/fips/fips.yaml) overriding the package default with ethernet + static UDP peer enabled |
| c-relay service | New [`iso/config/includes.chroot/etc/systemd/system/c-relay.service`](../iso/config/includes.chroot/etc/systemd/system/c-relay.service) — adapted from [upstream unit](../includes/c-relay/systemd/c-relay.service) |
| c-relay user | Chroot hook creates `c-relay` system user + `/opt/c-relay/` working dir |
| nginx config | [`iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf`](../iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf) gains `/relay` wss upgrade and `/admin/` proxy locations |
| Service-enable hook | [`0020-enable-services.hook.chroot`](../iso/config/hooks/live/0020-enable-services.hook.chroot) also enables `fips.service`, `fips-dns.service`, `c-relay.service` |
| Smoke test | [`n-os-tr-smoketest`](../iso/config/includes.chroot/usr/local/bin/n-os-tr-smoketest) replaces the 8 SKIPs with real checks |
| MOTD / README | First-boot script and docs updated to surface how to find c-relay admin nsec and fips identity |
| `.gitignore` | Add `iso/local-artifacts/` |

---

## 4. Concrete Artifact Details

### 4.1 c-relay static binary

- **Source:** [`includes/c_relay_static_x86_64`](../includes/c_relay_static_x86_64) — user-downloaded static-musl build, already present at the top level of `includes/` (NOT inside `includes/c-relay/`). Filename confirmed by directory listing.
- **Staged as:** `iso/config/includes.chroot_before_packages/live-artifacts/c_relay_x86` (canonical name used by the chroot hook and systemd unit — we rename on copy so `c-relay.service` can reference a stable filename regardless of which upstream build variant the user drops in).
- **Target in chroot:** `/opt/c-relay/c_relay_x86`, mode `0755`, owner `c-relay:c-relay`.
- **Copied by:** `build.sh` copies [`includes/c_relay_static_x86_64`](../includes/c_relay_static_x86_64) → staging area → chroot hook installs to `/opt/c-relay/`.

### 4.2 `iso/local-artifacts/fips_0.3.0_amd64.deb`

- **Source:** built by `build.sh` via `cd includes/fips && packaging/debian/build-deb.sh`. Requires `cargo-deb` installed on the build host (`cargo install cargo-deb` if absent; `build.sh` will check and fail clearly).
- **Contents (per [`Cargo.toml`](../includes/fips/Cargo.toml:70-83)):**
  - `/usr/bin/fips`, `/usr/bin/fipsctl`, `/usr/bin/fipstop`, `/usr/bin/fips-gateway`
  - `/etc/fips/fips.yaml` (package default — will be overridden by our includes.chroot version)
  - `/etc/fips/hosts`
  - `/lib/systemd/system/fips.service`, `/lib/systemd/system/fips-dns.service`, `/lib/systemd/system/fips-gateway.service`
  - `/usr/lib/fips/fips-dns-setup`, `/usr/lib/fips/fips-dns-teardown`
  - `/usr/lib/tmpfiles.d/fips.conf`
- **Runtime deps (per Cargo.toml):** `libc6, systemd, libdbus-1-3`, recommends `bluez` (skipped — not needed for non-BLE).
- **Installed by:** chroot hook runs `dpkg -i /tmp/artifacts/fips_*_amd64.deb` then cleans up. The postinst enables `fips.service` and `fips-dns.service` automatically.

### 4.3 `iso/config/includes.chroot/etc/fips/fips.yaml`

Overrides the Debian-packaged default at `/etc/fips/fips.yaml`. The live-build
`includes.chroot` mechanism copies this on top after package install. Content:

```yaml
# n-OS-tr fips configuration.
# Ephemeral identity: new keypair every boot (default).
# To make identity persistent across reboots on a live USB with a
# persistence partition, set node.identity.persistent: true.

node:
  identity:
    # ephemeral by default

tun:
  enabled: true
  name: fips0
  mtu: 1280

dns:
  enabled: true
  port: 5354

transports:
  udp:
    bind_addr: "0.0.0.0:2121"
  tcp:
    bind_addr: "0.0.0.0:8443"
  ethernet:
    # Ethernet transport auto-discovers LAN peers.
    # interface: omitted → fips picks the first non-loopback interface.
    # Override with an explicit interface name if your board uses
    # something other than the default (e.g. enp5s0, eno1, eth0).
    discovery: true
    announce: true
    auto_connect: true
    accept_connections: true

# Static peer for internet bootstrap.
# Matches the same "fips-test-node" / "vps-chi" public node used across
# the repo's example configs (see includes/fips/examples/... and
# includes/fips/testing/sidecar/.env).
peers:
  - npub: "npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98"
    alias: "fips-test-node"
    addresses:
      - transport: udp
        addr: "217.77.8.91:2121"
    connect_policy: auto_connect
```

Notes:
- Omitting `ethernet.interface` relies on fips's auto-selection logic. If that
  doesn't pick a sensible interface, add `interface: "eth0"` (verified post-boot
  via `ip link show`).
- Keeping the `udp` bootstrap peer in addition to `ethernet` gives the mesh
  internet reachability even when the LAN has no other fips peers.

### 4.4 `iso/config/includes.chroot/etc/systemd/system/c-relay.service`

Adapted from [upstream](../includes/c-relay/systemd/c-relay.service) with
minor tweaks for the live environment:

```ini
[Unit]
Description=C Nostr Relay (n-OS-tr slice C)
Documentation=file:///usr/share/doc/n-os-tr/README
After=network.target n-os-tr-firstboot.service
Wants=network-online.target

[Service]
Type=simple
User=c-relay
Group=c-relay
WorkingDirectory=/opt/c-relay
Environment=DEBUG_LEVEL=0
# c-relay self-provisions admin + relay keys on first start and prints
# the admin nsec to stdout (captured by journald).
ExecStart=/opt/c-relay/c_relay_x86 --debug-level=${DEBUG_LEVEL}
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=c-relay

# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/c-relay
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6
LimitNOFILE=65536
LimitNPROC=4096

[Install]
WantedBy=multi-user.target
```

### 4.5 Updated nginx site — `/relay` wss + `/admin/` proxy

New locations appended to [`iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf`](../iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf) inside the existing `server { listen 443 ssl; ... }` block, before the ginxsom FastCGI blocks:

```nginx
    # --- c-relay reverse proxy (slice C) ---------------------------------

    # Nostr relay WebSocket: wss://host/relay → ws://127.0.0.1:8888/
    # The client library expects a WebSocket upgrade on /relay; c-relay
    # itself listens on "/" so we rewrite and proxy to its root.
    location = /relay {
        proxy_pass http://127.0.0.1:8888/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
    }

    # NIP-11 relay info doc (available at wss://host/relay with
    # Accept: application/nostr+json). Proxied to the same backend.
    # If you want /relay (GET without upgrade) to return NIP-11 too,
    # the location above already handles it — c-relay's NIP-11 handler
    # triggers based on the Accept header, not the path.

    # c-relay embedded web admin UI: https://host/admin/ → http://127.0.0.1:8888/api/
    location /admin/ {
        proxy_pass http://127.0.0.1:8888/api/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
```

Notes:
- The `/admin/` path deliberately overlaps with a Blossom admin path, but
  Blossom's admin routes go via the FastCGI `@ginxsom` named location and
  c-relay's admin is explicitly reverse-proxied — nginx's longest-prefix
  match means `/admin/` → c-relay wins for GET/POST, and ginxsom admin is
  reached via a different path (if ginxsom has one). **Potential conflict
  with Slice A** — need to confirm ginxsom doesn't rely on `/admin/`.
  Current [n-os-tr.conf:53](../iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf:53) has `location /admin/  { try_files $uri @ginxsom; }` — this will need to be **removed or renamed** (e.g. `/blossom-admin/`) to avoid collision. See §6 Open Questions.
- The landing page at `/` (static `/var/www/html/index.html`) is unaffected.

### 4.6 New chroot hook — `0010-fetch-artifacts.hook.chroot`

```sh
#!/bin/sh
# Install c-relay and fips into the chroot from iso/local-artifacts/.
set -e

ART=/live-artifacts

if [ ! -d "$ART" ]; then
    echo "[fetch-artifacts] no $ART; skipping" >&2
    exit 0
fi

# --- c-relay -----------------------------------------------------------
if [ -f "$ART/c_relay_x86" ]; then
    echo "[fetch-artifacts] installing c-relay binary"
    # Create system user and directory
    if ! getent passwd c-relay >/dev/null 2>&1; then
        adduser --system --no-create-home --group --home /opt/c-relay c-relay
    fi
    mkdir -p /opt/c-relay
    install -m 0755 -o c-relay -g c-relay \
        "$ART/c_relay_x86" /opt/c-relay/c_relay_x86
    chown -R c-relay:c-relay /opt/c-relay
else
    echo "[fetch-artifacts] WARNING: $ART/c_relay_x86 not found" >&2
fi

# --- fips --------------------------------------------------------------
FIPS_DEB=$(ls "$ART"/fips_*_amd64.deb 2>/dev/null | head -1 || true)
if [ -n "$FIPS_DEB" ] && [ -f "$FIPS_DEB" ]; then
    echo "[fetch-artifacts] installing fips from $FIPS_DEB"
    dpkg -i "$FIPS_DEB" || apt-get -f install -y
else
    echo "[fetch-artifacts] WARNING: no fips_*_amd64.deb in $ART" >&2
fi
```

How `$ART` gets populated in the chroot: live-build copies
[`iso/local-artifacts/`](../iso/local-artifacts) into
`/live-artifacts/` inside the chroot via an `includes.chroot_before_packages/`
entry that bind-mounts or symlinks the directory, OR more simply, we place
the artifacts directly under
`iso/config/includes.chroot_before_packages/live-artifacts/` so live-build
copies them before package install. See §5 step 2 for the exact location.

### 4.7 Updated `build.sh`

Inserted **before** the `sudo "$REPO_ROOT/lb-wrapper.sh" config` line:

```bash
# --- Stage Slice B artifacts ------------------------------------------------
ARTIFACTS="$WORKSPACE_ISO_DIR/config/includes.chroot_before_packages/live-artifacts"
mkdir -p "$ARTIFACTS"

# 1. c-relay: copy the user-provided binary from includes/c-relay/
CRELAY_SRC=""
for candidate in \
    "$REPO_ROOT/includes/c-relay/c_relay_x86" \
    "$REPO_ROOT/includes/c-relay/build/c_relay_x86" \
    "$REPO_ROOT/includes/c-relay/c-relay" ; do
    if [[ -x "$candidate" ]]; then CRELAY_SRC="$candidate"; break; fi
done
if [[ -z "$CRELAY_SRC" ]]; then
    echo "[build.sh] ERROR: no c-relay binary found. Looked for:" >&2
    echo "  includes/c-relay/c_relay_x86" >&2
    echo "  includes/c-relay/build/c_relay_x86" >&2
    echo "  includes/c-relay/c-relay" >&2
    exit 1
fi
cp "$CRELAY_SRC" "$ARTIFACTS/c_relay_x86"
chmod 0755 "$ARTIFACTS/c_relay_x86"
echo "[build.sh] staged c-relay from $CRELAY_SRC"

# 2. fips: build the .deb (requires cargo-deb)
if ! command -v cargo-deb >/dev/null 2>&1; then
    echo "[build.sh] ERROR: cargo-deb not installed. Run: cargo install cargo-deb" >&2
    exit 1
fi
pushd "$REPO_ROOT/includes/fips" >/dev/null
packaging/debian/build-deb.sh
popd >/dev/null
FIPS_DEB=$(ls "$REPO_ROOT/includes/fips/deploy"/fips_*_amd64.deb | sort | tail -1)
if [[ -z "$FIPS_DEB" || ! -f "$FIPS_DEB" ]]; then
    echo "[build.sh] ERROR: fips .deb build produced no artifact" >&2
    exit 1
fi
cp "$FIPS_DEB" "$ARTIFACTS/"
echo "[build.sh] staged fips .deb: $(basename "$FIPS_DEB")"
```

### 4.8 Updated package list — `base.list.chroot`

Add runtime deps needed by fips (already pulled in transitively by the .deb
but explicit is safer) and by the smoke test:

```
nginx-light
spawn-fcgi
libfcgi-bin
ca-certificates
openssl
curl
jq
vim
htop
# Slice B/C additions:
libdbus-1-3
iproute2
nftables
```

### 4.9 Updated `0020-enable-services.hook.chroot`

```sh
#!/bin/sh
set -e

# Nginx
rm -f /etc/nginx/sites-enabled/default
ln -sf /etc/nginx/sites-available/n-os-tr.conf /etc/nginx/sites-enabled/n-os-tr.conf
systemctl enable nginx.service

# Slice A
systemctl enable ginxsom.service
systemctl enable n-os-tr-firstboot.service

# Slice B (fips) — units installed by the .deb postinst already enable
# them, but we're explicit for idempotence:
systemctl enable fips.service || true
systemctl enable fips-dns.service || true

# Slice C (c-relay)
systemctl enable c-relay.service
```

### 4.10 Updated smoke test

Replace the fips and c-relay SKIP blocks in [`n-os-tr-smoketest`](../iso/config/includes.chroot/usr/local/bin/n-os-tr-smoketest) with real checks:

```bash
# -------------------- fips mesh daemon -----------------------------
section "fips mesh daemon"

check "fips.service active" systemctl is-active fips.service
check "fips-dns.service active" systemctl is-active fips-dns.service
check "fips0 interface present" ip link show fips0

# fipsctl identity should return an npub-shaped string
npub=$(fipsctl identity 2>/dev/null | head -1 | tr -d '[:space:]')
case "$npub" in
    npub1*) pass "fipsctl identity returns npub" "${npub:0:20}..." ;;
    *)      fail "fipsctl identity returns npub" "got: ${npub:-<empty>}" ;;
esac

# fips UDP transport should be listening
check "fips UDP bound on :2121" bash -c 'ss -ulnp 2>/dev/null | grep -q ":2121"'

# -------------------- c-relay nostr relay --------------------------
section "c-relay nostr relay"

check "c-relay.service active" systemctl is-active c-relay.service
check "c-relay listening on :8888" bash -c 'ss -tlnp 2>/dev/null | grep -q ":8888"'

# The admin nsec should have been logged during first startup
if journalctl -u c-relay.service --no-pager 2>/dev/null | \
    grep -q "Admin Private Key:"; then
    pass "admin nsec logged in journal" "found"
else
    fail "admin nsec logged in journal" "grep miss"
fi

# wss end-to-end via nginx, using the shipped nak:
if timeout 10 nak req --relay wss://localhost/relay -k 1 -l 1 \
    >/dev/null 2>&1; then
    pass "wss://localhost/relay answers REQ" "ok"
else
    fail "wss://localhost/relay answers REQ" "timeout or error"
fi

# NIP-11 relay info doc
nip11=$(curl -sk -H 'Accept: application/nostr+json' \
    https://localhost/relay 2>/dev/null)
if echo "$nip11" | jq -e '.name' >/dev/null 2>&1; then
    pass "NIP-11 info doc reachable" "name=$(echo "$nip11" | jq -r .name)"
else
    fail "NIP-11 info doc reachable" "no JSON .name"
fi

# c-relay embedded admin UI via nginx
status=$(curl -sk -o /dev/null -w '%{http_code}' https://localhost/admin/ 2>/dev/null)
case "$status" in
    200|302|401) pass "admin UI reachable via nginx" "HTTP $status" ;;
    *)           fail "admin UI reachable via nginx" "HTTP $status" ;;
esac
```

Target final summary: `~15 PASS / 0 FAIL / 0 SKIP`.

### 4.11 MOTD and README updates

The [`n-os-tr-firstboot`](../iso/config/includes.chroot/usr/local/sbin/n-os-tr-firstboot) script's MOTD block gains additional hints:

```
  n-OS-tr — Nostr-addressed mesh node (preview)

  Ginxsom (Blossom) server pubkey (hex):
      $GINXSOM_SERVER_PUBKEY
      retrieve nsec: cat /var/lib/n-os-tr/keys/ginxsom.nsec

  c-relay Nostr relay:
      wss://$(hostname)/relay         (admin nsec: journalctl -u c-relay | grep 'Admin Private Key')
      https://$(hostname)/admin/      (web admin UI)

  fips mesh daemon:
      fipsctl identity                (show npub)
      fipsctl peers                   (list peers)

  Quick health check:      n-os-tr-smoketest
```

The [`iso/config/includes.chroot/usr/share/doc/n-os-tr/README`](../iso/config/includes.chroot/usr/share/doc/n-os-tr/README) gets corresponding additions.

---

## 5. Build & Deploy Sequence

1. **Place c-relay binary** at `includes/c-relay/c_relay_x86` (or one of the
   fallback paths `build.sh` checks). Ensure it's `chmod +x` and runs standalone.
2. **Install `cargo-deb`** on the build host: `cargo install cargo-deb`.
3. **Edit** all files listed in §3 according to §4.
4. **Move artifacts staging location:**
   - New directory: `iso/config/includes.chroot_before_packages/live-artifacts/`
     — gitignored.
   - `build.sh` writes c-relay binary and fips .deb here.
   - `0010-fetch-artifacts.hook.chroot` reads from `/live-artifacts/` inside
     the chroot (live-build copies the `includes.chroot_before_packages` tree
     into the chroot root before packages install).
5. **Add** `iso/config/includes.chroot_before_packages/live-artifacts/` to
   [`.gitignore`](../.gitignore).
6. **Full clean build:** `sudo ./build.sh --clean` (needed because package
   list changed).
7. **Burn** the resulting `iso/live-image-amd64.hybrid.iso` to USB with `dd`.
8. **Boot** on the B650I machine.
9. **Verify:** run `n-os-tr-smoketest` — expect all checks to PASS.

---

## 6. Open Questions / Known Conflicts

### 6.1 nginx `/admin/` path collision
Slice A's [n-os-tr.conf:53](../iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf:53)
routes `/admin/` to ginxsom. Slice C wants `/admin/` for c-relay.
**Resolution proposed:** change Slice A's Blossom admin to `/blossom/` (if
ginxsom supports path rewriting) or `/blossom-admin/`. Need to check
ginxsom's source to see whether its admin endpoints are hardcoded to `/admin/`
or whether the FastCGI layer can remap them.

### 6.2 Fips ethernet auto-interface selection
If the B650I's Ethernet shows up as `enp5s0` or similar and fips's
"auto-pick first non-loopback" heuristic grabs the wrong interface (e.g. Wi-Fi
— though we explicitly said runtime Wi-Fi is fine, we don't want fips
scanning it), we may need to hardcode `ethernet.interface: "enp5s0"` in
[fips.yaml](../iso/config/includes.chroot/etc/fips/fips.yaml). **Resolution:**
check `ip link show` on first boot; update if needed.

### 6.3 Stray file `=4` in [`includes/c-relay/`](../includes/c-relay)
Shown in the directory listing but not the c-relay binary (the real binary
is [`includes/c_relay_static_x86_64`](../includes/c_relay_static_x86_64)
at the top level of `includes/`). Likely a shell-redirect typo artifact
(`command >=4 file` or similar). Safe to delete during cleanup; does not
affect the build.

### 6.4 c-relay writable database location
c-relay writes `.nrdb` files to its `WorkingDirectory` (`/opt/c-relay/`). In
a live session with ephemeral rootfs this works — the writes go to the
tmpfs union layer. For persistence-partition boots we'd want `/opt/c-relay/`
on the persistence mount. **Not addressed in this slice** per decision #10.

### 6.5 fips running as root
The fips Debian package runs fips as root (needs TUN + raw sockets). The
hardening in [`fips.service`](../includes/fips/packaging/debian/fips.service:17-22)
is meaningful but fips itself is privileged. Acceptable for this slice.

### 6.6 Reverse proxy's X-Forwarded-Proto and c-relay's NIP-42
NIP-42 requires the relay to know its own URL (for the `["relay", "url"]` tag
in the AUTH challenge). c-relay has [logic](../includes/c-relay/src/nip042.c:103)
that falls back to `ws://127.0.0.1:PORT` — this means NIP-42 challenges will
reference the internal address, not `wss://host/relay`. Clients may then
fail auth. **Mitigation:** c-relay has a configurable `relay_url` config
key (set via admin event). Post-boot procedure documented in updated README
for users who want NIP-42 to work correctly. **Not blocking for basic
operation** — the relay accepts unauthenticated connections by default.

---

## 7. Success Criteria

From a booted USB on fresh hardware with wired Ethernet:

- [ ] `systemctl status fips-dns ginxsom c-relay nginx n-os-tr-firstboot` — all active
- [ ] `fipsctl identity` — prints a valid npub
- [ ] `fipsctl peers` — at minimum lists `fips-test-node` (may show as "connecting" if UDP blocked)
- [ ] `ip link show fips0` — fips0 TUN interface up
- [ ] `journalctl -u c-relay | grep 'Admin Private Key'` — returns the nsec (once)
- [ ] `curl -k https://localhost/` — landing page
- [ ] `curl -k -I https://localhost/admin/` — returns 200 or 302 (c-relay UI)
- [ ] `nak req --relay wss://localhost/relay -k 1 -l 1` — returns `["EOSE",...]` after an empty result
- [ ] `curl -ksI https://localhost/0000000000000000000000000000000000000000000000000000000000000000` — 404 (ginxsom still works)
- [ ] `n-os-tr-smoketest` — `15 PASS / 0 FAIL / 0 SKIP`

---

## 8. Rollback

If something goes sideways during development:

- The entire Slice A → Slice B+C diff is confined to these files/directories:
  ```
  build.sh                                                      (additive step)
  iso/config/hooks/live/0010-fetch-artifacts.hook.chroot        (new)
  iso/config/hooks/live/0020-enable-services.hook.chroot        (edited)
  iso/config/package-lists/base.list.chroot                     (edited)
  iso/config/includes.chroot/etc/fips/                          (new dir)
  iso/config/includes.chroot/etc/systemd/system/c-relay.service (new)
  iso/config/includes.chroot/etc/nginx/sites-available/n-os-tr.conf (edited)
  iso/config/includes.chroot/usr/local/sbin/n-os-tr-firstboot   (edited motd)
  iso/config/includes.chroot/usr/local/bin/n-os-tr-smoketest    (edited)
  iso/config/includes.chroot/usr/share/doc/n-os-tr/README       (edited)
  iso/config/includes.chroot_before_packages/                   (new, gitignored)
  .gitignore                                                    (edited)
  ```
- A revert is a `git checkout` of those paths plus removal of
  `iso/config/includes.chroot_before_packages/`.

---

## 9. Out of Scope (deferred to later slices)

- Persistent identity (ginxsom nsec, c-relay admin nsec, fips npub survive reboots).
- Gitea release fetch for artifacts (current plan is local-only).
- c-relay `relay_url` auto-configuration for NIP-42 (manual admin event for now).
- `/admin/` conflict resolution if ginxsom truly hardcodes that path.
- fips-gateway service (disabled; users can enable manually for LAN translation).
- Tor transport for fips (transport is available but not wired into the config).
- Installer variant (live-only for now).
- arm64 build.
- Signed / reproducible builds.
- Hardening tests: SELinux, AppArmor profiles.

---

## 10. References

- [`plans/iso_architecture.md`](./iso_architecture.md) — overall target design
- [`plans/network_boot.md`](./network_boot.md) — sibling plan, paused
- [`build.sh`](../build.sh) / [`lb-wrapper.sh`](../lb-wrapper.sh) — build entry
- [`iso/auto/config`](../iso/auto/config) — live-build options
- [`includes/fips/packaging/debian/build-deb.sh`](../includes/fips/packaging/debian/build-deb.sh)
- [`includes/fips/packaging/common/fips.yaml`](../includes/fips/packaging/common/fips.yaml)
- [`includes/fips/packaging/debian/fips.service`](../includes/fips/packaging/debian/fips.service)
- [`includes/c-relay/systemd/c-relay.service`](../includes/c-relay/systemd/c-relay.service)
- [`includes/c-relay/README.md`](../includes/c-relay/README.md)
- [`includes/c-relay/API.md`](../includes/c-relay/API.md)
- [Live-build manual — includes.chroot_before_packages](https://live-team.pages.debian.net/live-manual/html/live-manual/customizing-contents.en.html)
