Notes on Hexo

记录使用Hexo的Next主题搭建博客的过程和遇到的问题,传送门hexo troubleshootingnext troubleshooting.

Terminal

  • XSHELL - The Industry's Most Powerful SSH Client

    XShell is a popular and straightforward network program designed to emulate a virtual terminal. While it's not beginner-friendly, experienced users find it easier to use. With this tool, you can use a specific computer to act as a terminal.

  • Remote - SSH: 好朋友推荐的VS Code插件, 安装后在Remote Explorer中添加并输入ssh userName@00.00.00.00即可.

    The Remote - SSH extension lets you use any remote machine with a SSH server as your development environment. This can greatly simplify development and troubleshooting in a wide variety of situations.

博客环境部署

Hexo

Hexo需要

  • 10.13以上的Node.js
  • Git

安装Node.js的16.14.2版本时时, 官方nodesource/distributions命令无法安装, 可以先去Downloads下载后按照Installation的步骤安装, 步骤写得非常好! 先装nvm, 官方安装方法使用了curl或者wget, 可能都会出现网络问题, 直接clone后bash isntall.sh即可, 随后nvm install 17.8.0, nvm ls查看当前所有可用版本, nvm use 17`即可.

1
2
3
4
nvm install node  # "node" is an alias for the latest version
nvm install 14.7.0 # or 16.3.0, 12.22.1, etc
nvm ls-remote # list available versions
nvm which 12.22 # get the path to the executable to where it was installed:

使用npm安装hexo时报npm无权限的错. 解决方法见How to fix npm throwing error without sudo, 这在nvm安装node后自然解决.

注意Documentation中为了去除npxecho 'PATH="$PATH:./node_modules/.bin"' >> ~/.profile语句要在npx hexo init blog后, cd blog后运行.

最后一步为hexo init <folder>.

Hexo的更新方式为更改package.json中的版本号后npm update. 另见How can I upgrade hexo? #4572, 提到npm的使用方法为npm i hexo@5.2.0.

常用命令

1
2
hexo version  # 查看版本
hexo clean && hexo generate

Nginx

Nginx, stylized as NGIИX, is a web server that can also be used as a reverse proxy, load balancer, mail proxy and HTTP cache. The software was created by Igor Sysoev and publicly released in 2004. Nginx is free and open-source software, released under the terms of the 2-clause BSD license.

这一节感谢好朋友的两次帮助.

安装方式为:

1
2
sudo apt update
sudo apt install nginx

不知道有啥区别的常用命令:

1
2
nginx -t  # 验证配置
nginx -s reload # 重启服务

Server Configuration

/etc/nginx/sites-enabled/default中加入

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80 default_server;
listen [::]:80 default_server;

server_name www.yuchen.xyz;

index index.html;

location / {
root /home/ubuntu/blog/public;
# try_files $uri $uri/ =404;
}
}

403 Forbidden

一般问题出在/etc/nginx/nginx.conf第一行, 默认为www-data, 最粗暴可以直接改成root, 见配置 Hexo 之后, Nginx 报 403.

SSL Certificate

An SSL certificate is a digital certificate that authenticates a website's identity and enables an encrypted connection. SSL stands for Secure Sockets Layer, a security protocol that creates an encrypted link between a web server and a web browser.

What is an SSL certificate – Definition and Explanation

现在www.yuchen.xyz可以正常访问了, 但https://www.yuchen.xyz还不行, 需要部署SSL证书. 我的证书在阿里云, 但要部署在腾讯云上, 遇到唯一不同在于我从阿里云下的证书只有pemkey, 然后在腾讯云/etc/nginx/sites-enabled/default配置时要输入并放入crt文件, 我把pem输入并放入就可以了, 其他都一样, 没什么大问题. 腾讯云的Nginx 服务器 SSL 证书安装部署文档详细介绍了证书文件的含义, 以及配置过程, 可以在腾讯云的SSL 证书证书监控 SSLPod中分别查看证书和监控证书.

open() "/run/nginx.pid" failed (13: Permission denied)

不知道为什么访问网页时候显示SSL证书不受信任, 进服务器后nginx -t报错如下:

1
2
3
4
5
6
ubuntu@VM-4-5-ubuntu:/etc/nginx$ nginx -t
nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (13: Permission denied)
2022/06/11 20:48:23 [warn] 1608616#1608616: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:1
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
2022/06/11 20:48:23 [emerg] 1608616#1608616: open() "/run/nginx.pid" failed (13: Permission denied)
nginx: configuration file /etc/nginx/nginx.conf test failed

前面加sudo运行一下就好了, 见open() "/run/nginx.pid" failed (13: Permission denied), 运行结果如下:

1
2
3
ubuntu@VM-4-5-ubuntu:/etc/nginx$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

放图片与文件的位置

NexT为例, images文件夹为node_modules/hexo-theme-next/source/images, uploads文件夹为source/uploads.

备案

_config.next.yml内填写, 详见Site Beian Information.

1
2
3
4
5
6
7
footer:
beian:
enable: true
icp: 京ICP备 1234567890号-1
gongan_id: 1234567890
gongan_num: 京公网安备 1234567890号
gongan_icon_url: /images/beian.png

ICP

年代太久远, 忘了, 类似沪ICP备2020026897号-1

公安部

公安备案接入服务商如何填写?(网站接入信息)

个人网站网安备案时服务类型选什么: 服务类型不要选交互-个人博客, 选非交互-www服务否则会被打回.

访问量

LeanCloud

  • _post.md文档名称修改后访问会归零, 但有方法合并数据; 主页和帖子里都显示访客
  • LeanCloud (China)
  • theme-next/hexo-leancloud-counter-security

一通设置后hexo clean && hexo generate --deploy可能会报错

1
2
3
4
TypeError: Cannot read properties of undefined (reading 'log')
at postOperation (/home/ubuntu/blog/node_modules/hexo-leancloud-counter-security/index.js:73:10)
at /home/ubuntu/blog/node_modules/hexo-leancloud-counter-security/index.js:160:17
at processTicksAndRejections (node:internal/process/task_queues:96:

Hexo 静态博客升级指南中得知是hexo-leancloud-counter-security的bug, 我也懒得改文档, 按照官方给的TroubleShooting做了一次rm <blog directory>/source/leancloud.memo然后又deploy了两次就莫名其妙好了.

合并数据步骤:

  1. 进入LeanCloud, 点击Data Storage → Data → Counter, 下载成.csv, 然后删光历史的记录 (一定要删光, 否则会生成一条url类似/2021/12/21/2021-12-21-note-on-gnn/的数据, 而其原为/2021/12/21/note-on-gnn/).
  2. rm source/leancloud.memohexo clean && hexo g -d (否则阅读数据不会自动生成), 如果出现上述错误甚至是Too many request的报错就再执行hexo clean && hexo g -d, 执行到没错为止.
  3. 进入LeanCloud, 发现数据生成好了, 把访问数修改为记住的历史总访客数.
  4. 进入博客,发现访问数又回来啦:happy:.

不蒜子

只在帖子页显示访问量; 主页总浏览量换服务器后还能自动继承, 省事~

Emoji

  • Next文档中指向的next-theme/hexo-filter-emoji, 不知道为啥不行 (仅把:smile:转成了一个貌似是html表情字符串的东西), 使用crimx/hexo-filter-github-emojis即可, 安装方式为npm install hexo-filter-github-emojis --save, 然后修改_config.yml:

    1
    2
    3
    4
    5
    6
    githubEmojis:
    enable: true
    className: github-emoji
    inject: true
    styles:
    customEmojis:
  • hexojs/hexo-renderer-markdown-it中的markdown-it-emoji解决问题. 然而没怎么看文档, 上来就遇到ToC的问题, 不用了不用了😅.

显示数学公式

Next官方文档中提到了两种渲染引擎:MathJax和KaTeX,实测KeTeX效果不好,所以推荐使用MathJax。首先要安装Pandoc,此过程中可能会遇到问题。

Pandoc是由John MacFarlane 页面存档备份,存于互联网档案馆开发的标记语言转换工具,可实现不同标记语言间的格式转换,堪称该领域中的“瑞士军刀”。Pandoc使用Haskell语言编写,以命令行形式实现与用户的交互,可支持多种操作系统;Pandoc采用GNU GPL授权协议发布,属于自由软件。

  1. 在theme config文件中启用MathJax
1
2
3
4
math:
...
mathjax:
enable: true
  1. 卸载hexo-renderer-marked.
1
$ npm un hexo-renderer-marked
  1. 安装pandoc和hexo-renderer-pandoc, 安装时可以使用cat /proc/version检查一下服务器架构, 我的是amd64
1
2
3
4
$ wget https://github.com/jgm/pandoc/releases/download/2.10.1/pandoc-2.10.1-1-amd64.deb
$ sudo dpkg -i pandoc-2.10.1-1-amd64.deb
$ npm install hexo-renderer-pandoc
$ npm install

References

  1. https://theme-next.js.org/docs/third-party-services/math-equations.html?highlight=latex
  2. https://github.com/theme-next/hexo-theme-next/issues/1454
  3. Highlight Bash/shell code in Markdown files

博客自动部署

Nginx + Hexo 实现博客备份、自动部署及全站https

  1. 创建build.sh

    1
    2
    3
    4
    5
    #!/bin/bash
    git pull | tee build.sh
    source /home/ubuntu/.nvm/nvm.sh;
    nvm use 17 | tee -a build.log
    npx hexo clean && npx hexo g -d | tee -a build.log

    bash运行试试, 可能会报个无关紧要的错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ubuntu@VM-4-5-ubuntu:~/blog$ bash build.sh 
    ' is not a git command. See 'git --help'.

    The most similar command is
    pull
    build.sh: line 3: $'\r': command not found
    Now using node v17.8.0 (npm v8.6.0)
    INFO Validating config
    ......

    问问了好朋友, 这是因为我的build.sh是Windows上写的, 有字符串的问题, 他说可以用VS Code右下角的LF切换, 不过我就Ubuntu上手打了一份, 问题不大.

  2. 创建hooks.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    [
    {
    "id": "blog",
    "execute-command": "/home/ubuntu/blog/build.sh",
    "command-working-directory": "/home/ubuntu/blog",
    "trigger-rule":
    {
    "match":
    {
    "type": "payload-hash-sha1",
    "secret": "password",
    "parameter":
    {
    "source": "header",
    "name": "X-Hub-Signature"
    }
    }
    }
    }
    ]

  3. 使webhook常驻后台.若修改hooks.json想重启, 见Hook not found. Error #246, 使用到的命令如下

    1
    2
    3
    4
    5
    6
    7
    cd webhook-linux-amd64/
    nohup ./webhook -hooks /home/ubuntu/blog/hooks.json -verbose > webhook.log 2>&1 & # 在/webhook-linux-amd64/目录下运行
    jobs # 查看nohup中正在运行的程序
    sudo netstat -peanut # Active Internet connections (servers and established)
    ps -eafww | grep 12345 # what process has it
    sudo kill 12345 # execute the kill
    sudo kill -9 12345 # a more forceful kill
  4. 配置在Github上配置Webhooks, 设置Payload URLhttp://www.yuchen.xyz:9000/hooks/blog, 设置Content typeapplication/json, 输入和hooks.json对应的密码.

  5. Windows上写个脚本push.bat

    1
    2
    3
    4
    git add -A
    git commit -m "updated %date:~0,10% %time%"
    git push
    pause
  6. 测试一下, 运行push.bat, 运行vim /home/ubuntu/webhook-linux-amd64/webhook.log观察服务器端的运行日志, 若出现报错Permission denied则给bash.sh加权限, 命令为chmod +x the_file_name, 见Permission denied when running .sh scripts.

Troubleshooting

文章目录 (Table of Contents, ToC) 点击后不跳转

提出于Chinese TOC cannot jump #1537解决于中文目录无法跳转,英文可以跳转 #1543. 我这个问题是hexo-renderer-markdown-it导致的.

缺少tomorrow.css

.config.next.yml中代码高亮设置tomorrow报错Error: ENOENT: no such file or directory, open '/home/ubuntu/blog/node_modules/highlight.js/styles/tomorrow.css', 查看后发现其位于../highlight.js/styles/base16/tomorrow.css中, 设置为/base16/tomorrow也不行, 按照blog - hexo NexT主题下配置和美化的方法设置.config.ymlhightlight中的auto_detecttrue也不行, 暂时用monokai吧.

jsDelivr被墙导致博客无法加载

首先了解一下Content delivery network (CDN)是什么:

A content delivery network, or content distribution network (CDN), is a geographically distributed network of proxy servers and their data centers. The goal is to provide high availability and performance by distributing the service spatially relative to end users. CDNs came into existence in the late 1990s as a means for alleviating the performance bottlenecks of the Internet as the Internet was starting to become a mission-critical medium for people and enterprises. Since then, CDNs have grown to serve a large portion of the Internet content today, including web objects (text, graphics and scripts), downloadable objects (media files, software, documents), applications (e-commerce, portals), live streaming media, on-demand streaming media, and social media sites. 一个内容交付网络,或内容分发网络CDN),是一个由代理服务器及其数据中心组成的地理分布网络。其目的是通过在空间上相对于终端用户分布服务,提供高可用性和性能。CDNs出现在20世纪90年代末,是缓解互联网性能瓶颈的一种手段,当时互联网开始成为人们和企业的关键任务媒介。从那时起,CDN已经发展到为今天互联网内容的很大一部分提供服务,包括网络对象(文本、图形和脚本)、可下载对象(媒体文件、软件、文档)、应用程序(电子商务门户网站)、直播流媒体媒体、按需流媒体和社交媒体网站。

再了解一下jsDelivr是什么:

JSDelivr (stylized as jsDelivr) is a free public CDN for open-source projects. It can serve web files directly from the npm registry and GitHub repositories without any configuration.

该问题的来龙去脉详见jsdelivr被墙,hexo-next切换为自定义CDN. 官方声明参见CDN error in China #18348. 若干备用链接见[Feature] Add CDN for Chinese mainland #424. 解决方法为在_config.next.yml使用自定义链接:

1
2
3
4
vendors:
internal: local
plugins: custom
custom_cdn_url: https://lib.baomitu.com/${cdnjs_name}/${version}/${cdnjs_file}

SSL证书间歇性错误

遇到域名随机无法访问的问题, 上SSL Server Test测试结果一切正常. 好朋友教我查看了下域名指向, 发现出问题时指向我的老服务器ip, 上域名解析把指向其的两条删除就好了.

hexo next type: 'unknown block tag: endnote'

hexo g后产生的报错, 这是由于Typora的格式与Hexo的不匹配导致到Next中的note组件没有被正确识别, 目前已知要注意的书写规则有:

  • 不要在list后紧跟代码块

Next Tag Plugins

Label

  • default yellow
1
{% label @default yellow %}
  • primary purple
1
{% label primary@primary purple%}
  • success green
1
{% label success@success green %}
  • info blue
1
{% label info@info blue %}
  • warning orange
1
{% label warning@warning orange %}
  • danger red
1
{% label danger@danger red %}

图片名称双重显示

按照 ![alt](path)的格式贴图会显示双重图片名, 正确的方式是 ![](path title), 也即将图片名称写入"title"中.

关于插入图片的具体方式可参考How to Add Image to Hexo Blog Post, 以下列举了alt, path, title三个参数的含义:

  • alt is alternate text, provides alternative information for the image if user cannot view it for some reason like slow connection, wrong path or user is using screen reader;
  • path is image file link, most important part;
  • title is tooltip text (optional), appears when cursor is floating over the image.

以下是三种贴图方式的对比, 分别使用空图和非空图展示效果:

  • ![alt](path) , 在方括号内填入名称alt, 而path后省略名称, 会产生双重alt名称, 不是理想的贴图方式.

alt


  • ![](path title) , 在方括号内省略名称, 而在path后加上带双引号的title, 是理想的贴图方式.



  • ![alt](path title) , 方括号和path后都加入图片名称, 也会产生双重名称, 不是理想的贴图方式.

alt

代码高亮

使用shell, bash等给命令行代码高亮, 另见Highlight Bash/shell code in Markdown files.

代码高亮种类和样式见highlight.js.

  • 为了让博客中贴出的命令行代码美观, 又保证复制后能直接运行, 需要对参数做换行对齐处理, 如下:

    1
    2
    3
    4
    5
    6
    bin\OpenPoseDemo.exe --image_dir my_test ^
    --write_json output/ ^
    --display 0 ^
    --render_pose 0 ^
    --face^
    --hand

    另见PowerShell command over multiple lines.

node & nodejs

node & nodejs have different version

Look up Symbols

领英中添加博客链接无图片

想要把博客主页https://www.yuchen.xyz/加到个人主页上, 但主页头像不能被爬取到, 该问题的根本原因是/blog/public/index.html中没有设置meta property, 详见Making Your Website Shareable on LinkedIn, 一个例子如下

1
<meta name="image" property="og:image" content="[Image URL here]">

设置后, 在Sharing Debugger上查看结果, 根据提示完善设置, 见hexo 加上 Open Graph. 在Post Inspector上检查并更新结果, 见LinkedIn not picking up og:image.

问题在于每次在服务器部署博客时, /blog/public/index.html都会重置.

目前的解决方式为: 手动完成meta property的设置, 在领英上检查并更新后, 将博客主页加到个人主页上, 期待领英爬取的图片可以长期保存在其服务器上, 直到博客中的图片更新后再重复以上步骤.

Comments - Gitalk

theme next-third party services-comments-gitalk

Gitalk comments plug-in tutorial

Hexo - Add Gitalk Comment

hexo cleanhexo generate时报错 Plugin load failed: %s hexo-filter-emoji

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ERROR {
err: SyntaxError: /root/blog/node_modules/hexo-filter-emoji/emojis.json: Unexpected end of JSON input
at parse (<anonymous>)
at Object.Module._extensions..json (internal/modules/cjs/loader.js:1171:22)
at Module.load (internal/modules/cjs/loader.js:985:32)
at Function.Module._load (internal/modules/cjs/loader.js:878:14)
at Module.require (internal/modules/cjs/loader.js:1025:19)
at req (/root/blog/node_modules/hexo/lib/hexo/index.js:292:23)
at /root/blog/node_modules/hexo-filter-emoji/index.js:16:5
at /root/blog/node_modules/hexo/lib/hexo/index.js:305:14
at tryCatcher (/root/blog/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/root/blog/node_modules/bluebird/js/release/promise.js:547:31)
at Promise._settlePromise (/root/blog/node_modules/bluebird/js/release/promise.js:604:18)
at Promise._settlePromise0 (/root/blog/node_modules/bluebird/js/release/promise.js:649:10)
at Promise._settlePromises (/root/blog/node_modules/bluebird/js/release/promise.js:729:18)
at _drainQueueStep (/root/blog/node_modules/bluebird/js/release/async.js:93:12)
at _drainQueue (/root/blog/node_modules/bluebird/js/release/async.js:86:9)
at Async._drainQueues (/root/blog/node_modules/bluebird/js/release/async.js:102:5)
at Immediate.Async.drainQueues [as _onImmediate] (/root/blog/node_modules/bluebird/js/release/async.js:15:14)
at processImmediate (internal/timers.js:456:21)
} Plugin load failed: %s hexo-filter-emoji

使用npm uninstall hexo-filter-emoji --save后莫名其妙就好了, 另见Hexo迁移报错, hexo 搭建/使用中遇到的问题总结

登录报错/?error=redirect_uri_mismatch& #162, 就是因为文章标题有空格或中文, 文件名删了空格改为英文就好了.

hexo gitalk 报错 redirect_uri_mismatch

GitHub The redirect_uri MUST match the registered callback URL for this application. #174

gitalk报错问题

Related Issues not found Please contact @someone to initialize the comment #87

gitalk Error: Request failed with status code 403

在授权gitalk后出现403错误 #429

Gitalk 评论登录 403 问题解决

themes/next/layout/_third-party/comments/gitalk.swig中添加行proxy : '{{ theme.gitalk.proxy }}',

themes/next/_config.yml中添加行proxy: https://netnr-proxy.cloudno.de/https://github.com/login/oauth/access_token

可用代理:

https://netnr-proxy.cloudno.de/https://github.com/login/oauth/access_token

https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token

图床迁移

Gitee屏蔽外链, jsDelivr被墙, 免费稳定图床基本不用想了, 这里写了个Python脚本简单处理图片的文本替换, 以应对未来可能平凡迁移图片的情况. 注意这里能替换的只有形如![alt](path "title")格式的图片, 暂时没有考虑html格式的图片. 参考了如下问题:

  • UnicodeDecodeError:'gbk' codec can't decode byte 0x80 in position 0 illegal multibyte sequence: 读取带中文字符的文件
  • Regex to parse image link in Markdown: 目标图片格式的正则表达式
  • Splitting on last delimiter in Python string?: 使用rpartition()以最后一个目标字符为界线分割字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import os
import re
def replace(m):
url, _, name = m.group(1).rpartition('/')
if url == old_url:
if m.group(2):
return '![]({}/{}{})'.format(new_url, name, m.group(2))
else:
return '![]({}/{})'.format(new_url, name)
else:
return m.group(0)

input_path = 'D:\mine\_my_github\myhexo-tencent\source\_posts'
output_path = 'output'
old_url = 'https://gitee.com/carlossshi/img/raw/master'
new_url = 'https://cdn.jsdelivr.net/gh/CarlossShi/img'
md_files = [_ for _ in os.listdir(path) if _.split('.')[-1] == 'md']
md_files, len(md_files)

for md_file in md_files:
with open(input_path + '/' + md_file, 'r', encoding='UTF-8') as file:
data = file.read()
with open(output_path + '/' + md_file, 'w', encoding='utf8') as f:
f.write(re.sub(r'!\[[^\]]*\]\((?P<filename>.*?)(?=\"|\))(?P<optionalpart>\".*\")?\)', replace, data))