# 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
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:-}" ;;
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)