使用 Restic 备份数据

本文来自依云's Blog,转载请注明。 我很早就知道 Restic 这个备份工具了,但是因为我有 rsync 和 btrfs send/receive 方案所以一直没用过。某天,突然有个走 AWS s3 协议的服务器备份需求,这才把它翻了出来。 既然是 s3,首先设置环境变量 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY,然后仓库的地址是 s3:域名/存储桶名。好像一个存储桶里只能初始化一个 Restic 仓库?我还不清楚,也没有能给我玩的环境,就不管了。仓库地址可以设置到环境变量 RESTIC_REPOSITORY 中,这样就不用每次用 -r 参数指定了。执行 restic init 进行初始化。Restic 只支持加密备份,所以必须设置一个备份密码——倒也可以设置为空字符串,但是加密仍旧会进行,并且需要额外的参数来允许空密码。密码也可以放在 RESTIC_PASSWORD 环境变量里。 然后就是执行备份命令了,很简单的,比如: restic backup -x --exclude-caches --exclude='/home/*/.cache/' --exclude=/var/cache/pacman/pkg/'*' ... / 这样就备份了整个系统,并排除了一些目录。可以先加 -v --dry-run 参数测试一遍。确定好备份的参数之后,就可以设定计划、反复执行了: [Unit] Description=backup the system After=network-online.target [Service] Type=oneshot Environment=HOME=/root EnvironmentFile=/etc/restic.conf ExecStart=restic backup -x --exclude-caches ... / [Unit] Description=backup system daily [Timer] OnCalendar=daily RandomizedDelaySec=1h AccuracySec=6h Persistent=true [Install] WantedBy=timers.target 仓库地址和各种密码之类的放环境变量文件里了。把这个文件设置为只有 root 才能读,避免别的用户看到不该看的内容。然后 systemctl enable --now sysbackup.timer 就好了。 之后可以用 restic snapshots 查看备份的数据。使用 restic forget 删除旧的备份,比如 restic forget -d 30 -w 10 -m 10 -l 5 在最近30天、10周、10个月中各保留一份,外边最新的五个。这个命令只是删除这个备份的元数据,其关联的数据不会被删除。之后还要执行 restic prune 来实际删除数据——这个过程会比较费时费力。加 -v 可以查看诸如减小了多少空间占用的数据。 Restic 的备份是打包成块、压缩、加密存储的,带去重功能。但是增量备份会和主机名和路径相同的上一个备份进行比较。一个仓库里可以备份多台机器的相同或者不同的目录,但为了有效率地增量备份,每次增量备份时的目录不能变。这最后一点给我出了个难题。 我把 Arch Linux 中文论坛(以及维基)也用 Restic 备份到 sftp 目标里了。但这俩都是重数据库应用,因此直接对着 / 执行 restic backup 不行,数据有可能因为数据库的不同文件来自于不同的时间点而损坏。该系统在 btrfs 上并且有快照,对着快照备份就好了。问题是 Restic 执意不支持设置源路径,只能从文件系统上读取。我只好用 mount 命名空间把快照弄到 / 上再执行备份。脚本在这里:https://github.com/archlinuxcn/misc_scripts/blob/master/wiki/restic-backup-snapshot。由于权限的问题,用 bwrap 是不行的。对快照再做一个固定路径的可写快照来备份倒是能把路径固定到一个指定的位置,就是不太优雅,事后删掉快照也有额外的性能消耗。 关于备份数据的大小,restic snapshots 显示的是所有文件的名义大小的总和——是压缩前的大小,并且硬链接会算多次。restic stats --mode=restore-size 则是按硬链接去重后的未压缩大小。restic stats --mode=raw-data 才是压缩后的、备份实际占用的空间。 Restic 的备份仓库是加密的,因此无法直接查看,但它有 restic mount 子命令可以把备份仓库挂载了查看内容,就是性能比较差,只适合检查和恢复特定的少量文件。恢复大量数据推荐用 restic restore。另有 restic diff 命令可以查看不同快照之间的文件差异(新增、修改、删除了哪些文件,不含内容),方便在大小出现异常时查找元凶。 最后说说配置只能使用 sftp 的用户的方法。 首先在 sshd_config 里设置 Subsystem sftp internal-sftp。因为之后要进行 chroot,访问不到外部 sftp 服务的可执行文件的。然后设置指定的用户组只能用 sftp: Match Group sftp-users ChrootDirectory /srv/sftp X11Forwarding no AllowTcpForwarding no AllowAgentForwarding no ForceCommand internal-sftp -d /%u 最后创建用户,按常规设置 authorized_keys,并按之前配置的路径,在 /srv/sftp 下创建该用户与其用户名同名的存储数据的目录,记得要 chown 到该用户。注意 ChrootDirectory 的目标目录(也就是这里的 /srv/sftp)应当只能由 root 写入(就跟 /home 目录那样)。

2025/9/23
articleCard.readMore

Arch Linux 中文论坛迁移杂记

本文来自依云's Blog,转载请注明。 本篇只是心得体会加上赞美和吐槽。技术性的迁移记录在这里。 起 一个多月前,肥猫在 Arch Linux 中文群里说: (希望有好心人研究一下php74,最好加到archlinuxcn里,因为咱中文社区论坛卡在这个老版本了 然后话题自然就又到了论坛迁移的事情上来——毕竟 FluxBB 年久失修也不是一两天的事了。Arch Linux 官方讨论了几年还没结果,但中文社区这边并不是卡在往哪迁上,而是 基于什么的都可以迁移,但得有人干活 然后又过了些天,论坛使用的本就递送困难的 Sendgrid 停止了免费服务,导致中文论坛完全无法注册新用户了。虽然这件事通过更改为使用我们自己的邮件服务器就解决了,但迁移论坛的想法在我脑中开始成长。 承 至于迁移到哪个软件,我早已有了想法——包括 OpenSUSE 中文社区、Debian 中文社区、NixOS、OpenSUSE、Fedora、Ubuntu、Manjaro、Garuda Linux、CachyOS、KDE、GNOME、Python、Rust、Atuin、F-Droid、OpenWrt、Let's Encrypt、Mozilla、Cloudflare、Grafana、Docker 等等(还有一堆我没有那么熟悉的就不列了)大家都在用的 Discourse。这么多开源社区和商业组织都选择了它,试试总不会错的,用户也会比较熟悉。令我惊喜的是,它甚至有个 FluxBB 导入脚本。 于是在虚拟机里安装尝试。安装过程是由他们的脚本驱动构建的 docker 镜像,没什么特别的——除了比较耗资源。Docker 嘛,硬盘要吃好几 GiB,然后它是现场编译前端资源文件,CPU 和内存也消耗不少。默认的构建是包含 PostgreSQL、Redis、Nginx 的,但 PostgreSQL 也可以用外边的,就是得监听 TCP,并且构建出来的镜像里依旧会存在 PostgreSQL 的服务文件。Nginx 可以改成监听 UNIX 域套接字,然后让外边我自己的 Nginx proxy_pass 过来,这样证书也按自己的方式管理。Redis 我本来也是想拆出来的,但是为安全起见要设个密码嘛,然后构建就失败了……不过算了,反正也没别人用 Redis。 本地测的没有问题,于是去服务器上部署。由于发现它比较吃资源,所以给服务器加了几十G内存、100G 硬盘,还有闲置的 CPU 核心也分配上去了。后来发现运行起来其实也还好,就是内存吃得比较多——每个 Rails 进程大几百,32个加起来就快 10G 了。运行起来之后 CPU 不怎么吃,甚至性能比 MediaWiki 要好上不少,以至于我把反 LLM 爬虫的机制给降级到大部分用户都不需要做了。哦它的 nginx 会起 $(nproc) 个,太多了,被我 sed 了一下,只留下了八只(但其实也完全用不上,毕竟是异步的;Rails 那些进程可是同步的啊)。 run: - exec: sed -i "/^worker_processes/s/auto/8/" /etc/nginx/nginx.conf 说回部署。跑起来是没什么问题的。问题出在那个 FluxBB 导入脚本——本来导入就不快,它还跑到一半崩了,修了还崩。然后它不支持导入个性签名、用户头像、置顶帖,还遇到著名的 MySQL「utf8 不 mb4」的问题。来来回回修了又改,花了我好些天。等保留数据测试的时候,发现有好多帖子的作者变成「system」了。一查才发现我刻意没有导入被封禁的用户造成的。都已经配得差不多了,实在是不想删库重来,研究了一下,直接在 Rails 控制台写了段脚本更正了。这个 Rails 控制台能在 Rails 的上下文里交互式地执行代码,很方便改数据,我很喜欢,比 PHP 方便太多了! 另外这个导入脚本有一点好的是,它能够反复执行来更新数据——虽然这种支持反复执行的操作在我写的脚本里是常有的事,基本上从头开始成本太高的都会有,但别人写的脚本能考虑到这一点的可太少了。 用户的密码也导入了!帖子的重定向服务也写好了!虽然后来发现用户个人页面是登录用户才能访问的,给它重定向没什么用……反而是 RSS 重定向更有用,后来也补上了! 后来还发现有些用户名包含空格或者特殊符号啥的,被自动改名了。好在可以用邮箱认用户,不管了,等受影响的用户出现了再改。另外正式迁移之后才发现还有些数据没有迁移——版块的对应关系、用户的主题订阅,不是很重要,算了。 转 然后就迁移结束啦~所有到旧论坛的访问全部重定向到新论坛啦~不过我还是保留了一个不跳转的后门以便有需要时回去看看。为此我折腾了好久神奇的 nginx 的配置文件,最终得到以下片段: set $up 'redir'; if ($http_cookie ~ "noredir=1") { set $up 'noredir'; proxy_pass https://104.245.9.3; } if ($up = redir) { proxy_pass http://127.0.0.1:9009; } 就是根据 cookie 来 proxy_pass 到不同的服务啦。这样就可以访问一下 /noredir 设置上 cookie,就可以访问旧论坛,再访问一下 /yesredir 清一下 cookie 就恢复跳转到新论坛了。 说起 nginx,Discourse 还在这方面坑了我一下。它文档里给的设置是: proxy_set_header Host $http_host; 这个配置在 HTTP/3 时是坏的,应该用 $host。别问我 HTTP/2 也没有 Host 头啊,为什么它在用 HTTP/2 时就不会出错。我也不知道 ¯\(ツ)/¯。 于是新论坛上线啦~很多中国大陆用户的首次加载时间变成几十秒啦……还好这只是无缓存加载的时间,就当是下载软件了吧。之后每个标签页大约需要一两秒加载整个 SPA,在不同页面之间跳转并不慢。而这代价付出之后的回报是更现代的界面、丰富的功能。相比于旧论坛,现在: 终于有手机版啦,甚至还支持 PWA,体验非常丝滑。 实时预览的 markdown + bbcode 编辑器,还支持上传图片!再也不会有用户问论坛怎么传图片和日志了! 编辑器还支持草稿功能!不用怕写一半弄丢了,甚至可以换个设备接着写。 代码块角落里有个复制按钮,我再也不需要拖半天鼠标来复制日志然后粘贴进 Vim 里分析了! 快速、简单的搜索体验,用户再也不会找不到搜索功能在哪里了! 实时显示在回复的人。有人在回复的时候就可以等一等,发布帖子之后会立刻出现。发帖不需要跳转到不知道干什么的跳转页面了,读帖也不需要反复刷新了。 有收藏功能了,用户不用发一个根本没什么用的「mark 一下」的帖子了。 有「标记为已解决」的功能了。用户再也不需要问怎么把帖子标记为「已解决」了。 不用自己跑脚本解析 HTML 来向群里发新帖通知了。Discourse 的「聊天集成」功能配一下就好了。 甚至还有「RSS Polling」插件,可以把主站新闻转到论坛里,方便大家讨论。 Discourse 的邮件集成功能也挺不错的。配好之后,可以检测到退信,也可以直接回复邮件通知来回帖。甚至还有个邮件列表模式,就是把所有帖子都给用户发一遍,用户也可以直接回帖。通过邮件发布新主题的功能也有,但我没有启用——不同版块需要配不同的收件地址,有点麻烦,我不觉得有人会想用……就是这个邮件传回 Discourse 部分坑了我一把,但不是 Discourse 的错。 是 maddy 的文档太缺欠了。我要把 forum+...@archlinuxcn.org 这种地址给重写到 noreply@archlinuxcn.org,按例子像这样 table.chain local_rewrites { optional_step regexp "forum\+(.+)@(.+)" "noreply@$2" optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3" optional_step static { entry postmaster postmaster@$(primary_domain) } optional_step file /etc/maddy/aliases step sql_query { driver postgres dsn "user=maddy host=/run/postgresql dbname=maddy sslmode=disable" lookup "SELECT mailname FROM mailusers.mailinfo WHERE $1 = ANY(alias) and new = false" } } 这里第二行是我加的(虽然一开始把 $2 照着下边那个已有的写成了 $3)。结果是报错「用户不存在」、被退信。我开 debug 选项研究了好久,才意识到最后一步写的是 step,所以它总是要执行的——然而 noreply 这个用户并不在数据库里,所以就找不到了。 那把最后一行改成 optional_step 就好啦——我是这么想的,也是这么做的。然后就有人报告说 admin 邮箱拒收邮件了……又是一通研究,才发现因为这里的步骤全是 optional_step,所以 maddy 第一次用整个邮件地址来查的时候,无论如何都是会通过的——不会返回「目标不存在」,所以也就不会触发去掉域名、只用用户名查询的步骤,而数据库里记录的只有用户名,就导致 admin 邮箱的映射查不到了(映射到它本身,然而并没有以它为名的邮箱)。把 SQL 查询那一行改成这样子就好了: lookup "SELECT mailname || '@archlinuxcn.org' FROM mailusers.mailinfo WHERE regexp_replace($1, '@archlinuxcn.org$', '') = ANY(alias) and new = false" 然后是把收件的邮件交给 Discourse。他们有个 mail-receiver 容器用来干这事,但这个容器的主要部分其实是 Postfix。我读了一下它的代码,实际上只需要把邮件通过 API 发过去就行了。于是我用 Python 写了一个服务——imap2discourse。这部分的坑在于,这个 API 是把邮件全文用指定的参数 base64 或者不 base64 用 form-data 传过去的,我以为是用上传文件的方式来传,搞了半天它都报奇奇怪怪的错,后来一步一步在 irb 里按 mail-receiver 的代码对照检查,才发现原来是按传字符串的方式传的…… Discourse 的中文翻译不怎么样,好多随意的空格,也好多看不懂的翻译。好在它像 MediaWiki,支持修改界面文本。于是就一点一点地修了好多。上游使用的是 Crowdin 翻译平台,并不能直接 pr 翻译,所以等我什么时候研究一下才能把翻译贡献给上游了。 Discourse 的通知功能挺全面的。可以选择回帖之后要不要通知,邮件通知是只在没访问时发、完全不要还是全都要,网站图标上要不要显示通知计数,还可以开启浏览器的推送通知(然后我就发现 Android 火狐的推送通知无法切换到 PWA 窗口)。 至于管理功能,比 FluxBB 丰富好用太多啦~有各种访问统计报表。设置项有搜索功能。有管理员操作日志,也有选项变更日志,还有邮件收发日志(看看谁又把自己的邮箱域名拼错了)。能给用户添加备注,也能切换成指定的用户看看他们看到的论坛是什么样子的。能给用户添加字段,让用户填写他们用的操作系统和桌面环境,省得回帖时经常要询问。还能加载自定义的 JavaScript 和 CSS,甚至是加强版本。还有暂时用不上的 API 和 webhook。哦对了,我发现它还会自己拒绝一些常见的讨厌爬虫。 合 除了 FluxBB 之外,还有一个叫 planetplanet 的 RSS 聚合软件也是死了好多年,导致 planet.archlinuxcn.org 多年不更新了。Discourse 正好有从 RSS 发帖的功能,于是将星球也复活了一下,将大家的 RSS 作为帖子在专门的版块发出。虽然界面不是很理想,但将就着用啦。RSS 聚合也是有的。Discourse 的 RSS 功能相当完善,几乎在所有合理的网址后边添加 .rss 就能订阅。 也给旧论坛做了个静态存档站。暂时还没上线,因为肥猫又跑掉了。 Discourse 的备份功能会报错,因为它的容器里的 pg_dump 版本比较旧,和我在外边 Arch Linux 里运行的版本不一致。不过我觉得这样也挺好的——因为管理员是可以生成和下载备份文件的,也就是说,如果有管理员的权限被人恶意获取,那么他就能通过下载备份文件的方式获取整个 Discourse 数据库的内容。备份不了就少了这么个风险啦。当然备份我肯定是做了的,至于是如何做的,就等下一篇啦。

2025/8/26
articleCard.readMore

pacfiles: 高速的 pacman -F 替代品

本文来自依云's Blog,转载请注明。 缘起 Linux 发行版的软件包管理器通常都会提供这么一个功能——查找文件在哪个仓库中存在的软件包里。实现起来也挺简单:仓库维护一个每个软件包里都有哪些文件的数据库,软件去查就可以了——假如用户不介意性能问题的话。 最开始,我使用的是 pkgfile。它是使用 C++ 编写的,会把 Arch 官方提供的 .files 数据库(压缩的 tar 归档)转成 cpio 归档再用(压缩可以靠 btrfs,问题倒是不大)。它比 pacman -F 可快多了,但是我后来不用了,因为它当时不支持多架构——即在 pacman.conf 里把 Architecture 设置为多个值,比如我用的 x86_64 x86_64_v3。现在等我写好了 pacfiles,才发现它终于在大半年之前支持多架构了……不过它看起来开发还是不太活跃,选项和输出格式也和 pacman -F 有很大的差别。 效果对比 最主要的功能是按文件名搜索,因此让我们先看看这个: pacman -F 和 pkgfile 都是遍历整个数据库。pacman -F 和 pacfiles 是单线程的,pkgfile 是多线程,但我不知道为什么 pacman -F 会慢那么多。pkgfile 比 pacfiles 快一些,毕竟它提供的信息少、又不好看、还是多线程并行工作。另外值得注意的是,pacman -F 由于会预先加载整个数据库到内存,因此内存占用了近 3G。 有时候也会想要按完整路径搜索: 这次 pacfiles 因为有索引的帮助,并且不需要检查软件包是否已安装,比 pkgfile 快了不少。pacman -F 依旧又慢又吃内存。 接下来看看输出软件包的文件列表。这个由于输出结果多、输出格式又都差不多,我就重定向扔掉了,只看性能数据。 这次 pkgfile 比 pacfiles 略快。 有时候也会想用正则搜索: 这次 pkgfile 比 pacfiles 快了不少。使用正则搜索时,pacfiles 没有使用索引,也是遍历数据,所以快不起来了。 不过 pacfiles 是支持通配符搜索的,也能用上索引,很快的。pacman -F 不支持这个。而 pkgfile 嘛……它不仅慢,好像还又出 bug 了。 如果我写 pacfiles 之前得知 pkgfile 修了多架构那个 bug,我也许就不会写 pacfiles 了。不过现在对比下来,我也不后悔啦。 另外值得注意的是,pacfiles 无论是输出、还是命令行选项,都尽力兼容 pacman -F 的,以方便用户迁移。 幕后 其实我很早就想弄一个更快的 pacman -F 了。我首先想到的是,把数据塞进 SQLite3 里让它查。性能确实是好得不得了,但是一看生成的数据库,好几个 G……后来又尝试像 pacman -F 那样直接读压缩包,但是不一次性加载到内存,因此不需要那么多内存。但结果并不理想:解压和遍历搜索都不太能快得起来,最多并行处理多个数据库而已。plocate 是很快啦,但是它的数据结构是自己定制的,并不是库,不能直接拿来用。于是此事便放下了。 直到前不久,我读到《Succinct data structures》一文,特别是文中提到的 FM-index——这不正好能用来搜索文件名吗?不过,plocate 用的是什么数据结构来着?于是我去翻代码恢复了一下久远的记忆。哦,是 zstd 压缩的 trigram 倒排索引啊。好像也不错,还支持通配符呢。正则搜索它倒是没用上索引,因为作者认为「使用 locate 进行正则搜索太小众了」所以没有花精力去实现。 但是,以上关于数据结构的内容都不是重点!重点是,我发现了个 plocate-build 命令!它支持从纯文本创建 plocate 数据库!那我不是直接把文件名传给它就好了嘛~唯一有点遗憾的是,它不支持从管道读取文件名列表,因此需要先输出到临时文件中再给它使用,过程中会占用不少内存(/tmp 空间)。至于查询,调用 plocate 命令拿到结果再稍微处理一下就好了。于是想到就做,这就有了现在的 pacfiles(其实早期版本也在 git 历史里有)。 项目地址:https://github.com/lilydjwg/pacfiles。AUR 有 pacfiles-git 包。也可以 cargo install pacfiles 安装。

2025/3/5
articleCard.readMore

用 Android 手机当电脑的话筒

本文来自依云's Blog,转载请注明。 我之前是使用 ROC 来做这件事的。手机上安装 roc-droid,电脑上安装 pipewire-roc 然后执行 pactl load-module module-roc-source source_name=roc-source 就行。 但是这样会有一个问题:手机上的 roc-droid 会被休眠。换手机之前用的 Android 10 还好一点,可以设置半小时的「超长」关屏时间,并且屏幕关闭之后 roc-droid 还能活跃一段时间。现在换 Android 14 了,关屏之后 roc-droid 会立刻被休眠,也不能把 roc-droid 切到后台,否则录音会停止。为了让录音不中断,只能让手机「喝点咖啡因」来保持亮屏,于是不光网络和录音费电,屏幕也要费电。其实这个问题不是不能解决,放个持久通知就可以了,但是我不会 Android 开发呀。 ROC 方案另外的小问题有:网络会持续占用,即使没在使用。手机要么录音、要么播放,需要手工切换。roc-droid 时不时会崩溃。 后来从群友那里了解到可以在 termux 里跑 PulseAudio,我试了试,比 ROC 方案好用多啦。 手机上除了需要安装 termux 和 pulseaudio 外,还需要安装 Termux:API。为了方便启动,我还安装了 Termux:Widget。记得给 Termux:API 话筒权限。然后编辑 PulseAudio 配置文件 /data/data/com.termux/files/usr/etc/pulse/default.pa.d/my.pa: load-module module-sles-source load-module module-native-protocol-tcp auth-ip-acl=电脑的IP地址 auth-anonymous=true 这里的 sles 模块是用来录音的。 编辑 /data/data/com.termux/files/usr/etc/pulse/daemon.conf 文件,设置一小时不用才自动退出(默认20秒太短了): exit-idle-time = 3600 然后在需要的时候执行 pulseaudio 命令就可以了。 电脑上的话,其实设置 PULSE_SERVER 环境变量就可以用上了。不过为了更好的集成,我们创建个 tunnel: pactl load-module module-tunnel-source server=tcp:手机的IP地址 source 就是把手机当话筒用,改成 sink 的话则是把手机当音箱用了。 执行之后,在 PulseAudio / PipeWire 里就会多出来相应的 source(或者 sink)设备了。想怎么用就可以怎么用了~ 但若是要同时使用另外的音箱来播放声音的话,手机话筒会把音箱播放的声音录进去,造成「回声」。这时候,就需要设置一下回声消除了。我参考了 ArchWiki,PipeWire 配置如下: context.modules = [ { name = libpipewire-module-echo-cancel args = { monitor.mode = true source.props = { node.name = "source_ec" node.description = "Echo-cancelled source" } } } ] 然后去 pavucontrol 里设置一下它生成的两个录音操作的设备(一个是选话筒,另一个是选外放的音箱的 monitor 设备),并把消除了回声的 source 设备设置为默认音频输入设备就好了。

2025/1/11
articleCard.readMore

使用 ffmpeg 对音频文件进行响度归一化

本文来自依云's Blog,转载请注明。 我喜欢用本地文件听歌:没有广告、没有延迟、没有厂商锁定。但是有个问题:有的歌曲文件音量挺大的,比如 GARNiDELiA 和桃色幸运草Z的都感觉特别吵,需要调小音量,但有的音量又特别小,以至于我时常怀疑音频输出是不是出了问题。 这时候就要用到响度归一化了。响度衡量的是人的主观感知的音量大小,和声强——也就是声波的振幅大小——并不一样。ffmpeg 自带了一个 loudnorm 过滤器,用来按 EBU R128 标准对音频做响度归一化。于是调整好参数,用它对所有文件跑一遍就好了——我最初是这么想的,也是这么做的。 以下是我最初使用的脚本的最终改进版。是的,改进过好多次。小的改进如排除软链接、反复执行时不重做以前完成的工作;大的改进如使用 sem 并行化、把测量和调整两个步骤分开。之所以有两个步骤,是因为我要线性地调整响度——不要让同一个音频不同部分受到不同程度的调整。第一遍是测量出几个参数,这样第二遍才知道怎么调整。只过一遍的是动态调整,会导致调整程度不一,尤其是开头。 至于参数的选择,整体响度 I=-14 听说是 YouTube 它们用的,而真峰值 TP=0 和响度范围 LRA=50 是因为我不想给太多限制。 #!/bin/zsh -e for f in **/*.{flac,m4a,mp3,ogg,opus,wma}(.); do json=$f:r.json if [[ -s $json || $f == *_loudnorm.* ]]; then continue fi echo "Processing $f" export f json sem -j+0 'ffmpeg -i $f -af loudnorm=print_format=json -f null /dev/null </dev/null |& sed -n ''/^{$/,/^}$/p'' > $json; echo "Done with $f"' done sem --wait for f in **/*.{flac,m4a,mp3,ogg,opus,wma}(.); do json=$f:r.json output=$f:r_loudnorm.$f:e if [[ ! -f $json || -s $output || $f == *_loudnorm.* ]]; then continue fi echo "Processing $f" export f json output sem -j+0 'ffmpeg -loglevel error -i $f -af loudnorm=linear=true:I=-14:TP=0:LRA=50:measured_I=$(jq -r .input_i $json):measured_TP=$(jq -r .input_tp $json):measured_LRA=$(jq -r .input_lra $json):measured_thresh=$(jq -r .input_thresh $json) -vcodec copy $output </dev/null; echo "Done with $f"' done sem --wait 不得不说 zsh 的路径处理是真方便。相对地,sem 就没那么好用了。一开始我没加 </dev/null,结果 sem 起的进程全部 T 在那里不动,strace 还告诉我是 SIGTTOU 导致的——我一直是 -tostop 的啊,也没见着别的时候收到 SIGTTOU。后来尝试了重定向 stdin,才发现其实是 SIGTTIN——也不知道 ffmpeg 读终端干什么。另外,给 sem 的命令传数据也挺不方便的:直接嵌在命令里,空格啥的会出问题,最后只好用环境变量了。 等全部处理完毕,for f in **/*_loudnorm.*; do ll -tr $f:r:s/_loudnorm//.$f:e $f; done | vim - 看了一眼,然后就发现问题了:有的文件变大了好多,有的文件变小了好多!检查之后发现是编码参数变了:mp3 文件全部变成 128kbps 了,而 flac 的采样格式从 s16 变成了 s32。 于是又写了个脚本带上参数重新处理。这次考虑到以后我还需要对单个新加的歌曲文件处理,所以要处理的文件通过命令行传递。 #!/bin/zsh -e doit () { local f=$1 local json=$f:r.json local output=$f:r_loudnorm.$f:e echo "Processing $f" if [[ -s $json || $f == *_loudnorm.* ]]; then else ffmpeg -i $f -af loudnorm=print_format=json -f null /dev/null </dev/null |& sed -n '/^{$/,/^}$/p' > $json fi if [[ ! -f $json || -s $output || $f == *_loudnorm.* ]]; then else local args=() if [[ $f == *.mp3 || $f == *.m4a || $f == *.wma ]]; then local src_bitrate=$(ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate -of json $f | jq -r '.streams[0].bit_rate') args=($args -b:a $src_bitrate) fi if [[ $f == *.m4a ]]; then local src_profile=$(ffprobe -v error -select_streams a:0 -show_entries stream=profile -of json $f | jq -r '.streams[0].profile') if [[ $src_profile == HE-AAC ]]; then args=($args -acodec libfdk_aac -profile:a aac_he) fi fi if [[ $f == *.opus ]]; then local src_bitrate=$(ffprobe -v error -select_streams a:0 -show_entries format=bit_rate -of json $f | jq -r '.format.bit_rate') args=($args -b:a $src_bitrate) fi if [[ $f == *.ogg ]]; then local src_bitrate=$(ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate -of json $f | jq -r '.streams[0].bit_rate') if [[ $src_bitrate == null ]]; then src_bitrate=$(ffprobe -v error -select_streams a:0 -show_entries format=bit_rate -of json $f | jq -r '.format.bit_rate') fi args=($args -b:a $src_bitrate) fi if [[ $f == *.flac ]]; then local src_sample_fmt=$(ffprobe -v error -select_streams a:0 -show_entries stream=sample_fmt -of json $f | jq -r '.streams[0].sample_fmt') args=($args -sample_fmt:a $src_sample_fmt) fi ffmpeg -loglevel error -i $f -af loudnorm=linear=true:I=-14:TP=0:LRA=50:measured_I=$(jq -r .input_i $json):measured_TP=$(jq -r .input_tp $json):measured_LRA=$(jq -r .input_lra $json):measured_thresh=$(jq -r .input_thresh $json) $args -vcodec copy $output </dev/null touch -r $f $output fi } for f in "$@"; do doit $f done 然后我就神奇地发现,sem 不好用的问题突然没有了——我直接 parallel loudnorm ::: 文件们 就好了嘛……

2024/12/11
articleCard.readMore

为团队部署邮件服务

本文来自依云's Blog,转载请注明。 给服务器上的程序部署邮件服务十分简单,装个 Postfix 就搞定了。然而给人用的话就远远不够了。之所以要干这事,主要原因是之前使用的 Yandex 邮箱老出问题,丢邮件都算小事了,它还不让我登录 Web 界面,非要我填写我从未设置的密保问题的答案…… 准备工作 要部署邮件服务,首先当然要有域名和服务器了。需要注意的是,最好使用可以设置 PTR 记录的服务器,有些邮件服务器会要求这个。 邮件传输代理 这是最重要的部分。邮件传输代理,简称 MTA,是监听 TCP 25 端口、与其它邮件服务器交互的服务程序。我最常用的是 Postfix,给服务器上的程序用的话,它相当简单易用。但是要给它配置上 IMAP 和 SMTP 登录服务、以便给人类使用的话,就很麻烦。好在之前听群友说过 maddy,不仅能收发邮件,还支持简单的 IMAP 服务。唯一的缺点是不支持通过 25 端口发送邮件——需要走 465 或者 587 端口,登录之后才能发件。它的账号系统也是独立于 UNIX 账号的,给程序使用需要额外的配置。 具体配置方面,首先是域名和 TLS 证书。我不知道为什么,它在分域名证书的选择上有些问题,最后我干脆全部用通配符证书解决了事。数据库我使用的是 PostgreSQL。要使用本地 peer 鉴权的话,需要把 host 的值设置为 PostgreSQL 监听套接字所在的目录,比如我是这样写的: dsn "user=maddy host=/run/postgresql dbname=maddy sslmode=disable" PostgreSQL 监听套接字所在目录是编译时确定的。maddy 是 Go 写的,并不使用 libpq,因此它无法自动确定这个目录在哪里,需要手动指定。 关于邮箱别名,可以使用文本文件配置,也可以使用数据库查询指定。别名功能可以用来实现简单的邮件列表功能——发往某一个地址的邮件会被分发到多个实际收件人的邮箱中。但是它不支持去重,也就是说,往包含自己的别名地址发送邮件,自己会额外收到一份。设置起来大概是这样子的: table.chain local_rewrites { optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3" optional_step static { entry postmaster postmaster@$(primary_domain) } optional_step file /etc/maddy/aliases step sql_query { driver postgres dsn "user=maddy host=/run/postgresql dbname=maddy sslmode=disable" lookup "SELECT mailname FROM mailusers.mailinfo WHERE $1 = ANY(alias) and new = false" } } 哦对了,那个 postmaster 地址需要手动合并,不然就要每个域名创建一个账号了。在别名文件里写上 postmaster@host2: postmaster@host1 就行了。 maddy 会经常检查别名的修改时间然后自动重新加载,数据库查询当然是查出来是什么就是什么,所以还是比 Postfix 每次跑 postalias 命令要方便不少。 DNS 配置 邮件域名的 MX 记录当然要设置上的。邮件服务器 IP 的 PTR 记录也要设置到服务器的域名上(A / AAAA 记录指到服务器)。SPF 的记录也不能忘。DMARC 和 DKIM 的记录没那么重要,不过推荐按 maddy 的文档设置上。 我还给域名设置 imap、imaps 和 submission 的 SRV 记录,但似乎客户端们并不使用它们。 这些设置好之后就可以去 https://email-security-scans.org/ 发测试邮件啦。 反垃圾 maddy 内建对 rspamd 的支持,所以就用它好了。直接在 smtp 的 check 节里写上 rspamd 就好了。rspamd 跟着官方教程走,也基本不需要什么特别的设置,就是官方给的 nginx 配置有些坑人。我是这样设置的: location /rspamd/ { alias /usr/share/rspamd/www/; expires 30d; index index.html; try_files $uri $uri/ @proxy; } location @proxy { rewrite ^/rspamd/(.*)$ /$1 break; proxy_pass http://127.0.0.1:11334; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; } 注意这里给静态文件设置了过期时间,不然每次访问都要下载那些文件,非常慢。我是挂载在子路径下的,需要通过 rewrite 配置把子路径给删掉再传给 rspamd,不然会出问题。 邮件客户端自动配置 上边提到了 SRV 记录并不管用。实际上管用是在 https://autoconfig.example.org/mail/config-v1.1.xml 的配置文件。具体可以看 Lan Tian 的《编写配置文件,让 Thunderbird 自动配置域名邮箱》这篇文章。 Web 邮件客户端 使用的是 Roundcube,是一个 PHP 软件。可以跟着 ArchWiki 的教程配置。注意最好别跟着配置 open_basedir,因为会影响同一 php-fpm 实例上的其它服务。另外记得配过期时间,不然每次都要下载静态资源,很慢的。 因为上边部署了 rspamd 反垃圾服务,所以也可以给 Roundcube 启用一下 markasjunk 插件,并在 /usr/share/webapps/roundcubemail/plugins/markasjunk/config.inc.php 配置一下对应的命令: $config['markasjunk_spam_cmd'] = 'rspamc learn_spam -u %u -P PASSWORD %f'; $config['markasjunk_ham_cmd'] = 'rspamc learn_ham -u %u -P PASSWORD %f'; 不过我配置这个之后,命令会按预期被调用,但是 rspamd 的统计数据里不知为何总显示「0 Learned」。把垃圾邮件通过命令行手动喂给它又会提示已经学过该邮件了。

2024/10/24
articleCard.readMore

使用 nftables 屏蔽大量 IP

本文来自依云's Blog,转载请注明。 本来我是用 iptables 来屏蔽恶意IP地址的。之所以不使用 ipset,是因为我不想永久屏蔽这些 IP。iptables 规则有命中计数,所以我可以根据最近是否命中来删除「已经变得正常、或者分配给了正常人使用」的 IP。但 iptables 规则有个问题是,它是 O(n) 的时间复杂度。对于反 spam 来说,几千上万条规则问题不大,而且很多 spam 来源是机房的固定 IP。但是以文件下载为主、要反刷下行流量的用途,一万条规则能把下载速率限制在 12MiB/s 左右,整个 CPU 核的时间都消耗在 softirq 上了。perf top 一看,时间都消耗在 ipt_do_table 函数里了。 行吧,临时先加补丁先: iptables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 这样让已建立的连接跳过后边上万条规则,就可以让正常的下载速度快起来了。 此时性能已经够用了。但是呢,还是时不时需要我手动操作一下,删除计数为零的规则、清零计数、合并恶意 IP 太多的网段。倒不是这些工作自动化起来有困难(好吧,让我用 Python 3.3 来实现可能是有些不便以至于至今我都没有动手),但是这台服务器上有新工具 nftables 可用,为什么不趁机试试看呢? 于是再次读了读 nft 的手册页,意外地发现,它竟然有个东西十分契合我的需求:它的 set 支持超时!于是开虚拟机对着文档调了半天规则,最终得到如下规则定义: destroy table inet blocker table inet blocker { set spam_ips { type ipv4_addr timeout 2d flags timeout, dynamic } set spam_ips6 { type ipv6_addr timeout 2d flags timeout, dynamic } chain input { type filter hook input priority 0; policy accept; ct state established,related accept ip saddr @spam_ips tcp dport { 80, 443 } update @spam_ips { ip saddr timeout 2d } drop ip6 saddr @spam_ips6 tcp dport { 80, 443 } update @spam_ips6 { ip6 saddr timeout 2d } drop } } nftables 是自己创建 table 的,不用和别人「共用一张桌子然后打架」啦。然后定义了两个动态的、支持超时的、默认超时时间是两天的 set。nftables 的 table 可以同时支持 IPv4 和 IPv6,但是规则和 set 不行,所以得写两份。在 chain 定义中设置 hook,就跟 iptables 的默认 chain 一样可以拿到包啦。然后,已建立的连接不用检查了,因为恶意 IP 还没学会连接复用。接下来,如果源 IP 位于 set 内并且是访问 HTTP(S) 的话,就更新 set 的超时时间,然后丢弃包。限制端口是为了避免万一哪天把自己给屏蔽掉了。nftables 的规则后边可以写多个操作,挺直观、易于理解的。 然后让自己的恶意 IP 识别脚本用 nft add element inet blocker spam_ips "{ $IP }" 这样的命令向 set 里添加要屏蔽的 IP 就可以啦。两天不再有请求过来的 IP 会被自动解除屏蔽,很适合国内的三大运营商的动态 IP 呢。 跑了几天,被屏蔽的 IP 数量稳定在 26k—28k 之间。有昼夜周期,凌晨零点多和早上六七点是爆发期,晚间是静默期。性能非常好,softirq 最高占用不到 10%。 nftables 也很好用。虽然 nft 的手册页有点难懂,多看几遍、了解其写作结构之后就好很多了。不过要是支持 IP 地址到 counter 的动态 map 就好了——我想统计各 IP 的流量。nftables 还自带 Python 绑定,虽说这 API 走 JSON 感觉怪怪的,libnftables-json(5) 这文档没有超链接也很难使用,但至少弄明白之后能用。我用来写了个简单的统计脚本: #!/usr/bin/python3 import os from math import log10 from itertools import groupby import nftables def show_set(nft, name): ret, r, error = nft.json_cmd({'nftables': [{'list': {'set': {'family': 'inet', 'table': 'blocker', 'name': name}}}]}) if ret != 0: raise Exception(ret, error) try: elements = r['nftables'][1]['set']['elem'] except KeyError: # empty set return ips = [(x['elem']['val'], x['elem']['expires']) for x in elements] ips.sort(key=lambda x: x[1]) histo = [] total = len(ips) for k, g in groupby(ips, key=lambda x: x[1] // 3600): count = sum(1 for _ in g) histo.append((k, count)) max_count = max(x[1] for x in histo) w_count = int(log10(max_count)) + 1 w = os.get_terminal_size().columns - 5 - w_count count_per_char = max_count / w # count_per_char = total / w print(f'>> Histogram for {name} (total {total}) <<') for hour, count in histo: print(f'{hour:2}: {f'{{:{w_count}}}'.format(count)} {'*' * int(round(count / count_per_char))}') print() if __name__ == '__main__': nft = nftables.Nftables() show_set(nft, 'spam_ips6') show_set(nft, 'spam_ips') 最后,我本来想谴责用无辜开源设施来刷下行流量的行为的,但俗话说「人为财死」,算了。还是谴责一下运营商不顾社会责任、为了私利将压力转嫁给无辜群众好了。自私又短视的人类啊,总有一天会将互联网上的所有好东西都逼死,最后谁也得不到好处。

2024/8/27
articleCard.readMore

YubiKey 初体验

本文来自依云's Blog,转载请注明。 YubiKey 支持多种协议,或者说使用方式、模式,ykman 里称作「application」(应用程序)。很多程序支持多种 application。本文按 application 分节,记录一些自己的研究结果,并不全面。要全面的话,还请参考 ArchWiki 的 YubiKey 页面或者 YubiKey 官方文档。 在 Arch Linux 上,YubiKey 插上就可以用了,不需要特别的驱动方面的设置。有可能某些程序需要装个 libfido2 包来使用它。 Yubico OTP 插上之后,它会有一个键盘设备。摸一下,它就发送一长串字符然后回车。这串字符每次都不一样,并且需要与 Yubico 的服务器通信来验证是否有效。这串字符使用 AES 对称加密,也就意味着 Yubico 服务器也有私钥(你也可以自建服务器自己用)。 所以这是个没什么用的功能。并且在拔下设备时很容易误触,插到 Android 设备上之后输入法的屏幕键盘还可能不出来。所以我把它给禁用了: ykman config mode FIDO+CCID FIDO2 / U2F 这个 application 在 Android 上第一次使用的时候会提示设置 PIN。我已经设置好了,也不知道在电脑上会如何。需要注意的是,这个 PIN 可以使用字母,并不需要是纯数字。最多可以连续输错八次,但没有 PIN 被锁之后用来解锁的 PUK。 WebAuthn / Passkey 插上就可以在火狐和 Google Chrome 里使用了。可以在 https://webauthn.io/ 测试。作为可以直接登录的 passkey 使用的话,会要求输入 PIN 和触摸。如果仅仅是作为二步验证使用(比如 GitHub),则只需要触摸即可。 Android 上也是差不多的。不过 Android 支持把 passkey 存储在设备里(还会通过 Google 账号同步),使用 YubiKey 时需要从弹窗中选取「使用其它设备」。如果网站已经在设备里存储了 passkey,那么没有使用 YubiKey 的机会。 SSH OpenSSH 客户端需要安装 libfido2 包才能支持这些 -sk 结尾的 key。服务端不需要这个库。 有多个选项,具体参见 SSH Resident Key Guide。我总结了两种比较好的使用方式: ssh-keygen -t ed25519-sk -O resident -O verify-required ssh-keygen -t ed25519-sk -O no-touch-required 可以选择的 key 类型只有ecdsa-sk和ed25519-sk,并不支持 RSA。resident选项是把 key 存储到 YubiKey 上,之后可以通过ssh-keygen -K下载回来。如果不加这个选项的话,那么仅凭 YubiKey 是无法使用的,得同时有生成的文件。verify-required是验证 PIN。默认是需要触摸的,可以用no-touch-required选项关闭,但是需要服务端在 authorized_keys 里设置这个选项。 从安全角度考虑,如果 YubiKey 丢失,那么仅凭该设备不应当能获得任何权限——所以在使用 resident 密钥时必须验证 PIN(我总不能赌偷或者捡到的人猜不中我的用户名或者访问的服务器吧)。这与自动化执行 SSH 命令相冲突。另一种使用方式,不需要 PIN、不需要触摸,倒是很方便自动化,也可以防止私钥被运行的程序偷走或者自己失误泄露,但是需要服务端设置no-touch-required选项,而 GitHub 和 GitLab 并不支持。倒是可以不同场合使用不同的 key,但是管理起来太复杂了。 resident 密钥倒是可以使用 ssh-add 加载到 ssh-agent 里,之后应该就不需要交互即可使用了。但我现在启动系统要输入硬盘密码,登录到桌面并日常使用的话,还要输入用户密码和火狐主密码,已经够多了,不想再加一个 PIN。所以我还是不用了吧。 我倒是想给 termux 里的 ssh 用 YubiKey,毕竟手机上一堆乱七八糟的闭源程序,外加系统已经失去更新,感觉不怎么安全。但是搜了一圈看起来并不支持。 PAM 安装 pam_u2f 包,然后使用 pamu2fcfg 生成个文件。最后去改 PAM 配置就好啦,比如在 /etc/pam.d/sudo 开头加上 auth sufficient pam_u2f.so cue userpresence=1 这样会用触摸 YubiKey 来认证用户。如果把 YubiKey 拔了,pam_u2f 会被跳过。但是 YubiKey 正常的情况下,没有办法跳过 pam_u2f,所以通过 ssh 登录的时候会很难受……好吧,用 pam_exec 还是有办法跳过的,但是它似乎读不到环境变量,只能放个文件来控制,所以还是很麻烦。最好的办法是我在 pam_u2f 运行的时候按一下 Ctrl-C,它就放弃掉就好了,但这个 issue 已经等了快要六年了。 LUKS cryptsetup 并不直接支持 FIDO2。要使用 systemd-cryptenroll 来添加 keyslot: sudo systemd-cryptenroll --fido2-device=auto /dev/disk/by-partlabel/XXX 可以用sudo cryptsetup luksDump /dev/disk/by-partlabel/XXX命令看到 systemd 不光添加了一个 keyslot,还同时添加了一个 token 用于存储一些配置信息。 解密: sudo systemd-cryptsetup attach XXX /dev/disk/by-partlabel/XXX '' fido2-device=auto 或者用 cryptsetup open 也行。但因为添加的 slot 是需要 PIN 的,cryptsetup open 不加 token 相关的选项时会跳过该 slot,直接问你密码。 sudo cryptsetup open --token-only /dev/disk/by-partlabel/XXX XXX 配置好之后,解密 LUKS 设备就不需要输入又长又复杂的密码啦。不过最好还是时不时验证一下自己还记得密码,要是需要用的时候才发现密码因为长期不用而遗忘了就不妙了。我的系统硬盘本来解密的次数就少,就不用它了,只给备份硬盘用了。 OpenPGP ykman 要管理 OpenPGP 智能卡应用,需要启用 pcscd 服务,但是 GnuPG 可以不用它。 sudo systemctl enable --now pcscd.socket 要让 ykman 和 GnuPG 能同时访问 YubiKey,可能还需要以下设置: pcsc-driver /usr/lib/libpcsclite.so card-timeout 5 disable-ccid pcsc-shared YubiKey 所有不同 application 的 PIN 是分开的。OpenPGP application 有 PIN 和管理 PIN,默认各能试三次。使用 key 的时候会用到 PIN,导入 key 的时候会用到管理 PIN。初次使用的时候记得用ykman openpgp access命令把默认的 123456 和 12345678 都给改了(不知道为什么我没找到在gpg --card-edit里更改管理 PIN 的方法)。导入的教程可以参考官方文档的 Importing keys。我的型号是 YubiKey 5C Nano,是支持 ed25519 / cv25519 算法的。 把 key 导入到 YubiKey 之后,可以再用ykman openpgp keys set-touch设置一下哪些操作需要触摸。默认是都不需要的。然后正常使用就可以了。 要注意的是,YubiKey 只存储了私钥,所以本地要有公钥才可以正常使用。所以要换个系统使用的话,一种办法是把公钥上传到 OpenPGP 服务器上然后导入,另一种办法是自己导出成文件再导入。 SSH 也可以用 OpenPGP 密钥,所以也能用 YubiKey 上的 OpenPGP 密钥。甚至还能把现有的 ed25519 SSH key 导入进去用(不过我没有尝试)。 PIV 这个 PIV 涉及 PKCS#11,有点复杂。暂时不想研究。

2024/8/23
articleCard.readMore

fcitx5 码表同步方案

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

2024/7/28
articleCard.readMore

我正在使用的火狐扩展(2024年版)

本文来自依云's Blog,转载请注明。 距离上次分享好久了,于是又来啦~ 桌面版 每一项第一行是扩展标题和链接,第二行是扩展自己的描述信息,第三行(如有)是我为写本文添加的介绍和评论。 dd { margin-bottom: 0.5em; } 篡改猴 使用用户脚本自由地改变网络 复制链接/标签名称和地址 将链接名称和地址复制到剪贴板 复制链接地址 使用快捷键 "a" 来复制链接地址 对着链接点右键,然后按 a 键就可以复制到链接啦。 书签搜索 使用已加为书签的搜索引擎搜索选定文本 我在访问哪个 Cloudflare® 数据中心? 显示正在访问的 Cloudflare® 名称信息 云盘万能钥匙 您的云盘智能助手 大概没什么用了吧…… About Sync Show information about Firefox Sync. 同步出现问题时用过。它也可以直接发送请求、修改服务端的信息,比如删掉已卸载扩展的同步数据啥的。 Auto Tab Discard 如果您打开了很多标签页,这个扩展能提升浏览器速度并减少内存占用。 就是标签页休眠啦。 Behind The Overlay Revival Click to close any overlay popup on any website. 一键关弹窗,不用找关闭按钮在哪里。 Bypass Paywalls Bypass News Sites' Paywalls cliget Download login-protected files from the command line. 为下载的文件生成 wget / curl 的命令行。我现在很少用了,主要用途是在服务器上下载不能直接下载的文件。 Control Panel for YouTube Gives you more control over YouTube by adding missing options and UI improvements 这个扩展功能不少,我主要用的地方有:隐藏短视频(浪费时间)、自动生成的音乐合集(我从来不听这个)、即将开播的视频(又不能看,显示着干嘛)、已观看完毕的视频。隐藏视频结尾总是挡到我看内容的卡片、结束时的推荐视频。将短视频播放器重定向到有进度条的正常播放器。 Cookie Quick Manager An addon to manage (view, search, create, edit, delete, backup, restore) cookies. Dark Reader 适用于所有网站的暗色主题。关爱眼睛,就使用 Dark Reader 进行日常浏览。 Decentraleyes 保护您免受集中式的内容交付网络(CDN)的跟踪。 Discard Tab Adds Discard action to tab right-click 手动休眠标签页,避免浪费系统资源。 Flagfox 显示描述当前服务器位置的国旗。 Foxy Gestures 适用于 Firefox 的鼠标手势 FoxyImage Collection of Image Related Actions Google™ Translator A handy multi-language translator built on top of Google translate. Header Editor 管理浏览器请求,包括修改请求头和响应头、重定向请求、取消请求 用来做一些 hack 操作的,比如添加 referrer、跨域头;在新标签页中查看 imgur 的图片(不要给我网页);让 Grafana 不走代理、直连数据源以加快加载速度。这扩展在火狐上还能修改响应体。 I don't care about cookies Get rid of cookie warnings from almost all websites! Image Max URL Finds larger or original versions of images Link Status Redux Shows an indicator on a popup panel along with the link address when the mouse cursor is over a link to a page you bookmarked or visited before. 显示链接的上次访问时间用的。 matrix.to opener 在你的 Matrix 客户端中直接打开 matrix.to 链接 MergEase • GitHub Code Review Diff tool for GitHub pull requests 更准确地 diff GitHub 提交和 pull request,有点像 difft,是把 diff 发给服务端来生成的。 Mind the Time Keep track of how much time you spend on the web, and where you spend it. A ticker shows the time spent at the current site or total time spent on the web today. A summary page shows data for today and recent history. Octotree - GitHub code tree GitHub on steroids 给 GitHub 的侧边栏文件树。 Popup window 將 Tab 彈出至獨立視窗,去除頁籤列、網址列和書籤列等介面 这扩展在 Wayfire 上不太好用,弹窗和原本窗口会跟在一起,而且关闭的时候容易关到弹窗后边的窗口。 Push to Kindle Send web articles to your Kindle 哦,这个应该没用了…… Redirect Link Redirect a link to somewhere else. 用于打开网页对应的互联网档案馆或者 archive.today 存档用的。 Redirector Automatically redirect content based on user-defined rules. 和上边那个名字相似、但功能完全不同。自动重定向用的,比如看图要看原图、绕开 link.zhihu.com、统一中文维基百科中间的语种路径、把移动版 URL 重定向到桌面版、去掉 b23.tv 的小尾巴等等。 Reload PAC button A button to reload the PAC definitions Rotate and Zoom Image Allows to rotate and zoom images directly on any website from context menu. RSS Reader Extension (by Inoreader) Build your own newsfeed 装了这个才能用快捷键让 InoReader 在后台打开文章。 RSSPreview Preview RSS feeds in browser ScrollAnywhere 使用鼠标中键在页面上的任何位置拖动滚动条。还支持“抓取和拖动”样式和动画。 横着滚、竖着滚、滚来滚去~ SingleFile 将一个完整的页面保存到单个 HTML 文件中 Snap Links Select multiple links, checkboxes and other elements and act on them such as open them in new tabs or check/un-check them. SponsorBlock for YouTube - 跳过赞助商广告 跳过 YouTube 视频中的赞助广告、订阅提醒等片段。标记视频中的赞助广告来节约大家的时间。 Stylus Stylus 是一个调整网页外观的用户样式管理器。它可以让您轻松为许多热门网站安装主题和皮肤。 给网页加自定义 CSS 用的,我的用途有:叫网页不要使用奇奇怪怪的 Windows / MacOS 系字体;把暗色网页弄亮堂一点,避免在白天看不清;在各种文档网页里标记访问过的链接,免得老是点过去才发现内容已经读过了,或者不容易找到自己频繁访问的链接;去掉讨厌的圆角。 Textarea Cache Allows to save automatically the content in a text input field. 不小心关掉了正在编写、尚未提交的内容,可以用它来恢复。 Tile Tabs WE Take tabs from parent windows and arrange them in layouts of tiled sub-windows. 这扩展在 Wayland 上不能移动窗口,不过还是可以把窗口调整为合适平铺的大小,并可选加上滚动同步啥的。 Tree Style Tab - 树状标签页管理 以树状结构显示标签页。 uBlacklist 在谷歌的搜索结果中屏蔽特定的网站显示。 内容农场走开! uBlock Origin 一款高效的网络请求过滤工具,占用极低的内存和 CPU。 Unpaywall Legally get full text of scholarly articles as you browse. User-Agent Switcher and Manager Spoof websites trying to gather information about your web navigation to deliver distinct content you may not want 有时候还是不得不假装自己在用 Google Chrome 或者 Windows。 v2ex plus 优雅便捷的 V2EX 扩展 Vimium The Hacker's Browser. Vimium provides keyboard shortcuts for navigation and control in the spirit of Vim. wxIF View the EXIF/IPTC/XMP data for images. YouTube Anti Translate Updated A small extension to disable YT video titles autotranslation. 机器翻译太难懂啦。Google 从来都意识不到人是可以会多种语言的。 移动版 由于获取方式的差异,这个列表没有扩展描述。不过大部分都和桌面版是重复的。 篡改猴 我在访问哪个 Cloudflare® 数据中心? ClearURLs Control Panel for YouTube Cookie Quick Manager Dark Reader Decentraleyes Google Search Fixer Header Editor Push to Kindle Stylus Text Reflow WE uBlacklist uBlock Origin Unpaywall Video Background Play Fix 在后台继续播放视频和音频,可以用于在后台播放 YouTube Music。 Web Archives 打开当前页面的存档页面。移动版没有右键菜单所以用不了 Redirect Link。 代码 桌面版的列表是在 about:addons 页面,打开 devtools 执行以下代码取得的: const r = $$('addon-card').map( (el) => { return { title: el.querySelector('h3').textContent, desc: el.querySelector('.addon-description').textContent, id: el.getAttribute('addon-id'), } } ) let parts = [] for(let ext of r) { parts.push(`<dt><a href="https://addons.mozilla.org/firefox/addon/${encodeURIComponent(ext.id)}/">${ext.title}</a></dt>\n<dd>${ext.desc}</dd>`) } console.log(parts.join('\n')) 而移动版是在 about:debugging 页面,连接上移动版火狐之后,执行以下代码获取的: const r = $$('[data-qa-target-type="extension"]').map( (el) => { return { title: el.querySelector('[title]').title, id: el.querySelector('dd').textContent, } } ) let parts = [] for(let ext of r) { parts.push(`<dt><a href="https://addons.mozilla.org/android/addon/${encodeURIComponent(ext.id)}/">${ext.title}</a></dt>`) } console.log(parts.join('\n'))

2024/7/9
articleCard.readMore

使用 PipeWire 实现自动应用均衡器

本文来自依云's Blog,转载请注明。 之前我写过一篇文章,讲述我使用 EasyEffects 的均衡器来调整 Bose 音箱的音效。最近读者 RNE 留言说可以直接通过 PipeWire 实现,于是前几天我实现了一下。 先说一下换了之后的体验。相比于 EasyEffects,使用 PipeWire 实现此功能获得了以下好处: 少用一个软件(虽然并没有多大)。 不依赖图形界面。EasyEffects 没有图形界面是启动不了的。 占用内存少。EasyEffects 有时候会占用很多内存,不知道是什么问题。 自己实现的切换逻辑,更符合自己的需求。EasyEffects 只能针对指定设备加载指定的配置,不能指定未知设备加载什么配置。因此,当我的内置扬声器名称在「alsa_output.pci-0000_00_1f.3.analog-stereo」、「alsa_output.pci-0000_00_1f.3.7.analog-stereo」或者「alsa_output.pci-0000_00_1f.3.13.analog-stereo」等之间变化时,我需要一个个名称地指定要加载的配置。 只要打开 pavucontrol 就能确认均衡器是否被应用了。EasyEffects 需要按两下Shift-Tab和空格(或者找找鼠标)来切换界面。 缺点嘛,就是我偶尔使用的「自动增益」功能没啦。不过自动增益的效果并不太好,我都是手动按需开关的。没了就没了吧。 配置方法 首先要定义均衡器。创建「~/.config/pipewire/pipewire.conf.d/bose-eq.conf」文件,按《Linux好声音(干净的均衡器)》一文的方式把均衡器定义写进去就好了。我的文件见 GitHub。 然后需要在合适的时候使用这个均衡器。实际上上述配置加载之后,PipeWire 里就会多出来一对名叫「Bose Equalizer Sink」的设备,一个 source 一个 sink。把 source 接到音箱,播放声音的程序接到 sink,就用上了。别问我为什么 source 的名字也是「Sink」,我不会分开定义它们的名字…… 自动化应用使用的是 WirePlumber 脚本。它应该放在「~/.local/share/wireplumber/scripts」里,但是我为了方便放到 dotconfig 仓库里管理,在这里放了个到「~/.config/wireplumber/scripts」的软链接。脚本干的事情很简单:在选择输出设备的时候,看看当前默认设备是不是 Bose 音箱;如果是,就选择之前定义的「Bose Equalizer Sink」作为输出目标。不过因为文档匮乏,为了干成这件事花了我不少精力,翻看了不少 WirePlumber 自带脚本和源码。最终的脚本也在 GitHub 上。 结语 PipeWire 挺强大的,就是文档太「瘦弱」啦。能用脚本配置的软件都很棒~ 再次感谢读者 RNE 的留言~

2024/6/22
articleCard.readMore

如果你发现你的 OOM Killer 在乱杀进程

本文来自依云's Blog,转载请注明。 请对 systemd 做如下设置,避免它将你的用户进程调整为更容易被杀: 建立 /etc/systemd/user.conf.d/oom.conf 文件,并写入: [Manager] DefaultOOMScoreAdjust=0 建立 /etc/systemd/system/user@.service.d/resources.conf 文件(及其中间目录),并写入: [Service] OOMScoreAdjust=0 systemd 的默认设置也会干扰火狐浏览器自己的设定,造成与预期相反的行为。

2024/1/23
articleCard.readMore