Hexo-自定义部署

Hexo-自定义部署

1. 多Page仓库部署

Hexo-初始化静态网站 中提到,GitHub 允许一个账号拥有 1 个 账号 Page 和多个 项目 Page,因此可以利用多个项目 Page 达到分内容、分域名、冗余部署等。

开启项目 Page 的前提是已经开启了账号 Page,确保可用默认域名 [username].github.io 访问账号 Page。

1.1 开启项目Page

新建一个 GitHub 仓库用作项目 Page,GitHub 对项目 Page 的仓库名没有限制。

新建完成后需要初始化仓库以及提交一个初始 Commit,以一个简单的 HTML 源文件为例,在本地创建如下代码并上传到项目 Page 仓库的 main 分支中:

1
2
3
<html>
<h1>This is Project Page.</h1>
</html>

在项目 Page 仓库的 Settings - Pages 中的部署源选项 Source 选择 main 分支,则项目 Page 就已经建立好了,默认情况下这个项目 Page 的访问域名为:[username].github.io/DemoPage

1.2 自定义域名

GitHub Page 设置域名的方式是在 Page 仓库根目录下创建一个没有后缀的文件 CNAME,并在文件中写入一行(且只能有一行)需要自定义的域名,例如:

1
xxx.com

然后在 DNS 添加一条 CName记录,让 xxx.com 指向 [username].github.io 即可。需要注意的是,CNAME 文件中指定的是纯域名,不携带任何协议网络(例如 httphttps 等)。

假设账号 Page 设置了自定义域名 xxx.com、项目 Page 的仓库名为 DemoPage

默认情况下,项目 Page 的访问路径是作为账号 Page 的子路径访问的,则项目 Page 的访问路径会自动设置为 xxx.com/DemoPage

如果想要用自定义域名访问,只需要在 DemoPage 仓库根目录下创建 CNAME 文件,并在其中写入对应的域名。

  • 作为子域名访问时,写入例如:demo.xxx.com
  • 作为独立的顶级域名访问时,写入例如:yyy.com

然后在对应域名的 DNS 中添加一条 CName 记录,同样指向账号 Page 的默认域名 [username].github.io,GitHub 会自动判断请求的域名是否指向了某个 Page,如果是则自动转发。


2. 云服务器部署

考虑到 GitHub Page 在国内访问不太稳定,受不同运营商、不同地域的影响差别很大,因此也希望能部署到云服务器上。

2.1 搭建云服务器

云服务器按照个人需求选择即可。

以腾讯云轻量应用服务器 + Ubuntu Server 18 LTS 为例。

2.1.1 准备部署环境

  • 确保服务器已经可用,且具有公网 IP,允许公网访问。
  • 确保本地已有可用 SSH 密钥。
  • 服务器绑定 SSH 公钥,用于远端部署。

2.1.2 配置Ubuntu默认账号

腾讯云轻量应用服务器的默认账号为 ubuntu,如果创建服务器实例时已经设置了该账号的密码则可以跳过。否则需要通过「重置密码」设置,注意设置时选择用户名 系统默认 ubuntu

2.1.3 登录服务器

腾讯云轻量应用服务器在选择 Ubuntu 镜像时,默认会创建一个用户 lighthouse,并且腾讯云默认禁用了 root 用户,尽管可以通过配置开启,但远端登录时仍有许多问题。因此建议切换为标准的默认用户 ubuntu,否则后续需要多次调整权限。

网页端控制台可以在登录后通过 su ubuntu 切换至 ubuntu 账号,远端登录时可以通过 SSH 连接直接指定登录用户:

1
2
3
// [username] 即为需要登录的用户,前提是该用户已创建。
// [server ip] 即为服务器的公网 IP,如果没有则需要购买公网 IP。
ssh [username]@[server ip]

(1)如果首次从本地远端登录,可能会提示密钥交换信息:

1
2
3
The authenticity of host 'XXX.XXX.XXX.XXX (XXX.XXX.XXX.XXX)' can't be established.
RSA key fingerprint is XXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no/fingerprint)?

输入 yes 继续即可。

(2)如果曾经连接过,但是服务器重置了或者公网 IP 有变更,则远端登录时可能提示错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Please contact your system administrator.
Add correct host key in ~/.ssh/known_hosts to get rid of this message.
Offending RSA key in ~/.ssh/known_hosts:4
RSA host key for XXX.XXX.XXX.XXX has changed and you have requested strict checking.
Host key verification failed.

根据提示编辑 ~/.ssh/known_hosts 文件,删除曾经保存的服务器 IP 对应的 Host 信息:

1
2
// 删除公网 IP 对应的这条 Host 记录并保存
XXX.XXX.XXX.XXX ecdsa-sha2-nistp256 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

然后再重新登录即可。

2.2 配置云服务器

配置云服务器有两种方式:

  1. 使用默认 ubuntu 用户 + sudo 操作,但某些脚本可能需要单独设置 root 权限。
  2. 使用 root 用户配置,但远端使用 SSH 鉴权 Push 时也需要指定为 root 用户,可能会有安全性问题,不推荐。

结合上述问题,本文以第 2 种方式为例,使用 root 用户配置服务器,但禁用远端登录 root 用户,配置完后再对远端 Push 涉及的目录设置为 ubuntu 用户权限。

2.2.1 开启Root用户

腾讯云轻量应用服务器默认禁用了 root 用户,需要先以普通用户(例如 ubuntu)登录后再手动开启:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 设置 root 用户密码,可以与其他用户的相同:
ubuntu@~$ sudo passwd root
// 二次输入密码后提示设置成功:
passwd: password updated successfully

// 2. 编辑配置文件 Authentication 部分,开启 root 密码登录:
ubuntu@~$ sudo vi /etc/ssh/sshd_config

// 2.1 取消 PermitRootLogin 选项的注释,
// 默认配置 prohibit-password,禁止 Root 用户使用密码登录:
PermitRootLogin prohibit-password

// 2.2 如果需要允许 Root 用户登录,则修改 PermitRootLogin:
PermitRootLogin yes
// 然后添加一条配置,允许使用密码认证:
PasswordAuthentication yes

// 3. 保存配置退出编辑模式后,重启 SSH 服务
ubuntu@~$ sudo service ssh restart

断开远端连接重新登录;如果允许直接通过密码远端登录 root 用户,则可以直接通过 ssh root@[server ip] 登录。本文为了安全性保持禁用 root 用户直接密码登录,因此仍需先以 ubuntu 用户登录,然后再切换至 root 用户:

1
2
3
4
5
6
7
8
// 从本地登录至远端 ubuntu 用户:
[luis@~]# ssh ubuntu@XXX.XXX.XXX.XXX

// 登录后切换至 root 用户,输入 root 密码:
ubuntu@~$ su root

// 切换成功后可以看到已经切换至 root 用户,且命令进入特权模式:
root@/home/ubuntu#

2.2.2 安装环境

在云服务器部署静态网站,主要需要用到两个开发环境:

  • Nginx: 用于自动把公网请求映射到具体的静态网页资源。
  • Git: 用于从本地推送静态网站资源。

腾讯云轻量应用服务器的 Ubuntu 系统默认自带了 Git 环境,因此只需要安装 Nginx:

1
2
3
4
// 更新 apt:
root@/home/ubuntu# apt-get update
// 安装 nginx:
root@/home/ubuntu# apt-get install git nginx -y

2.2.3 配置网站目录

采用 Nginx + Git 托管静态网站需要用到两个目录:

  • Nginx 最终代理和转发的网站资源文件目录。
  • Git 接收远端 Push 网站资源文件的 Git 仓库目录。通过配置 Git Hook 将收到的文件自动关联到 Ngnix 的资源文件目录。

由于本文最终远端 Push 时使用 ubuntu 用户,因此将在 ubuntu 的用户目录 /home/ubuntu/ 下配置所有网站目录。

本文中创建了 /home/ubuntu/Projects/ 目录并将 Nginx 和 Git 目录统一配置在 Projects 目录下,实际路径可自由设置。

(1)创建 Nginx 网站资源目录并授权;

1
2
3
root@~# cd /home/ubuntu/Projects
root@/home/ubuntu/Projects# mkdir -p ./WebServer
root@/home/ubuntu/Projects# chmod -R 755 ./WebServer

(2)创建 Git 接收裸仓库的目录并授权;

1
2
3
4
5
6
// 注意创建的是裸仓库,添加 --bare 参数
root@/home/ubuntu/Projects# git init --bare Web.git
// 提示创建成功
Initialized empty Git repository in /home/ubuntu/Projects/LuisBlogWeb.git/

root@/home/ubuntu/Projects# chmod -R 755 ./Web.git

Git 裸仓库是指该仓库将只保存仓库的提交历史(.git 文件),而不会保存实际文件,但同样支持 Push 和 Pull,通常作为服务器存储仓库。

(3)配置 Nginx 代理和转发目录(注意备份);

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
root@/home/ubuntu/Projects# vim /etc/nginx/sites-available/default

// 修改 server 部分
server {
# HTTP configuration
#
## 监听普通 HTTP 的 80 端口:
listen 80 default_server;
listen [::]:80 default_server;

# SSL configuration
#
## 监听 HTTPS 的 443 端口:
listen 443 ssl default_server;
listen [::]:443 ssl default_server;

## 设置代理和转发的目录:
root /home/ubuntu/Projects/WebServer;

## 设置自定义域名:
server_name XXX.YYY;
}

// 然后重启 Nginx 服务。
root@/home/ubuntu/Projects# service nginx restart

2.2.4 验证网站访问性

(1)在 WebServer 目录下创建一个测试网页:

1
2
3
4
5
6
root@/home/ubuntu/Projects# vim WebServer/index.html

// 随便填充一个元素:
<html>
<h1>This is My Web.</h1>
</html>

然后访问公网 IP,如果可以正常访问说明配置正确;如果网页报 404 错误,则需要查看 Nginx 错误日志。

(2)Nginx 日志存放目录配置在 Nginx 配置文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@/home/ubuntu/Projects# vim /etc/nginx/nginx.conf

// 找到 http 部分的 Logging 设置:
http {
##
# Logging Settings
##

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}

// 然后查看错误日志:
root@/home/ubuntu/Projects# vim /var/log/nginx/error.log

// 日志内容:
yyyy/MM/dd hh:mm:ss [crit] 18205#18205: *1 stat() "/home/ubuntu/Projects/WebServer/" failed (13: Permission denied), client: XXX.XXX.XXX.XXX, server: XXX.YYY, request: "GET / HTTP/1.1", host: "XXX.XXX.XXX.XXX"

错误日志提示发生了 403 权限拒绝问题。

(3)查看 Nginx 的运行和启动用户:

1
2
3
4
root@/home/ubuntu/Projects# ps aux | grep "nginx: worker process" | awk '{print $1}'

www-data
root

发现 Nginx 启动用户是 root,但运行用户是 www-data,所以出现了用户权限被拒绝的情况。

(4)将 Nginx 的运行用户也修改为 root 用户:

1
2
3
4
5
6
7
root@/home/ubuntu/Projects# vim /etc/nginx/nginx.conf

// 将 user 修改为 root 用户:
user root;

// 然后重启 Nginx 服务
root@/home/ubuntu/Projects# service nginx restart

再次访问云服务器的公网 IP,可以正确访问了。

2.2.5 创建Git-Hook

由于 Nginx 只能将网络请求代理和转发到 WebServer 目录下的网站资源文件,而本地的网站资源文件是通过 Git Push 到 Web.git 仓库下的,因此需要创建一个 Git-Hook,自动将 Web.git 下的资源文件关联到 WebServer 目录中:

1
2
3
4
5
6
7
8
// 创建一个 Hook 文件 post-receive
root@/home/ubuntu/Projects# vim ./Web.git/hooks/post-receive

// 在 post-receive 中写入一行:
git --work-tree=/home/ubuntu/Projects/WebServer --git-dir=/home/ubuntu/Projects/Web.git checkout -f

// 保存退出后,给 post-receive 文件添加执行权限:
root@/home/ubuntu/Projects# chmod +x ./Web.git/hooks/post-receive

2.3 本地Push至云服务器

经过上述配置,服务器中的 Git 接收和 Nginx 代理转发已经配置好了,即可从本地将 Hexo 生成的静态网站 Push 至服务器。

(1)在 Hexo 站点配置文件中添加云服务器 Git 部署配置:

1
2
3
4
5
6
7
8
deploy:
## 部署至 GitHub Page 仓库:
- type: git
repository: git@github.com:[username]/[username].github.io.git
branch: main
## 部署至云服务器:
- type: git
repository: ubuntu@[server ip]:/home/ubuntu/Projects/Web.git

(2)本地执行 Hexo 命令部署:

1
2
3
4
5
6
7
8
[luis@WebLocal]# hexo clean && hexo g -d

// 有可能报错 unable to create temporary object directory:
error: remote unpack failed: unable to create temporary object directory
error: failed to push some refs to 'ubuntu@43.129.198.91:/home/ubuntu/Projects/LuisBlogWeb.git'

// 也有可能报错 Permission denied:
error: unable to create file xxx: Permission denied

向云服务器 Push 时报错,如果原因都是类似于「无法创建 XXX」、「无法访问 XXX」、「Permission denied」等等,绝大多数是因为 Git Push 的用户没有目标路径的权限导致的,在本例中,/home/ubuntu/Projects 目录以及其内所有子文件都是 root 用户权限,而云服务器为了安全禁用了远端登录 root 用户,所以 Git 在以 ubuntu 用户 Push 时就会导致权限不足。

(3)将云服务器中 /home/ubuntu/Projects 目录以及其内的所有子文件都改为 ubuntu 用户权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 先切换到 ubuntu 用户:
root@/home/ubuntu/Projects# su ubuntu

// 然后切换到 ubuntu 用户目录下:
ubuntu@~/Projects$ cd

// 将 Projects 和其内的所有子文件都修改为 ubuntu 用户的权限:
ubuntu@~$ sudo chown -R $USER:$USER ./Projects

// 查看文件权限,Projects 已经变成 ubuntu 用户:
ubuntu@~$ ll

drwxr-xr-x 4 ubuntu ubuntu 4096 Aug 1 hh:mm Projects/

再次从本地执行 Hexo 命令,即可成功部署。

2.4 云服务器配置SSL证书

为了访问安全性以及提升访问质量,可以给域名套上 SSL 证书。

如果使用自定义域名,需要确保自定义域名已经申请了 SSL 证书,并且认证通过绑定至域名。如果自定义二级域名访问,则需要分别为顶级域名和二级域名各申请一张 SSL 证书。

2.4.1 GitHub-Page开启HTTPS

GitHub Page 只需要确保域名已绑定 SSL 证书,然后在 Page 仓库的 Settings - Pages 中勾选 Enforce HTTPS 即可。

2.4.2 云服务器开启HTTPS

由于云服务器采用 Nginx 转发请求,因此需要将 SSL 证书下载并配置到 Nginx 中。配置 SSL 需要切换至 root 用户。

(1)上传 SSL 证书至云服务器;

从域名注册商下载已绑定的 SSL 证书并解压,找到 Nginx 下的两个证书,例如:domain.crtdomain.key,然后从本地上传到远端:

1
2
3
// 在本地终端执行以下代码:
[luis@ssl]# scp ./domain.crt ubuntu@XXX.XXX.XXX.XXX:/home/ubuntu/domain.crt
[luis@ssl]# scp ./domain.key ubuntu@XXX.XXX.XXX.XXX:/home/ubuntu/domain.key

注意,这一步命令可能会遇到报错 Permission denied (publickey),则检查以下配置:

  • 检查目标路径的用户权限,例如服务器当前用户是 ubuntu,但 /DemoDir 这个目录可能是 root 用户权限,解决办法为:

    • scp 命令改为指定 root 用户登录服务器,但通常服务器为了安全会禁用 root 用户的远端登录,因此不推荐。
    • scp 目标路径临时选择一个 ubuntu 用户有权限的目录,然后登录到服务器后再移动到其他目录,此时可以自由切换 root 用户了。
  • 检查服务器用户的 SSH 配置中是否绑定了本地公钥:

    • 查看服务器的 ~/.ssh/authorized_keys 文件,是否包含本地 ~/.ssh/id_rsa.pub 公钥内容。
    • 如果没有,则将本地 id_rsa.pub 公钥内容追加到服务器 authorized_keys 文件后(该文件可以包含多个公钥的内容)。

(2)将证书文件复制到云服务器中 Nginx 的默认配置路径:

1
2
root@/home/ubuntu/Projects# cp domain.crt /etc/nginx/domain.crt
root@/home/ubuntu/Projects# cp domain.key /etc/nginx/domain.key

(3)修改 Nginx SSL 配置(注意备份);

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
root@/home/ubuntu/Projects# vim /etc/nginx/nginx.conf

// 参考以下配置修改:
http {
##
# Basic Settings
##

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;

# server_names_hash_bucket_size 64;
# server_name_in_redirect off;

include /etc/nginx/mime.types;
default_type application/octet-stream;

## 开启访问错误拦截
proxy_intercept_errors on;

##
# Server Settings
##
## 禁止 IP 直接访问
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
return 444;
}
## 特定子域名单独转发(需要 DNS 解析)
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
server_name AAA.xxx.yyy BBB.xxx.yyy;
return 301 https://xxx.yyy$request_uri;
}
## 其他子域名全部转发至顶级域名(需要 DNS 解析)
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
server_name *.xxx.yyy;
return 301 https://xxx.yyy$request_uri;
}
## HTTP 自动转发至 HTTPS
server {
listen 80;
listen [::]:80;
server_name xxx.yyy; ## 自定义域名
return 301 https://$host$request_uri;
}
## 默认只代理 HTTPS 请求
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name xxx.yyy; ## 自定义域名
root /home/ubuntu/Projects/WebServer;
# autoindex on;
index index.html index.htm index.nginx-debian.html;
# 错误页面导航
error_page 404 500 502 503 504 https://xxx.yyy/404;
}

##
# SSL Settings
##
ssl_certificate domain.crt; ## 下载的 SSL CRT 证书
ssl_certificate_key domain.key; ## 下载的 SSL Key 密钥
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
}

(4)修改 Nginx 默认 Server 配置(注意备份):

1
2
3
4
5
6
7
8
9
root@/home/ubuntu/Projects# vim /etc/nginx/sites-available/default

// 去除(注释掉)默认配置中 Server 的重复配置项:
server {
# listen 80 default_server;
# listen [::]:80 default_server;
# root /var/www/html;
# server_name _;
}

(5)验证 Nginx 配置;

执行 Nginx 测试命令,检查是否配置通过:

1
2
3
4
5
6
7
8
root@/home/ubuntu/Projects# sudo nginx -t

// 如果输出如下,说明配置正确,否则根据 [warn] 或 [error] 的提示修改。
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

// 测试通过后,重启 Nginx 服务:
root@home/ubuntu/Projects# sudo nginx -s reload

2.5 验证网站访问

  • 以 HTTP 协议访问云服务器公网 IP:报错 444.
  • 以 HTTPS 协议访问云服务器公网 IP:报错 444。
  • 以 HTTP 协议访问自定义域名:自动转发至 HTTPS。
  • 以 HTTPS 协议访问自定义域名:正常访问。

如果以上均验证成功,则网站已经正常部署。

2.6 常见异常

如果访问服务器时报错 403,先按上文所述查看 Nginx 的报错日志:

1
ubuntu@~$ vim /var/log/nginx/error.log

(1)directory index of "..." is forbidden

1
yyyy/MM/dd hh:mm:ss [error] 28043#28043: *6 directory index of "/home/ubuntu/Projects/WebServer/" is forbidden, client: XXX.XXX.XXX.XXX, server: XXX.YYY, request: "GET / HTTP/1.1", host: "XXX.YYY"

首先检查 Git Push 是否成功,Hexo 在 Deploy 时,一旦某个文件 Push 失败则 Git 会中断 Push 任务,但只要 Push 任务结束 Hexo 就会提示 INFO Deploy done: git,所以看到这个提示并不一定代表 Push 是成功的,需要检查 Deploy 日志,确保没有提示任何文件写入失败。如果有,大概率是权限问题导致的,可参照上文配置;如果没有,则需要检查 Nginx 配置:

1
2
3
4
5
6
7
8
9
10
11
ubuntu@~$ vim /etc/nginx/nginx.conf

http {
## 默认只代理 HTTPS 请求
server {
// 在默认代理 server 内添加该配置:
index index.html index.htm index.nginx-debian.html;
// 如果不确定网站使用的默认页,则添加如下配置:
autoindex on;
}
}

参考文献