我的博客方案:靜態網站與 IPFS

隨著前幾年多個出名的博客服務商終止運營,或者不再提供更新與維護,國文博客開始文藝復興,重新歸復到獨立建站,個中滋味大概只有國朝網民知曉。縱然外國亦有不少博客服務商,但在互聯網開始被爭霸之中的主權國家裹挾之今日,又有哪個主權國家更高尚一些嗎?除了內容自託管,我們別無選擇。

內容的完全控制和方便遷移,這是我託管博客的兩大目標。考慮到國朝網路運營商對 web 端口的屏蔽,如不映射到海外服務器,那麼在自家主機上開放 HTTP/HTTPS 服務,在可預見的未來都是不可能的。為了達成這樣的目標,我選擇靜態網站託管服務與分布式儲存兩種方案。

選擇靜態網站的一個理由是不需要考慮數據庫的維護。相較於動態網站使用數據庫來存儲網站內容和用戶數據,靜態網站以靜態文件的形式存儲內容,進而減少了維護成本。個人使用 wordpress 的過程也有曾遭受過黑客攻擊的經歷,缺少安防經驗也促使我不再使用動態網站。除此之外,靜態網站也更易遷移,選擇靜態網站可方便地將數據從一個託管服務提供商遷移到另一個。

而選擇分布式儲存是由於國內網路環境的限制而不得已的選擇,同時也能有效增強自己對數據的控制權。分布式網站有很多方案,其中比較知名的有 ZeroNetIPFS,但 zeronet 目前已停止更新,我們的選擇可能只有 IPFS 了。

IPFS 全稱為 InterPlanetary File System (星際文件系統),是一個基於內容尋址的分布式存儲方案,目前有 Brave瀏覽器 支援使用 ENS域名 或者傳統域名進行訪問 IPFS 網站。

我的網誌已經歷十幾年,雖然文章不多,但也歷經折騰,然而卻不曾介紹過我的部署方案。是時候將我的部署方案公之於眾了,以下供大家參考與批判。

我的博客選擇是靜態網站生成器 hugo ,主題為 DoIt, DoIT 的基本配置可參考其官方網站

  • 網誌文章的 markdown 文件放入 $PWD/yoursite/content/posts
  • 簡單頁面的 markdown 文件放入 $PWD/yoursite/content/

md 文件的名稱就是改頁面的連結,示例如下:

對於使用 Hugo 創建博客文章,需要注意和普通 Markdown 文件的區別。在 Hugo 中,無法使用如同普通 markdown 文件般使用 # 來標記文章標題。相反,你需要在文件內使用前置參數進行標記。以下是一個示例前置參數:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
title: "三和大神的双十一怎么过?"
date: 2019-11-11T11:52:18+08:00
lastmod: 2020-06-16T15:58:26+08:00
draft: false
author: "王小嗨"
summary: "双十一,我去三和做了一晚快递日结。"
featuredImage: "/images/godsofsanhe1111/20191111.1.jpg"
images: ["/images/godsofsanhe1111/20191111.1.jpg"]
toc: false
keepStatic: false
auto: true
categories: ["Capitalism","Life"]
tags: ["三和大神","日结","零工","快递"]
license: '<a rel="license external nofollow noopener noreffer" href="https://creativecommons.org/licenses/by-nc/3.0/" target="_blank">CC BY-NC 3.0</a>'

在這個示例中,title 屬性標示了文章的標題,date 標示了文章的發布日期和時間,lastmod 標示文章最後修改的時間,draft 屬性表示文章是否是草稿狀態。另外,還有一些其他的屬性,比如前面列舉的 authorsummaryfeaturedImage 等等。在這個標題之後,可以使用正常的 Markdown 語法來編寫文章的內容。其中圖片文件在 $PWD/yoursite/static/images 目錄。

在安装 hugo 程式,下載好 DoIt 主題, 完善 config.toml 配置文件,寫好妳的文章之後,cd 至 $PWD/yoursite ,運行 hugo server -D 可對博客網站預覽,若滿意,則運行 hugo 命令。在 $PWD/yoursite/public 目錄中,妳將會得到博客的所有靜態文件。

有了整個網站的靜態文件,我們需將其上傳至託管服務商。目前,可供選擇服務商有好幾家,大部分服務商的免費方案都足夠個人博客使用。

以上服務商,都支持綁定 GitHub 倉庫,它會自動拉取最新倉庫更新並保持同步。其中 Fleek ,不僅支持生成 http 頁面,還會將文件上傳至 IPFS 網路,給出 CID。

先在 GitHub 新建一個 repository, 與本地的 $PWD/yoursite/public 目錄進行關聯,並將博客文件上載至倉庫。

1
2
3
4
5
6
7
cd $PWD/your_site/public
git init
git add .
git commit -m "deploy blog"
git branch -M main
git remote add origin git@github.com:your_github_name/your_blog_repository.git
git push -u origin main

之後,有新博文之後,再重新運行 hugo server -D 預覽博客,若滿意,則運行 hugo 命令更新博客的靜態文件,再將其更新至 GitHub 倉庫。

1
2
3
4
cd $PWD/yoursite/public
git add .
git commit -m "blog update"
git push -u origin main

git push 完成後,託管服務商會自動讀取倉庫更新,將其部署至服務器。

在託管服務商創建我們的博客項目並綁定 GitHub 倉庫後,通常服務商會提示我們綁定域名。如若沒有相關提示,也可以在其管理界面中方便地找到綁定域名的選項,按照其要求添加相關 DNS 記錄,比如 A 記錄 或者 CNAME 記錄。

為了更方便、更安全地管理域名的 DNS,建議使用 Cloudflare 服務。首先,在 Cloudflare 上添加域名,然後在域名服務商的管理界面中將 DNS 服務器變更為 Cloudflare 提供的 DNS 服務器,最後等待生效即可。

當然,如果不想購買域名的話,也可使用服務商提供的二級域名。但是,若使用自有域名,我們將更方便地將網站從一個服務商遷移至另一個,僅需更改 DNS 記錄即可。

將網誌託管給服務商固然很方便,這也增加了被審查與阻斷的風險。作為國朝臣民,我們天然地對任何服務商沒有信任。如果選擇 IPFS 方案託管網誌,這無疑將大大地治癒我們的書報檢查PTSD。但要特別注意:IPNS 並不匿蹤,書寫與言說就要承受利維坦之重,請審慎考量再使用鍵鼠。

IPFS 擁有一個域名系統 IPNS (InterPlanetary File System),可讓我們更方便地配合域名使用 IPFS , IPFS 託管網站的要義是用戶自己控制 IPNS 的 private key (私鑰)。儘管已有提供 IPNS 託管服務的選項,但這要付出信任成本。如若選擇自行生成與託管 IPNS Key ,這是一個 trustless 的抉擇,只要自己做好安全風險的管控。

要進行 IPFS 網站的自託管,首先要 安裝 IPFS 程式。因為需要 7*24 的不間斷服務,將 IPFS 安裝在功耗較低的小主機是一個好的選擇,所有選擇 docker 安裝 IPFS 是一個好的選項。

1
2
3
4
5
6
7
docker run -d --name ipfs \
  -v $PWD/docker/ipfs/ipfs:/data/ipfs \
  -v $PWD/docker/ipfs/export:/export \
  -u 1000:1000 \
  -p 4001:4001 \
  -p 8080:8080 \
  ipfs/go-ipfs:latest  

安裝完成後,進入 IPFS 容器的 Shell ,運行以下命令,將服務应用 lowpower 配置,這會優化 IPFS 資源的佔用情況。

1
ipfs config profile apply lowpower

接著在 Shell 中, cd 至 export 目錄,運行以下命令,生成一個 IPNS key pair。

1
ipfs key gen your_key_name

記錄給出的 IPNS 的 public key (公鑰),並使用以下命令將其導出。

1
ipfs key export your_key_name

在文件管理中,進入至 $PWD/docker/ipfs/export ,將目錄中的 Key 文件妥善保管,以下是 IPFS KEY 管理的命令。

1
2
3
4
5
6
7
ipfs key export <name>           - Export a keypair
ipfs key gen <name>              - Create a new keypair
ipfs key import <name> <key>     - Import a key and prints imported key id
ipfs key list                    - List all local keypairs.
ipfs key rename <name> <newName> - Rename a keypair.
ipfs key rm <name>...            - Remove a keypair.
ipfs key rotate                  - Rotates the IPFS identity.

有了 IPNS Key 之後,就可將內容的 CID 發布到 IPNS 。如果直接訪問 IPFS 公鑰,當然也可以,但也這讓訪問者望而卻步,一大串字符讓人望而卻步。為了方便記憶,我們需要將 IPNS 與域名綁定,綁定方法可參考我的文章Planet:一款IPFS静态站点生成器中的域名绑定綁定部分。

使用 IPFS 自託管靜態網站,主要是因為其分布式特性,它讓我們重拾萬維網之互聯真義。然而,由於個人博客的讀者不多,使用 IPFS 的用戶更是寥寥無幾,因此在 IPFS 自託管的同時,使用 remote pinning services 來提高網站的可用性是個不錯的選擇。

在介紹靜態網站託管服務商的時候,有提到 Fleek ,它就有提供 remote pinning services ,在生成動態網站的同時,可生成一個網站的 CID。

如果我們在每次更新網站後,從 Fleek 生成的域名讀取出它綁定的 CID,並在本地 pin 這個 CID,然後再將其發布至我們自己的 IPNS ,那麼我們就可以實現同時更新靜態網站服務商和 IPNS 託管。所以,我請 ChatGPT 寫了可自動這一系列操作的腳本,如下所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash
#
# Script: update-ipns.sh
# Description: Check if IPNS record needs to be updated and update it if necessary.

set -euo pipefail

# Function: get IPNS CID
get_ipns_cid() {
  local ipns_name=$1
  local ipfs_container=$2
  docker exec "${ipfs_container}" ipfs resolve -r "/ipns/${ipns_name}" \
    | tail -n1 \
    | awk '{ print $NF }'
}

# Variables: define container name, IPNS names and options
IPFS_CONTAINER="ipfs"
SOGOLA_IPNS="your_domain_name"
FLEEK_IPNS="your_fleek_site_name.on.fleek.co"
PIN_OPTS="--progress"
PUBLISH_OPTS="--key=your_ipns_public_key"

# Check if the IPFS container is running
if [ ! "$(docker ps -q -f name=${IPFS_CONTAINER})" ]; then
    echo "${IPFS_CONTAINER} container is not running. Please start the container and try again."
    exit 1
fi

# Get IPNS CID
SOGOLA_CID=$(get_ipns_cid "${SOGOLA_IPNS}" "${IPFS_CONTAINER}")
FLEEK_CID=$(get_ipns_cid "${FLEEK_IPNS}" "${IPFS_CONTAINER}")

# Check if IPNS record needs update
if [ "${SOGOLA_CID}" != "${FLEEK_CID}" ]; then
  # Publish new IPNS record
  docker exec "${IPFS_CONTAINER}" ipfs name publish "${PUBLISH_OPTS}" --lifetime=8766h -- "${FLEEK_CID}"
  # Unpin the previous IPNS record
  docker exec "${IPFS_CONTAINER}" ipfs pin rm "${SOGOLA_CID}"
  # Pin the updated IPNS record
  docker exec "${IPFS_CONTAINER}" ipfs pin add "${PIN_OPTS}" -- "${FLEEK_CID}"
  echo "IPNS record updated successfully."
else
  echo "IPNS record is already up-to-date, no need to update."
fi

# Add error handling
if [ $? -eq 0 ]; then
  echo "Update completed."
else
  echo "Error occurred while updating IPNS record. Please check."
  exit 1
fi

將上述腳本中的 SOGOLA_IPNS,FLEEK_IPNS,PUBLISH_OPTS 分別替換並保存為 update-ipns.sh ,使用自動化工具定時執行,即可使用 Git 更新博客後,實現靜態網站的 HTTP 與 IPFS 同時更新的功能。

如果覺得只有一個 remote pinning service 不保險的話,還可以使用 4EVERLAND ,這個服務商支持使用 IPNS 進行 PIN。

透過個間網站託管服務商,我們可以通過控制域名來遊走之間,輕鬆實現切換服務商。而通過自託管 IPFS ,拜分布式託管之幸,我們終可回歸到萬維網之本心,重新掌握我們自己的數據。

就撰寫這篇博文期間,我留意到一則新聞,ICANN 和 Verisign 提议允许任何政府扣押域名。是時候,我們該嘗試無許可與去信任的 crypto 了。

整治書報檢查制度的真正而根本的辦法,就是廢除書報檢查制度,因為這種制度本身是惡劣的,可是各種制度卻比人更有力量。我們的意見可能是正確的,也可能是不正確的,不過無論如何,新的檢查令終究會使普魯士的作者要么獲得更多的現實的自由,要么獲得更多的觀念的自由,也就是獲得更多的意識。

Rara temporum felicitas, ubi quae velis sentire et quae sentias dicere licet[當你能夠想你願意想的東西,並且能夠把你所想的東西說出來的時候,這是非常幸福的時候]。

————馬克思《評普魯士最近的書報檢查令》

「清風不識字」,主權國家宰制一切。當言說成為禁忌,開設一個部落格已然成為罪證,我只祝福妳們成為幸福的人。那些獨立博客的作者有福了,因為妳們必然承受大地之重。