Add sidecar-nostr-relay example and fix openwrt CI path

Add a complete example running a strfry Nostr relay exclusively over
the FIPS mesh using the sidecar pattern. Includes Docker Compose
stack, network isolation via iptables, .fips DNS resolution, nak CLI,
build script with cross-compilation support, and documentation.

Also fix the package-openwrt.yml workflow path to match the
openwrt → openwrt-ipk directory rename.
This commit is contained in:
Origami74
2026-03-19 16:23:13 +00:00
parent 8c349f524e
commit c8b7459fbc
24 changed files with 731 additions and 1 deletions

View File

@@ -96,7 +96,7 @@ jobs:
env:
PKG_VERSION: ${{ steps.version.outputs.version }}
LLVM_STRIP: llvm-strip
run: ./packaging/openwrt/build-ipk.sh --arch ${{ matrix.build_arch }}
run: ./packaging/openwrt-ipk/build-ipk.sh --arch ${{ matrix.build_arch }}
- name: Upload artifact
uses: actions/upload-artifact@v4

View File

@@ -0,0 +1,12 @@
# FIPS sidecar default configuration.
# Override these values or create a .env.local file.
# Node identity — generate with: fipsctl keygen
# Must be set before running: export FIPS_NSEC=<your-nsec>
FIPS_NSEC=
# Peer configuration (leave empty for standalone operation)
FIPS_PEER_NPUB=npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98
FIPS_PEER_ADDR=217.77.8.91:2121
FIPS_PEER_ALIAS=vps
FIPS_PEER_TRANSPORT=udp

View File

@@ -0,0 +1,3 @@
fips
fipsctl
fipstop

View File

@@ -0,0 +1,53 @@
FROM debian:trixie-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
iproute2 iputils-ping dnsutils dnsmasq iptables \
openssh-client openssh-server python3 \
tcpdump netcat-openbsd curl iperf3 && \
rm -rf /var/lib/apt/lists/*
# Install nak (Nostr Army Knife) — detect arch and download the correct binary.
# Asset naming: nak-<version>-linux-<arch>
RUN ARCH=$(dpkg --print-architecture) && \
case "$ARCH" in \
amd64) NAK_ARCH="linux-amd64" ;; \
arm64) NAK_ARCH="linux-arm64" ;; \
armhf) NAK_ARCH="linux-arm" ;; \
*) echo "Unsupported arch: $ARCH" && exit 1 ;; \
esac && \
NAK_VERSION=$(curl -sSL --retry 3 \
"https://api.github.com/repos/fiatjaf/nak/releases/latest" \
| grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\(.*\)".*/\1/') && \
echo "Installing nak ${NAK_VERSION} for ${NAK_ARCH}" && \
curl -sSL --retry 3 \
"https://github.com/fiatjaf/nak/releases/download/${NAK_VERSION}/nak-${NAK_VERSION}-${NAK_ARCH}" \
-o /usr/local/bin/nak && \
chmod +x /usr/local/bin/nak && \
nak --version
# Setup SSH server with no authentication (test only!)
RUN mkdir -p /var/run/sshd && \
ssh-keygen -A && \
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
sed -i 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config && \
sed -i 's/UsePAM yes/UsePAM no/' /etc/ssh/sshd_config && \
passwd -d root
# dnsmasq: forward .fips to FIPS daemon, everything else to Docker DNS
RUN printf '%s\n' \
'port=53' \
'listen-address=127.0.0.1' \
'bind-interfaces' \
'server=/fips/127.0.0.1#5354' \
'server=127.0.0.11' \
'no-resolv' \
>> /etc/dnsmasq.conf
COPY fips fipsctl fipstop /usr/local/bin/
RUN chmod +x /usr/local/bin/fips /usr/local/bin/fipsctl /usr/local/bin/fipstop
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1,49 @@
# strfry: official multi-arch image (linux/amd64 + linux/arm64)
# replaces the nostr-rs-relay source build which was amd64-only.
#
# IMPORTANT: use alpine (same musl-based libc as the strfry source image) as
# the final stage. Copying the strfry binary into a glibc-based image such as
# debian:bookworm-slim causes "not found" at exec time because the musl dynamic
# linker (/lib/ld-musl-*.so.1) and its shared libraries are absent there.
FROM ghcr.io/hoytech/strfry:latest AS strfry
FROM alpine:3.18
RUN apk add --no-cache nginx
# nginx: reverse proxy port 80 (IPv4 + IPv6) → strfry 127.0.0.1:7777
RUN printf 'server {\n\
listen 80;\n\
listen [::]:80;\n\
location / {\n\
proxy_pass http://127.0.0.1:7777;\n\
proxy_http_version 1.1;\n\
proxy_read_timeout 1d;\n\
proxy_send_timeout 1d;\n\
proxy_set_header Upgrade $http_upgrade;\n\
proxy_set_header Connection "Upgrade";\n\
proxy_set_header Host $host;\n\
}\n\
}\n' > /etc/nginx/http.d/nostr-relay.conf && \
rm -f /etc/nginx/http.d/default.conf
COPY --from=strfry /app/strfry /usr/local/bin/strfry
# Copy musl shared libraries that strfry depends on from the source image.
COPY --from=strfry \
/usr/lib/liblmdb.so.0 \
/usr/lib/libcrypto.so.50 \
/usr/lib/libssl.so.53 \
/usr/lib/libsecp256k1.so.2 \
/usr/lib/libzstd.so.1 \
/usr/lib/libstdc++.so.6 \
/usr/lib/libgcc_s.so.1 \
/usr/lib/
COPY --from=strfry /lib/libz.so.1 /lib/
WORKDIR /usr/src/app
RUN mkdir -p strfry-db
RUN printf '#!/bin/sh\nnginx\nexec strfry relay\n' > /entrypoint-app.sh && \
chmod +x /entrypoint-app.sh
ENTRYPOINT ["/entrypoint-app.sh"]

View File

@@ -0,0 +1,289 @@
# FIPS Nostr Relay Sidecar
Runs a [strfry](https://github.com/hoytech/strfry) Nostr relay reachable
exclusively over the FIPS mesh. The relay container shares the FIPS
sidecar's network namespace and is isolated from the host network by
iptables — it can only be reached via the node's `.fips` name.
## How to Run
### 1. Build the FIPS binaries
From the repo root, compile FIPS for Linux and copy the binaries into the
Docker build context:
```bash
cd examples/sidecar-nostr-relay
./scripts/build.sh
```
Cross-compilation from macOS is supported via `cargo-zigbuild`.
### 2. Set your node identity
The relay needs a unique FIPS identity. Generate one with:
```bash
fipsctl keygen
```
Then set it in `.env`:
```bash
# .env
FIPS_NSEC=nsec1... # paste your nsec here
```
`FIPS_NSEC` is required — the container will refuse to start without it.
### 3. Start the stack
```bash
docker compose up -d
```
This starts two containers that share a network namespace:
- **fips** — FIPS daemon + dnsmasq. Owns the namespace, creates `fips0`.
- **app** — strfry relay + nginx. Joins the namespace via `network_mode: service:fips`.
### 4. Verify
```bash
# FIPS node is up and has a mesh address:
docker exec sidecar-nostr-relay-fips-1 fipsctl show status
# Relay is listening (should show nginx on :80 and strfry on :7777):
docker exec sidecar-nostr-relay-fips-1 ss -tlnp
# Peer link to the public node is established:
docker exec sidecar-nostr-relay-fips-1 fipsctl show peers
# Or check the logs:
docker compose logs -f
```
### 5. Connect to the relay
Your node's npub (and therefore its `.fips` name) is derived from its keypair:
```bash
docker exec sidecar-nostr-relay-fips-1 fipsctl show status
```
Connect from any FIPS-peered client using the node's npub:
```
ws://npub1xxxx.fips:80
```
## Security Model
The sidecar pattern enforces strict network isolation on the app container:
- **No IPv4 access**: iptables blocks all eth0 traffic except FIPS UDP
transport (port 2121) and TCP transport (port 443). The app container
cannot reach the Docker bridge, the host network, or any IPv4 address.
- **No IPv6 on eth0**: ip6tables blocks all IPv6 traffic on eth0. The app
container cannot use link-local or any Docker-assigned IPv6 addresses.
- **FIPS mesh only**: The only routable network path is through `fips0`
(`fd::/8`). All application traffic traverses the FIPS mesh with
end-to-end encryption.
- **Loopback allowed**: `lo` is unrestricted for inter-process communication
within the shared namespace.
This means the app container treats the FIPS mesh as its sole network. Even
if the application is compromised, it cannot bypass the mesh or communicate
with the transport layer directly.
## Architecture
```text
┌───────────────────────────────────────────────────┐
│ Shared network namespace │
│ │
│ ┌───────────────┐ ┌──────────────────────────┐ │
│ │ fips-sidecar │ │ fips-app │ │
│ │ │ │ │ │
│ │ fips daemon │ │ your workload │ │
│ │ fipsctl │ │ │ │
│ │ dnsmasq │ │ │ │
│ └───────────────┘ └──────────────────────────┘ │
│ │
│ Interfaces: │
│ lo — loopback (unrestricted) │
│ eth0 — Docker bridge (iptables: FIPS only) │
│ fips0 — FIPS TUN (fd::/8, unrestricted) │
└───────────────────────────────────────────────────┘
```
The FIPS sidecar owns the network namespace and creates the `fips0` TUN
interface. The app container joins via `network_mode: service:fips` and
sees the same interfaces. The entrypoint script applies iptables rules
before launching the FIPS daemon:
**IPv4 rules** (iptables):
- ACCEPT on `lo` (both directions)
- ACCEPT UDP sport/dport 2121 on `eth0` (FIPS UDP transport)
- ACCEPT TCP dport 443 / sport 443 on `eth0` (FIPS TCP transport)
- DROP everything else on `eth0`
**IPv6 rules** (ip6tables):
- ACCEPT on `lo` (both directions)
- ACCEPT on `fips0` (both directions)
- DROP everything on `eth0`
### DNS Resolution
DNS inside the container is handled by dnsmasq (127.0.0.1:53):
- `.fips` queries are forwarded to the FIPS daemon's built-in DNS resolver
(127.0.0.1:5354), which resolves npub-based names to `fd::/8` addresses
- All other queries are forwarded to Docker's embedded DNS (127.0.0.11)
The `resolv.conf` mount points the container's resolver at 127.0.0.1,
where dnsmasq handles the routing.
## Build
```bash
cd examples/sidecar-nostr-relay
./scripts/build.sh
```
This compiles FIPS for Linux, copies the binaries into the Docker context,
and builds the sidecar and app images. Cross-compilation from macOS is
supported via `cargo-zigbuild`.
## Run with Peers
To connect the sidecar to an existing mesh, provide the peer's npub and
transport address:
```bash
FIPS_PEER_NPUB=npub1... \
FIPS_PEER_ADDR=203.0.113.10:2121 \
FIPS_PEER_ALIAS=gateway \
docker compose up -d
```
Verify the peer link:
```bash
docker exec sidecar-nostr-relay-fips-1 fipsctl show peers
docker exec sidecar-nostr-relay-fips-1 fipsctl show links
```
## Verify Connectivity and Isolation
From the app container:
```bash
# Ping a mesh node by npub (resolves via .fips DNS):
docker exec sidecar-nostr-relay-app-1 ping6 -c3 npub1xxxx.fips
# Fetch a web page from a mesh node over FIPS:
docker exec sidecar-nostr-relay-app-1 curl -6 "http://npub1xxxx.fips:8000/"
# Docker bridge is blocked — this should fail:
docker exec sidecar-nostr-relay-app-1 ping -c1 -W2 172.20.0.13
# Loopback is allowed:
docker exec sidecar-nostr-relay-app-1 ping -c1 127.0.0.1
```
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `FIPS_NSEC` | *(required)* | Node secret key (hex or nsec1 bech32) |
| `FIPS_PEER_NPUB` | *(empty)* | Peer's npub to connect to |
| `FIPS_PEER_ADDR` | *(empty)* | Peer's transport address (e.g. `203.0.113.10:2121`) |
| `FIPS_PEER_ALIAS` | `peer` | Human-readable peer name |
| `FIPS_UDP_BIND` | `0.0.0.0:2121` | UDP transport bind address |
| `FIPS_TCP_BIND` | `0.0.0.0:8443` | TCP transport bind address |
| `FIPS_PEER_TRANSPORT` | `udp` | Peer transport type (`udp` or `tcp`) |
| `FIPS_TUN_MTU` | `1280` | TUN interface MTU |
| `FIPS_NETWORK` | `fips-sidecar-net` | Docker network name (set to join external network) |
| `FIPS_SUBNET` | `172.20.1.0/24` | Docker network subnet |
| `FIPS_IPV4` | `172.20.1.20` | Sidecar's IPv4 address on the Docker network |
| `RUST_LOG` | `info` | FIPS log level |
## Troubleshooting
**`FIPS_NSEC is required`** — The `FIPS_NSEC` environment variable is not
set. Either add it to `.env` or pass it on the command line. Generate a
random key with: `openssl rand -hex 32`
**`fips0` interface not appearing** — The FIPS daemon needs `/dev/net/tun`
and `NET_ADMIN` capability. Check that the compose file includes both:
```yaml
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
```
**No peer connection established** — Verify the peer address is reachable
from the sidecar container (`docker exec sidecar-nostr-relay-fips-1 ping -c1 <peer-ip>`).
If joining an external Docker network, ensure `FIPS_NETWORK`, `FIPS_SUBNET`,
and `FIPS_IPV4` match the target network. Check logs with
`docker logs sidecar-nostr-relay-fips-1`.
**DNS not resolving `.fips` names** — Verify dnsmasq is running:
`docker exec sidecar-nostr-relay-fips-1 pgrep dnsmasq`. Check that `resolv.conf` is
mounted (should contain `nameserver 127.0.0.1`). Verify the FIPS DNS
resolver is listening: `docker exec sidecar-nostr-relay-fips-1 dig @127.0.0.1 -p 5354 <npub>.fips AAAA`.
**iptables errors in entrypoint** — The sidecar container requires
`NET_ADMIN` capability for iptables. Without it, the isolation rules
cannot be applied and the entrypoint will fail.
## Production Considerations
**Secrets management**: The default `.env` contains a hardcoded nsec for
development. In production, use Docker secrets, a vault, or inject the key
via a secure CI/CD pipeline. Never commit production keys to version control.
**Logging**: Set `RUST_LOG` to control log verbosity (`debug`, `info`,
`warn`, `error`). For production, configure the Docker logging driver with
size limits:
```yaml
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
```
**Resource limits**: Add memory and CPU constraints in the compose file:
```yaml
deploy:
resources:
limits:
memory: 256M
cpus: "0.5"
```
**Multiple peers**: The entrypoint supports a single peer via environment
variables. For multiple peers, mount a custom `fips.yaml` directly:
```yaml
volumes:
- ./my-fips.yaml:/etc/fips/fips.yaml:ro
```
**Health checks**: Add a Docker health check using `fipsctl`:
```yaml
healthcheck:
test: ["CMD", "fipsctl", "show", "status"]
interval: 30s
timeout: 5s
retries: 3
```

View File

@@ -0,0 +1,6 @@
# Override: use a pre-existing external network instead of creating one.
# Used by test-sidecar.sh for multi-instance testing on a shared network.
networks:
fips-net:
external: true
name: ${FIPS_NETWORK:-fips-sidecar-net}

View File

@@ -0,0 +1,54 @@
networks:
fips-net:
name: ${FIPS_NETWORK:-fips-sidecar-net}
driver: bridge
ipam:
config:
- subnet: ${FIPS_SUBNET:-172.20.1.0/24}
services:
fips:
build:
context: .
hostname: fips-sidecar
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
restart: "no"
ports:
- "2121:2121/udp" # FIPS UDP transport
- "8443:8443/tcp" # FIPS TCP transport
- "80:80/tcp" # Nostr relay WebSocket (via nginx)
environment:
- RUST_LOG=${RUST_LOG:-info}
- FIPS_NSEC=${FIPS_NSEC}
- FIPS_PEER_NPUB=${FIPS_PEER_NPUB:-}
- FIPS_PEER_ADDR=${FIPS_PEER_ADDR:-}
- FIPS_PEER_ALIAS=${FIPS_PEER_ALIAS:-peer}
- FIPS_UDP_BIND=${FIPS_UDP_BIND:-0.0.0.0:2121}
- FIPS_TUN_MTU=${FIPS_TUN_MTU:-1280}
- FIPS_PEER_TRANSPORT=${FIPS_PEER_TRANSPORT:-udp}
volumes:
- ./resolv.conf:/etc/resolv.conf:ro
networks:
fips-net:
ipv4_address: ${FIPS_IPV4:-172.20.1.20}
app:
build:
context: .
dockerfile: Dockerfile.app
network_mode: "service:fips"
restart: unless-stopped
depends_on:
- fips
volumes:
- relay-data:/usr/src/app/strfry-db
- ./relay/strfry.conf:/usr/src/app/strfry.conf:ro
- ./resolv.conf:/etc/resolv.conf:ro
volumes:
relay-data:

View File

@@ -0,0 +1,86 @@
#!/bin/bash
# FIPS sidecar entrypoint: generate config, apply iptables isolation, launch FIPS.
set -e
# --- Generate FIPS config from environment variables ---
FIPS_NSEC="${FIPS_NSEC:?FIPS_NSEC is required}"
FIPS_UDP_BIND="${FIPS_UDP_BIND:-0.0.0.0:2121}"
FIPS_TCP_BIND="${FIPS_TCP_BIND:-0.0.0.0:8443}"
FIPS_TUN_MTU="${FIPS_TUN_MTU:-1280}"
FIPS_PEER_TRANSPORT="${FIPS_PEER_TRANSPORT:-udp}"
mkdir -p /etc/fips
# Build peers section
PEERS_SECTION=""
if [ -n "$FIPS_PEER_NPUB" ] && [ -n "$FIPS_PEER_ADDR" ]; then
FIPS_PEER_ALIAS="${FIPS_PEER_ALIAS:-peer}"
PEERS_SECTION=" - npub: \"${FIPS_PEER_NPUB}\"
alias: \"${FIPS_PEER_ALIAS}\"
addresses:
- transport: ${FIPS_PEER_TRANSPORT}
addr: \"${FIPS_PEER_ADDR}\"
connect_policy: auto_connect"
fi
cat > /etc/fips/fips.yaml <<EOF
node:
identity:
nsec: "${FIPS_NSEC}"
tun:
enabled: true
name: fips0
mtu: ${FIPS_TUN_MTU}
dns:
enabled: true
bind_addr: "127.0.0.1"
transports:
udp:
bind_addr: "${FIPS_UDP_BIND}"
mtu: 1472
tcp:
bind_addr: "${FIPS_TCP_BIND}"
peers:
${PEERS_SECTION:- []}
EOF
echo "Generated /etc/fips/fips.yaml"
# --- Apply iptables rules for strict network isolation ---
#
# Goal: only FIPS UDP transport (port 2121) may use eth0.
# All other eth0 traffic is dropped. fips0 and loopback are unrestricted.
# This ensures the app container (sharing this network namespace) can only
# communicate over the FIPS mesh.
# IPv4: allow only FIPS transport on eth0
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o eth0 -p udp --dport 2121 -j ACCEPT
iptables -A OUTPUT -o eth0 -p udp --sport 2121 -j ACCEPT
iptables -A INPUT -i eth0 -p udp --dport 2121 -j ACCEPT
iptables -A INPUT -i eth0 -p udp --sport 2121 -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 443 -j ACCEPT
iptables -A OUTPUT -o eth0 -j DROP
iptables -A INPUT -i eth0 -j DROP
# IPv6: allow fips0 and loopback, block eth0
ip6tables -A OUTPUT -o lo -j ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A OUTPUT -o fips0 -j ACCEPT
ip6tables -A INPUT -i fips0 -j ACCEPT
ip6tables -A OUTPUT -o eth0 -j DROP
ip6tables -A INPUT -i eth0 -j DROP
echo "iptables isolation rules applied"
# --- Start dnsmasq and launch FIPS ---
dnsmasq
exec fips --config /etc/fips/fips.yaml

View File

@@ -0,0 +1,49 @@
# nostr-rs-relay configuration for FIPS mesh deployment.
# Full reference: https://github.com/scsibug/nostr-rs-relay
[info]
# Shown in NIP-11 relay information document.
relay_url = "" # Set to ws://[<your-fips-ipv6>]:8080 after first start
name = "FIPS Nostr Relay"
description = "A Nostr relay accessible over the FIPS mesh network."
pubkey = "" # Optional: your npub (hex) as relay operator
contact = "" # Optional: contact address
[database]
# SQLite database path inside the container (mapped to relay-data volume).
data_directory = "/usr/src/app/db"
[network]
# Bind on all interfaces (IPv4 + IPv6) — FIPS delivers traffic via the fips0
# TUN as IPv6 (fd00::/8). Using 0.0.0.0 with port 8080; the relay also needs
# to accept IPv6 connections so we set port separately and rely on the OS
# dual-stack socket (IPV6_V6ONLY=0).
# nostr-rs-relay address field is just the IP, port is separate.
address = "0.0.0.0"
port = 8080
ping_interval_seconds = 300
[limits]
# Adjust to taste. These are conservative defaults for a personal relay.
messages_per_sec = 10
subscriptions_per_min = 20
max_blocking_threads = 4
max_event_bytes = 131072 # 128 KiB per event
max_ws_message_bytes = 131072
max_ws_frame_bytes = 131072
broadcast_buffer = 16384
event_persist_buffer = 4096
[authorization]
# Set to true to require NIP-42 AUTH before accepting events.
# Useful for a private relay — only authenticated users can publish.
nip42_auth = false
nip42_dms = false
[verified_users]
# NIP-05 verification (optional).
mode = "disabled"
[pay_to_relay]
# Lightning payments for relay access (optional).
enabled = false

View File

@@ -0,0 +1,71 @@
##
## strfry configuration for FIPS mesh deployment.
## Full reference: https://github.com/hoytech/strfry
##
db = "/usr/src/app/strfry-db/"
dbParams {
# Maximum size of the database (bytes). 10 GiB is a safe default.
mapsize = 10737418240
}
relay {
# Bind on all interfaces so the FIPS TUN (IPv4 + IPv6) can reach it.
bind = "0.0.0.0"
port = 7777
nofiles = 0
info {
name = "FIPS Nostr Relay"
description = "A Nostr relay accessible over the FIPS mesh network."
pubkey = ""
contact = ""
}
# Maximum size of an inbound WebSocket message (bytes).
maxWebsocketPayloadSize = 131072
# Send a ping every N seconds to keep connections alive.
autoPingSeconds = 55
# Enable per-message compression.
enableTcpNoDelay = false
rejectFutureEventsSeconds = 900
rejectEphemeralEventsOlderThanSeconds = 60
rejectEventsNewerThanSeconds = 900
maxFilterLimit = 500
maxSubsPerConnection = 20
writePolicy {
# Plugin executable for write-policy decisions (leave empty to allow all).
plugin = ""
}
compression {
enabled = true
slidingWindow = true
}
logging {
dumpInAll = false
dumpInEvents = false
dumpInReqs = false
dbScanPerf = false
}
numThreads {
ingester = 3
reqWorker = 3
reqMonitor = 3
negentropy = 2
}
negentropy {
enabled = true
maxSyncEvents = 1000000
}
}

View File

@@ -0,0 +1 @@
nameserver 127.0.0.1

View File

@@ -0,0 +1,57 @@
#!/bin/bash
# Build the FIPS binary and Docker sidecar image.
# Usage: ./build.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DOCKER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# Find project root (directory containing Cargo.toml)
PROJECT_ROOT="$(cd "$DOCKER_DIR/../.." && pwd)"
if [ ! -f "$PROJECT_ROOT/Cargo.toml" ]; then
echo "Error: Cannot find Cargo.toml at $PROJECT_ROOT" >&2
echo "Expected layout: <project-root>/examples/sidecar-nostr-relay/scripts/build.sh" >&2
exit 1
fi
# Detect host OS
UNAME_S=$(uname -s)
CARGO_TARGET="x86_64-unknown-linux-musl"
if [ "$UNAME_S" = "Darwin" ]; then
echo "Detected macOS host — using cross-compilation for Linux..."
if ! command -v cargo-zigbuild &> /dev/null; then
echo "Error: cargo-zigbuild not found." >&2
echo "Please install it: cargo install cargo-zigbuild" >&2
exit 1
fi
if ! rustup target list --installed | grep -q "$CARGO_TARGET"; then
echo "Installing Rust target $CARGO_TARGET..."
rustup target add "$CARGO_TARGET"
fi
echo "Building FIPS for Linux (release) using cargo-zigbuild..."
cargo zigbuild --release --target "$CARGO_TARGET" --manifest-path="$PROJECT_ROOT/Cargo.toml"
echo "Copying binaries to docker context..."
cp "$PROJECT_ROOT/target/$CARGO_TARGET/release/fips" "$DOCKER_DIR/fips"
cp "$PROJECT_ROOT/target/$CARGO_TARGET/release/fipsctl" "$DOCKER_DIR/fipsctl"
cp "$PROJECT_ROOT/target/$CARGO_TARGET/release/fipstop" "$DOCKER_DIR/fipstop"
else
echo "Building FIPS (release)..."
cargo build --release --manifest-path="$PROJECT_ROOT/Cargo.toml"
echo "Copying binaries to docker context..."
cp "$PROJECT_ROOT/target/release/fips" "$DOCKER_DIR/fips"
cp "$PROJECT_ROOT/target/release/fipsctl" "$DOCKER_DIR/fipsctl"
cp "$PROJECT_ROOT/target/release/fipstop" "$DOCKER_DIR/fipstop"
fi
echo "Done. Binaries at $DOCKER_DIR/{fips,fipsctl,fipstop}"
echo ""
echo "Building Docker image..."
docker compose -f "$DOCKER_DIR/docker-compose.yml" build
echo ""
echo "Ready: cd examples/sidecar-nostr-relay && docker compose up -d"