目录

拥抱 IPFS

在最近博客的例行维护(更换主题修复 post)过程中,我偶然读了这篇文章。之前就对 IPFS 有点兴趣,这次花了点时间去看了一下相关知识,发现最近的 IPFS 生态完善了许多,我就花了点时间迁移了整个工作流。这篇文章就是过程中的一些记录。

什么是 IPFS

IPFS是我认为自区块链诞生以来最具使用价值的区块链项目之一。IPFS 全名星际文件系统,是一种去中心化的文件存储系统。IPFS 使用区块链技术来计算文件哈希,并使用区块来存储文件。

IPFS 的底层技术类似于 BT,一个 node 既可以是文件的下载者也可以是上传者:当你询问某一个文件的 hash 的时候,IPFS 会在节点间传递询问,有这个文件的节点会相应并把文件发送给你;当你添加了一个文件你就把相关的哈希添加到了网上,当别人询问这个哈希的时候本地节点提出响应并传输文件。

当然,帮别人保存和上传文件对自己没有什么好处,所以 IPFS 提出了 FileCoin 来激励用户接入 IPFS。由于 FileCoin 公网这些还没上线,同时我个人本身一直不看好加密货币,就暂时不介绍了,感兴趣的同学可以看 IPFS 官方的介绍

IPFS 的去中心化和区块链特性有诸多优势,比如区块链带来的天生文件永久保存和文件的一致性保证,而去中心化带来的极低的均摊存储成本和全球内容分发,也难怪有人认为 IPFS 是 Web3.0 时代的基础协议之一。

本博客已经部署在 IPFS 上:https://ipfs.lxdlam.com

IPFS 静态网站的坑

那 IPFS 怎么部署一个静态网站呢?

从原理上来说,静态网站就是一个静态文件的集合,那我们直接安装 IPFS 然后 ipfs add public 就可以了?没这么简单,我们还有两个问题需要解决。

Pin

由于 IPFS 是分布式的文件系统,并不是所有人都需要保存所有文件,于是 IPFS 对每个节点保有的文件是允许进行限制和清理的。这意味着,你的文件虽然发到了 IPFS 上,但是因为没人有这个文件的副本(就好像 BT 中没人做种),别人也访问不到。

当然,为了解决这个问题,IPFS 提供了一个操作叫做 Pin,把你的文件“钉”在节点上。这就万事大吉了吗?没有。当某个节点钉住了你的文件之后,这个节点必须可一直访问才能保证你的文件持续在线。这也就是说,我们必须有个一直在线的节点来响应用户请求才能保证你的网站一直在线,不然用户一个请求过来发现文件不存在,网站就下线了。

为了使你的文件持续在线,现在有很多的 Pinning 服务来在他们的服务器上 Pin 你的文件(就像 BT 的 Seedbox)。常见的 Pinning 服务有下面几个:

我使用的是最后一个 Pinata,只要你 Pin 在他们服务上的文件总大小小于 1G 就可以免费使用,管理 Pin 的内容也比较方便。

当然,还可以选择用一个树莓派来做 Pinning,但是这个就要求网络环境好一点,不然博客访问速度就会很难受 ;)。

Gateway

如果你现在尝试访问一个 IPFS 的文件,你大概率是访问不了的,因为你的设备上并没有 IPFS 环境,设备不知道这个协议怎么处理。解决这个问题的就是 Gateway。

Gateway 可以看作一个 IPFS 代理,在这个代理后面有一个 IPFS 环境。当我们使用文件哈希访问 Gateway 的时候,Gateway 会帮我们请求 IPFS 节点,并把这个文件通过 HTTP/HTTPS 的方式传回来。IPFS 官方同时提供了一个叫做 DNSLink 的解决方案,通过 CNAME 到一个 Gateway 并给你的域名添加一个 dnslink=/ipfs/<hash> 的 TXT 记录,就能实现自定义域名跳转 Gateway 访问 IPFS 文件。我们自定义博客域名的方案也就搞定了。

常用的 Gateway 很多,比如上面我提到的 Pin 服务基本都提供了自己的 Gateway。我使用的是 Cloudflare 的,不仅免费还能得益于 Cloudflare 的全球 CDN 加速(虽然好像也没啥用)。

迁移到 IPFS

迁移到 IPFS 还是挺简单的,主要经历了下面的步骤:

相关服务注册

先注册一下 PinataCloudflare。因为种种原因,我这次也顺便把 DNS 从阿里云云解析前移到了 Cloudflare,所以我的 DNS 记录也就在 Cloudflare 了。

注册完 Pinata 之后应该就能看到自己的 API KEY 和 SECRET API KEY 了,先复制一份备用。

Cloudflare 添加两个记录:

  • 一个博客域名到 www.cloudflare-ipfs.com 的 CNAME 的记录。比如我的就是 ipfs.lxdlam.com
  • 一个 _dnslink.<domain> 的 TXT 记录,可暂时不填。比如我的就是 _dnslink.ipfs.lxdlam.com

然后来到个人资料下面的 API 令牌页面,生成一个 API 令牌,给到两个权限:

  • 区域 -> DNS -> 编辑
  • 区域 -> 区域 -> 读取

区域资源处需要包含所有区域。然后记录下这个 API Token 备用。

工具安装

我们的部署将使用 ipfs-deploy,这是一个自动把静态网站上传到 Pinning 服务然后修改 DNSLink 记录来保持域名指向正确的工具。我们先装到本地验证一下整个工作流对不对。

1
yarn global add ipfs-deploy

然后应该就可以用 ipd 或者 ipfs-deploy 来访问了。

如果你想尝试一下把站点发布到本地的 IPFS 节点的话,安装 IPFS 可以参考官网教程

调整博客配置和测试

由于两边配置略有区别的原因,我复制了一份 config.toml 然后命名为 config.ipfs.toml

配置修改有两个要点:

  • 由于 IPFS Gateway 会 CNAME 到一个新的域名上,使用绝对路径就会产生问题,在新的配置文件中添加 relativeURLs = true 来确保你使用的是相对路径。
  • 调整一些和域名相关的配置,将其全部指向新的域名。

接下来尝试发布。先把相关的 key 配置到环境变量里:

1
2
3
4
5
export IPFS_DEPLOY_PINATA__API_KEY=<Pinata 的 API KEY>
export IPFS_DEPLOY_PINATA__SECRET_API_KEY=<Pinata 的 SECRET API KEY>
export IPFS_DEPLOY_CLOUDFLARE__API_TOKEN=<Cloudflare 的 API TOKEN>
export IPFS_DEPLOY_CLOUDFLARE__ZONE=<Cloudflare 的域名区域>
export IPFS_DEPLOY_CLOUDFLARE__RECORD=<Cloudflare 上的 dnslink TXT 记录地址>

然后生成并发布站点:

1
2
hugo --gc --minify --cleanDestinationDir --config=config.ipfs.toml
ipd -p pinata -d cloudflare

然后尝试访问你的新博客地址,如果一切正常的话就没问题啦!

配置 CI

由于 CircleCI 访问起来实在是太慢了,加上 Github Actions 最近特别好用,我这次直接把博客的 CI 迁移到了 Actions。

下面是我的 Actions 配置文件,由于 IPFS 目前还在发展,我主要还是使用 Github Pages 来托管博客,所以持续集成的时候分开编译并部署到不同地方。如果你只想用 IPFS 的话,只用看 IPFS job 就行。

 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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
name: Blog Deploy
on: [push]
jobs:
    pages:
        name: Build and deploy to github pages
        runs-on: ubuntu-latest
        steps:
            - name: Checkout blog sources
              uses: actions/checkout@v2
              with:
                submodules: true
                fetch-depth: 0

            - name: Setup Hugo
              uses: peaceiris/actions-hugo@v2
              with:
                hugo: '0.69.0'
                extended: true

            - name: Build site
              run: hugo --gc --minify --cleanDestinationDir --config=config.toml

            - name: Deploy to github pages
              run: |
                cd ./public
                git init
                git config user.name "<github user name>"
                git config user.email "<github user email>"
                git add .
                git commit -m "Blog builds $GITHUB_RUN_ID using Github Actions"
                git push --quiet --force "https://${PAGES_DEPLOY_TOKEN}@${GITHUB_REPO_URL}" master:master                
              env:
                PAGES_DEPLOY_TOKEN: ${{ secrets.PAGES_DEPLOY_TOKEN }}
                GITHUB_REPO_URL: <Your github pages repository>

    ipfs:
        name: Build and deploy to ipfs
        runs-on: ubuntu-latest
        steps:
            - name: Checkout blog sources
              uses: actions/checkout@v2
              with:
                submodules: true
                fetch-depth: 0

            - name: Setup Hugo
              uses: peaceiris/actions-hugo@v2
              with:
                hugo: '0.69.0'
                extended: true

            - name: Setup Node
              uses: actions/setup-node@v1
              with:
                node-version: '13.x'

            - name: Build site
              run: hugo --gc --minify --cleanDestinationDir --config=config.ipfs.toml

            - name: Deploy to ipfs
              run: npx ipfs-deploy -p pinata -d cloudflare -C -O
              env:
                IPFS_DEPLOY_PINATA__API_KEY: ${{ secrets.PINATA_API_KEY }}
                IPFS_DEPLOY_PINATA__SECRET_API_KEY: ${{ secrets.PINATA_SECRET_API_KEY }}
                IPFS_DEPLOY_CLOUDFLARE__API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
                IPFS_DEPLOY_CLOUDFLARE__ZONE: <Your cloudflare domain zone>
                IPFS_DEPLOY_CLOUDFLARE__RECORD: <Your dnslink record>

${{ secrets.XXX }} 在仓库的 Options->Secrets 处添加。不要在你的仓库明文放置这些 Token!

把这个文件放到 .github/workflows/ci.yml 然后 push 到 Github 上就可以等待 CI 自动部署你的博客啦。