最近升级了博客的搭建与发布工作流,在此总结一下。

背景

一直使用Hexo+Github Pages这个方法发布博客。具体的工作流如下:

  • 使用VSCode打开Hexo工作目录编辑Markdown博客
  • 编写了一个Shell脚本用于新建与发布文章,参考文章
    • 该Shell脚本支持新建文章到特定子目录下,便于分类管理
    • 该Shell脚本支持发布Hexo静态网站,同时把Hexo工作目录也push到Github
  • 对于插入图片的问题,我编写了一个VSCode插件Paste Image,这个插件可以直接从剪贴板粘贴图片到Markdown中,图片保存的位置可以配置

但是使用过程中一直伴随着一些痛点:

  • Github国内速度较慢,导致Hexo生成后的静态网站push到Github上的速度有时候会非常慢,写了半天的文章,发布的时候却卡住了,有点无法忍受
  • Github Page在国内的访问不稳定,速度慢,甚至时不时会被墙掉
  • VSCode编辑博客,虽然可以利用VSCode强大的编辑功能,但是图片不能预览这一块还是不太方便
  • 如果在多台电脑上编辑博客,比如家里和公司,两个地方都要搭建nodejs和hexo环境

正好最近注册了博客域名,然后阿里云搞活动买了三年ECS,同时发现了Markdown编辑工具Typora,这个编辑器界面美观,支持及时渲染Markdown,最重要的是支持粘贴图片,可以通过配置指定图片的保存位置。

所以指定了一个博客升级计划:

  • 使用自己的云主机来替代Github Pages,提高访问的速度和稳定性
  • 博客个工程目录也搭建在云主机,提高发布时git push的速度,提高发布幸福感
  • 使用Typora进行博客编辑,图片做到既可以编辑时预览,又可以做到发布后正常访问

整体结构

Hexo搭建在自己的VPS上的方案网上有非常多的文章,比如这篇搭建VPS的hexo博客,这类标准方案的结构如下:

这种模式是Hexo+私有服务器的标准模式,用户执行hexo generate在本地Hexo工作目录的public目录下生成静态网站,然后执行hexo deploy,使用hexo-deployer-git插件以git push的方式推送静态网站到服务器的Git仓库,Git仓库的Git Hook复制静态网站的内容到Nginx的网站目录下,博客的发布就完成了。

这种模式已经非常方便,但是有两个痛点:

  • hexo deploy只会发布生成的静态网站,Hexo工作目录本身还需要用git管理起来,否则有丢失风险,所以还需要执行命令推送hexo工作目录
  • hexo deploy推送的是生成的静态网站,生成的内容较多,我的使用感觉是这一步推送比较慢,因为git操作是在hexo命令中执行的,比较不可控,不好排查为啥比较慢,只能多次执行

所以我使用了另外一种发布流程:

这种发布流程是在服务端进行hexo generate操作,生成后的静态完整直接复制到Nginx网站目录下就行了。这种模式在本地发布时更加简单了,但是远程git hook所触发的流程更新复杂。

下面来说一下如何搭建这一套工作流。

本地环境

本地环境需要的操作:

  • 安装Hexo
  • 新建Hexo博客项目
  • 初始化Hexo工作目录为git项目
  • 编写博客新建与发布的工具脚本

安装Hexo

1
$ npm install hexo-cli -g

新建Hexo博客项目

1
2
3
$ hexo init hexo-test
$ cd hexo-test
$ npm install

初始化Hexo工作目录为git项目

1
2
3
$ git init
# 服务端环境搭建好后,这个git地址才会生效
$ git remote add origin <git@服务器IP:blog.git>

编写博客新建与发布的工具脚本

新建博客可以使用hexo new命令,但是新建的博客只能在source/_post目录下。我为了方便分类博客,在source/_post目录下通过子文件夹的方式分类博客。

但是今天我才发现,这种做法对于博客的地址是有影响的,比如source/_post/list.md生成的地址是/2018/08/08/list/,但是如果你把文章放到子目录下,比如source/_post/java/list.md,这样生成的文章的地址是/2018/08/08/java/list/,也就是文章的父目录也被加入到URL中,这样的坏处是文章一旦写好,目录的结构就不能变化了,否则会导致文章地址变化。

同时为了支持Typora按Hexo的规则保存图片,也需要在新建的博客中添加特定的配置信息,这个也在新建文章的脚本中进行。

这里说一下Typora这个编辑器。Typora是一个Markdown编辑器,大家可以上它的官网了解一下:Typora — a markdown editor, markdown reader

Typora的一大特点就是支持保存图片:

但是默认的全局配置和Hexo的图片保存方式不匹配,所以我以前都是手动再复制图片到Hexo的img文件夹中,然后再修改博客中图片地址,非常麻烦。不过Typora救命般的支持文章级别的配置,支持在文章的YAML Header中添加配置,支持的两个配置项为:

  • typora-root-url:图片的根目录
  • typora-copy-images-to:粘贴图片时,保存图片到哪个目录

Hexo的图片根目录是就是网站的根目录,对应的目录是source,我把图片保存在source/img中,所以typora-root-urltypora-copy-images-to这两个配置项只要按照规则配置为sourcesource/img这两个目录相对当前文章所在的目录就行了,这样Typora编辑时能正常保存并显示图片,Hexo发布后,图片也能正常在网站上显示!

所以这个我放在Hexo工作目录下名为blog.sh的脚本功能有:

  • 新建博客blog n
    • 支持新建文章到特定子目录,比如blog n java/jvm/xxx
    • 支持在文章的YAML中添加Typora图片配置,计算好相对路径
  • 发布博客blog d
    • 自动执行git add,git commit,git push操作

脚本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/bin/bash
# author: mazhibin
# log : 2016-02-25 新建
# : 2016-03-15 获取git仓库状态,只在有修改的情况下,才会执行发布操作
# : 2016-03-18 修复判断git仓库状态的bug(没有搞懂shell的if语句导致的)
# : 2016-04-17 n命令添加子目录功能,blog.sh n "java/test"就会新建test博客,然后移动到java目录下
# : 2018-08-12 添加多级分类目录的能力,比如blog.sh n "java/lang/test"
# : 2018-08-12 添加在Front-matter中添加Topora图片配置的能力


if [ "$#" = 0 -o "$1" = "-h" ];then
echo "Usage: sh blog.sh [dnp]"
echo " d 发布博客"
echo " n 新建博客"
echo " p 发布草稿"
fi

cd "$(dirname "$0")"

# 判断git仓库是否有未提交的修改
function git_change(){
[ $(git status --porcelain 2>/dev/null | wc -l) != "0" ]
}

# 如果没有修改则不会进行提交
if [ "$1" = "d" ];then
if ! git_change;then
echo "not modified."
exit
fi
git add .
git commit -m "Site updated: `date +%Y-%m-%d\ %H:%M:%S`"
git push origin master
echo -e "deploy `date +%Y-%m-%d\ %H:%M:%S`\n" >> ~/blog_log
fi

# 新建博客,也可以新建草稿(可以指定子目录)
if [ "$1" = "n" ];then
blog_dir="other" # 不指定博客子目录,都移动到other下
blog_name="$2"

if [[ "$2" =~ "/" ]];then
# 指定博客在子目录中
blog_dir=$(echo "$2" | sed 's/\/[^/]*$//')
blog_name=$(echo "$2" | sed 's/.*\///')
fi

blog_dir="./source/_posts/$blog_dir"
blog_img_dir="./source/img"
echo "blog_dir=${blog_dir}"
echo "blog_name=${blog_name}"

# 先判断子目录是否存在,不存在就咨询要不要新建
if [ ! -d "$blog_dir" ];then
echo "子目录'${blog_dir}'不存在,是否新建?(输入y新建): \c"
read need_create
if [ "$need_create" = 'y' ];then
mkdir -p $blog_dir
else
echo "子目录不存在,创建博客取消"
exit
fi
fi

# 新建
if [ "$#" = "3" ];then
blog_real_name=$(hexo n "$blog_name" "$3")
else
blog_real_name=$(hexo n "$blog_name")
fi

# hexo的输出为:INFO Created: ~/project/blog/blog/source/_posts/te-st-5.md,从中提取文件名
echo "hexo n output=${blog_real_name}"
blog_real_name=$(echo "$blog_real_name" | sed 's/.*\///' | sed 's/\.md//')
echo "blog_real_name=${blog_real_name}"

# 移动(如果覆盖则提示)
mv -i "./source/_posts/${blog_real_name}.md" ${blog_dir}
#mv -i "./source/_posts/${blog_real_name}" ${blog_dir}

# 在Front-matter中添加Typaro图片配置项
# 获取相对路径 https://unix.stackexchange.com/a/233882
typora_image_relative_path=$(realpath --relative-to="${blog_dir}" ${blog_img_dir})
typora_root_relative_path=$(realpath --relative-to="${blog_dir}" "./source")

## sed替换为回车 https://stackoverflow.com/a/18410122/6720024
sed -i '' '2,100s/---/typora-root-url: '${typora_root_relative_path//\//\\/}$'\\\n---/' "${blog_dir}/${blog_real_name}.md"
sed -i '' '2,100s/---/typora-copy-images-to: '${typora_image_relative_path//\//\\/}$'\\\n---/' "${blog_dir}/${blog_real_name}.md"
fi

# 发布草稿到正式博客
if [ "$1" = "p" ];then
hexo p "$2"
fi

服务端环境

服务端环境需要的操作:

  • 搭建Git远程仓库
  • 安装Nginx
  • 编写Git Hook

搭建Git远程仓库

1
2
3
4
5
6
7
8
9
10
# 安装git
$ yum install git -y
# 添加git用户
$ useradd git
# 设置git用户密码
$ passwd git
# 切换到git用户
$ su git && cd
# 在git家目录新建博客git仓库
$ mkdir blog.git && cd blog.git && git init --bare

安装Nginx

1
2
3
4
5
6
7
8
9
# 安装nginx
$ sudo yum install -y nginx
# 新建静态网站目录
$ mkdir /var/www/hexo
# 设置权限给git用户
$ chown git:git -R /var/www/hexo
# 手动修改nginx配置文件/etc/nginx/nginx.conf的root配置项为/var/www/hexo
# 启动nginx
$ nginx

编写Git Hook

整个发布流程的关键是本地push,远程仓库收到push请求后,执行我们的自定义操作。Git Hook可以在Git的操作上挂上钩子,这样我们能在Git的工作流上添加我们的自定义流程。很多自动化发布就是基于Git Hook做的。因为我们要在远程push操作完成后执行操作,所以需要在/home/git/blog.git/hook/post-receive中添加脚本。

我们Git Hook的任务有:

  1. 从仓库中clone Hexo工作目录的内容到临时目录。因为远程仓库都是使用bare模式新建的,目录中只有git管理信息没有项目本身的文件,所以需要clone出项目到一个目录下
  2. 如果是第一次clone出来的项目,还需要安装nodejs依赖和hexo主题
  3. 执行hexo generate生成静态网站
  4. 复制生成的静态网站到Nginx的目录下

/home/git/blog.git/hook/post-receive代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash
GIT=/home/git/blog.git
CLONE=/tmp/blog
WWW=/var/www/hexo

# 判断CLONE目录是否存在,不存在clone,存在pull
if [[ ! -d ${CLONE} ]]; then
echo "CLONE目录不存在,执行git clone"
git clone $GIT $CLONE
cd ${CLONE}
# 安装主题
rm -rf ${CLONE}/themes/*
# 这一步如果太慢,可能导致客户端push时提示超时
git clone https://github.com/mushanshitiancai/maupassant-hexo.git ${CLONE}/themes/maupassant
# 安装nodejs库
cnpm install
else
echo "CLONE目录存在,获取最新内容"
# cd ${CLONE}
# 不知道为什么,上面cd到CLONE目录后,执行git命令提示不是git仓库,所以改用命令指定工作目录
git --git-dir=${CLONE}/.git --work-tree=${CLONE} fetch --all
git --git-dir=${CLONE}/.git --work-tree=${CLONE} reset --hard origin/master

git --git-dir=${CLONE}/themes/maupassant/.git --work-tree=${CLONE}/themes/maupassant fetch --all
git --git-dir=${CLONE}/themes/maupassant/.git --work-tree=${CLONE}/themes/maupassant reset --hard origin/master
fi

echo "执行hexo生成"
cd ${CLONE}
hexo g

echo "复制生成后的静态网站到网站目录"
rm -rf ${WWW}/*
cp -rf ${CLONE}/public/* ${WWW}

我使用maupassant这个主题,fork这个项目,修改配置,然后push。安装主题时直接pull自己的项目地址就可以了。

工作流程

首先新建博客:

1
$ blog n "blog/Hexo博客搭建与发布最强套路"

本地在Typora中编辑博客:

然后发布博客:

1
$ blog d

博客就发布好了!

待解决问题

无论如何,痛点都是无止境的。这套工作流还存在的问题是:

  • Typora图片插入后如果删除图片,图片文件还会继续存在,如果没有注意删除,会导致存在未被引用的垃圾,需要有一个机制自动清理这些垃圾图片
  • 目前Hexo工作目录是推送到个人主机上,还是存在丢失风险,需要有几个机制push到github上
  • maupassant主题结合畅言评论框时,不会在主页上面显示最近评论
  • 使用第三方评论框,有丢失数据风险。之前使用多说,后来多说挂了,后来使用Disqus,因为评论不多,数据也都没管

参考资料