Skip to content

Help pin the site's IPFS mirror

Alongside the main site, the anoni.net docs are also published as an IPFS mirror, so the content stays readable when the main site is blocked or taken down. Content on IPFS only survives while some node pins it, and right now only the community's own node does. Every extra node that helps pin is one more complete copy on the network, and more resistance to takedown.

This page walks you through running one always-on IPFS node plus a small scheduled script that keeps up with the latest version automatically. It works the same on Windows, Linux, and macOS, with or without Docker.

What you need to help

  • A computer that stays on most of the day and can reach the network (a desktop, a home server, or a small always-on box).
  • IPFS installed (this page shows you how).
  • A scheduled script that pins the latest version every few hours.

Can't run a node? The "No node" section at the end covers pinning through a service instead.

Why the CID changes every time (30-second IPFS primer)

If IPFS is new to you, one idea makes the rest of this page make sense. For the deeper design, see the IPFS content addressing docs; here is just enough to work with.

  • IPFS hashes a file's content into a CID (Content Identifier), and that CID is the content's address. Identical content always has the same CID.
  • Change the content and the CID changes with it. Every time the docs site updates and republishes, a brand-new CID is produced.
  • IPNS (InterPlanetary Name System) is a fixed name that always points to the current CID. The site's IPNS name is the k51… string below.
  • Pinning means "guarantee this specific CID stays available." A pin is bound to a CID and does not follow IPNS on its own. So the moment the site publishes a new version, what you pinned last time is still the old CID.

That is exactly what the script does: resolve IPNS to the current CID, pin that new CID, then drop the old one. Because the CID changes, this has to run periodically, which is why it's scheduled.

How it works (why a schedule is enough, no notification needed)

Nobody has to tell you "a new version is out." IPNS is the shared sync point, and your script resolves it to get the latest CID by itself. When the site publishes, it just updates IPNS as usual; your side resolves every few hours, notices the change, and pins the new version. No manual coordination anywhere.

The site's IPFS coordinates (public values, use them directly):

Each run does this: resolve IPNS to the current CID, pin the new CID, unpin the previous one, reclaim space. The script confirms the new version pinned successfully before dropping the old one. If resolving or fetching fails, it keeps the copy you already have and never empties your node.

Step 1: Run an always-on IPFS node

For pinning to fetch the content, your machine needs a continuously running IPFS daemon. Pick one setup below.

Install kubo, IPFS's official command-line implementation. On macOS you can use Homebrew:

brew install ipfs

On Linux, download the kubo build for your architecture from the official guide. Then initialize and start the daemon:

ipfs init          # first time only
ipfs daemon

To keep the daemon running long-term, use a systemd user service on Linux, or brew services / launchd on macOS. For a quick test, running ipfs daemon in the background is fine.

The easiest option is IPFS Desktop. It bundles kubo and, once you log in, stays running in the system tray with the daemon always on.

After installing, make sure ipfs is callable from the command line. A standalone kubo install needs ipfs.exe added to your PATH; with IPFS Desktop, if PATH can't find ipfs, use the full path in the script instead.

Run kubo with a docker-compose.yml, naming the container ipfs_host:

services:
  ipfs_host:
    image: ipfs/kubo:latest
    restart: always
    volumes:
      - ./ipfs-data:/data/ipfs
    ports:
      - "4001:4001"   # swarm; exposing it helps reach other nodes

Start it:

docker compose up -d

From here, run IPFS commands through the container by setting an environment variable the script picks up automatically (used in the next step):

export IPFS_CMD="docker exec ipfs_host ipfs"

Step 2: Get the pin script

The script resolves IPNS, pins the new version, and unpins the old one on its own. The IPNS name is hard-coded in it, so there's nothing to edit. Docker users: set IPFS_CMD from the previous step first.

Linux, macOS, Docker: anoni-pin.sh

Download anoni-pin.sh

#!/usr/bin/env bash
#
# anoni-pin.sh — pin the latest IPFS mirror of the anoni.net docs site.
#
# Usage:
#   ./anoni-pin.sh          # uses the local `ipfs`
# Run it on a schedule (cron) every 6 hours or so. See the docs for setup.
#
# Docker users: route ipfs commands through the container
#   export IPFS_CMD="docker exec ipfs_host ipfs"
#
# Design: pin the new version first, only then unpin the old one. If resolving
# or fetching fails, keep the copy you already have — never leave the node empty.
#
set -eu

# The docs site's IPNS name (public value, same as DNSLink _dnslink.anoni.net)
IPNS="k51qzi5uqu5dlfm2jj0f70ex3r3babmwy8qh071inwknttr7wqa3uhdwvlmrmw"

# Override the ipfs command via IPFS_CMD (see the Docker note above)
IPFS="${IPFS_CMD:-ipfs}"

# Remembers the last CID we pinned, so we can unpin it next time
STATE="${ANONI_PIN_STATE:-${HOME}/.anoni-pin/last_cid}"
mkdir -p "$(dirname "$STATE")"

# 1. Resolve the IPNS name to the current CID (--nocache forces a fresh lookup)
NEW="$($IPFS name resolve --nocache "/ipns/$IPNS" 2>/dev/null || true)"
if [ -z "$NEW" ]; then
    echo "[anoni-pin] resolve failed (network or node issue), keeping current state"
    exit 0
fi

OLD="$(cat "$STATE" 2>/dev/null || true)"
if [ "$NEW" = "$OLD" ]; then
    echo "[anoni-pin] already on the latest version $NEW, nothing to do"
    exit 0
fi

# 2. Pin the new version first (fetches the whole thing). With set -e, a failure
#    here aborts the script before we unpin anything.
echo "[anoni-pin] pinning new version: $NEW"
$IPFS pin add "$NEW"

# 3. Record state, unpin the old version, reclaim space
echo "$NEW" > "$STATE"
if [ -n "$OLD" ] && [ "$OLD" != "$NEW" ]; then
    echo "[anoni-pin] unpinning old version: $OLD"
    $IPFS pin rm "$OLD" || true
fi
$IPFS repo gc >/dev/null 2>&1 || true
echo "[anoni-pin] done, now pinning $NEW"

Save it and make it executable:

chmod +x anoni-pin.sh
./anoni-pin.sh          # run once by hand to check it works

Windows: anoni-pin.ps1

Download anoni-pin.ps1

#
# anoni-pin.ps1 — pin the latest IPFS mirror of the anoni.net docs site (Windows / PowerShell).
#
# Usage: make sure an IPFS daemon (IPFS Desktop or kubo) is running, then
#   powershell -NoProfile -ExecutionPolicy Bypass -File .\anoni-pin.ps1
# Run it on a schedule with Task Scheduler every 6 hours or so. See the docs.
#
# Docker users:
#   $env:IPFS_CMD = "docker exec ipfs_host ipfs"
#
# Design: pin the new version first, only then unpin the old one. If resolving
# or fetching fails, keep the copy you already have.
#

# The docs site's IPNS name (public value, same as DNSLink _dnslink.anoni.net)
$IPNS = 'k51qzi5uqu5dlfm2jj0f70ex3r3babmwy8qh071inwknttr7wqa3uhdwvlmrmw'

# Remembers the last CID we pinned, so we can unpin it next time
$State = if ($env:ANONI_PIN_STATE) { $env:ANONI_PIN_STATE } else { Join-Path $env:LOCALAPPDATA 'anoni-pin\last_cid.txt' }
New-Item -ItemType Directory -Force -Path (Split-Path $State) | Out-Null

# Override the ipfs command via IPFS_CMD (see the Docker note). Split into exe + prefix args.
$cmd   = if ($env:IPFS_CMD) { $env:IPFS_CMD } else { 'ipfs' }
$parts = $cmd -split '\s+'
$exe   = $parts[0]
$pre   = @()
if ($parts.Length -gt 1) { $pre = $parts[1..($parts.Length - 1)] }
function Invoke-Ipfs { & $exe @pre @args }

# 1. Resolve the IPNS name to the current CID (--nocache forces a fresh lookup)
$New = (Invoke-Ipfs name resolve --nocache "/ipns/$IPNS" 2>$null | Out-String).Trim()
if ([string]::IsNullOrWhiteSpace($New)) {
    Write-Host '[anoni-pin] resolve failed (network or node issue), keeping current state'
    exit 0
}

$Old = if (Test-Path $State) { (Get-Content $State -Raw).Trim() } else { '' }
if ($New -eq $Old) {
    Write-Host "[anoni-pin] already on the latest version $New, nothing to do"
    exit 0
}

# 2. Pin the new version first. Check LASTEXITCODE (not exceptions); on failure keep the old version.
Write-Host "[anoni-pin] pinning new version: $New"
Invoke-Ipfs pin add $New
if ($LASTEXITCODE -ne 0) {
    Write-Host '[anoni-pin] pin add failed, keeping the old version'
    exit 1
}

# 3. Record state, unpin the old version, reclaim space
Set-Content -Path $State -Value $New -NoNewline
if ($Old -and $Old -ne $New) {
    Write-Host "[anoni-pin] unpinning old version: $Old"
    Invoke-Ipfs pin rm $Old 2>$null
}
Invoke-Ipfs repo gc 2>$null | Out-Null
Write-Host "[anoni-pin] done, now pinning $New"

Run it once by hand to check it works (PowerShell blocks scripts by default, so -ExecutionPolicy Bypass allows this one run):

powershell -NoProfile -ExecutionPolicy Bypass -File .\anoni-pin.ps1

Step 3: Schedule it

Have the script run every few hours. The site doesn't update often, so every 6 hours is plenty; shorten it if you want to keep up faster.

Use cron. Edit your crontab:

crontab -e

Add a line that runs every 6 hours and writes output to a log:

0 */6 * * * /path/to/anoni-pin.sh >> $HOME/.anoni-pin/log 2>&1

To also catch up on missed runs after a reboot, use a systemd timer with Persistent=true instead, which is more reliable.

Use Task Scheduler. The quickest way is to create it from the command line, running every 6 hours:

schtasks /Create /TN "anoni-ipfs-pin" `
  /TR "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Tools\anoni-pin.ps1" `
  /SC HOURLY /MO 6 /RL LIMITED

Replace C:\Tools\anoni-pin.ps1 with wherever you saved it. You can also use the Task Scheduler GUI: set the program to powershell and the arguments to -NoProfile -ExecutionPolicy Bypass -File your-path.

Schedule according to your host OS (cron on Linux/macOS, Task Scheduler on Windows; see the two tabs above). The only difference is setting IPFS_CMD before the script runs, for example via a small wrapper:

#!/usr/bin/env bash
export IPFS_CMD="docker exec ipfs_host ipfs"
exec /path/to/anoni-pin.sh

Point cron at this wrapper.

Step 4: Verify

Confirm the content is actually pinned. Resolve the current CID, then check it's in the pin list:

CID=$(ipfs name resolve --nocache /ipns/k51qzi5uqu5dlfm2jj0f70ex3r3babmwy8qh071inwknttr7wqa3uhdwvlmrmw)
ipfs pin ls --type=recursive | grep "${CID#/ipfs/}"

You can also open it on your local gateway and check it renders: http://127.0.0.1:8080${CID}/. Docker users: replace ipfs above with docker exec ipfs_host ipfs.

Maintenance and notes

  • It keeps up automatically. When the site changes its CID, the next scheduled run pins the new version and drops the old one. You do nothing.
  • Disk use stays flat. After unpinning the old version the script runs a garbage collection, so only the latest version takes space. The site is a plain static site and isn't large.
  • It won't lose the copy you have. The script pins the new version successfully before dropping the old one, and keeps your existing copy if resolving or downloading fails.
  • To stop helping: unpin the current version and remove the schedule. It doesn't affect any other node.
  • Privacy and risk: what you pin is public documentation, so there's no privacy concern. Offering IPFS pinning carries different legal risk across jurisdictions, so weigh that for where you operate.

No node: pin through a service instead

If you'd rather not maintain a daemon and a schedule, use a pinning service like Pinata or Storacha. Paste the current CID into their interface to pin it manually, or script their API to feed it new CIDs, with the same logic as this page (resolve IPNS to a CID, hand the CID to the service).

The trade-off is that survival now depends on a third-party provider rather than your own node. Fine as a backup; for real decentralization, running your own node is the most direct.