devops-自动化部署自己的博客

自动化部署自己的博客

思路汇总

我自己分几个文件夹写了一些笔记和资料,现在想用Hugo部署到自己的云主机上同时同步到github上,利用gitpage在生成一个blog,也就是自动生成两个同步的blog,初步体验一下DevOps流程。目前本地使用win10,带有git;云主机是Debian 10,有golang 1.15.14环境。主要使用了git hooks和jenkins,以及rsync作为补充。

DevOps

总体思路:

  1. 由于本地文件夹都放在一起,首先通过编辑.gitignore文件,指定需要git管理的几个文件。
  2. 将自己的云服务器作为一个git remote仓库,管理自己的这些资料和笔记
  3. 在云服务器的git repo中设置hook,将这些本来在不同文件夹中笔记和资料复制到特定文件夹中。注:用hook复制的方法并不好,因为当文件名称修改或被删除时,就无法实现文件的同步。
    • 改成在远程主机上再设置一个git仓库,设置post-receive hook通知jenkins将提交的内容pull到云主机新的仓库中
  4. 建立hugo文件到git仓库的软链接,不能使用软链接的用rsync同步过去,当hugo编译的时候实际使用的是git仓库中的文件
  5. 使用jenkins自动构建部署云服务器上的Hugo blog
  6. 将完整的编译好的Hugo blog推送到GitHub,利用GitPages生成网站。

为什么不设置两个remote repo分别推送?怕自己疏忽,造成大量的冲突处理问题。

为什么不先推送到GitHub再用Github Actions推送到云上?自己觉得在云上折腾比较方便,就当云是测试环境,GitHub是发布环境吧。

选定需要git管理的内容

我们在有很多文件/文件夹的地方执行git init .时,会有大量不需要的文件也被放入待添加空间,我们需要先创建一个.gitignore文件筛选出我们需要的文件。比如,我们需要保留images,学习笔记,工程笔记,网页资料四个文件夹,其他全部不要,则.gitignore文件可以写成:

 1# 先忽略跟文件下所有文件,开头使用/防止递归
 2/*
 3# 取反所有需要git管理的文件
 4# images图片文件夹
 5!/images
 6# 笔记文件夹:学习笔记,工程笔记
 7!/学习笔记
 8!/工程笔记
 9# 资料文件夹,已提前都转为网页格式
10!/网页资料
11# .gitignore本身
12!.gitignore

然后查看目前状态:

 1$ git status
 2On branch master
 3No commits yet
 4Untracked files:
 5  (use "git add <file>..." to include in what will be committed)
 6        .gitignore
 7        images/
 8        学习笔记/
 9        工程笔记/
10        网页资料/
11nothing added to commit but untracked files present (use "git add" to track)

没有问题,接下来正常流程,添加进git本地库。

1$ git add .
2......
3$ git commit -m "首次将images,学习笔记,工程笔记,网页资料四个文件夹添加进repo"
4......

如果是初次使用git,还要先配置用户信息,这一点很重要,因为每一个Git提交都会使用这些信息,它们会写入到你的每一次提交中,不可更改。比如:

1$ git config --global user.name "John Doe"
2$ git config --global user.email johndoe@example.com
3# 然后查看现有的全局信息,已有用户信息
4$ git config --list

gitignore添加忽略文件失效的处理方法

如果项目开始的时候没有将一些无关文件配置进.gitignore里面,导致文件已经进行跟踪了,但是目前想把这些文件添加到.gitignore文件中,但是发现没有效果。这是因为.gitignore对已被git管理的文件无效。在这种情况下,必须使用git rm --cached < file name >这个命令来移除对这个文件的跟踪,然后将不想被跟踪的文件添加到.gitignore文件里面就可以了。

在云服务器上搭建git服务器

由于git是完全分布式设计,本质上,各个git库之间是没有主次之分的(各个库之间实际上都可以互相pull/push)。因此,在云服务器上搭建一个git服务器和客户端安装git过程上没有区别。在win 10 上安装git安装包(包括git bash,它有一个奇葩设计之后再说),在Debian 10上使用sudo apt install git安装。我们“人为决定”让云服务器上的git repo作为“公共库”,本地主机上的作为工作环境。

创建Git用户和库文件

在云服务器上,我们最好再添加一个专用git用户(不添加也可以,URL中git@domain.com:port或git@ip:port就是git作为用户名,如果你用的是别的用户,比如jack,那么就用jack@domain.com:port)

 1# 在云服务器上
 2$ sudo adduser git
 3......... # 这里会设定git的密码,要记住
 4.........
 5.........
 6$ cd /srv && sudo mkdir gitblogs.git # 未来把/srv/gitblogs.git作为库位置,一般git库都以.git作为结尾
 7$ sudo chown -R git:git gitblogs.git # 把所属用户从root改成git
 8$ su - git # 带 - 符号表示环境变量也跟着切换
 9# 如果把git设置成非登录用户,则以下操作用root执行,再把文件拥有者和组改成git
10.......... # 输入git用户密码
11$ id # 实际id号码可能有区别
12uid=1001(git) gid=1001(git) groups=1001(git)

有的用useradd git创建新用户,这样只是创建了一个最小用户结构,里面很多设置、环境变量得自己弄,比较麻烦。自己也没那么多安全性讲究(包括但不限于nonlogin设置),就不那么麻烦了,直接sudo adduser git搞定。下面我们要在云端建立存储内容的仓库,有两种方式:一是建立裸库,二是建立普通库来实现同步。建立裸库之后,里面什么都没有,即使push内容之后也不会显示到工作区,也无法进行操作,只是单纯的一个存储仓库,需要在云端建立一个中转普通仓库来读取内容;建立普通库可以不用再在云端建立中转库,但是需要post-receive钩子来实现内容更新。我这里更推荐第一种。

下面是初始化裸库:

1$ cd /srv/gitblogs.git
2# 远程服务器初始化仓库
3$ git init . --bare

下面是初始化普通库

1$ cd /srv/gitblogs.git
2# 远程服务器初始化仓库
3$ git init .
4# 允许向普通库push
5$ git config receive.denyCurrentBranch ignore

详细说明如下:

(1)我们可以用git init .建立一个普通库,而非裸库。当你创建一个普通库时,在工作目录下,除了.git目录之外,你还可以看到库中所包含的所有源文件。你拥有了一个可以进行浏览和修改(add, commit, delete等)的本地库。当你创建一个裸库时,在工作目录下,只有一个.git目录,裸库是没有工作区的!库仅包含记录着版本历史的文件。如果建立的是普通库,由于有工作区,可以直接把hugo博客内容的软连接建立到普通库的文件中,但是git不鼓励直接操作远程仓库的内容,所以我推荐在云上建立裸库后,在建立第二个普通库作为中转。

(2)自Git 1.6.2版以来,Git默认不会让你推送到非裸库。这是因为git push命令仅更新远程存储库上的分支和HEAD引用。它不会更新非裸机中的工作区副本、暂存区和版本指针。因此我们让git需要忽略这个要求,即git config receive.denyCurrentBranch ignore。此外,由于工作区也不会自动更新,我们后面会使用post-receive hook来更新工作区内容。

添加ssh公钥

首先,保证服务端是允许通过公钥登陆,在/etc/ssh/sshd_config中,去掉公钥登录相关的注释符。

1# 通过公钥登录,如果需要就去掉前面的注释符
2PubkeyAuthentication yes
3# 指定存放客户端ssh公钥的文件的位置
4# Expect .ssh/authorized_keys2 to be disregarded by default in future.
5AuthorizedKeysFile      .ssh/authorized_keys .ssh/authorized_keys2

其次,在客户端生成公私钥对,win 10可以用cmd或git bash生成:

1# 生成公私钥对,下面一路回车即可
2$ ssh-keygen -t rsa
3Generating public/private rsa key pair.
4Enter file in which to save the key (++++++++++):
5Created directory '++++++++++'.
6Enter passphrase (empty for no passphrase):
7Enter same passphrase again:
8Your identification has been saved in ++++++++++/id_rsa.
9Your public key has been saved in ++++++++++/id_rsa.pub.

'++++++++++'指的是公私钥对存放的位置(Windows用户在C:\用户\“用户名”\.ssh目录下可以看到系统的.ssh公钥id_rsa.pub),打开该位置,然后将id_rsa.pub文件中的内容复制出来,放到服务端.ssh/authorized_keys文件中(没有就在此位置新建一个),即可通过公钥进行clone、push和pull操作。

也可不添加公钥,每次使用git用户的密码进行clone、push和pull操作。

本地库设置远程库的地址

由于为了平衡简单和安全,我们不用HTTP和Git协议(没装git-daemon),而选用SSH协议作为本地端和云服务器段的传输。SSH协议用于为Git提供远程读写操作,是远程写操作的标准服务,在智能HTTP协议出现之前,甚至是写操作的唯一标准服务。

对于拥有shell登录权限的用户帐号,可以用下面的语法访问Git版本库:

1#语法1: 
2$ ssh://[<username>@]<server>[:<port>]/path/to/repos/myrepo.git
3#语法2:
4$ [<username>@]<server>:/path/to/repos/myrepo.git

第一种是使用ssh://开头的SSH协议标准URL写法,另外一种是SCP格式的写法。 两种写法均可,SSH协议标准URL写法稍嫌复杂,但是对于非标准SSH端口(非22端口),可以通过URL给出端口号。由于我使用5122作为ssh端口号,所以我使用第一种语法。

在本地git库添加远程仓库地址

1# 添加远程地址
2$ git remote add origin ssh://git@"your IP or domain":"port number"/srv/gitblogs.git
3# 推送本地仓库到远程仓库
4$ git push -u origin master

下面要要体现一个git bash的骚操作了

由于,我的云服务器上的git用户是新创建的,因此其ssh公钥也是新添加的,首次登陆git用户也是通过root用户su过去的,之后就没有尝试登陆。我通过git push -u origin master推送的时候,git bash跳出了让我输密码的框框,如下图:

git_bash坑人设计

我觉得有些不对劲,因为我已经添加了公钥,理论上不应该再需要密码。我还是尝试输入了云服务器git用户的密码,显示不对,我有输入本地电脑的密码也不对,我就很奇怪。查了查资料,也没有我这个情况。

接下来,我尝试直接用VS code的git插件push,居然等了10多分钟还push不上去。最后,我使用windows自带的cmd执行git push -u origin master,跳出来一行通知:

1Microsoft Windows [版本 10.0.18363.1916]
2(c) 2019 Microsoft Corporation。保留所有权利。
3> git push -u origin master
4The authenticity of host '[xxx.xxx.xxx.xxx]:xxxx ([xxx.xxx.xxx.xxx]:xxxx)' can't be established.
5ECDSA key fingerprint is SHA256:8KEtnlH6cLQlFGYgDZgK12qDiiElEgX3PDV+X9xaiYY.
6Are you sure you want to continue connecting (yes/no)?

额,发现了没有?首次登陆ssh服务端首要确认key fingerprint的!也就是说,我们在git bash跳出的密码框里输入的不是什么密码,而是“yes”!!!

我重置了环境,在git bash跳出的密码框输了“yes”,不出所料,通过了…………🤣🤣🤣所以,git bash为什么要跳出密码框啊!误导人啊!之后,VS Code的git插件也能自动push上去了,这里也算发现VS Code git插件的一个小小问题,希望以后能改正吧。

远程仓库的工作区更新方法

无论是使用裸仓库还是使用普通仓库,我们都要读取最新更新的内容,裸仓库可以被正常clone和push更新,但是裸仓库不包含工作区,所以并不会存在在裸仓库上直接出现可用文件,需要建立一个普通中转仓库读取最新内容;而普通仓库存在工作区不自动更新问题,也需要用户自己更新。

推送给裸仓库的情形

如果之前建立的是裸仓库,里面是不会显示出任何用户内容的,需要建立一个中转普通仓库。这个中转普通仓库从裸仓库中pull最新的内容,提供给hugo blog。

我们在云端裸仓库同一文件夹中,新建一个中转仓库:

1$ cd /srv && sudo mkdir blogtransfer.git # /srv/blogtransfer.git作为中转仓库,一般git库都以.git作为结尾
2$ sudo chown -R git:git blogtransfer.git # 把所属用户从root改成git
3$ su - git # 带 - 符号表示环境变量也跟着切换
4# 将裸仓库内容clone到中转仓库,使用git的本地协议
5$ git clone /srv/gitblogs.git/ /srv/blogtransfer.git
6Cloning into '/srv/blogtransfer.git'...
7done.
8$ ls /srv/blogtransfer.git
9images  学习笔记  工程笔记  网页资料

初次git clone后,每当我们从本地提交新的内容,可以在/srv/blogtransfer.git文件夹中,使用git pull更新。

 1$ cd /srv/blogtransfer.git/
 2$ git pull # 我做了一个修改,来进行测试
 3remote: Counting objects: 7, done.
 4remote: Compressing objects: 100% (4/4), done.
 5remote: Total 4 (delta 3), reused 0 (delta 0)
 6Unpacking objects: 100% (4/4), done.
 7From /srv/gitblogs
 8   f9e0b20..a33731d  master     -> origin/master
 9Updating f9e0b20..a33731d
10Fast-forward
11 ...s-\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262\350\207\252\345\267\261\347\232\204\345\215\232\345\256\242.md" | 105 ++++++++++++++++++++++++++++++++++++++++++++++----------------------
12 1 file changed, 71 insertions(+), 34 deletions(-)

这样,我们就可以从中转仓库读取内容。

推送给普通远程仓库的情形

如果之前建立的是普通库,当我们在本地完成第一次远程仓库推送后,登录到服务器的/srv/gitblogs.git目录会发现,该目录里还是什么都没有啊,那和裸库有什么区别?!我们之前说了:

git push命令仅更新远程存储库上的分支和HEAD信息。它不会更新非裸机中的工作区副本、暂存区和版本指针

如果我们用git status查看,会发现多了大量的修改记录,因此我们需要手动把版本库的“指针”指向最新的地方。

1# 把工作区回退到git库的版本(此情况下等同于更新)
2$ git reset --hard

这样再查看/srv/gitblogs.git目录就有最新的内容了。需要注意的是,每一次push推送后,普通仓库都要用git reset --hard手动更新工作区。

设置post-receive hook完成内容自动更新

上面一章就已经提到过push裸仓库需要中转仓库来更新内容和push到非裸库,工作区不更新的这两方面问题,大多数人都是用post-receive hook来处理的。

Hook就是钩子,本质是一种触发器,代表在某种情况触发某种操作。Git的钩子脚本位于版本库.git/hooks目录下,当Git执行特定操作时会调用特定的钩子脚本。当版本库通过git init或者git clone创建时,会在hooks目录下创建示例脚本,用户可以参照示例脚本的写法开发适合的钩子脚本。

钩子脚本要设置为可运行,并使用特定的名称。Git提供的示例脚本都带有.sample扩展名,是为了防止被意外运行。如果需要启用相应的钩子脚本,需要对其重命名(去掉.sample扩展名)。

1$ ls
2applypatch-msg.sample  fsmonitor-watchman.sample  pre-applypatch.sample  prepare-commit-msg.sample  pre-rebase.sample   update.sample
3commit-msg.sample      post-update.sample         pre-commit.sample      pre-push.sample            pre-receive.sample

其中,pre-commit、post-update、pre-receive、update等等代表着钩子被触发的情况。我们要的post-receive这里恰好没有,没关系,自己创建一个就行。

需要指出:裸仓库内直接可以看到hooks目录,而非裸仓库需要在隐藏目录.git/hooks下查看。

  • 裸仓库
1# 新建post-receive构造,这是一个shell文件
2$ touch /srv/gitblogs.git/hooks/post-receive 
3# 增加可执行权限
4$ chmod +x /srv/gitblogs.git/hooks/post-receive
  • 普通仓库
1# 新建post-receive构造,这是一个shell文件
2$ touch /srv/gitblogs.git/.git/hooks/post-receive
3# 增加可执行权限
4$ chmod +x /srv/gitblogs.git/.git/hooks/post-receive

裸仓库的更新

复制以下脚本到post-receive文件来实现中转仓库的更新。

注意:这个脚本要复制到gitblogs.git这个裸库的hooks/post-receive文件中,而不是中转库的!

1#!/bin/bash
2git --work-tree=/srv/blogtransfer.git/ --git-dir=/srv/blogtransfer.git/.git pull

普通远程仓库的更新脚本

我们将下面这个脚本复制到post-receive文件来完成普通远程仓库的更新工作区。

 1#!/bin/bash
 2# 几个地址变量
 3GITBLOGS_PATH=/srv/gitblogs.git/
 4# 下面是自己记录的日志,方便查询记录用
 5LOG_PATH=/tmp/gitblogs-post-receive-log
 6
 7echo "Here is the post-receive hook to update work tree by 'git reset --hard'" >> $LOG_PATH
 8
 9# 输入时间等信息用于方便记录
10date >> $LOG_PATH
11echo "git reset --hard" >> $LOG_PATH
12# 回复工作区到最新版本库
13git --work-tree=$GITBLOGS_PATH --git-dir=$GITBLOGS_PATH/.git reset --hard >> $LOG_PATH

使用HUGO作为博客生成工具

我们这里这里使用Hugo作为生成博客的工具。我们根据自身需求,要做以下修改:

  1. 我的笔记是分类放在多个文件夹中,希望Hugo能够从多个目录中读取markdown 博客文章,这样我就不需要对本地文件结构做调整。
  2. 我有一个网页资料的文件夹,希望把它单独设置一个目录页面,而不出把它算成博客文章。我需要一个能生成文件夹目录txt文章的工具,然后此目录页面通过js读取并生成链接。
  3. 我的在本地写markdown的时候,使用图片的相对路径,且所有图片相对于博客文章的路径都是../images/,而Hugo默认的图片路径都是根目录的绝对路径。
  4. 希望能把内容条件给百度等搜索引擎,github page其实不需要这个功能。
  5. 增加安全的评论功能,应该需要生成永久链接。
  6. 处理yaml front matter与一级目录重复的问题。
  7. 文章中遇到太多公式的时候。hugo自带的编译器渲染出来的公式效果很差,有大量无法编译的公式。

问题1解决:我将文章目录都放到了hugo的content目录,hugo会自动读取content下所有的目录和文件,并根据目录结构生成这也是选择hugo框架的一个优势。

问题2解决:由于hugo在content文件中不支持编译纯html文件,我将网页资料的文件夹放在/static/目录下,然后在主题的lib_webpages模板中用hugoreadDir函数读取文件夹中的内容,然后拼接成链接,不需要能生成文件夹目录txt文章的工具等步骤。目录则使用/content/library/webpages.html生成,其调用了主题中的lib_webpages模板。有个小缺点,即URL的层级关系被破坏。

问题3解决:在本地我用的是VS Code写markdown,我发现它是支持打开的文件作为根目录来索引文件的,即支持以打开的目录为"/"索引。因此,我把我所有本地的markdown文件中的../images/改成了/images/。其实用绝对路径对本地写markdown也是有好处的,就是主要打开的文件夹不变,就可以用多层文件结构编写文档,而不用总记相对路径;对网站的SEO也有利。此外,hugo对相对路径的支持也是有很多问题的,不值得花太多精力处理。

问题4TODO:参考https://www.kyfws.com/post/kyfws-hugo-baidu-seo/

问题5解决:可以通过hugo内置的_internal/disqus.html完成评论功能,但是感觉我目前不太需要。

问题6解决:为了不让vscode 的markdownlint报错,我们首先将其多个一级标题警告“MD025/single-title/single-h1: Multiple top-level headings in the same document”取消。因为,VS Code的markdownlint插件会把yaml元标题当成是一级标题,因此需要在本地编辑环境目录中设置.markdownlint.json文件

1{
2  "MD025":false
3}
4

然后,我们为了让文章看起来更美观,采用以下形式:

1---
2yaml meta info
3title : "TITLE"
4date : "2020-02-02"
5---
6
7目录/table of contents
8
9## H1 title<!-- omit in toc -->

这样在渲染成网页的时候,两个大标题会夹着目录,因此会美观一些。如果hugo能提供自动把一级标题当元信息,或者VS Code能够markdown能够渲染yaml头信息就不同这么麻烦。此外,我将H1 title前面变成二级标题,可以在不改变.markdownlint.json文件的情况下在VS Code中不报错。

问题7解决:在有大量公式的文章中,不要使用hugo自带的golden编译器,替换成pandoc。pandoc的安装方法可在网上查到。我们还需要在该文章的头部信息中加入markup: pandoc这一条。

修改Hugo博客的clarity主题

我的博客是基于clarity主题修改而来,基本改动有

  • 在头部导航添加了归档和分类两项,添加了二者的模板、功能和archive type;
  • 在头部导航添加了资料库->资料web版,基于readDir实现了网页信息遍历的post,网页资料存放在/static/网页资料
  • 在头部导航添加了资料库->书签地址,书签位置/content/library
  • 在头部导航添加了专栏项,基于原来的series修改而来;
  • 删除了多语言支持,因为总是出bug;
  • 修改了侧边栏的内容显示顺序,取消了个人信息阅读更多按钮,将链接移植到了名字上;
  • 精简了分享链接,由于外国大多社交网站不可访问和国内社交网站的封闭性,只保留了复制网页链接的功能,删除了多余的社交网站链接;
  • 简化了默认的archetypes;
  • 修改了主题配色、链接、404页面等样式

现在只要将我的markdown文章的文件夹放入content目录,网页资料的内容放到/static/网页资料,images中的图片文件夹放到/static/images中,即可自动生成静态网站。

上传修改好的Hugo博客

首先,我们把改好的网站主题上传到云主机,路径为/opt/blogtheme,并把/opt/blogtheme的拥有者改为git。然后将其中content的内容建立软链接,连接到git工作区的相应目录。

1# 将/opt/blogtheme的拥有者改为git
2$ sudo chown git:git -R /opt/blogtheme
  • 如果使用裸仓库,需要从中转仓库提取需要的文件,路径为/srv/blogtransfer.git。我们建立目录的软链接:
1# 如果主题中已经有了/opt/blogtheme/static/projectnotes,则删除
2$ ln -s /srv/blogtransfer.git/工程笔记 /opt/blogtheme/content/projectnotes
3# 如果主题中已经有了/opt/blogtheme/static/studynotes,则删除
4$ ln -s /srv/blogtransfer.git/学习笔记 /opt/blogtheme/content/studynotes

在hugo 0.62以后,不在支持static的软链接复制(说是因为存在可能导致循环链接),因此hugo主题的static文件夹中无法直接使用ln,退一步,我使用rsync同步文件夹。我们把下面两命令加到之前的post-receive脚本中。

1# rsync -a source destination
2# 图片同步
3rsync -a /srv/blogtransfer.git/images/ /opt/blogtheme/static/images
4# 网页同步
5rsync -a /srv/blogtransfer.git/网页资料/ /opt/blogtheme/static/网页资料
  • 如果使用普通仓库,路径为/srv/gitblogs.git/。我们建立目录的软链接:
1# 如果主题中已经有了/opt/blogtheme/static/projectnotes,则删除
2$ ln -s /srv/gitblogs.git/工程笔记 /opt/blogtheme/content/projectnotes
3# 如果主题中已经有了/opt/blogtheme/static/studynotes,则删除
4$ ln -s /srv/gitblogs.git/学习笔记 /opt/blogtheme/content/studynotes

在hugo 0.62以后,不在支持static的软链接复制(说是因为存在可能导致循环链接),因此hugo主题的static文件夹中无法直接使用ln,退一步,我使用rsync同步文件夹。我们把下面两命令加到之前的post-receive脚本中。

1# 在hugo0.62以后,不在支持static的软链接复制(说是因为存在可能导致循环链接),因此static文件中
2# 无法直接使用ln,退一步,我用rsync同步。
3# rsync -a source destination
4# 图片同步
5$ rsync -a /srv/gitblogs.git/images/ /opt/blogtheme/static/images
6# 网页同步
7$ rsync -a /srv/gitblogs.git/网页资料/ /opt/blogtheme/static/网页资料

这样,我们也实现了内容和格式的分离,/opt/blogtheme做为存放格式的位置,基本不用动,每次只要git更新内容即可。

使用Jenkins自动化部署网站

经过上述准备,我们已经准备好了所需的内容,下面就是使用jenkins将它们构建成网站。我们首先新建网站的目录文件夹/opt/public/

1$ sudo mkdir /opt/public
2# 将/opt/public的拥有者改为git
3$ sudo chown git:git /opt/public

如果直接从shell手动输入构建命令,就一句指令很简单。

 1$ hugo --minify -s /opt/blogtheme -d /opt/public/
 2Start building sites …
 3hugo v0.89.4-AB01BA6E+extended linux/amd64 BuildDate=2021-11-17T08:24:09Z VendorInfo=gohugoio
 4
 5                   | ZH-CN
 6-------------------+--------
 7  Pages            |   335
 8  Paginator pages  |    54
 9  Non-page files   |     0
10  Static files     |   361
11  Processed images |     0
12  Aliases          |    80
13  Sitemaps         |     1
14  Cleaned          |     0
15
16Total in 18050 ms

不过,我们希望在git完成push后自动执行。这里我们使用jenkins来完成自动构建命令。

首先,为了解决jenkins的权限和环境变量问题,安装好jenkins后,我们需要先进行以下配置:

  1. 防止jenkins出现command not found的错误,我们要为jenkins添加PATH环境变量。在系统管理->系统配置->全局属性对话框中选中“环境变量”,添加键值对“PATH”和PATH对应值。PATH对应值在系统中使用echo $PATH获取,复制粘贴到这里即可。
  2. 防止jenkins出现Permission denied权限不够的问题,我们要重新以git用户启动jenkins,以git用户启动是因为我们之前文件拥有者都是git。
 1$ sudo vim /etc/sysconfig/jenkins
 2......
 3 ## Type:        string
 4 22 ## Default:     "jenkins"
 5 23 ## ServiceRestart: jenkins
 6 24 #
 7 25 # Unix user account that runs the Jenkins daemon
 8 26 # Be careful when you change this, as you need to update
 9 27 # permissions of $JENKINS_HOME and /var/log/jenkins.
10 28 #
11 29 JENKINS_USER="git" # 由默认值jenkins改为git
12......
13# 修改jenkins对应文件拥有者
14$ sudo chown -R git:git /var/lib/jenkins
15$ sudo chown -R git:git /var/cache/jenkins
16$ sudo chown -R git:git /var/log/jenkins
17# 重启jenkins
18$ sudo systemctl restart jenkins

接下来,我们打开jenkins网站界面,默认是8080端口。首先按照页面提示进行初始化(因为我的jenkins早就安装了,这里就不介绍初始化流程了),然后点击左边菜单栏的“新建任务”,输入任务名称,选择构建一个自由风格的软件项目。

jenkins1

按照如下配置,设置构建流程。我们不需要源码,因此也没有工作区,在构建一栏中加上需要执行的命令。构建触发器接下来再设置。

jenkins2

设置好后,我们点击保存,然后点击左侧菜单栏的“立即构建”,就可通过jenkins执行构建命令。目前为止,我们做的以上的工作,只不过是把在命令行做的工作放到jenkins里完成而已,若需要自动触发构建,还需要构建触发器,如下图所示:

jenkins3

构建的触发器种类选择“触发远程构建”,然后在身份验证令牌中输入一串随机数作为验证码,接下面,我们只需要访问一个网址http://127.0.0.1:8080/job/hugoblog/build?token=TOKEN_NAME,就能触发这个构建。由于我们的git库和jenkins在同一台主机上,所以IP写127.0.0.1就行,端口8080是jenkins的默认端口,后面的url是固定的,最后在“TOKEN_NAME”处用我们刚在身份验证令牌中输入的随机替代。我们在浏览器中输入这个URL,就可以发现jenkins启动了一个新的构建。

一般这样就没什么问若发生403错误:Error 403 No valid crumb was included in the request,则考虑jenkins关闭全局安全设置中的“跨站请求伪造保护”;若真心为了安全考虑,则增加获取crumb值,具体步骤自行搜索。

接下来我们要把触发网址的工作交给git的hook脚本,我们在post-receive脚本的最后添加如下命令:

1# 触发jenkins编译新网站
2# -X 表示请求类型,用post;-u 表示认证信息,填写登录jenkins的用户名密码;-v表示显示详细过程
3# URL触发远程构建所用的URL
4curl -X post -u 'username:password' -v http://127.0.0.1:8080/job/hugoblog/build?token=TOKEN_NAME

到此,我们已经完成了本地git push后,自动完成内容更新、网站构建的工作。

推送到Github并生成Gitpages

如果要使用GitPages,我们需要将hugo编译好的网站,而非原始数据推送到GitHub

首先,我们要在GitHub新建一个库,库名比较特殊,必须是:用户名.github.io,且所有字母必须小写。选择public类型,然后下面的README、证书、.gitignore都不用添加。如下图所示:

创建gitpages

由于我们自己云服务和GitHub的域名不一样,所以不能直接把云上编译好的网站直接push到GitHub。我们需要改变baseUrl参数,重新编译。为了公式完整,我们之前在markdown编译时用了pandoc编译速度慢,同时国内连接github网络不稳定,所以我在jenkins中单独为Github的推送流程新建了一个任务,以免拖累本地编译过程。

我们先做一些准备工作。

第一步,我们为了能让云服务器有权限把内容推送给GitHub的库,因此要将自己的公钥存放到GitHub上。我们存放的是云服务器上git用户的公钥,查看git用户的公钥cat /home/git/.ssh/id_rsa.pub,若没有此文件则使用git用户的ssh-keygen -t rsa创建。将公钥放到GitHub右上角图像->settings->SSH and GPG keys->New SSH key,然后把公钥复制粘贴过来。

第二步,我们为推送到github的库做一些初始化工作。

 1# 为GitHub新建库文件夹,并将所有者改为git
 2$ sudo mkdir cd /opt/surprisedcat.github.io
 3$ sudo chown git:git /opt/surprisedcat.github.io
 4# 切换到git用户执行以下操作
 5$ su - git
 6# 重新编译网站,注意baseUrl是不同的
 7$ hugo --minify --baseUrl=https://surprisedcat.github.io -s /opt/blogtheme  -d /opt/surprisedcat.github.io/
 8Start building sites …
 9hugo v0.89.4-AB01BA6E+extended linux/amd64 BuildDate=2021-11-17T08:24:09Z VendorInfo=gohugoio
10
11                   | ZH-CN
12-------------------+--------
13  Pages            |   335
14  Paginator pages  |    54
15  Non-page files   |     0
16  Static files     |   364
17  Processed images |     0
18  Aliases          |    80
19  Sitemaps         |     1
20  Cleaned          |     0
21
22Total in 18596 ms
23# 初始化git库
24$ cd /opt/surprisedcat.github.io
25$ git init .
26# 第一次commit和添加远程库,commit带上日期
27$ git add .
28$ git commit -m "`date` commit"
29# 将主分支重命名为main
30$ git branch -M main
31$ git remote add origin git@github.com:SurprisedCat/surprisedcat.github.io.git
32$ git push -u origin main

第三步,在jenkins中新建构建任务。源码管理选择“无”,构建触发器选择如下图:

jenkins-github

我们建立两个触发器,一是传统远程触发器,二是在云构建完成后,自动构建推送到github的流程。

在构建栏目下,选择“执行shell”,并填入以下命令:

1hugo --minify --baseUrl=https://surprisedcat.github.io -s /opt/blogtheme  -d /opt/surprisedcat.github.io/
2cd /opt/surprisedcat.github.io
3git add .
4git commit -m "`date` commit"
5git push -u origin main

点击保存。

一整套配置完成。

接下来,为了安全起见,我们不再允许git使用shell登录(过河拆桥啦),我们修改/etc/passwd下git用户的最后一项,将/bin/bash改为/usr/bin/git-shell,这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。