Add Docker and Nix packaging for the node

server/docker/: multi-stage image (rust:1-slim build, trixie runtime)
with all four binaries plus the pinned yggdrasil 0.5.14 .deb
(sha256-verified, fetched from the official release), a compose
example per household (host network, NET_ADMIN, /dev/net/tun,
init: true), a German-commented node.toml.example (safe default:
leech mode) and a README covering privileges, two-household wiring,
leech mode and the kill switch. Image build and smoke tests verified
locally with docker 29.8.1.

flake.nix: builds the workspace as one spidernet package via
rustPlatform.buildRustPackage (cargoLock against the committed
Cargo.lock) plus an experimental NixOS module; written without a
local Nix and marked accordingly.
This commit is contained in:
2026-09-24 20:12:51 +02:00
parent f2f3609b87
commit 57b9b44d8a
6 changed files with 563 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
# syntax=docker/dockerfile:1
#
# SpiderNet node container image (multi-stage).
#
# NO WARRANTY — SpiderNet is provided "as is", without warranty of any kind
# (AGPL-3.0, sections 15/16). Running a node is entirely at the operator's
# own responsibility, including all traffic sent and received with it.
#
# Build context is the `server/` directory (see docker-compose.yml):
# docker build -t spidernet-node -f docker/Dockerfile ..
# --- Stage 1: build the Rust workspace --------------------------------------
FROM rust:1-slim AS build
WORKDIR /server
COPY Cargo.toml Cargo.lock ./
COPY crates ./crates
# --locked: the build must match the committed Cargo.lock exactly.
RUN cargo build --release --locked --workspace
# --- Stage 2: runtime --------------------------------------------------------
# trixie, not bookworm: rust:1-slim is trixie-based, and binaries built
# against its glibc (2.41) need a matching runtime (bookworm only has 2.36).
FROM debian:trixie-slim
# Yggdrasil sidecar: current stable release of yggdrasil-go. Release v0.5.14
# ships official .deb packages (no raw tarballs). Binary inside: /usr/bin/yggdrasil.
# Digests from https://api.github.com/repos/yggdrasil-network/yggdrasil-go/releases/latest
ARG YGGDRASIL_VERSION=0.5.14
ARG TARGETARCH
# ca-certificates: TLS for sn-exit/sn-fetch (rustls) and tls:// peers.
# curl is only needed to fetch the yggdrasil .deb and is removed again.
# TARGETARCH is only set by BuildKit; fall back to the host arch otherwise
# (both use the same amd64/arm64 naming as the yggdrasil .deb assets).
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
&& ARCH="${TARGETARCH:-$(dpkg --print-architecture)}" \
&& case "$ARCH" in \
amd64) YGGDRASIL_SHA256=72745496b09baa1c3f5a6741e1257a704cfdb9d00cdf3ddbef025987b239699d ;; \
arm64) YGGDRASIL_SHA256=ac8c9a9a76c54b878fd251aef5a2bbd20375c6109b28b32435683441462360c9 ;; \
*) echo "unsupported architecture: $ARCH" >&2; exit 1 ;; \
esac \
&& curl -fsSL -o /tmp/yggdrasil.deb \
"https://github.com/yggdrasil-network/yggdrasil-go/releases/download/v${YGGDRASIL_VERSION}/yggdrasil-${YGGDRASIL_VERSION}-${ARCH}.deb" \
&& echo "${YGGDRASIL_SHA256} /tmp/yggdrasil.deb" | sha256sum -c - \
&& dpkg-deb -x /tmp/yggdrasil.deb /tmp/yggdrasil-root \
&& cp /tmp/yggdrasil-root/usr/bin/yggdrasil /usr/local/bin/yggdrasil \
&& rm -rf /tmp/yggdrasil.deb /tmp/yggdrasil-root \
&& apt-get purge -y curl \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /server/target/release/sn-node /usr/local/bin/sn-node
COPY --from=build /server/target/release/sn-exit /usr/local/bin/sn-exit
COPY --from=build /server/target/release/sn-fetch /usr/local/bin/sn-fetch
COPY --from=build /server/target/release/sn-fair /usr/local/bin/sn-fair
# WORKDIR matches the state volume in docker-compose.yml; a config that
# leaves `state_dir` at its default then resolves to the volume as well.
WORKDIR /var/lib/spidernet
ENTRYPOINT ["sn-node", "daemon", "--config", "/etc/spidernet/node.toml"]
+125
View File
@@ -0,0 +1,125 @@
# SpiderNet-Node mit Docker
Dieser Ordner enthält alles, um den SpiderNet-Node eines Haushalts als
Container zu betreiben: Multi-Stage-Dockerfile, ein Compose-Beispiel für
einen Host und eine kommentierte Beispiel-Konfiguration.
## Haftungsausschluss
> **KEINE GARANTIE.** Diese Software wird "as is" ohne jegliche Garantie
> bereitgestellt (AGPL §§ 15/16). Die Betreiber und Contributor:innen dieses
> Projekts übernehmen **keinerlei Verantwortung oder Haftung** dafür, wozu
> Nutzer:innen die Software verwenden oder welche Inhalte über sie
> übertragen werden. Die Verantwortung für gesendete und empfangene Inhalte
> liegt vollständig bei den Nutzer:innen.
## Quickstart
```sh
# 1. Konfiguration anlegen und anpassen
cp node.toml.example node.toml
$EDITOR node.toml
# 2. Image bauen und starten (Build-Kontext ist server/, daher "context: ..")
docker compose up --build -d
# 3. Beobachten
docker compose logs -f
# 4. Overlay-Status ansehen (Admin-Socket lauscht auf localhost:9001)
docker compose exec node sn-node status
# 5. Einen großen Download über die gepoolte Bandbreite laden
docker compose exec node sn-fetch "https://…/große-datei.iso" \
-o /tmp/out.iso -e "1=2,2=1@http://[<ygg-adresse-des-nachbarn>]:8080"
```
Image direkt bauen (ohne Compose), Build-Kontext `server/`:
```sh
docker build -t spidernet-node -f docker/Dockerfile ..
```
## Was die Container-Privilegien bedeuten
| Einstellung | Warum |
|---|---|
| `network_mode: host` | Yggdrasil muss auf dem realen LAN peeren und auf der Overlay-Seite erreichbar sein. Mit Bridge-NAT würden Peer-Verbindungen und der Exit-Port nicht stimmen. |
| `cap_add: [NET_ADMIN]` | Yggdrasil legt das TUN-Interface (`ifname`) an und konfiguriert es — das braucht `CAP_NET_ADMIN`. |
| `/dev/net/tun` | Das Kernel-TUN-Gerät, das Yggdrasil öffnet. |
| `init: true` | Sauberer PID 1: Reaping + SIGTERM erreicht den Daemon, damit `docker compose stop` als sauberer Kill-Switch funktioniert. |
Der Container läuft als `root` — für TUN im Container üblich. `NET_ADMIN`
ist bewusst nur als Capability gesetzt, ohne `privileged`.
## Zwei Haushalte verbinden
Beide Hosts laufen mit demselben Image; die Rolle (Exit oder Leech) steht
nur in der jeweiligen `node.toml`.
- **Die Mesh-Verbindung liegt außerhalb des Containers:** Glasfaser,
Switch oder WiFi zwischen den Haushalten. Der Container peeret über das
Host-Netzwerk.
- **Auto-Peering:** Im selben LAN (Multicast aktiv) finden sich
Yggdrasil-Nodes selbst — `peers = []` reicht.
- **Explizit:** Falls Multicast nicht geht (z. B. VLANs), trägt man in
`node.toml` bei `peers` URIs wie `tcp://<nachbar-lan-ip>:1337` ein.
- **ACL:** Die 64-Hex-Public-Keys der erlaubten Nachbarn gehören in
`allow_keys`, sonst kann jeder Node im Overlay peeren.
Die `node.toml` jedes Haushalts enthält persönliche Daten der Nachbarschaft
(Public Keys, Adressen) und später den Node-Private-Key im State-Verzeichnis:
**niemals committen**, außerhalb des Repos aufbewahren.
## Leech-Modus (Empfang ohne Exit)
Die Beispiel-Konfiguration startet sicher im **Leech-Modus** — ein
Haushalt, der nur empfangen will, muss nichts ändern:
```toml
[exit]
enabled = false
```
Das ist ausdrücklich legitim — Leeches werden bei Sättigung zuerst
gedrosselt, aber nie ausgeschlossen. Es gibt kein separates Leech-Image;
das Compose-Beispiel funktioniert unverändert.
Wer seinen Uplink als Exit anbieten will (opt-in), setzt:
```toml
[exit]
enabled = true
allow = ["<overlay-adresse-des-nachbarn>"]
```
`enabled = true` mit leerer `allow`-Liste ist ungültig — der Daemon
verweigert dann den Start (Ausnahme: Dev-Modus mit Loopback-bind).
## Kill-Switch
```sh
docker compose stop
```
Stoppen des Exits bzw. des Daemons ist der Kill-Switch: Sobald der Daemon
nicht läuft, stellt der Haushalt keinen Uplink mehr bereit. Der eigene Node
fällt auf den eigenen Internetanschluss zurück. Wichtig zum Verständnis:
SpiderNet v0 leitet **normalen Verkehr nicht um** — kein Routing, kein
NAT-Trick. Nur Downloads, die `sn-fetch` explizit über das Netz verteilt,
nutzen die gepoolte Bandbreite; alles andere geht wie gewohnt über den
eigenen Anschluss.
## Zustand
`node.key` und die generierte `yggdrasil.conf` liegen im State-Verzeichnis
(`/var/lib/spidernet`, Named Volume `spidernet-state` im Compose-Beispiel).
Der Node behält damit seine Identität über Container-Neustarts. Volume
löschen = neue Node-Identität.
## Noch offen / Hinweise
- Der `daemon`-Subcommand ist implementiert und die Beispiel-Konfiguration
wurde damit verifiziert (`sn-node daemon --check` → `config ok`).
- Images werden lokal gebaut, nicht in ein Registry gepusht (das Projekt
kennt keine zentrale Infrastruktur).
+74
View File
@@ -0,0 +1,74 @@
# SpiderNet node — single-household example (one node per host).
#
# NO WARRANTY — SpiderNet is provided "as is" without warranty of any kind
# (AGPL-3.0, sections 15/16). Running a node is entirely at the operator's
# own responsibility.
#
# The same image serves both roles:
# - exit household: [exit] enabled = true in node.toml (plus neighbor
# addresses in [exit].allow — enabled with an empty allowlist is
# rejected by the daemon)
# - leech household: [exit] enabled = false in node.toml (the example
# config default — safe, receive-only)
# There is no separate leech image; only the config differs.
#
# Quickstart:
# cp node.toml.example node.toml # then edit node.toml
# docker compose up --build -d
services:
node:
build:
# Build context is server/, matching the Dockerfile paths.
context: ..
dockerfile: docker/Dockerfile
image: spidernet-node
container_name: spidernet-node
# Proper PID 1: reaps zombies and makes sure SIGTERM reaches sn-node
# so `docker compose stop` behaves as the documented kill switch.
init: true
# Yggdrasil must bind its TUN interface and peer on the real LAN;
# both require the host network stack (no NAT translation).
network_mode: host
cap_add:
# Needed by yggdrasil to create/configure the TUN interface.
- NET_ADMIN
devices:
# The kernel TUN device used for the overlay interface.
- /dev/net/tun:/dev/net/tun
restart: unless-stopped
volumes:
# Config is read-only; state (node.key, yggdrasil.conf) goes to a
# named volume so the node identity survives container upgrades.
- ./node.toml:/etc/spidernet/node.toml:ro
- spidernet-state:/var/lib/spidernet
# ---------------------------------------------------------------------------
# EXIT VARIANT (offering the household's uplink as exit):
#
# The example config defaults to leech mode ([exit] enabled = false). To
# become an exit household set enabled = true in node.toml and add the
# neighbor overlay addresses to [exit].allow. This compose file works
# unchanged for both roles (kill switch: `docker compose stop`).
#
# If you prefer two service definitions (e.g. to toggle via profiles), note
# that `network_mode: host` allows only ONE node per host — stop the other
# service first:
#
# node-leech:
# build:
# context: ..
# dockerfile: docker/Dockerfile
# image: spidernet-node
# container_name: spidernet-node-leech
# init: true
# network_mode: host
# cap_add: [NET_ADMIN]
# devices: ["/dev/net/tun:/dev/net/tun"]
# restart: unless-stopped
# volumes:
# - ./node-leech.toml:/etc/spidernet/node.toml:ro
# - spidernet-state-leech:/var/lib/spidernet
volumes:
spidernet-state:
+95
View File
@@ -0,0 +1,95 @@
# Beispielkonfiguration für den SpiderNet-Node-Daemon (`sn-node daemon`).
#
# Vorlage: diese Datei nach `node.toml` kopieren und anpassen. Die
# Compose-Datei in diesem Ordner bindet `./node.toml` ein.
#
# KEINE GARANTIE — SpiderNet wird "as is" ohne jegliche Garantie
# bereitgestellt (AGPL §§ 15/16). Nutzung auf eigene Verantwortung;
# die Verantwortung für gesendete und empfangene Inhalte liegt bei den
# Nutzer:innen.
#
# WICHTIG: Die echte node.toml enthält persönliche Daten der Nachbarschaft
# (Public Keys, Adressen) und später den Node-Private-Key im state_dir.
# node.toml NIEMALS ins Repository committen.
[node]
# Admin-Socket des Yggdrasil-Knotens (tcp://host:port oder unix:///pfad).
# Wird auch als Yggdrasil-AdminListen verwendet, wenn der Daemon das
# Yggdrasil-Sidecar selbst verwaltet (siehe [yggdrasil].manage).
admin_endpoint = "tcp://localhost:9001"
# Verzeichnis für den Zustand des Nodes (node.key, generierte
# yggdrasil.conf). Optional, Standard "spidernet-state" — relativ zum
# Arbeitsverzeichnis des Daemons. Im Container besser absolut setzen,
# damit es auf das State-Volume fällt:
state_dir = "/var/lib/spidernet"
[yggdrasil]
# true = der Daemon startet und überwacht das Yggdrasil-Sidecar selbst.
# false = es wird ein bereits laufender Yggdrasil-Daemon verwendet.
manage = true
# Pfad/Name des Yggdrasil-Binary; nur relevant, wenn manage = true.
# Im Container ist yggdrasil unter /usr/local/bin/yggdrasil installiert.
binary = "yggdrasil"
# Peer-URIs (tcp://, tls://, quic://, socks://) für manuelles Peering.
# Über das Nachbarschafts-LAN peert Yggdrasil per Multicast automatisch;
# explizite Peers braucht man z. B. über WLAN-Brücken hinweg.
peers = []
# Listener-URIs für eingehende Peerings (tcp://, tls://, quic://).
listen = []
# Access-Control: 64-stellige Hex-Public-Keys der Nachbarn, die mit uns
# peeren dürfen (AllowedPublicKeys). Leere Liste = jeder darf peeren.
allow_keys = []
# true = Multicast-Auto-Peering im LAN deaktivieren.
no_multicast = false
# TUN-Interface: "auto" (Standard), "none" (headless, nur Router-Modus)
# oder ein fester Interface-Name.
ifname = "auto"
[exit]
# true = dieser Haushalt stellt seinen Uplink als Exit bereit (opt-in).
# false = Leech-Modus (Standard hier): der Node empfängt nur, bietet
# keinen Exit an — ausdrücklich legitim, niemand wird gedrosselt, nur
# fair priorisiert.
#
# Wer Exit anbieten will: enabled = true setzen UND in `allow` die
# Overlay-Adressen der Nachbarn eintragen — enabled = true mit leerer
# `allow`-Liste ist ungültig (der Daemon verweigert den Start; Ausnahme:
# Dev-Modus mit Loopback-bind).
enabled = false
# Explizite Bind-Adresse. Leer (Standard) = "[<Yggdrasil-Adresse>]:port",
# d. h. der Exit lauscht auf der eigenen Overlay-Adresse.
# UNGÜLTIG: allow = [] zusammen mit einem nicht-Loopback-bind — der Exit
# wäre sonst für beliebig viele Quellen offen (Schutz: ohne Allowlist nur
# Loopback, das ist der Dev-Modus).
bind = ""
# TCP-Port des Exit-Dienstes (u16, Standard 8080).
port = 8080
# Overlay-Adressen der Nachbarn, die diesen Exit nutzen dürfen.
# Leere Liste = nur Loopback (Dev-Modus); bei nicht-Loopback-bind Pflicht.
# Beispiel:
# allow = ["200:abcd:ef01::2", "200:abcd:ef01::3"]
allow = []
# Kontingent pro Nachbar: Bytes pro Zeitfenster (u64, Standard 1024 MiB).
# 0 = unbegrenzt (kein Kontingent).
quota_mib = 1024
# Länge des Kontingent-Zeitfensters in Stunden (fixed window).
window_hours = 24
# KILL-SWITCH: Wer nichts mehr teilen will, stoppt einfach den Exit bzw.
# den Daemon (Container: `docker compose stop`). Damit endet die
# Bereitstellung des eigenen Uplinks sofort — der eigene Node fällt auf
# den eigenen Internetanschluss zurück. SpiderNet v0 leitet normalen
# Verkehr nicht um: nur Downloads, die sn-fetch explizit über das Netz
# verteilt, nutzen die gepoolte Bandbreite.