幫忙 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 名稱:
k51qzi5uqu5dlfm2jj0f70ex3r3babmwy8qh071inwknttr7wqa3uhdwvlmrmw - 瀏覽器打開看:https://anoni-net.ipns.dweb.link/
腳本每次跑的動作是:解析 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¶
#!/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 — 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 與排程的話,可以改用 Pinata、Storacha 這類 pinning service。在它們的介面直接貼上當前 CID 手動 pin,或用它們的 API 寫個排程餵新 CID,邏輯跟本頁腳本一樣(解析 IPNS 拿 CID,再送給服務 pin)。
差別在存活重新依賴第三方服務商,而不是你自己的節點。當備援可以,想真正分散化還是自架節點最直接。