久违的再次迁移博客,这次是由 Hexo 迁移到 Hugo,对于 Hexo 其实也没并没有觉得不好用,当然,可能还是我懒癌写的博客篇数太少的缘故。
Github 开启了免费私有仓库之后,自建 Git 仓库的需求消失了,阿里云服务器唯一存在的意义就只有博客了。这次除了用 Hugo 代替 Hexo 之外,还会使用 Caddy 来替换 Nginx,顺便完善一下之前一直有计划但是没动手实现的自动更新。
目标
首先是确定要完成的目标:
- 使用 Hugo 替换 Hexo
- 使用 Caddy 替换 Nginx
- 通过 Caddy Git 插件自动更新
- 使用 Docker 编排部署
Hugo
Hexo 和 Hugo 都是通过 Markdown 来生成静态文件,博客数据这块迁移没有什么难点,考虑到博客篇数很少,手动修改格式来适配 Hugo 即可,顺便检查一下文章,删掉一些年轻时的黑历史不太重要的文章。
我选择使用 Beautiful Hugo - A port of Beautiful Jekyll Theme,根据 Hugo 官网的 Quick Start 走遇到第一个坑。
路径问题
官网 Quick Start 中是将 md 文件存放到 content/posts
路径,但是 Beautiful Hugo 找寻的路径是 content/post
。一般来说是可以通过 .Site.Params.mainSections
变量配置该路径的,不过 Beautiful Hugo 使用了硬编码来配置路径。
<div class="posts-list">
{{ $pag := .Paginate (where .Data.Pages "Type" "post") }}
好在已经有人提交 PR#241 来修复这个问题,不过在合并之前只能先用 content/post
路径。
放置备案号
国内要求将备案号放在页面中,虽然 Beautiful Hugo 支持国际化多语言,但是这个中国本地化特色需求没有支持。修改 themes/beautifulhugo/layouts/partials/foot.html
可以解决问题,不过这种侵入式的修改不太优雅。
注:可以复制 themes/beautifulhugo/layouts/partials/foot.html
到根目录 layouts/partials/foot.html
,再进行修改。
打开 themes/beautifulhugo/laouts/partials/footer.html
可以看到,Beautiful Hugo 在生成 poweredBy 内容时使用了 i18n 的方式。
<p class="credits theme-by text-muted">
{{ i18n "poweredBy" . | safeHTML }}
{{ with .Site.Params.commit }} • [<a href="{{.}}{{ getenv "GIT_COMMIT_SHA" }}">{{ getenv "GIT_COMMIT_SHA_SHORT" }}</a>]{{ end }}
</p>
在 themes/beautifulhugo/i18n/zh-CN.yaml
中可以查看到 poweredBy 最终渲染的内容,可以利用这个来实现在 poweredBy 后添加备案号。
# Footer
- id: poweredBy # Accepts HTML
translation: '由 <a href="http://gohugo.io">Hugo v{{ .Site.Hugo.Version }}</a> 强力驱动 • 主题 <a href="https://github.com/halogenica/beautifulhugo">Beautiful Hugo</a> 移植自 <a href="http://deanattali.com/beautiful-jekyll/">Beautiful Jekyll</a>'
在项目根目录创建 i18n/zh-CN.yaml
,并添加以下内容。
# Footer
- id: poweredBy # Accepts HTML
translation: '由 <a href="http://gohugo.io">Hugo v{{ .Site.Hugo.Version }}</a> 强力驱动 • 主题 <a href="https://github.com/halogenica/beautifulhugo">Beautiful Hugo</a> 移植自 <a href="http://deanattali.com/beautiful-jekyll/">Beautiful Jekyll</a> • <a target="_blank" href="http://www.beian.miit.gov.cn/">{{ .Site.Params.beian }}</a>'
然后就可以在 config.toml
中配置备案号了,并且只有当语言为中文时才会显示。
DefaultContentLanguage = "zh-cn"
[Params]
beian = "鄂ICP备15001586号"
Caddy
Caddy 和 Hugo 一样都是 golang 开发的,从个人角度看,最大的优势是单个二进制文件部署,自动完成 HTTPS、HTTP/2 配置,简单好用说的就是它了。Caddy 并没有提供官方的 Docker 镜像,而且由于 Caddy Plugin 存在,必须自定义构建 Docker 镜像,同时还需要在镜像中加入 Hugo 运行环境。
构建 Caddy 镜像
在项目根目录下创建 docker 文件夹,然后创建构建镜像需要的 Dockerfile
Caddyfile
docker-entrypoint.sh
文件。
- Dockerfile
FROM alpine:latest as builder
ARG hugo_version="0.55.6"
ARG plugins="http.cache,http.cors,http.expires,http.realip,http.git"
RUN apk add --no-cache curl bash gnupg
RUN curl https://getcaddy.com | bash -s personal ${plugins}
RUN curl -L https://github.com/gohugoio/hugo/releases/download/v${hugo_version}/hugo_${hugo_version}_Linux-64bit.tar.gz | tar xz -C /usr/local/bin/
FROM alpine:latest
RUN apk add --no-cache openssh-client ca-certificates git
COPY --from=builder ["/usr/local/bin/caddy","/usr/local/bin/hugo","/usr/local/bin/"]
ENV CADDY_DOMAIN="localhost" \
CADDY_TLS_EMAIL="root@example.com" \
CADDY_GIT_REPO="https://github.com/example" \
CADDY_GIT_BRANCH="master" \
CADDY_GIT_HOOK="/webhook" \
CADDY_GIT_HOOK_TYPE="github" \
CADDY_GIT_HOOK_SECRET="secret"
WORKDIR /root
COPY --chown=root:root ["docker-entrypoint.sh","/usr/local/bin/"]
COPY --chown=root:root ["Caddyfile","/etc/caddy/"]
RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \
mkdir -p "caddy/etc" \
"caddy/www" \
"caddy/logs" \
"caddy/assets" \
"caddy/repo"
VOLUME ["/root/caddy"]
EXPOSE 80 443
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["caddy","-agree","-conf","/etc/caddy/Caddyfile"]
- Caddyfile
Caddy 的配置文件支持变量,所以通过环境变量来配置主要的 Caddyfile,然后使用 import 的方式导入其他配置。
注意:Caddyfile 中变量语法为 {$ENV_VAR}
。
{$CADDY_DOMAIN} {
log {$CADDY_LOG_ROOT}/{$CADDY_DOMAIN}/access.log
root {$CADDY_WWW_ROOT}/{$CADDY_DOMAIN}
gzip
tls {$CADDY_TLS_EMAIL}
git {
repo {$CADDY_GIT_REPO}
branch {$CADDY_GIT_BRANCH}
path {$CADDY_REPO_ROOT}/{$CADDY_DOMAIN}
clone_args --depth=1
hook {$CADDY_GIT_HOOK} {$CADDY_GIT_HOOK_SECRET}
hook_type {$CADDY_GIT_HOOK_TYPE}
then git submodule init
then git submodule update
then hugo --destination={$CADDY_WWW_ROOT}/{$CADDY_DOMAIN}
}
}
import {$CADDY_ETC_ROOT}/*.Caddyfile
- docker-entrypoint.sh
CADDYPATH 默认 ${HOME}/.caddy
路径,用于保存生成的证书资源文件。
#!/usr/bin/env sh
set -e
export CADDY_ROOT=/root/caddy
export CADDYPATH=${CADDY_ROOT}/assets
export CADDY_WWW_ROOT=${CADDY_ROOT}/www
export CADDY_LOG_ROOT=${CADDY_ROOT}/logs
export CADDY_REPO_ROOT=${CADDY_ROOT}/repo
export CADDY_ETC_ROOT=${CADDY_ROOT}/etc
mkdir -p ${CADDY_WWW_ROOT}/${CADDY_DOMAIN} ${CADDY_LOG_ROOT}/${CADDY_DOMAIN}
exec "$@"
编写编排文件
在项目根目录下创建 docker-compose.yaml
与 .env
文件,如果路径不同,编排文件中相关项目也需要变化。
- docker-compose.yaml
version: "3.7"
services:
caddy:
build:
context: docker
restart: unless-stopped
env_file: .env
volumes:
- type: bind
source: ${CADDY_ROOT}
target: /root/caddy
networks:
- caddy-network
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
networks:
caddy-network:
name: caddy-network
- .env
在 .env 中还可以使用 HUGO_TITLE
HUGO_BASEURL
变量对 Hugo 进行配置。
CADDY_ROOT=/local_caddy_root_path
CADDY_DOMAIN=blog.example.com
CADDY_TLS_EMAIL=admin@exmaple.com
CADDY_GIT_REPO=https://github.com/example/blog.git
CADDY_GIT_BRANCH=master
CADDY_GIT_HOOK=/webhook
CADDY_GIT_HOOK_SECRET=your_hook_secret
HUGO_TITLE=Your Hugo Title
HUGO_BASEURL=https://blog.example.com
其他
在 Github 上添加好 WebHook 之后就完成所有内容了,每当 Push 后都会自动更新博客。
根据需要可以调整 Caddyfile 的配置,如果还要安装其他 Caddy Plugin 可以在 docker-compose.yaml 中添加 build args 的方式定义 plugin,重新构建镜像。
version: "3.7"
services:
caddy:
build:
context: docker
args:
plugins: "http.cache,http.cors,http.expires,http.realip,http.git"
hugo_version: "0.55.6"
External Network
如果除了博客之外还有其他服务也需要通过 Caddy 来代理,可以在其他编排文件中使用 External Network 的方式。
- docker-compose.yaml
version: "3.7"
services:
py4s:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
expose:
- "3000"
ports:
- target: 22
published: 10022
protocol: tcp
mode: host
volumes:
- ./gitea/data:/data
networks:
- caddy-network
networks:
caddy-network:
external: true
- gitea.Caddyfile
在本地 CADDY_ROOT 中添加 etc/gitea.Caddyfile
。
gitea.example.com {
proxy / gitea:3000
}