Skip to content

Commit c5ef6d6

Browse files
committed
docs: substantive CRG C annotation (EXPLAINME.adoc)
1 parent f21420f commit c5ef6d6

1 file changed

Lines changed: 218 additions & 5 deletions

File tree

EXPLAINME.adoc

Lines changed: 218 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,237 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
23
= ipfs-overlay — Show Me The Receipts
34
:toc:
5+
:toclevels: 3
46
:icons: font
57

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
714

815
[quote, README]
916
____
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.
1119
____
1220

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+
13144
== File Map
14145

15-
[cols="1,2"]
146+
[cols="2,3", options="header"]
16147
|===
17148
| Path | What's There
18149

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.
20232
|===
21233

22234
== Questions?
23235

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

Comments
 (0)