Site Navigation

Edit on GitHub


SnapStack - Collaborative Writing, Stacked as Immutable Snapshots

What IPFS gives you (in this context)

Minimal setup (Kubo CLI)

  1. Install Kubo (ipfs) and init:
# macOS
brew install ipfs
ipfs init
ipfs daemon

Docs: install, init, and basic CLI. (docs.ipfs.tech)

  1. Add your text collection and get a CID:
ipfs add -Qr /path/to/texts     # -Q = quiet (just the CID), -r = recursive

Re-running ipfs add -r after edits yields a new top-level CID (subfiles’ CIDs stay the same if unchanged). (Stack Overflow)

  1. Create (once) and publish an IPNS name:
ipfs key gen --type=rsa --size=2048 texts-key
ipfs name publish --key=texts-key <CID_FROM_STEP_2>
# Output looks like: Published to k51q...: /ipfs/<CID>

IPNS is a mutable pointer you can republish to when the CID changes; nodes periodically rebroadcast the record. (docs.ipfs.tech)

  1. Peers fetch & pin:
# Using the CID directly
ipfs pin add <CID>

# Or follow your [[IPNS name]] to the current CID, then pin:
ipfs name resolve k51q...             # shows /ipfs/<currentCID>
ipfs pin add <currentCID>

About pinning (local or via a remote service). (docs.ipfs.tech, docs.pinata.cloud)

Updating your shared set

Each time you edit files:

NEWCID=$(ipfs add -Qr /path/to/texts)
ipfs name publish --key=texts-key $NEWCID

That’s it—your IPNS address stays the same; it just points to the latest snapshot. (Under the hood, directory CIDs change when contents change.) (Stack Overflow)

Optional niceties

Trade-offs to know

love this direction. here’s a lightweight, low-drama set of “how we work” rules for a small group (3–10 people) using IPFS/IPNS as a publish-signed-snapshot workflow for a Markdown collection.

Workflow rules for SnapStack

0) Ground rules (philosophy)


1) Roles

Tip: every contributor may also create a personal IPNS key to share work-in-progress or proposals.


2) Directory & docs (in the repo itself)

/ (root of the snapshot)
  /docs/…                # markdown content
  /assets/…              # images, diagrams
  /scripts/…             # helper scripts (publish, validate)
  CONTRIBUTING.md        # how to propose changes (this doc distilled)
  STYLE.md               # headings, links, filenames, TOC rules
  CHANGELOG.md           # human-readable release notes (top entry = tip)
  MANIFEST.json          # machine-readable: CID, parentCID, author, date, summary, version
  LICENSE
  README.md              # what this collection is, how to fetch latest

MANIFEST.json (one line JSON, easy to diff/view):

{
  "cid": "bafy…",
  "parent_cid": "bafy…",
  "version": "2025-08-29-001",
  "timestamp": "2025-08-29T16:45:00-07:00",
  "author": "Alice <alice@example.org>",
  "summary": "Fix typos in 03-intro.md; add diagram alt text; linkcheck clean",
  "files_changed": ["docs/03-intro.md", "assets/flow-1.png"]
}

3) Versioning


4) The happy path (90% of the time)

A) Prepare your proposal (Contributor)

  1. Sync latest: resolve main IPNS → CURRENT_CID. Fetch/pin and work from that.

  2. Edit locally. Follow STYLE.md.

  3. Self-check:

    • run scripts/validate.sh (spelling, markdown lint, linkcheck)
    • update CHANGELOG.md (top entry) and MANIFEST.json (leave cid empty for now; set parent_cid to CURRENT_CID)
  4. Build your snapshot: NEW_CID=$(ipfs add -Qr .)

  5. Freeze manifest: write NEW_CID into MANIFEST.json’s cid field.

  6. Share proposal: post NEW_CID + short summary (and a diff link if you keep a Git mirror—optional).

B) Review & accept (MoR)

  1. Resolve proposal: ipfs get NEW_CID (or browse via gateway).
  2. Run scripts/validate.sh yourself (don’t trust; verify).
  3. Quick editorial review: spot-check changed files; ensure CHANGELOG.md + MANIFEST.json sane.
  4. Publish to main: ipfs name publish --key=main-key NEW_CID
  5. Announce: post the new version string, NEW_CID, and the CHANGELOG entry.
  6. Pin policy: ask everyone (or your pinning service) to pin NEW_CID. Optionally unpin parent_cid after grace period.

5) Light conflict avoidance


6) Trust & keys


7) Rebasing your local edits (when someone beats you to publish)

  1. Note your work-in-progress CID (WIP_CID).
  2. Pull latest main → BASE_CID.
  3. Reapply your changes on top of BASE_CID (manual since no merges). Keep edits small; copy over files you changed.
  4. Produce a fresh NEW_CID, set parent_cid = BASE_CID, reshare proposal.

8) Pinning & retention


9) Quality gates (fast, local)

Put these in scripts/validate.sh.

Example scripts/validate.sh (bash):

#!/usr/bin/env bash
set -euo pipefail

# 1) markdownlint (install locally or via container)
command -v markdownlint || echo "(!) install markdownlint for full checks"

# 2) basic spelling with codespell if present
if command -v codespell >/dev/null; then
  codespell -q 3 -S "assets/*,node_modules/*,vendor/*"
fi

# 3) rudimentary link check (skip gateways to avoid false negatives)
if command -v lychee >/dev/null; then
  lychee --exclude "ipfs://|ipns://|localhost" --offline false docs || true
fi

echo "✓ basic validation done"

10) Scripts (tiny helpers)

scripts/publish_latest.sh (run by MoR):

#!/usr/bin/env bash
set -euo pipefail

KEY_NAME="${KEY_NAME:-main-key}"

# Ensure MANIFEST.json cid matches the tree we’re about to publish
NEW_CID="$(ipfs add -Qr .)"
MANIFEST_CID="$(jq -r .cid MANIFEST.json || true)"

if [[ "$MANIFEST_CID" != "$NEW_CID" ]]; then
  echo "Updating MANIFEST.json cid to $NEW_CID"
  tmp="$(mktemp)"
  jq --arg cid "$NEW_CID" '.cid=$cid' MANIFEST.json > "$tmp" && mv "$tmp" MANIFEST.json
  NEW_CID="$(ipfs add -Qr .)"
fi

echo "Publishing $NEW_CID to IPNS key: $KEY_NAME"
ipfs name publish --key="$KEY_NAME" "$NEW_CID"

echo "Reminder: pin the new CID:"
echo "  ipfs pin add $NEW_CID"

scripts/adopt_new_release.sh (for all peers):

#!/usr/bin/env bash
set -euo pipefail

IPNS_ADDR="${1:?Usage: adopt_new_release.sh <ipns-name-or-peerid>}"

CURRENT="/ipfs/$(ipfs name resolve "$IPNS_ADDR" | sed 's|/ipfs/||')"
CID="${CURRENT#/ipfs/}"

echo "Latest main CID: $CID"
ipfs pin add "$CID"
echo "✓ pinned $CID"

11) Communications


12) Backups & mirrors (optional but handy)


13) SLAs & latency expectations


14) Security & recovery


15) Minimal checklist cards

Contributor (before proposing):

Maintainer (before publishing to main):


Optional SnapStack Hashchain

  1. “Signed snapshots” (recommended baseline)

Keep MANIFEST.json with cid, parent_cid, timestamp, author.

Sign the manifest (ssh-sig, minisign/age, or GPG). Publish the signed tree to IPFS; update IPNS.

Why it’s enough: anyone can verify the chain (parent_cid links) and the signature on each step.

This is essentially a hash-chain; no extra infra beyond your existing workflow.

  1. Add public timestamps (cheap, no new infra)

Feed the CID (or manifest hash) into OpenTimestamps to anchor its existence time into Bitcoin via Merkle aggregation. You get a small .ots proof file that anyone can later verify. opentimestamps.org Bitcoin Stack Exchange

When to add: you want “this snapshot existed no later than ” without running a chain.

Suggested “SnapStack” recipe (simple + strong)

Keep the MANIFEST.json with parent_cid chain (as you already planned).

Sign each manifest (team-wide shared policy for keys).

OpenTimestamps the manifest’s SHA-256 and store the .ots next to it. opentimestamps.org

(Optional) Also post the tuple {cid, manifest hash, signature} to Rekor so anyone can audit via a public transparency log.


Pages that link to this page