起因是我想 self-host 一个本身设计是用在 Github Pages 上的 jekyll 博客主题,大概流程是在本地用 jekyll serve 起来以后,用 nginx 挂反代上 https。到这一步其实都还好,但是吊诡的是,我在 shell 里面用 bundle exec jekyll liveserve
跑起来以后,把这个命令做成一个 systemd service 就会一直报错。行为是这样的:
Dec 11 13:10:22 WordPress jekyll[25228]: Generating...
Dec 11 13:10:22 WordPress jekyll[25228]: Jekyll Feed: Generating feed for posts
Dec 11 13:10:22 WordPress jekyll[25228]: Liquid Exception: no implicit conversion of nil into String in /_layouts/default.html
Dec 11 13:10:22 WordPress jekyll[25228]: /var/lib/gems/2.5.0/gems/jekyll-github-metadata-2.13.0/lib/jekyll-github-metadata/client.rb:133:in `join': no implicit conversion of nil into String (TypeError)
Dec 11 13:10:22 WordPress jekyll[25228]: from /var/lib/gems/2.5.0/gems/jekyll-github-metadata-2.13.0/lib/jekyll-github-metadata/client.rb:133:in `pluck_auth_method'
Dec 11 13:10:22 WordPress jekyll[25228]: from /var/lib/gems/2.5.0/gems/jekyll-github-metadata-2.13.0/lib/jekyll-github-metadata/client.rb:46:in `build_octokit_client'
Dec 11 13:10:22 WordPress jekyll[25228]: from /var/lib/gems/2.5.0/gems/jekyll-github-metadata-2.13.0/lib/jekyll-github-metadata/client.rb:26:in `initialize'
Dec 11 13:10:22 WordPress jekyll[25228]: from /var/lib/gems/2.5.0/gems/jekyll-github-metadata-
...
Dec 11 13:10:18 WordPress jekyll[25224]: from /var/lib/gems/2.5.0/gems/jekyll-3.9.0/exe/jekyll:15:in `<top (required)>'
Dec 11 13:10:18 WordPress jekyll[25224]: from /usr/local/bin/jekyll:23:in `load'
Dec 11 13:10:18 WordPress jekyll[25224]: from /usr/local/bin/jekyll:23:in `<main>'
之后由于 Liquid Exception,ruby 解释器就会退出,接着触发 systemd 的 restart。但奇怪的是,我在 shell 里面运行就从来不会遇到这个错误。经简单查询以后,这个 no implicit conversion of nil into String
的错误在 jekyll 里面非常常见,很多组件都有发生这个问题,尽管查看了很多 issue 但都帮助不大。
后来仔细观察命令行运行的输出以后,发现有 GitHub Metadata: No GitHub API authentication could be found. Some fields may be missing or have incorrect data.
这样一个报错。这个报错在 systemd 的 log 里面没有,因此我怀疑这两个是否为同一个错误。在这个错误的时候,我发现了这个 issue,评论中有人提到了这样的解决方案:在 _config.yml
中加一行 github: [metadata]
,我试了一下,问题就得到了解决。
这就引出了另一个问题,为什么 jekyll 在 shell 下和 systemd 下的行为不一致。经查询之后,发现原因在于 systemd 和普通的 shell 在执行程序的时候使用的环境变量不一致,而在错误发生的 client.rb#L133 处需要引用 $HOME
这个环境变量,这个在 systemd 下是不存在的。因此如果想要解决这个问题,除了按照前文中的方法进行修正以外,还可以在 systemd 的 unit 里面使用 Environment=
参数手动指定要使用的环境变量。
从 portable 的角度来说,这两个解决方案应该使用前者,因为后者和代码的实现有很强的耦合性,而且仅仅加上这个环境变量也不能完全在 systemd 下模拟 shell 的运行环境。
最后放一下 nginx 的 vhost 的配置文件和 systemd service 的写法:
vhost.conf
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate </path/to/your/.crt>;
ssl_certificate_key <path/to/your/.key>;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on;
ssl_session_timeout 10m;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_buffer_size 1400;
add_header Strict-Transport-Security max-age=15768000;
ssl_stapling on;
ssl_stapling_verify on;
server_name <your_domain>;
access_log <your_log> combined;
index index.html index.htm index.php;
root <your_root_dir>;
if ($ssl_protocol = "") { return 301 https://$host$request_uri; }
location ~ /(\.user\.ini|\.ht|\.git|\.svn|\.project|LICENSE|README\.md) {
deny all;
}
location /.well-known {
allow all;
}
location / {
proxy_pass http://localhost:4000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
systemd.service
[Unit]
Description=Daemon to start Jekyll service
[Service]
Type=simple
WorkingDirectory=</path/to/your/site>
ExecStart=/usr/bin/bundle exec jekyll liveserve --livereload-max-delay 1 --trace
PIDFile=/var/run/jekyll.pid
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
注意要使用 liveserve 运行而非 serve,不然所有的链接都会是 localhost。