在 fly.io 免费运行 Ghost 博客:安装、备份和恢复

最近把论文交了后,手又痒痒,折腾了下 ghost。 fly.io 是一个 PaaS,现在可以在上面安装若干个 Ghost 博客,而且完全免费。

这篇文章介绍了在 fly.io 上免费安装、备份和还原 Ghost 的方法。备份和恢复数据对其他服务器上的用例具有参考意义。配合使用 cron,定时将全站数据备份到 Github,可以说,真的可以安心用 Ghost,舍弃静态博客了。

1.前置条件

  1. 注册 fly.io 账号,安装 flyctl 命令行,需要一张支持外币的信用卡。(必要
  2. 熟悉基本的 git 命令。(非必要)
  3. 了解 fly.io 的 定价 。(非必要)
  4. 了解 Mailgun。(非必要)

安装命令行

curl -L https://fly.io/install.sh | sh

对于 Windows,运行

iwr https://fly.io/install.ps1 -useb | iex

2.安装 Ghost

下面的命令在 Linux 和 Mac 上通用。

打开终端,执行:

mkdir blog #创建一个目录
cd blog #进入这个目录
flyctl auth login #登录,之后浏览器会弹出登录会话
flyctl launch --image=ghost:5 -r hkg --name=<AppName> --no-deploy

对最后一条命令的解释:

  • --image=ghost:5,表示 5.x 大版本会自动在每一次部署时更新,若打算选中一个具体的版本,比如 5.36.0,则把 5 换成它。 官方镜像在这里

  • - r hkg表示选择服务器的节点为香港(离我们最近的节点)。 区域代码在这里

  • --name=<AppName> 表示创建的 app 名字,将<AppName> 替换成你自己的,并且注意,将来生成的默认网址是<AppName>.fly.dev,所以请想一个独一无二的名称。

  • --no-deploy表示暂时不部署。

这会在 blog 目录里生成一个 fly.toml 文件,使用文本编辑器打开它,使用如下代码覆盖。


app = "<AppName>"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []

[build]
  image = "ghost:5"

[env]
  url = "https://<AppName>.fly.dev"
  database__client = "sqlite3"
  database__connection__filename = "content/data/ghost.db"
  database__debug = "false"
  database__useNullAsDefault = "true"
  mail__from = "noreply@example.com"
  mail__options__auth__pass = "<YourMailgunPassword>"
  mail__options__auth__user = "postmaster@example.com"
  mail__options__host = "smtp.mailgun.org"
  mail__options__port = "465"
  mail__transport = "SMTP"

[experimental]
  auto_rollback = true

[mounts]
  destination = "/var/lib/ghost/content"
  source = "data"

[[services]]
  http_checks = []
  internal_port = 2368
  processes = ["app"]
  protocol = "tcp"
  script_checks = []
  [services.concurrency]
    hard_limit = 25
    soft_limit = 20
    type = "connections"

  [[services.ports]]
    force_https = true
    handlers = ["http"]
    port = 80

  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443

    [services.ports.http_options.response.headers]
      Referrer-Policy = "strict-origin"
      Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
      X-Content-Type-Options = "nosniff"
      X-Frame-Options = "SAMEORIGIN"
      x-xss-protection = "1; mode=block"
      Permissions-Policy = "camera=(), microphone=(), geolocation=(), browsing-topics=()"

  [[services.tcp_checks]]
    grace_period = "1s"
    interval = "15s"
    restart_limit = 0
    timeout = "2s"

请将<AppName>替换成你自己的,并且,如果不用 Mailgun ,请删除[env]mail__开头的所有代码。

其中[services.ports.http_options.response.headers]这一部分是可选的,用于设定 http header。可以逐项搜索Strict-Transport-Security,比如,确定你是否要确定将这项加入。部署完毕之后,http header 检测地址在 这里

检查完毕后,保存并关闭 fly.toml 文件。然后回到终端,继续执行:

  flyctl volumes create data  -r hkg --no-encryption --size 1 #创建一个大小为1Gb的卷,区域为hkg,不加密(有助于提高性能)。免费额度共3GB。
  flyctl deploy #部署

等待片刻,部署成功!会显示successful.

3. 初始化 Ghost

访问

https://<AppName>.fly.dev/ghost/

以初始化 ghost。在这个之后,你就可以自由使用 ghost 来发布内容了。绑定域名、备份和恢复数据是可选的操作。

4. 绑定域名

运行:

  flyctl ips list

以查看你的 app 的 IP 地址。然后在你的 DNS 服务器上为 ipv4 创建 A 记录,为 ipv6 创建 AAAA 记录,指向你的域名example.com(必须)。创建 CNAME,<AppName>.fly.dev,指向你的www.example.com (非必须)。也就是

类型 记录
A @ ipv4 address
AAAA @ ipv6 address
CNAME www <AppName>.fly.dev

注意:在绑定域名之前,一定要确保正确创建 DNS 记录。

然后运行:

flyctl certs create example.com
flyctl certs create www.example.com

等待片刻,运行

flyctl certs check example.com
flyctl certs check www.example.com

以检查证书颁布的情况,一般只要 DNS提前配置正确,大约需要一两分钟。运行

flyctl ips list

以查看证书颁发情况。

注意:不要反复安装证书,因为有限额, 每 7 天只能颁布 5 张证书 。建议在调试完 app 后,再绑定。

5.重新部署

注意到 fly.toml 的

[env]
  url = "https://<AppName>.fly.dev"

是 fly.io 分配的地址,如果你点击 ghost 网站的主页标题,就会转向这个地址,所以,当你绑定了自己的域名,将其换成

[env]
  url = "https://example.com"

保存并关闭,然后运行flyctl deploy

6. 备份和恢复

以后每一次需要进入你本地的配置目录,才能正确使用 flyctl。

6.1 方法 1:使用 SFTP 备份 Ghost

优点是无需复杂配置,但复制速度极慢。

6.1.1 备份

首先登录(假设你以后需要备份);

cd blog
flyctl auth login

然后运行

flyctl ssh sftp shell -r -a <AppName>

成功后,会显示>>(我这里是红色的),然后输入

get /var/lib/ghost/content/

这将备份全站数据,包括主题、图片、视频、文章等等。速度很慢,请耐心等待。

也可以只备份图片和视频,运行

get /var/lib/ghost/content/images/
get /var/lib/ghost/content/media/

这在 blog 目录里将生成一个 content.zip,或,images.zip 和 media.zip。将它们备份到其他地方,或命名为 content-YYYY-MM-DD.zip。

6.1.2 恢复

现在假设你重新安装了一个新的 ghost,假设还是在 fly.io,那么安装目录仍然保持为/var/lib/ghost/content/,假设你今后在别的地方,比如 AWS 上安装 ghost,确保创建的目录为这个而不是一般教程所说的/var/www/ghost/content/,否则恢复数据会遇到麻烦。

仍然运行

cd blog
flyctl auth login
flyctl ssh sftp shell -r -a <AppName>

然后运行

put content.zip /var/lib/ghost/content.zip

成功后,在终端新一个标签页,然后运行

flyctl ssh console

进入 ssh 后,运行

apt-get update && apt-get upgrade && apt-get install unzip

以安装unzip。然后运行

cd /var/lib/ghost/
unzip content.zip

会直接覆盖原先的 content 文件夹。

为验证是否成功解压,运行

cd /var/lib/ghost/content/images/
ls #列出当前文件夹下的所有的文件

查看是否有新的数据。

为修复权限,运行

chown -R node:node /var/lib/ghost/content/

注意:可能的情况,无法正确覆盖 content 文件夹,那么请尝试只备份 /var/lib/ghost/content/images//var/lib/ghost/content/media/。此时运行的是

put images.zip /var/lib/ghost/content/images.zip
put media.zip /var/lib/ghost/content/media.zip

rm -rf /var/lib/ghost/content/images/
rm -rf var/lib/ghost/content/media/

unzip /var/lib/ghost/content/images.zip
unzip /var/lib/ghost/content/media.zip

可能会破坏 content 文件夹的权限,运行

ls -l /var/lib/ghost/content/

以查看 images 和 media 文件夹的用户和权限,若其权限为 root,则不能上传图片和视频了,则运行

chown -R node:node /var/lib/ghost/content/
chown -R node:node /var/lib/ghost/content/images/
chown -R node:node /var/lib/ghost/content/media/

以修复,注意,其中的node是在 fly.io 案例中的 ghost 用户,具体情况视ls -l /var/lib/ghost/content/命令的结果为准,总之确保 images 和 media 文件夹的用户和 content 下的其他文件的用户一样。

6.2 方法 2:使用 Github 备份 Ghost

优点是复制速度快,但缺点是安装配置相对复杂。

6.2.1 备份

1.登录并进入 ssh

cd blog
flyctl auth login
flyctl ssh console

2.安装 git

apt-get update && apt-get upgrade && apt-get install git

3.在 github 网页端先创建一个私密仓库(推荐),假设名为<YourRepository>

4.生成密钥

ssh-keygen -t rsa -C "<GithubEmail>"  #连续按回车键

5.终端新建标签,运行

flyctl ssh sftp shell -r -a <AppName>
get /root/.ssh/id_rsa.pub

回到 blog 文件夹里找到 id_rsa.pub 文件,用文本编辑器打开,并复制其中的密钥。将其添加到 github 的setting→SSH and GPG keys→SSH keys中,保存。

测试与 github 的通信

ssh -T git@github.com

留意有没有 successful 的字眼。

ssh -T git@github.com
Hi Pathsis! You've successfully authenticated, but GitHub does not provide shell access.

6.初始化 content 的 git

cd /var/lib/ghost/content/
git init
git config --global user.name "<GithubUsername>"
git config --global user.email <GithubEmail>
git remote add origin git@github.
com:<GithubUsername>/<YourRepository>.git
git config --global --add safe.directory /var/lib/ghost/content
git add .
git commit -m "auto backup"
git push -u origin master --force

确保成功 push 到 github。

7.新建一个脚本

apt-get update && apt-get upgrade && apt-get install nano
cd /var/lib/ghost/content/
nano auto_run.sh

复制以下代码到其中

#!/bin/sh

cd /var/lib/ghost/content # 切换到目录
git config --global init.defaultBranch master
git config --global --add safe.directory /var/lib/ghost/content
git pull # 拉取仓库
git add . # 添加暂存
git commit -m "auto backup" # 提交
git push --force

ctrl+O之后再回车,以写入;按ctrl+x,以退出 nano。

赋予该文件运行权限

chmod a+x auto_run.sh

以后,登录 ssh 后,可以直接运行

sh /var/lib/ghost/content/auto_run.sh

以执行新的 push。

6.2.2 恢复

1.登录 shh,重新安装 git,添加密钥,不再赘述。

2.清空 content 文件夹

cd /var/lib/ghost/
rm -rf content

会返回说,该文件夹无法删除,因为其busy,没关系,它已经被清空,但没有被删除。

3.初始化 content 文件中的 git,并强制 pull

cd /var/lib/ghost/content/
git init
git config --global user.name "<GithubUsername>"
git config --global user.email <GithubEmail>
git pull --force git@github.com:<GithubUsername>/<YourRepository>.git

4.验证是否成功拉取

ls

5.确保成功拉取数据后,重启 app

fly apps restart <AppName>

目前来看,重启或重新部署,会清除安装的环境和软件,但不会删除 ghost 的数据。

6.在 docker 中运行 cron 实在困难,不用尝试了。在普通虚拟机中,可以设置 cron,以定时运行

sh /var/lib/ghost/content/auto_run.sh