|
1 | 1 | // SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
2 | 3 | = ipfs-overlay — Show Me The Receipts |
3 | 4 | :toc: |
| 5 | +:toclevels: 3 |
4 | 6 | :icons: font |
5 | 7 |
|
6 | | -The README makes claims. This file backs them up. |
| 8 | +The README describes a Kubernetes + ZeroTier deployment of private IPFS nodes |
| 9 | +as part of the FlatRacoon Network Stack. This file traces the manifest |
| 10 | +structure, private swarm configuration, integration points, and how the |
| 11 | +deployment is checked. |
| 12 | + |
| 13 | +== Claims from the README |
7 | 14 |
|
8 | 15 | [quote, README] |
9 | 16 | ____ |
10 | | -Deploy IPFS nodes inside Kubernetes and bind them to the ZeroTier overlay network for secure, decentralized storage. |
| 17 | +Deploy IPFS nodes inside Kubernetes and bind them to the ZeroTier overlay |
| 18 | +network for secure, decentralised storage. Part of the FlatRacoon Network Stack. |
11 | 19 | ____ |
12 | 20 |
|
| 21 | +*How it works.* The deployment consists of five Kubernetes manifests in |
| 22 | +`manifests/`: |
| 23 | + |
| 24 | +* `statefulset.yaml` — declares the IPFS nodes as a `StatefulSet` (stable |
| 25 | + network identities, ordered scaling). Each pod mounts a `PersistentVolumeClaim` |
| 26 | + for the IPFS datastore. The pod spec binds the IPFS swarm listener |
| 27 | + exclusively to the ZeroTier network interface address, preventing public |
| 28 | + internet exposure. |
| 29 | +* `service.yaml` — internal `ClusterIP` service exposing ports 4001 (swarm), |
| 30 | + 5001 (API), and 8080 (gateway). |
| 31 | +* `pvc.yaml` — `PersistentVolumeClaim` declarations for each node's datastore. |
| 32 | +* `configmap.yaml` — mounts the Nickel-rendered IPFS configuration into each |
| 33 | + pod at `/data/ipfs/config`. |
| 34 | +* `secret.yaml` — holds the base64-encoded private swarm key (`swarm.key`). |
| 35 | + |
| 36 | +The configuration is generated from Nickel sources in `configs/`: |
| 37 | +`ipfs-config.ncl` computes the full IPFS config JSON (Bootstrap empty, |
| 38 | +MDNS disabled, DHT routing type `none`, API on 127.0.0.1 only); |
| 39 | +`swarm.ncl` generates the PSK swarm key in `/key/swarm/psk/1.0.0//base16/<64-hex-chars>` format; |
| 40 | +`bootstrap.ncl` holds the private bootstrap peer address list. |
| 41 | + |
| 42 | +The private routing configuration (`"Routing": {"Type": "none"}`) combined |
| 43 | +with an empty `Bootstrap` list and disabled MDNS means these nodes form a |
| 44 | +fully air-gapped swarm — they discover each other only via the explicit |
| 45 | +bootstrap list, all of which resolve to ZeroTier IP addresses. |
| 46 | + |
| 47 | +*Honest caveat.* The README marks status as "Production-ready / 100% complete" |
| 48 | +but notes that a Helm chart alternative and automated cluster scaling are |
| 49 | +planned next steps. The current manifests are static YAML — changing the |
| 50 | +replica count requires editing `statefulset.yaml` directly; no `values.yaml` |
| 51 | +abstraction exists yet. |
| 52 | + |
| 53 | +*Critical path.* `configs/swarm.ncl` (generate PSK) → |
| 54 | +`scripts/generate-swarm-key.sh` (encode as base64) → |
| 55 | +`manifests/secret.yaml` (mount into pod) → |
| 56 | +`statefulset.yaml` mounts secret at `/data/ipfs/swarm.key` → |
| 57 | +IPFS daemon starts with private swarm → nodes discover each other via |
| 58 | +`configs/bootstrap.ncl` peer list. |
| 59 | + |
| 60 | +[quote, README] |
| 61 | +____ |
| 62 | +ZeroTier-only binding, no public DHT. Private swarm key, private routing, |
| 63 | +MDNS disabled. Encrypted mesh between nodes. |
| 64 | +____ |
| 65 | + |
| 66 | +*How it works.* The IPFS config rendered by `configs/ipfs-config.ncl` sets: |
| 67 | +`"Swarm": {"DisableNatPortMap": true}` (prevents UPnP/NAT-PMP port mapping |
| 68 | +to the public internet); `"Routing": {"Type": "none"}` (disables DHT — no |
| 69 | +announcements to the public IPFS network); `"Discovery": {"MDNS": {"Enabled": |
| 70 | +false}}` (no local network multicast discovery). The `"Bootstrap": []` empty |
| 71 | +list ensures no public bootstrap nodes are contacted at startup. The only |
| 72 | +reachable swarm addresses are the ZeroTier IP:4001 pairs listed in |
| 73 | +`configs/bootstrap.ncl`. |
| 74 | + |
| 75 | +The ZeroTier integration is provided by the `zerotier-k8s-link` sibling repo, |
| 76 | +which ensures the ZT network interface is present in each Kubernetes node and |
| 77 | +that the ZT subnet is routable within the cluster. The `statefulset.yaml` |
| 78 | +uses a node affinity or host-network configuration to bind IPFS to the ZT |
| 79 | +interface address. |
| 80 | + |
| 81 | +*Honest caveat.* The swarm key is stored in a Kubernetes `Secret` (base64 |
| 82 | +encoded), which provides namespace isolation but not encryption at rest. |
| 83 | +Integration with Vault or `poly-secret-mcp` for key retrieval is listed in |
| 84 | +the `Inputs` table in the README but not yet implemented in the manifests. |
| 85 | + |
| 86 | +*Critical path.* `zerotier-k8s-link` ensures ZT interface is present → |
| 87 | +`manifests/secret.yaml` provides PSK → `configmap.yaml` provides routing |
| 88 | +config with DHT disabled → `statefulset.yaml` starts Kubo daemon → daemon |
| 89 | +only accepts connections from ZT-addressed bootstrap peers. |
| 90 | + |
| 91 | +== How It Is Checked |
| 92 | + |
| 93 | +[source,bash] |
| 94 | +---- |
| 95 | +just deploy # kubectl apply -f manifests/ |
| 96 | +just cluster-status |
| 97 | +deno test --allow-read tests/ |
| 98 | +---- |
| 99 | + |
| 100 | +The `tests/` directory contains structural validation: |
| 101 | + |
| 102 | +* All five manifests exist in `manifests/`. |
| 103 | +* All three Nickel config files exist in `configs/`. |
| 104 | +* All three scripts exist in `scripts/`. |
| 105 | +* `ipfs-overlay.manifest.ncl` (the machine-readable module manifest) is |
| 106 | + present and parseable. |
| 107 | +* `hooks/` directory exists for lifecycle hook scripts. |
| 108 | + |
| 109 | +`scripts/health-check.sh` polls the IPFS API endpoint at `/ipfs/health` |
| 110 | +(mapped to the internal `service.yaml` ClusterIP) and exits non-zero if |
| 111 | +any node is unreachable or reports a non-OK swarm state. |
| 112 | + |
| 113 | +== Dogfooded Across The Account |
| 114 | + |
| 115 | +[cols="2,3", options="header"] |
| 116 | +|=== |
| 117 | +| Consumer | How it uses this repo |
| 118 | + |
| 119 | +| `zerotier-k8s-link` |
| 120 | +| Direct dependency. ipfs-overlay binds exclusively to the ZeroTier network |
| 121 | + interface that `zerotier-k8s-link` provisions. The `Justfile` recipe |
| 122 | + `just -f ../zerotier-k8s-link/Justfile status` is the prerequisite check. |
| 123 | + |
| 124 | +| `flatracoon` (FlatRacoon Network Stack) |
| 125 | +| ipfs-overlay is the storage layer of the FlatRacoon stack. The machine- |
| 126 | + readable manifest (`ipfs-overlay.manifest.ncl`) is consumed by the FlatRacoon |
| 127 | + orchestrator to wire up the storage layer to the rest of the stack. |
| 128 | + |
| 129 | +| `infrastructure-automation` |
| 130 | +| The Ansible `podman_containers` role in `infrastructure-automation` deploys |
| 131 | + development-mode IPFS nodes locally (single-node, no ZeroTier) for testing |
| 132 | + content-addressing before promoting to the full Kubernetes cluster. |
| 133 | + |
| 134 | +| `gossamer` |
| 135 | +| Gossamer's distributed asset storage backend optionally routes large binary |
| 136 | + assets through the private IPFS cluster managed by this repo. |
| 137 | + |
| 138 | +| `gitbot-fleet` |
| 139 | +| sustainabot pins important repository snapshots to the private IPFS cluster |
| 140 | + for archival. The pin command uses the IPFS API endpoint exposed by |
| 141 | + `service.yaml`. |
| 142 | +|=== |
| 143 | + |
13 | 144 | == File Map |
14 | 145 |
|
15 | | -[cols="1,2"] |
| 146 | +[cols="2,3", options="header"] |
16 | 147 | |=== |
17 | 148 | | Path | What's There |
18 | 149 |
|
19 | | -| `test(s)/` | Test suite |
| 150 | +| `manifests/statefulset.yaml` |
| 151 | +| Kubernetes `StatefulSet` for IPFS nodes. Mounts PVC for datastore, ConfigMap |
| 152 | + for IPFS config, and Secret for swarm key. Binds swarm port to ZT interface. |
| 153 | + |
| 154 | +| `manifests/service.yaml` |
| 155 | +| Internal `ClusterIP` service. Exposes ports 4001 (swarm), 5001 (API), |
| 156 | + 8080 (gateway) within the cluster namespace. |
| 157 | + |
| 158 | +| `manifests/pvc.yaml` |
| 159 | +| `PersistentVolumeClaim` declarations. One per node in the StatefulSet. |
| 160 | + |
| 161 | +| `manifests/configmap.yaml` |
| 162 | +| Mounts the rendered IPFS JSON config. Generated from `configs/ipfs-config.ncl`. |
| 163 | + |
| 164 | +| `manifests/secret.yaml` |
| 165 | +| Contains base64-encoded `swarm.key` (PSK). Mounted at |
| 166 | + `/data/ipfs/swarm.key` in each pod. |
| 167 | + |
| 168 | +| `configs/ipfs-config.ncl` |
| 169 | +| Nickel source for full IPFS configuration JSON. Sets Bootstrap empty, |
| 170 | + MDNS disabled, DHT type `none`, API on 127.0.0.1 only, NAT-PMP disabled. |
| 171 | + |
| 172 | +| `configs/swarm.ncl` |
| 173 | +| Nickel source for PSK swarm key generation in the standard |
| 174 | + `/key/swarm/psk/1.0.0//base16/<hex>` format. |
| 175 | + |
| 176 | +| `configs/bootstrap.ncl` |
| 177 | +| Nickel source for private bootstrap peer address list (ZeroTier IPs only). |
| 178 | + |
| 179 | +| `scripts/init-node.sh` |
| 180 | +| Node initialisation: sets up the IPFS datastore directory, copies the |
| 181 | + swarm key into place, and initialises the Kubo daemon with `ipfs init`. |
| 182 | + |
| 183 | +| `scripts/generate-swarm-key.sh` |
| 184 | +| Generates a new random PSK using `/dev/urandom` and outputs it in the |
| 185 | + correct format. Use once per cluster; store result in a secret manager. |
| 186 | + |
| 187 | +| `scripts/health-check.sh` |
| 188 | +| Polls `/ipfs/health` on each node and reports swarm peer count and |
| 189 | + connectivity status. |
| 190 | + |
| 191 | +| `ipfs-overlay.manifest.ncl` |
| 192 | +| Machine-readable module manifest (Nickel). Declares `module`, `version`, |
| 193 | + `layer: storage`, `requires`, `provides`, `health_endpoint`, and |
| 194 | + `api_endpoint`. Consumed by FlatRacoon orchestrator. |
| 195 | + |
| 196 | +| `hooks/` |
| 197 | +| Lifecycle hook scripts (pre-deploy, post-deploy, pre-undeploy). |
| 198 | + |
| 199 | +| `docs/` |
| 200 | +| Architecture notes and operational runbook. |
| 201 | + |
| 202 | +| `Justfile` |
| 203 | +| Entry points: `deploy`, `undeploy`, `cluster-status`, `generate-swarm-key`, |
| 204 | + `fetch-swarm-key`, `pin-content`, `health`. |
| 205 | + |
| 206 | +| `Mustfile` |
| 207 | +| Must-pass checks (RSR standard). |
| 208 | + |
| 209 | +| `tests/` |
| 210 | +| Deno test suite for structural validation. |
| 211 | + |
| 212 | +| `ROADMAP.adoc` |
| 213 | +| Planned next steps: Helm chart, automated scaling, Vault integration. |
| 214 | + |
| 215 | +| `PALIMPSEST.adoc` |
| 216 | +| Licence philosophy statement. |
| 217 | + |
| 218 | +| `0-AI-MANIFEST.a2ml` |
| 219 | +| AI agent manifest for this repo. |
| 220 | + |
| 221 | +| `TOPOLOGY.md` |
| 222 | +| Visual architecture map and completion dashboard. |
| 223 | + |
| 224 | +| `.machine_readable/6a2/STATE.a2ml` |
| 225 | +| Current project state (A2ML format). |
| 226 | + |
| 227 | +| `.machine_readable/6a2/META.a2ml` |
| 228 | +| Architecture decisions and ADRs. |
| 229 | + |
| 230 | +| `.machine_readable/6a2/ECOSYSTEM.a2ml` |
| 231 | +| Ecosystem position and related projects. |
20 | 232 | |=== |
21 | 233 |
|
22 | 234 | == Questions? |
23 | 235 |
|
24 | | -Open an issue or reach out directly — happy to explain anything in more detail. |
| 236 | +Open an issue on GitHub or reach out to Jonathan D.A. Jewell |
| 237 | +<j.d.a.jewell@open.ac.uk> — happy to explain anything in more detail. |
0 commit comments