這篇主要講一下這個博客是怎麼部署的
微信用戶: 點擊查看原文格式會更好一些。
主題選取這個其實在前篇已經說過了
這裡說下這個主題的特點吧:
5色可選。5種雙色主題 (orange is default, red, blue, green, pink)可以直接在配置文件裡修改。
Fira Code作為默認等寬字體
響應式設計,各種解析度瀏覽效果都不錯
採用 PrismJS 實現客戶端代碼高亮
最重要的是,它簡潔。
沒錯,真的很簡潔,沒有過多的功能,甚至沒有archive頁面,沒有tag cloud,只自帶了3個shortcode。
對於hugo新手來說,越是簡單的主題,你才越好下手修改。要不然剛接觸hugo,就用了一個功能特別複雜的主題,
這對於掌握主題的DIY技能是沒有幫助的。
現在你看到我的博客,我已經是根據自己的需求做了非常多的調整了。
我調整後的terminal主題:
同樣5色可選。
使用Jetbrains Monohttps://github.com/JetBrains/JetBrainsMono等寬字體進行代碼高亮
響應式設計保持不變
採用hugo自帶的代碼高亮方案(輸出class) + 自定義的Gruvbox高亮配色
調整hugo默認的RSS 2.0 feed輸出為atom輸出,調整RSS摘要輸出為全文輸出
增加archive,tagcloud模板,增加基本的KaTex自動渲染LaTex支持。
增加TOC顯示,返回TOP按鈕,平滑滾動。
cover圖片顯示增加page bundle支持
如果你碰巧也喜歡這個主題,老燈的代碼是開放的,自取:https://github.com/ttys3/hugo-theme-terminal/tree/ttys3
代碼高亮這裡要說一下,在hugo裡面,代碼高亮有兩種選擇,一種是客戶端實現方式,另一種是後端實現方式。
客戶端實現就是用markdown的fenced code使代碼在輸出時保持原樣(當然,跟html本身衝突的<或>會進行處理),
然後由客戶端加載js進行高亮渲染。後端的方式,即是採用hugo默認的markdown解析引擎goldmark來調用
chroma進行高亮渲染.
chroma跟python裡的Pygments 採用一致的方式進行高亮,甚至連風格樣式也保持一致。
所有Chroma的配色都是使用_tools/style.py腳本自Pygments的配色轉換而來。
所以一般來說,這兩個的渲染結果應該是一致的,Pygments有什麼配色,chroma就有什麼配色,自然,
hugo就會有什麼配色。配色效果可以去Chroma配色相冊查看
雖然Chroma是porting自Pygments, 但是也不是完全一樣,比如作者解釋了為什麼Chroma比Pygments支持的高亮語言更少一些:
I mostly only converted languages I had heard of, to reduce the porting cost.
不過這完全不是問題,因為常用的語言它都支持了,而那些名字都沒聽過的語言,我也不需要高亮。
那麼,既然hugo自帶高亮, 主題也自帶高亮,我是要用主題的前端方案呢?還是用後端的hugo方案呢?
前後端高亮方案對比對比項hugo後端高亮PrismJS前端高亮RSS feed輸出可保持高亮配色(當noClasses=true)無高亮瀏覽器兼容性不相關相關inline CSS支持支持不支持官方配色數量較多很少(8)第三方配色數量較多較多看上去兩者相比不分高下。所以,主要看需求了。
如果要求RSS feed輸出也保持一致的代碼高亮,則要使用hugo自帶的高亮且啟用inline CSS輸出。
如果要求支持所有瀏覽器,則也應該選擇hugo自帶的方案。
see PrimsJS Limitations
No IE 6-10 support. If someone can read code, they are probably in the 85% of the population with a modern browser.
如果是使用hugo自帶的高亮且使用的是class的方式的話,我覺得應該用這兩個都OK。
就看你喜歡的那個配色在對應的方案裡有沒有提供。
package main
import "fmt"
// This is a comment
func main() {
fmt.Println("hello world")
}
輸出對比:
我把二者的輸出用在線工具格式化一下方便對比 https://codebeautify.org/htmlviewer/
PrismJS
<pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4" class=" language-go">
<code class=" language-go" data-lang="go">
<span class="token keyword">package</span> main
<span class="token keyword">import</span>
<span class="token string">"fmt"</span>
<span class="token keyword">func</span>
<span class="token function">main</span>
<span class="token punctuation">(</span>
<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
fmt
<span class="token punctuation">.</span>
<span class="token function">Println</span>
<span class="token punctuation">(</span>
<span class="token string">"hello world"</span>
<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code>
</pre>
hugo
<pre class="chroma">
<code class="language-go" data-lang="go">
<span class="kn">package</span>
<span class="nx">main</span>
<span class="kn">import</span>
<span class="s">"fmt"</span>
<span class="kd">func</span>
<span class="nf">main</span>
<span class="p">()</span>
<span class="p">{</span>
<span class="nx">fmt</span>
<span class="p">.</span>
<span class="nf">Println</span>
<span class="p">(</span>
<span class="s">"hello world"</span>
<span class="p">)</span>
<span class="p">}</span>
</code>
</pre>
二者的解析引擎都是基於正則表達式的, 有錯漏肯定是難免的。
看上去解析結果差不太多,不過貌似PrismJS 沒有把 fmt.Println 調用裡的 fmt 解析正確。
chroma的輸出結果使用了更簡短的class名稱,看上去更精簡一些。
最終我還是選定了hugo內置的高亮。放棄了前端方案。
內置的配色我感覺沒有喜歡的,於是看看base64有沒有chroma的配色,還真找到一個。
對於這個porting不太滿意,因為它太多紫色的東西。
jetbrains 有一個 gruvbox theme自帶的gruvbox color各方面都不錯,我把它port過來了
https://github.com/ttys3/base16-chroma/blob/master/chroma-base16-css/cssvar/base16-gruvbox-dark-jb.css
最終形成我自己的fork:
https://github.com/ttys3/base16-chroma
由於 base16 配色並不是專門為Chroma或Pygments設計的,因此,得把base16的 scheme 人肉轉換到 Chroma 過來。
最終效果就是現在你看到的高亮效果。
這期間花了我不少時間,關於如何製作新的 base16 配色並應用於 hugo, 後續有空我再單獨發文分享。
另外這個主題默認的Fira Code我給它換成Jetbrains Monohttps://github.com/JetBrains/JetBrainsMono woff2字體,woff2體積幾乎比woff小一半。
如果你查看兼容資料庫https://caniuse.com/#woff2, 你會發現只有IE11 和 Opera Mini不支持。
所以,可以果斷地用woff2了。
由於 hugo 不像 WP 這類有資料庫的動態博客系統, hugo是生成靜態頁面的。
用目錄來組織文章, markdown文件一般不會遇到問題,但是圖片和其它附件呢?
如何有效的管理?方便寫文章和修改,同時方便後期的查找和管理?
幸好,hugo 有一種叫 page bundle的模式。
就是一個頁面下可以包含其它資源。
如果是一個葉節點頁面bundle, 那麼它下面可以放一個index.md 及 圖片和其它附件。
比如我現在這個文章deploy-hugo-blog-witn-container 就是一個page bundle,文件結構如下:
content/post/hugo/deploy-hugo-blog-witn-container
├── cover-2020-04-16-03-50-14.png
└── index.md
這樣的好處非常明顯, 文章自己的圖片放在文章自己的目錄。
如果是公用的圖片怎麼辦?放到 static/img 下面啊。
還有一個問題,在寫文章的時候,怎麼方便的在截圖完成後,把圖片存到當前目錄並自動引入markdown中?
有一個神奇的 VS Code 插件叫Markdown Paste(telesoho.vscode-markdown-paste-image), 這個非常方便的功能就由它實現了。
用起來非常絲滑順手。
剪切板中的圖片,Ctrl+Alt+V 就能直接到markdown文件所在目錄,並且自由粘貼markdown的圖片代碼。
這裡主要是要感謝王不對,
如果沒有看他的文章,我還一直不會去了解hugo的page bundle這個功能。
當然,這是圖片存在本伺服器的情況,如果是用又拍雲、七牛或者騰訊雲COS做圖床的話,VS Code裡另外有其它的插件可以現實。
因為我是全站走CDN了,因此就懶得再弄CDN的圖床了。
配置配置的話,採用最小化配置。
也就是,凡是hugo默認的配置,我們不寫出來,我們只增加自己要修改的部分。
除了個單個config.toml配置,hugo還引入配置目錄的功能。
每個頂層配置對象都擁有一個文件,如下圖示:
├── config
│ ├── _default
│ │ ├── config.toml
│ │ ├── languages.toml
│ │ ├── menus.en.toml
│ │ ├── menus.zh.toml
│ │ └── params.toml
│ ├── production
│ │ ├── config.toml
│ │ └── params.toml
│ └── development
│ ├── config.toml
│ └── params.toml
考慮到上述的結構,運行hugo --environment development時,hugo將讀取config/_default的默認配置
然後和development合併。
也就是_default是默認配置,production 或 development 可以覆蓋,也可以增加配置
直接運行hugo serve時的默認環境是development
直接運行hugo時的默認環境是production
這樣非常方便本地開發和部署到線上時切換不同的配置。
但是看了一下,官方的文檔對於這個功能的描述非常的不清晰。
看了這個功能相關的issue 大概了解了一些用法。
我想還是暫是不使用這個功能。
由於hugo是靜態博客生成器,因此,我們要引入評論功能,只能通過API來嵌入評論。
hugo本身已經內置了Disqus,只需要配置一個id就能啟用。
雖然國外的 Disqus評論系統其實還算好用,但是國內很無奈,主域名基本無法直接訪問,然而它的js裡也嵌入了api請求域名,
算起來差不多有三四個域名,所以,用反向代理我也嫌棄麻煩。放棄之。
國內的有changyan,也提供免費使用,但是肯定是不考慮的。原因不用多說。
基於gihtub的評論系統也有一些,比如gitment, gitalk等,但是我還是不想用這種,因為最近連github也是不能直接訪問了。
還有個Valine是基於LeanCloud的服務的,這個我也不想用,用的什麼對象存儲,然後嚴重依賴LeanCloud這個國內的服務。
說是「無後端評論系統」,其實是把後端放在了LeanCloud。另外,這個新版本也要移除郵件提醒功能了,
需要郵件提醒還得再整一個第三方郵件提醒。
isso是基於py的,貌似很久沒人維護了,這個也不滿足條件。
go-isso實現還沒完成,暫時也不是可用狀態,繼續找。
找到一個叫remark42的評論系統,這個評論系統啥都好,就是:一,沒有中文。二,不記錄郵箱和網址。三,發郵件提醒只能用SMTP。
關於第一點,我已經提交了一個PR給官方,官方已經把中文語言merge進去了。關於第二點,基於隱私,不記錄郵箱,
這個我可以理解,但是不記錄網址,博客與博客之間就達不到那種互動互踩的效果了。其它博主在你的博客留言了,你卻不知道他的博客網址。
不記錄評論者網址這一點,我有時間再想辦法改進一下。
第三點,我提交了一個使用sendgrid和mailgun的WEB API發郵件的PR給官方,然後官方拒絕了。
理由是「這會使代碼膨脹」, 這是什麼理由?你集成了oauth登錄功能,加了常見的github, google, twiiter,突然有一天你覺得yandex
也要加上了,然後你就把yandex也加上了,你的代碼就不「膨脹」了?
為什麼SMTP已經是「能用」狀態了,帶要用web api發送郵件?很簡單,我都是用自己的VPS架設的博客,基於安全考慮,不希望SMTP協議暴露源站
IP。用WEB API可以很輕鬆地解決這個問題。
然後remark42官方提議,我另外弄一個side container跑一個SMTP到WEB API發送郵件的bridge服務。
然後,發郵件的流程就變成了:remark42 => SMTP-WEB_API-bridge => SendGrid/Mailgun WEB API
問題是,明明可以通過代碼解決了,再加個side container,至於麼?
這是把開發者的責任,轉架到了使用者身上。
並且,他拒絕我這個PR,然而他自己的oauth實現也是這樣實現的。如果按照他這個邏輯,這個oauth登錄功能也會使「代碼膨脹」,
因為提供oauth登錄服務的廠家也是在變動的。你們俄羅斯(remark42的作者是俄羅斯的)的用yandex, 到我們這了可能就要加別的廠家的登錄接口功能了。
另外,我看了下他那個Dockerfile, 寫得層數實在太多。我數了一下他最終生成的鏡像層數,不多不少,正好17層。
一般我做鏡像,基本上都會控制在10層以下,一般在5層左右。
因為docker用的是overlayfs, 一層一層覆蓋的,層數越多,io效率越低。
那你可能會多,我層數多,加一個配置文件都是一個新層,方便更新啊。如果你要這樣說的話,那我反問你,
做base image的人,是不是也要一個文件一個軟體地加,給你弄個100層?方便你更新?
但是個人維護這麼一個項目是極其要時間了,這也是為什麼,我還是比較傾向於讓官方接受我這個PR。
但是remark42的項目主負責人對於源站IP洩漏這種安全問題壓根沒有什麼意識,在我的PR裡他還反問我,大家的伺服器都是public IP了,
這個IP本身就是公開的,還存在什麼源站IP洩漏?我只能呵呵了。
我回了一句:你的伺服器沒有被DDos 攻擊,你當然不會意識到這是一個問題。
最後,歡迎大家關注我的remark42 fork: https://github.com/ttys3/remark42/
同時也歡迎有興趣的童鞋一起完善改進。
我現在使用的remark42是編譯自我的fork的,然後通過github actions自動提交到了dockerhub:
https://hub.docker.com/r/80x86/remark42
remark42部署效果圖:
LaTeXhugo目前的goldmark媽蛋解析引擎還不支持latex。
雖然結合katex js,實現一些簡單的LaTeX解析是沒問題的,但是如果LaTeX裡有_和[] 這種媽蛋裡也有的標記,
就會導致LaTeX的標記走丟了(被媽蛋搶走了)。
hugo產出的HTML直接用nginx容器跑http服務。
remark42容器提供評論API。
前端採用envoy做代理,hugo和remark42埠由envoy開放。
一個基本的適用於hugo站點的nginx配置(/etc/nginx/conf.d/default.conf)如下:
# nginx config for hugo site
# author: ttys3
server {
listen 8080;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
index index.html;
# absolute_redirect must off for hugo site if no `$uri/index.html` on try_files
# absolute_redirect off;
# port_in_redirect off;
# server_name_in_redirect off;
root /app/public;
location / {
try_files $uri $uri/index.html =404; #fixup for hugo
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
然後直接運行docker官方的nginx容器nginx:1.17.9,映射/etc/nginx/conf.d/default.conf配置文件,並映射/app/public目錄:
sudo docker run -d --name hugo \
-v hugo-public目錄位置:/app/public:ro,z \
-v default.conf文件位置:/etc/nginx/conf.d/default.conf:z \
--mount type=tmpfs,destination=/tmp \
nginx:1.17.9
怎麼申請github, google, twitter的oauth登錄接口,這裡有詳細的步驟:https://github.com/ttys3/remark42#register-oauth2-providers
sudo docker run -d --name remark42 \
-e REMARK_PORT=8081 \
-e REMARK_URL='https://remark42的訪問地址' \
-e PUID=1000 \
-e TIME_ZONE="Asia/Shanghai" \
-e SITE='網站域名(不需要帶http和埠)' \
-e ADMIN_SHARED_ID='管理員的用戶id' \
-e ADMIN_SHARED_EMAIL='管理員郵件地址@example.com' \
-e ADMIN_PASSWD='管理員密碼' \
-e SECRET='用於加密的密鑰,自己隨便md5一下弄個串出來放這就好了' \
-e AUTH_EMAIL_ENABLE=false \
-e AUTH_EMAIL_FROM='郵件認證或通知提醒時的發件人郵箱@sg.ttys3.net' \
-e AUTH_GITHUB_CID='github-api-key填寫這' \
-e AUTH_GITHUB_CSEC='github-api-key-secret填寫這' \
-e AUTH_GOOGLE_CID='谷歌api-key填寫這' \
-e AUTH_GOOGLE_CSEC='谷歌api-key-secret填寫這' \
-e AUTH_TWITTER_CID='twitter-api-key填寫這' \
-e AUTH_TWITTER_CSEC='twitter-api-key-secret填寫這' \
-e NOTIFY_TYPE=email \
-e AUTH_EMAIL_SUBJ='荒野無燈weblog登錄確認' \
-e NOTIFY_EMAIL_VERIFICATION_SUBJ='確認訂閱來自荒野無燈weblog的評論' \
-e NOTIFY_EMAIL_ADMIN=true \
-e EMAIL_PROVIDER=sendgrid \
-e EMAIL_SG_API_KEY='你的sendgrid密鑰填這' \
-e EMOJI=true \
-v /home/data/remark42:/srv/var:rw,z \
80x86/remark42:latest
AUTH_GITHUB_CID / AUTH_GITHUB_CID 這幾個不是必須的。
比如你只想啟用github登錄評論,則只需要配置AUTH_GITHUB_CID 和 AUTH_GITHUB_CSEC。
目前我實現了mailgun和sendgrid這兩個發送郵件的provider, 如果你用mailgun,請修改上面的
EMAIL_PROVIDER=sendgrid為 EMAIL_PROVIDER=mailgun
更多配置信息請去https://github.com/ttys3/remark42 查看。
envoy容器部署Envoy 是什麼?
Envoy 是專為大型現代 SOA(面向服務架構)架構設計的 L7 代理和通信總線。
Envoy Proxy 在代理方面性能咋樣呢?老燈這裡放兩張2018年的圖(來源):
Envoy Proxy 主要是用來在微服務架構中做service mesh的, 但是這裡我們用它來做edge proxy對它來說當然更是小菜一碟。
關於做edge proxy的例子,官方的最佳實踐文檔中有:Configuring Envoy as an edge proxy
這裡老燈分享上我的配置envoy.yaml:
# https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/edge
overload_manager:
refresh_interval: 0.25s
resource_monitors:
- name: "envoy.resource_monitors.fixed_heap"
typed_config:
"@type": type.googleapis.com/envoy.config.resource_monitor.fixed_heap.v2alpha.FixedHeapConfig
# TODO: Tune for your system.
max_heap_size_bytes: 2147483648 # 2 GiB
actions:
- name: "envoy.overload_actions.shrink_heap"
triggers:
- name: "envoy.resource_monitors.fixed_heap"
threshold:
value: 0.95
- name: "envoy.overload_actions.stop_accepting_requests"
triggers:
- name: "envoy.resource_monitors.fixed_heap"
threshold:
value: 0.98
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 9901
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 80
per_connection_buffer_limit_bytes: 32768 # 32 KiB
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
stat_prefix: ingress_http
use_remote_address: true
common_http_protocol_options:
idle_timeout: 3600s # 1 hour
http2_protocol_options:
max_concurrent_streams: 100
initial_stream_window_size: 65536 # 64 KiB
initial_connection_window_size: 1048576 # 1 MiB
stream_idle_timeout: 60s # 1 mins, must be disabled for long-lived and streaming requests
request_timeout: 60s # 1 mins, must be disabled for long-lived and streaming requests
access_log:
- name: envoy.file_access_log
config:
path: "/dev/stderr"
route_config:
virtual_hosts:
- name: comment
domains:
- "cmt.ttys3.net"
routes:
- match: { prefix: "/" }
route:
cluster: service_comment
idle_timeout: 15s # must be disabled for long-lived and streaming requests
- name: hugo
domains:
- "ttys3.net"
routes:
- match: { prefix: "/" }
route:
cluster: service_hugo
idle_timeout: 15s # must be disabled for long-lived and streaming requests
- name: cgit
domains:
- "git.ttys3.net"
routes:
- match: { prefix: "/" }
route:
cluster: service_cgit
idle_timeout: 15s # must be disabled for long-lived and streaming requests
# hugo nginx only listen on http
http_filters:
- name: envoy.router
config: {}
clusters:
- name: service_hugo
connect_timeout: 3s
per_connection_buffer_limit_bytes: 32768 # 32 KiB
lb_policy: round_robin
type: "STRICT_DNS"
hosts:
- socket_address:
address: hugo
port_value: 8080
- name: service_comment
connect_timeout: 3s
per_connection_buffer_limit_bytes: 32768 # 32 KiB
lb_policy: round_robin
type: "STRICT_DNS"
hosts:
- socket_address:
address: remark42
port_value: 8081
- name: service_cgit
connect_timeout: 3s
per_connection_buffer_limit_bytes: 32768 # 32 KiB
lb_policy: round_robin
type: "STRICT_DNS"
hosts:
- socket_address:
address: cgit
port_value: 8082
注意:
Envoy Proxy的配置文件是YAML格式,注意要用空格,不要用tab.
docker的DNS服務會自動把容器名稱映射到容器的IP位址,因此clusters配置這裡的address: hugo要與前面創建的hugo容器名稱hugo對應。
這裡老燈開了3個服務,一個hugo, 一個comment, 一個cgit.
我在創建容器的時候,分別讓hugo監聽8080, remark42 comment監聽 8081, cgit 監聽8082埠。
然後為了簡單起見,我們只讓Envoy Proxy對外暴露出80埠,因此我這裡也沒有配置SSL。
為什麼呢?因為通過走cloudflare CDN之後,CF可以自動提供SSL證書。
如果要弄SSL,咱也可以直接弄個自籤名的SSL就行了,因為 CF 也認。
或者,講究一點,弄個ACME自動證書。
這裡開放的9901 埠是用於查詢一些Envoy Proxy的狀態的,注意這個埠是不需要認證就能訪問的,因此用容器跑的時候,
一般不要綁定到0.0.0.0讓外網能訪問到。
因為通過這個WEB接口是能關掉Envoy Proxy,甚至能修改配置的。
這個WEB UI可以說是非常的簡潔了,只有非常基礎的界面:
運行Envoy Proxy容器:
sudo docker run -d --name envoy \
-v envoy.yaml文件的絕對路徑:/etc/envoy/envoy.yaml:z,ro \
-p 127.0.0.1:9901:9901 \
-p 80:80 \
envoyproxy/envoy:v1.14.1
這裡我們只開放了80埠,如果啟用了SSL, 需要加上-p 443:443 \
podman注意事項由於老燈目前是在用podman,沒有用docker, 也就是以上命令裡的docker替換成podman就能跑了。
使用docker的注意以上運行容器的命令都要加上--restart=unless-stopped, 不然機器重啟後容器不會自動啟動。
使用podman的則要使用podman generate systemd 容器id或名稱 來生成systemd unit文件並啟用服務來實現開機自啟。
其它Envoy Proxy容器可不可以用nginx做基於域名的虛擬主機來做反向代理替換掉?
完全可以。
我暫時用了最簡單的方法,直接一個up.sh腳本完事
#!/bin/sh
env TZ='Asia/Shanghai' hugo --gc --minify --enableGitInfo && \
rsync -ravz --progress --checksum --delete public/ root@伺服器地址:/home/http/html/ttys3.net
這裡的/home/http/html/ttys3.net 就是hugo網站的根目錄了。
也可以採用在服務端架設gitolite,然後用git hook實現自動發布hugo站點,這個後續有時間再分享。
其它部署方式?hugo支持很多部署方式,包括直接託管在github pages
或者用Github Actions實現自動化部署Hugo博客,
以及託管在netlify.
更多部署方式可以在hugo官網找到。
Mailgun和SendGrid都提供免費的plan,默認情況下,Mailgun每月的發送量送得更多(限制1萬封)。
SendGrid在試用期過後,每天的郵件數量會限制在100封,感覺可用性不是很高。
Mailgun(https://www.mailgun.com/) Free Plan provides 10,000 Emails per month
SendGrid(https://sendgrid.com) Trial Plan provides 40,000 emails for 30 days.
After your trial ends, you can send 100 emails/day for free
淺色系:
https://github.com/olOwOlo/hugo-theme-even
https://github.com/xianmin/hugo-theme-jane
https://github.com/zhengzangw/hugo-theme-ztyblog
暗黑系:
https://github.com/panr/hugo-theme-hello-friend
hello-friend的第三方fork版:https://github.com/rhazdon/hugo-theme-hello-friend-ng
更多主題請去https://themes.gohugo.io/查看。
hugo基礎教程?Hugo 從入門到會用 https://blog.olowolo.com/post/hugo-quick-start/
博客遷移——Hugo使用整體概覽 https://www.rectcircle.cn/posts/blog-migration/
TODO