作者归档:admin

如何在机械硬盘上打包大量小文件

虽然 SSD 的 4k 性能远高于机械硬盘,但是可能也能有所帮助

设想这样一个场景,有一个文件夹下面存了几百万个小文件,然后想把这个文件夹复制到另一台有 SSD 的机器上进行处理,但是很不幸的是,这堆小文件存在机械硬盘上。

使用 tar 打包是一个办法,但是 tar 在 Linux 上默认遍历磁盘内容的时候并不会按照 inode 的顺序,以此引发的寻道时间就足够喝一壶的了,因此需要一个方法让 tar 按照 inode 的顺序读取这些文件。很不幸的是,tar 并没有提供这个选项,我们只能迂回一下达到目的:

$ cd /path/to/small/files/folder
$ ls -U -i | sort -k1,1 -n | cut -d' ' -f2- > ~/filelist # folder content sorted with inode
$ tar -I "zstd -19 -T0" -cvf /path/to/another/disk/archive.tar.zst -T ~/filelist

tar -T 让 tar 从 filelist 中读取文件列表并打包,这时候 tar 就可以按照我们给的顺序(inode)来进行打包了,-I 这里使用 zstd 压缩。

这个情况并没有处理文件夹下有多层子目录,里面小文件更多的情况。

一个奇怪的 interactive shell 和 systemd service 行为不一致的现象

起因是我想 self-host 一个本身设计是用在 Github Pages 上的 jekyll 博客主题,大概流程是在本地用 jekyll serve 起来以后,用 nginx 挂反代上 https。到这一步其实都还好,但是吊诡的是,我在 shell 里面用 bundle exec jekyll liveserve 跑起来以后,把这个命令做成一个 systemd service 就会一直报错。行为是这样的:

Dec 11 13:10:22 WordPress jekyll[25228]:       Generating...
Dec 11 13:10:22 WordPress jekyll[25228]:        Jekyll Feed: Generating feed for posts
Dec 11 13:10:22 WordPress jekyll[25228]:   Liquid Exception: no implicit conversion of nil into String in /_layouts/default.html
Dec 11 13:10:22 WordPress jekyll[25228]: /var/lib/gems/2.5.0/gems/jekyll-github-metadata-2.13.0/lib/jekyll-github-metadata/client.rb:133:in `join': no implicit conversion of nil into String (TypeError)
Dec 11 13:10:22 WordPress jekyll[25228]:         from /var/lib/gems/2.5.0/gems/jekyll-github-metadata-2.13.0/lib/jekyll-github-metadata/client.rb:133:in `pluck_auth_method'
Dec 11 13:10:22 WordPress jekyll[25228]:         from /var/lib/gems/2.5.0/gems/jekyll-github-metadata-2.13.0/lib/jekyll-github-metadata/client.rb:46:in `build_octokit_client'
Dec 11 13:10:22 WordPress jekyll[25228]:         from /var/lib/gems/2.5.0/gems/jekyll-github-metadata-2.13.0/lib/jekyll-github-metadata/client.rb:26:in `initialize'
Dec 11 13:10:22 WordPress jekyll[25228]:         from /var/lib/gems/2.5.0/gems/jekyll-github-metadata-

...
Dec 11 13:10:18 WordPress jekyll[25224]:         from /var/lib/gems/2.5.0/gems/jekyll-3.9.0/exe/jekyll:15:in `<top (required)>'
Dec 11 13:10:18 WordPress jekyll[25224]:         from /usr/local/bin/jekyll:23:in `load'
Dec 11 13:10:18 WordPress jekyll[25224]:         from /usr/local/bin/jekyll:23:in `<main>'

之后由于 Liquid Exception,ruby 解释器就会退出,接着触发 systemd 的 restart。但奇怪的是,我在 shell 里面运行就从来不会遇到这个错误。经简单查询以后,这个 no implicit conversion of nil into String 的错误在 jekyll 里面非常常见,很多组件都有发生这个问题,尽管查看了很多 issue 但都帮助不大。

后来仔细观察命令行运行的输出以后,发现有 GitHub Metadata: No GitHub API authentication could be found. Some fields may be missing or have incorrect data. 这样一个报错。这个报错在 systemd 的 log 里面没有,因此我怀疑这两个是否为同一个错误。在这个错误的时候,我发现了这个 issue,评论中有人提到了这样的解决方案:在 _config.yml 中加一行 github: [metadata],我试了一下,问题就得到了解决。

这就引出了另一个问题,为什么 jekyll 在 shell 下和 systemd 下的行为不一致。经查询之后,发现原因在于 systemd 和普通的 shell 在执行程序的时候使用的环境变量不一致,而在错误发生的 client.rb#L133 处需要引用 $HOME 这个环境变量,这个在 systemd 下是不存在的。因此如果想要解决这个问题,除了按照前文中的方法进行修正以外,还可以在 systemd 的 unit 里面使用 Environment= 参数手动指定要使用的环境变量。

从 portable 的角度来说,这两个解决方案应该使用前者,因为后者和代码的实现有很强的耦合性,而且仅仅加上这个环境变量也不能完全在 systemd 下模拟 shell 的运行环境。

最后放一下 nginx 的 vhost 的配置文件和 systemd service 的写法:

vhost.conf

server {
  listen 80;
  listen [::]:80;
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  ssl_certificate </path/to/your/.crt>;
  ssl_certificate_key <path/to/your/.key>;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
  ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;  ssl_prefer_server_ciphers on;
  ssl_session_timeout 10m;
  ssl_session_cache builtin:1000 shared:SSL:10m;
  ssl_buffer_size 1400;
  add_header Strict-Transport-Security max-age=15768000;
  ssl_stapling on;
  ssl_stapling_verify on;
  server_name <your_domain>;
  access_log <your_log> combined;
  index index.html index.htm index.php;
  root <your_root_dir>;
  if ($ssl_protocol = "") { return 301 https://$host$request_uri; }
  
  location ~ /(\.user\.ini|\.ht|\.git|\.svn|\.project|LICENSE|README\.md) {
    deny all;
  }
  location /.well-known {
    allow all;
  }
  location / {
    proxy_pass  http://localhost:4000;
    proxy_set_header        Host            $host;
    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

systemd.service

[Unit]
Description=Daemon to start Jekyll service

[Service]
Type=simple
WorkingDirectory=</path/to/your/site>
ExecStart=/usr/bin/bundle exec jekyll liveserve --livereload-max-delay 1 --trace 
PIDFile=/var/run/jekyll.pid
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

注意要使用 liveserve 运行而非 serve,不然所有的链接都会是 localhost。

Firefox 89 UI 调整

Firefox 89 换了新的 Proton UI,总体设计上讲是好看的,也更符合现代浏览器的审美,但是默认隐藏了 compact 界面风格,加上过大的纵向 padding 对现代的带鱼屏显示器极为不友好,需要做一些调整。

要改这玩意,我们需要修改 Firefox 的 userChrome.css,并且打开被隐藏的 compact 模式。

Compact Toolbar Density

about:config -> browser.compactmode.show -> true

userChrome.css

首先,创建 userChrome.css: about:support -> Profile Folder -> Open Folder 打开当前的 profile 文件夹,在下面创建一个 “chrome” 文件夹,再放入一个空白的 userChrome.css 文件。

之后调整 toolkit.legacyUserProfileCustomizations.stylesheets 为 true,让 Firefox 在启动时加载 userChrome.css

界面调整

我主要在 Compact 下做了如下界面风格调整:

  • Tab 使用 6px 圆角
  • 将 Tab 和下方页面连在一起,而非默认的浮动风格
  • 增加 Tab 之间的纵向分隔符
  • 降低菜单项之间的 padding 到 4px
  • 书签栏在 Compact 下使用和 Normal 相同的 2px 纵向 padding
/*** Tighten up drop-down/context/popup menu spacing ***/

menupopup > menuitem, menupopup > menu {
  padding-block: 4px !important;
}
:root {
  --arrowpanel-menuitem-padding: 4px 8px !important;
}

/*** Proton Tabs Tweaks ***/

/* Adjust tab corner shape, optionally remove space below tabs */

#tabbrowser-tabs {
    --user-tab-rounding: 6px;
}
.tab-background {
  border-radius: var(--user-tab-rounding) var(--user-tab-rounding) 0px 0px !important;
  margin-block: 1px 0 !important;
}


/* Inactive tabs: Separator line style */

.tab-background:not([selected=true]):not([multiselected=true]):not([beforeselected-visible="true"]) {
  border-right: 1px solid rgba(0, 0, 0, .20) !important;
}
/* For dark backgrounds */
[brighttext="true"] .tab-background:not([selected=true]):not([multiselected=true]):not([beforeselected-visible="true"]) {
  border-right: 1px solid var(--lwt-selected-tab-background-color, rgba(255, 255, 255, .20)) !important;
}
.tab-background:not([selected=true]):not([multiselected=true]) {
  border-radius: 0 !important;
}
/* Remove padding between tabs */
.tabbrowser-tab {
  padding-left: 0 !important;
  padding-right: 0 !important;
}

/* Tweak Options as of 5/30/2021; Generated Sat Jun 05 2021 16:07:09 GMT-0400 (Eastern Daylight Time) */

/* Use Normal top and bottom padding for Compact */
#PlacesToolbarItems .bookmark-item {
  padding-top: 2px !important;
  padding-bottom: 2px !important;
}

Reference

https://www.userchrome.org/firefox-89-styling-proton-ui.html
https://www.userchrome.org/how-create-userchrome-css.html
https://support.mozilla.org/en-US/questions/1243994#answer-1182082
https://support.mozilla.org/en-US/questions/1186601

WSL SSH 登录

仅做记录。

Step 1:在 WSL 中重新安装 OpenSSH

sudo dpkg-reconfigure openssh-server

Step 2:修改 /etc/ssh/sshd_config,将 Port 改为一个 1024 以上的值,如果没有配置密钥的话,允许密码登录

Step 3:重启 SSH 服务

sudo service ssh --full-restart

这样就可以从另一台 PC 上登录进本机的 WSL 了,VSCode Remote 之类的也都能正常运行。

使用 Docker 快速架设一个 IPSec VPN Server

仅作记录使用,环境为 Ubuntu 18.04 LTS

Docker Image 来自 hwdsl2/docker-ipsec-vpn-server

# Install docker
apt update
apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt update
apt install docker-ce docker-ce-cli containerd.io

# Pull the docker image
docker pull hwdsl2/ipsec-vpn-server

之后创建一个 env 文件规定 IPSec 使用的 PSK、用户名和密码,假设存储在 /home/user/.config/vpn.env

# Define your own values for these variables
# - DO NOT put "" or '' around values, or add space around =
# - DO NOT use these special characters within values: \ " '
VPN_IPSEC_PSK=your_ipsec_pre_shared_key
VPN_USER=your_vpn_username
VPN_PASSWORD=your_vpn_password

# (*Optional*) Define additional VPN users
# - Uncomment and replace with your own values
# - DO NOT put "" or '' around values, or add space around =
# - Usernames and passwords must be separated by spaces
# VPN_ADDL_USERS=additional_username_1 additional_username_2
# VPN_ADDL_PASSWORDS=additional_password_1 additional_password_2

# (*Optional*) Use alternative DNS servers
# - Uncomment and replace with your own values
# - By default, clients are set to use Google Public DNS
# - Example below shows Cloudflare's DNS service
# VPN_DNS_SRV1=1.1.1.1
# VPN_DNS_SRV2=1.0.0.1

# (*Optional*) Advanced users can set up IKEv2. See:
# https://git.io/ikev2docker

使用 systemd 开机自启动,假设文件在 /etc/systemd/system/ipsec.service:

[Unit]
Description=IPSec Docker
After=docker.service
Requires=docker.service

[Service]
User=root
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-/usr/bin/docker stop ipsec-vpn-server
ExecStartPre=-/usr/bin/docker rm ipsec-vpn-server
ExecStart=/usr/bin/docker run --name ipsec-vpn-server --env-file /home/user/.config/vpn.env --restart=always -p 500:500/udp -p 4500:4500/udp -d --privileged hwdsl2/ipsec-vpn-server
ExecStop=/usr/bin/docker stop ipsec-vpn-server
ExecStopPost=/usr/bin/docker rm ipsec-vpn-server

[Install]
WantedBy=multi-user.target

之后使用 docker logs ipsec-vpn-server 就能看到本次使用的登录凭据。

Xfinity Gateway 桥接模式使用

给手上的 RT-ACRH17 刷了梅林,然后实在是看那个 Double-NAT 不爽,打算把 Xfinity 的那个 modem 改成桥接模式。折腾一番之后终于搞定了,这里记录下几个问题。

我这个 modem 是 ARRIS TG1682G,型号是 XB3,调成桥接模式只要按照 Xfinity 的文档操作即可,调整完以后理论上只有 LAN1 可用,用一根网线从 modem 的 LAN1 直接接到 PC 上,看下通不通,如果通的话,说明 modem 本身没有问题,然后记录下 PC 有线网卡的 MAC 地址。

之后进入 RT-ACRH17 的设置界面,按照下图调整:

这里 DNS 我用的是 CloudFlare 的 public DNS,也可以换成例如 Google DNS 之类,这个无所谓。重点在于,在 ISP 特殊要求下,将 MAC 地址一栏中填入刚刚记下的 PC 有线网卡 MAC 地址,然后将 DHCP 查询频率改成普通。似乎 Xfinity 那里会限制这个 MAC 地址,如果不做克隆的话会无法完成 DHCP。也就是说,垃圾 Comcast 实际上是限制你用自己的无线路由的。

然后是 IPv6 的配置:

如果没有将 modem 改为桥接模式的话,这里类型应该选 Passthrough,在本文的场景下,应选择 Native,其他选项按照图内配置。同样的,这里的 DNS 我用的是 CloudFlare 的 DNS,可以改成其他你喜欢的地址。

稍等一段时间这里会出现一个 /64 的 prefix,说明配置成功。这里响应稍微有些慢,在 IPv4 上线以后可能还要两三分钟这里才会出现,是正常情况,等一下就好。

然后就结束了,重点就在于 MAC 地址要克隆 PC 的有线网卡地址,不然服务端会做一些奇怪的限制导致 DHCP 失败连不上网。

关于 GADWP 插件

几个月前,用于在 WordPress 上添加 Google Analytics 跟踪的插件 GADWP 经历了一次更新,似乎是被某个公司买了,然后加了一堆臃肿的功能,比原来请求了除 Analytics 以外更多的权限,然后也开始收费了。

如果想要原来的版本,有两个办法,一个是使用 fork 出来的 GAinWP,另一个是使用最后一个没有更新的版本 5.3.10,把 6.0 版本删掉然后上传 zip 包就可以了,最后别忘了去 Google 账号里面 revoke 掉 ExactMetrics 的权限。