前些日子的 Hexo 自动化构建折腾起来真心有趣,但是速度好慢,几十篇文章从构建到压缩处理需要等待至少好几秒的时间,构建时内存占用也很高,虽然只是一时但就是让人兴致大减。
所以肯定得换构建引擎,然后就是 Hugo 了,Go 语言编写,速度没得说,问题是扩展功能不多,所以今天可是折腾了一天,笑。
实现效果:
- 本地保存即时全自动发布。
自动化流程:
- 本地编辑器保存,webdav (或者其他你喜欢的方式)服务自动上传服务器。
- 服务器监测到有文件上传,启动图片压缩程序,自动压缩图片。
- 图片压缩之后,启动 Hugo 构建页面。
- Hugo 构建结束,启动 Gulp 压缩网页资源文件。
- 构建任务结束,推送到所有服务器。
- 最后由各个节点的 Nginx 服务器向外展示网站。
为什么没有部署到 Github?也没有上传图片到 CDN?
- 因为 Github 不支持绑定域名的站点使用 HTTPS 访问;
- 我服务器多,自动同步,普通 CDN 太慢。
为了简化初次部署的繁琐,以上操作只需要一行命令即可启动。在此之前还是先解释一下整个流程的原理。
一共两个镜像:
REPOSITORY SIZE
zuolan/hugo 19.3 MB
zuolan/hugo:minify 79.73 MB
1. 自动上传服务器
这一步通过 Nextcloud 实现,我日常就是用这种私有云盘,所以这一步我跳过,如果你需要实现自动上传,可以考虑其他方法。比如 scp、rsync 之类的,Windows 下自动同步的工具更是数不尽啦。
2. 主镜像:zuolan/hugo
整合功能包括:
2.1. 服务器监测到有文件上传
这一步的工具就是 inotifywait 了,已经整合到容器中,当数据卷有变动就会触发一系列动作。
2.2. 图片压缩程序
就是一个脚本,自动压缩图片,使用的是 TinyPNG 的服务,虽然 Linux 有不少图片处理程序,但是压缩算法不好,这里使用的在线压缩服务非常不错,这个功能也已经整合到容器中。
因为是在 sh 中执行的脚本,平时在 bash 和 zsh 养成的习惯写法在 sh 中不一定有效,所以暂时先把这部分的脚本写死放进了容器中,等明天完善了再把这部分代码丢上来。
2.3. Hugo 构建页面
这个作为整个流程最关键的一步,近百篇文章耗时也只有几十毫秒。
构建一般不会出错,但是如何触发下一步压缩网页文件就是让我折腾了一天的关键点。
划重点时间
如何触发下一步压缩构建好的网页文件呢?
首先想到的就是使用前端打包工具执行,但是一个 Node.js 环境就会使我的主镜像体积翻了一番,这点不能忍,我要努力把主镜像压缩到仅有 6 MB 的存储体积。
所以把 Hugo 和 Gulp 放一块是不可能的,毕竟不是每个人需要压缩功能。
所以就想到了在容器中运行容器,但是需要在容器中装 Docker 环境,镜像体积必然破百 MB,而且每次容器重启还得重新拉压缩镜像,太二,所以不予考虑。
那,就使用 docker-compose 吧,通过挂载 docker.sock 与宿主机通信,避免了上面的问题,试了一下,只有 22 MB,还能接受。
但是额外加一个 docker-compose 还是让我不舒服,我要做到尽可能小的体积,所以,我选择 curl。
直接对着 unix socket 来操作。
2.3.1. 在容器中启动容器
API 文档地址:https://docs.docker.com/engine/api/v1.26/
API 返回的是 json 格式,本来用 jq 可以搞定的,迅速取到相应的值,但是不想装额外的软件包,所以强行使用 cut 把结果剪切出来了,好粗暴,笑。
ID=$(curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" -d '{"Image": "zuolan/hugo:minify", "Volumes": {"/hugo/public":"/work/html"}}' -X POST http:/v1.26/containers/create | cut -d: -f2 | cut -d, -f1 | cut -d\" -f2)
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/start
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/wait
curl --unix-socket /var/run/docker.sock "http:/v1.26/containers/$ID/logs?stdout=1"
curl --unix-socket /var/run/docker.sock -X DELETE http:/v1.26/containers/$ID
2.3.2. 主镜像源代码
run.sh
#!/bin/sh
SEPARATOR="================================================================"
echo "正在执行初次构建:"
echo $SEPARATOR
echo "正在构建页面:"
hugo
echo "正在压缩网页资源:"
ID=$(curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" -d '{"Image": "zuolan/hugo:minify", "Volumes": {"/hugo/public":"/work/html"}}' -X POST http:/v1.26/containers/create | cut -d: -f2 | cut -d, -f1 | cut -d\" -f2)
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/start
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/wait
curl --unix-socket /var/run/docker.sock "http:/v1.26/containers/$ID/logs?stdout=1"
curl --unix-socket /var/run/docker.sock -X DELETE http:/v1.26/containers/$ID
echo "页面已经发布,容器进入监视状态。"
VOLUMES="/hugo"
INOTIFY_EVENTS="create,delete,modify,move"
INOTIFY_OPTONS="--monitor --exclude=public"
inotifywait -rqe ${INOTIFY_EVENTS} ${INOTIFY_OPTONS} ${VOLUMES} | \
while read -r notifies;
do
echo $SEPARATOR
echo "文件有变动:"
echo "$notifies"
echo "正在重新构建页面:"
hugo
ID=$(curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" -d '{"Image": "zuolan/hugo:minify", "Volumes": {"/hugo/public":"/work/html"}}' -X POST http:/v1.26/containers/create | cut -d: -f2 | cut -d, -f1 | cut -d\" -f2)
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/start
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/wait
curl --unix-socket /var/run/docker.sock "http:/v1.26/containers/$ID/logs?stdout=1"
curl --unix-socket /var/run/docker.sock -X DELETE http:/v1.26/containers/$ID
echo "新的页面构建完成。"
echo $SEPARATOR
done
Dockerfile
FROM alpine
WORKDIR /hugo
ENV GIT_USER=izuolan [email protected]
COPY run.sh /run.sh
RUN apk add --no-cache inotify-tools hugo curl && \
chmod a+x /run.sh
VOLUME ["/hugo"]
CMD ["/run.sh"]
3. 压缩网页文件
因为 Hugo 是使用 Go 语言写的,在网页处理上缺少相应的功能,所以为了压缩网页,这里使用 Gulp 来自动化压缩网页。
比较遗憾的是我不知道 Gulp 有哪些可以实现 replace (替换 link)效果的插件(类似的插件),所以没能实现把外联式资源、图片嵌入网页的功能。哪位大佬知道请告知~注意是不能修改 HTML 的情况下实现。如果修改了主题文件就不能适用于他人了。
3.1. Gulp 压缩网页资源文件
某一次的自动压缩过程:
$ docker-compose logs -f
Attaching to hugo, minify, blog
hugo | 正在执行初次构建:
hugo | ================================================================
hugo | 正在构建并部署页面:
hugo | Started building sites ...
hugo | Built site for language en:
hugo | 0 draft content
hugo | 0 future content
hugo | 0 expired content
hugo | 6 regular pages created
hugo | 16 other pages created
hugo | 0 non-page files copied
hugo | 14 paginator pages created
hugo | 7 tags created
hugo | 5 categories created
hugo | total in 584 ms
hugo | 页面已经发布,容器进入监视状态。
minify | 正在执行资源文件压缩:
minify | ================================================================
minify |
minify | > @ build /work
minify | > gulp build
minify |
minify | [12:50:26] Requiring external module babel-register
minify | [12:50:28] Using gulpfile /work/gulpfile.babel.js
minify | [12:50:28] Starting 'build'...
minify | [12:50:28] Starting 'minify-html'...
minify | [12:50:29] Finished 'minify-html' after 1.43 s
minify | [12:50:29] Starting 'minify-js'...
minify | [12:50:32] Finished 'minify-js' after 2.33 s
minify | [12:50:32] Finished 'build' after 3.77 s
minify | 页面已经压缩,容器进入监视状态。
3.1.1. 压缩镜像源代码
Dockerfile
FROM mhart/alpine-node
WORKDIR /work
COPY . /work
RUN mkdir /work/html && \
npm install
VOLUME ["/work/html"]
CMD ["npm", "run", "build"]
gulpfile.babel.js
import gulp from 'gulp'
import htmlmin from 'gulp-htmlmin'
import uglify from 'gulp-uglify'
import runSequence from 'run-sequence'
gulp.task('minify-html', () => {
return gulp.src('html/**/*.html')
.pipe(htmlmin({
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
removeComments: true,
useShortDoctype: true,
}))
.pipe(gulp.dest('./html'))
})
gulp.task('minify-js', () => {
return gulp.src('./html/**/*.js')
.pipe(uglify())
.pipe(gulp.dest('./html'));
});
gulp.task('build', (callback) => {
runSequence('minify-html','minify-js', callback)
})
package.json
{
"private": true,
"scripts": {
"build": "gulp build"
},
"devDependencies": {
"babel-preset-es2015": "^6.5.0",
"babel-register": "^6.5.2",
"gulp": "^3.9.1",
"gulp-cli": "^1.2.1",
"gulp-htmlmin": "^1.3.0",
"gulp-uglify": "^2.0.0",
"run-sequence": "^1.1.5"
},
"babel": {
"presets": [
"es2015"
]
}
}
3.2. 推送到所有服务器
这个就各显神通了,文件都有,自己看着办。
不知不觉写到 23 点 49 分,怎么使用就留到明天吧。