由于已经停止使用gogs而改为使用gitlab,加之后来把gogs搬进docker的时候无法直接使用post-recieve了,所以改成了webhook激发uwsgi的生成方式。推荐阅读:webhook生成静态博客。
类似Hexo、Hugo、Jekyll和Pelican等静态博客生成器,通常来说都是在本地生成之后再上传到服务器的。上传到服务器的方式通常是用rsync
来同步output文件夹,而这样做有一个问题:即使主要内容没有更新,很多html还是会发生变化(例如新增一篇文章之后,所有的index页面都发生了变化),导致每次几乎都是全部重新上传,等待时间极长。以前是非常苦恼于这个问题,后来看jekyll的manual,从它的自动部署篇获取了知识,经过一番改造,成功搞出了现在的git push
之后在服务器生成的操作流程。
这种生成方法的优势在于只需要上传发生了变化的源文件(外加git的数据),相比较之前每次都是全量上传,total size大大减少,对于小水管来说体验提升很多。劣势则是需要服务器端也安装与本地相同的生成环境,不仅第一次部署的时候需要花费时间,后续如果本地装了新的插件则需要再上服务器安装相同的东西,保证环境同步。不过对于一个稳定的博客来说——此处指不再折腾博客背后的技术,把心思放在写博客上——生成环境是不怎么变化的,所以劣势也不会太影响体验。
Edit:通过网上冲浪,学会了jekyll的生成时安装依赖的操作,这个部署环境的麻烦也减少了很多。
本文主要是根据 Jekyll Automated Deployment 改造而来的。
Bare Git
在进行这些操作之前,首先需要你的服务器上有名为git的用户,而且服务器的ssh能够让git用户登录。
目录结构与服务器环境
服务器的域名 | git.remote.domain |
用于自动生成和publish的服务器用户 | git |
nginx读取的用于提供静态网页的目录 | /var/www/blog |
bare git的目录 | /opt/blog.git |
本地blog的目录 | /home/useranme/blog |
gogs目录 | /opt/docker/gogs |
需要注意,nginx的owner和group通常是http,为了让git能够写入/var/www/blog
同时其内容又可以被nginx读取,就需要把/var/www/blog
的所有权设置成git:http
。
操作
首先假设本地的博客已经是一个git repo,而且已经在服务器装好与本地相同的生成环境。接下来就需要在服务器上创建一个bare git:
1
2
3
4
sudo mkdir /opt/blog.git
sudo chown git:git /opt/blog.git
cd /opt/blog.git
sudo -u git git --bare init
在本地博客的目录里添加remote:
1
git remote add origin git@服务器IP:/opt/blog.git
现在在本地push
应该是可以正常push到服务器的。(更改remote url的git命令:git remote set-url origin 新url
)。
Jekyll和环境
这里用的是特别的操作,参考Set up Jekyll environment on macOS。基本上是以下步骤。
注意,这里省略了如何用jekyll初始化一个新网站的操作,因为我用的主题的作者给的安装方式……就是把他的github repo clone下来,删掉他自己的资料,再把自己的放进去。一般来说按照下面的步骤把bundler装好、在第5步之前执行bundle config set --local path vendor/bundle
,就可以执行jekyll官方的初始化站点操作了,这样就可以继续修改Gemfile那一步了。
mac(本地)
-
安装brew
-
用brew安装ruby
-
把ruby的bin添加到PATH里,比如:
export PATH = "/opt/homebrew/opt/ruby/bin:$PATH"
-
用gem安装bundler:
gem install bundler
-
在博客目录配置好一个普通的Gemfile,例如:
1 2 3 4 5 6 7 8 9 10 11 12
source 'https://rubygems.org' gem 'jekyll', '~>4.2' gem "kramdown-parser-gfm", '~> 1.1.0' gem 'rouge' gem 'jekyll-paginate' gem 'jekyll-sitemap' gem 'jekyll-feed' gem 'jemoji' gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw] gem 'wdm', '>= 0.1.0' if Gem.win_platform? gem "webrick", "~> 1.7"
注意第一行的
source
是一定要有的。 -
/home/useranme/blog
内设置神秘bundler option:bundle config set --local path vendor/bundle
-
这个设置会让后续无论在哪个平台上执行
bundle install
都会把博客在Gemfile
里指定的依赖install
到博客根目录下的vendor/bundle
目录里,不影响全局,也确保了每次publish的时候用的都是满足Gemfile
的最新的依赖。 -
执行完这个命令后,会生成一个
config
文件(.bundle/config
),内容是:1 2
--- BUNDLE_PATH: "vendor/bundle"
虽然原文推荐将其添加进
.gitignore
,但是从本文实践来看,反而是应该把它也加入git track file里。
-
-
正常
bundle install
装好依赖,就可以了。
Linux(服务器)
按正常操作安装好ruby
和ruby-bundler
。
Post-receive
现在就需要利用到git的hook来实现服务器收到git push之后自动执行生成了。
这里默认:
- 安装了zsh,其实用bash应该也可以。
- push上去的是master branch,如果是别的名字那把
git clone
那一行的-b
后面的名字改就行
这个post-receive
文件需要放在/opt/blog.git/hooks/
里:
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
#!/usr/bin/env zsh
source /home/git/.zshrc
# bare git源目录
GIT_REPO=/opt/blog.git
# 用于生成的临时目录
TMP_GIT_CLONE=/tmp/blog_repo
# nginx读取的目录
PUBLIC_WWW=/var/www/blog
git clone $GIT_REPO $TMP_GIT_CLONE -b master
# 切换到临时目录
pushd $TMP_GIT_CLONE
# 如果服务器在国内,设置好代理服务器
# export https_proxy=http://127.0.0.1:8080;export http_proxy=http://127.0.0.1:8080;export all_proxy=socks5://127.0.0.1:8080
bundle install
bundle exec jekyll build
# 清理nginx读取的目录原有的内容
rm -rf /var/www/blog/*
# 将生成的网站文件移动到nginx读取的目录
mv _site/* /var/www/blog/
# 退出临时目录
popd
# 删掉临时目录
rm -rf $TMP_GIT_CLONE
exit
Gogs
很多时候用纯命令行的bare git实在是不方便:一,它没有web端,很难管理;二,域名支持很难搞,通常都是IP。总而言之,部署一个Gogs那就好用多了。
Docker
在/opt/docker/gogs
里新建两个目录data
和db
,分别用于gogs自己的文件和database的文件。使用这个docker-compose.yml
(放在/opt/docker/gogs
里)来快速部署一个能用的(记得自己改数据库密码):
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
version: '2'
services:
postgres:
image: postgres
restart: always
environment:
- "POSTGRES_USER=gogs"
- "POSTGRES_PASSWORD=数据库密码"
- "POSTGRES_DB=gogs"
volumes:
- "/opt/docker/gogs/db:/var/lib/postgresql/data"
networks:
- gogs
gogs:
image: gogs/gogs:latest
restart: always
ports:
- "127.0.0.1:20022:22"
- "127.0.0.1:20880:3000"
links:
- postgres
environment:
- "RUN_CROND=true"
networks:
- gogs
volumes:
- "/opt/docker/gogs/data:/data"
depends_on:
- postgres
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
gogs:
driver: bridge
- 注意
extra_hosts
里面增加的那一个entry,后面需要通过这个才能发送webhook给主机上的python做自动生成。 - 将gogs的ssh端口映射到主机上的20022端口,用于FRP穿透给公网服务器(VPS)的;将它的web页面端口映射到主机上的20880端口,用于本地主机nginx反代。
在docker compose up -d
之后,进入后面的步骤。
Nginx
类似于 nginx自签名证书与client_auth机制 中介绍的那样,配置好VPS和本地服务器的Nginx。本地的Nginx关键的proxy_pass
部分是这样的:
1
2
3
4
5
6
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:10880;
}
此时通过访问https://git.remote.domain
,流量会通过VPS转发回本地的服务器(如果不能,可能是没配置好FRP,那就先做完frp那一步再回来)先完成安装。注意所有的domain相关选项都设置成git.remote.domain
。安装过程的参数请参考docker镜像页面。安装完成后会自动重定向到一个invalid的URL,所以,首先docker compose stop
停掉整个镜像,手动打开/opt/docker/gogs/data/gogs/conf/app.ini
进行一些修改:
-
[server]
下,DOMAIN
改成git.remote.domain
;EXTERNAL_URL
改成https://git.remote.domain/
-
[security]
下,新增一行LOCAL_NETWORK_ALLOWLIST=host.docker.internal
(否则webhook会显示该地址解析为默认禁止的IP地址,导致无法自动构建网站)
保存修改之后重新docker compose start
应该就能正常访问gogs的页面了。
FRP内网穿透
HTTP部分直接参考 nginx自签名证书与client_auth机制 的就可以了,不需要特别改动的。
但是由于需要把gogs的ssh暴露到VPS上才能实现ssh推送,所以本地机子上的frpc.ini需要添加一个:
1
2
3
4
5
[gogs]
type=tcp
lcoal_ip=127.0.0.1
local_port=20022
remote_port=20022
重启本地机子上的frpc服务。另外还需要在VPS的防火墙放行20022端口。如果是从上一步跳过来的,现在可以回去做完安装流程了。
然后在gogs上新建一个仓库,再用更改remote url的git命令把博客仓库的远程地址改成gogs提供的ssh链接,大概是这样的:
1
git remote set-url origin git@git.remote.domain:gogs的用户名/blog.git
进行一次push,应该就能在Gogs上看到最新的源代码版本了。
POST Server自动构建
由于Gogs用Docker部署,docker镜像内是没有jekyll命令的,所以要依靠gogs的webhook功能发POST给主机上的监听程序调用主机上的jekyll来生成网页。
首先进入“仓库设置”-“管理Web钩子”,添加一个新的钩子,类型为Gogs,推送地址填http://host.docker.internal:25666
,数据格式json,只推送push,确认添加即可。
然后是python程序部分,这次把os.system呼叫的对象变成了另一个sh文件,毕竟调试起来方便。这个python脚本(postserver.py)和build.sh脚本都会放在/opt/autogenblog
里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
class handler(BaseHTTPRequestHandler):
def _set_headers(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_HEAD(self):
self._set_headers()
def do_POST(self):
self._set_headers()
self.send_response(200)
self.end_headers()
if self.headers['X-Gogs-Event'] == "push":
os.system("/opt/autogenblog/build.sh")
#监听0.0.0.0是因为作为主机,来自docker的webhook请求不是127.0.0.1的,也很难确定docker分配给gogs网络的地址,所以很难确定到底应该是哪个地址,还不如直接全监听
with HTTPServer(('0.0.0.0', 25666), handler) as server:
server.serve_forever()
而build.sh的内容则是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env bash
# 这个repo路径要看gogs的用户名的,自己改过来
GIT_REPO=/opt/docker/gogs/data/git/gogs-repositories/gogs用户名/blog.git
TMP_GIT_CLONE=/tmp/blog_repo
PUBLIC_WWW=/var/www/blog
git clone $GIT_REPO $TMP_GIT_CLONE -b master
pushd $TMP_GIT_CLONE
# 如果服务器在国内,设置好代理服务器
# export https_proxy=http://127.0.0.1:8080;export http_proxy=http://127.0.0.1:8080;export all_proxy=socks5://127.0.0.1:8080
bundle install
bundle exec jekyll build
rm -rf /var/www/blog/*
mv _site/* /var/www/blog/
popd
rm -rf $TMP_GIT_CLONE
exit
python脚本和build.sh都加上执行权限,然后在/etc/systemd/system
里面新增一个autogenblog.service用来长期运行python脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=Auto generate blog content
After=network.target nss-lookup.target
[Service]
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ProtectKernelTunables=true
NoNewPrivileges=true
WorkingDirectory=/opt/autogenblog
ExecStart=/opt/autogenblog/postserver.py
Restart=always
[Install]
WantedBy=multi-user.target
保存之后systemctl enable --now autogenblog.service
即可。那么一切都搞定了。gogs甚至提供了测试钩子的功能,可以测试一下能不能正常生成网页、正常浏览。
利用keychain,不用再提示输入密码
在gogs里面把push用的公匙贴进“帐户设置”-“管理 SSH 密钥”里面。然后在个人电脑上修改:
.ssh/config
:
1
2
3
4
Host git.remote.domain
User git
Port sshd的端口
IdentityFile 用于ssh的key文件路径
这样,假如是mac,用了ssh-add --apple-use-keychain
把这个key添加到keychain之后,每次登录就会自动解锁,也就因此可以不用输入密码就push了。