起因是我想 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。