个人博客:https://horcrux.me , 本篇公众号与博客同步更新。

好几年前(2019?)刚学 Python 的时候就想搭一个自己的博客,但是那会工作很忙,也不知道能写多少东西,于是很快就从入门到放弃了。最近开始学习 Go 才知道 Hugo 是一个用 Go 编写的静态网站生成器,它简单、易用、部署也特别快。然后前段时间也开了个公众号浩克碎片,已经写了几篇,这样内容也算有了,但是用公众号终究是有些限制,也很有时效性,所以是时候搭个自己的博客了。

方案选择

一个博客即是一个网站,那一般来说需要以下几个条件吧:

  • 域名
  • 服务器
  • 网站内容

有些人会用 Github Pages,这样域名和服务器就都解决了,但是 Github 在国内不稳定,速度也慢,所以最好还是用自己的服务器(嗯才不是因为我已经有了一个服务器要折腾折腾)。

所以你需要买一个域名,一个服务器。国内的话可以选择阿里云、腾讯云等,而且往往域名和服务器可以打包一起,价格也比较美丽。当然需要解决备案问题,不过也很快。国外的域名和服务器一般分开买,域名可以选择 Google,Namecheap,GoDaddy,服务器可以选择 DigitalOcean, Linode, Vultr。虽然不需要备案,但是就不保证你的网站一定可访问了。

还有一个可选项:CDN,可以加速网络访问、隐藏服务器地址、提高站点安全。国内有七牛云、阿里云、腾讯云,国外有 Cloudflare、 AWS,可自行选择。

网站内容的生成和服务提供就有太多选择了,之前学 Python 的时候就了解过 Django。还有 Hexo(Node.js)、Jekyll(Ruby)…但是最近学习 Go,听说 Hugo 又非常快,那 Hugo 就决定是你了。
你只要按照 Markdown 格式写好文章,再敲一下 Hugo 命令就自动生成 html,css 等内容。

另外需要提供 Http 服务,Apache、Nginx、Tomcat、Caddy 都能做到且能做更多, 我选择了性能最好的 Nginx。
将 Hugo 生成的内容放到服务器上,配置好 Nginx 就可以。

还有一个可选但是强烈建议的是启用 HTTPS. 使用 Let’s Encrypt (简单教程见这里) 又或者使用 CDN 提供的或免费或收费的证书都可以。

最后是部署问题,我的目标是写好 Markdown 后,做一个类似「确认发布」的简单操作,网站内容就会同步更新。我看别人有各种方案,最后我选择的是使用 Github 远程仓库Webhook 的方式。有些人也会用 Docker 来部署或迁移自己的网站,我目前的方案已经满足需求,暂时不需要使用 Docker.

使用Hugo

域名和服务器的操作不在这篇里写了,不同的选择各不一样,按照服务提供商的官方操作来就行。这篇主要记录下内容生成和自动化部署部分。

首先是 Hugo 安装,本来呢按照官网给的(这里),要么用安装包要么用包管理工具,都没什么问题,偏偏学了一点 Go 想着后面可以按照自己需求改点什么,就想着通过源码安装,然后问题就来了…
服务器上源码安装成功,本地机器安装报错: SECURITY ERROR This download does NOT match an earlier download recorded in go.sum.。本着学习 go.sum 机制查了很久,也学到了一些东西,最后解决了这个问题,简单说根本原因是某个依赖库没有按规范打 tag 导致,具体探究和学习过程后面另写一篇补上。

接下来都比较简单,官网也有个 Quick Start.

Create a New Site

hugo new site your_site 创建一个新的 Hugo 站点,本质就是创建了一个叫 your_site 的文件夹,里面的目录结构差不多是这样

1
2
3
4
5
6
7
8
9
.
├── archetypes
│   └── default.md
├── config.toml
├── content
├── data
├── layouts
├── static
└── themes

Add a Theme

这里选择自己喜欢的主题,本例中使用 Ananke theme, 接下来

1
2
3
4
cd quickstart
git init
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
echo theme = \"ananke\" >> config.toml

这一步把 your_site 初始化为一个 git 仓库,并在 themes 下添加了 git 子模块 ananke。同时将这个主题添加到配置文件中。

Add Some Content

hugo new posts/my-first-post.md 创建第一篇文章,hugo 会自动在文件开头添加元数据:

1
2
3
4
5
---
title: "My First Post"
date: 2019-03-26T08:47:11+01:00
draft: true
---

draft 是一种属性,为 true 时,输入 hugo 命令不会为本篇生成 html,除非加 -D 参数。

tips: 如果使用 even 主题的话,posts 需要改为 post

Hmmm… 说点算题外又不算题外的话, 最近写 Markdown 有点多, 然后发现了一个痛点, 就是常常在写中文的时候却需要输入英文的标点符号, 一直以来都是先切换到英文状态, 写好标点后再切回中文, 好麻烦啊! 终于忍不了查了下,在 win10 下其实通过 ctrl+. 快捷键就可以切换了, 切为英文标点的话, 无论输入中文还是英文, 标点都是英文的. 切成中文标点的话就跟正常情况一样.

Start the Hugo server

接下来输入 hugo server -D 命令就会为你的站点启动一个本地的 http 服务,这样你就可以在浏览器中访问 http://localhost:1313/ 来预览最终效果。

如果输入 hugo , 会在当前目录下生成 public 文件夹,里面就是最终的内容,后面可以把 public 下所有内容部署到服务器上。

自定义

config.toml 配置文件中有很多可以自定义修改的配置项,你可以修改试试,大多都有注释。另外还可以修改主题里的内容甚至基于一些现有的主题设计自己的主题,这些就是比较高级的玩法了。

配置Nginx

Nginx 是一款 Web 服务器软件,可以用作反向代理、负载均衡、Http 缓存等,功能强大,性能优异,配置也很灵活。我对它的了解和使用也很肤浅,这里我主要用它来提供 http 服务。

Nginx 的安装就不提了,可源码安装也可以通过包管理器。

安装之后修改配置文件,Nginx 默认配置文件是 /etc/nginx/nginx.conf,其中又会加载一些其他的配置文件,我们这里需要修改的是 /etc/nginx/site-enabled/default, 我之前的内容大致如下:

 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
server {
    listen 443 ssl;
    server_name _;
    ssl_certificate_key /etc/nginx/cloudflare/key.pem;
    ssl_certificate /etc/nginx/cloudflare/cert.pem;
    ssl_reject_handshake on; # if nginx version >= 1.19.4
    # return 444;
}
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}
server {
        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name horcrux.me;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }
        location /your_webhook_url {
                proxy_pass http://127.0.0.1:9000/hooks/hook_hook;
        }

        error_page 404 /404.html;
        location = /404.html {
                root /var/www/html/;
        }

        listen [::]:443 ssl ipv6only=on;
        listen 443 ssl;
        ssl_certificate_key /etc/nginx/cloudflare/key.pem;
        ssl_certificate /etc/nginx/cloudflare/cert.pem;
        #ssl_certificate /etc/letsencrypt/live/horcrux.me/fullchain.pem;
        #ssl_certificate_key /etc/letsencrypt/live/horcrux.me/privkey.pem;
}

这里一共三个 sever 模块,第三个是最主要的。server_name 匹配域名 horcrux.me, 监听 443 端口(https),使用 CDN 服务商 Cloudflare 提供的证书和密钥,此证书仅对 Cloudflare 与源服务器之间的加密有效。如果你不用 CDN ,同时使用了 Let’s Encrypt 的证书,放开井号开头的 ssl_certificate 部分即可。

root /var/www/html; 指明站点目录,前面 Hugo 生成的 public 文件夹内容就要放到 /var/www/html 下。
error page 404 部分配置了自定义的404页面路径,这样使404的显示与 Hugo 的主题一致。
location /your_hook_url 部分配置了特定 URL 代理到 webhook 的服务上,具体在下面的自动化部署部分会说明。

前两个 server 模块的 server_name 都是 _ ,表示匹配任意域名,与第三个 server 一起看表示除 horcrux.me 的请求都返回444,表示关闭连接且不返回任何数据。这样就提高了站点的安全性。
当然这算是初级设置。我们可以做以下几点递增站点安全性。

  1. 配置防火墙直接禁掉80端口,只允许 https
  2. 如果你用的 Nginx 版本是 1.19.4 以上,可以使用 ssl_reject_handshake on,直接拒绝TLS握手
  3. 白名单机制,只允许 CDN 服务器的 IP 访问

这样的话只要第三个 server 块就可以。具体可以参看这篇

Webhook触发自动部署

现在,本地电脑上已经有了网站内容(Hugo 生成的 public 文件夹下的所有文件),服务器上的 Nginx 也配置好, 我们只要把本地的 public 文件夹下所有文件拷贝到服务器上 Ngixn 配置的路径(/var/www/htlm)下, 通过浏览器访问你的域名就可以看到了。

问题是,每次写一篇文章都要执行 Hugo + 拷贝的过程,当网速不佳或者站点内容变多时,这是一件非常低效且不优雅的事情。

所以我们需要非常简单方便的方式。另外我也需要一个备份、一个换了电脑也能写字部署的方式。
我选择将 Hugo 初始化的这个 git 仓库放到 Github 上,这样备份有了,换电脑/地方写字时直接拉取最新仓库即可。

然后问题就在服务器这端了,服务器 clone 好这个仓库后,如何知晓仓库有更新并拉取最近内容呢?作为一个小白,我查了好久才发现 webhook 这种方式。

关于 webhook,维基百科是这么说的:

Webhooks are “user-defined HTTP callbacks”. They are usually triggered by some event, such as pushing code to a repository or a comment being posted to a blog. When that event occurs, the source site makes an HTTP request to the URL configured for the webhook. Users can configure them to cause events on one site to invoke behavior on another.

简单说就是可以通过事先定好的 URL 通知到站点,然后站点执行相应的操作,我们这里就是拉取最新内容,然后拷贝到 Nginx 配置的路径下。

Github 的仓库设置中就有 webhook 的选项,配置好 URL 和 触发事件就可以了。根据我们的需求,触发事件选「Just the push event」就能满足。URL 随你配置。

但是服务器端配置了 CDN 和 Nginx,要怎么匹配特定 URL 呢?上一步配置 Nginx 的时候有这样的一项:

1
2
3
location /your_webhook_url {
        proxy_pass http://127.0.0.1:9000/hooks/hook_hook;
}

这是将特定 URL 转发到了 http://127.0.0.1:9000/hooks/hook_hook 上,而这个9000端口是另一个程序监听的,这个程序我用了 Github 上开源的 webhook ,它可以接受请求并执行你设定好的任务。

通过源码或者包管理工具安装这个 webhook,写好配置文件,比如就叫 hooks.json:

 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
[
{
    "id": "hook_hook",
    "execute-command": "/your_blog_path/auto_deploy.sh",
    "command-working-directory": "/your_blog_path"
},
"trigger-rule": {
    "and": [
        {
            "match": {
                "type": "payload-hmac-sha256",
                "secret": "your_webhook_secret"",
                "parameter": {
                    "source": "header",
                    "name": "X-Hub-Signature-256"
                }
            }
        },
        {
            "match": {
                "type": "value",
                "value": "refs/heads/main",
                "parameter": {
                    "source": "payload",
                    "name": "ref"
                }
            }
        }
    ]
}
]

/your_blog_path/auto_deploy.sh 的内容大致如下:

1
2
3
4
5
#! /bin/sh
cd /your_blog_path
git pull
hugo
cp -a /your_blog_path/public/* /var/www/html/

your_blog_path 换成你自己的路径。然后用 screen 命令新建终端运行 webhook -ip 127.0.0.1 -hooks hooks.json -verbose, 又或者 nohup webhook -ip 127.0.0.1 -hooks hooks.json & 后台运行,都行。

这样当你在本地写好文章 push 到 Github 远程仓库上的时候,就会通知到你的服务器,服务器上拉取最新内容并执行 hugo 命令,然后将新生成的内容拷贝到 Nginx 配置的路径下。新的文章就算部署好了。哟呵呵呵~

关于图床

写博客免不了会穿插一些图片,本来我也是喜欢用图床的,但是使用下来我总觉得不太适合目前的状态,使用七牛云之类的话我又要支付一笔费用,使用 Github 的仓库的话…我为什么不一开始就用 Github Pages 呢。 Gitee 用了几天总觉得一来必须是公开的仓库,二来速度似乎也没有那么的快。SM.MS 的话感觉走下坡路,未来都不一定长久。所以目前小的图片我还是直接放在服务器上了。大的图片…再说咯…

20220617更新: 可以考虑使用 Cloudflare + Backblaze B2 打造一个免费的图像 CDN

还有一些待完成的

  • 评论系统。这是最重要的,最近一定要加上。
  • 站点统计
  • RSS 订阅
  • SEO 优化
  • 加点音乐播放器之类的