diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index b8c44b74..ece11af8 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -370,6 +370,8 @@ COPY archipelago-tor-helper.service /etc/systemd/system/archipelago-tor-helper.s COPY archipelago-tor-helper.path /etc/systemd/system/archipelago-tor-helper.path COPY nostr-vpn.service /etc/systemd/system/nostr-vpn.service COPY archipelago-wg-address.service /etc/systemd/system/archipelago-wg-address.service +COPY nostr-relay.service /etc/systemd/system/nostr-relay.service +COPY nostr-relay-config.toml /etc/archipelago/nostr-relay-config.toml # WireGuard kernel module auto-load on boot RUN echo "wireguard" >> /etc/modules-load.d/wireguard.conf @@ -396,6 +398,7 @@ RUN systemctl enable NetworkManager || true && \ systemctl enable archipelago-doctor.timer || true && \ systemctl enable archipelago-reconcile.timer || true && \ systemctl enable archipelago-tor-helper.path || true && \ + systemctl enable nostr-relay || true && \ systemctl enable nostr-vpn || true && \ systemctl enable archipelago-wg-address || true @@ -403,10 +406,11 @@ RUN systemctl enable NetworkManager || true && \ RUN rm -f /usr/sbin/policy-rc.d # Create directories (including Cloud storage for FileBrowser) -RUN mkdir -p /var/lib/archipelago/{data,config,containers} && \ +RUN mkdir -p /var/lib/archipelago/{data,config,containers,nostr-relay,nostr-vpn} && \ mkdir -p /etc/archipelago && \ mkdir -p /opt/archipelago/{bin,scripts,web-ui} && \ mkdir -p /var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads} && \ + cp /etc/archipelago/nostr-relay-config.toml /var/lib/archipelago/nostr-relay/config.toml && \ chown -R archipelago:archipelago /var/lib/archipelago /opt/archipelago # Clean up @@ -495,6 +499,16 @@ NGINXCONF echo " Using archipelago-wg-address.service from configs/" fi + # Copy private Nostr relay service (native, for NostrVPN signaling) + if [ -f "$SCRIPT_DIR/configs/nostr-relay.service" ]; then + cp "$SCRIPT_DIR/configs/nostr-relay.service" "$WORK_DIR/nostr-relay.service" + echo " Using nostr-relay.service from configs/" + fi + if [ -f "$SCRIPT_DIR/configs/nostr-relay-config.toml" ]; then + cp "$SCRIPT_DIR/configs/nostr-relay-config.toml" "$WORK_DIR/nostr-relay-config.toml" + echo " Using nostr-relay-config.toml from configs/" + fi + # Copy WireGuard helper script (privileged peer management) if [ -f "$SCRIPT_DIR/../scripts/archipelago-wg" ]; then cp "$SCRIPT_DIR/../scripts/archipelago-wg" "$WORK_DIR/archipelago-wg" @@ -966,6 +980,22 @@ else echo " ⚠ NostrVPN image not available — nvpn binary will be missing" fi +# Extract nostr-rs-relay binary from container image (native system service for VPN signaling) +echo " Extracting nostr-rs-relay binary..." +RELAY_IMAGE="$($CONTAINER_CMD images -q 80.71.235.15:3000/archipelago/nostr-rs-relay:0.9.0 2>/dev/null)" +if [ -z "$RELAY_IMAGE" ]; then + $CONTAINER_CMD pull 80.71.235.15:3000/archipelago/nostr-rs-relay:0.9.0 2>/dev/null || true +fi +RELAY_CONTAINER=$($CONTAINER_CMD create 80.71.235.15:3000/archipelago/nostr-rs-relay:0.9.0 2>/dev/null) || true +if [ -n "$RELAY_CONTAINER" ]; then + $CONTAINER_CMD cp "$RELAY_CONTAINER:/usr/local/bin/nostr-rs-relay" "$ARCH_DIR/bin/nostr-rs-relay" 2>/dev/null && \ + chmod +x "$ARCH_DIR/bin/nostr-rs-relay" && \ + echo " ✅ nostr-rs-relay binary extracted ($(du -h "$ARCH_DIR/bin/nostr-rs-relay" | cut -f1))" + $CONTAINER_CMD rm "$RELAY_CONTAINER" 2>/dev/null || true +else + echo " ⚠ nostr-rs-relay image not available — relay binary will be missing" +fi + # Copy WireGuard helper script if [ -f "$WORK_DIR/archipelago-wg" ]; then cp "$WORK_DIR/archipelago-wg" "$ARCH_DIR/bin/archipelago-wg" diff --git a/image-recipe/configs/archipelago-wg-address.service b/image-recipe/configs/archipelago-wg-address.service new file mode 100644 index 00000000..8a9e3069 --- /dev/null +++ b/image-recipe/configs/archipelago-wg-address.service @@ -0,0 +1,14 @@ +[Unit] +Description=Assign WireGuard server address to wg0 +After=nostr-vpn.service +Wants=nostr-vpn.service +ConditionPathExists=/sys/class/net/wg0 + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/bash -c 'ip address show dev wg0 | grep -q "10.44.0.1" || ip address add 10.44.0.1/16 dev wg0' +ExecStart=/bin/bash -c 'iptables -t nat -C POSTROUTING -s 10.44.0.0/16 ! -o wg0 -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -s 10.44.0.0/16 ! -o wg0 -j MASQUERADE' + +[Install] +WantedBy=multi-user.target diff --git a/image-recipe/configs/nostr-relay-config.toml b/image-recipe/configs/nostr-relay-config.toml new file mode 100644 index 00000000..1be79a43 --- /dev/null +++ b/image-recipe/configs/nostr-relay-config.toml @@ -0,0 +1,19 @@ +[info] +relay_url = "ws://0.0.0.0:7777/" +name = "Archipelago Private Relay" +description = "Private Nostr relay for Archipelago mesh VPN peer discovery" + +[database] +data_directory = "/var/lib/archipelago/nostr-relay" +in_memory = true + +[network] +address = "0.0.0.0" +port = 7777 +ping_interval = 120 + +[limits] +messages_per_sec = 50 +max_event_bytes = 65536 +max_ws_message_bytes = 65536 +max_ws_frame_bytes = 65536 diff --git a/image-recipe/configs/nostr-relay.service b/image-recipe/configs/nostr-relay.service new file mode 100644 index 00000000..8d1c133a --- /dev/null +++ b/image-recipe/configs/nostr-relay.service @@ -0,0 +1,24 @@ +[Unit] +Description=Archipelago Private Nostr Relay +After=network-online.target +Wants=network-online.target +Before=nostr-vpn.service + +[Service] +Type=simple +User=archipelago +ExecStartPre=/bin/bash -c 'mkdir -p /var/lib/archipelago/nostr-relay' +ExecStart=/usr/local/bin/nostr-rs-relay --config /var/lib/archipelago/nostr-relay/config.toml +Restart=always +RestartSec=3 +TimeoutStopSec=10 + +# Resource limits — relay is lightweight (in-memory mode) +MemoryMax=512M +LimitNOFILE=4096 + +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/scripts/archipelago-wg b/scripts/archipelago-wg new file mode 100755 index 00000000..4733075a --- /dev/null +++ b/scripts/archipelago-wg @@ -0,0 +1,58 @@ +#!/bin/bash +# archipelago-wg — Privileged WireGuard helper for the Archipelago backend. +# Installed to /usr/local/bin/archipelago-wg with a sudoers rule so the +# unprivileged archipelago/debian service user can manage wg0 without +# full root or disabling NoNewPrivileges. +# +# Usage: +# archipelago-wg setup — Create wg0 interface +# archipelago-wg add-peer — Add peer to wg0 +# archipelago-wg remove-peer — Remove peer from wg0 + +set -euo pipefail + +case "${1:-}" in + setup) + KEY_FILE="${2:?Usage: archipelago-wg setup }" + [ -f "$KEY_FILE" ] || { echo "Key file not found: $KEY_FILE" >&2; exit 1; } + + # Ensure kernel module is loaded + modprobe wireguard 2>/dev/null || true + + # Create interface + ip link add dev wg0 type wireguard 2>/dev/null || true + wg set wg0 listen-port 51820 private-key "$KEY_FILE" + # Assign server address if not already set + ip address show dev wg0 | grep -q "10.44.0.1" || ip address add 10.44.0.1/16 dev wg0 + ip link set up dev wg0 + + # NAT masquerade for VPN clients + iptables -t nat -C POSTROUTING -s 10.44.0.0/16 ! -o wg0 -j MASQUERADE 2>/dev/null || + iptables -t nat -A POSTROUTING -s 10.44.0.0/16 ! -o wg0 -j MASQUERADE + + # Open firewall port + if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then + ufw allow 51820/udp >/dev/null 2>&1 || true + fi + + echo "wg0 configured" + ;; + + add-peer) + PUBKEY="${2:?Usage: archipelago-wg add-peer }" + ALLOWED_IP="${3:?Usage: archipelago-wg add-peer }" + wg set wg0 peer "$PUBKEY" allowed-ips "$ALLOWED_IP" + echo "peer added" + ;; + + remove-peer) + PUBKEY="${2:?Usage: archipelago-wg remove-peer }" + wg set wg0 peer "$PUBKEY" remove + echo "peer removed" + ;; + + *) + echo "Usage: archipelago-wg {setup|add-peer|remove-peer}" >&2 + exit 1 + ;; +esac diff --git a/scripts/first-boot-containers.sh b/scripts/first-boot-containers.sh index e07ad8bc..1d865f4b 100644 --- a/scripts/first-boot-containers.sh +++ b/scripts/first-boot-containers.sh @@ -86,6 +86,20 @@ done chown -R archipelago:archipelago "$TOR_HOSTNAMES" 2>/dev/null log "Tor hostnames populated: $(ls $TOR_HOSTNAMES 2>/dev/null | tr '\n' ' ')" +# ── Private Nostr Relay: start for VPN signaling and general use ────── +if command -v nostr-rs-relay >/dev/null 2>&1; then + # Relay config is pre-installed by ISO at /var/lib/archipelago/nostr-relay/config.toml + mkdir -p /var/lib/archipelago/nostr-relay + if [ ! -f /var/lib/archipelago/nostr-relay/config.toml ] && [ -f /etc/archipelago/nostr-relay-config.toml ]; then + cp /etc/archipelago/nostr-relay-config.toml /var/lib/archipelago/nostr-relay/config.toml + fi + chown -R archipelago:archipelago /var/lib/archipelago/nostr-relay + systemctl enable --now nostr-relay 2>/dev/null || true + log "Private Nostr relay started on port 7777" +else + log "nostr-rs-relay binary not found — skipping relay setup" +fi + # ── NostrVPN: configure native system service with node identity ────── if command -v nvpn >/dev/null 2>&1; then NOSTR_SECRET=$(cat /var/lib/archipelago/identity/nostr_secret 2>/dev/null) @@ -107,7 +121,26 @@ if command -v nvpn >/dev/null 2>&1; then # Configure nvpn with node identity and endpoint if [ -f "$NVPN_CONFIG_DIR/config.toml" ]; then - su -l archipelago -c "nvpn set --endpoint '${HOST_IP}:51820'" 2>/dev/null || true + su -l archipelago -c "nvpn set --endpoint '${HOST_IP}:51821'" 2>/dev/null || true + fi + + # Add this node's own relay as a signaling relay + # Direct relay (public IP) — only if not behind NAT + if [ -n "$HOST_IP" ] && ! echo "$HOST_IP" | grep -qE '^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)'; then + su -l archipelago -c "nvpn relay add 'ws://${HOST_IP}:7777'" 2>/dev/null || true + fi + # Tor relay (works behind NAT) + RELAY_ONION=$(cat /var/lib/archipelago/tor-hostnames/relay 2>/dev/null) + if [ -n "$RELAY_ONION" ]; then + su -l archipelago -c "nvpn relay add 'ws://${RELAY_ONION}:7777'" 2>/dev/null || true + fi + + # Sync config to daemon HOME so the service finds it + # (service runs with HOME=/var/lib/archipelago/nostr-vpn) + DAEMON_CONFIG_DIR="/var/lib/archipelago/nostr-vpn/.config/nvpn" + mkdir -p "$DAEMON_CONFIG_DIR" + if [ -f "$NVPN_CONFIG_DIR/config.toml" ]; then + cp "$NVPN_CONFIG_DIR/config.toml" "$DAEMON_CONFIG_DIR/config.toml" fi # Ensure env file exists for the service