Posts in category “Linux”

A `--user` systemd service restarts every 5 minutes? Check logins, not cron

A systemctl --user service kept starting, then stopping ~12s later, every 5 minutes — flooding a notification channel each time. But crontab -l was empty and systemctl --user list-timers had no matching timer. So who was cycling it?

Usually it's not a scheduled job. A user-level service's lifetime is tied to the per-user systemd manager (user@<uid>.service), and without linger that manager only runs while the user has at least one login session:

  • Someone logs in (even a 2-second SSH/rsync) → the manager starts → it reaches default.target → your WantedBy=default.target service gets pulled up.
  • ~10s after the last session exits → the manager tears down → your service stops with it.
  • Next login repeats the whole dance.

So the service's "restart cadence" is really the login cadence. The system journal makes it obvious:

journalctl --since "-15min" | grep -iE "Accepted publickey|New session|Removed session|Reached target exit.target"

In my case the culprit was another box running */5 * * * * rsync … host:/backup/… — a short SSH connection every 5 minutes that lit up the entire user manager and dropped it again. The moment the host had a persistent session (a lingering tmux), the symptom vanished — which is the strongest tell: it only happens when nobody is logged in.

The fix is to detach the manager from login so it stays resident:

sudo loginctl enable-linger <user>
# verify
loginctl show-user <user> --property=Linger   # Linger=yes

With linger on, the manager starts at boot and no longer stops when sessions end, so the service becomes a real background daemon. For a user-level service that must run while no one is logged in, enable --now is not enough — enable-linger is the missing prerequisite.

user 级 systemd 服务每 5 分钟自己重启一次?先查登录会话,别急着找 cron

一个 systemctl --user 服务莫名每 5 分钟启动、十几秒后又停,反复刷屏。crontab -l 空的,systemctl --user list-timers 也没有对应 timer——那到底是谁在动它?

答案往往不是定时任务,而是 user 级服务的生命周期绑在 per-user systemd manager(user@<uid>.service)上。没开 linger 时,这个 manager 只在该用户"至少有一个登录会话"时存活:

  • 有人(哪怕一条 2 秒的 SSH/rsync)登录 → manager 启动 → 到达 default.target → 你那个 WantedBy=default.target 的服务被拉起。
  • 会话退出约 10 秒后 → manager 拆除 → 服务跟着停。
  • 下次登录再来一遍。

所以服务的"重启节奏"其实是登录节奏。用系统 journal 一看就露馅:

journalctl --since "-15min" | grep -iE "Accepted publickey|New session|Removed session|Reached target exit.target"

我这次的真凶是另一台机器上的 */5 * * * * rsync … host:/backup/…——每 5 分钟一条短 SSH 连接,把整个 user manager 点起来又放下。一旦本机有了常驻会话(比如一个挂着的 tmux),现象立刻消失——这就是最强的判定线索:只在"没人登录"时发生

治本是让 manager 脱离登录、常驻:

sudo loginctl enable-linger <user>
# 确认
loginctl show-user <user> --property=Linger   # Linger=yes

开 linger 后 manager 开机即起、不随会话退出而停,服务真正变成后台常驻。要让一个 user 级服务在无人登录时也长跑,enable --now 是不够的,enable-linger 才是前提。

kitty showed huge gaps between characters — `monospace` was resolving to a CJK font

Every character in kitty had a massive gap after it, like the cell width was double what it should be. The obvious first move — swap font_family in kitty.conf — did nothing. Neither did the version a second pair of hands tried. Two failed attempts, and the config looked completely normal.

The config wasn't the problem. monospace is just a fontconfig alias, and on this machine fontconfig was handing it to a CJK font:

$ fc-match monospace
NotoSansCJK-Regular.ttc: "Noto Sans Mono CJK SC" "Regular"

kitty bases its cell width on the primary font, and a CJK font's cell is full-width — so narrow ASCII glyphs end up stranded in the middle of wide blank cells. The same kitty.conf was perfectly fine on another machine, because over there fc-match monospace returns DejaVu Sans Mono.

What was poisoning the alias was a file Ubuntu's language-selector drops in:

$ grep -A6 '<family>monospace</family>' \
    ~/.config/fontconfig/conf.d/64-language-selector-prefer.conf
		<family>monospace</family>
		<prefer>
			<family>Noto Sans Mono CJK SC</family>
			<family>Noto Sans Mono CJK JP</family>
			...

It prepends the Noto CJK mono fonts to the monospace alias, so they outrank every Latin mono. Comment out that one <alias> block and monospace goes back to DejaVu:

$ fc-match monospace
DejaVuSansMono.ttf: "DejaVu Sans Mono" "Book"

The reload trap that made this take an hour

Here's the part that hurts. After fixing fontconfig, pressing ctrl+shift+F5 to reload kitty... still showed the gaps. It looked exactly like the fix hadn't worked — the same dead end that had already wasted two attempts.

kitty caches its fontconfig resolution in-process. ctrl+shift+F5 re-reads kitty.conf, but it never re-queried fontconfig, so a change to how monospace resolves never reached the running kitty. Only quitting and reopening kitty — a fresh process — picked it up.

So the rule, painfully earned: when a kitty font change "doesn't work," before you conclude the fix is wrong, fully restart kitty and re-check. The fix may have been right all along; you were just looking at a cached font. And when the symptom is uniformly wide gaps across every character, run fc-match monospace before touching kitty.conf at all.

Migrating a vagrant-libvirt VM to a newer host: skip `vagrant package`, hand it to virsh

Moving a vagrant-libvirt VM between Linux hosts looks like a two-step (vagrant package then vagrant up on the other side), and that's exactly what I tried. Both steps died in the same place: fog-libvirt's stream upload/download of a large qcow2 from/to libvirt's storage pool reset mid-flight (Cannot recv data: Connection reset by peer, hung at 0%).

The streaming bug is in fog-libvirt's vol upload/download. The fix is to bypass vagrant-libvirt's vol-upload/vol-download entirely: flatten the overlay qcow2 against its backing file, drop the result in a libvirt storage pool by hand, then virsh define + virsh start. Treat vagrant-libvirt as the boot-time scaffolding only; the running VM is plain libvirt after that.

1. Make the box self-contained

vagrant package exists to bake the VM's disk + metadata into a .box file. With a libvirt provider the disk is usually a qcow2 with a backing file (qemu-img info ... | grep "backing file"), and vol-download only streams the overlay — you'd ship an incomplete box. Skip it and flatten manually:

…more

WSL SSL Certificate Errors on Corporate Networks

If curl throws SSL certificate problem: unable to get local issuer certificate every time in WSL, it's usually a stale CA bundle — especially common on corporate networks running SSL inspection (Zscaler, etc.).

First, refresh the bundle:

sudo apt-get update && sudo apt-get install -y ca-certificates
sudo update-ca-certificates

That fixes most cases. If not, try a full reinstall:

sudo apt-get install --reinstall ca-certificates
sudo update-ca-certificates --fresh

On corporate networks, you likely need your company's root CA certs. You probably already have them somewhere in your Windows filesystem (.crt or .cer files). Copy them all into the trusted store:

sudo cp /c/Certificates/*.crt /usr/local/share/ca-certificates/
sudo cp /c/Certificates/*.cer /usr/local/share/ca-certificates/
sudo update-ca-certificates

Skip .pfx files — those contain private keys and are a different format.

If you don't have the certs handy, export them from Windows:

# PowerShell — list all root certs, look for your company name
Get-ChildItem Cert:\LocalMachine\Root | Select-Object Subject, Issuer | Sort-Object Subject

Or use certmgr.msc (Win+R → certmgr.msc) → Trusted Root Certification Authorities → find your company's cert → right-click → Export → Base-64 encoded X.509.

Test with curl https://google.com after each step.