Ghost 部落格重建筆記

隔了快三年的新文章,因為我的部落格已經掛掉好久,連 google 都搜尋不到了。想要把部落格弄回來好幾次,甚至還寫過好幾篇 ghost 升級的筆記 draft,但都沒發出去,因為部落格掛掉了...

Ghost 部落格重建筆記

就這樣,我的部落格已經掛掉...兩年還是三年了。想要把部落格弄回來的熱情,大概 9.5 個月會發生一次,但每一次都沒有成功。好多次快成功了,甚至還寫過好幾篇 ghost 升級的筆記,但都沒發出去,因為最後沒有成功...誰料得到網站的保存期限這麼短,一兩年不理他就會自己壞掉。

上一次(去年大概九月?)還因為看到 mysql 版本好舊,而 ghost 要求一定要 mysql 8 才能升級,我就下個 mysql upgrade 指令,整個...就爛掉了,也無法復原。上一次真的成功還是升級到 ghost 2(現在都 5 了...)

這次下定決心重新再來,從 aws 的 ec2 instance 開始重建,連我自己都覺得我怎麼還記得這些。但是,即使有網路上各種文章、教學,以及非常厲害的 ChatGPT 和 Claude 陪我進行每一個步驟(兩個都是付費版),中間還是踩了很多坑。

這篇文章整理的是保證可以運作(至少一年吧)。如果你是新手小白也想用 ghost 架設部落格,可以嘗試。

ps. 其實我也很不想每年只有一篇文章,然後都是講如何把我爛掉的 blog 架回來,這好像已經是我最近寫文章的唯一主題了(囧)。

建立 AWS EC2 Instance

其實除了 EC2 你還有更多選擇,EC2 反而比較麻煩。但因為我很想練習自己架設 ubuntu,享受被虐的快感,所以我還是選這個了。

我也沒有用 Docker,因為這台主機就放這個東西,好像沒必要像俄羅斯套娃?但也許之後想要放其他網站來就會有需要了。

登入到 EC2 之後,選擇 Launch Instance,注意:

  • 選擇 Ubuntu 20 or 22 LTS:ghost 不支援 24
  • 64but-x86
  • 選新的 key,記得下載,或是用舊的
  • instance type 可以先選免費的,之後再改,ghost 官方說至少 1g ram,但我用 1g 的跑一下 ghost 就掛掉,後來還是換 2g 的
  • Security groups 記得設定 80 和 443 給所有 IPv4 和 IPv6 的人訪問(SSL 是標配吧)

建立好之後,先做幾件事情。

  1. 獲得 public ip,假設是 12.34.56.78,到 route 53 設定網址。
A 12.34.56.78
  1. 確認你下載的 .pem 權限是 444
chmod 400 /path/to/your-key-pair.pem

就可以這樣登入了

ssh -i /path/to/your-key.pem ubuntu@your-instance-public-ip

安裝 ghost 及他需要的東西

ghost 需要這些:

  • Ubuntu 20.04 or Ubuntu 22.04
  • NGINX (minimum of 1.9.5 for SSL)
  • Node.js (版本限制 18.x,但反正有 nvm 所以還好)
  • MySQL 8
  • Systemd
  • 官方說 "A server with at least 1GB memory" -> 我覺得要 2g 耶!跑了 ghost restart 就掛掉

請注意

  • 不能用 root 安裝
  • 新增一個 ubuntu 使用者,但不能叫做 ghost,因為這個名字是 ghost cli 要用的

其他依照這個文件的步驟,很快就會裝好。
https://ghost.org/docs/install/ubuntu

ghost 安裝期間,網域應該已經生效了,可以去你設定的網域看看,或是檢查 nginx 設定。

sudo nano /etc/nginx/sites-available/你的網域

在本機也安裝一份

既然有自己的部落格,一定會想亂搞一下版型,把它變成想要的樣子。所以本機一定要可以開發。這部份很簡單。

要注意幾點:

如果你之前寫過的版型,可以放進來 ghost 的 content/themes,用 gscan 來看看他是不是還符合現在 ghost 需要的版本。時間一久幾乎都會有一堆問題。

設定部屬

在本機開發時,可以在本機測試。但做完之後要如何部屬到 aws 那台伺服器呢?這是我這次卡最久的。明明看起來很單純。

流程是單純的:

  1. 本機開發、測試完
  2. 從本機 push 到 remote 的 main
  3. 透過 webhook 讓遠端伺服器跑一個小 script 去做 git pull

第一步,建立 git repo。因為我只會改 themes 裡面的版型,所以我在 github 建立了一個 repo 是我自己複製 casper 來改的 casper_customized

像這樣:https://github.com/hanamizuki/casper-customized

  1. 進入 github 建立 repo
  2. 進入 repo,點 settings > webhook
  3. Payload 先這樣設定:http://your-aws-ip:3000/deploy-theme,比如 http://12.34.56.78:3000/deploy-theme
  4. Content type: 選擇 "application/json"
  5. Secret: 自己設定一個密碼,例如:mySecretKey123!
  6. "Which events would you like to trigger this webhook?" 選擇 "Just the push event"
  7. 建立 webhook

第二步,回到本機,在本機開發中 content/themes 裡面,建立 casper_customized 資料夾。

設定 git

cd /path/to/your/ghost/content/themes/casper_customized
git init
git remote add origin https://github.com/your-name/casper-customized.git
git pull origin main

這時可能會把一個 github 預設的 readme 抓回來,然後我們就來把之前弄過的版型放進這個資料夾,然後 commit、push,讓這個資料順利放到 github。要注意,如果你是免費 github,這就等於 open source 了。記得不要在版型檔案放什麼個人資料進去。

第三步,回到 aws 那台 server。

ssh -i /path/to/your-key.pem ubuntu@your-instance-public-ip

建立那邊的 casper_customized 資料夾,或是別的名字,都可以。

cd /path/to/ghost/content/themes
sudo mkdir -p casper_customized
sudo chown ghost:ghost casper_customized
cd casper_customized/

這邊非常要注意的是,要讓 ghost cli 運作順利,"content" 的 owner 會是 ghost:ghost,為什麼呢?我也不知道,但總之其他檔案都是自己的 user 建立的,contents 卻必須是 ghost:ghost,這也變成後來要搞自動部屬有點麻煩的地方。

如果懷疑 ghost 檔案權限跑掉,可以用 ghost doctor 確認,如果有不符合的,他會一個一個檔案列給你看。然後記得用 ghost restart 確認是否正常運作。有時候即使 ghost doctor 沒偵測到問題,還是會爛掉,因為他不會去檢查 systemd 之類的詳細設定。

總之,先把 ubuntu 上的 git 也設定好。

cd /path/to/ghost/content/themes/casper_customized
git init
git remote add origin https://github.com/your-name/casper-customized.git
git pull origin main

這樣就同步了。接下來我們希望只要 push 到這個 main repo 的都會自動部屬。以下是 ChatGPT 和 Claude 一致認同的作法。

  1. 寫一個 script,比如 /var/www/deploy/deploy-theme.sh
sudo nano /var/www/deploy/deploy-theme.sh

內容就是到時候 webhook 被勾到要執行的事情。

#!/bin/bash
set -ex
cd /path/to/ghost/content/themes/casper_customized
sudo -n -u ghost git fetch --all
sudo -n -u ghost git reset --hard origin/main
sudo systemctl stop ghost_service_name
sudo systemctl start ghost_service_name

為什麼 git 一定要 -u ghost 呢?魔鬼藏在細節裡(好啦!我懂屁,總之最後只能這樣),因為 ghost 的資料結構中,content 底下的 owner 一定要是 ghost:ghost,這樣 ghost cli 才能順利運作。可是 script 不會是這位 ghost 來執行,所以必須要切換使用者,才不會卡住。

另外 ghost_service_name 是你的 ghost 執行緒名稱,可以用 sudo systemctl status ghost*.service 來找到。

比如說,你看到這段:

kitty@servier:~$ sudo systemctl status ghost*.service
● ghost_kitty.service - Ghost systemd service for blog: hellokitty.tw
     Loaded: loaded (/lib/systemd/system/ghost_kitty.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2024-08-04 09:11:36 UTC; 18min ago

那剛剛的 script 最後兩行就是:

sudo systemctl stop ghost_kitty
sudo systemctl start ghost_kitty

這兩行是取代 ghost restart 的作法,可以重啟 ghost。因為 ghost cli 有奇怪的權限要求,最後會提。

  1. 建立一個 webhook server,用 node.js
mkdir -p /var/www/deploy/webhook-server
cd /var/www/deploy/webhook-server
npm init -y
npm install express github-webhook-handler
  1. 建立 server.js 文件
sudo nano /var/www/deploy/webhook-server/server.js

內容如下,記得把 {{your-name}} 換成你 ubuntu 的 username:

const express = require('express');
const { exec } = require('child_process');
const app = express();

app.use(express.json());

app.post('/deploy-theme', (req, res) => {
  console.log('Received a push event for', req.body.repository.name, 'to', req.body.ref);
  
  exec('sudo -n -u {{your-name}} /var/www/deploy/deploy-theme.sh', { env: { ...process.env, PATH: process.env.PATH + ':/usr/local/bin' } }, (e>
    if (error) {
      console.error(`exec error: ${error}`);
      return res.status(500).send('Deployment failed');
    }
    console.log(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);
    res.status(200).send('Deployment successful');
  });
});

app.listen(3000, () => console.log('Webhook server is running on port 3000'));
  1. 建立 systemd 的 webhook-server.service
sudo nano /etc/systemd/system/webhook-server.service

內容

[Unit]
Description=GitHub Webhook Server
After=network.target

[Service]
ExecStart=/usr/bin/node /var/www/deploy/webhook-server/server.js
Restart=always
User={{your-name}}
Group={{your-name}}
Environment=PATH=/usr/bin:/usr/local/bin
Environment=NODE_ENV=production
WorkingDirectory=/var/www/deploy/webhook-server

[Install]
WantedBy=multi-user.target
  1. 設定 NOPASSWD,看起來有點可怕。不過因為如 1. 裡面說的,有些權限問題,所以需要有限度的讓你的 user 可以在 ghost 擁有的 content/themes 資料夾底下執行 git,並且為了能在 git pull 之後重啟 ghost,必須讓 {{your-name}} 這位使用者可以做 systemctl 的指令。

打開 visudo

su visudo

在最下面加上這段:

{{your-name}} ALL=(ghost) NOPASSWD: /usr/local/bin/git
{{your-name}} ALL=(ALL) NOPASSWD: /usr/local/bin/systemctl

這邊的 /usr/local/bin/git 等路徑有可能你的設定不同,可以用 which git 來找。

  1. 啟用
sudo systemctl daemon-reload
sudo systemctl restart webhook

測試

上面的方式應該可以了,現在我們來測試看看。

確認 webhook server 有沒有跑起來:

ps aux | grep node

應該要有 webhook-service

打開 journal

sudo journalctl -u webhook-server.service -f

試著推個東西到 git repo 的 main,這時畫面會有東西。

如果有權限問題,可以用這個確認 NOPASSWD 有沒有生效。

sudo -l -U {{your-name}}

如果不成功,可以到 git repo > settings > webhook > recent deliveries 重新發送。

在這邊:
Screenshot 2024-08-04 at 5.21.23 PM.png

說來慚愧,我試好多次才發現是 ghost 的權限設定問題,而且 script 裡面不能用 ghost cli,因為 visudo 的設定在 ghost cli 是無效的,還是會跳密碼,所以只能用 sudo systemctl stop ghost_service_namesudo systemctl start ghost_service_name 了。

目前還算順利。以前需要很多硬幹的事情,沒想到都已經有解決方式。比如說版型升級可以用 gscan,之前還要慢慢對照。加上 AI 的幫助,很多像我這種沒有時常寫程式的人都可以快速建立想要的東西,真是神奇。希望這次可以真的開始認真寫文章。

對這篇文章有什麼建議或想法嗎?請 按此在 Twitter 留言按此在 Threads 留言 ,我會回應你 :)