Ghost 部落格重建筆記
隔了快三年的新文章,因為我的部落格已經掛掉好久,連 google 都搜尋不到了。想要把部落格弄回來好幾次,甚至還寫過好幾篇 ghost 升級的筆記 draft,但都沒發出去,因為部落格掛掉了...
就這樣,我的部落格已經掛掉...兩年還是三年了。想要把部落格弄回來的熱情,大概 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 是標配吧)
建立好之後,先做幾件事情。
- 獲得 public ip,假設是 12.34.56.78,到 route 53 設定網址。
A 12.34.56.78
- 確認你下載的 .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/你的網域
在本機也安裝一份
既然有自己的部落格,一定會想亂搞一下版型,把它變成想要的樣子。所以本機一定要可以開發。這部份很簡單。
要注意幾點:
- 本機可能也有開發其他案子,node.js 版本記得切換到 ghost 需要的 18.x
- 其他說明看這邊:https://ghost.org/docs/install/local/
- 裝好之後來這邊就可以打開: http://localhost:2368/ghost
如果你之前寫過的版型,可以放進來 ghost 的 content/themes,用 gscan 來看看他是不是還符合現在 ghost 需要的版本。時間一久幾乎都會有一堆問題。
設定部屬
在本機開發時,可以在本機測試。但做完之後要如何部屬到 aws 那台伺服器呢?這是我這次卡最久的。明明看起來很單純。
流程是單純的:
- 本機開發、測試完
- 從本機 push 到 remote 的 main
- 透過 webhook 讓遠端伺服器跑一個小 script 去做 git pull
第一步,建立 git repo。因為我只會改 themes 裡面的版型,所以我在 github 建立了一個 repo 是我自己複製 casper 來改的 casper_customized
。
像這樣:https://github.com/hanamizuki/casper-customized
- 進入 github 建立 repo
- 進入 repo,點 settings > webhook
- Payload 先這樣設定:
http://your-aws-ip:3000/deploy-theme
,比如http://12.34.56.78:3000/deploy-theme
- Content type: 選擇 "application/json"
- Secret: 自己設定一個密碼,例如:
mySecretKey123!
- "Which events would you like to trigger this webhook?" 選擇 "Just the push event"
- 建立 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 一致認同的作法。
- 寫一個 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 有奇怪的權限要求,最後會提。
- 建立一個 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
- 建立 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'));
- 建立 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
- 設定 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
來找。
- 啟用
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 重新發送。
在這邊:
說來慚愧,我試好多次才發現是 ghost 的權限設定問題,而且 script 裡面不能用 ghost cli,因為 visudo 的設定在 ghost cli 是無效的,還是會跳密碼,所以只能用 sudo systemctl stop ghost_service_name
和 sudo systemctl start ghost_service_name
了。
目前還算順利。以前需要很多硬幹的事情,沒想到都已經有解決方式。比如說版型升級可以用 gscan,之前還要慢慢對照。加上 AI 的幫助,很多像我這種沒有時常寫程式的人都可以快速建立想要的東西,真是神奇。希望這次可以真的開始認真寫文章。