7
25
2023
2

btrfs 元数据满了怎么办

上一篇《btrfs 翻车记》记叙了我们服务器上的 btrfs 出事的情况,好像吓到一些用户了 QAQ。其实那次情况比较特殊啦。一般来说,就算元数据用满了,也不至于改内核代码才能救回来。不过元数据满的问题确实困扰了许多用户,正好这些天群里有不少人遇到了,本文就记录一下元数据满了之后如何处置。

问题和处置

问题的现象是部分文件操作报错「No space left on device」,但是 df 等工具明明报告还有空间。btrfs filesystem usage 的输出是这样:

btrfs filesystem usage 截图

我们可以看到,还有 373G 的空闲空间(Free)呢。但是呢,「Device unallocated」已经不足 1G 了。在充分大的文件系统上,btrfs 会以 1G 为单位来分配块组(block group,简称 bg)。所以现在这个情况,已经无法分配新的 bg 啦。然后我们再往下看,「Metadata」的部分,总共 4.5G,已经用了 4G。还剩下 512M,刚好是 Global reserve 的大小。也就是说,不算保留空间的话,元数据已经没有空间可用了,所以才会报错。

那现在怎么办呢?如果文件系统上有不需要的很大的文件,并且没有快照,删除后空间可以立即释放的话,可以删除试试,看看能不能刚好空出来一个 bg。不然就试着跑一下 balance,像这样:

btrfs balance -dusage=0 截图

这个命令是说,把使用率为 0% 的数据 bg 整理一下。从输出「had to relocate 0 out of ...」可以看出,没有这样的 bg,操作没有效果。可以试试增加 -dusage 的值,看看能不能成功。很遗憾,这个案例中未能成功:

btrfs balance -dusage=1 截图

那只有另外添加一些空间来腾挪数据了。如果有闲置的分区(或者暂时用不上的 swap 分区)就可以拿来用。不然的话插个U盘也行。不需要多大,几个 G 就行。挪好数据就可以去掉了,不会长期使用的。另一种很有风险的做法是使用内存来暂存数据,但这样一旦死机或者断电,整个文件系统就完蛋了,不建议使用。

准备好空闲分区后,就可以 btrfs device add 添加上去了。这里通常要加 -f 参数,抹除分区里原有的数据。注意不要添加错设备了哦。

btrfs device add 截图

可以看到,设备添加上去之后,又可以往文件系统里写数据了。接下来跑 btrfs balance -dusage=10 之类的命令腾些数据 bg 出来就好了。这里要注意只 balance 数据 bg,不要动元数据的 bg,因为元数据越是集中存放,将来就越可能需要分配新的 bg,就越有可能遇到没 bg 可以分配的情况。

btrfs-heatmap 工具可以查看 bg 的分布和使用情况。以下是 balance 好之后的状态(使用 --curve linear 参数):

btrfs heatmap

图中,白色的是数据 bg,蓝色的是元数据 bg。颜色越亮,使用率越高。纯黑的是未分配空间。可以看到,这里有大量用得不多的数据 bg。balance 操作就是把它们给合并了一些,空出来不少黑色区域(最下方的黑色部分是新添加的设备上的未分配空间)。这是 usage 截图:

btrfs filesystem usage 截图

算一算,除去新添加设备的空间,原存储设备上也能有 46G 的未分配空间了(看上去新添加设备并没开始使用,只是让 btrfs 相信它有足够的空间用)。接下来把之前添加的设备删除就可以了:btrfs device del /dev/sdb1 /。等它运行完毕就可以拔掉该设备了(如果是可移动介质的话)。

预防

这个问题的本质就是 bg 的碎片化导致明明看上去有空间,但是元数据用不了,因此报错,需要手动处理。要识别即将出问题的文件系统也很简单:btrfs filesystem usage 看一看,如果「unallocated」很小(不足 1G)就要赶紧 balance 一下了(当然前提是有不少碎片化的空闲空间)。注意,这个时候千万不要删快照!删快照可能会快速消耗保留的元数据空间,从而导致添加设备都加不上、还报错只读的情况。

btrfs 最近添加了自动块组回收(automatic block group reclaim)功能,但默认并没有启用。因为是新功能,可能会有 bug,你也不知道它会什么时候运行,所以我暂时不建议使用。自己写个定时脚本,在系统空闲的时候运行也不错的。


本文中的图像素材均由遇到问题的群友提供。

Category: Linux | Tags: linux btrfs
7
6
2023
8

btrfs 翻车记

如标题所言,我用了多年的 btrfs,终于还是遇到翻车啦。由于文件系统翻车了,相关日志内容缺失,所以本文我仅凭记忆描述事件,就不提供准确的日志输出了。

事件经过

出事的是 archlinuxcn 的编译机。那天中午时分我就收到了 Grafana 给我发送的莫名其妙的报警邮件,称某个监控项无数据了。我去面板上瞅了半天,明明有数据的啊。不解,但是忙别的事情去了,也没有细究。晚些时候,我又收到了好些同类告警,遂登录机器打算检查 Grafana 日志。但操作过程中,退出 zsh 的时候我好像看到了写命令历史时出现「read-only filesystem」的字样?于是检查了一下,发生大事不好了,文件系统真变只读了!

这个 btrfs 是我上次迁移机器的时候换上的,因为我觉得过了这么多年,btrfs 挺稳定了啊,而定时快照很方便,devtools 也支持通过快照来快速创建打包用的 rootfs(虽然大部分时候都在 tmpfs 上打包了用不上)。但我们编译机一直以来有个问题:硬盘有多少用多少。前一任编译机用的 1T 硬盘,是刚刚够用,现在换 2T 了,结果用着用着又只剩下200多GiB啦……肥猫最近开始玩 bees 去重了,听说在其它机器上效果显著。不过这台编译机快照多,这「蜂群」嗡嗡了几天都没完事,还出事了。

btrfs filesystem usage 一看就发现,空闲200多G数据是真的(因此没有触发相关报警),但是元数据满了,也没有未分配空间了。这种事群里有多位群友遇到过了,问题不大,加个设备再 balance 一下就好了。我一开始是这么想的,刚好有两个挺大的 swap 分区能够用来腾挪。结果 btrfs 告诉我,文件系统只读,添加设备失败!那好,我 remount rw 一下。结果 btrfs 说文件系统出毛病了,不支持 remount rw!

这个时候我才感觉事情有点难办了。这个 btrfs 文件系统是 /,并不能卸载啊。没有找到在线修理的办法,只好呼叫凤凰卷,通过 iDRAC 进入 archiso。期间服务器重启了一次,但连过去依旧是只读的。进到 archiso 之后,尝试抢在报错之前添加设备,但是并没有成功。会卡住一会儿,然后报错「No space left on device」。按 farseerfc 的建议,clear_cache 和 zero-log 都试过了,但并没有解决问题。有人建议把大文件 truncate 一下,看看能不能刚好释放出 1G 的连续空间出来,但是我有定时快照呀,truncate 了也不会立即释放空间。最后卷直接下单了新机器,开始 btrfs send……

事后

服务器迁移还比较顺利。数据接收完毕,网络配置更新一下,引导器装好,重启,熟悉的编译机就回来啦~除了 nvchecker 好像跑得有点慢?怎么 ping Google 要 60ms 的?原来是忘记更新 /etc/resolv.conf 了,里边还写着旧 ISP 的 DNS 服务器地址呢。systemd-resolved 这次做了回好事,把 DNS 服务 fallback 到了 9.9.9.9。DNS 解析慢是 fallback 过程造成的,而 ping Google 延迟高,是因为 9.9.9.9 不知道怎么回事,给解析到比较远的地方去了。

新编译机 CPU 比之前那台快了不少,硬盘也增加到了 3.4T。挺好的,除了这时机不太好,旧编译机还有近一个月到期……另外由于是突发状况,所以没有及时缩短 DNS TTL,导致迁移完成之后 DNS 解析没有及时跟上(隔天我陆陆续续从另一台使用这台编译机转发邮件的机器那里收到了好些邮件,都是抱怨这编译机连不上的)。

蜂群(bees)也重新开始工作了。这次快照较少,我还专门为了它们暂停过自动快照,过了一段时间之后首次扫描终于完成了。之后它们就能很快跟上进度,不会消耗大量 CPU 了。

我添加了定时任务来执行 btrfs balance start -dusage=10 /,每周释放一些使用率低的数据块组,避免空间分配了又不怎么用,到最后明明有剩余空间却让元数据无处可写。

farseerfc 对出事的 btrfs 进行了更多不同方案的修复尝试,但依然未能修好。

一些抱怨

没想到我用了这么多年的 btrfs,还是被坑到了。明明还有不少空闲空间,但是 btrfs 不知道用。我看到最近有个「automatic block group reclaim」特性,支持自动回收块组了,但是搜索结果第一项结果是今年年初有人在邮件列表上报告说它有问题……出现问题 ro 挺好的,但是这个状态下不让进行维护操作就太难受了。作为 / 文件系统使用时,对于远程机器来说,即使有 iDRAC 或者 IPMI 之类的东西,用起来也费事,还不得不中断可能还活着的服务。而对于不支持远程访问的机器就更麻烦了,比如在家办公时办公室的机器,或者出差旅游探亲时在家的机器。我也考虑过在 initramfs 里配网络、开 sshd,但是并没有现成的工具,事发时再配的话,一次性成功的可能性太低了。

至于事发原因,蜂群(bees)只是加快了元数据空间的使用(dedupe 快照的结果),其本身并没有问题。出事重启之后,在再次被挂载为只读之前,还是写入了不少数据,包括一次成功的快照(后来查 pacman 数据库损坏的问题时发现的)。这可能是后续添加设备都无法成功的原因。

以前用的 ext4,在文件系统快满时只是碎片化严重、效率降低,它甚至还会给 root 保留一部分空间来处理问题。后来用 zfs,快满了就 0B/s,等于废掉。现在 btrfs 遇到空间不足也没有好太多,变只读了。(我还打算抱怨一下新文件系统可靠性不如旧的来着,想想前不久在群里看到 btrfs 抓到了位反转,还是不抱怨了。大家各有千秋。)


2023年07月08日更新:farseerfc 把它救活了!核心方法是把这里的 global reserve 大小由 512M 改成 2G。因为之前重启了一次,那时不仅成功创建了一个新快照,还删掉了一个旧的。然后它删着删着就把 512M 的 global reserve 给用完了,就报错、事务回滚,于是就过不去了。和邮件列表上这个问题是一样的:Global reserve and ENOSPC while deleting snapshots on 5.0.9 — Linux BTRFS

2023年07月25日更新:其实本文所述内容是罕见情况啦,并没有多少人会遇到的,大家不用害怕。另外新写了一篇《btrfs 元数据满了怎么办》,记录大多数人遇到的元数据满的问题如何解决。

Category: Linux | Tags: linux btrfs
6
29
2023
2

在 nspawn 里运行 docker

有个服务器需要维护,因此需要将其上的所有服务暂时迁移走。打算直接扔到另一台比较闲的服务器上,直接拿 systemd-nspawn 跑起来得了。简单方便,除了网络之外不需要额外配置。但问题是,这些服务里包含一个使用 docker 运行的 ElasticSearch,在同为容器的 nspawn 里跑会有问题吗?

试了一下,还真有的问题。dockerd 会报权限错误而跑不起来。但稍微搜一下就找到了解决方案:

SYSTEMD_SECCOMP=0 systemd-nspawn --capability=all --network-bridge=br0 --boot -D rootfs

nspawn 默认会限制一些权限。这样可以让其不做任何限制,相当于 docker 的 --privileged 参数。然后就可以嵌套着跑啦。

跑起来之后检查一下,所有服务均正常运作了,没有任何问题。把网络配好,外边的 nginx 负责一下转发(因为懒所以没配外网 IP),就可以接替工作啦。

PS: 迁移的过程中遇到了一个小坑。rsync 不加 --numeric-ids 的话会尽量保持用户名不变,等里边的系统跑起来就各种权限问题了。所以转移 rootfs 的时候一定得记着加上 --numeric-ids

Category: Linux | Tags: linux systemd docker
3
5
2023
0

Linux 上的字体配置与故障排除

常见汉字字体

电脑系统要显示字,首先得有字体。现在 Linux 上常用的、在维护的开源中文字体就一套,同时被 Noto思源两个项目收录。Noto 系列字体是 Google 主导的,名字的含义是「没有豆腐」(no tofu),因为缺字时显示的方框或者方框被叫作「tofu」。思源系列字体是 Adobe 主导的。其中汉字部分被称为「思源黑体」和「思源宋体」,是由这两家公司共同开发的,两个字体系列的汉字部分是一样的。

Noto 字体在 Arch Linux 上位于以下软件包中:

  • noto-fonts: 大部分文字的常见样式,不包含汉字
  • noto-fonts-cjk: 汉字部分
  • noto-fonts-emoji: 彩色的表情符号字体
  • noto-fonts-extra: 提供额外的字重和宽度变种

Noto 系列字族名只支持英文,命名规则是 Noto + Sans 或 Serif + 文字名称。其中汉字部分叫 Noto Sans/Serif CJK SC/TC/HK/JP/KR,最后一个词是地区变种。

思源系列则有:

  • adobe-source-sans-fonts: 无衬线字体,不含汉字。字族名叫 Source Sans 3 和 Source Sans Pro,以及带字重的变体,加上 Source Sans 3 VF
  • adobe-source-serif-fonts: 衬线字体,不含汉字。字族名叫 Source Code Pro,以及带字重的变体
  • adobe-source-code-pro-fonts: 等宽字体,不含汉字。字族名叫 Source Code Pro,以及带字重的变体,加上 Source Code Variable。
  • adobe-source-han-{sans,serif,mono}-{cn,hk,jp,kr,tw}-fonts: 五个地区的汉字之黑体、宋体和等宽版本
  • adobe-source-han-{sans,serif,mono}-otc-fonts: 所有地区合体了的汉字之黑体、宋体和等宽版本

其中等宽版本的中文字体位于 [archlinuxcn] 仓库中。

思源汉字字体的字族名有两种,「独立包装」的版本(非 OTC 版本),是「Source Han Sans/Serif」或本地化名称、空格、地区代码(CN/HK/TW/JP/KR)。比如「思源黑体 CN」、「源ノ角ゴシック JP」等。也有带字重的别名。

而全部打包的 OTC 版本,字族名是本地化名称或者英文的「Source Han Sans/Serif」空格再加上「HC/TC/HC/K」变种代码。如果没有变种代码,则是日文变种。为了区分,香港繁体的版本附带「香港」字样,比如黑体叫「思源黑體 香港」。这些字体也有不同字重的别名。另外有个半宽的版本,是在字族名的变种代码前加「HW」字样,仅有少数几个字符是半宽的。

OTC 版本有趣的地方在于,对于大多数软件来说,不管你叫它的哪个地区的名字,它都会以设定的语种来显示。比如网页声明语种为日文(<html lang=ja>),那么不管字体指定为「源ノ角ゴシック」还是「思源黑体」或者「본고딕」,它都会「门上插刀、直字拐弯、天顶加盖、船顶漏雨」。所以用这个字体的话,不妨一律写「Source Han Sans」,然后加好语种标记。我知道的唯一例外是 mpv 的 ass 字幕文件,里边指定本地化名称的话,会使用那个语种的变体显示。

早些年还没有 Noto 和思源的时候,Linux 系统上通常使用文泉驿正黑或者文泉驿微米黑。后者是基于 Android 系统上的 Droid Sans Fallback 字体,体积较小。再之前是文鼎系列字体,也就是名字「AR PL」开头、包名叫 ttf-arphic-{uming,ukai} 的那些。

字体的属性

字体有很多属性,常用的有字族(family)、倾斜(slant)、字重(weight)。后两者合一起叫样式(style)。

字族就是它的名字啦。常见的指代字体的方式除了字族之外还有 Postscript 名,它不含空格、使用短横线将样式附加在名称之后,比如「DejaVuSans-BoldOblique」。后者是 CSS @font-face 规则中使用 local唯一指定样式的方法(除非该字体把样式也写到了字族名里)。

倾斜就是斜不斜,英文叫「Roman」「Italic」或者「Oblique」,Italic 是专门的斜体写法(更接近手写样式), Oblique 是把常规写法倾斜一下完事。

字重就更简单了,就是笔划的粗细。常见的有 Regular、Normal、Medium、Bold、Semibold、Black、Thin、Light、Extralight 等。

详细信息可以 man 5 fonts-conf 查询。

通用字族名

很多时候,程序并不在乎用户具体使用的是哪款字体,像很多网站的 CSS 那样把各个平台的常见字体全部列出来太傻了,又容易出问题。所以,人们发明了「通用字族名」,也就是 sans-serif (sans)、serif 和 monospace (mono) 这些。中文分别叫无衬线字体、衬线字体和等宽字体。但是中文字体不讲衬线不衬线的,而是叫「黑体」和「宋体」(有些地区叫「明体」)。黑体常用于屏幕显示的正文,而宋体常用于印刷文本的正文。

另外,中文没有斜体。英文使用斜体的场合,中文通常是使用仿宋或者楷体。中文本也没有粗体。传统上,强调的时候,中文使用着重号,也就是在字的下方或者右方加点,像这样子(如果你看到的着重号在文字上方,那是因为你用的 Chrome/Chromium 浏览器不听页面指示,执意将它作为日文处理了)。

最近有一个新加的通用字族名叫作「emoji」。Pango 渲染表情符号的文本时,会自动使用 emoji 字体。但是 Qt 尚不支持,导致有时会出问题,而将 emoji 字体排到常规字体之前的做法,又会导致数字和空格显示为全角。火狐自带了一个 SVG 格式的 emoji 字体,会自动使用。很多软件(比如 Telegram)也会使用图片来取代 emoji 字符。

CSS 4 又加了一套 ui- 开头的字族名但是除了 Safari 没浏览器支持。fontconfig 倒是可以通过配置来支持上,但是由于火狐的一个 bug 导致 ui-sans-serif 无效。

fontconfig 配置

大部分 Linux 桌面软件都或多或少地使用 fontconfig 来获取字体配置信息。其中 Pango(GTK 使用的文字渲染库)的支持是最好的。很多简陋的图形界面库则只用来读取默认字体,可能完全不支持字体回落,造成部分文字明明有字体却显示为「豆腐」。

了解了通用字族名,我们就可以为它们指定我们喜欢的字体啦。在 ~/.config/fontconfig/fonts.conf 里为每一个通用字族名像这样写即可:

  <match target="pattern">
    <test qual="any" name="family">
      <string>sans-serif</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Sans</string>
      <string>文泉驿正黑</string>
      <string>Twemoji</string>
      <string>Font Awesome 6 Free</string>
      <string>Font Awesome 6 Brands</string>
      <string>Source Han Sans</string>
    </edit>
  </match>

因为我并没有完全采用思源字体来显示汉字,所以我还是为不同语言和地区变种分别匹配了不同的字体。我完整的配置文件见:https://github.com/lilydjwg/dotconfig/tree/master/fontconfig。其中,web-ui-fonts.conf 文件用于提供 CSS 4 新增的字族名,而 source-han-for-noto-cjk.conf 则使用思源系列字体来代替 Noto CJK 系列字体。

查看浏览器使用的字体

排查字体问题时,一个常见的要知道的事实是,软件究竟在用什么字体来显示这些文本?想知道这个通常很难,但是对浏览器来说却很简单。所以字体匹配问题首先看浏览器能不能复现。

火狐浏览器,对着有疑问的字点右键,选择「检查」(也可以按 Q 键),然后看弹出的开发者工具右边的「字体」选项卡即可。鼠标悬停到下方灰色的字体名上时还能将使用该字体的字高亮显示。

在火狐中查看网页所使用的字体

Google Chrome 浏览器及其变种类似,对着有疑问的字点右键,选择「检查」(也可以按 N 键),然后看弹出的开发者工具右边的「计算样式」选项卡,拖动到最下面,可以看到使用的字体名以及有多少个字形。

在 Google Chrome 中查看网页所使用的字体

至于这个字体是怎么选上的,可以切换到「规则」(火狐)或者「样式」(Google Chrome)选项卡来看 CSS 规则。搜索「font-family」看看具体被应用上的规则是哪一条。通常这里会写上一大排字体名。火狐会将正在使用的那个加上下划线,但是有时候不准确(比如该 HTML 元素使用了多种字体)。更好的除错方法是,从头到尾一个个删字体,删到哪一个时网页上的字体变动了,就说明在使用的是哪一个。我通过这种方式找出了好些我学生时代不懂事从 Windows 下复制过来的字体导致的问题。

Google Chrome 默认的字体比较奇怪,是「Times New Roman」、「Arial」和「Monospace」。见《Google Chrome 中的字体设置》一文。

Qt

https://z.sh/qtfontbugs。其中最著名的 bug 是 QTBUG-80434 (https://z.sh/434)。

小技巧

使用 gucharmap 软件可以检查所有字符使用指定的字体时的渲染效果,以及它回落到什么字体上了。找到要查看的字符,然后对着它按住右键即可。

使用 fc-match -s NAME:charset=HHHH 可以查看针对指定字符的字体优先顺序,包含这个字符的字体会优先。如果不加 -s 就是看指定的模式会匹配上的字体了。其中 HHHH 是该字符的 Unicode 码点之十六进制值。如 fc-match :charset=7684 查看默认字体下「的」字会用什么字体,而 fc-match serif:charset=7684:lang=ja 查看在语种为日文的时候,使用 serif 字族名会使用哪个字体来显示「的」字。使用 fc-list :charset=HHHH 则是查看包含该字符的所有字体。

参考资料

2
9
2023
6

新的 PaddleOCR 部署方案

PaddleOCR 发布 2.6 版本了,支持 Python 3.10 啦,于是可以在 Arch Linux 上跑了~所以我决定再部署一次。

我之前跑 PaddleOCR 有两个方案,使用 chroot 加一大堆 systemd 的限制选项,以及使用 bwrap 和用户命名空间。

chroot 的方案总感觉不知道限制够了没。实际上当初那篇文章写完我就意识到这服务怎么用我的 uid 在跑啊,乱发信号好像还能把我的进程都杀掉的样子。另外这个 chroot 其实是我用来学习、研究和适配 Debian 用的,并不是专门跑这个服务的,感觉有点——怎么说呢——碍事?总之不太好。

bwrap 方案更干净一些,不过创建起来挺麻烦的(所以我才只部署了一次嘛)。不使用用户命名空间可能会简单一些,但那样就是用我的用户在跑了。

所以这次我决定试试方案,使用 systemd-nspawn。另外(再次)尝试了使用 NVIDIA GPU 的版本,把我电脑上闲得发慌的 GeForce 940MX 显卡给用上了。

过程

首先去 Arch 镜像里的 iso/latest/ 目录下载个 archlinux-bootstrap-x86_64.tar.gz 回来。在 /var/lib/machines 下创建个叫 paddleocr 的 btrfs 子卷 / zfs 文件系统 / 普通目录用来存放新的 rootfs。sudo bsdtar xf ...... -C /var/lib/machines/paddleocr 解压出来。记得一定要用 bsdtar 以避免丢失某些文件元信息(虽然我不知道那些信息有啥用但是有警告就是不爽嘛)。

然后就可以 systemd-nspawn -M paddleocr 拿到个 shell 了。这里边只安装了 base 和 arch-install-scripts。可以先修改 pacman 镜像然后 pacman -Syu python 滚一下顺便装上 Python。然后 useradd -s /bin/bash -m -U paddleocr 创建个跑 paddleocr 的用户。su - paddleocr 切过去,python -m venv venv 创建虚拟环境,然后进去按 PaddleOCR 的文档装就行了。装好运行起来没问题之后,写个 for 循环把所有支持的语种都识别一遍,以下载各语言的模型(当然你也可以只下载你想要的)。做好之后可以清一下缓存啥的。gdu 就挺好用的。

哦,以上是 CPU 版本的安装流程。GPU 版本的可没有这么简单。首先要把显卡设备传进这个 nspawn 里。创建 /etc/systemd/nspawn/paddleocr.nspawn 文件,然后里边写上:

[Exec]
ResolvConf=off
NoNewPrivileges=true
User=paddleocr

[Files]
Bind=/run/paddleocr
Bind=/var/cache/pacman/pkg

Bind=/dev/nvidia0
Bind=/dev/nvidiactl
Bind=/dev/nvidia-modeset
Bind=/dev/nvidia-uvm
Bind=/dev/nvidia-uvm-tools

[Network]
Private=true

哦,这里有挂载 pacman 缓存目录前边忘了说,不过这个不重要啦。这里指定了用户,但是可以在命令行上用 -u root 覆盖的,不影响进去维护。私有网络,也就是给它配置个网络命名空间,里边除了 lo 外啥网络接口都没有。那它怎么访问网络呢?它访问不了网络啦。所以要 bind mount 进去一个 /run/paddleocr,用于通信的 UNIX 域套接字将会放在这里。网络不通,走文件系统就好啦。

然后找台机器把 AUR 包 cuda-10.2 和 cudnn7-cuda10.2 打一下,但是不用安装。我们不搞 CUDA 开发,里边有一大堆东西都是不需要的。把需要的库复制进 rootfs 里去就行了。至于需要什么库?进那个虚拟环境的 Python 里,import paddle 然后 paddle.utils.run_check() 跑一下就知道了。复制库之后记得跑 ldconfig 啊。

PaddleOCR 能跑起来之后,就可以把我的服务丢进去跑啦。最终命令长这样:

sudo systemd-nspawn -M paddleocr --user=paddleocr /home/paddleocr/paddleocr-http --loglevel=warn -j 4

-j 参数是限制并发识别数的,避免过载 CPU 或者 GPU,并不是线程数。

跑起来之后,sudo setfacl -m u:$USER:rwx /run/paddleocr/http.sock 给自己授权,然后 curl 一下试试:

time curl -sS -F file=@a.png -F lang=zh-Hans --unix-socket /run/paddleocr/http.sock http://localhost:5174/api | jq .

对于小图片的话挺快的,不到一秒就能出结果。我使用 CPU 版本跑的话,会慢个近十倍的样子。顺便说一下,这是我对服务进行性能优化之后的结果。之前每张图都开新进程跑太慢了。大概是需要加载一大堆库,然后把模型上传到 GPU,每张图一进程的话 GPU 版本反而会明显慢于 CPU 版本。代价是服务会一直占用大约 2G 内存,即使你并没有在用。

系统挂起到内存或者休眠到磁盘时,内存里的内容是被保留了,但是 GPU 显存并没有,大概因此会报 cuda runtime error 999。这时候,只需要停止服务,卸载 nvidia_uvm 内核模块然后重新加载,再启动服务就可以恢复了。如果 nvidia_uvm 卸载不掉的话,那就没办法了,要么重启,要么改用 CPU 版本。NVIDIA 是有个把显存 dump 到内存里存起来的方案的,但是没必要啊,尤其是休眠到磁盘上的时候,多浪费时间啊。

文件下载

你可以直接用我做好的文件。通过本地的 IPFS 服务访问:

http://localhost:8080/ipns/k51qzi5uqu5di433o42zgqk2xck3y160q1hyvqbyyerd36au2pk0c2jw3hcqxx/

你也可以用别的网关来访问,都一样。如果 IPNS 解析失败的话,试试

http://localhost:8080/ipfs/QmNV31bApmgRcHCQjGufQ3zrFDaf6JBWvBt8pU2TA2Baz6/

我把用于跑服务、设置权限的配置文件打了个 Arch 软件包。nspawn 用的 rootfs 也打包上传了。PaddleOCR CPU 和 GPU 版本是分开的,所以有两个包。CPU 版本的 nspawn 叫 paddleocr-cpu,服务名也是。把 rootfs 解压到正确的地方之后,systemctl start paddleocr 或者 paddleocr-cpu 就好啦。用户需要加入 paddleocr 组才能访问 HTTP 套接字哦。

如果遇到CUDA error(803), system has unsupported display driver / cuda driver combination报错,请将系统当前的 libcuda.so.1 复制进 nspawn 里:

sudo cp /usr/lib/libcuda.so.1 /var/lib/machines/paddleocr/usr/local/lib

另外服务配置文件放到 GitHub 上了:paddleocr-service

Category: Linux | Tags: linux systemd OCR
1
21
2023
6

使用 EasyEffects 调整 Bose 音箱的体验

最近到手一个「Bose SoundLink Mini 蓝牙扬声器 II-特别版」音箱,蓝牙名称「Bose Mini II SE SoundLink」。这家伙小巧、沉重,黑色版和我显示器的黑色支架也挺合得来的。然而音质上我遇到了一点问题。

就如同 Bose 产品页说的,它「低音浑厚」。效果就是,只要播放的声音有一点低音,它都给它放大到很明显,震动人心的同时也震动了我的桌面。这用来听强调低音的音乐应该非常有感觉。可是,我听的大部分歌曲都是女声呀。这就像纯净清澈的蓝天蒙上了一层雾霾。

我用白噪声、粉噪声和频率连续变化的正弦波测了一下,用 Spectroid 查看,发现这音箱会加强 100Hz 及 7kHz 附近的声音。所以我把播放的声音处理一下,降低这个地方的强度不就好了吗——嗯,我需要个均衡器。

我记得群里有人提到一个叫 PulseEffects 的软件,于是找了一下。它已经更名为 EasyEffects 啦,不过仅支持 PipeWire。而我还在使用 PulseAudio,于是先装上 pulseeffects-legacy 试了一下。效果十分不错,清澈的女声回来啦(还丢掉了笔记本扬声器所附带的金属感)。不过有点吃 CPU,即使不显示频谱图,也大约得消耗掉 10% 的 CPU。群友说 EasyEffects 的资源占用很小,于是我花了一些时间,切换到 PipeWire 上来啦。

EasyEffects 均衡器截图

切换起来其实不难,我主要是担心有功能不支持以及遇到 bug。pacman -Syu pipewire-pulse pipewire-alsa wireplumber easyeffects就好啦。然后把 PulseAudio 的服务停掉,PipeWire 的对应物开起来,就切换完毕了。PipeWire 的 PulseAudio 兼容性还不错,pavucontrol 用起来完全没有问题,甚至还解决了之前蓝牙编码器在连接之后从 SBC XQ 变回 SBC 的问题。EasyEffects 的 CPU 占用大约在 4%,低了不少。我关心的另一个问题是网络支持,但我发现这个 PipeWire 也兼容了,同样的命令pactl load-module module-native-protocol-tcp auth-ip-acl=192.168.57.0/24对 PipeWire 也能用。

至于 bug 嘛,确实有一些。虚拟机里通过网络播放的时候,偶尔会卡一下。EasyEffects 有时候会需要重启。刚刚不知道为什么音箱明明是连接上的状态,但是就是没声音。重连之后才恢复。我大概会用一些天,如果不是很严重的话我就不切回去了。

哦对了,easyeffects --gapplication-service 这样启动 EasyEffects 就可以不显示图形界面了。但是依旧需要连接上 Wayland 或者 X11,所以需要安排在图形界面启动之后运行。我给 EasyEffects 写了个 systemd 服务,WantedBy 自己写的 xprofile.target,然后在~/.xprofile的最后启动一下,就可以了。

至于其它系统,Windows 上可以使用 Equalizer APO,Android 上我使用的播放器 Poweramp 也有均衡器功能。而且这俩也是可以给指定的输出设备配置的(不过好像 Equalizer APO 只支持一组配置)。

最后再吐槽一下,作为音箱,做不到把声音完美还原也就算了,就请不要主动乱改好吗……哦对了,手机上的相机不少也有同样的问题。我不反对你们后期搞点效果,但是请不要不支持真实的世界。(经常听女声的人,记得看到「低音浑厚」就离远点儿~)

Category: 硬件 | Tags: linux 蓝牙 音频 外部设备
9
7
2022
17

让离线软件真正离线

去年我做了个索引 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 说的是支持那么多种语言,但实际上只有简体中文等少数语言支持得好(繁体都不怎么样),别的语言甚至连语言名和缩写都弄错,越南语识别出来附加符号几乎全军覆没。

Category: Linux | Tags: linux 安全 隐私 网络
8
18
2022
15

我所讨厌的网页行为

有些网页的行为通常不被视为 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 很吵,也会浪费能源、加剧气候变化。

Category: 用户体验 | Tags: 网页 accessibility
8
10
2022
2

tmux 状态栏优化

在 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 自己已经把时间对齐到更新间隔了。

Category: Linux | Tags: tmux Rust linux
8
8
2022
5

Google Chrome 中的字体设置

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 或其变种,所以制作网页上还是得手动指定一些现代点的字体。

Category: Linux | Tags: 字体 Google Chrome

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com