跳转至

帮忙 pin 文件站的 IPFS 镜像

anoni.net 文件站除了主站,还有一份 IPFS 镜像,让文件在主站被封锁或下架时仍然读得到。IPFS 上的内容要有节点 pin 才会存活,目前只有社群自己的节点在 pin。你多跑一个节点帮忙 pin,网络上就多一份完整副本,抗删除的底气更足。

这页教你用一个常驻的 IPFS 节点,加一支自动跟上最新版本的定时脚本。整个过程对 Windows、Linux、macOS 都适用,有没有用 Docker 都可以。

要帮忙,你需要

  • 一台几乎整天开着、能联网的电脑(台式机、家用服务器、或一台小主机都可以)。
  • 安装 IPFS(下面会带你装)。
  • 跑一支定时脚本,每隔几小时自动 pin 最新版本。

帮不了节点也没关系,文末的「不想自架节点」有用 pinning service 代 pin 的替代做法。

为什么 CID 每次都不一样(IPFS 30 秒入门)

第一次接触 IPFS 的话,先理解一件事,后面的定时才有道理。IPFS 的深入设计见 去中心化网站发布,这里只说明到够用为止。

  • IPFS 把文件内容算出一段 hash,叫做 CID(Content Identifier),这段 CID 就是内容在 IPFS 上的地址。内容相同,CID 就相同。
  • 内容一改,CID 就跟着变。文件站每次更新、重新发布,都会产生一个全新的 CID。
  • IPNS(InterPlanetary Name System)是一个固定不变的名字,永远指向「目前最新」的那个 CID。文件站的 IPNS 名字就是下面那串 k51…
  • pin 的意思是「保证留住某个 CID 的内容」。pin 绑在 CID 上,不会自己跟着 IPNS 走。所以文件站一发新版,你上次 pin 的还是旧 CID。

结论就是这支脚本要做的事:先把 IPNS 换算成当前的 CID,再 pin 那个新 CID,然后放掉旧的。因为 CID 会变,这件事要定期重跑,这就是为什么要设定时任务。

运作原理(为什么定时就够,不用等通知)

你不需要社群通知你「换新版了」。IPNS 就是大家共用的同步点,你的脚本自己去解析就拿得到最新 CID。文件站发布时只是照常更新 IPNS,你这边每隔几小时解析一次、发现变了就 pin 新版,全程不用人工协调。

文件站的 IPFS 坐标(公开值,可以直接用):

脚本每次跑的动作是:解析 IPNS 拿到当前 CID,pin 新 CID,unpin 上次那版,回收空间。脚本先确认新版 pin 成功,才会放掉旧版。万一解析失败或抓不到内容,它会保留你手上现有的副本,不会让你的节点变空。

步骤一:跑一个常驻的 IPFS 节点

pin 要能抓齐内容,本机就得有一个持续运作的 IPFS daemon。下面依你的环境选一种。

安装 kubo(IPFS 官方的命令行版本)。macOS 可以用 Homebrew:

brew install ipfs

Linux 依 官方说明 下载对应架构的 kubo。装好后初始化并启动 daemon:

ipfs init          # 第一次才需要
ipfs daemon

要让 daemon 长时间常驻,Linux 建议做成 systemd user service,macOS 可以用 brew services 或 launchd。临时测试时,直接让 ipfs daemon 在后台跑也可以。

最省事的是安装 IPFS Desktop,它内含 kubo,登录后会自动在系统托盘常驻,daemon 一直开着。

装好后确认命令行能调用到 ipfs。独立版 kubo 需要自己把 ipfs.exe 加进系统 PATH,用 IPFS Desktop 的话,若 PATH 找不到 ipfs,在脚本里改用完整路径即可。

用一个 docker-compose.yml 跑 kubo,容器名取 ipfs_host

services:
  ipfs_host:
    image: ipfs/kubo:latest
    restart: always
    volumes:
      - ./ipfs-data:/data/ipfs
    ports:
      - "4001:4001"   # swarm,对外开有助于连到其他节点

启动:

docker compose up -d

之后对 IPFS 下指令都透过容器,设一个环境变量让脚本自动套用(下一步会用到):

export IPFS_CMD="docker exec ipfs_host ipfs"

步骤二:取得 pin 脚本

脚本会自己解析 IPNS、pin 新版、unpin 旧版。IPNS 名字已经写死在里面,不用改。Docker 用户记得先设好上一步的 IPFS_CMD

Linux、macOS、Docker:anoni-pin.sh

下载 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"

保存后给执行权限:

chmod +x anoni-pin.sh
./anoni-pin.sh          # 先手动跑一次确认正常

Windows:anoni-pin.ps1

下载 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"

先手动跑一次确认正常(PowerShell 默认会挡脚本,用 -ExecutionPolicy Bypass 放行这一次):

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

步骤三:设定定时任务

让脚本每隔几小时自动跑。文件站更新不频繁,每 6 小时足够,想更快跟上可以缩短。

用 cron。编辑定时表:

crontab -e

加一行,每 6 小时跑一次,并把输出写进 log:

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

想要开机补跑漏掉的任务,也可以改用 systemd timer(设 Persistent=true),效果更稳。

用「任务计划程序」。最快的方式是命令行创建,每 6 小时跑一次:

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

C:\Tools\anoni-pin.ps1 换成你实际存放的路径。也可以用「任务计划程序」的图形界面,触发程序填 powershell,参数填 -NoProfile -ExecutionPolicy Bypass -File 你的路径

定时任务照你主机的操作系统设(Linux、macOS 用 cron,Windows 用任务计划程序,见前两个分页)。差别只在脚本执行前要先设好 IPFS_CMD,例如包一层小 wrapper:

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

把 cron 指到这支 wrapper 即可。

步骤四:验证

确认脚本真的把内容 pin 住了。先解析出当前 CID,再看它在不在 pin 列表:

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

也可以在本机 gateway 打开看,内容正常显示就成功了:http://127.0.0.1:8080${CID}/。Docker 用户把上面的 ipfs 换成 docker exec ipfs_host ipfs

维护与注意事项

  • 会自动跟上新版:文件站换 CID 后,下一次定时任务就会 pin 新版、放掉旧版,你不用做任何事。
  • 硬盘不会一直变大:脚本 unpin 旧版后会执行一次垃圾回收,只留最新版本占空间。文件站是纯静态网站,体积不大。
  • 不会弄丢你手上的版本:脚本先确认新版 pin 成功才放掉旧版,解析或下载失败时会保留现有副本。
  • 想停止帮忙:unpin 目前版本、把定时任务移除即可,不影响其他节点。
  • 隐私与风险:你 pin 的是公开文件,没有隐私顾虑。提供 IPFS pin 在不同司法管辖下的风险不同,相关限制见 去中心化网站发布 的「已知限制与风险」。

不想自架节点:用 pinning service 代 pin

不想维护 daemon 与定时任务的话,可以改用 PinataStoracha 这类 pinning service。在它们的界面直接贴上当前 CID 手动 pin,或用它们的 API 写个定时任务喂新 CID,逻辑跟本页脚本一样(解析 IPNS 拿 CID,再送给服务 pin)。

差别在存活重新依赖第三方服务商,而不是你自己的节点。当备援可以,想真正分散化还是自架节点最直接。

相关阅读