Arch Linux 星球

November 30, 2022

Alynx Zhou

YubiKey 和 GNOME 和智能卡登录

最近我终于决定买了一个 YubiKey 5C,说出来不怕各位笑话,我买这玩意最初的动机只是觉得每次开机和解锁输密码太麻烦(但是为什么我不觉得 sudo 输密码麻烦呢?)。这还和我之前处理了一个 openSUSE 的 PAM 问题有关,我发现 GDM 有好几种不同的 PAM 配置,除了平时用的 gdm-password 密码登录,还有 gdm-fingerprint 指纹登录和 gdm-smartcard 智能卡登录。我一开始是打算买个指纹传感器的,查了一下 fprintd 的文档,支持的型号并不多,而且在淘宝上问客服 USB Product ID 和 Vendor ID 显然得不到回答,就退而求其次买智能卡了,而搜索智能卡得到最多的结果就是 YubiKey,进入一个不了解的领域之前和大部分人选一样的一般不会错太多,于是就下手了。

话说回来智能卡登录,如果你搜索 YubiKey 相关的文章,绝大多数都会告诉你把 pam_u2f.so 加到需要密码登录的 PAM 配置里,比如 sudo 或者 gdm-password,但这显然不是我想要的,我要的方案不是替换密码登录,而是和密码登录平行的配置文件,我知道 GNOME 有已经写好的智能卡配置,但是任何地方都搜不到如何启用,设置里没有相关选项,连 Arch Wiki 给的方案都只是用 pam_u2f.so。Red Hat 的支持文档里倒是提到了智能卡登录,然而用的却是他们自己的某个工具配置的。显然这是个起夜级 feature,最好的办法也许是找个起夜级 Linux 的桌面工程师来问问,哦什么我自己就是起夜级 Linux 桌面工程师,那没事了。还要说的一件事是怎么想智能卡这东西都和安全相关,而我自己不是专业的安全行业人士,所以我不会尝试解释清楚和安全相关的一些名词,以及如果我哪里真的写错了,希望专业人士多多指点,我肯定改。

总之相信你自己因为你才是职业选手,我还是自己看看这东西怎么弄吧,毕竟用 Arch 再用 GNOME 同时还打算搞 GNOME 的智能卡登录的人没几个,所以 Wiki 没有倒也正常。首先肯定是看 /etc/pam.d/gdm-smartcard 这个文件,里面别的看起来都比较正常,只有一行看起来和智能卡有关系:

auth       required                    pam_pkcs11.so        wait_for_card card_only

线索是有了,看来我需要这个 pam_pkcs11.so,虽然我也不知道这是什么,先搜一下哪个包有这个文件比较好。pacman -F pam_pkcs11.so 竟然没有返回任何结果,我确定不是我的 pacman 数据库没更新,那只能去浏览器里搜索了,最后我搜到了 https://github.com/OpenSC/pam_pkcs11,虽然我也不知道 PKCS#11 是个什么玩意,但反正它是个 PAM 模块,既然不在官方仓库里,那大概率 AUR 里有人打包了,于是直接 paru pam_pksc11 就装了一个上来。

但装是装好了,也看不出来这玩意和 YubiKey 有什么联系,我大概是搜索了 PKCS YubiKey 然后搜到了 YubiKey 给的文档 Using PIV for SSH through PKCS #11,好吧虽然我不是要用来 SSH 但是多半也有点用。看下来反正这个东西和 YubiKey 的 PIV 功能有关,我把 PIV 相关的文档都看了一遍,结果是云里雾里,相当没有头绪。一大堆文档告诉你各种各样的需求要做什么,但是几乎没怎么说这都是什么,于是恰好我的需求不在列表里我就不知道怎么办了。我又回头去看 pam_pkcs11 的文档,它写了一长串的东西,我反复看了几遍之后发现只要看 第 11 节的 HOWTO 部分 就可以了。虽然我也不太清楚它都在说什么,但是至少这里告诉我说需要一个 root CA certificate,但我是个人使用哪来的这玩意,再回头看 YubiKey 的那篇文档里面恰好提到了什么 self-signed certificate,我拿这个试一试,结果成功了。为了方便参考,下面我就不讲我是怎么倒推这些奇怪的需求的了,而是顺序讲一下都需要配置什么。

首先如果你像我一样刚买了一个 YubiKey 打算利用它的 PIV 功能,那你得先初始化它,也就是改掉默认的 PIN,PUK 和管理密钥,这个可以通过官方的 YubiKey Manager 软件来操作,有 Qt 写的 GUI 版和命令行版本:

# pacman -S yubikey-manager yubikey-manager-qt

我推荐使用命令行版本操作,因为那个 GUI 经常转圈转半天或者点了没反应:

% ykman piv access change-pin
% ykman piv access change-puk
% ykman piv access change-management-key --generate --protect --touch

默认 PIN 是 123456,默认 PUK 是 12345678,而管理密钥是个特别长的一串,用 --generate 可以让 ykman 给你生成一个,--protect 则是把这个直接存到 YubiKey 里面并用 PIN 保护,--touch 则是说每次要管理密钥的时候需要你摸一下。我也不是很懂,也许写进去以后需要的时候就不用自己背这玩意而是输 PIN 就行了吧,反正建议看官方文档 Device setupykman--help

我的建议是不要看太多官方文档,因为它一会告诉你用 yubico-piv-tool 创建密钥,一会告诉你说可以用 openssl 创建密钥,一会又告诉你可以用 pkcs11-tool 搭配 libykcs11.so 创建密钥,算了吧,头都看晕了,我的测试是用 yubico-piv-tool 就可以了。

% paru yubico-piv-tool

9a 这个槽创建一个 key 并把它的公钥写出来,为什么是 9a 好像因为这是第一个槽来着,自己去查官方文档吧,也可以写到别的槽里面:

% yubico-piv-tool -s 9a -a verify-pin -a generate -o public.pem

需要先输入 PIN,然后灯闪的时候需要摸一下 YubiKey,它就开始生成了。

还要给这个密钥生成一个签名:

% yubico-piv-tool -s 9a -a verify-pin -a selfsign-certificate -S "/CN=Alynx Zhou/" -i public.pem -o cert.pem

注意 CN= 后面的部分,这里会被 pam_pkcs11.so 用来验证这个智能卡属于系统里面哪个用户,所以简单的话直接写你的登录用户名,当然你像我一样不想写用户名也是有办法对应的,同样要输入 PIN。

再把证书导回到同一个槽,我也不知道为什么,文档说了我照做了:

% yubico-piv-tool -s 9a -a verify-pin -a import-certificate -i cert.pem

还是要输入 PIN 然后灯闪的时候摸一下。

到这里 YubiKey 的配置就结束了。

要在系统上使用智能卡验证需要安装系统上和智能卡交互的软件包:

# pacman -S ccid opensc pcsclite
% paru pam_pkcs11

启动一个相关的 daemon,或者启动 socket 也行,需要的时候它就自己起来了:

# systemctl enable --now pcscd.socket

如果我没漏掉什么乱七八糟的,就可以配置 PAM 模块了,它有一个配置目录叫 /etc/pam_pksc11,首先你要把上面生成的证书放到 /etc/pam_pkcs11/cacerts

# cd /pam/pkcs11/cacerts
# cp PATH_TO_YOUT_CERT/cert.pem ./

你要在同一个目录下面运行一个什么什么 hash 命令生成一个 hash:

# pkcs11_make_hash_link

接下来你要去搞它的配置文件,先复制一个样本过来:

# cp /usr/share/doc/pam_pkcs11/pam_pkcs11.conf.example /etc/pam_pkcs11/pam_pkcs11.conf

好像其实也没什么需要改的。文档说默认的配置用的是 OpenSC 的 PKCS#11 库,虽然 YubiKey 的文档一直跟你说什么 libykcs11.so,我的测试结果是不用理它,通用的接口就够了,以及这个 libykcs11.so 是属于 yubico-piv-tool 这个包的。

假如你刚才 CN= 后面写的不是你的用户名,那你需要一些配置告诉 pam_pkcs11.so 你这个证书对应的哪个用户,这一步在它的配置文件里叫 mapper。默认启用了一些 mapper 比如 pwent,这个就是把 CN= 后面的内容和 /etc/passwd 里面的用户名做匹配,但是如果你像我一样写的是全名,那就需要另一个默认启用的模块叫 subject。至于 subject 是什么需要运行下面这个命令:

% pkcs11_inspect

它会输出各种 mapper 对应的 data,比如 pwent 输出的就是 Alynx Zhousubject 输出的则是 /CN=Alynx Zhou。我们需要复制一个 subject_mapping 配置文件的样本过来:

# cp /usr/share/doc/pam_pkcs11/subject_mapping.example /etc/pam_pkcs11/subject_mapping

在这个文件后面加一行:

/CN=Alynx Zhou -> alynx

我的用户名是 alynx,你可以换成你自己的。

到这一步 pam_pkcs11.so 这个模块已经可以通过智能卡验证你的身份了,但是如果你火急火燎兴高采烈的重启了系统,GDM 还是会和你要密码。原因其实很简单,虽然现在 /etc/pam.d/gdm-smartcard 已经可用了,但 GDM 只有在检测到智能卡之后才会调用这个文件尝试智能卡登录,很显然它没检测到智能卡。

这里就比较难搞清楚了,我智能卡插的好好的,上面各种程序都能用,为什么你检测不到?我尝试用什么 GDM YubiKey 之类的关键词搜索了半天,也没人告诉我 GDM 到底怎么检测智能卡的。没有办法还是读代码吧,GNOME Shell js/gdm/util.js 里面的逻辑是通过 D-Bus 的 org.gnome.SettingsDaemon.Smartcard 获取智能卡信息,那我打开 D-Feet 从 Session Bus 里面找到这个,直接运行 org.gnome.SettingsDaemon.Smartcard.ManagerGetInsertedTokens,什么都没有。

根据 D-Bus 的信息,很显然这个接口是 gnome-settings-daemonsmartcard 插件提供的,我大概是搜索了什么 gsd-smartcard PKCS#11 的关键字之后找到了 https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/merge_requests/208,其实我一开始也没太看懂这是什么意思,但得到一些有用的信息:

  • gsd-smartcard 用了什么 NSS API 获取智能卡设备。
  • 这玩意要一个什么 system shared certificate NSS database。
  • 除了 Red Hat 家那一套好像没什么别的发行版弄这个。

这一路下来乱七八糟的名词已经够多的了现在又多了一个什么 NSS 而且只有 Red Hat 才配置了 system shared certificate NSS database,但不管怎么样我是职业选手我不能轻言放弃,还好 Arch Wiki 有这么一个页面 Network Security Services,但这不是管证书的吗,和智能卡设备有什么关系啊。这时候我又翻开了 Arch Wiki 关于智能卡的页面 Smartcards,里面讲了在 Chromium 里面加载智能卡需要在 NSS 数据库里面加一个模块(什么乱七八糟的),不过它操作的都是用户的家目录下面的数据库,这显然不是 system shared certificate NSS database。然后如果手工执行 /usr/lib/gsd-smartcard -v,会发现这玩意尝试读取 /etc/pki/nssdb 获取什么智能卡驱动列表,我系统里面根本没这个目录。算了,既然是 Red Hat 搞的东西,我看看他们怎么写的。正好我有个 Fedora 的虚拟机,打开虚拟机一看还真有这个目录,那就运行下面命令看看:

% modutil -dbdir /etc/pki/nssdb -list

结果里面除了默认项还真有个叫 p11-kit-proxy 的玩意,我又回去看了一眼那个 Merge Request,现在我完全明白了,不知道为什么 NSS 这玩意会记录一个读取智能卡的驱动列表,然后 gsd-smartcard 是通过 NSS 获取到智能卡的驱动列表之后再尝试查询智能卡,实际上现在没什么人用 NSS 这个功能了,你这还得往系统的 NSS 数据库里面写东西,除了红帽子家都没人搞这个了,就算有用 NSS 读的(比如浏览器)也是读用户的 NSS 数据库。别的用智能卡的都直接用 p11-kit 去读智能卡,所以这个 Merge Request 也改成直接用 p11-kit 读了。不知道为什么这个 Merge Request 没能合并。再多说一句,就算是 Red Hat 的系统 NSS 数据库,现在也不直接写智能卡的驱动了,而也是通过 p11-kit,所以刚才在 Fedora 的数据库里只看到 p11-kit-proxy 这一个驱动……

既然这样我们也在这个数据库里写一个 p11-kit-proxy,根据 Arch Wiki 的智能卡页面,如果你要通过 p11-kit 操作 OpenSC 的驱动(这都什么乱七八糟的),那可能需要安装一个 AUR 包来保证它被加载(实际上就是个文件而已):

% paru opensc-p11-kit-module

创建数据库目录并往数据库里写 p11-kit-proxy

# mkdir /etc/pki/nssdb
# modutil -dbdir sql:/etc/pki/nssdb -add "p11-kit-proxy" -libfile p11-kit-proxy.so

如果你和我一样又心急火燎的重启了,就会发现还是没用。这不科学啊,Fedora 的数据库里也是这么写的,看一眼 D-Bus 为什么还是没有智能卡。

实际上最后我发现只差一点点,Fedora 给这个目录下文件的权限是 -rw-r--r--,而我这边创建好的是 -rw------gsd-smartcard 是以 session 用户运行的当然读不了。所以改一下权限就可以了。

# chmod 0644 /etc/pki/nssdb/*

接下来插着 YubiKey 重启,GDM 启动的 gsd-smartcard 就能读到系统的 NSS 数据库,检测到智能卡,于是调用 /etc/pam.d/gdm-smartcard,直接让你输入用户名,输入之后会提示你输入智能卡的 PIN,然后 pam_pkcs11.so 进行验证,就可以登录了。锁屏之后也只要输入智能卡的 PIN 就可以解锁。

按理说如果给 pam_pkcs11.so 发一个空白的用户名,它会根据智能卡返回用户名的,不知道为什么我在 GDM 用不了,一定要开机手动输入,有空我看看代码也许可以修改一下。 我也不知道为什么一定要在 GDM 启动之前插入卡才可以,显示用户列表之后再插入卡我这里没反应。

更新(2022-11-30):花了我半天时间研究 GDM 和 PAM,问题不在 GDM,而是因为 Arch Linux 的 gdm-smartcard 首先调用了 pam_shells 检查用户是否有合法的 shell,遇到空用户名它第一个失败了,于是我提交了 一个 MR,把 pam_shells 挪到 pam_pkcs11 下面,这样它会检查自动返回的用户名。(虽然这些 PAM 配置文件是发行版自己写的但是大家都提交到 GNOME 那边了,我只改了 Arch 的因为我在用,别的发行版的用户先偷着乐吧。)

如果你想用智能卡解锁的话,一定得是用智能卡登录才可以,它会检查当前的卡是不是登录所用的那张卡,不是的话就只能密码解锁了。折腾这一套花了我一整天时间,因为资料实在是太少了,根本不知道它是怎么工作的。

以及最后我还发现一篇文章,里面的内容也是讲这个 NSS 数据库的解决方案的,也许我早看见这个就不会这么麻烦了: Fixing NSS and p11-kit in Fedora (and beyond)

by Alynx Zhou (alynx.zhou@gmail.com) at November 30, 2022 01:08 PM

November 25, 2022

Alynx Zhou

DaVinci Resolve 在 Linux 下的输入法支持

令人出乎意料,我竟然是 DaVinci Resolve(后面都简称达芬奇了)的付费用户。虽然它不是开源软件,但是有很好的 Linux 支持,使用体验和功能都是同类中的佼佼者,而且收费也相当合理。我选择付费一个原因是你支持我,我就支持你,这其实和我支持 Steam 和 Valve 的理由差不多。另一个原因是众所周知的由于什么所谓系统专利许可证的原因达芬奇 Linux 版本不能解码 H264 和 H265 这两种常见的视频编码,只能使用 NVIDIA 显卡的 NVENC 和 NVDEC 来处理,而达芬奇将显卡加速功能作为收费的卖点。于是我就这样半自愿的上了贼船。

当然排除掉解码问题之后还有另一个比较难受的地方,就是达芬奇 Linux 版没有输入法支持,于是完全没办法输入中文。我猜测不像是某些故意恶心人的企业对 Linux 不友好,而单纯是因为英语开发者没有“输入法”这种概念。毕竟达芬奇的图形界面是基于 Qt 的,而 Qt 直接有现成的输入法支持,构建的时候打开开关就可以了嘛。为此我甚至专门跑到 BlackMagic Design 的用户论坛发了个帖子(https://forum.blackmagicdesign.com/viewtopic.php?f=33&t=150886),作为付费用户,我给你钱,你就得给我办事,就是这么硬气。显然某位员工看到了我的帖子并把它移动到了 Feature Requests 版面,然后就没有然后了。闭源拖拉机总是这样,我看到了,但我懒得改,你给我等着吧。我倒不是说开源拖拉机的维护者都比较勤快,但是至少代码放在那里,说不定用户自己就给你改了送到你面前了,一般再懒的维护者都乐意接受。谁叫我没找到和达芬奇一样好用的开源视频剪辑软件呢。

不过从它用的 Qt 这一点上来看,应该是有什么办法可以 hack 一下让它支持输入法的。虽然我不是很熟悉 Qt,但是 Fcitx 的开发者 @csslayer 给了我一个方案,他之前写了一篇博客是关于给 Mathematica 添加输入法支持的(https://www.csslayer.info/wordpress/fcitx-dev/a-case-study-how-to-compile-a-fcitx-platforminputcontext-plugin-for-a-proprietary-software-that-uses-qt-5/),他觉得达芬奇也可以如法炮制,于是我阅读了一下,简单地概括就是首先查出来软件用了什么版本的 Qt,然后下载对应的源码,因为输入法支持属于 Qt 的某种插件,所以只要构建插件的时候链接到软件自带的 Qt,再把得到的插件复制到软件的 Qt 目录就可以了。一般来说软件就算修改了自带的 Qt,也不会修改有关插件的部分,所以我打算如法炮制一下。

首先是查看达芬奇自带的 Qt 的版本,这个非常简单:

% strings /opt/resolve/libs/libQt5Core.so.5 | rg 'Qt 5'
Qt 5.15.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by Clang 9.0.1 )
This is the QtCore library version Qt 5.15.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by Clang 9.0.1 )
If that is not possible, in Qt 5 you must at least reimplement

到这里应该就是去下载 Qt 5.15.2 版本的源码了,不过我突发奇想看了一眼系统安装的 Qt 版本:

% pacman -Qi qt5-base | rg Version
Version         : 5.15.7+kde+r176-1

一般来说主次版本号不变的话不会有什么不兼容的改动,会不会我直接把系统的 .so 文件复制过去就可以用了呢?Qt 5 的 ibus 支持已经是 Qt 本身代码库的一部分了,安装到系统的路径是 /usr/lib/qt/plugins/platforminputcontexts/libibusplatforminputcontextplugin.so,我尝试直接把它链接过去:

% sudo mkdir /opt/resolve/libs/plugins/platforminputcontexts
% sudo ln -s /usr/lib/qt/plugins/platforminputcontexts/libibusplatforminputcontextplugin.so /opt/resolve/libs/plugins/platforminputcontexts

然后就没有然后了,我启动达芬奇之后 ibus 就直接工作了。没想到他们虽然不太了解 Linux 输入法,Qt 版本跟的倒是还挺新的。

just works

对于 Fcitx5 用户的话,首先要注意 Qt 5 的 Fcitx5 支持并不在 Qt 的代码库里,所以你需要安装 fcitx5-qt。不过文件路径的话都是一样的,只要把文件名里的 ibus 改成 fcitx5 就可以了。如果直接链接不能用,需要按照老 K 博客里的办法自己编译的话,需要下载单独的 fcitx5-qt 代码库。当然从根源上解决问题的话还是希望大家去论坛回复我的帖子,让 BlackMagic Design 开启构建开关,就不需要用奇怪的办法 hack 了。

by Alynx Zhou (alynx.zhou@gmail.com) at November 25, 2022 09:50 AM

November 24, 2022

Alynx Zhou

Fontconfig 和 Noto Color Emoji 和抗锯齿

我一直用的是以前积攒的一份 fontconfig 配置,主要功能就是设置对于无衬线字体优先用 Roboto 显示英文字体,然后回退到 Noto Sans CJK SC 显示中文字体,因为 Roboto 比 Noto 的英文字好看,以及对等宽字体优先用 Monaco。虽然大部分都是网上抄来的,我自己并不太懂,但是这个配置一直工作的还可以。直到我开启了 RIME 内置的 emoji 输入法,发现 emoji 显示成了空白。

一开始没觉得是什么很难解决的问题,在字体列表末尾加上 Noto Color Emoji 不就行了?不过事情要是真的这么简单,也就没必要写个博客记下来了。忘记当时怎么查的了,总之是搜到 一个 firefox 的 bug,提 bug 的人表示自己一直是开着 hintfull 和 antialias 的,关掉这个 Noto Color Emoji 才能显示。于是我看了一眼我的配置,也有这么一段:

<!-- 针对所有字体的默认设置,力求显示效果最好。 -->
<match target="font">
  <!-- 禁用内嵌点阵。 -->
  <edit name="embeddedbitmap" mode="assign">
    <bool>false</bool>
  </edit>
  <!-- 禁用合成粗体。 -->
  <edit name="embolden" mode="assign">
    <bool>false</bool>
  </edit>
  <!-- 设置显示器为 RGB 排列。 -->
  <edit name="rgba" mode="assign">
    <const>rgb</const>
  </edit>
  <!-- 开启轻度微调。 -->
  <edit name="hinting" mode="assign">
    <bool>true</bool>
  </edit>
  <edit name="autohint" mode="assign">
    <bool>true</bool>
  </edit>
  <edit name="hintstyle" mode="assign">
    <const>hintslight</const>
  </edit>
  <!-- 开启抗锯齿。 -->
  <edit name="antialias" mode="assign">
    <bool>true</bool>
  </edit>
</match>

那我觉得针对 Noto Color Emoji 关掉这几个选项就行了,正好这个 bug 下面就有人提供了配置:

<!-- Noto Color Emoji 不支持开启抗锯齿和微调,所以在全局开启之后还得给它关掉。 -->
<match target="font">
  <test name="family" qual="any">
    <string>Noto Color Emoji</string>
  </test>
  <edit name="scalable" mode="assign">
    <bool>true</bool>
  </edit>
  <edit name="embeddedbitmap" mode="assign">
    <bool>true</bool>
  </edit>
  <edit name="hinting" mode="assign">
    <bool>false</bool>
  </edit>
  <edit name="antialias" mode="assign">
    <bool>false</bool>
  </edit>
</match>

重新登录,输入法里面的 emoji 都显示出来了,我以为事情到这里就结束了,没想到噩梦才刚刚开始……

某一天用 firefox 打开 twitter,发现里面字体都是毛毛糙糙的,我觉得很离奇,明明我只是对 Noto Color Emoji 关了抗锯齿,为什么 twitter 的字体也受到影响了?而且其他页面的字体都是正常的,后来我又打开过一个微软的文档页面,也出现了类似的毛病。我研究了一下发现这两个页面都用了自定义的 webfont。可是 webfont 和 Noto Color Emoji 有什么关系?人类摸不着头发。

我查了很多博客文章,不过它们都关注在正常的字体顺序上,还没有人研究 webfont 有什么问题。于是我只能自己研究,不过我不能保证我对 fontconfig 的理解完全正确,这东西看起来非常复杂,不过我还是大致搞清楚了一些事情。引用我看到的 一篇英文博客 的标题:

当我凝视着 fontconfig 时,fontconfig 也在凝视着我。

首先我找到了这个叫 fontconfig 几个常见的坑 的文章,里面信息量挺大的,不过说实话没有解决掉我的问题——我明明只对匹配到 Noto Color Emoji 时候关掉了抗锯齿,为什么 webfont 也受到影响了呢?这篇文章主要介绍怎么调整字体列表顺序,不过说实话我没有遇到那么多障碍,简单的写法就工作了。同一个作者还写了 Color Emoji in openSUSE 的文章,不过总感觉是交待到一半,翻了文章列表没有后续了。而且这篇文章关心的也是字体 fallback 时候某些字符选到错误的字体的问题,和我这个不太相关。

然后还有 Linux fontconfig 的字体匹配 这篇文章,它介绍了浏览器是怎么和 fontconfig 合作匹配字体的,但是对于 webfont 也是没有提到。包括它的后续文章 用 fontconfig 治理 Linux 中的字体 也没提到和 Noto Color Emoji 相关的调整(可能和它用的 Twemoji 有关系)。

然后我尝试按照文章里的办法对 firefox 进行一个 fontconfig 的调试,FC_DEBUG=4 firefox --new-instance --private-window https://twitter.com/ 直接吐了 12 万行输出,我实在是不知道从哪里看了。翻了一下午胡乱修改了好几次配置也没找出来哪里有问题。就在我眼花的时候偶然看到一条有用的,fontconfig 吐出来一个匹配结果,family 是 Roboto, Noto Sans CJK SC, Noto Color Emoji, sans-serif… 等等一长串,style 是 。这个就很有趣了,因为我看 firefox 里面,twitter 用的 webfont 叫 TwitterChirp,它的 style 也是这么个 (我怀疑是个 bug),那这条可能就是相关的了。然后我发现这个匹配结果下面 antialias 是 false,但理论上来说这条又不是最先匹配到 Noto Color Emoji,怎么会应用 Noto Color Emoji 的设置呢?我猜测是因为默认 test 语句是 qual="any" 含义似乎是只要匹配到的有一个满足就应用,正常匹配一个字体应该是不会返回这么一长串 family 的(我用 fc-match 一般只返回一个符合条件的 family),虽然我不知道为什么 webfont 会返回这么一长串结果,总之我改成 qual="all" 也就是必须所有结果都是 Noto Color Emoji 时候才应用就好了对吧!然后重新登录,emoji 能显示,我以为已经胜利了!结果事情证明是半场开香槟……

当我以为完全没问题,正打算用 atom 整理一下这份配置的时候,突然发现不对劲了……Atom里面的中文字体失去了抗锯齿……你可以想像我当时心情有多绝望。具体为什么还是同样的我不知道,不过我猜测恰好是和 Linux fontconfig 的字体匹配 里面提到的 chromium 查询字体的奇葩逻辑有关系……可能只是它某次查询刚好漏掉了 Noto Sans CJK SC 匹配到了 Noto Color Emoji 然后就给了个 antialias false,然后再通过什么奇怪的 UI 字体查询找出了中文字体……反正总之我的心好累,我实在不知道该怎么解决了,我只想用个 emoji,我甚至还给 Noto Color Emoji 提了请求支持抗锯齿的 issue……然后我决定要不换成 Twemoji 算了。

Twemoji 倒也不是开箱即用,它不能禁用内嵌点阵字体,不过好歹这个不像抗锯齿那么要命,就算影响了其他字体大概也许能够忽略吧……于是我改成了这个。不过这时候我突然灵光一闪有了新主意。

按照前面那几篇博客和 这一篇手册翻译 的讲解,fontconfig match 的 target 分为三个阶段,第一是 scan,扫描字体文件,构建一个集合。然后是 pattern,按照规则构建一个字体列表。最后是 font,意味着已经挑出了需要的字体列表。所以调整字体回退顺序一般都放在 pattern 阶段。而网上大部分都把对于 freetype2 微调的选项放在了 font 阶段,不过我想放在 scan 阶段是不是也可以?这样扫描到 Noto Color Emoji 的时候就对它设置选项,也就不存在 family 列表可能有很多项的问题了。测试了一下发现基本一切正常,于是就把所有 freetype2 微调选项移到了 scan 阶段。(我不是 fontconfig 的专家,这一段要是有错误还希望指正。)

下面是我现在的配置,首先我配置文件放的位置是 /etc/fonts/local.conf,因为 /etc/fonts/fonts.conf 是发行版默认的设置,用来加载其他配置文件所以不能改,然后我想对所有用户都生效,所以没有放在我的家目录,Arch Linux 包含一个 /etc/fonts/conf.d/51-local.conf 文件,/etc/fonts/fonts.conf 会加载它,然后它再加载 /etc/fonts/local.conf

首先是 XML 开头必须要有的那些东西:

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>

然后接下来就是针对所有字体的 freetype2 微调选项,按照上面介绍的放在 scan 阶段:

  <!-- 针对所有字体的默认设置,力求显示效果最好。 -->
  <match target="scan">
    <!-- 禁用内嵌点阵。 -->
    <edit name="embeddedbitmap" mode="assign">
      <bool>false</bool>
    </edit>
    <!-- 禁用合成粗体。 -->
    <edit name="embolden" mode="assign">
      <bool>false</bool>
    </edit>
    <!-- 设置显示器为 RGB 排列。 -->
    <edit name="rgba" mode="assign">
      <const>rgb</const>
    </edit>
    <!-- 开启轻度微调。 -->
    <edit name="hinting" mode="assign">
      <bool>true</bool>
    </edit>
    <edit name="autohint" mode="assign">
      <bool>true</bool>
    </edit>
    <edit name="hintstyle" mode="assign">
      <const>hintslight</const>
    </edit>
    <!-- 开启抗锯齿。 -->
    <edit name="antialias" mode="assign">
      <bool>true</bool>
    </edit>
  </match>

内嵌点阵的效果通常没有矢量绘制的效果好,合成粗体也只是个临时方案,微调什么的见仁见智了就。

然后对于 Twemoji 要打开内嵌点阵:

  <!-- 当然另一个简单方案是换成 Twemoji,不过它不能关这个。 -->
  <match target="scan">
    <test name="family" qual="any">
      <string>Twemoji</string>
    </test>
    <edit name="embeddedbitmap" mode="assign">
      <bool>true</bool>
    </edit>
  </match>

对于 Noto Color Emoji 也是打开内嵌点阵关闭微调和抗锯齿:

  <!-- Noto Color Emoji 不支持开启抗锯齿和微调,所以在全局开启之后还得给它关掉。 -->
  <match target="scan">
    <test name="family" qual="any">
      <string>Noto Color Emoji</string>
    </test>
    <edit name="scalable" mode="assign">
      <bool>true</bool>
    </edit>
    <edit name="embeddedbitmap" mode="assign">
      <bool>true</bool>
    </edit>
    <edit name="hinting" mode="assign">
      <bool>false</bool>
    </edit>
    <edit name="antialias" mode="assign">
      <bool>false</bool>
    </edit>
  </match>

我个人还有一个需求,Monaco 作为一个等宽字体竟然支持连字,导致某次我看别人的代码时候 fi 连在一起让我以为他没有对齐缩进,结果人家说是我的问题,十分尴尬,于是我在这里关掉了它。不过有些程序比如 firefox 是不支持这个的,解决方法是用 FontForge 编辑字体文件删掉连字相关的数据再导出……

  <!-- 我真不理解一个等宽字体要连字功能干嘛?故意变得不等宽? -->
  <match target="scan">
    <test name="family" qual="any">
      <string>Monaco</string>
    </test>
    <edit name="fontfeatures" mode="append">
      <string>liga off</string>
      <string>dlig off</string>
    </edit>
  </match>

更新(2022-11-24):某些网站的前端脑子里不知道装的什么东西,比如简书的前端可能脑子里装的是苹果,他们把 -apple-system,BlinkMacSystemFont,"Apple Color Emoji" 排在 sans-serif 前边,于是你看到的数字可能是 Emoji 里面的。再比如 B 站直播首页所有的字体都是微软字体,连 sans-serif 都没有,你没看错,这个不合格的前端连最后要加 sans-serif 保证兼容性都不知道,而且还把 Microsoft Sans Serif 拼错了,如果我是他的老板,我真想一拳打在他头上告诉他这个世界不是只有微软字体,然后再把他开除掉。现在我需要加条规则让他匹配到我想要的字体(虽然这样在一些需要微软雅黑的 office 软件里面可能有问题,不过反正我的工作不需要用垃圾 office 哈哈哈哈哈哈哈哈哈哈),所以需要对这些弱智做一些特殊照顾。因为 Fontconfig 是按顺序处理的,所以如果你想把某个字体换成 sans-serif 之类的处理,就得在那之前进行替换。然后很多网站写的都是 Apple Color Emoji,我们这里自然是没有的,要换成我们默认的。

<!--
    有一点需要注意,sans-serif,serif,monospace, emoji 这些默认字体名并不是真正
    的字体,而是锚点,是用来进行后续的字体插入的。因此如果你想通过把一些字体映射
    成 sans-serif 来修正一些网站奇怪的逻辑的话,请务必写在处理 sans-serif 部分之
    前,这样才会被正常替换。
  -->
  <!--
    我建议这个世界新增一种物种叫苹果人,即打开脑壳之后没有脑子,装的全是苹果的
    人,显然简书的前端就属于此类(-apple-system,BlinkMacSystemFont,
    "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Segoe UI",
    "PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",
    Helvetica,Arial,sans-serif)。正常人怎么会把 Apple Color Emoji 放前面?
  -->
  <alias>
    <family>-apple-system</family>
    <prefer>
      <family>sans-serif</family>
    </prefer>
  </alias>

  <alias>
    <family>BlinkMacSystemFont</family>
    <prefer>
      <family>sans-serif</family>
    </prefer>
  </alias>

  <!--
    B 站直播首页的前端认为这个世界上只有微软字体(Arial,Microsoft YaHei,
    "Microsoft Sans Serif",Microsoft SanSerf,微软雅黑),
    所以我不得不开着这几个规则。如果我是他的老板,我就会开除掉这个不合格的前端。
  -->
  <alias>
    <family>Microsoft Sans Serif</family>
    <prefer>
      <family>sans-serif</family>
    </prefer>
  </alias>

  <!-- 我机器上可能真的有微软雅黑,而我真的不想看见它们,反正我也不做排版。 -->
  <alias>
    <family>Microsoft YaHei</family>
    <prefer>
      <family>Roboto</family>
      <family>Noto Sans CJK SC</family>
    </prefer>
  </alias>

  <!-- 替换 Apple Color Emoji 字体。 -->
  <alias>
    <family>Apple Color Emoji</family>
    <prefer>
      <family>emoji</family>
    </prefer>
  </alias>

  <!-- 替换 Noto Color Emoji 字体。 -->
  <!-- <alias> -->
  <!--   <family>Noto Color Emoji</family> -->
  <!--   <prefer> -->
  <!--     <family>emoji</family> -->
  <!--   </prefer> -->
  <!-- </alias> -->

  <!-- 这也是一个常见的默认字体,我的 UI 字体就是无衬线。 -->
  <alias>
    <family>system-ui</family>
    <prefer>
      <family>sans-serif</family>
    </prefer>
  </alias>

然后就是常见的默认字体代称(sans-serif,serif,monospace,emoji)回退列表了,我是按照 Roboto/Roboto Slab/Monaco -> Noto Sans/Serif (Mono) CJK SC -> Noto Color Emoji 的顺序回退的:

  <!-- 然后该配置我们喜欢的默认字体了。 -->
  <!-- 默认无衬线字体。 -->
  <alias>
    <family>sans</family>
    <prefer>
      <family>Roboto</family>
      <family>Noto Sans CJK SC</family>
      <family>emoji</family>
    </prefer>
  </alias>

  <alias>
    <family>sans-serif</family>
    <prefer>
      <family>Roboto</family>
      <family>Noto Sans CJK SC</family>
      <family>emoji</family>
    </prefer>
  </alias>

  <!-- 默认有衬线字体。 -->
  <alias>
    <family>serif</family>
    <prefer>
      <family>Roboto Slab</family>
      <family>Noto Serif CJK SC</family>
      <family>emoji</family>
    </prefer>
  </alias>

  <!-- 默认等宽字体。 -->
  <alias>
    <family>monospace</family>
    <prefer>
      <family>Monaco</family>
      <family>Noto Sans Mono CJK SC</family>
      <family>emoji</family>
    </prefer>
  </alias>

  <!-- 默认 emoji 字体。 -->
  <alias>
    <family>emoji</family>
    <prefer>
      <family>Noto Color Emoji</family>
    </prefer>
  </alias>

这里的写法和 <alias> 是等价的。设置完这个记得把所有软件的自定义字体设置里面无衬线设置为 sans-serif,有衬线设置为 serif,等宽设置为 monospace,这样才会遵守这里的回退顺序。

剩下还有一些冗长的用来在不同语言下选择不同的 Noto Sans CJK 变体的设置,因为 Noto Sans CJK 是一个多语种集合字体,然后中日韩对一些汉字有不同的变体,所以需要这一段,不过我就不贴在这里了,完整的文件可以在文章末尾下载。

最后收个尾:

</fontconfig>

完整的文件在这里:local.conf

😼

by Alynx Zhou (alynx.zhou@gmail.com) at November 24, 2022 04:39 AM

November 23, 2022

Alynx Zhou

GTK 和 libhandy 和 Arc-Dark 主题

黑夜让我选了黑色的主题,但是有些程序非要寻找光明?

我自认为不是个对应用程序外观有着病态一致性要求的人,我也从不介意一些个性化的程序选择自己的特殊样式。所以当 GTK 4 推荐的 libadwaita 不再支持传统的 GTK 主题的时候我也没什么反应:毕竟这玩意默认的样式看起来还挺好看的。但即使是我这样宽容的人,对于 GTK 3 那个熟的不能再熟的 Adwaita 主题也审美疲劳了,那个银色和棕色会让所有手机厂笑话的,即使有些手机厂的审美还不如这玩意。

我个人最喜欢的配色其实是 Atom 的 One Dark 和 One Light,但我没那个精力利用调色盘自己维护一份主题,所以我退而求其次选择了在观感上比较接近的 Arc 主题,这个主题其实是一个系列,我自己只在乎里面的两个变体:全亮色的 Arc 和全暗色的 Arc-Dark(似乎它自己 README 里面给的截图也有点问题)。

我自己是一个暗色模式爱好者,毕竟长时间面对屏幕,白底黑字实在是太刺眼了,相对而言,深蓝色做背景色浅灰色做前景色要好看很多。在很久很久以前混沌初开,Linux 桌面程序员还没有意识到需要有个全局的暗色/亮色开关的时候,设置主题非常简单粗暴,打开 GNOME Tweaks 把 GTK Theme 设置为 Arc-Dark,我就心满意足了。

可能是 libadwaita 不能更换主题导致很多反对的声音,并没有太多人谈论随之而来的全局暗色模式开关,但是某天我更新了系统之后发现设置里多了一个亮色/暗色选择,我觉得挺好,那我这里选暗色就行了嘛,果然所有用了 libadwaita 的程序都跟着变了亮暗,不过我用着用着就感觉不对劲了——怎么以前那些 GTK 3 的程序不用 Arc-Dark 而是用 Adwaita-dark 了,这和我想的不一样啊?然后我研究了一下,觉得更奇怪了,GTK 4 + libadwaita 的 GNOME Settings 用的是 libadwaita 的暗色版本(预期行为),GTK 3 的 GNOME Tweaks 用的是 GTK 3 的 Adwaita-dark(不对劲),但是同样是 GTK 3 的 GNOME Terminal 用的是我设置的 Arc-Dark(预期行为)。好家伙好家伙,我这一个桌面上三花聚顶了。

三花聚顶

总这么待着我觉得怪怪的,于是我研究了一下,如果我要是选亮色模式呢?现在 GNOME Settings 是 libadwaita 的亮色版本了,然后 GNOME Tweaks 和 GNOME Terminal 都是 Arc-Dark,虽然好像一致了,又好像有点不一致,这回从三花聚顶变成黑白通吃。总之我忍受了很久 GTK 4 程序 大部分 是白的而 GTK 3 程序是黑的,直到我再也受不了决定翻开代码看看这些人是怎么写的。

黑白通吃

为了能说明白,下面我就不从结果反推原因了,毕竟大家看到这里可能已经云里雾里,没必要和我再重复一遍破案过程了。

可能大部分人不是 GTK 开发者也不使用 GTK,对这玩意怎么调用主题存在一定的误区。实际上可以分为以下几类:

  1. 不使用 libhandy 的 GTK 3 程序(比如 GNOME Terminal)和不使用 libadwaita 的 GTK 4 程序(比如 Show Me The Key),这一类程序不考虑 GNOME Settings 里面的亮色/暗色开关(指的是 GSettings 里面 org.gnome.desktop.interfacecolor-scheme 选项),而只考虑 gtk-application-prefer-dark-theme,这个值属于 GtkSettings,需要编辑 ~/.config/gtk-4.0/settings.ini~/.config/gtk-3.0/settings.ini。以及是的你没看错,GTK 4 不一定非要用 libadwaita,实际上虽然这个库叫 libadwaita,但它和 GTK 3 那个叫做 Adwaita 的默认主题几乎没有关系,它是 GTK 3 的组件库 libhandy 的进化版本。GNOME 推荐使用这个以便让整个桌面有统一的风格,但是 GTK 4 仍然是个完整的 UI 库,程序开发者完全可以不使用 libadwaita。
  2. 使用 libadwaita 的 GTK 4 程序(比如 GNOME Settings),这一类程序不考虑 GNOME Tweaks 里面的 GTK Theme 选项(实际上是 GSettings 里面 org.gnome.desktop.interfacegtk-theme 选项),只使用 libadwaita 内置的配色,所以我们也完全不需要关心它,它永远按照设置里的开关工作。
  3. 使用 libhandy 的 GTK 3 程序(比如 GNOME Tweaks),这个就相当复杂了,libhandy 考虑了桌面环境的亮色/暗色主题切换,但也考虑了用户自定义的 GTK Theme,于是在这里它华丽的乱套了。
  4. 还有最后一类程序,它们出于特定需要自己给自己套了自定义的 CSS,所以你拿它一点办法也没有,直接忽略(比如 Show Me The Key 的悬浮窗口)。

看到这里一定有小黑子要怒吼了:“看看你们搞的乱七八糟的玩意!GNOME 真垃圾!老子就要刀耕火种就要当原始人,libadwaita 滚啊!”但是我建议用你那可怜的小脑袋瓜想一想,上面三条里面反而 libadwaita 是最符合预期的一个(亮色模式用亮色,暗色模式用暗色),所以我不会解决第二个,而是解决另外的两个。

首先从 libhandy 下手,相关的代码位于 https://gitlab.gnome.org/GNOME/libhandy/-/blob/main/src/hdy-style-manager.c#L286-L348,如果你可怜的小脑袋瓜也没耐心看看代码的话,那么我大发慈悲替你读了一遍。相关的逻辑大概是说首先覆盖掉当前程序的 gtk-application-prefer-dark-theme(别忘了 libhandy 程序也是 GTK 3 程序),这个值会被设置成 color-scheme 的值。然后获取当前系统的 GTK Theme,因为我们考虑到浅色和深色主题切换,所以主题名被分成基础名和种类名两部分,如果系统的主题以 -dark 结尾,那就去掉这个后缀,得到基础名,并设置为当前程序的主题。那问题难道出在 Arc-Dark 的结尾是 -Dark 而不是 -dark 吗?也不是,GTK 主题对于暗色和亮色的区分不在主题名上,而是在主题目录下面的两个文件,一个叫做 gtk.css,另一个叫做 gtk-dark.css,如果 color-schemeprefer-dark,libhandy 就会加载后者而非前者,这部分的代码在 https://gitlab.gnome.org/GNOME/libhandy/-/blob/main/src/hdy-style-manager.c#L106-L141。于是在系统设置为暗色模式的时候,libhandy 会去加载 Arc-Dark 的 gtk-dark.css,但 Arc-Dark 作为一个暗色变体,只有 gtk.css,所以加载失败,libhandy 回退到 Adwaitagtk-dark.css。而系统设置为亮色的时候,libhandy 会去加载 Arc-Dark 的 gtk.css,而作为一个暗色变体,这个文件实际写的是暗色配色,于是看起来正常了。(以及如果你 GTK Theme 设置为 Adwaita-dark 从这里你就会发现实际上加载的是 Adwaitagtk-dark.css,而不是 Adwaita-darkgtk.css,即使它们的配色是一样的。)

那么显然又有另一个问题,既然主题是靠内部的两个文件区分亮色和暗色的,为什么又会有 Adwaita-darkArc-Dark 这种名字里带暗色后缀的变体呢?并且还要在 libhandy 里面处理这个后缀,是不是多此一举?我们可以暂时先不考虑这个问题,而先简单解决第三条。从上面的分析可以得知为了能正常支持系统的亮色暗色切换,我们需要的是一个同时包含亮色暗色的主题,而不是一个只有暗色变体的主题,于是我们不能把 GTK Theme 设置为 Arc-Dark,而应该使用 Arc,但假如你在 GNOME Tweaks 里面设置好之后,你会发现仍然是黑白通吃:GNOME Settings 是 libadwaita 的暗色版本,GNOME Tweaks 是 Arc 的暗色版本,而 GNOME Terminal 和 Show Me The Key 却变成了亮的 Arc!

小黑子们不要笑得太早了

我知道有的小黑子要迫不及待开始炮轰 GNOME 了:“什么玩意,整来整去不还是整不好吗,不如来当原始人。”但问题其实就是原始人留下的。现在我们回头看第一条:不使用 libhandy 的 GTK 3 程序和不使用 libadwaita 的 GTK 4 程序,这一类程序不考虑 GNOME Settings 里面的亮色/暗色开关,而只考虑 gtk-application-prefer-dark-theme。所以这个奇怪的表现恰好验证了这一条,同时也解释了“既然主题是靠内部的两个文件区分亮色和暗色的,为什么又会有 Adwaita-darkArc-Dark 这种名字里带暗色后缀的变体”这个问题:因为在一开始的设计里并没有什么全局亮色/暗色开关,也就没有要求主题同时提供 gtk.cssgtk-dark.css,那么为了让用户可以自选亮色暗色,只有提供两个不同的主题来解决问题。这也就是在 AdwaitaArc 都提供了 gtk-dark.css 的情况下仍然存在 Adwaita-darkArc-Dark 的原因。然后在主题添加了 gtk-dark.css 之后,为了让 libhandy 的程序能够跟随系统开关切换亮色和暗色,就不能为了那些传统程序把 GTK Theme 设置为暗色变体的主题了,此时如果设置为同时包含两个文件的主题,默认这些程序会选择 gtk.css,也就会出现上面截图里的情况。解决这个的方案也不是很困难,gtk-application-prefer-dark-theme 就是为此添加的,支持它的 GTK 程序会按照这个选项来加载 gtk.cssgtk-dark.css。如果你像我一样平时主要用暗色模式,那就手动编辑 ~/.config/gtk-4.0/settings.ini 写入以下内容(GTK 3 的话就是 ~/.config/gtk-3.0/settings.ini):

[Settings]
gtk-application-prefer-dark-theme=1

你要是亮色爱好者,那就改成 0。这下倒是满足原始人的刀耕火种需求了哈,毕竟他们看起来也不想要系统的亮色/暗色开关的样子,不过说不定以后哪天系统的亮色/暗色开关也会同时修改这个选项呢?只是读取这个选项的 GTK 程序不会像 libhandy/libadwaita 的程序那样会动态切换,必须要关了重开才行。

还有一个奇怪的问题要注意,通常我们是在 GNOME Tweaks 里面设置 GTK Theme,不过根据 https://gitlab.gnome.org/GNOME/gnome-tweaks/-/blob/master/gtweak/tweaks/tweak_group_appearance.py#L75-L88,它会把上面那个 gtk-application-prefer-dark-theme 设置成 0,看注释里面的 BUG 描述,应该也是为了某种刀耕火种的情况解决的(甚至那时候还推荐搞个单独的暗色主题,并且删除了全局的倾向暗色的开关),大概那时候还没推荐用 libhandy,也没有 libadwaita,也没有设置里这种全局暗色/亮色的开关。总之我不建议经常修改 GTK 主题,并且每次修改之后记得手动修改这个选项。如果你觉得这种反复横跳又要保证兼容以前的决策的行为很蠢,那我只能说毕竟你不能要求以前的开发者预见到未来的人们怎么定义桌面的功能。

当然如果你毫不在乎亮色暗色切换(我就是要一直用暗色,所以你暗色模式给我选对了暗色主题就行了!),那还有个比较投机取巧的解决方案:把 Arc-Dark 的 gtk.css 复制并改名 gtk-dark.css 就可以了,原理不难理解。并且 Arc 主题已经做了这样的修改,只是还没有 Release(参见 https://github.com/jnsh/arc-theme/commit/73ada8563591fa48ae365686a358e874ca12edad)。

by Alynx Zhou (alynx.zhou@gmail.com) at November 23, 2022 10:23 AM

November 09, 2022

Alynx Zhou

谁动了我的 DNS 解析?

如果有人看到这个标题以为是什么科学上网相关然后高兴地点进来的话不要怪我,我其实想说的是 Linux 上有关 DNS 解析的流程,这个标题显然是化用自《谁动了我的奶酪?》,即使我并没有读过这本书。我计网真的没认真听课,写的内容都是我现学现卖的,有不对的希望读者指正。

需求

我有一台 NAS,一台 PC 和一台路由器,为了能上网也为了家里的无线设备可以连接 NAS,我给 PC 和 NAS 分别接上路由器,但是路由器只有千兆网口,而 PC 和 NAS 各自多一个 2500 Mbps 的网卡,为了实现最高的连接速度,我又买了一根网线把 PC 和 NAS 直接连接起来,于是现在三台设备两两相连。

直连两台设备其实非常简单,Network Manager 里面 IPv4 设置成手动,然后分别配置 IP 地址和子网掩码,再关掉 IPv6 就可以了,比如我分别设置 IP 为 10.10.10.110.10.10.2,然后子网掩码就是 255.255.255.0。然后在进行各种网络访问的时候只要使用这个 IP 就可以通过直连访问了。

但是我还是不太满意,我设置了帅气的主机名,为什么还得用 IP 访问呢?但如果我查询主机名对应的 IP,发现得到的并不是直连的 IP,而是比如 192.168.1.80 这样的通过路由器的 IP。于是我开始研究如何配置让 DNS 解析给我返回直连的 IP。

long long ago

一般要讲故事,开头都是“很久很久以前……”,不过计算机领域也没什么太古老的故事可讲,毕竟公认的互联网前身 ARPANET 也就是二十世纪的事情。那个时候能互联的机器一共也就那么几个,所以解决的办法简单粗暴:我们每个机器都保存一个文件,里面记录所有人对应的域名和 IP 不就行了?这个优良传统一直留了下来,也就是现在所有系统里都有的 hosts 文件,不管你写的对不对,它的优先级都比 DNS 查询要高。对于我这个及其简单的网络环境,这肯定是不错的解决方案,但是程序员总会觉得这种非自动化的手段太 low 了,于是就被我 pass 掉了。

然后随着加入网络的机器越来越多,这个办法不好用了,毕竟每来一个新人就要所有人更新自己的文件,这复杂度也太高了。所以干脆我们搞一个集中的服务器专门放这个列表,其它机器都向它查询就好了。这就是 DNS 服务器的原理了,然后在局域网里,一般路由器和 DNS 服务器以及 DHCP 服务器都是同一台机器,因为很自然的所有设备都会连到路由器上,而 DHCP 服务器恰好知道它分配出去的 IP 地址,所以如果你输入主机名恰好能解析,那通常是你的路由器做了这些工作。但对于我这个子网来说,为了这两台电脑再配置 DHCP 和 DNS 显然太麻烦了,pass。

再后来各种子网越来越多,子网里的设备也越来越多,比如打印机这种,以至于现在各种智能家居,不可能再搞一个服务器用来注册“喂,我是茶壶”这种东西,于是苹果搞出了一个叫 Zeroconf 的协议,大概是在 DNS 的基础上可以让子网里支持这个协议的设备互相发现互相通知自己是什么。因为和 DNS 相关,所以有一个部分是 MulticastDNS (mDNS),简单来说就是不通过 DNS 服务器,而是通过这个协议发现的设备列表实现 DNS 解析。所以这是第三种方式。

以上三种方式其实都是我从 Arch Wiki 抄来的:https://wiki.archlinux.org/title/Network_configuration#Local_network_hostname_resolution

所以我决定搞一个第三种,这个好说,wiki 写了可以用 Avahi 做这个,不过怎么 systemd-resolved 也能做 mDNS?这玩意不是管 /etc/resolv.conf 的吗?Network Manager 不是也管这个吗?

chattr +i /etc/resolv.conf

很多 Linux 用户都知道修改 DNS 服务器可以通过编辑 /etc/resolv.conf 实现,很多 Linux 用户也被 /etc/resolv.conf 困扰,一些人发现自己的这个文件是个软链接,而另一些人发现这个文件总被 Network Manager 覆盖,还有些人的发行版让他们用一个叫 resolvconf 的工具处理,然后现在 systemd 又搞了个叫 resolved 的东西来插一脚……我说的这些已经足够让一些不想学新东西同时又神经紧张的人开始大喊“fuck systemd, fuck network manager, fuck desktop environment and fuck the whole modern world”然后执行 chattr +i /etc/resolv.conf 了。不过别着急小炸药包们,也许这个世界上新出现的各种东西目的并不只是惹恼你们这群大笨蛋,哦是的,没错,我说,大笨蛋,恐龙勇士(停停停不要翻译腔了),而是真的有场景需要他们。也许对于某个 VPN 连接需要使用自己的 DNS 服务器,总之,不要觉得世界都围着你转,至少读一下这些东西的文档,会告诉你怎么阻止它们修改你的 /etc/resolv.conf 的。

但其实也不是一个 /etc/resolv.conf 搞定所有,有关这个的故事也是 long long ago,但毕竟是 UNIX 纪元之后的事情,没有太久,大概确实上古时代的程序都是直接读这个获取 DNS 服务器然后再做 DNS 解析的,但实际上这也不一定 OK,比如像之前说的打印机这种怎么解决?以及 hosts 呢?所以就有了更复杂的解决方案,大部分程序做 DNS 解析实际上是调用 glibc 里面 getaddrinfo 这个 API,所以在它后面我们就可以做一些工作。一个叫做 Name Service Switch 的东西发明出来就是干这个的,它可以理解为一个基于插件的结构,我们可以通过阅读 /etc/nsswitch.conf 里面的 hosts 这一行来理解,比如我这里默认是这样的:

hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns

简单翻译一下的话意思就是查询一个域名的时候首先看看是不是 systemd-machined 的容器(mymachines 模块),不是的话再问问 systemd-resolved 能不能解析(resolve 模块),如果 systemd-resolved 可用,那到这也就完事了,后面的就不管了([!UNAVAIL=return]),至于为什么我一会解释,然后 files 模块会读 hosts 文件,所以它优先级总是高于 DNS 服务器,然后看看是不是本机(myhostname 模块),然后再读 /etc/resolv.conf 里面的 DNS 服务器进行查询。

对于一个普通的桌面用户,应该使用的只是 Network Manager,默认 Network Manager 不会用 systemd-resolved,于是大部分情况一个外部域名最后还是查询 DNS 服务器,和以前没什么本质区别。那 Network Manager 你为什么要修改 /etc/resolv.conf?原因之一就是之前提到不同的 VPN 服务可能有不同的 DNS 服务器,因此建议这些用户不要手动编辑这个文件,可以直接在 Network Manager 的连接配置里设置某个连接的 DNS 服务器。

那 systemd-resolved 又是什么玩意?是不是 systemd 作者又要搞出什么花样替换我习惯的东西?但这东西好像还真是有些实际的需求,它不是一个简单的 /etc/resolv.conf 的管理工具,而可以理解为是一个自带缓存的 DNS 服务器。glibc 通过 /etc/resolv.conf 查询 DNS 其实是不做缓存的,有些场景用户可能希望能自己缓存结果加快速度,这时候就需要搞这个东西了,它自己是一个 DNS 服务器,因此也就不再执行 nsswitch 里面后续的组件(用你的 dns 模块查询了我怎么缓存?)。除此之外它还声称自己提供了一个更好的 D-Bus 接口用来解析,而不是用 getaddrinfo,不过话又说回来,谁闲得没事去支持你这新搞的 D-Bus API,特别是你自己还搞了个 getaddrinfo 的模块。主观来说我其实不推荐一般的桌面用户配置这个,因为大概率你是在一个路由器后面,你的 DNS 服务器一般设置的都是路由器,而路由器上的 DNS 服务器一般会做缓存,所以其实完全没必要在自己电脑上启用这个……我也没遇到任何一定要使用它这个 D-Bus API 的程序。那 systemd-resolved 你为什么要修改 /etc/resolv.conf?原因是为了兼容那些直接读这个的上古程序,实际上人家就在这里写一行,就是让这些程序去查 systemd-resolved 内置的 DNS 服务器。

那至于 resolvconf 又是啥?这是一个叫 openresolv 的项目搞出来的东西,需求就是有各种程序都打算自己修改 /etc/resolv.conf,不单单是上面那两个,还有一些 VPN 服务之类的,那你们干脆都别管了,我来管,听我的(现在有 N + 1 种解决方案了)。但实际上我也不推荐你使用这个,因为桌面用户根本没有使用场景,你用 Network Manager 的话,就不要再单独使用什么 VPN 工具,因为 Network Manager 本身支持很多种 VPN 连接,你直接用它管理就好了。就算你需要用 systemd-resolved,其实这个也替你考虑好了,Network Manager 支持 systemd-resolved,检测到你用它的话,就不会去改 /etc/resolv.conf,而是直接去修改 systemd-resolved 的配置了。

计算机科学是门艺术,当且仅当你不需要考虑兼容性的时候……

那说回到 mDNS 这个问题,为什么我不直接用 systemd-resolved 解决呢?一个是上面提到的我不需要再做一次 DNS 缓存了,另一个是因为 CUPS 这个打印服务依赖 Avahi,它其实不只用到 mDNS,还用到了 Zeroconf 里面其它的功能比如发现设备去连接打印机,虽然我暂时也用不到 CUPS,但我确实是不想搞 systemd-resolved 的 DNS 服务器了,还是配置 Avahi 吧。当然假如你说我既想要 systemd-resolved 的 DNS 缓存和 D-Bus API 又想要 Avahi 的 Zeroconf 怎么办呢?额,其实也有办法,systemd-resolved 提供了选项让你关掉它的 mDNS 功能,具体我没有尝试,不过这样应该就不会冲突了。所以不要见到点新东西就生气,人家把各种兼容的东西都考虑到了,看两眼文档还不行吗……

然后搞清楚整个流程之后 Avahi 的配置其实不难,首先安装 nss-mdns 这个包,顾名思义是给 nsswitch 提供 mdns 模块,然后启动 avahi-daemon.service,然后编辑 /etc/nsswitch.conf,在 resolve 模块之前加入 mdns4_minimal [NOTFOUND=return]

hosts: mymachines mdns4 resolve [!UNAVAIL=return] files myhostname dns

mdns4 模块会试图通过 mDNS 也就是找网络上其它的 Zeroconf 协议设备来解析 IPv4 地址,4 表示只尝试 IPv4,因为这种内网设备多半你不会给它分配 IPv6,当然也有 6 和没有数字同时支持两种的,不过由于现在的程序都优先查询 IPv6,而我只给直连配置了 IPv4,所以如果不用只支持 4 的,就会 fallback 到后面的模块,那就跑到路由器上查去了,我就是不想走路由器的。

读者看爽了,但好像结果不是我想要的……

等到我把所有的东西都搞好以后我发现一个问题……mDNS 虽然说是子网上的设备互相发现,但是它没规定是哪个子网……于是喜闻乐见的每次 getent ahosts timbersaw.local 查询给我返回不一样的 IP,一会是 10.10.10.2 一会是 192.168.1.80,看起来还是写 hosts 比较靠谱……

最后我的配置是不用 mdns4,而是用 mdns4_minimal,这两个的区别是后者只考虑 .local 结尾的域名,并且如果查找不到的话直接返回 NOTFOUND,而不是继续 fallback:

hosts: mymachines mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files myhostname dns

然后再修改 /etc/hosts 分别添加不带 .local 的主机名(因为 .local 会在 files 之前先被 mDNS 处理)。

by Alynx Zhou (alynx.zhou@gmail.com) at November 09, 2022 01:06 PM

November 08, 2022

Felix Yan

[Arch] systemd 时代的 NBD 客户端持久化配置方法

NBD 用于提供块设备给远程设备使用是一种非常简便、低成本的方法。然而,让 NBD 开始工作的方法在网上能找到很多,但是 NBD 客户端的配置持久化却很难搜到比较完整的资料。在参考了一些过时博客、manpage 等比较分散的资料之后,我总算是凭借蛛丝马迹找到了应当是正确的配置方法。

1、自动加载 nbd 内核模块

echo nbd > /etc/modules-load.d/nbd.conf

(虽然——我觉得这件事应该在 nbd 包里完成,因为上游不愿意默认提供的理由只是为了考虑 nbd 未被编译为内核模块的情况。)

2、/etc/nbdtab

没错,光是发现这个东西就花了我不少时间。

nbd 服务器、连接选项等本来在 nbd-client 命令中配置的内容,应当被写到这个文件里。

一个简单的例子:

nbd0 192.168.0.10 export0 persist

显而易见,分别对应设备名、服务器地址、服务器上配置的 export 名、其他选项。完整的介绍可以参考对应的 manpage

3、/etc/fstab 和 nbd@<设备名>.service

到这里就是最后一步了,也是非常容易出错的一步。

此处的设备名应当和 nbdtab 内配置的设备名相符,nbdtab 的配置由这个对应的服务应用。和其他网络设备一样,挂载点、挂载相关的配置应当设置在 /etc/fstab。

这里需要使用 x-systemd.requires 来声明对 systemd 服务的依赖关系。由于服务会被这个依赖关系自动唤起,不需要手动 enable 服务。

/dev/nbd0 /var/lib/archbuild btrfs defaults,x-systemd.requires=nbd@nbd0.service,_netdev,nofail 0 0

这里的 _netdev 会让 systemd 等待网络可用后再进行挂载。注意我在这里写了 nofail 以避免因为网络原因使设备无法启动。如果要考虑在访问时再挂载,也可以改用 noauto,x-systemd.automount 之类的方法。


参考资料:

The post [Arch] systemd 时代的 NBD 客户端持久化配置方法 first appeared on Felix's Blog.

by Felix Yan at November 08, 2022 08:19 PM

November 07, 2022

Felix Yan

[Arch] OpenSSL 3 更新杂记

最近 Arch Linux 终于把 OpenSSL 更新到了 3.x 系列版本。一直以来,在处理涉及打包工具链本身的 soname bump 等更新问题时,我们一直缺乏一个透明、优雅的流程。

以往采用过的方法包括但不限于:临时往编译环境里手动塞旧版本兼容包、手动在过渡版本的新版 PKGBUILD 里再编译一份旧版包然后把 lib 装进去等。由于处理这件事的开发者一般独自完成了整个过程,留下来的资料除了 IRC 里的寥寥几语往往十分有限,对于其他开发者、或是下游发行版试图重现这个过程来说,都是一个比较痛苦的过程。

这一次趁着 OpenSSL 3 的机会,本喵深度参与了整个 bootstrap rebuild 过程,并且在 RISC-V port 里复现了一遍。现在记录一下大致的过程和遇到的问题,以备不时之需。

1、首先把旧版库打包,使其可以与新版库同时安装。

openssl-1.1:https://github.com/archlinux/svntogit-packages/commit/d50ecccc79b637830b71795bd919e6467e118ef0

由于需要避免文件冲突,相应的编译选项(–libdir)和 package() 过程中做了一些兼容性处理。如果这个包还需要在 rebuild 之后留下来,比如这次的 openssl-1.1 的情况,头文件和 pkgconfig 的 .pc 文件也需要做处理。如果只是作为兼容包,可以仅保留带 soname 和具体版本的库文件本身。(当然,这种情况下也可以考虑在新版包里直接编译一份旧版库安装进去,毕竟只是临时使用。

2、让新版库临时依赖旧版库的包

https://github.com/archlinux/svntogit-packages/commit/eef05b437f55c4d9403668ebdc27973c6a6c2134#diff-37538beb61ff63edebbf735dfcf39e5d732f49183d6beb097169d971875ca422R56

这里用到的技巧是,在 package() 方法内追加 depends,以避免编译环境中提前引入这个依赖,产生文件冲突(此时的仓库中,原包名对应的包仍然是旧版本,和 openssl-1.1 兼容包存在文件冲突)。

3、用此时的环境 rebuild 整个工具链需要用到的基础包

这一步具体要处理哪些包需要仔细分析。以这次 openssl rebuild 为例,一共有这么多包需要先使用这个依赖了旧版库的新版包来 bootstrap 以避免破坏打包环境本身:

coreutils
cryptsetup
curl
kmod
krb5
libarchive
libevent
libfido2 (for systemd's tests)
libssh2
openldap
pacman
sudo
systemd
tpm2-tss (for systemd's tests)

4、rebuild 新版库,去掉对旧版库的依赖

在完成这个步骤之后,需要重建 build chroot 以去掉之前引入的旧版库(这确实是 Arch 工具链的一个毛病,当前的机制不能自动保证 chroot 是干净、完整的)。

5、将前面步骤里 rebuild 过的基础包再次 rebuild

这一步主要是为了保证环境干净。编译得到的包应该和之前没有差异。

至此,已经可以继续平常的 rebuild 过程处理剩下的包了。


OpenSSL 更新进入稳定仓库后,在群组里果然遇到了大胆部分更新的用户不幸炸掉自己的 pacman。甚至有使用 pacman-static 的用户也因为 sudo 链接到 openssl 而悲剧了。再次奉劝大家切忌对官方仓库内的软件包部分更新,尤其是这种一看就不好惹的系统核心库!


另外,本喵自己的老 OpenVPN 配置在更新后也被制裁了:

SSL_CTX_use_certificate:ca md too weak

于是只好加入了 tls-cipher "DEFAULT:@SECLEVEL=0" 从此远离了推荐的配置

The post [Arch] OpenSSL 3 更新杂记 first appeared on Felix's Blog.

by Felix Yan at November 07, 2022 02:10 PM

November 06, 2022

Felix Yan

用 pacman-accel 给 pacman 加速

我在选镜像站的时候,总会遇到一个矛盾:镜像站访问快、镜像站和上游同步延迟低(同步到了最新的包)两者不可兼得。

比较容易想到的解决思路是:只从同步延迟低的镜像下 db,然后从速度快的镜像开始依次试,跳过 404 的镜像,直到找到一个已经存在该文件的镜像。

在过往的十来年里,我一直是通过写一个脚本来分别给 pacman -Sy 和 pacman -Su 设置不同的镜像来勉强解决的。但是这个用法在 pacman 最新系列中被破坏了——pacman 加入了一个镜像站如果 404 次数过多,在同一次更新中就再也不尝试了的新行为。

想到以往的用法会在命令中夹杂许多 404 报错,需要专门的脚本来换镜像体验也并不是很好,我写了个非常简单的本地服务来实现这个需求:

#!/usr/bin/ruby
#
# A simple local redirector for pacman, to get you the latest packages and
# utilize available mirrors.
#
# Usage:
# - Set multiple mirrors in /etc/pacman.d/mirrorlist-accel with ordering:
# https://fastest-mirror-but-updates-once-a-day/archlinux/
# https://relatively-slower-mirror-that-updates-more-frequently/archlinux/
# ...
# https://pkgbuild-dot-com-or-another-mirror-that-gives-you-the-latest-packages/
#
# - Set /etc/pacman.d/mirrorlist to this redirector:
# Server = http://127.0.0.1:4567/$repo/os/$arch

require 'http'
require 'sinatra'

mirrors = File.readlines("/etc/pacman.d/mirrorlist-accel").filter_map { |line| line.strip if line && line[0] != "#" }

get '/*' do |path|
    # Set TIER 0/1 mirrors as the last one, for:
    #  - DB syncing
    #  - Download fallback
    # These two use cases always the same server for consistency.
    mirror = mirrors[-1]

    unless path.end_with? '.db'
        # Find a faster mirror with the requested file present
        mirrors[..-2].each { |m|
            response = HTTP.head(m + path)
            if response.status == 200
                mirror = m
                break
            else
                logger.info "skipping #{m} for #{path}, code: #{response.status}"
            end
        }
    end

    logger.info "redirecting to #{mirror + path}"
    redirect mirror + path, 302
end

set :bind, ENV.fetch("PACMAN_ACCEL_BIND", "127.0.0.1")
set :port, ENV.fetch("PACMAN_ACCEL_PORT", "4567")

如注释所说,在 /etc/pacman.d/mirrorlist-accel 里按照本地访问速度依次设置几个快的镜像,并把最后一个镜像设置为和上游同步频繁的镜像即可。

贴上我目前使用的配置供参考:

$ cat /etc/pacman.d/mirrorlist-accel
https://mirrors.bfsu.edu.cn/archlinux/
https://mirrors.wsyu.edu.cn/archlinux/
https://geo.mirror.pkgbuild.com/

$ cat /etc/pacman.d/mirrorlist
Server = http://127.0.0.1:4567/$repo/os/$arch

原理很简单:pacman 访问本地 HTTP 服务,这个服务对非 .db 的下载请求按照配置依次 HTTP HEAD 直到找到一个返回值为 200 的镜像,然后返回 302 让 pacman 从这个镜像下载。

由于 .db 文件全部由最后一个镜像提供,而前面配置的镜像不存在对应文件时最终也会 fallback 到最后一个镜像,这样使用应该不会产生新的 db 不一致问题。

这个简单的工具日后会在我的小工具集 GitHub 仓库继续维护,同时我也创建了一个简单的 AUR 包:https://aur.archlinux.org/packages/pacman-accel-git

The post 用 pacman-accel 给 pacman 加速 first appeared on Felix's Blog.

by Felix Yan at November 06, 2022 12:43 AM

October 29, 2022

October 06, 2022

Lainme

给Git-for-Windows加上Pacman包管理

Git的Windows版自带一个msys2,但是没有集成Pacman包管理,不方便安装新的软件。这里记录一下给它加上Pacman包管理的方法。文章主要参考:

首先从MSYS2的仓库(https://repo.msys2.org/msys/x86_64/)下载pacman、pacman-mirrors、msys2-keyring三个软件并解压成.tar格式(可以安装7zip的插件modern7z来解压)。解压完成后用管理员身份打开一个Git Bash并执行tar命令,比如

tar -xvf ~/Downloads/msys2-keyring-1_20220623-1-any.pkg.tar -C / usr
tar -xvf ~/Downloads/pacman-mirrors-20220205-1-any.pkg.tar -C / etc
tar -xvf ~/Downloads/pacman-6.0.1-9-x86_64.pkg.tar -C / usr

然后配置Pacman,

curl --location https://raw.githubusercontent.com/git-for-windows/git-sdk-64/main/etc/pacman.conf > /etc/pacman.conf

再配置Keyring

pacman-key --init

导入Key

pacman-key --populate msys2
curl -L https://raw.githubusercontent.com/git-for-windows/build-extra/HEAD/git-for-windows-keyring/git-for-windows.gpg |
pacman-key --add - &&
pacman-key --lsign-key E8325679DFFF09668AD8D7B67115A57376871B1C &&
pacman-key --lsign-key 3B6D86A1BA7701CD0F23AED888138B9E1A9F3986

最后还需要手动创建Pacman需要的两个目录,

mkdir -p /var/lib/pacman/local 
mkdir -p /var/lib/pacman/sync

这样Pacman应该就可以工作了。

by lainme (lainme@undisclosed.example.com) at October 06, 2022 05:44 PM

September 23, 2022

中文社区新闻

从软件仓库中移除了 python2

Python 2 已于 2020 年 1 月结束了生命。我们一直在积极地减少软件仓库中对 python2 依赖的项目数量,终于到了最后能从我们的发行版中移除掉它的时候了。如果你的系统中仍然安装着 python2 请考虑删除掉它和所有 python2 的包。
如果你还需要使用 python2 包当然可以还留着,但是请注意它不再会收到安全更新。如果你需要打补丁的版本,请考虑使用 AUR 或者 非官方的第三方仓库

by farseerfc at September 23, 2022 02:17 PM

September 17, 2022

中文社区新闻

Arch Linux 邮件列表的变化

作为抛弃已经EOL的Python2工作的一部分,我们从 mailman2 迁移到了 mailman3 。

为了保持 DKIM 签名完整,我们已经关掉了改写 “From” 邮件头和主题(附加上列表名)的功能。这意味着想要回复邮件列表的时候,必须使用“回复邮件列表”功能,以及你可能需要更新你的过滤规则以匹配新的”From” 邮件头。

过去订阅过邮件列表的邮箱已经迁移到新的列表,不需要重新订阅。不过要管理你的订阅设置,必须重新注册新的 mailman3 账户。

by farseerfc at September 17, 2022 12:57 AM

September 10, 2022

frantic1048

GSC - Nendoroid 凯露

Karyl

最近终于稍微忙过来了,拍一波到了许久的凯露。本文不会有可怕的接头环节。

首先是盒子和藏在神奇位置的作为特技的装饰物。部件非常多,甚至连尾巴都是可拆卸的。

Karyl Karyl

接下来是本体! Karyl

素材力很高的书。 Karyl

「疯狂星期四,V 我 50」 Karyl

「快给我,不然……」 Karyl

「这就对了」 Karyl

「(ಥ︿ಥ)」 Karyl

September 10, 2022 05:28 PM

September 07, 2022

百合仙子

让离线软件真正离线

本文来自依云's Blog,转载请注明。

去年我做了个索引 Telegram 群组的软件——落絮,终于可以搜索到群里的中文消息了。然而后来发现,好多消息群友都是通过截图发送的,落絮就索引不到了。也不能不让人截图嘛,毕竟很多人描述能力有限,甚至让复制粘贴都能粘出错,截图就相对客观真实可靠多了。

所以落絮想要 OCR。我知道百度有 OCR 服务,但是我显然不会在落絮上使用。我平常使用的 OCR 工具是 tesseract,不少开源软件也用的它。它对英文的识别能力还可以,尤其是可自定义字符集所以识别 IP 地址的效果非常好,但是对中文的识别能力不怎么样,图片稍有不清晰(比如被 Telegram JPEG 压缩)、变形(比如拍照),它就乱得一塌糊涂,就不说它给汉字之间加空格是啥奇怪行为了。

后来听群友说 PaddleOCR 的中文识别效果非常好。我实际测试了一下,确实相当不错,而且完全离线工作还开源。但是,开源是开源了,我又没能力审查它所有的代码,用户量太小也不能指望「有足够多的眼睛」。作为基于机器学习的软件,它也继承了该领域十分复杂难解的构建过程,甚至依赖了个叫「opencv-contrib-python」的自带了 ffmpeg、Qt5、OpenSSL、XCB 各种库的、不知道干什么的组件,试图编译某个旧版 numpy 结果由于太旧不支持 Python 3.10 而失败。所以我决定在 Debian chroot 里安装,那边有 Python 3.9 可以直接使用预编译包。所以问题来了:这么一大堆来源不明的二进制库,用起来真的安全吗?

我不知道。但是我知道,如果它联不上网的话,那还是相对安全的。毕竟我最关心的就是隐私安全——一定不能把群友发的图片泄漏给未知的第三方。而且联不上网的话,不管你是要 DDoS 别人、还是想挖矿,收不到指令、传不出数据,都行不通了嘛。我只要它能从外界读取图片,然后把识别的结果返回给我就好了。

于是一个简单的办法是,拿 bwrap 给它个只能访问自己的独立网络空间它不就访问不了互联网了吗?不过说起来简单,做起来还真不容易。首先,debootstrap 需要使用 root 执行,执行完之后再 chown。为了进一步限制权限,我使用了 subuid,但这也使得事情复杂了起来——我自己都难以访问到它了。几经摸索,我找到了让我进入这个 chroot 环境的方法:

#!/bin/bash -e

user="$(id -un)"
group="$(id -gn)"

# Create a new user namespace in the background with a dummy process just to
# keep it alive.
unshare -U sh -c "sleep 30" &
child_pid=$!

# Set {uid,gid}_map in new user namespace to max allowed range.
# Need to have appropriate entries for user in /etc/subuid and /etc/subgid.
# shellcheck disable=SC2046
newuidmap $child_pid 0 $(grep "^${user}:" /etc/subuid | cut -d : -f 2- | tr : ' ')
# shellcheck disable=SC2046
newgidmap $child_pid 0 $(grep "^${group}:" /etc/subgid | cut -d : -f 2- | tr : ' ')

# Tell Bubblewrap to use our user namespace through fd 5.
5< /proc/$child_pid/ns/user bwrap \
  --userns 5 \
  --cap-add ALL \
  --uid 0 \
  --gid 0 \
  --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup --share-net \
  --die-with-parent --bind ~/rootfs-debian / --tmpfs /sys --tmpfs /tmp --tmpfs /run --proc /proc --dev /dev \
  -- \
  /bin/bash -l

这里给了联网权限,是因为我需要安装 PaddleOCR。没有在创建好 chroot 之后、chown 之前安装,是因为我觉得拿着虽然在 chroot 里但依旧真实的 root 权限装不信任的软件实在是风险太大了。装好之后,再随便找个图,每种语言都识别一遍,让它下载好各种语言的模型,接下来它就再也上不了网啦(为避免恶意代码储存数据在有网的时候再发送):

#!/bin/bash -e

dir="$(dirname $2)"
file="$(basename $2)"

user="$(id -un)"
group="$(id -gn)"

# Create a new user namespace in the background with a dummy process just to
# keep it alive.
unshare -U sh -c "sleep 30" &
child_pid=$!

# Set {uid,gid}_map in new user namespace to max allowed range.
# Need to have appropriate entries for user in /etc/subuid and /etc/subgid.
# shellcheck disable=SC2046
newuidmap $child_pid 0 $(grep "^${user}:" /etc/subuid | cut -d : -f 2- | tr : ' ')
# shellcheck disable=SC2046
newgidmap $child_pid 0 $(grep "^${group}:" /etc/subgid | cut -d : -f 2- | tr : ' ')

# Tell Bubblewrap to use our user namespace through fd 5.
5< /proc/$child_pid/ns/user bwrap \
  --userns 5 \
  --uid 1000 \
  --gid 1000 \
  --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup --unshare-net \
  --die-with-parent --bind ~/rootfs-debian / --tmpfs /sys --tmpfs /tmp --tmpfs /run --proc /proc --dev /dev \
  --ro-bind "$dir" /workspace --chdir /workspace \
  --setenv PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
  --setenv HOME /home/worker \
  -- \
  /home/worker/paddleocr/ocr.py "$1" "$file"

kill $child_pid

这个脚本会把指定文件所在的目录挂载到 chroot 内部,然后对着这个文件调用 PaddleOCR 来识别并通过返回结果。这个调用 PaddleOCR 的 ocr.py 脚本位于我的 paddleocr-web 项目

不过这也太复杂了。后来我又使用 systemd 做了个服务,简单多了:

[Unit]
Description=PaddleOCR HTTP service

[Service]
Type=exec
RootDirectory=/var/lib/machines/lxc-debian/
ExecStart=/home/lilydjwg/PaddleOCR/paddleocr-http --loglevel=warn -j 2
Restart=on-failure
RestartSec=5s

User=1000
NoNewPrivileges=true
PrivateTmp=true
CapabilityBoundingSet=
IPAddressAllow=localhost
IPAddressDeny=any
SocketBindAllow=tcp:端口号
SocketBindDeny=any
SystemCallArchitectures=native
SystemCallFilter=~connect

[Install]
WantedBy=multi-user.target

这里的「paddleocr-http」脚本就是 paddleocr-web 里那个「server.py」。

但它的防护力也差了一些。首先这里只限制了它只能访问本地网络,TCP 方面只允许它绑定指定的端口、不允许调用 connect 系统调用,但是它依旧能向本地发送 UDP 包。其次运行这个进程的用户就是我自己的用户,虽然被 chroot 到了容器里应该出不来。嗯,我大概应该给它换个用户,比如 uid 1500,应该能起到跟 subuid 差不多的效果。

顺便提一句,这个 PaddleOCR 说的是支持那么多种语言,但实际上只有简体中文等少数语言支持得好(繁体都不怎么样),别的语言甚至连语言名和缩写都弄错,越南语识别出来附加符号几乎全军覆没。

by 依云 at September 07, 2022 07:12 AM

frantic1048

在 LilyPond 乐谱中插入有动态的 Ghost Note

一开始用 LilyPond 打谱的时候,学到的插入 ghost note 的方式是使用 \parenthesize 来给音符弄个括号,这样生成的乐谱阅读上没有什么问题,但是生成的音频听起来的话,音符加 \parenthesize 前后声音完全没有区别,试听乐谱的时候不太方便。搜了一圈 2019 年也有人遇到同样问题,但是看起来没有后续。

目标上来说希望有个神奇命令能搓出一个「真」的 ghost note:让单个音符生成的乐谱和 \parenthesize 效果一样,同时生成的音频里音量能比普通音符小一些。

先来波折腾结果示例,两个小节里的 ghost note(那些带括号的音符)分别由 LilyPond 的 \parenthesize 和本文介绍的自定义函数生成。

ghost-note-comparison

ghost-note.ly

过程

文档翻啊翻,找到 midi-extra-velocity 这个影响音符音量,但是又不会像强弱记号那样影响后续乐谱音量的属性,加上现成的 \parenthesize 加个括号,搓一个函数:

ghosta = #(define-scheme-function
           (note)
           (ly:music?)
           (displayMusic note)
           (ly:music-set-property! note 'midi-extra-velocity -50) ;; 减小音量
           (displayMusic note)
           #{
             < \parenthesize #note >
           #}
           )
 
% 乐谱里用 `\ghosta sn4` 这样的方式调用

define-scheme-function 用法参见 2.2.1 Scheme function definitions。第三个参数开始的所有内容会被依次求值,最后一个部分会作为函数的返回值。

displayMusic 是 LilyPond 提供的检查音符(音乐)内容的函数,它能把音符在 LilyPond 内部的表示形式给显示出来(默认显示在 lilypond 的命令行输出里),一般会看到的是一串更低级的 LilyPond 函数的调用,调试 Scheme 函数的时候很有用。

对于刚刚实现的那个函数,写一个 \ghosta sn4,函数里两个 displayMusic 会给出下面的信息(为了方便阅读,下面内容有手动排版和加注释,不影响含义):

;; 修改之前的音符
(make-music 'NoteEvent
  'drum-type 'snare
  'duration (ly:make-duration 2))
 
;; 修改之后的音符
(make-music 'NoteEvent
  'midi-extra-velocity -50
  'drum-type 'snare
  'duration (ly:make-duration 2))

可以看到预期的属性被设置上去了,接下来检查乐谱,括号显示没啥问题,然后是音频,发现音符的音量并没有什么变化,这不太对劲。

接着翻着翻到了源代码,发现 midi-extra-velocity 看起来只有在音符的 articulations 属性里面的元素上才有机会被用上LilyPond - Internal Reference 对 articulations 属性的描述是

articulations (list of music objects)

Articulation events specifically for this note.

直接在音符的属性上修改属性看起来不太行了,那就照它说的搓一个设定了midi-extra-velocity 属性的 ArticulationEvent。用来生成 ArticulationEvent 的内置函数 make-articulation 需要一个额外的 name 参数为了避免更多的奇奇怪怪错误, 尝试用了 LilyPond 已有的 accent 这个用于重音的 articulation 名。

ghostb = #(define-scheme-function
           (note)
           (ly:music?)
           #{
             < \parenthesize #note #(make-articulation "accent" 'midi-extra-velocity -50) >
           #}
           )
 
% 乐谱里用 `\ghostb sn4` 这样的方式调用

这下声音调整成功了,但是音符多了个重音记号,这个还好,能用 \omit 去掉。

TL;DR

于是生成 ghost note 的能用的函数终于搓出来了:

ghost = #(define-scheme-function
           (note)
           (ly:music?)
           #{
             <\parenthesize #note \omit #(make-articulation "accent" 'midi-extra-velocity -50)>
           #}
           )
 
% 乐谱里用 `\ghost sn4` 这样的方式调用

参考

September 07, 2022 12:00 AM

September 01, 2022

Lainme

在Centos7/8上使用HillstoneVPN

公司内网使用的是Hillstone的SSL VPN,官方的Linux客户端只支持非常有限的几个老发行版,连Centos7都不支持。不过有些发行版并非真的不能用,只是程序里对发行版做了判断,不是指定的几个就强行退出。

后来我发现有人做了一个移除了版本限制的客户端,在Centos7下面可以完美运行:https://github.com/JackMoriarty/HillstoneVPN/releases

但在Centos8上运行时总是报错,

Unit dbus-org.freedesktop.resolve1.service not found

这个报错的服务是systemd-resolved的,默认没有开启。但我启动systemd-resolved服务后,虽然不再报错,VPN连接后却完全无法上网。搜索后才知道systemd-resolved更改了很多原本的机制,VPN软件必须做相应的修改才能使用。那么怎么才能不用systemd-resolved,又不让客户端报错呢?我花了几个小时终于发现了最简单的解决方法,删除/sbin/resolvconf

虽然客户端可以用了,但我对这个客户端很不满意:

  • 无法做成systemd服务开机自动启动和连接指定的VPN
  • 需要在桌面持续运行,无法很好的最小化

经过一些搜索后,我发现了一个第三方的命令行客户端Hilldust (https://github.com/LionNatsu/hilldust),基本能解决上述问题,但它也有自己的一点不足

  • 不支持从配置文件读入参数,在多人使用的电脑上会暴露密码
  • 没有从VPN服务器返回的路由表中设置路由,对我们公司的网络无法设置正确的路由
  • 不能很好的作为systemd服务运行,主要是退出时的判断仅有KeyInterrupt,没有考虑SIGNAL。
  • 使用iproute2来配置网络,可能和NetworkManager冲突。另外网络复位主要靠save & restore,如果在VPN运行期间还做了其他网络的调整,复位的时候也会回退这些调整。

于是我在Hilldust上重新做了一个Wrapper,处理了一下这些地方,在Centos7/8上都测试通过了。如果你也需要的话,可以试试:https://github.com/lainme/hilldustWrapper

by lainme (lainme@undisclosed.example.com) at September 01, 2022 01:06 PM

August 30, 2022

中文社区新闻

Grub引导器升级的配置文件不兼容

grub 包最近的变化给 fwsetup 命令增加了新的命令参数,改变了生成出的引导配置中调用命令的方式。根据你的硬件和设置这可能导致系统不能引导,因为安装的引导器和配置文件之间不兼容。在 grub 包升级之后,我们建议重新运行以下命令,安装和再次生成配置:

grub-install ...
grub-mkconfig -o /boot/grub/grub.cfg

关于 grub-install 的具体用法请参考 wiki 页面:GRUB – ArchWiki

by farseerfc at August 30, 2022 11:36 PM

August 22, 2022

frantic1048

给罗技 G903 换微动

用了三年多的 G903 最近按键开始双击了,网上翻了一下有现成的微动板可以直接换,于是就搞了一组同款欧姆龙 D2F-F-K(50M) 微动板,以及一组新的脚贴(因为会被拆掉)来拯救鼠标。工具上需要 1.5 的十字螺丝刀、T5 螺丝刀、镊子和翘棒,以及肯定会发现鼠标超级脏大量积灰皮屑谜之毛茸茸,弄点清洁工具。图里左右侧蓝色线的那个东西就是微动板。

G903

首先顶盖有 1 颗露出来的 T5 螺丝和 6 颗隐藏的十字螺丝。开关上方(靠近 USB 口)这颗是 T5 螺丝,直接拧下。

G903

下面这几颗,需要先拆掉图里圈出来的位置的胶贴,以及撬开一点点开关下方的贴纸,用镊子去戳边缘,然后撬开就行。

G903 G903 G903

然后可以捏着鼠标侧面来分开顶盖和底板,然后从 USB 口那侧掀开,绝对不能大力,因为两者还有排线连着。

G903

上图方框的位置是排线的插口,那个白色的东西可以掀开,然后排线就会松开;圆圈是电池的线,那个就直接拔下来,比较费劲,需要小心不要把插座弄坏了。

G903 G903 G903

连接的部分弄开之后就可以拿开底板了,接下来是电池(它挡住了一颗需要拆的螺丝 (┙>∧<)┙ へ ┻┻),电池是背面被双面胶粘着的,用翘棒温柔地从电池一侧捅进去,一点点分离开电池底面有胶的地方,过程电池会稍微一点点变形,问题不大 ˊ_>ˋ 。

G903

电池掀掉之后是下面这几个螺丝,拆了之后,把鼠标侧键的盖子扒下来,分离手掌部位的那块外壳。

G903

然后是鼠标左右键的螺丝。

G903

这个时候已经能看到微动了,但是还得继续拆中间滚轮上面那个盖子的螺丝。

G903

然后拆固定微动板子的螺丝。

G903

然后拔掉图里的插头,分离旧的微动板,把新的微动板的插头插到相同的位置,这样换微动就算完成了。

G903

最后,看到一堆零件后悔也没用了,怎么拆的就怎么组装回去,鼠标就好了。这鼠标真是难拆!!!

G903

August 22, 2022 07:00 PM

August 18, 2022

Alynx Zhou

不应该做 EVA,而应该做环太平洋

想必把 2018 年的 DARLING in the FRANXX (名字太长了,后面简称 DitF 吧)称作有争议的作品应该不会有人反对,不过我恰好是个不喜欢追新番的人,不然也许我在 2018 年写篇关于这个的博客应该会能获得不少点击量。总之我在 2022 年下载了全集并且几乎是不间断的在三天之内看完了,可能不是特别好评价,但是觉得还是得写点什么。如果读者觉得“怎么复读了很多已有的观点”或者“和我想看的完全不一样”,还麻烦多包涵或者自行关闭标签页。

我认为这是一个优点和缺点同样突出的作品,倒不像很多人觉得是烂尾,结尾至少情理之中可以接受。主要问题是在于塑造人物形象和完善背景设定之间的冲突,也就是标题里写的“不应该做 EVA,而应该做环太平洋”。看完之后我半开玩笑地和 @垚 说:“都怪庵野秀明,非要在巨大机器人动画里面加上一堆反乌托邦末世玄学宗教的背景设定,导致后来的巨大机器人动画不这么做就好像缺了点什么一样。”我其实对巨大机器人动画不算是专家(比如我显然没看过高达),但我觉得对 EVA 还算是熟悉。

虽然我想说的问题是剧情方面的,不过还是要简单提一下作画。@垚 和我表示他一开始是奔着 TRIGGER 才去看的 DitF,结果看过之后对于动作场面大失所望。我其实也不是特别了解 TRIGGER,只是之前被他拉去电影院看了普罗米亚。一定要比较的话确实不管是美术风格还是动作场面都没有普罗米亚那么有特色,但我还是觉得至少在及格线以上了。我觉得特别出色的是机体的设计,不管是 EVA 还是环太平洋,机体设计都是偏向机械化的(虽然 EVA 内在是生物,但是外表仍然是机械),同时是男性化的设计。我还是头一次在动漫里看到女性形象和不是特别机械化设计的机体,而且甚至有丰富的表情,非常新鲜的同时也很符合设定(实际上动画里很多时候使用了 FRANXX 的形象代替女性寄驶员),属于是一个巨大的加分项。叫龙的设计也算新鲜,至少对我来说,看第一集的时候我明明期待的是出来一个传统的怪兽形象的,结果出来的是这么一种可以算是放飞自我的东西。虽然在逻辑上可能比较难以解释它的存在性,不过好在是动漫嘛,不需要在里面找现实。(话说回来了解我的朋友应该知道我最喜欢的敌人设计是 NieR: Automata 里面的机械生命体。)

然后说到我最关注的剧情了,我始终认为作品的核心是剧情。而且剧情是很难把控的东西,特别是对于原创剧情的作品来说,把剧情写好真的是一种不多见的能力。在我看来 DitF 在人物形象和感情戏上达到了一个极高的高度。主要人物有很多,但是每个人的个性都很清晰,并且我没有觉得哪个人的性格令我讨厌。同时故事本身不是简单直白一眼看到头的类型。比如第 13 集将故事推向了高潮,不仅仅因为这一集本身讲述的内容非常感人,而且将整个剧情前半部分埋下的伏笔全部都衔接上了(我甚至差点以为剧情要按绘本发展走向 bad end)。贯穿全篇出现过不止一次的“比翼鸟”的比喻,也非常的符合主题。但我在看一些二创视频的时候还是能看到一些 2018 年的评论在说第 14 集的剧情是喂屎,我确实可以理解追更的朋友当初等了一周之后看到这些阴差阳错然后还要提心吊胆等上一周才能看到下一集的焦躁心情,但这一集的矛盾激化成功的在一个高潮之后推进了剧情的节奏同时与下一集的高潮形成对比,而且这一集的内容非常的合理,虽然是各种巧合,但又很符合现实,符合人物的心理和动机。至于其它一些风评不好的部分比如搭档交换的剧情,只能说是见仁见智,有人不喜欢无可厚非,我还是觉得这部分也增加了故事的复杂性。

但是与塑造人物形象形成对比,完善背景设定方面我认为有比较严重的硬伤。24 集里面在前半部分简单介绍了一个可以说是反乌托邦的设定,然后大量的篇幅用来刻画人物之间的关系和人物细腻的心理活动,结果在感情戏达到高潮之后仿佛是编剧突然想起来“啊,我们挖了好大的坑还没填呢”一样,开始匆忙的填之前的坑。比如我到现在也没想通只在第 15 集里出现了一次的那个巨大的手到底是什么和有什么存在意义。比如叫龙公主在第 17 集开始有大篇幅的剧情之前几乎没有任何铺垫(你别告诉我第 15 集核心里掉出来的小人就算铺垫了),这和感情戏部分各种伏笔先放好然后再衔接完全不像是一部作品的风格,反正你让我看到前半段各种叫龙出现之后是想不到有这么一个个体的存在的。第 19 集通过介绍博士的角度介绍了人类向不死方向的发展,多少算是成功地填了一部分坑。然后整个作品似乎就陷入了“编剧发现还剩五集了填不完坑了于是开始放飞自我”的方向发展了,星实体是个什么东西?之前完全没铺垫过,现在强行在一集内塞给观众。鹤望兰·天燕座又是怎么来的?就算不在动漫里找现实,这也过于不符合逻辑了,看看别的叫龙是什么样子,它们怎么搞出这么个造型的啊。到底是博士发癫了还是编剧发癫了?然后可能是由于实在没办法了,机械降神一个最大反派叫做 VIRM,这已经不是硬伤了,这是直接一刀把脑袋砍掉从脖子往下截肢了。于是一个完全没有铺垫,思维及其简单,做事不讲逻辑的工具 boss 出现了……你可以告诉我打了半年的敌人并不是真正的敌人,但我不能容忍你用这么一种侮辱观众智商的方式告诉我这个事实。然后再一次放飞自我把科技水平拉到太空时代,说实在的,这个和反派登场比起来,已经到了我看见什么都不惊讶的程度了。然后再经过几集漫长的毫无必要的人类叫龙和 VIRM 的太空混战剧情终于结束了这种煎熬,我只能说这部分比起种田是完全的不讲道理了。

至于最后大结局的“生孩子”剧情,想必也有很多人不满意,虽然我自己讨厌小孩,但我觉得这部分不是什么问题。可能是考虑到当下的现实环境,年轻人确实对催生比较反感吧,但放在剧情里面,作为一个新世界开始的必要环节实际上是没什么问题的。最后黑色头发的少年和樱花色头发的少女在树下相遇的结尾也是我最满意的部分之一了,或者可能我恰好是一个容易被这种剧情打动的人吧。

另外 @垚 表示同样是 TRIGGER 的作品,普罗米亚的剧情要好很多,但我不是特别同意,我个人觉得普罗米亚最后 1/4 的剧情其实也向着强行收尾的方向走了,不过一部剧场版动画和季番还是不一样的,剧场版动画只有两个小时,不会有太复杂的背景设定,故事本身又是快节奏,就算强行推进一下剧情,观感也不会太差,而且独特的美术风格和配乐相当大程度的掩盖了剧情的问题。

总而言之,我其实是不太在意“符合逻辑但是大部分观众都不喜欢”的剧情的,比起这个,我觉得“不符合逻辑”的剧情问题要严重得多,这说明剧情走向已经变成无法把控的东西了,为了强行在剩下的集数里面结束故事,不得不强行引入一些东西。如果你问我怎么修改剧情能解决掉硬伤,我其实一开始也没什么思路。不过和 @垚 简单聊了一下之后我意识到了问题所在。从感情戏的篇幅和水平来看,很显然巨大机器人战斗只是个载体,这应该是个披着机器人战斗的皮的爱情故事,并且爱情故事部分相当的成功。在我看来 DitF 的思维和 EVA 其实是有相当大的差别的,虽然可能大家总是津津乐道碇真嗣、绫波零、明日香、渚薰之间的情感关系,但 EVA 没有对这部分的直接描写,更多是通过侧面细节描写,以及粉丝进行的分析推理得出的。所以 EVA 可以在背景设定上挖很大的坑然后有充足的时间填坑。因此我的想法是让这个作品向环太平洋的方向靠近,去掉反乌托邦的设定,比如什么种植园和 APE 都可以不要,直接快进到不知道为什么出现了名叫叫龙的怪兽进攻人类,于是人类设计了 FRANXX 并要少年少女操作来防御。然后人物的背景全部都不需要修改,最终的结局就像环太平洋一样直接摧毁掉叫龙来源就可以了。虽然可能会被 EVA 观众认为“没有达到 EVA 的高度”,但既然本来就不在一个赛道上(我这写的是爱情故事啊),这也没什么所谓了。考虑到第 13 集和 15 集的口碑,这样改应该不会折损它的优点。

最后还是要说,虽然有这么明显的硬伤,这部作品突出的优点还是让我受到了很大震撼并且在接下来的一周都沉浸在剧情里不能自拔。我个人也非常喜欢这部作品的 ED,无论是旋律还是歌词,以及演唱方面都可以说是一流的作品,特别是第 13 集高潮部分的《ひとり》,单从音乐和剧情结合的角度来说,确实达到了 EVA 的高度(让我想起来《翼をください》。《トリカゴ》也是绝妙的作品,特别是伴随 ED 出现的画面,“如果这些人物所在的是一个没有叫龙和 FRANXX 存在的世界会是什么样子呢?”(很遗憾我对 OP 没什么感觉)我同样对广、02、五郎和莓的人物形象非常的喜爱,复杂的情感关系使得这些人物变得颇为立体,而且他们完全没有动漫里一些经常出现的会让我讨厌的人物特质。

by Alynx Zhou (alynx.zhou@gmail.com) at August 18, 2022 05:06 PM

百合仙子

我所讨厌的网页行为

本文来自依云's Blog,转载请注明。

有些网页的行为通常不被视为 bug,甚至是故意为之,但很令人讨厌。这里记录一些我所讨厌的网页「特性」。它们被归为两类,要么导致某些场景下用不了,或者用着很不方便,要么很打扰人。

可访问性问题

忽视系统、浏览器设置,在浏览器使用浅色主题的情况下默认使用深色主题,或者在浅色主题下代码部分使用深色主题。反过来问题不大,因为我有 DarkReader

主体文本不支持选择和复制。选择和复制之后,用户能做很多事情,比如查生字、翻译、搜索相关主题。

已访问链接与未访问链接显示没有差别。

消除可交互元素(链接、按钮)的 outline。这个 outline 以前是虚线框,现在火狐改成了蓝色框,用于标识当前键盘交互的对象。

搜索框不支持回车确认,必须换鼠标点击。

位于文本框后的按钮不支持使用 Tab 键切换过去,并且 Tab 键在此文本框中也没有任何显著的作用。必须换鼠标点击。

需要交互的元素不能被 vimium 插件识别为可点击。这大概是使用非交互元素来处理交互事件,甚至事件监听器都不在元素本身上。

使用 JavaScript 实现原本可以直接用链接实现的内容(链接目标是某个 JavaScript 函数调用)。这导致我无法使用中键来在新标签页中打开。

显著不同的内容没有独立的 URL。尤其见于一些单页应用(SPA)。要到达特定内容(比如加书签或者分享给别人),就只能记录先点哪里、再点哪里等。

预设用户的屏幕大小,导致浏览器窗口过小的时候部分关键内容(如登录按钮)看不到、无法操作。

交互元素没有无障碍标签。成堆的「未加标签 按钮」。

通过 User-Agent 判断浏览器,并拒绝为某些 User-Agent 服务(但实际上去除这个限制之后,功能是完全没有问题的)。

当没有带声音自动播放权限时,无声播放主体内容(而非等待用户操作使其获得权限)。说的就是 Bilibili。

为大屏幕用户(如桌面用户)展示为手机屏幕设计的页面。这些页面中字体特别巨大,并且不能被浏览器缩放影响。交互元素上鼠标指针不改变为手状,甚至只支持触摸操作而不支持鼠标点击。

悬浮于主内容之上的「在App中打开」。点名批评 imgur。它的按钮不光挡住图片,而且用户放大图片的时候它也被放大,挡住更多图片内容。

不能禁用的图片懒加载,或者视频内容被移出画面、切换到后台就停止加载。点名批评 Telegram、维基百科。我等你加载呢,你非要我盯着看你加载浪费时间?现在网好,你赶紧给我加载好,进电梯或者地铁或者山洞了,我再慢慢看你的内容啊。

视频内容被移出画面就停止播放。点名批评知乎。我让你播放你就给我播放。我不看视频,是因为视频画面没啥可看的,可是我听音频部分呀。

覆盖浏览器的 Ctrl-F 查找快捷键,并不提供方案来避免覆盖。我就搜索当前页面,不要你的站内搜索功能。

注册前请务必先阅读用户条款和规则,用户条款和规则页面需登录后才可访问。

简体中文内容指定繁体中文的字体,或者添加繁体中文的标签。或者反过来。

打扰用户

在内容页面,任何会动的非主体内容,包括但不限于广告、内容推荐。形式可以是动态 GIF、滚动动画、视频等。用于首页渲染效果的背景动画和视频不算,作为主体内容者也不算。

针对非音视频网站,自动或者非用户明确表达地(比如在用户点击不相关内容时)播放带音频的内容。

消耗 CPU 的背景特效。如 canvas-nest。会让 CPU 很吵,也会浪费能源、加剧气候变化。

by 依云 at August 18, 2022 09:14 AM

August 17, 2022

Alynx Zhou

阻止 clangd 污染项目根目录的一些方法

Emacs 的 lsp-mode 推荐使用 clangd 分析 C/C++ 代码,用起来体验还不错,但是让人非常恼火的是用户要主动或者被迫地在项目根目录下面添加一些文件,比如 .clang_complete 或者 compile_commands.json 来让 clangd 知道项目需要包含哪些库的头文件,以及 clangd 会直接把建立的索引丢到项目根目录下面的 .cache 目录里。虽然可以把这些加入 .gitignore,但保不齐哪个脾气古怪的上游维护者会和你纠缠半天让你解释为什么要加这些,实在是很麻烦。

compile_commands.json 比较好解决,clangd 提供了一个参数 --compile-commands-dir=<string> 可以指定查找这个文件的目录,我直接把它设置为 ./build/,因为大部分项目的 .gitignore 都会包含构建目录,也免得运行 bear -- meson compile 之后再把这个文件从 build 目录移出来。

(use-package lsp-mode
  :ensure t
  :commands lsp
  :hook ((c-mode . lsp-deferred)
         (c++-mode . lsp-deferred)
         (c-or-c++-mode . lsp-deferred)
         (lsp-mode . lsp-enable-which-key-integration))
  :bind (:map lsp-mode-map
              ("M-." . lsp-find-definition)
              ("M-," . lsp-find-references))
  :custom
  ;; Move lsp files into local dir.
  (lsp-server-install-dir (locate-user-emacs-file ".local/lsp/"))
  (lsp-session-file (locate-user-emacs-file ".local/lsp-session"))
  (lsp-keymap-prefix "C-c l")
  ;; Only enable log for debug.
  ;; This controls `*lsp-log*` buffer.
  (lsp-log-io nil)
  ;; JavaScript (ts-ls) settings.
  ;; OMG, the FUCKING EVIL SHITTY VSCode TypeScript language server generates
  ;; log in project dir, can MicroSoft stop to let their software put shit in
  ;; front of users?
  (lsp-clients-typescript-server-args '("--stdio" "--tsserver-log-file" "/tmp/tsserver-log.txt"))
  (lsp-javascript-format-insert-space-after-opening-and-before-closing-nonempty-braces nil)
  ;; Always let clangd look for compile_commands.json under build dir so it will
  ;; not make project root dirty.
  (lsp-clients-clangd-args ("--header-insertion-decorators=0" "--compile-commands-dir=./build/" "--enable-config")))

对于 .cache/ 就不是那么好解决了,根据 https://github.com/clangd/clangd/issues/341#issuecomment-1003560792,似乎他们并没有关闭或者修改缓存目录的支持。不过我想到一个弯道超车的方案,git 本身应该是有从其它位置加载用户定义的 gitignore 文件的功能的,我利用这个写一个本地的 gitignore 不就行了吗,搜索之后得到 https://stackoverflow.com/questions/5724455/can-i-make-a-user-specific-gitignore-file,操作起来也很简单。首先我把这个文件放到 ~/.config/git/gitignore,里面写上要忽略的 glob,然后运行 git config --global core.excludesfile ~/.cache/git/gitignore 就大功告成。

不过就在我写这篇文章时,clangd 的 issue 上有人回复我,根据 https://github.com/clangd/clangd/issues/184#issuecomment-998244415,现在 clangd 应该是会把索引放在 compile_commands.json 所在的目录,所以多少也算是解决了问题吧。虽然这样删掉构建目录之后索引缓存也没了,不过我觉得比起重建缓存,还是弄脏项目目录更恶心一点。

更新(2022-08-17):还有一个头疼的问题是 GLib 的 g_clear_pointer 宏里面使用到了对指针本体取 sizeof 的语法,而 clangd 默认会认为这是个错误,于是 lsp 就会标出一大堆问题。可以对项目进行设置关掉这一条,不过又会弄脏项目目录,查询文档得知 clangd 会读取 ~/.config/clangd/config.yaml 这个用户级别的配置文件,于是在里面写入内容关掉这条检查:

Diagnostics:
  ClangTidy:
    Remove: bugprone-sizeof-expression

然后给 clangd 传递 --enable-config 这个参数即可。

by Alynx Zhou (alynx.zhou@gmail.com) at August 17, 2022 08:28 AM

August 10, 2022

百合仙子

tmux 状态栏优化

本文来自依云's Blog,转载请注明。

在 tmux 的状态栏里,通常会显示当前时间。配置起来也非常简单,%Y-%m-%d %H:%M:%S这样的时间格式化字符串扔过去就可以了。然而这样做有个小问题:这个时间只能精确到秒。我的意思不是说我想让它显示毫秒,而是希望它像电视台和广播电台的时间一样,显示(播报)「12:00:00」的时候,就刚好是这一秒的开始。

一般来说,这么延迟个一秒以内的随机数问题不大,除了你有多个这种时间戳的时候——

tmux inside tmux inside tmux

这些时间戳哪个先更新、哪个后更新可完全说不准的,你可能看到明明在地球另一边的服务器上先到某一秒,本地才跟上。甚至同一个 tmux 的不同客户端里,这个时间戳的更新时间都可能会有差异。

我想优化这个的另一个原因是,我经常使用 extrace 来查看程序调用另一程序使用的命令行参数,然而我本地连了多少个 tmux,每秒便会有多少个 sh + awk 进程出来读系统负载。尤其是我从 Awesome 换到 Wayfire 之后,顶栏改用 waybar 了,很多指示器都是内建或者自己写的外部脚本,不再需要每隔几秒跑个子进程去获取信息,这样 tmux 调用子进程来刷新状态造成的干扰就突显了出来。

于是就有了 accurate-time 程序。它每个整秒会去读系统负载,然后和当前时间一起送给 tmux 来显示。每秒一个进程,已经少了很多啦。

既然是我的程序自己来读负载,也就方便做更多事情了,比如根据负载情况使用不同的文字颜色:绿色表示低负载,灰白是稍微有点活干,蓝色和 cyan 是比较忙碌,黄色、品红表示已经忙不过来啦,红色就是要累趴下啦。之前偶然间发现 qemu-git 这个包使用 ninja、但是链接的时候又套了一层 make,造成系统负载冲到了两百多。但是无论高低,tmux 的负载显示都是红色,所以我可能之前已经视而不见许多次了。加上颜色之后,这类异常就更容易被注意到了。以前我本地每次风扇呼呼地转才发现系统负载高,但是我要是用耳机的话就听不到了,现在也多了个高负载的指示。

安装和配置很简单,cargo build --release 编译,然后把编译出来的 target/release/accurate-time 扔到 $PATH 里,再如下配置 tmux 状态栏右边即可:

if-shell "accurate-time tmux" {
  set -g status-interval 0
} {
  set -g status-interval 1
  set -g status-right "#[fg=red]#(awk '{print $1, $2, $3}' /proc/loadavg) #[fg=colour15]%Y-%m-%d %H:%M:%S"
}

本来我还打算给 waybar 上的时间也这么做一下的,不过程序写好了才发现 waybar 自己已经把时间对齐到更新间隔了。

by 依云 at August 10, 2022 10:43 AM

Alynx Zhou

PHP 故释

// 新来的!如果你看到这段注释,说明上一个负责重构这个项目的程序员已经被气死了!
// 请你把下一行的数字加一,然后祝你好运!
// 63

起因是昨天晚上吃完饭回家路上和铁道迷闲聊说起他正在重写的 PHP 项目。于是我随口编了一个 恐怖 段子。

为什么是 63:

% node
Welcome to Node.js v18.7.0.
Type ".help" for more information.
> Math.floor(Math.random() * 100)
63
> 

by Alynx Zhou (alynx.zhou@gmail.com) at August 10, 2022 05:24 AM

August 08, 2022

百合仙子

Google Chrome 中的字体设置

本文来自依云's Blog,转载请注明。

Google Chrome 是我的备用浏览器,主要用于对比和检查网页的渲染效果,以及某些网页在火狐上可能不太正常,就用 Google Chrome 试试。

但 Google Chrome 有个非常令人恼火的问题:默认字体实在太难受了!

如果没有指明字体,那么 Google Chrome 默认使用 Times New Roman 字体。这是我十年前从 Windows 那边拷过来的字体,看上去细细软软的,很复古,不太适合屏幕显示。屏幕显示一般使用无衬线字体,但 Google Chrome 默认是衬线字体。好吧,那网页要是指明要 sans-serif 字体呢?Google Chrome 这次看上了 Arial,同样是一款古老的、来自于 Windows 的字体。

Google Chrome 默认使用 Windows 字体

行吧……反正我也不是多喜欢这两字体,就全部删掉好了!结果,呃,「自定义」??

Google Chrome 固执己见

实际上它们分别是「Liberation Serif」和「Liberation Sans」字体。这是我的系统上 fc-match「Times New Roman」和「Arial」给出的字体,由 ttf-liberation 包提供,google-chrome 包依赖(看 AUR 上的评论,这是因为 Google Chrome 的 PDF 渲染需要这个字体)。

Google Chrome 就是这么喜欢 Times New Roman 和 Arial,系统上不安装它也要找个替代品来用,就是不听用户通过 fontconfig 设置的默认字体。用户要想 Google Chrome 听点话,需要在「设置」->「外观」->「自定义字体」里像这样设置一下:

Google Chrome 使用 fontconfig 的设置方法

而火狐的话,用户不需要在这种犄角旮旯里设置:

火狐默认使用 fontconfig 的设置

可惜大部分用户都在用 Google Chrome 或其变种,所以制作网页上还是得手动指定一些现代点的字体。

by 依云 at August 08, 2022 04:05 AM

July 21, 2022

Alynx Zhou

从 PulseAudio 到 PipeWire

这篇的操作是在之前 运行在 JACK 上层的 PulseAudio 基础上进行的。

我自己的音频配置比较复杂,虽然 PipeWire 号称能兼容 PulseAudio 和 JACK 的 client 并且在某些发行版成为了默认选项,我还是没很快换掉。因为我想像之前用 PulseAudio 那样把 PipeWire 做成 JACK 的 client,虽然它的文档一直说支持这样,但是看起来两个月前代码才写好。于是我最近尝试了一下。

首先需要安装 pipewirepipewire-alsapipewire-pulse 和一个 PipeWire Media Session Manager,我用的是新的 WirePlumber 但是不要装 pipewire-jack 因为这个是模拟 JACK server 的。然后配置你的 session manager 开启 alsa.jack-device = true,然后理论上就能在设置里看到 JACK Sink/Source 了……但是……

首先装 pipewire-pulse 会替代 pulseaudio,但是按照上篇文章应该是安装了 pulseaudio-jack 这个依赖 pulseaudio 的包,解决方法是先卸载掉 pulseaudio-jack。然后继续安装重新登录应该 PipeWire 已经起来了,按理说这时候启动 JACK 就可以,但是不管我怎么搞都看不到 JACK Sink/Source,所以就备用方案,直接用 PipeWire 替代 JACK 看看,虽然早就可以这么做了,但是之前之前尝试 PipeWire 感觉不是那么稳定,所以就没一直用。

使用 PipeWire 当 JACK 的话要安装 pipewire-jack,和前面差不多的问题是 pipewire-jack 替代 jack2 但是 jack2-dbus 依赖 jack2,那就先删掉 jack2-dbus 再装就好了,然后重新登录,一切正常,Qjackctl 的 Graph 也能正常操作。而且比较有趣的是这样原本使用 PulseAudio 的程序也会在 JACK Graph 里面显示成节点(因为最后都通过 PipeWire),使用 Ardour 录音也没什么问题。其实我的需求还是比较简单的,也不需要什么太低的延迟,只是很多录音的程序都用 JACK 所以才要用。

用了一段时间之后感觉没什么问题,设备之间来回切换也没有卡顿了,驱动我的 2i4 也是完全正常,以后应该就先这样用了。

更新(2022-07-21):我发现在 Ardour 里面录音还是有问题,具体表现是录超过 1 分钟就会报 xrun,怀疑是这个 bug:https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/2257。不过其它 DAW 比如 REAPER 或者 Zrythm 都没问题,我倒是挺想换成 Zrythm 的,但是它还在 Beta 阶段。以及前面板插入耳机似乎设置里检测不到,但是开启一下 pauvcontrol 又能检测到了,总之是一些奇怪的小问题,也许我应该换回去。

更新(2022-07-21):我把我的 Sony Playstation Eye 拔掉之后似乎 Ardour 就正常了……我记得这个摄像头的麦克风阵列以前可以用的,不知道为什么现在 PulseAudio 都用不了了,所以看起来不是 PipeWire 的问题。至于为什么只有 Ardour + PipeWire 会出现这个问题,我猜是因为 Ardour 会连接所有可用的设备给自己用,于是就被这个不工作的设备影响出现延迟,而原版的 JACK 只会请求一个设备,Ardour 根本就看不到 PS Eye。也许我还是得买个正经的摄像头……

by Alynx Zhou (alynx.zhou@gmail.com) at July 21, 2022 09:16 AM

July 18, 2022

Alynx Zhou

可能只适合我自己的 RIME 配置 2

上一篇:可能只适合我自己的 RIME 配置

这一篇的原因是我最近在偶然间刷博客刷到一篇 讲 RIME 简体输入方案的文章,里面提到说朙月拼音因为是繁体转简体所以会出现各种错误(其实我个人倒是没怎么遇到过),然后推荐了一个完全针对简体字的输入方案 极光拼音,我自己其实只会输入简体字,不怎么需要输入繁体字的功能,所以打算试试。

已经有人在 AUR 打包了 rime-aurora-pinyin,所以我直接拿来用了,然后类似于我上一篇文章处理朙月拼音的办法,给这个也做了一些自定义设置,主要是添加 emoji,修改默认的全角标点上屏行为,以及加载扩展过的字典,不过遇到了几个问题。

首先是我像上篇文章说的那样直接在 patch 下面添加 __include: emoji_suggestion:/patch 并不能输入 emoji,我研究了很长时间,甚至以为 emoji 功能依赖繁体转简体。结果其实并不是,打开 emoji_suggestion.yaml 可以看到下面几句:

patch:
  switches/@next:
    name: emoji_suggestion
    reset: 1
    states: [ "🈚️️\uFE0E", "🈶️️\uFE0F" ]
  'engine/filters/@before 0':
    simplifier@emoji_suggestion
  emoji_suggestion:
    opencc_config: emoji.json
    option_name: emoji_suggestion
    tips: all

switches 的部分可以先忽略,关键在于 engine,这个 emoji 输入的原理是添加一个 filter,它接收一个输入,然后去附带的 opencc 的词典里查找这个输入得到对应的结果,再把这个输出给下一个 filter,按照词典,输入应该是中文字或者词,并且我看了一下词典,简体和繁体是都有的,所以也不存在简繁转换的问题。其实问题在于这段配置会把它作为第一个 filter 加入列表,而极光拼音的默认 filter 列表是这样的:

  filters:
    - uniquifier
    - charset_filter@gb2312
    - charset_filter@gbk

也就是说如果把 emoji 的 filter 加到第一个,它的输出就要继续经过 uniquifiercharset_filter@gb2312charset_filter@gbk,后两个是极光拼音为了排除掉几乎用不到的生僻字而添加的。而 emoji 显然不属于 gb2312 也不属于 gbk,自然就被过滤掉了。

所以我的解决方案是把 emoji 的 filter 加到列表最后,其实加到哪里无所谓,只要你确定前一个 filter 的输出是中文,能触发 emoji 的 opencc 词典就好了,我单独写了一个 emoji_suggestion.patch.yaml 文件:

switches/@next:
  name: emoji_suggestion
  reset: 1
  states: [ "🈚️️\uFE0E", "🈶️️\uFE0F" ]
engine/filters/@next: simplifier@emoji_suggestion
emoji_suggestion:
  opencc_config: emoji.json
  option_name: emoji_suggestion
  tips: all

导入的时候就写 __include: emoji_suggestion.patch:/。不过虽然我这个不再需要原来的那个 YAML 了,还是需要 rime-emoji 这个项目里其余的文件的。

顺便这也解释了为什么使用朙月拼音时候 emoji 后面的提示框显示的是繁体而非简体,因为朙月拼音从词库直接吐出来的是繁体,然后直接经过第一个 filter 就是 emoji,自然 emoji 查找时候用的就是繁体,然后才会经过简繁转换的 filter,所以如果把 emoji 的 filter 挪到简繁转换的 filter 后面,提示就会变成简体。

解决了 emoji 问题之后还有另一个问题,因为这个 emoji 的 filter 的输入是中文词组,也就意味着必须词库能吐出对应的中文词才能输入 emoji,比如说刚配置出来极光拼音的时候是吐不出来“笑哭”这个词的,所以就不会触发笑哭的 emoji。据说其它平台的输入法也有这个问题。其实没什么太好的解决方案,你可以说自己先手动打几次对应的词然后等 RIME 记住这个输入,不过我觉得也不太好。我想到的办法是既然需要词库里有,不如就让我用 emoji 的 opencc 词典生成一个 RIME 词库,然后扩展词库的时候加进去,这样无论如何都能吐出来了。其实也不是很麻烦,但是需要你把文字转成对应的拼音,那当然不能人工做这个操作了,我利用 Node 的 pinyin 库写了个脚本来做这件事:

#!/usr/bin/env node

const fs = require("fs");
const OpenCC = require("opencc");
const {pinyin} = require("pinyin");

// 我的词库只需要简体中文,如果你需要繁体中文,把 `t2s` 改成 `s2t` 应该就好了。
const converter = new OpenCC("t2s.json");

const outputFileName = "emoji_suggestion.dict.yaml";

const inputFileNames = [];

if (process.argv.length <= 2) {
  console.log(`Usage: ${process.argv[1]} file1 file2 ...`);
  process.exit(0);
}

for (let i = 2; i < process.argv.length; ++i) {
  inputFileNames.push(process.argv[i]);
}

const results = {};

for (const inputFileName of inputFileNames) {
  const words = fs.readFileSync(
    inputFileName, "utf8"
  ).split("\n").filter((line) => {
    return line.length !== 0;
  }).map((line) => {
    return line.split("\t")[0];
  });
  for (const w of words) {
    // rime-emoji 的 opencc 词典同时包含简体中文和繁体中文,但比如极光拼音
    // 这种默认不包含简繁转换的方案多半只想要其中一种,所以使用 opencc 对候选词
    // 进行一次转换。
    const word = converter.convertSync(w);
    if (results[word] != null) {
      continue;
    }
    const py = pinyin(word, {
      "heteronym": true,
      "segment": true,
      "style": "normal"
    }).map((array) => {
      // 有些时候就算利用结巴分词了,这个库仍然会没法判断多音字的读音然后丢出好
      // 几个结果,只取第一个好了。
      return array[0];
    }).join(" ");
    // 遇到处理不了的生僻字这个库会直接丢出原本的字……什么奇怪逻辑,只能判断是不
    // 是字母或空格了。
    if (/^[a-z ]*$/.test(py)) {
      results[word] = py;
    }
  }
}

const outputLines = [
  "# Rime dictionary for emoji",
  "# encoding: utf-8",
  "# Generated by `gen-emoji-dict.js` written by Alynx Zhou",
  "",
  "---",
  "name: emoji_suggestion",
  "version: \"0.1\"",
  "sort: by_weight",
  "...",
  ""
];
for (const k in results) {
  outputLines.push(`${k}\t${results[k]}`);
}
// console.log(outputLines.join("\n"));
fs.writeFileSync(outputFileName, outputLines.join("\n"), "utf8");

当然这个脚本不是很完美,比如 pinyin 识别不了的生僻字直接忽略了,不过我觉得它都识别不了,我多半也不会打出来的。然后虽然可以利用 jieba 分词提高多音字的准确性,还是有些不正确的,这些遇到了再手动纠错吧。

最后把这个词库添加进扩充词库:

# 原来要结合默认词库和第三方词库,
# 需要自己编写一个词库让它 fallback 到极光拼音和第三方词库。
# 我说佛老师对不起对不起,我不懂规矩。
---
name: aurora_pinyin.extended
version: "0.1"
# `by_weight`(按词频高低排序)或 `original`(保持原码表中的顺序)。
sort: by_weight
# 听说默认简化字八股文效果不好,还是算了。
# https://blog.coelacanthus.moe/posts/tech/a-new-rime-simp-pinyin-schema/
# 因为导入的朙月拼音词库是繁转简,所以这里不能导入简化字八股文。
# 导入简化字八股文。
# vocabulary: essay-zh-hans
# 选择是否导入预设词汇表【八股文】。
# use_preset_vocabulary: true

import_tables:
  # 主要是为了肥猫 wiki 词库。极光拼音好像是内置常用简化字表的。
  - zhwiki
  - aurora_pinyin
  - emoji_suggestion

顺便说一下我其实也不太了解这个扩展词库的顺序怎么设置比较好,不过我尝试的结果是像这样把 emoji 放在最后面,就不会每次输入在前面提示很多并不常用的 emoji 词组的问题。

我这个脚本生成的词库只有简体,不过我发现朙月拼音的简繁转换还是可以正常处理简体词库的,也就是说会变成 词库出简体 -> 简繁转换 -> 繁体变 emoji,所以直接加给朙月拼音也没问题,如果我需要用繁体中文,可以直接切换方案到朙月拼音(虽然实际上我的配置是简化字版,不过看起来主要区别只是默认是否开启繁体转简体)。平时输入简体则直接用极光拼音。

完整配置在 GitHub Repo 更新。

by Alynx Zhou (alynx.zhou@gmail.com) at July 18, 2022 10:50 AM

July 15, 2022

中文社区新闻

wxWidgets 3.2 更新可能需要手动干预

wxWidgets 3.2 在 GTK3 前端之外也提供了 Qt 前端,所以相关包的名称已从 wxgtk- 改名到 wxwidgets- 。不再提供 GTK2 前端。如果你安装过 wxgtk2 升级过程中可能遇到下述错误:

error: failed to prepare transaction (could not satisfy dependencies) :: removing wxgtk-common breaks dependency 'wxgtk-common' required by wxgtk2

这种情况下请删除 wxgtk2 并继续升级。

by farseerfc at July 15, 2022 10:32 PM

June 30, 2022

中文社区新闻

[archlinuxcn] 社区仓库现提供 debuginfod 服务

[archlinuxcn] 仓库现提供 debuginfod 服务(支持部分有调试符号的包),设置以下环境变量即可:

DEBUGINFOD_URLS="https://debuginfod.archlinux.org https://repo.archlinuxcn.org"

by lilydjwg at June 30, 2022 03:53 AM

June 22, 2022

Leo Shen

Use ZED with msmtp on Debian

Install msmtp and its mta. 1 apt install msmtp msmtp-mta mailutils Edit /root/.msmtprc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 defaults auth on tls on tls_trust_file /etc/ssl/certs/ca-certificates.crt logfile ~/.msmtp.log aliases /etc/aliases account alerts host SMTP_ADDRESS port 587 from SENDER_EMAIL_ADDRESS user SMTP_ACCOUNT password SMTP_PASSWORD account default : alerts Edit /etc/zfs/zed.d/zed.rc: Change ZED_EMAIL_ADDR to your desired mailbox

June 22, 2022 01:33 PM

June 12, 2022

Lainme

在CentOS7上安装H3C的iNode客户端

最近公司的VPN系统切换到了H3C的iNode,虽然H3C的网站上有安装手册,但那个安装过程繁琐且毫无必要,还会把系统弄的乱乱的,所以这里记录一下我自己的安装过程。

首先需要获取Linux版的软件,一般而言公司买了H3C的服务,都有相应的软件的,可以找IT要。如果要直接从H3C网站下载,可以从 https://www.h3c.com/cn/Service/Document_Software/Software_Download/IP_Management/iNode/iNode_PC/ 下载最新版。下载需要登陆,用户名和密码可以用 https://zhiliao.h3c.com/Theme/details/124921 链接中提供的那个(竟然真的可以登陆,我当时都震惊了……)

解压缩后在Linux目录下找到需要的版本,比如X86-64架构的就是“iNodeManager_H3C_Linux64_7.30(E0585).tar.gz”,解压缩后得到“iNodeManager”目录,进入这个目录执行安装脚本(其实大多数情况下没有必要……除非你的系统没有装过任何QT软件和7z才需要),

tar -xzf iNodeManager_H3C_Linux64_7.30\(E0585\).tar.gz
cd iNodeManager
bash ./install64.sh

再执行“iNodeManager”程序,

./iNodeManager

这个程序是对客户端进行定制的,完成后会生成客户端的安装程序,可以根据自己的需要勾选里面的选项。由于我们公司只用到了SSLVPN功能,我就只勾选了这一个,其他都不选(避免不必要的麻烦),如图所示,

点击右下角的“Finish”后会弹出新的对话框,勾选“Generate customized client setup program”,再点击“OK”。

完成后退出,并在iNodeManager目录下找到生成的“iNodeSetup”目录,进入这个目录解压自己需要的客户端版本,比如“iNodeClient_Linux64_7.3 (E0585).tar.gz”,得到“iNodeClient”目录,

cd iNodeSetup/
tar -xzf iNodeClient_Linux64_7.3\ \(E0585\).tar.gz

将这个目录移动到/opt下并用root权限执行安装脚本

sudo mv iNodeClient/ /opt/
cd /opt/iNodeClient/
sudo ./install_64.sh

安装完后就可以在系统菜单里找到iNode客户端。CentOS7要能正常启动客户端需要安装libpng12,别的系统没有测试。

sudo yum install libpng12

启动客户端后点击“+”添加链接,因为之前只选择了SSLVPN,所以协议选择的步骤直接“Next”就可以

其他连接参数的输入和Windows版差不多,这里就不写了。

by lainme (lainme@undisclosed.example.com) at June 12, 2022 11:52 PM

June 06, 2022

Phoenix Nemo

修复 CentOS 'org.freedesktop.login1' timed out

又到了喜闻乐见帮人修系统的时间。

根据情况描述是 “stuck at ‘Starting switch root’”,初步判定是 initramfs 坏掉了。经过询问果不其然,某个特别爱没事儿就更新的家伙在更新的时候被数据中心的智障远程手按了电源¯(ツ)/¯

既然启动不能那就进 Rescue 呗。虽然 CentOS 这系统不怎么样,但是至少也算在原版 ISO 就提供了 Rescue 的选项,还能自动寻找根分区挂载。本来以为小事一桩,准备 chroot 的时候突然冒出来一句 “chroot: cannot run command `/bin/sh’: Exec format error” 就瞬间心里一顿$#%#$^$#@#%#

一般来讲,报错 “Exec format error” 代表二进制格式错误,例如在 32 位系统中执行 64 位二进制文件。但这不可能,首先同样是 x86_64 架构,安装的系统和 ISO 的系统也是同样的版本,磁盘也没有报错,为什么会出现这种问题?

更新时断电导致文件被写坏的可能性并不是没有,但奇怪的是执行 /bin/bash 也报一样的错误。能坏到这种程度怕不是 glibc 挂了…?但总之第一步是要能先进系统瞧瞧究竟,于是心一横,直接用 Live CD 的 /usr 覆盖磁盘上的对应目录。

1
~> cp -r /usr /mnt/sysimage/

然后再 chroot,果然成功进入系统,yum 命令也可以用了。既然是更新过程中断电,那应该先尝试修复未完成的更新。

1
2
3
4
~> yum-complete-transaction
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
No unfinished transactions left.

啊咧。

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
~> yum update
--> Finished Dependency Resolution
Error: Multilib version problems found. This often means that the root
cause is something else and multilib version checking is just
pointing out that there is a problem. Eg.:

1. You have an upgrade for libselinux which is missing some
dependency that another package requires. Yum is trying to
solve this by installing an older version of glibc of the
different architecture. If you exclude the bad architecture
yum will tell you what the root cause is (which package
requires what). You can try redoing the upgrade with
--exclude glibc.otherarch ... this should give you an error
message showing the root cause of the problem.

2. You have multiple architectures of glibc installed, but
yum can only see an upgrade for one of those arcitectures.
If you don't want/need both architectures anymore then you
can remove the one with the missing update and everything
will work.

3. You have duplicate versions of glibc installed already.
You can use "yum check" to get yum show these errors.

...you can also use --setopt=protected_multilib=false to remove
this checking, however this is almost never the correct thing to
do as something else is very likely to go wrong (often causing
much more problems).

Protected multilib versions: glibc..... != glibc.....
kbd... x86_64 != kbd ... x86_64

…还真是。

进一步了解到这个系统并没有安装 32 位包,那么这个 32 位的 glibc 是哪儿来的呢?yum check 无果,更何况还有一个奇怪的 kbd 包甚至跟 32 位毫无关系。看来坏的不轻,但是既然确定系统中没有 32 位包,那么可以继续莽到底了。

1
2
3
4
5
6
7
# 完成所有未更新的包
~> yum update --setopt=protected_multilib=false
# 为重装系统做准备
~> mkdir /root/tmp
~> cd /root/tmp
# 重装所有包
~> yum reinstall * --setopt=protected_multilib=false

一通操作猛如虎,第一次重装所有包时还会报错 ldconfig 一些库是空文件,第二次完整重装就看不到报错了。看来可行,果断重启,一堆 OK 之后看到了 login 提示。

1
2
3
4
5
6
7
8
localhost login: root

Login incorrect

Login incorrect


Login incorrect

嗯嗯嗯???我还没输入密码呢???

还没输入密码就报错,至少不是 PAM 的问题。在那之前呢?应该是 systemd-logind 了。幸运的是网络可以正常启动,SSH 能够正常登入系统。如果网络或者 SSH 也不行,那就只好再次进 rescue 了。

系统里很多本来应该有的底层服务没有启动,例如 systemd-logind。想查看这货的状态,结果报错:

1
Failed to activate service 'org.freedesktop.systemd1': timed out

咦?systemd 自己都没正常启动,看看日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
localhost dbus-daemon[1166]: Encountered error 'Error in file /etc/dbus-1/system.d/org.freedesktop.hostname1.conf, line 1, column 0: no element found
' while parsing '/etc/dbus-1/system.d/org.freedesktop.hostname1.conf'
localhost dbus-daemon[1166]: Encountered error 'Error in file /etc/dbus-1/system.d/org.freedesktop.import1.conf, line 1, column 0: no element found
' while parsing '/etc/dbus-1/system.d/org.freedesktop.import1.conf'
localhost dbus-daemon[1166]: Encountered error 'Error in file /etc/dbus-1/system.d/org.freedesktop.locale1.conf, line 1, column 0: no element found
' while parsing '/etc/dbus-1/system.d/org.freedesktop.locale1.conf'
localhost dbus-daemon[1166]: Encountered error 'Error in file /etc/dbus-1/system.d/org.freedesktop.login1.conf, line 1, column 0: no element found
' while parsing '/etc/dbus-1/system.d/org.freedesktop.login1.conf'
localhost dbus-daemon[1166]: Encountered error 'Error in file /etc/dbus-1/system.d/org.freedesktop.machine1.conf, line 1, column 0: no element found
' while parsing '/etc/dbus-1/system.d/org.freedesktop.machine1.conf'
localhost dbus-daemon[1166]: Encountered error 'Error in file /etc/dbus-1/system.d/org.freedesktop.systemd1.conf, line 1, column 0: no element found
' while parsing '/etc/dbus-1/system.d/org.freedesktop.systemd1.conf'
localhost dbus-daemon[1166]: Encountered error 'Error in file /etc/dbus-1/system.d/org.freedesktop.timedate1.conf, line 1, column 0: no element found
' while parsing '/etc/dbus-1/system.d/org.freedesktop.timedate1.conf'
...

病的不轻。这些文件内容消失了,重装包并不会覆盖它们。只好手动来覆盖了。

1
2
3
4
5
6
7
# 安装必要工具
~> yum install yum-utils rpm2cpio
# 获取并解包 rpm
~> cd /root/tmp
~> yumdownloader dbus systemd
~> rpm2cpio dbus-1.10.24-15.el7.x86_64.rpm | cpio -idmv
~> rpm2cpio systemd-219-78.el7_9.5.x86_64.rpm | cpio -idmv

于是得到了这些包的内容。手动将 etc/dbus-1 目录下的所有文件覆盖到文件系统,然后执行 kill 1

1
2
3
4
5
6
7
localhost systemd[1]: Received SIGTERM from PID 4332 (bash).
localhost systemd[1]: Reexecuting.
localhost systemd[1]: systemd 219 running in system mode. (+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ
localhost systemd[1]: Detected architecture x86-64.
localhost dbus[1166]: [system] Successfully activated service 'org.freedesktop.systemd1'
localhost systemd[1]: Started Login Service.
localhost systemd-logind[4723]: New seat seat0.

仔细检查一遍,各种服务都在正常启动了!

(๑•̀ㅂ•́)و✧

后记:别没事儿更新生产用系统。

后记 2:西方国家的数据中心远程手没有一个靠谱的。

又到了喜闻乐见帮人修系统的时间。

June 06, 2022 03:22 AM

June 03, 2022

Felix Yan

萌新的 PolarFire SoC Icicle Kit 初体验

这两天翻出来了去年代收的 PolarFire SoC Icicle Kit。因为隔壁的 FPGA 大佬们看不上这块板子,我打算尝试物尽其用一下,目标只是用板子上的 RISC-V 核启动 Arch Linux RISC-V 的 rootfs 测试(把它当作一块 SD 卡槽没有问题、并且带 PCIE 的 HiFive Unleashed 来用。隔壁嵌入式群的大佬们:买椟还珠!)。如此便开始了年轻人的 FPGA 初体验(可能还是不能算)。

噩梦的开始

一开始尝试的当然是最新版的 Yocto 镜像,毕竟这是“官方”的 Linux 镜像。结果刷完后立刻遇到了启动失败:

一开始我还以为是 SD 卡坏了。在多次尝试未果后……

当时的猜测是(不一定对),可能因为板子上 FPGA 部分(抱歉,我不知道专业的称呼)不够新,所以我打算刷一下 HSS。结果这成为了噩梦的开始。

可怕的“硬件”工具链

我最初参考的文档来自 U-boot:https://u-boot.readthedocs.io/en/latest/board/microchip/mpfs_icicle.html

这份文档可能已经颇为过时,里面编译 HSS 的部分从一开始就找不到名叫“icicle-kit-es”的 BOARD.

在我加上 mpfs- 前缀,并根据后续报错依次按照我的 CROSS 工具链目标修改了 PLATFORM_RISCV_ABI=lp64d PLATFORM_RISCV_ISA=rv64gc 之后,我遇到了第一个大魔王:SoftConsole

好在这个工具可以无需注册直接下载。

顺利安装完成后,按照要求设置 SC_INSTALL_DIR,我终于看到了……下个错误:缺少 fpgenprog。

由于不想安装完整的 Libero SoC(一个巨大的需要折腾 license 的开发工具集),我试图去下载这里提到的 Program Debug Tool。从文档上找到下载链接点开后,看到了这样的悲剧:

它没了。

后面提到的 Programming and Debug 工具下载也藏在了注册墙后面,我考虑转向其他思路试试,先暂时没有继续了。

(另:关于 Libero SoC 的安装会有多坑,可以参考这位受害者的体验: https://www.cnx-software.com/2021/10/25/installing-libero-soc-in-ubuntu-and-windows-10/

Arch 内核的陨落

由于目前 Arch Linux RISC-V 基本没有处理 bootloader 部分的工作,我打算先试试直接使用旧版 Yocto 自带的 U-boot 来启动 Arch 的内核、rootfs。由于不熟悉 U-boot 和这些嵌入式镜像格式,我先花了点时间学习 U-boot 的启动逻辑 和操作 uImage 的常用命令等。

Yocto 的镜像里有一个很小的 /boot 分区,里面有 boot.scr.uimg 脚本和 fitImage 镜像。我遇到的第一个问题是,这个 FAT16 的 /boot 分区装不下 Arch 的 kernel,而且把 FAT16 扩容并转换为 FAT32 是 libparted 等常用工具不支持的操作。

在艰难尝试了许多次之后,我确定了这个分区后面的那个类型为 BIOS boot 但 GParted 抱怨为损坏分区的分区可能存放的是 uboot 本体(或者含它的 HSS?尚未仔细研究)。因此向后扩容 /boot 的想法可能不大靠谱。我只好退而求其次,把第一个分区删掉,直接把最后的 rootfs 所在分区标记为 legacy_boot

然后我复制了 Unmatched 上的 extlinux.conf 并做了相应修改(内核版本、UUID 等)。第一次尝试启动失败在缺少了两个环境变量:

后来在大佬们的帮助下,我找到了设置它的办法。其实正确的值就来自上面的 boot.scr.uimg,设置前者为 ${ramdisk_addr_r},后者则只需要设置一个足够大的值。

然而这次 Arch 的内核挂在了无限循环刷屏 L2CACHE ERROR 上。

由于大佬觉得这个问题不好解决,我决定先暂时放弃内核,用 Yocto 的内核启动 Arch rootfs 测试。

用魔法打败魔法

在现在的情况下,直接用原版的 boot.scr.uimg 和 fitImage 组合至少存在这样的问题:

  • 1、Arch rootfs 期待一个 rw 的 /。默认的 ro 环境会导致启动后一大堆服务失败、SSH Host Key 未生成等奇怪的问题。
  • 2、默认的 root= 设置为了 SD 卡的第三个分区(/dev/mmcblk0p3)。但经过上面的操作,SD 卡上现在已经只剩两个分区了。

为了修改 cmdline 解决这些问题,我在 U-boot shell 和 uEnv.txt 里鼓捣了各种操作都没有成功。看起来这个内核是把 cmdline 写死编译进去了。

在不重新编译内核的情况下,怎么更改里面写死的 cmdline 呢?那当然是直接修改二进制了!

首先把 fitImage 拆开,用 dumpimage 工具提取出里面的内核和 fdt 文件。从输出中可以看到,内核是 gzip 压缩过的:

用 gzip 解压这个文件,然后用 vim 打开:

诶嘿,我们的 cmdline 找到了。直接修改为想要的值(分区号直接替换,然后换掉一个应该影响不那么大的参数写上 rw),并保持字符串总长度不变(填空格补齐)。一共有两处,做同样处理即可。

修改完后,直接把未压缩的内核、fdt 复制到 rootfs 内,然后添加一个 extlinux 启动项:

label yocto
        menu label Arch Linux with Yocto kernel
        linux /boot/yocto.kernel.patched
        fdt /boot/yocto.dtb

Bingo!成功启动。

至此,最初的目标有了一个最低限度的成果。

尾声/题外话

感谢 PLCT 实验室、TUNA、AOSC 等社区一直以来的帮助,肥猫现在可能从完全不懂嵌入式稍微进步了一点点。

没想到折腾这块难啃的板子会成为我荒废三年多的博客再次更新的契机。我的这一轮 Arch Linux RISC-V 移植项目从最初尝试至今也已经超过三年,而今年年底就是我进入 Arch 十周年了。希望自己下次写博客不要又是三年以后 😛

The post 萌新的 PolarFire SoC Icicle Kit 初体验 first appeared on Felix's Blog.

by Felix Yan at June 03, 2022 07:26 PM

May 12, 2022

中文社区新闻

撤销以 wireplumber 替代 pipewire-media-session

两天前, wireplumber 新的打包会提示替代 pipewire-media-session 包,因为后者作为 PipeWire 的会话管理器,它的上游已经停止更新不再会有新版本了。不幸的是这个步骤有些仓促。
我们的 pipewire 音频包(pipewire-alsa, pipewire-jackpipewire-pulse)也提供了设置让 media-session 激活 PipeWire 的音频特性。如果没有按照这些包并且没有这些配置,PipeWire还是能用来处理屏幕录制而不干扰 ALSA 或 PulseAudio 。
WirePlumber 无视了这个机制,会总是试图让 PipeWire 接管音频设备,意味着 PulseAudio 和单用 ALSA 的用户的音频坏了。
已经撤销了包替换的修改,但是我们仍然在试图寻找一个更好的方案迁移到 WirePlumber 。如果你目前不使用 PipeWire 的音频功能,并且系统中已经安装了 wireplumber,请重新安装 pipewire-media-session 包后重启,以恢复音频功能。

pacman -Syu pipewire-media-session

by farseerfc at May 12, 2022 11:29 PM

May 11, 2022

Lainme

在CentOS7上安装最新版WPS

最新版的WPS需要GLIBC2.18以上,而CentOS7的版本是2.17,因此需要自行编译一个新的GLIBC。之前在CentOS 7上安装Dropbox这篇文章中写过如何编译和使用新的GLIBC,但当时写的方法不是很完全,这里再写一个更完整的版本。

依然先编译GLIBC,但最后一定要安装locale相关文件,不然WPS无法打开带有中文文件名的文件。

wget https://ftp.gnu.org/gnu/glibc/glibc-2.27.tar.gz
tar xzvf glibc-2.27.tar.gz
cd glibc-2.27
mkdir build
cd build
../configure --prefix=/opt/glibc-2.27
sudo make
sudo make install
sudo make localedata/install-locales

编译完成后将系统的库都链接过来,一劳永逸

cd /opt/glibc-2.27
sudo mv lib lib64
sudo mkdir lib
cd lib
sudo ln -s ../lib64/* .
sudo ln -s /lib64/* .

然后安装WPS并用patchelf修改WPS的各个可执行文件

cd /opt/kingsoft/wps-office/office6
sudo patchelf --set-interpreter /opt/glibc-2.27/lib/ld-linux-x86-64.so.2 et
sudo patchelf --set-interpreter /opt/glibc-2.27/lib/ld-linux-x86-64.so.2 wpp
sudo patchelf --set-interpreter /opt/glibc-2.27/lib/ld-linux-x86-64.so.2 wps
sudo patchelf --set-interpreter /opt/glibc-2.27/lib/ld-linux-x86-64.so.2 wpscloudsvr
sudo patchelf --set-interpreter /opt/glibc-2.27/lib/ld-linux-x86-64.so.2 wpsd
sudo patchelf --set-interpreter /opt/glibc-2.27/lib/ld-linux-x86-64.so.2 wpsoffice
sudo patchelf --set-interpreter /opt/glibc-2.27/lib/ld-linux-x86-64.so.2 wpspdf

by lainme (lainme@undisclosed.example.com) at May 11, 2022 10:35 PM

May 09, 2022

Leo Shen

Have fun with ZFS: Maintenance and Error Recovery

Now we have a pool, running smoothly. Let's learn a bit about regular maintenance, and what to do when things go wrong. Monitoring pool health The simplest way to check pool status is to use zpool status to view the pool status manually. This may be sufficient for small scale, non-critical systems; but for critical systems, we will want to know something went wrong the instant such event happens.

May 09, 2022 05:21 PM

中文社区新闻

QEMU >= 7.0.0 修改了拆包方式

从 qemu 7.0.0 更新开始我们用元包(meta package)将它拆包成了更细分的打包。

  • qemu包现在是被元包 qemu-base, qemu-desktopqemu-full 提供的虚包。
  • 7.0.0之前的qemu包的功能现在被qemu-desktop代替。
  • qemu-headless包的功能现在被qemu-base代替。
  • qemu-arch-extra包和qemu-headless-arch-extra包的功能现在被qemu-emulators-full代替。
  • 元包qemu-full包提供所有 QEMU 相关的包(除了qemu-guest-agent)

by farseerfc at May 09, 2022 10:53 AM

May 07, 2022

Alynx Zhou

装了台 NAS

本来我是不打算装 NAS 的,甚至都把我的星际蜗牛关了,因为我觉得我又没有网络多人协作的需求,而且我醒着的时候我的台式机也醒着,我睡着了又不会用到网络存储。不过自从我买了相机开始拍照片录视频,存储空间就越来越紧张了,先是把我机箱里的硬盘从 2T 直接升级到 8T,又觉得没有冗余始终心慌慌。偏偏我现在这个机箱哪里都不错,就是机械硬盘位不太充裕。为了扩展存储空间,也只能装一个 NAS 了。

决定好装 NAS 之后比较难的就是选硬件了,我肯定不会买那些闭源拖拉机的,我要装 Linux。肯定排除掉星际蜗牛,因为我实在不放心用那个背板带四块硬盘。然后对这种低功耗的设备用那种主板和 CPU 集成的赛扬应该不错,还免了主动散热,问题是我不知道在哪里能买到。所以还是考虑普通零售的硬件,不过大部分零售的硬件都有点性能过剩了,挑来挑去挑出下面的一套配置,比较个人倾向,不建议大家直接拿过来用。

  • CPU:i3-10105T 645
  • 主板:七彩虹 B460I 579
  • 机箱:乔思伯 N1 669
  • 电源:银欣 SX500-LG 549
  • 散热:利民 AXP90-X47 139
  • 内存:光威 8G DDR4 2666 169x2
  • 网卡:EDUP PCI-E 2.5Gbps 网卡 89
  • SSD:闪迪 至尊高速 NVMe 256G 259
  • HDD:西数 HC550 16T 1498x4
  • HDD:希捷 酷鹰 2T 375
  • 扩展:乐扩 M.2 A+E Key 转 SATA 75

下面简单介绍一下为什么选这套。

首先装 NAS 第一个要决定的不是别的而是硬盘,毕竟你这设备就是拿来放硬盘的,因为台式机里面已经有 8T 的硬盘了,我也不想再经历换硬盘时候拷贝数据的痛苦,所以还是直接上了 16T 的。这种容量建议直接购买企业盘,不过也就和静音说拜拜了。没选希捷的银河系列不是因为别的,只是因为西数 HC 系列便宜点而已。选 4 块盘而不是 3 块的原因也很简单,我不会用 ZFS,肯定要用 btrfs,但是 btrfs 的 RAID5 有 bug,于是性能容量冗余的平衡点就是 RAID10 了,那就需要 4 块硬盘起步。

决定好硬盘之后就得选合适的机箱,毕竟你得能把硬盘装进去,不过我对大部分的 NAS 机箱都不满意,有些类似个人产品或者众筹的机箱比如什么宝藏盒TANK之类的看起来很能装,但是造型不伦不类,做工也一般般,然后可能迎广或者万由有一些成品 NAS 机箱看起来不错,但是基本上是高配蜗牛,而且我实在不想用 flex 电源。其实某种意义上我是先看中乔思伯 N1 这个机箱才决定装的 NAS,我对这个牌子其实没什么好感,但这个机箱实在是过于好看,而且可以装 SFX 或者 SFX-L 电源,还是有很多零售产品可以选的。

选好机箱以后就是机箱里能塞进什么硬件就塞什么硬件了,乔思伯 N1 和大部分 NAS 机箱一样都只能放 ITX 主板,但 ITX 主板可选的实在不多。首先我想要一个内置核显的 CPU,因为最简单的调试办法肯定是给机箱接上键盘鼠标显示器,也比较方便调整 BIOS,而 ITX 只有一个宝贵的 PCIE 插槽,再说 NAS 多半也没必要上个亮机卡耗电。而 AMD 那边 200GE 太弱了,Ryzen Pro 系列又太强了,实在不知道选什么比较好,而且也不容易买到最低配的 Ryzen Pro CPU,所以就选 Intel i3 了,正好还有带 T 的低功耗版本。同时 10 代的主板还比较好买,也就是说能买到新的。虽然这台机箱前面有 USB-C,不过实际上是和 USB 3.0 共享一个插座,所以也不需要主板有 USB-C 的插座。七彩虹这块 B460 的优点在于后面还是有一个标准的 USB-C 接口,以及能在京东自营买到。

SFX 电源虽然不如 ATX 电源好选,但是仍然比 flex 电源好选太多了,而且这台机器还可以放下 SFX-L 的电源,12cm 的风扇肯定比 4cm 要安静很多。于是我就在京东随便挑了一个观感不坏的牌子的全模组 SFX-L 电源里功率最低的。不过装这个机箱的时候最好注意一点,买的电源的 24pin 线最好不要是那种捆成圆柱型的,因为需要从主板边上极小的缝隙里拉过来,排线肯定比圆柱线要容易过,银欣这款是排线。当然你是定制模组线的有钱人当我没说。

这款机箱最高可以支持 70mm 的下压散热,所以其实完全可以买利民那款 12cm 的散热器,不过我觉得也无所谓了,我比较担心 12cm 的下压散热会导致在主板上装内存和 SSD 比较困难,9cm 的也够用了。

内存其实是随便选的,因为我不用 ZFS,其它文件系统对于 ECC 也没那么严格的依赖。然后 i3 和 B460 也不会支持更高的频率了。

考虑到我的台式机上有两个网口,分别是 1Gbps 和 2.5Gbps 的,我就在想可不可以把 NAS 和台式机通过 2.5Gbps 直接连起来(路由器什么时候普及 2.5Gbps 啊),这样速度基本上和访问本机的机械硬盘没有太大差距。但是主板厂家实在是太抠门了,本来 ITX 主板型号就不多,内置 2.5Gbps 网卡的就更少了。虽然让一个 2.5Gbps 的网卡占着 PCI-E x16 插槽实在是有点浪费,但也没别的选择了。至于万兆,考虑到要使用专门的线和专门的网卡,还得给台式机也装一个,算了吧,反正机械硬盘的阵列也跑不满万兆。

然后系统盘只要随便搞一个 NVMe 就可以了,反正安装一个 Arch Linux 不会占用多少空间。实际上比较头痛的是怎么接上所有的硬盘。这款机箱有 5 个在背板上的 3.5 寸硬盘位和一个单独的 2.5 寸硬盘位。大部分 ITX 主板都只有 4 个 SATA 接口,本来我觉得接 4 盘的阵列够了,但是后来我发现北邮人 pt 可以使用我的交大学校邮箱注册……要是挂 PT,我觉得还是单独放一块硬盘比较好,那接口就不够用了。比较靠谱的办法是买 HBA 卡把 PCI-E 转成 SATA,但是已经被 2.5 Gbps 网卡用掉了。我还没有发现同时有网卡和 SATA 的 PCI-E 扩展卡。还有一个方案是 M.2 转 5 个 SATA 的转接卡,其实倒也可以用一块 2.5 寸的 SSD 当作系统盘,然后解放出这个 M.2 接口,不过我实在不放心把 4 盘阵列放在这种转接卡上……而且我估计系统盘也是不能接在转接卡上的。当然你可以买一块有 2.5 Gbps 网卡的 ITX 主板,不过那基本只有 Z590 可选了,最便宜的华擎 Z590 ITX 也要 1400。所以我最后采用的方案是放弃掉无线网卡,反正 NAS 摆在路由器附近可以拉网线,然后把无线网卡下面那个 A+E Key 的 M.2 接口利用上。有这样的转接卡,可以转接出两个 SATA 口,虽然我只要一个就够了。

不过当我拿到转接卡的快递之后发现安装还是有点麻烦。最好的办法是拧下无线网卡之后发现那个保护罩可以拆掉一侧的面板,然后可以把无线网卡从保护罩上拿下来再换成转接卡。但是那个固定无线网卡的螺丝实在太紧了我拧不下来,只能放弃这个保护罩,反正转接卡应该也不会受到什么外力,就在我把转接卡插好打算开机之前,我发现还是有点问题……虽然 SATA 接口已经够矮了,但是里面那个用不上的 SATA 接口还是顶住了前面的音频接口,导致转接板是歪的。其实可以淘宝再买一个 M.2 A+E Key 的延长线,不过那样我纠结的就是把延长出来的转接卡固定在哪里了。不管了!反正我就要一个 SATA 口,用刀把里面的 SATA 口的顶端削掉吧!虽然削的很粗糙但是还是达到了效果,然后用一块胶带把那个口粘住,防止 SATA 接口里面的金属触点接触到音频口保护壳而短路,再把一块泡沫放到转接板后面顶住转接板,我觉得就差不多了。

直接装上是歪的:

1.jpg

“高个的 SATA 接口被我给锯了,比矮个的还矮!”:

2.jpg

“磨”改之后的效果,应该没问题吧:

3.jpg

最后就是绞尽脑汁把各种线塞到 ITX 机箱里面,实在是太痛苦了。对了,这个机箱自带的风扇接线不是很长,而它那个硬盘背板上的插针又是满速的,如果你要是想插到主板上进行调速,最好自备一根 4pin 风扇延长线。

剩下就没什么好说的了,毕竟我不打算在这里复述一遍 Arch Wiki。我对 btrfs 使用的参数是 -m raid1c3 -d raid10,然后在系统里设置了 samba 和 transmission 的daemon。最后附上一些装完的照片。

4.jpg

5.jpg

6.jpg

by Alynx Zhou (alynx.zhou@gmail.com) at May 07, 2022 09:49 AM

April 09, 2022

Leo Shen

Have fun with ZFS: Tuning

We now have a working ZFS pool. Now, we will do some tweaking so that our pool can perform as good as possible. Disable atime (access time) By default, ZFS records the latest access time of a file. In many circumstances, this isn't actually useful and negatively impact performance. If you're not using applications that rely on access time (notable example being some mail clients, which use atime to see if mail has been read), we can turn it off to reduce disk writes.

April 09, 2022 06:25 PM

April 05, 2022

Lainme

给手机设置受限的SFTP服务器

准备允许自己的手机通过SFTP读写服务器上的一些文件,但无论是密码登陆还是密钥登陆都不能让我很放心,所以做了一些额外的配置并且要求

  • 手机只能登陆SFTP不能登陆SSH,且只能读写指定的目录
  • 不能影响任何现有的功能,比如我桌面端的登陆

先给手机建立一个专门的目录,目录及上级目录都要是root所有(为chroot做准备)

sudo mkdir /home/mobile

然后将所有允许读写的目录都mount bind过来。比如目录/home/[用户名]/Documents

sudo mkdir /home/mobile/Documents
sudo chown [用户名]:[用户名] /home/mobile/Documents
sudo mount --bind /home/[用户名]/Documents /home/mobile/Documents

然后设置SSH服务器如下(只显示关键配置)

Port 22
Port 2333

Subsystem sftp internal-sftp

Match LocalPort 2333
        AllowGroups [用户名]
        AllowUsers [用户名]
	X11Forwarding no
	AllowTcpForwarding no	
        AuthorizedKeysFile /home/[用户名]/.ssh/authorized_keys_sftp
	ChrootDirectory /home/mobile/
	ForceCommand internal-sftp

这里除了原本的22端口额外设置了一个2333端口,并且在连接这个端口的时候用authorized_keys_sftp来验证,chroot到/home/mobile目录,并且只允许SFTP。

然后重启SSH服务器

sudo systemctl restart sshd

在防火墙上放行2333端口,并将一组密钥对的公钥放到/home/[用户名]/.ssh/authorized_keys_sftp,私钥放到手机连接SFTP的APP上就可以连接了。密钥也可以设置Passprase。

by lainme (lainme@undisclosed.example.com) at April 05, 2022 07:41 PM

April 04, 2022

Lainme

用Syncthing进行文件同步

Syncthing是点对点的开源文件同步软件,在Linux上只需要将软件下载下来解压缩执行就可以直接使用,但这样设备的发现和数据中继都是用的官方提供的服务器,如果想要信息和数据更为可控可以搭建自己的发现和中继服务器。另外,虽然syncthing不需要中心客户端,但为了能时刻保持数据的完整以及简化多设备之间的同步,最好还是能有一个中心客户端作为核心24小时在线。

如果完全采用中心客户端的形式(即所有从属客户端都只和中心客户端连接,相互之间不连接),并且中心客户端安装在服务器上,也可以不要发现和中继,直接用IP地址直连。

准备工作

以Debian/Ubuntu为例,可以直接添加官方APT源来安装。

添加APT源的key

curl -s https://syncthing.net/release-key.txt | sudo apt-key add -

添加APT源

echo "deb https://apt.syncthing.net/ syncthing stable" | sudo tee /etc/apt/sources.list.d/syncthing.list

更新一下

sudo apt update

发现服务器

安装发现服务器

sudo apt install syncthing-discosrv

默认的设置可以直接使用,所以激活和启用服务就行了

sudo systemctl enable stdiscosrv
sudo systemctl start stdiscosrv
sudo systemctl status stdiscosrv

服务启动后会打印出设备ID(用sudo systemctl status stdiscosrv就能看到),只需要在防火墙上打开8443端口,就能用

  https://[服务器IP]:8443/?id=[设备ID]

作为发现服务器使用了。

中继服务器

安装软件

sudo apt install syncthing-relaysrv

中继服务器需要修改一些配置,编辑/etc/default/syncthing-relaysrv,修改如下

# Default settings for syncthing-relaysrv (strelaysrv).
NAT=false

## Add Options here:
RELAYSRV_OPTS=-pools=""

这会禁用UPnP/NAT-PMP,同时将中继服务器设置为私有(不加入公共池)。

激活和启用服务

sudo systemctl enable strelaysrv
sudo systemctl start strelaysrv
sudo systemctl status strelaysrv

和之前类似,服务启动后会打印出中继服务器的完整地址,例如

relay://0.0.0.0:22067/?id=[设备ID]&pingInterval=1m0s&networkTimeout=2m0s&sessionLimitBps=0

不过里面的IP是“0.0.0.0”,后面的参数一般也不需要。在客户端填中继服务器信息的时候只需要以下形式。当然,22067的端口也需要在防火墙上打开。

relay://[服务器IP]:22067/?id=[设备ID]

中心客户端

同样从APT源安装

sudo apt install syncthing

然后激活和启用

sudo systemctl enable syncthing@[用户名].service
sudo systemctl start syncthing@[用户名].service
sudo systemctl status syncthing@[用户名].service

这里的用户名最好不要用root,一般而言是你希望放文件的用户名,最好和你其他客户端的用户一致(对于Linux而言不一致可能有账户权限问题)。客户端启动后也会显示出设备ID,可以填入其他客户端中进行同步。同时最好能在防火墙和路由器上打开22000端口,以便能够直连。

客户端启动后需要在Web界面做一些配置。如果中心客户端是安装在服务器上的,可以用SSH隧道把客户端的端口绑定到本地,就能用本地浏览器打开设置了。绑定的方法是

ssh -L [本地想用的端口]:localhost:8384 [服务器地址]

本地端口一般不要是8384,否则会和本地的客户端冲突。绑定后在本地浏览器输入

http://localhost:[本地想用的端口]

就可以打开服务器上的客户端界面。

安装在服务器上的中心客户端因为必然可以直连,可以不做什么设置,禁用Global Discovery和Enable Relaying就行。

如果是其他机器,基本设置如下:

  • 激活Global Discovery,但将地址改成自己的。
  • 激活Enable Relaying,但将监听栏的地址改成
tcp://0.0.0.0:22000, [你自己的Relay地址]

从属客户端

在自己的其他设备上安装syncthing客户端,设置上和中心客户端一样,然后添加中心客户端。如果中心客户端是安装在服务器上的,添加的时候可以把地址从dynamic改成tcp://[服务器IP]:22000

by lainme (lainme@undisclosed.example.com) at April 04, 2022 03:47 PM

April 01, 2022

百合仙子

从 getmail6 到 offlineimap

本文来自依云's Blog,转载请注明。

起因

上个月收到这样一封邮件:

自 5 月 30 日起,您可能会无法再访问那些采用安全性较低的登录技术的应用

意思就是说,Google 觉得把密码直接交给邮件客户端,权限太大,不够安全。所以要用户改用基于 OAuth2 的认证方式,只给程序邮件相关的权限。哦,你说应用专属密码?要用那个必须得启用两步验证——也就是意味着遇到灾难的话,我无法从一无所有的状态开始恢复。

从 POP 到 IMAP

getmail6 只支持使用 XOAUTH2 认证的 IMAP 协议,并不在 POP 协议上支持这个(不知道是否有可能)。所以我得换 IMAP 协议了。

具体操作步骤在 getmail6 的示例配置中有写。简单来说就是自己去申请个桌面软件的 app 信息,然后给自己的用户添加试用权限,再通过 OAuth2 获取 refresh token 和 access token,就能登录了。getmail6 自带了个 getmail-gmail-xoauth-tokens 程序用来走 OAuth2 流程,不需要另外安装程序来处理的同时也可以给其它程序使用。

所以我的 msmtp 配置就不用麻烦了,改两行配置就好:

auth oauthbearer
passwordeval getmail-gmail-xoauth-tokens ~/.getmail/gmail/lilydjwg@gmail.com.json

但是呢,虽然邮件是收回来了,IMAP 和 POP 还是挺不一样的。POP 没有「文件夹」的概念,所有收到的邮件,不管我有没有在 Gmail 网页或者客户端上阅读、归档,不管它进了哪个标签(文件夹)(「垃圾邮件」除外),我都会收到,并且把收过的邮件标记为已读。

而通过 getmail6 使用 IMAP 收取,我能做的选择就是,要不要把收过的邮件标记为已读或者删掉(可在 Gmail 中设置为归档)。不管如何,getmail6 只会收到它运行时位于收件箱中的邮件。如果我选择标记为已读的话,那么已读邮件也不会被 getmail6 收到。所以标已读的话,我在别的地方看过的邮件不会被收到。删除的话会好点,收过的邮件归档了,还省得我手动去归档,但是在别的地方,收过的邮件和已处理的邮件没了区分。

所以不如上 offlineimap,完全同步好了。

从 getmail6 到 offlineimap

offlineimap 的配置就比较复杂了,一是要对文件夹名进行转码,二是我要设定只同步指定的文件夹:收件箱、Maillist 和垃圾邮件。要同步垃圾邮件的原因是,Gmail 经常把有用的邮件往里边扔。

[general]
accounts = gmail
maxsyncaccounts = 10
socktimeout = 60
pythonfile = ~/.offlineimap/offlineimap.py

[Account gmail]
localrepository = gmail-local
remoterepository = gmail-remote

[Repository gmail-local]
type = GmailMaildir
localfolders = ~/.Maildir
filename_use_mail_timestamp = no
nametrans = gmail_nametrans_local

[Repository gmail-remote]
type = Gmail
remoteuser = lilydjwg@gmail.com

sslcacertfile = /etc/ssl/cert.pem
ssl = yes
starttls = no

oauth2_client_id_eval = get_client_id("lilydjwg@gmail.com")
oauth2_client_secret_eval = get_client_secret("lilydjwg@gmail.com")
oauth2_access_token_eval = get_access_token("lilydjwg@gmail.com")

nametrans = gmail_nametrans_remote
folderfilter = gmail_folderfilter
import os
import json
import subprocess

_LOADED_DATA = {}

def _load_data(account):
  with open(os.path.expanduser(f'~/.getmail/gmail/{account}.json')) as f:
    _LOADED_DATA[account] = json.load(f)

def get_client_id(account):
  if account not in _LOADED_DATA:
    _load_data(account)
  return _LOADED_DATA[account]['client_id']

def get_client_secret(account):
  if account not in _LOADED_DATA:
    _load_data(account)
  return _LOADED_DATA[account]['client_secret']

def get_access_token(account):
  cmd = [
    'getmail-gmail-xoauth-tokens',
    os.path.expanduser(f'~/.getmail/gmail/{account}.json'),
  ]
  out = subprocess.check_output(cmd, text=True)
  return out

def gmail_nametrans_remote(foldername):
  foldername = foldername.removeprefix('[Gmail]/').encode('ascii').decode('imap4-utf-7')
  if foldername == '垃圾邮件':
    foldername = 'Spam'
  elif foldername == '草稿':
    foldername = 'Drafts'
  return foldername

def gmail_nametrans_local(foldername):
  if foldername == 'Spam':
    foldername = '[Gmail]/垃圾邮件'
  elif foldername == 'Drafts':
    foldername = '[Gmail]/草稿'
  return foldername.encode('imap4-utf-7').decode('ascii')

def gmail_folderfilter(foldername):
  foldername = foldername.encode('ascii').decode('imap4-utf-7')
  return foldername in [
    'INBOX', '[Gmail]/垃圾邮件', '[Gmail]/草稿',
    'Maillist',
  ]

然后在 Gmail 那边创建个过滤器,把来自邮件列表的邮件扔到「Maillist」文件夹里去。搜索「 (to:@googlegroups.com OR from:vim-dev-github@256bit.org OR to:@zsh.org)」并创建过滤器,选择操作「跳过收件箱、 应用标签“Maillist”」即可。注意以后在修改的时候直接修改「包含字词」字段即可,并且记得「OR」「AND」「NOT」之类的操作符需要改回大写。

这样做完之后还有个问题:一封邮件同步到 offlineimap 后,我在 mutt 里阅读并删掉了它。offlineimap 一看,哟,邮件没了,得在服务器上删掉。Gmail 根据我的设置,把从 IMAP 删除的邮件归档,但是它并没有选项来标记为已读。所以这封邮件最终会以未读的状态躺在「所有邮件」里。

于是我去 App Script 里写了个脚本,把这些邮件标记为已读:

function mark_as_read() {
  const threads = GmailApp.search('is:unread AND NOT (label:Maillist OR in:inbox)', 0, 30)
  for(const thread of threads) {
    Logger.log('Marking as read: %s', thread.getFirstMessageSubject())
    thread.markRead()
  }
}

手动运行一遍之后,就可以在左侧栏里给它设置个触发器定时跑啦。

新邮件提示

使用 offlineimap 之后,最大的问题变成了邮件散落在不同的账号下的不同文件夹,一个个过去翻看太低效了。所以我就给 zsh 设置了提醒:

mailpath=(
  ~/.Maildir/INBOX/new'?GMail has a new message.'
  ~/.Maildir/Spam/new'?GMail has a new spam.'
  ~/.Mail/inbox'?New local mails.'
)

问号前边是邮箱的路径,后边是提示信息。之前那个 mbox 格式的邮箱我还留着,用来收取来自本地 cron 的邮件。

一个小问题是,procmail 用不成了。不过现在各种无用的网站消息也少了,所以不需要通过 procmail 处理垃圾邮件了(新浪微博我没有使用邮件注册、LinkedIn 和 Twitter 消停了、网易和QQ邮箱不用了)。现在中文邮件列表也几乎没人用了,我也不用让程序去重写「回复:RE:回复:」这类糟糕的邮件标题和过滤掉自动回复了。

by 依云 at April 01, 2022 06:29 AM

March 26, 2022

百合仙子

微信消息通知的困扰

本文来自依云's Blog,转载请注明。

一直以来,不得不用的微信以其糟糕的通知体验让我十分不爽。

在手机上,我使用的是 Google Play 商店里的微信。在电脑上,我使用的是通过 Wine 运行的 Windows 版本微信([archlinuxcn] 仓库里的 wine-wechat-setup 脚本可用于安装)。

消息通知不及时

这个问题是最近我的手机日渐陈旧之后我才注意到的。表现是,在一段时间(比如一两天)不使用微信之后,收到新的微信消息或者视频通话,可能会延迟几个小时收到通知。在 Android 通知日志中可以确认,收到通知的时间和消息在微信中展示的时间有数小时之差,并不是因为我没有及时看手机。

我的 Telegram 从来不这样丢消息,即使因为后台进程过多 Telegram 被杀之后,通知只会不能在其它端阅读之后被清除,而不会延迟那么久。而微信,即使它还在后台运行着,却经常占着资源不干活,何况消息通知本应走 FCM。

打开微信即清除所有通知

我有一条微信消息,但是我现在不方便立即回复(比如需要使用电脑而我正出门在外),所以我会让那条通知一直留着。其实以前版本的 Android 系统更方便,可以将通知延后一段时间,只是不能自动指定延后的时间比较遗憾,后来被移除真的太可惜了。

然后呢,比如我要进个超市,或者测个核酸,付个钱啥的,只好打开微信扫码呗。结果所有还未处理的通知全部不见了!等忙完当时的事情,回到家里的时候,我就不一定还记得我还有几条微信消息还没处理了。

多端不同步容易错过

我在用电脑的时候,如果不登录电脑版微信的话,那么我将不会注意到手机上的微信有新消息。那就登录电脑版吧,然后我去上个厕所吃个饭,不用电脑的时候又会错过消息。在电脑端登录的时候让手机上也显示消息通知?那样所有消息都要看两遍,而且消息多的时候还得仔细回想某条消息到底是不是已经处理了。

Telegram 的消息同步做得真好啊。你在哪端用,哪端先给你发通知。其它端的消息会晚几秒出现。一旦在任意端读取了消息,另外的端上的相应消息全部都会被取消掉,不会有消息重复的问题(不过最近好像 Android 上的通知取消变得不那么可靠了)。

手机不可离远

现在电脑版微信终于不需要天天在手机上确认登录了。只要我每两三天登录一次,就可以避免把手机扔去充电了、来到电脑前、又去找手机的麻烦事。——我一开始是这么以为的。直到我发现,我刚刚看到通知、正要处理的消息,并没有在电脑版微信里同步出来。

不知道为什么,微信跟电脑用户有仇似的。明明我有电脑了,不需要凑合于手机的小屏幕和戳戳戳的屏幕键盘了,微信还非得把手机给拉过来。

语音通话不支持耳机接听

某天,我因为沉迷于放置型游戏,把手机扔桌子上充上电让它自个儿玩,自己去睡觉了。第二天早上睡正酣的时候,来了个电话,我拿耳机给接了。然后需要通话的另一方不知道怎么想的,没有商量就发起了微信语音通话,这个耳机根本接不到……

我也尝试过让 Google 助理回拨电话,不过使用不熟练,并没有成功。你说我为什么不起床去接?我睡着被电话惊醒了,还没回过神来啊 QAQ。

by 依云 at March 26, 2022 09:55 AM

March 25, 2022

中文社区新闻

Keycloak 17.0.1-2 更新需要手动重新配置

keycloak包在17.0.1-2版本前跑了WildFly服务器。因为上游在正式迁移到Quarkus发布,Arch Linux 也将跟从这一决定。这意味着升级时需要一些手动干预。
配置文件需要从老的 .xml 格式更新到新的 /etc/keycloak/keycloak.conf
在更新之前,停止 keycloak 服务,升级包,并且在开启服务前迁移配置:

systemctl stop keycloak.service
pacman -Syu keycloak
# migrate configuration /etc/keycloak/keycloak.conf
systemctl start keycloak.service

参阅 Keycloak 的 迁移文档 和 Keycloak Quakus 服务器文档

by farseerfc at March 25, 2022 11:53 PM

March 23, 2022

Alynx Zhou

March 18, 2022

Leo Shen

Have fun with ZFS: Setting up storage pool

Now we've explored the benefits and limitations of ZFS, let's create a pool and try it out! This guide will not explore how to use ZFS as root, as it is very distro/OS-dependent. View guides about this topic on your distro/OS's own Wiki or Handbook. Concepts Unlike filesystems that operates on a single disk, partition or logical volume (i.e. ext4 and NTFS), ZFS can span across multiple storage volumes.

March 18, 2022 03:49 PM

March 08, 2022

中文社区新闻

Arch Linux 项目负责人竞选结果

本月我们举行了我们的负责人竞选,结果是没有对我们的当前负责人 Levente Polyák 的异议。根据我们的 选举规则,他将继续连任一个新任期。

恭喜 Levente Polyak 赢得新任期!

by farseerfc at March 08, 2022 02:05 PM

百合仙子

Qt 的字体渲染问题

本文来自依云's Blog,转载请注明。

GUI 程序我现在依然倾向于 GTK,因为虽然 Qt 拥有良好的跨平台性,但可能是太注重跨平台性了,在 Linux 平台上反而有一些水土不服的问题。

字体太多,支持太少

你可能觉得,系统上字体太少,所以经常会遇到不常见的字符无法显示的情况。然而对于 Qt 来说,字体越多,反而越容易遇到个别字符不能显示的情况。

这是我的 /etc/fonts/conf.d/66-qt.conf 中的一段。因为顺序的原因,我只能放到 /etc 下。除了针对 sans-serif 配置外,我也有同样的配置应用于 serif 和 monospace。

<fontconfig>
  <!-- Adjust font order for Qt applications -->
  <alias>
    <family>sans-serif</family>
    <prefer>
      <!-- 格拉哥里字母:Ⰽⱁⱀⱄⱅⰰⱀⱅⰹⱀ Ⰹⱍⰹⰳⱁⰲ -->
      <family>Noto Sans Glagolitic</family>
      <!-- 爪哇文:꧁   ꧂ -->
      <family>Noto Sans Javanese</family>
      <!-- 西夏文:𗷲𗒅 -->
      <family>Noto Serif Tangut</family>
      <!-- 埃及象形文字:𓁹 -->
      <family>Noto Sans Egyptian Hieroglyphs</family>
      <!-- 苏美尔楔形文字:𒆠𒂗𒂠 -->
      <family>Noto Sans Cuneiform</family>
      <!-- 中日韩统一表意文字扩展 C:𫚥 -->
      <family>HanaMinB</family>
      <!-- 拉让文:ꥃ -->
      <family>Noto Sans Rejang</family>
      <!-- 越南傣文:ꪀꪑ -->
      <family>Noto Sans Tai Viet</family>
      <!-- 切罗基文:ꮳꮧꮢ ᨣ -->
      <family>Noto Sans Cherokee</family>
      <!-- 老傣仂文:ᨣ -->
      <family>Noto Sans Tai Tham</family>
      <!-- 安纳托利亚象形文字:𔘓 -->
      <family>Noto Sans Anatolian Hieroglyphs</family>
      <!-- 马姆穆文补充:𖤍  -->
      <family>Noto Sans Bamum</family>
      <!-- 图标字体(PUA): -->
      <family>OperatorMonoSSmLig Nerd Font</family>
      <!-- 巴塔克文:ᯤ -->
      <family>Noto Sans Batak</family>
      <!-- 古北欧文:ᛋᛖᚱᚣᚨᛚᚳᚨᚾᛞᛚᛖ -->
      <family>Noto Sans Runic</family>
    </prefer>
  </alias>
</fontconfig>

这个配置的意思是,把这些字体的优先级提高一些。当使用 fontconfig 的程序要显示字符的时候,它会指定一个模式,匹配到一个字体列表。渲染文字的时候,就可以遍历这个列表,直到找到可以显示这个字符的字体,所以一般来说,只要系统上装了对应字符的字体,它就能显示出来。

但是 Qt 额外地需要这个配置,因为 Qt 只会检查列表中的前255项。而世界上的不同文字那么多,所以想要能够显示它们,就得有一堆字体。比如 noto-fonts 这个包里就有614个字体文件,远超 Qt 支持的数量。总有些奇奇怪怪的文字被网友用来当颜文字,或者挂在名字上彰显个性。不这么调整一下,Qt 遇到了就只能「吃豆腐」了。

空心豆腐

当一个字符显示不出来的时候,那么怎么显示好呢?一般会显示成某种方框。Pango火狐会将该字符的 Unicode 码点以十六进制的形式显示在方框里边,这样虽然不知道这个字符长什么样子,但至少知道它是哪个字符,也知道多块豆腐是不是同一字符,在不能复制字符本身的时候很有用。比如当它出现在求助者的截图里的时候,比如当它出现在不能复制的地方的时候。

然而 Qt 不这样做。管你什么字符,Qt 统一显示为空心方框。从视觉上完全无法知晓它到底是什么字符,要是复制不到的话,就别想弄明白你缺什么字体了。

PS: Matrix 客户端 fluffychat 的 Web 版,使用的是 Fluffy 图形界面库,即使在 Web 版,文字渲染依然完全是自己做的。不管浏览器的设置不管系统的设置,豆腐块是带叉号的方框,还不能选中,十分讨厌。

非 BMP 字符

所有使用 UTF-16 的平台(Java、JavaScript、Windows、Qt),外加 MySQL 容易遇到的一个问题:非 BMP 字符(也就是那些 U+FFFF 之后的字符)会被当作是两个字符处理。随着 emoji 的流行,大家应该都修了不少。然而,Qt 在展示非 BMP 字符的时候,你可以选中半个字符。如果不小心漏掉半个的话,复制出来的半个字符就会变成问号(还好不是 GBK 时代那样弄乱后续所有字符)。

font features

一些字体可以通过 fontconfig 设置 fontfeatures 属性来启用(或者禁用)一些特性,比如连字,带斜杠的 0,小型大写字母,居中的中文标点,等等。Pango 很早就支持了,火狐最近也支持了,但 Qt 那边依旧没啥动静。(感谢 Coelacanthus 的评论。)

by 依云 at March 08, 2022 08:45 AM

March 07, 2022

Alynx Zhou

我如何在 Emacs 里面处理缩进宽度和 Tab 宽度

Tabs are 8 characters, and thus indentations are also 8 characters. There are heretic movements that try to make indentations 4 (or even 2!) characters deep, and that is akin to trying to define the value of PI to be 3. -- Linux Kernel Coding Style

当然,我不是在任何情况下都同意上面那句话,虽然在写 C 的时候它是绝对的真理。我个人倾向于取其中一部分——任何试图把 Tab 宽度定义为 8 以外的行为都无异于把 PI 定义为 3。

写这篇文章是因为我意识到一个问题:Tab 宽度和缩进宽度是两个无关的变量。很多人简单地把缩进宽度理解为 Tab 宽度,不过这不能怪他们,因为大部分的编辑器都把这两者当作相同的东西。而当我重新开始用 Emacs,才发现这两个本来应该是不一样的。

为了防止迷惑,首先解释几个我说的名词:

  • Tab 宽度:字面意义上的一个 Tab 字符应该相当于几个空格的宽度,在 Emacs 里面是 tab-width 这个变量,我个人建议固定为 8。
  • 缩进宽度:当你的代码应该增加一个缩进级别的时候,应该向右缩进几个空格的宽度,在 Emacs 里面每个模式都有不同的变量控制,比如 c-indent-offset
  • 缩进级别:代码逻辑层次(或许可以简单理解为作用域)每增加一层,一般就应该增加一个缩进层级。
  • 使用 Tab 缩进:不是说只使用 Tab 缩进的意思。事实上如果你的代码可以只使用 Tab 缩进,那么 Tab 宽度为几都无所谓,因为一个缩进宽度就是一个 Tab,但实际上你不一定只使用 Tab 缩进,因为你可能还包含对齐的情况,比方说函数头参数太多,而你希望换行后的参数能够和左括号的下一个字符对齐,那么这个距离大概率无法被 Tab 宽度整除。因此使用 Tab 缩进的含义是“总缩进宽度能够用 Tab 宽度整除的部分用 Tab,余数的部分用空格”,也就是 Emacs 里面 (indent-tabs-mode 1) 的效果。
  • 使用空格缩进:和上面相反,意味着缩进部分完全不使用 Tab 字符,那么 Tab 宽度为几也无所谓。也就是 Emacs 里面 (indent-tabs-mode -1) 的效果。

我个人的建议和倾向主要是两个,第一个是 Tab 宽度固定为 8,第二个则是如果你认为缩进宽度为 8 也就是一个 Tab 对你来说太宽,那么你就应该完全不用 Tab 缩进,也就是使用空格缩进,而不是修改 Tab 宽度。

这样做的理由很简单,主要是我最近修改 GTK 代码时候发现的,GTK 代码是典型的 GNU 风格,我一开始以为它是完全不使用 Tab 缩进,并且缩进宽度是 2,并且我在 Atom 里面也是把这个项目的 Tab 宽度设为 2,但我前段时间发现 GTK 实际上是使用 Tab 的,比如一个很有意思的情况:某段代码的总缩进级别是 5,而它实际上使用的是 1 个 Tab 和 2 个空格,如果你把 Tab 宽度设置成 2,那这段代码在你的编辑器里就会错误的显示成 2 个缩进级别,只有你的 Tab 宽度是 8 的时候才能正确的显示。

这些奇怪缩进的项目给了我两个教训:Tab 宽度和缩进宽度并不是一个东西,以及最好假设 Tab 宽度为 8。当然,以上的建议都是以遵照现有代码的风格为前提,应该不会有人任性到把别人的项目都改成自己风格再提交贡献吧。

我倒不是完全的 Tab 缩进党,比如在 Python 里我设置缩进宽度是 4,而在 JavaScript 里我设置缩进宽度是 2,主要是因为对于 C 这种不允许嵌套函数并且没有类似 class 这种层次的语言来说,缩进级别完全就是函数内部逻辑,当你总缩进宽度达到 24 的时候,你已经在函数里有三层逻辑了。而对 Python 或者 JS 这类函数经常是类的方法的语言,本身函数就已经带着一个缩进级别了,三个缩进级别仅仅只代表两层函数内部逻辑,更别提比如回调函数是匿名函数的情况了。

至于当缩进宽度不是 8 时使用空格缩进而不是用 Tab 然后修改 Tab 宽度的理由也很简单,和上面说的一样,完全使用 Tab 缩进时虽然 Tab 宽度不会影响总缩进级别,但是一旦遇到对齐的情况,Tab 宽度不一致,对齐的部分就不再一样了。

说了这么多理论,该到具体的我怎么在 Emacs 里面处理了,首先是默认值,我个人是定义 Tab 宽度为 8 并且设置允许使用 Tab 缩进。

(setq-default tab-width 8)
(setq-default indent-tabs-mode t)

默认的 C 编码风格是 GNU,但是 GNU 的编码风格实在是太恐怖了,特别是大部分人的入门书上应该都是 K&R 或者类似的风格。我换成了我喜欢的 linux 内核风格。有的变量你可以直接使用 setq,但另一些使用 setq 会说你声明了一个新的自由变量,这个时候还是遵照建议使用 Emacs 的 customize 系统吧。

(customize-set-variable 'c-default-style '((java-mode . "java")
                                           (awk-mode . "awk")
                                           (other . "linux")))

前面说过 Emacs 对于不同的模式使用不同的变量作为缩进宽度,这样对于编写配置其实很困难,因为让你时刻记住哪个模式用哪个变量显然不太现实,这里我用了一个比较投机取巧的办法——Emacs 有所谓 buffer-local 变量的设定,也就是说一个变量会有一个默认值,然后每个 buffer 都可以有一个该变量的副本,可以设置成不同的值,如果没有则使用默认值。利用这个功能我创建了一个单独的 buffer-local 变量 indent-offset,然后把以上所有这些都设置为该变量的别名,于是我对每个 buffer 只要修改 indent-offset 的副本就可以了。我这里只写了我使用到的模式的变量,如果有其他的就加到列表里,或者我看 doom-modeline 的代码里几乎包含了所有常见模式的变量,或许可以拿来用。

(defconst mode-indent-offsets '(c-basic-offset
                                js-indent-level
                                css-indent-offset
                                sgml-basic-offset
                                python-indent-offset
                                lua-indent-level
                                web-mode-code-indent-offset
                                web-mode-css-indent-offset
                                web-mode-markup-indent-offset
                                markdown-list-indent-width)
  "Different modes' indent variables to make alias to indent-offset.")

(dolist (mode-indent-offset mode-indent-offsets)
  (defvaralias mode-indent-offset 'indent-offset))

(defvar-local indent-offset tab-width)

那么接下来就是我们如何针对不同的 buffer 进行不同的设定了,这里分为两部分,一部分是这个 buffer 是否使用 Tab 缩进,另一部分则是缩进宽度设置为多少。Atom 里面有一个状态栏插件,点击就可以快速设置,Emacs 里面我写了两个简单的函数方便调用,执行对应的函数设置是否使用 Tab,并且它们都会询问你想把缩进宽度设置为多少。我分别把这两个函数绑定到 C-c i TABC-c i SPC。对于 GTK,操作起来可以是 M-x indent-tabs RET 2 RET

(defun indent-tabs (num)
  "Mark this buffer to indent with tabs and set indent offset to NUM chars."
  (interactive `(,(read-number "Indent offset (chars): " indent-offset)))
  (indent-tabs-mode 1)
  (when (/= indent-offset num)
    (setq indent-offset num)))
(global-set-key (kbd "C-c i TAB") 'indent-tabs)

(defun indent-spaces (num)
  "Mark this buffer to indent with spaces and set indent offset to NUM chars."
  (interactive `(,(read-number "Indent offset (chars): " indent-offset)))
  (indent-tabs-mode -1)
  (when (/= indent-offset num)
    (setq indent-offset num)))
(global-set-key (kbd "C-c i SPC") 'indent-spaces)

当然以防万一你真的遇到一个脾气古怪的作者,一定要使用 Tab 缩进并修改 Tab 宽度,我也写了个修改 Tab 宽度的函数。这个我绑定到 C-c i w 了。

(defun set-tab-width (num)
  "Mark this buffer to set tab width to NUM chars."
  (interactive `(,(read-number "Tab width (chars): " tab-width)))
  (when (/= tab-width num)
    (setq tab-width num)))
(global-set-key (kbd "C-c i w") 'set-tab-width)

现在我们有了给每个 buffer 修改设定的办法了,但是通常你对某种语言有一个自己偏爱的风格,肯定希望以这个为默认值,所以我写了一些代码,给每个模式设置成我喜欢的默认风格。同样地如果你装了更多的模式,或者和我有不同的喜好,就修改这些列表好了。

这里使用 (set-tab-width 8) 是因为有些模式比如 markdown-mode 把 tab-width 定义为 4,按照前面说的,我觉得这是错的,同时上游为了保持向前兼容不好修改,于是这里简单地覆盖掉。

(defconst indent-tabs-modes '((prog-mode . 8)
                              ;; `markdown-mode` is not a `prog-mode`.
                              (markdown-mode . 8)
                              (gfm-mode . 8))
  "Modes that will use tabs to indent.")

(defconst indent-spaces-modes '((lisp-mode . 2)
                                (emacs-lisp-mode . 2)
                                (js-mode . 2)
                                (css-mode . 2)
                                (html-mode . 2)
                                (yaml-mode . 2)
                                (lua-mode . 3)
                                (python-mode . 4))
  "Modes that will use spaces to indent.")

(dolist (pair indent-tabs-modes)
  (add-hook (intern (concat (symbol-name (car pair)) "-hook"))
            `(lambda () (indent-tabs ,(cdr pair)) (set-tab-width 8))))

(dolist (pair indent-spaces-modes)
    (add-hook (intern (concat (symbol-name (car pair)) "-hook"))
              `(lambda () (indent-spaces ,(cdr pair)) (set-tab-width 8))))

有些 modeline 包含一个显示缩进信息的部分,比如 doom-modeline 显示 TAB 或者 SPC 表示使用 Tab 缩进或使用空格缩进,然后如果该模式有自己的缩进宽度变量就显示,没有就显示 Tab 宽度(我没仔细读,总之它对这俩不做显式区分)。而按照上文,我肯定是倾向显式区分这两个东西的,所以我们不用它的,而是自定义一段 modeline,第一部分显示 TABSPC,第二段显示 indent-offset,第三段显示 tab-width。我没太搞懂 Emacs 的 modeline constructor 的语法,我觉得我写对了但却没有,于是最后变成 :eval 一个 format 调用了。

(setq mode-line-misc-info '(:eval (format "%s %d %d"
                                          (if indent-tabs-mode "TAB" "SPC")
                                          indent-offset
                                          tab-width)))

最后我在网上抄了两个配置,一个是让它按回车时候不要自动缩进。另一个是修改默认的删除缩进的行为,默认当你在对着一个 Tab 按下退格键的时候,Emacs 把这个 Tab 变成缩进宽度数量的空格,然后删掉一个空格,这太诡异了,我就让它删掉一个字符好了。

(setq-default electric-indent-inhibit t)
(setq backward-delete-char-untabify-method nil)

这样当你遇到一个和自己习惯不一样的文件,基本只要看情况调用 indent-tabs 或者 indent-spaces 即可。我在 Atom 用的插件还有一个自动猜测文件是使用 Tab 还是几个空格作为缩进的功能,不过我看了一下代码,它并不能解决我之前说到的 GTK 的问题,也就是说它会猜成 SPC 2 2 而不是 TAB 2 8,我懒得自己想一个猜测算法,于是就还是靠自己判断了。

一个我比较想实现的功能是记录每个目录我用了什么设定,因为基本上每个项目都用一样的风格,这样就不用每次编辑文件都手动设置。我知道可以在每个目录创建一个文件记录 Emacs 的一些目录范围变量,但是问题是不是所有项目都想让你添加一个编辑器相关的文件,甚至你都不好修改 .gitignore 排除你的文件。我比较想把这个存储记录丢到 Emacs 的目录里,不过并不知道怎么实现,如果哪天我搞清楚了,就去写一个。

by Alynx Zhou (alynx.zhou@gmail.com) at March 07, 2022 01:11 AM

February 26, 2022

Alynx Zhou

Emacs 和 Monaco 字体和 Box-drawing Character

2016 年的我开始用 Atom 这种“modern”的编辑器,2022 年的我却又开始用回岁数比我都大的 GNU Emacs。切换的理由其实很简单,我曾经以为一直能追上最新版 Electron 的 VSCode 会成为第一个纯 Wayland 的代码编辑器——只要 Chromium 那边支持纯 Wayland 就好了嘛,然而直到 Emacs 那边的 pgtk 分支合并进主线(以防有读者不太清楚来龙去脉我解释一下,Emacs 虽然有图形界面,但实际上只是用 X 实现了一个 Terminal 层,而传统的 GTK3 界面只是使用 GTK3 创建一个 X 窗口,然后其它操作都是通过 X 进行,这实际上非常不适合 GTK3,导致了很多 bug,同时也使 Emacs 没法利用 GTK 的 Wayland 后端。而 pgtk 分支则是在 X 部分之外另起炉灶,利用 GTK 实现了一个和 X 部分平行的 Terminal 层,全部的绘制操作都是以 GTK/Cairo 的现代程序方式进行,自然也就摆脱了对 X 的依赖。总之在 Emacs 这样又老又庞大的代码库上做如此大范围的工程我觉得可以称得上是一项壮举了。),Chromium 的 ozone backend 还是问题多多。虽然 Emacs/Vim 这种软件看起来确实有点老派作风,但没想到也有走在这些“现代”编辑器前面的地方。

至于这和我换掉 Atom 有什么联系呢?主要是我发现在家里的台式机上,所有 XWayland 程序在 nvidia 驱动下面都会有闪回的情况,也就是说你打字的时候突然会闪回前几帧的画面,过一会再闪回来,你经常看不到自己输入的字符。Electron 程序尤其严重,也就导致我没有办法使用 Atom 写代码,于是不得不捡起以前东拼西凑的 Emacs 配置重新研究。(奇怪的是我自己的台式机也是 nvidia 驱动,没遇到过这种问题。)

扯远了,这篇文章主要想记录的问题是什么呢?其实还要和 Emacs 绘制界面的方式有关系,对于 Atom/VSCode 这种基于浏览器的程序来说绘制点什么图形元素很简单,但是对于 Emacs/Vim 这种来自于终端里的程序,开发者们习惯的是处理字符而不是处理图形,于是你会发现比如 80 column ruler 或者 indent guide 这种东西,在 Emacs 里面其实是通过在对应的位置插入竖线字符实现的……我个人不太喜欢这样,一个原因是我以为竖线字符并不是占满整行而是上下有空白。我一直以为这是 Emacs 的问题——你干嘛用竖线字符画 UI 啊。

有问题的样子

直到有一天我输错了 alias 在 GNOME Terminal 里面打开了 Emacs,我惊讶的发现竖线竟然接上了头!

终端里的样子

我当时就震惊了,我的终端和 Emacs 用的是同样的 Monaco 字体,怎么会不一样呢?难道是 Monaco 字体有问题?于是我上网搜了一下,我以前一直以为是 Emacs 的哪个设置比如 line-spacing 我没搞好,怎么搜也搜不出来,这次换成搜字体一下子就找到原因了:一个 Alacritty 的 issue 里面和我有同样的问题,不过他是 tmux 的分割线接不上头,都是 Monaco 字体。

为什么只有 Monaco 接不上头呢?原来在字符界面下画这些竖线的字符和平时用 Shift+\ 输入的字符并不是一个,这类字符叫做 Box-drawing Character,主要的范围是 U+2500U+257F,这些字符用于在终端里绘制方框或者其它形状,所以应该是没有 padding 的,才能接上头,而 Monaco 这里有问题,它给这些字符加上了 padding,导致接不上。我尝试着给 Emacs 的字体换成 Source Code Pro,竖线立刻就连上了。

怎么解决?换字体?不可能的,我是 Monaco 的狂粉,看惯了 Monaco 再看别的字体都觉得傻了吧唧的。如果不是因为它好看我才不会忍受它这么多缺点(没有内置粗体,虽然是等宽字体内部的连字表却和非等宽字体一样,有版权不能二次分发)。解决方法其实比较简单,把 U+2500U+257F 的字符换成正常字体里的就可以了。简单的解决办法是用 FontForge 的同个实例打开 Monaco 和另一款字体(我选择了 Menlo,Menlo 是苹果用来替代 Monaco 做内置默认等宽字体的,应该会比较接近),然后选择 Monaco 的这个范围清空,然后选择 Menlo 的复制粘贴过来。不过我没在 FontForge 里面找到连续区间选择的办法,上网搜了一下说可以用它的脚本 API 选,办法是打开 File 菜单里面的 Execute Script,执行 fontforge.activeFont().selection.select(("ranges", None), 0x2500, 0x257F)。如果你不知道怎么把 Menlo 里面的一段字形复制粘贴到 Monaco 里面,你也可以在 Menlo 里执行这段脚本,然后反选,全部清空,然后把这部分生成一个字体,再去 Monaco 里面选 Elements 菜单里面的 Merge Fonts。

或者你也可以看看这个 叫 Menloco 的项目,是我偶然间搜索到的有同样问题的用户的解决方案,这个项目包含更多的细微 tweak 脚本,帮你利用 Monaco 和 Menlo 合并出一个 Box-drawing Character 能完美接头的字体。不过有几个地方需要注意,一个是这个项目的作者应该是 macOS 的用户,如果你不是 macOS,需要自己想办法搞到 Menlo.ttf 和 Monaco.ttf,简单的办法是找个用 Mac 的朋友让他发给你,不过有可能你得到的是 Menlo.ttc,需要用 FontForge 打开选择 Regular 字重,然后导出成单个的 ttf。你还需要修改 utils/find-font.sh,这个脚本的 font_paths 只包含 macOS 放置字体的目录,你得加上你自己放这两个字体的目录。以及这个项目默认生成的字体名(不是文件名)叫 Menloco,如果你不想修改已有的写着 Monaco 的配置文件的话,就把 merge 这一项下面的 --font-name=$(RESULT) 改成 --font-name=$(INTO) 就好了。

生成一个没问题的字体之后你还可以像我一样用 FontForge 做一些修改,比如说我发现 Menlo 有很多 Monaco 没有的字符,于是我直接把 Menlo merge 进了生成的字体里。并且我之前提到过 Monaco 作为一个等宽字体,内置的连字表竟然是非等宽的,不是像 Fira Code 那种把不同的编程符号连字起来同时保持等宽的连字,而是像普通无衬线一样把 fi 一类的字符连起来变成单个字符宽度。我被坑得最狠的一次就是 review 同事的 patch,我问他这里是不是少了个空格,他说在他那看没问题,最后我发现是 Monaco 连字了!虽然你可以通过配置 fontconfig 关闭连字,但是 Firefox 是不吃这个配置的,而你也不可能给每个网页的代码块都加上关闭连字的 CSS。所以我直接在 FontForge 里面干掉了连字表,具体方法就是打开 Element 菜单下面的 Font Info,点击左侧的 Lookup,选中带 liga 的项 delete 之后导出字体即可。

还有一个比较古怪的 Emacs 问题,Emacs 设置字体和其它程序不太一样,可以先设置一个默认字体然后针对不同的字符集设置不同的字体,一般要为中文单独设置字体才能得到合适的效果,就像下面这样:

(set-face-attribute 'default nil
                    :family "Monaco"
                    ;; :slant 'normal
                    :width 'normal
                    :weight 'normal
                    ;; 1 height is 1/10 pt.
                    :height 140)

(dolist (charset '(kana han symbol cjk-misc bopomofo))
  (set-fontset-font t charset (font-spec :family "Noto Sans Mono CJK SC"
                                         ;; :slant 'normal
                                         :width 'normal
                                         :weight 'normal)))

首先要注意 height 的单位是 1/10,所以你想要的字号需要乘 10 才行。

然后你会发现明明你只设置了一个字号,可是中文和英文字体却不是等高的!也就是说如果你在本来都是英文的一行里面输入一个中文字,那这行的高度就会突然跳一下变高,非常烦人,也许是这两个字体在同样的字号的时候尺寸并不完全一致,但是明明其它程序都能正常处理,为什么这里这么怪!

你可能会想要对中文那段单独设置 size 缩小一点,但是这样不行,你用 C-x C-= 放大字体的时候中文字体就会固定大小不跟着你变了。正确的解决方法是加入下面一句:

(setq face-font-rescale-alist '(("Noto Sans Mono CJK SC" . 0.85)))

这里你可以对任意的字体指定缩放参数,不会影响按键放大缩小。我尝试了一下 0.85 比较合适,虽然可能这样汉字看起来会稍微小一点,但是 0.9 就太高了仍然会跳。或许你会问那这样中文字宽不是英文字宽两倍了?那没有办法,Monaco 本身就属于一个比较宽的字体,那些满足英文宽度是高度一半的字体都比较瘦长,我个人是不喜欢这样的风格的,所以对于我来说等高就行了,宽度我不太在乎。

更新(2022-02-25T19:01:31):我最近研究了一下,发现原来字号并不等于行高。虽然字体有一个 em size 作为基础的方块大小,但是设计师经常指定一些奇怪的 ascender 和 descender 值让字体的高度超出 em size……我不太清楚 Atom 或者说 Chromium 是怎么进行中英文混排的,不过 Emacs 排版时候是对齐 baseline,然后 ascender 和 descender 完全一致才能保证行高不会在切换字体时候变化。但是这实在是太难了,大部分中文字体和英文字体都对不上,特别是 Noto Sans CJK 系列,不知道为什么比其它的高特别多。一个简单的解决办法是使用等距更纱黑体,里面混合的英文等宽字体 Iosevka 和中文的思源黑体拥有一致的 ascender 和 descender,但我实在是不喜欢 Iosevka。并且不知道为什么,Noto Sans CJK 和等距更纱黑体里面的内置的思源黑体应该是同一种字体,Noto Sans CJK 就要比他高很多。我还尝试了修改 Monaco 的 ascender 和 descender,不过这部分非常复杂,涉及到好几个不同的值,而且不同字体比例尺也不一样。最关键的是修改之后相当于把字体拉高了,于是 box-drawing character 又接不上了……上面那个修改缩放参数其实也不是无级缩放,实际上是乘字号之后取整然后再去找对应大小的字符,所以其实就是找小几号的 Noto Sans CJK。具体的可以在 Emacs 里面 M-x describe-font 查看详情。

更新(2022-02-26T09:51:25):补充一下,浏览器的混排应该和 Emacs 是一样的,我刚才尝试了一下,我的博客行高固定是因为我给 <pre> 设置了 line-height: 1.5,这个大小超过了 Noto Sans CJK 的行高,如果删掉这一行,你就会发现含有 Noto Sans CJK 的行比只有 Monaco 的行要高很多。不过 Emacs 没办法像浏览器一样指定一个最小行高,只能是它自己根据这一行的字符计算行高,所以没什么比较好的解决办法。

搞定这些之后,至少 Emacs 里面看起来比较顺眼了。

正常的样子

by Alynx Zhou (alynx.zhou@gmail.com) at February 26, 2022 01:51 AM

February 17, 2022

Lainme

解决Snapcraft的方框字问题

从snap安装的软件经常会出现文字显示为方框的情况,目前发现一个比较有效的解决方式如下:

先执行

snap run --shell [软件名]

然后执行

fc-cache -r

运行结束后退出即可。

by lainme (lainme@undisclosed.example.com) at February 17, 2022 02:57 PM

February 15, 2022

Alynx Zhou

不写文章的博客生成速度最快

春节假期虽然是假期,但是在家的时间里我基本没闲着,毕竟按照传统(指 Ken Thompson 在假期里写出了 UNIX 的第一个雏形的传统,不过维基百科上并没有详述是不是这样,我也懒得考证),假期是造轮子的好机会。所以我就胡乱捣鼓一通看看能不能让我的博客生成器更圆一点。

重新理解 await

第一个简单的修改就是把启动时加载文件的部分并行化,其实我也没有非要尽可能并行,性能也不是我第一位的追求。

我一开始写代码的时候知道有 await 之后就开始在各种地方给 Promise 用 await,随便你怎么说都行,后来我也才意识到,给所有的 Promise 都加上 await 不就是相当于每一个 Promise 都写在上一个的 then 里面,那不还是顺序执行吗?写多了以后对异步有理解了我才想起来这里完全可以用 Promise.all(),反正加载插件/脚本/模板/语言是不太有先后依赖顺序的(非要说的话,插件和脚本里可能包含模板引擎,所以可能下一步是给前两个和后两个分开顺序加载)。

程序解决不了的事情就让人来做

随便你怎么说,我一直觉得有些程序很难理解的事情而人类很好理解的就该让人类来做,而不是费很大力气得到不好的效果。一个多少接近这一条的问题就是主题文件的依赖问题——很多生成器比如 Hexo 在以 server 模式运行的时候都有 watch 功能,可以监测文件变化然后实时重新生成,用户只要一刷新就可以看到最新的更改。直接看上去不难,写 Node 的谁还不知道 chokidar 了。但是问题就在于,普通用户写文章不太需要一边看渲染一边写(你不会连 Markdown 这么简单的格式都不能脑补吧?我鄙视 Typora),反而是主题作者经常需要看到自己刚编写的样式是什么样子,而这是一个很复杂的地方,CSS 预处理器和 HTML 模板引擎通常都有 import/include 一类的语法,让你不用写很多重复的片段,而博客生成器并不能理解每一种预处理器和模板引擎的语法。假如你在 A 模板里 include 了 B,然后修改了 B,此时你刷新一个用了 A 模板的页面,你是看不到页面更新的,那在最需要这个功能的时候这个功能变成了废物。

有一个简单的解决方案是不要预编译你的模板,而是每次需要渲染页面的时候重新读取模板文件编译出一个函数来。不过这对于生成器而言不太优雅,有些模板引擎比如 nunjucks 可能有内建的 cache 支持,但另一些可能是没有的。再说你来来回回读这么多次磁盘,那干嘛不直接把渲染页面的工作也挪到用户浏览器(指前端)里呢?还要博客生成器做什么。

我一开始的打算是既然生成器自己不好理解依赖语法,那就像处理博客文章一样,让主题作者给文件加上 front matter 记录这个文件的依赖。不过问题又来了,对于直接使用的模板(指博客生成器读取并编译它们)可以在读取时去掉 front matter,但是假如这个模板 include 的模板也带有 front matter,此时实际上是模板引擎(比如 nunjucks)自己去磁盘上读那个文件,它不会去掉 front matter。而给每一种模板引擎都重写文件加载器显然是不现实的,所以最后的方案就是要求主题作者提供一个 file-dependencies.yaml 的文件,里面记录每个文件的依赖关系,大概像是这样:

layouts:
  includes/layout.njk:
    - includes/footer.njk
    - includes/header.njk
    - includes/sidebar.njk

第一层是需要 watch 的目录名,因为不是所有的目录都需要实时刷新,显然这种耗时的操作越少越好,第二层是依赖其他文件的文件,第三层则是一个被它依赖的文件的列表。程序里面我写了一个 Watcher 类,启动时会读取这个文件,然后将它翻转过来:以被其它文件依赖的文件作为 key,而依赖这个文件的文件成为 value 列表。因为我们总是检测到被依赖的文件变化之后才开始统计受到它影响的文件。然后就是一个递归收集受到影响的文件的函数,因为文件依赖并不是只有一层的。收集之后分别给这些文件也加入到需要更新的列表里面。

写完这个功能之后我最大的感叹就是:要是我写主题的时候能有这种功能,我应该能节省不少检查的时间吧……

后面闲着又给这个做了点修改,一个是支持用 glob pattern 作为被依赖的文件,虽然我个人觉得还是把需要的文件都列出来算了,但是想必对于处在开发调试期的主题来说很有用,作者可能不一定想得起来及时更新这个文件。另一个是考虑到使用 glob 之后很容易在无意间搞出循环依赖,又写了在递归里打破循环依赖的功能,我通常不擅长处理递归的逻辑,但我脑子灵光一闪想起来可以给函数最后一个参数设置成一个记录已经处理过哪些文件的 Set,然后递归的时候作为参数传进去,然后就这么成了。

现在如果开启 server 模式,各种文件以及语言文件模板文件,甚至包括这个依赖描述文件自己都是实时更新的了,只有主题和站点的两个关键配置文件需要重启之后才能重新加载。

削减依赖的路上没有最少只有更少

事实上我在添加依赖的时候已经非常克制了,我尽量只使用那些我不得不用的 npm 包,有些功能能自己实现的我都自己实现了。但是总有还能去掉的依赖。我首先干掉了 Stylus,理由很哭笑不得,它好像不是那么流行了,我都找不到合适的支持它的 Emacs 插件。我本来想换成 Less 或者 SCSS,但是前者语法和 Stylus 差得有点多,后者有好几种语言的实现,本来我看 GNOME 都在用 C 写的 libsass,我觉得挺好,也打算用这个,结果发现它教程里挂着说“不推荐使用 @import 而推荐 @use 但目前只有 dart 版本的实现支持了后者”,让我感到说不出来的难受,我不太喜欢这种奇怪的新语言,而且搞这么多实现,还整出参差不齐的语法,实在是麻烦,虽然 dart 可以编译到 JavaScript,我还是放弃了。其实我自己写 Stylus 的时候不是放飞自我型的,而是尽可能贴近 CSS 型的,所以干脆把我的样式都改成了原生 CSS 了,顺手就干掉了一个依赖,如果有需要的人还是装插件吧。

再之后就是我一直使用 glob 来匹配文件,但是它还依赖一些别的包,我在想能不能用依赖更少的包替换它。glob 使用 minimatch 分析 glob pattern,但是比如 chokidar 使用的是另一个替代品叫 picomatch。我找到一个叫 fdir 的库,虽然接口用起来怪怪的,但是如果你用它做 glob,只需要它和 picomatch,没有引入别的依赖,于是我就试了一下。但是不得不感叹作者能不能写点阳间的代码,这个库默认忽略软链接文件,这显然是不能用的,但是假如你设置解析链接文件,它倒好,就算我要的是相对路径,它还是直接解析并拼接了绝对路径——我只是想让你把链接文件当普通文件返回一下!就在我打算放弃并用回阳间的 glob 包的时候,我发现 chokidar 自己依赖的 readdirp 库也可以配合 picomatch 使用。但是如果你直接把 glob pattern 丢给它让它自己调用 picomatch 编译的话,又有一些阴间的限制,所幸可以给它传自定义的过滤器函数,正好我还需要支持一些别的功能,就自己编译 glob 传给它了。

实际上我现在只有 10 个左右的直接依赖,全部的运行依赖大概是 36 个,在 Node.js 编写的程序里面应该算是相当克制的了,考虑到这里面大部分都是 nunjucks 的间接依赖,我觉得我做得相当不错。

对于其它我编写的简单的 npm 包我还是比较追求 0 依赖,只有 0 依赖的包才是比较可控的,如果一个 npm 包在 npm 上显示有 1 个依赖,那你就不能确定这一个依赖有多少间接依赖,而不更新的间接依赖会引入多少安全问题你也无法得知。我在给 Hikaru 选依赖的时候一般也会倾向于 0 依赖的包(这就是为什么选择 commander.js 而不是 yargs 的原因)或者是一些虽然有依赖,但是递归翻几次就是 0 依赖的包(chokidar 属于这类,而且我还给 nunjucks 提过一个 PR 使用 commander.js 代替了 yargs),而不会选那些依赖摞依赖无穷无尽的包。

这里不得不吐槽一下 jsdoc、mocha 这些大型的开发用的工具库,依赖实在是太混乱了,甚至 jsdoc 似乎同时依赖 marked 和 markdown-it……开发者写新功能的时候能不能长点心啊。还有我之前用 react-scripts 的时候,用旧版提示依赖里面有的版本有安全问题,升级到新版依赖算了一大堆,甚至出现了不同版本的间接依赖,而且仍然是有问题的版本,还是希望开发者对自己的依赖都关心关心,及时更新依赖版本,不要锁死一个版本懒得改就觉得万事大吉了。

如果你对削减 npm 包的依赖感兴趣,强烈建议你读一下 chokidar 开发者写的这篇文章

worker_threads 是多线程,但和我想的不太一样……

我:你懂 Node 吗?为什么我多线程比单线程慢? 铁道迷:Node 就是单线程拖拉机啊。 我:你不懂,88。

凡是跟你说“Node.js 只有单线程”的人,都可以直接和他说“你不懂 Node.js”了。Node 要解决的就是非阻塞 IO 的问题,而非阻塞 IO 肯定不是多线程能解决的。至少对于内部的 fs 来说,调用异步函数的时候是 libuv 从线程池里面拉出一个线程去解决任务,而 JavaScript 本身的线程不会被阻塞。如果你还想了解“有哪些代码会阻塞主线程而哪些代码不会”,可以看 Node.js 官网的这篇文章

说实话,对于用户自己写的 JavaScript 代码段,似乎是没什么好办法把它丢到主线程之外的线程上去执行的……请务必注意“回调”,“异步”和“非阻塞”的区别——不是所有回调都是异步的,比如我传了一个回调函数它也可能是同步的,而就算你把一段耗时的同步代码套上 setImmediate 和 Promise,也不意味着它就不会阻塞主线程了……它执行的时候还是在主线程执行,只是你把它延迟了,推到了 event loop 空闲的时候去运行——但轮到它运行起来还是阻塞住了主线程。(说出来不怕笑话,我也是写得多了最近才认识到这些区别(乐),如果我写的不对还希望大家指正。)

起因是我读了 Sukka 的这篇文章,介绍了 Node 新加的 worker_threads 模块,是允许你开启子线程运行代码的。正好我想到我的博客生成器里有一段分析每篇文章内容并修饰里面的图片和链接的代码,要是能起一个线程池,把每个文章分配给不同的线程去处理,那多是一件美事啊!

我发现这个 worker_threads 并不是很好用,它并不像 pthread,直接运行一个函数,而是要求你必须传一个 JavaScript 脚本的路径进去,这就给代码编写搞出了很大的麻烦。我做的修改在另一个 worker 分支里面,可以访问 https://github.com/AlynxZhou/hikaru/commits/worker 看到。不过一个尴尬的事情是写好以后我跑了几次,发现多线程还没有我之前单线程运行快……而且很反直觉的是线程越多越慢,在我 12 核心的处理器上四个线程是跑在主线程之外最快的,比什么 2、6、8、12 都快。

说实话,我也没有搞清楚到底是怎么回事,可以确定任务确实是分发给了不同的线程,但是为什么会这么慢呢?一个可能的原因是在不同的线程之间并不是像 C 一样直接共享内存空间,传递参数的时候 Node 是复制之后传递的,虽然我觉得我传递的数据量不至于让复制造成瓶颈吧,但这是我能想到唯一的原因了。而且阅读例子的时候也发现,好像推荐的用法是比如做 http server,每一个线程都是启动之后一直监听,而不像我这样是不断的分配大量的小任务。总之我很需要一个像 pthread 那样简单的给我执行一段代码的线程,而 worker_threads 好像倾向长时间运行一个单独文件……强扭的瓜不甜,我还是放弃吧。

然后说回到性能这件事上,我倒不是觉得生成器现在的性能难以忍受,否则我就该用 Hugo,相反我实在是觉得现在的生成速度出乎意料的棒了,而且又不像 Hugo 只支持 go template,Node 有很多种模板引擎可以选(我就是不喜欢 go 你来打我呀)。完整生成一遍我的博客大概耗时 800 毫秒,而且我也没有把渲染啦解析啦之类的功能用多线程解决,并且我的生成器没有缓存,每次都是从头生成的。没有缓存这点其实和之前 watch 文件的功能很类似,不够可靠还不如不要算了。在我以前用 Hexo 的时候它是有缓存的,但是这个缓存经常导致奇怪的结果,比如该更新的页面没有伴随文章一起更新之类的,我也不是很理解到底怎么回事(特别是考虑到 Hexo 里面还有一个奇怪的 JSON 数据库 warehouse),所以每次我都是 clean 之后再从头生成,那我自己实现的时候自然就砍掉这种不靠谱的功能了。

当然我问了琪神 Hugo 生成他的博客耗时多少,似乎只需要 200 毫秒的样子,不过除了 go 是编译型语言而且 Hugo 肯定用了协程渲染以外,他只有 6 篇文章而我有 83 篇文章也是一个很重要的因素!他甚至连分页都不需要生成!

所以如果你真的只追求速度不追求别的,什么语言都是浮云,总结出来就一句话:不写文章的博客生成速度最快!

by Alynx Zhou (alynx.zhou@gmail.com) at February 15, 2022 10:33 AM

February 11, 2022

Alynx Zhou

StackHarbor 的 2021 尾记

今年一直拖着没写总结,不是因为懒,主要是因为我总觉得好像距离去年写总结也没太久,我还能回忆起来去年写总结时候是什么样……也说不上是因为我记忆力不太好还是因为这一年实在没什么能让我记住的事情。

2021 年工作没什么变化,不过我换了个好一点的住处,虽然仍然是旧楼但是里面是新装修过的,至少让人待着的时候感到舒适,而且还可以吸室友的猫,虽然小猫也经常搞破坏,不过猫做什么都可以原谅。

看了去年的总结,才发现我是 2021 年把 FlipClock 改成 Meson 管理编译的,于是想起来我这一年学了 Meson 怎么用,因为 CMake 实在是让我烧脑筋。我今年还终于让 scrcpy 能够模拟 USB 键盘,这样使用 scrcpy 的时候也能用数字键选词了,多少算是在一个大项目里面做了一个大贡献。

然后其他能记得住的就是最近折腾的一些东西了,春节假期没写博客也是因为忙着写代码。写了一个简单的网页小游戏,基本是平面版神庙逃亡,感觉还是挺有成就感的。然后给之前的弹钢琴页面改成了原生 JS 实现,放弃 React 的原因是依赖太多了,而且不是每个软件包都能及时更新自己的依赖。比如你用一个老版本的 create-react-app 会带来许多有漏洞的依赖,然后你更新到最新版因为依赖变了,有些库又依赖了另一个有漏洞的版本。我觉得为了简单的 UI 和数据绑定这个代价是不值得的,所以我就重写了一个。然后给我的博客生成器添加了文件依赖的支持,很久很久以前当我刚开始用静态博客生成器的时候就在头疼这个,博客生成器理解不了你的模板引擎或者预处理器的导入和扩展语句,于是没办法在一个文件更新的时候更新所有包含它的依赖,导致主题开发要不停的关掉生成器再开。一开始我是想给这些文件都加上 front matter,但是我发现虽然我自己加载的时候可以去掉 front matter,模板引擎处理导入语句的时候是他自己读文件所以没办法去掉 front matter,我不想给每个模板引擎都写自定义的 loader,所以最后改成用一个单独的文件记录依赖了。最后的结果就是单独抽出了一个 Watcher 类,可以查表查出所有需要更新的文件然后调回调,效果还挺不错的。以及本来我想清理一下博客的样式然后顺便换个 CSS 预处理器,因为感觉 Stylus 不是特别火了,但是考察了一下,less 的语法和 Stylus 差得有点多,而 scss 虽然使用广泛,而且有纯 C 语言写的实现,但是它有好多个实现,并且官网上说只有 Dart 语言写的版本才不过时,其它的都只实现了过时的 @import 而用户应该用最新的 @use,总之搞得这么麻烦看起来就不想用。虽然 Dart 能编译成纯 JS,我还是觉得没兴趣。于是最后我把我的样式都改成了原生 CSS,然后去掉了生成器内置的 Stylus 支持。

显示器从去年的一个换成了两个,因为我是一个经常最大化窗口的人,切工作区虽然快,但是比如在浏览器和代码编辑器之间切换的时候还是会打断思路,有了双屏就没这个烦恼。一开始我想的是双屏更方便一边全屏游戏一边干别的事情,不过好像也就那样,因为从全屏切出来到另一个屏幕也不是很顺畅。不过买双屏的时候很头疼的就是因为不是一个批次,两个显示器的色温差得不是一点,最终是换了一台然后用校色仪调了好久,虽然不是 100% 一致,但是至少差不多了。

说到这个我想说,感觉幸运和倒霉是伴随而来的,不太可能一直幸运,经常是发生一些开心的事之后又发生一些不开心的事,比如去年买了新相机,经常没事拍点照片什么的,结果昨天晚上我突然发现不开镜头盖的时候 CMOS 有个红点……我觉得我基本可以排除激光损伤,应该就是坏点,但是我用机内的像素映射却解决不掉。感觉最后还得跑一趟维修站,而我在老家,一时半会不会回北京,这边也没有维修站,总之麻烦的事情就会让我心累。买了一张升降桌,不过这个最便宜的款式不太靠谱,现在升降功能不太正常,于是只是变成了一台大桌子。不过椅子坏了之后换了个比较便宜的工学椅倒确实令我满意。

今年没有换手机,主要是把这个钱投资到镜头或者灯上让我更高兴一点,手机厂商出的手机真是越来越烂了。不过把备用的 iPhone 8 换成了 iPhone XR,在二手频道看到一个成色很好,而且是红色款的,我早就觉得 iPhone 8 屏幕太小了。

动漫也想不起来看过什么,今年也许只看了 EVA 最后一部剧场版和 love live superstar,反正看 EVA 就是大家都想看看他怎么收尾而已,总体来说还是不错,特别是看了纪录片之后。然后 love live 我以前没看过,我的评价就是挺好听的。然后我感觉在电影院看到 Fate/Stay Night HF 第三季的可能性应该是没了,特别是考虑到今年院线上的国外电影的比例……还能说什么呢懂得都懂哈哈。

想不起来看过多少书,总之前段时间有天晚上睡不着,一口气把挪威的森林看完了,我早就买了这本书,但是以前都是打开看几页就看不下去了,这次一口气看完感觉还挺好的。

今年的另一个好消息是 NVIDIA 驱动终于补全了 Wayland 支持,感谢所有在这个过程中努力的人,比如 NVIDIA 那个一直在处理相关事情的用蜗牛做头像的老哥,事情总要是靠人推进的嘛。不过回家之后莫名发现我的 Atom 在 XWayland 下面会疯狂地闪回,明明我自己住处的电脑也是一样的配置就没问题。然后这时候我发现 Emacs 的纯 GTK 分支已经合并进了主线,也就是说 Emacs 现在终于是纯 Wayland 程序了,我立刻搞了一个最新版本,然后捡起来以前的配置文件,又仔细研究仔细鼓捣,目前多少是堪用了,虽然还有不少地方不像 Atom 那么习惯,主要还是我懒得仔细研究,反正凑合用,我是真的怀念 Atom 上的一些功能,比如注释反注释代码块、移动代码块、方便的查找替换还有现代化的界面……Emacs 的界面元素感觉还是基于文本的,然后包括粗体字渲染感觉也不太光滑,比如 indent guide 或者 80 column ruler 这种东西就应该直接在界面上划线嘛,不要用字符做。不过我在用 Atom 的时候也怀念 Emacs 的一些功能比如纯键盘分屏和 C-SPC Mark Region。

今年的游戏时间基本都拿来打 Dota 了,虽然看 TI 看得很难受,但是和朋友一起玩游戏还是挺快乐的,这也属于幸运和倒霉的周期了吧。塞尔达什么的,等我想起来慢慢玩……

跨年的时候还是和去年一样朋友聚餐了,我还在 B 站上传了 vlog,和蓝猫铁道迷一起吃寿喜锅很开心。今年写代码没少让琪神当小黄鸭,谢谢琪神。

总之先写这些,如果我又想起什么,我后面再慢慢加上吧,最近冻着了嗓子疼,希望倒霉的周期赶紧过去,过段时间还得找找机会去修相机呢……

by Alynx Zhou (alynx.zhou@gmail.com) at February 11, 2022 01:18 AM

February 03, 2022

中文社区新闻

调试符号(debug)包和 debuginfod

我们很高兴地宣布 Arch Linux 将有调试符号包了。
我们的 debuginfod 实例将提供调试符号信息和源码列表,这些可以被调试器比如 gdb 和 delve 利用。
https://debuginfod.archlinux.org/
一些由赞助商提供服务器的镜像站已经开始提供 debug 软件源的镜像,同时我们正在商讨新的镜像站要求

目前并不是所有包都提供调试符号包,这是我们正在进行的工作。
更多信息请参阅 Debuginfod 维基页,以及我们近期刚更新的 Debugging/Getting traces 维基页。

by farseerfc at February 03, 2022 04:33 AM

February 02, 2022

百合仙子

Wayfire 迁移进展(四):不那么 high 的 DPI

本文来自依云's Blog,转载请注明。

使用24寸4k屏幕作为主屏的时候很简单,设置 scale 为 2 就好了。但是,当 2 嫌太大、1 嫌太小的时候,问题就来了。比如我希望使用 120dpi,把 scale 设置为 1.25 可好?

scale=1.25 text

而这才是理想的效果:

120dpi text

看不出来差别?放大八倍,你看差别多明显:

8x compare

正常 120dpi 渲染出来的文字边缘清晰犀利,次像素平滑左红右蓝。再看看 scale=1.25 的文字,线条经常糊掉,次像素平滑效果几乎完全被抹掉。实际看上去的效果就是跟透明麿沙玻璃看屏幕似的,线条边缘总是有点糊糊的感觉,1080p 的屏幕被降级成了 720p 似的。

之所以出现这样的情况,是因为 Wayland 只支持整数倍缩放。因为,Wayland 混成器不能告诉客户端你得把窗口给画成 1.25 倍的,而客户端也无法告诉混成器我这个图像画的是 1.25 倍。所以,混成器只好告诉客户端你给我画个 2 倍的图像吧。混成器拿到图像之后再缩小 0.625 倍,自然有些逻辑像素就不能对应到单个的物理像素上去了。

所以,我还是设置 scale=1,不要混成器帮我去缩放。我自己通过另外的办法告诉客户端把字写大点儿。图标之类的就顾不上啦,反而大点小点都还能看。比如我要 1.25 倍大小的文字,就这样做:

  • GTK 3:在 dconf 里设置org.gnome.desktop.interface.text-scaling-factor=1.25就好了。最开始的截图就是 dconf-editor 里这一项配置。
  • Qt:设置环境变量 QT_WAYLAND_FORCE_DPI=120
  • Telegram:除了上边这个环境变量外,额外地在它自己的设置里设置 150% 的缩放(Telegram 的字偏小所以要设置得大一些)。设置环境变量是为了 fcitx5。
  • waybar:config 文件中设置 heightstyle.css 中设置 font-size
  • Xwayland:和 X11 下的 HiDPI 设置差不多的。比如 GTK 2 设置 Xresources Xft.dpi: 120 就好了。

我遇到的差不多就这些了。没办法,Linux 就是这么乱 QAQ。不过虽然 Wayland 协议不支持,好歹还有绕过的办法。

by 依云 at February 02, 2022 09:23 AM

January 25, 2022

ヨイツの賢狼ホロ

咱和()的 2021 年

有些事情在慢慢变化的嘛,例如咱逐渐开始偷懒到只在 Matters 发表文章这件事。 以及换服务器玩脱了把咱自己的 Mastodon 搞爆炸了什么的……

可以去那里看看咱这一年写了些什么,和发了什么牢骚 。(笑)

以及人老了就喜欢偷懒啦,所以后面大概主要会用那边的 WordPress 来写文章了。

在这里,名字还是一样的。

至于这里嘛,虽然后面可能不会再有大的更新了,不过咱应该还会尽力留着。 万一哪天咱又有心情开始折腾了呢……

咱也谢谢和咱一起路过这六年的大伙儿了。

by ホロ at January 25, 2022 04:00 PM

January 22, 2022

中文社区新闻

linux-firmware 20220119.0c6a7b3-2 需要内核 >=5.3 以及做了拆包

linux-firmware 从 20220119.0c6a7b3-2 开始实现了内核固件压缩。 Linux 内核从 5.3 开始支持加载 xz 压缩过的固件。这需要启用
CONFIG_FW_LOADER_COMPRESS 内核编译选项。所有官方 Arch Linux 内核早已开启了这一选项[1]。

并且 linux-firmware 包已经被拆分成多个小包,进一步减少磁盘占用。拆包将一些很少使用的硬件上较大的固件文件分到了独立的包中。受影响的硬件包括: Mellanox Spectrum 交换机, Marvell 设备, Qualcomm 芯片集, Cavium LiquidIO 服务器适配器, QLogic 设备, Broadcom NetXtreme II 10Gb 以太网适配器。
如果需要的话请确保安装这些附加固件包。 [2]

[1] FS#72899
[2] FS#72559 + svn commit

by farseerfc at January 22, 2022 12:54 AM

December 31, 2021

Leo Shen

2021 Recap: Seek

And here goes 2021. Here's a snippet. What I Wrote In 2021 I wrote 3 English articles and 2 Chinese articles. I actually had a lot of blog ideas during this year, but the vast majority of them failed to become a full blog post. It's so hard to write something that is interesting and unique at the same time. Maybe I should lower my standard a little bit.

December 31, 2021 11:59 AM

December 27, 2021

frantic1048

Pop Up Parade - 真红(Rozen Maiden)

Shinku

看到这款真红的消息时十分激动,竟然能看到早年看的作品里喜欢的角色出手办,宣传图看起来也没啥问题,于是就订了。

拍照的时候想着搞个暗色的环境更搭一点,换上了纯黑色的背景纸,但是没想到反光太强了,拍出来变成了灰色,Darktable 里稍微调了一下也是深灰色。然后尝试灯亮度下来一点,结果噪点过多又拍了第二波。折腾一番之后发现深灰色也挺好的。

头一次遇到不是用纸质而是全透明的包装,还蛮好看的。

Shinku

本体没有需要组装的部件,直接放底座上就好了。巨长的双马尾保护得很好,没有出现悲剧,可喜可贺。

Shinku Shinku Shinku Shinku Shinku Shinku Shinku Shinku Shinku Shinku

Shinku Shinku Shinku Shinku Shinku

比较奇妙的是一些角度看过去,真红的微笑就消失了,特别是稍微偏低一点的角度。

Shinku

角度抬高,微笑又回来了。相机还是抬高一点拍好。

Shinku Shinku Shinku Shinku Shinku

极限距离!

Shinku

December 27, 2021 05:07 PM

中文社区新闻

libxml2>=2.9.12-6 更新需要手动干预

libxml2 包在版本 2.9.12-6 之前缺失了预编译好的 python 模块。这个问题已经在 2.9.12-6 中修复,所以更新时需要覆盖未被跟踪到的 pyc 文件。如果你在升级时遇到如下报错:

libxml2: /usr/lib/python3.10/site-packages/__pycache__/drv_libxml2.cpython-310.opt-1.pyc exists in filesystem
libxml2: /usr/lib/python3.10/site-packages/__pycache__/drv_libxml2.cpython-310.pyc exists in filesystem
libxml2: /usr/lib/python3.10/site-packages/__pycache__/libxml2.cpython-310.opt-1.pyc exists in filesystem
libxml2: /usr/lib/python3.10/site-packages/__pycache__/libxml2.cpython-310.pyc exists in filesystem

更新时请使用命令:

pacman -Syu --overwrite /usr/lib/python3.10/site-packages/__pycache__/\*

完成升级。

by farseerfc at December 27, 2021 08:18 AM