Skip to content

fix(udev): scope SIGUSR2 hotplug signal to ceralive.service main pid (stop killing avahi/mDNS)#149

Merged
andrescera merged 1 commit into
mainfrom
fix/udev-sigusr2-scoped-kill
Jul 16, 2026
Merged

fix(udev): scope SIGUSR2 hotplug signal to ceralive.service main pid (stop killing avahi/mDNS)#149
andrescera merged 1 commit into
mainfrom
fix/udev-sigusr2-scoped-kill

Conversation

@andrescera

Copy link
Copy Markdown
Member

What

The two USB/audio hotplug udev rules now deliver SIGUSR2 to the CeraUI backend via a unit-scoped systemctl kill, instead of a broad pkill -f:

- RUN+="/usr/bin/pkill -o -SIGUSR2 -f ceralive"
+ RUN+="/usr/bin/systemctl kill --kill-whom=main --signal=SIGUSR2 ceralive.service"
  • deployment/99-ceralive-check-usb-devices.rules (Elgato/Cam Link, vendor 0fd9)
  • deployment/98-ceralive-audio.rules (the single RUN+= under LABEL="signal_ceralive", reached by all 5 GOTO branches + the usbaudio fall-through)

main.ts's SIGUSR2 handler is unchanged — only the delivery mechanism is fixed.

Why

pkill -f ceralive substring-matches the full command line of every process. Once avahi registers a hostname it renames its process title to avahi-daemon: registering [<hostname>.local]; this device's hostname is ceralive.local, so avahi-daemon's title contains ceralive and was caught by the same broad pkill. SIGUSR2's default disposition is terminate, and avahi-daemon.service has no Restart=, so mDNS (ceralive.local resolution) stayed dead until reboot — the "mDNS keeps dying" complaint.

Confirmed live on real hardware via journalctl — both the backend (PID 544) and avahi-daemon (PID 453) received SIGUSR2 at the same udev event:

avahi-daemon.service: Main process exited, code=killed, status=12/USR2
avahi-daemon.service: Failed with result 'signal'.
ceralive[544]: {"level":"error","msg":"SIGUSR2"}

systemctl kill targets the unit's cgroup — zero substring ambiguity — the same idiom already used by ceralive-addon-reconciler.service. --kill-whom=main scopes the signal to the tracked MainPID, preserving the old pkill -o single-process intent. This matters: srtla_send/bcrpt are spawned by the backend and share ceralive.service's cgroup while streaming, and neither handles SIGUSR2 — so the whole-cgroup default (all) would terminate the active stream on every hotplug. systemctl kill is safe from a udev RUN+= rule: it sends the signal via a synchronous PID-1 D-Bus call and creates no systemd job, so the --no-block/deadlock caveats that apply to start/stop/restart do not apply.

How to verify

  • cd apps/backend && bun test src/tests/udev-rules-sigusr2-scope.test.ts — new static regression guard (4 tests) over the shipped rule files: no pkill -f ceralive remains, both rules carry the scoped systemctl kill --kill-whom=main --signal=SIGUSR2 ceralive.service idiom, and every audio-rule GOTO path converges on the single scoped RUN+=.
  • cd apps/backend && bun tsc --noEmit — clean.
  • biome check — clean on changed files.
  • On device: plug/unplug a Cam Link or USB-audio device and confirm avahi-daemon stays up (systemctl is-active avahi-daemon) and ceralive.local still resolves, while the backend still re-scans devices.

Risks

  • Low. The rules only change how the existing SIGUSR2 is delivered; the handler and the hotplug re-scan behavior are unchanged.
  • --kill-whom=main requires systemd >= 252 (Debian bookworm floor); the device already relies on modern systemctl (ceralive-addon-reconciler.service).
  • Complementary hardening (Restart=on-failure on avahi-daemon.service) is handled separately in image-building-pipeline as defense-in-depth.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ed6bafb3-9d32-4f88-973b-c0507de48d83

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/udev-sigusr2-scoped-kill

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The two USB/audio hotplug udev rules signalled the backend with
`pkill -o -SIGUSR2 -f ceralive`. `pkill -f` substring-matches the full
command line of every process. Avahi renames its process title to
`avahi-daemon: registering [<hostname>.local]` after registering a hostname,
and this device's hostname is `ceralive.local` — so avahi-daemon matched the
broad pkill and took SIGUSR2 (default disposition: terminate) on every hotplug.
With no Restart= on avahi-daemon.service, mDNS stayed dead until reboot.

Deliver the signal through `systemctl kill --kill-whom=main --signal=SIGUSR2
ceralive.service` instead — the same cgroup-scoped idiom already used by
ceralive-addon-reconciler.service. --kill-whom=main preserves the old `pkill -o`
single-process intent, so the whole-cgroup default can never collaterally
SIGUSR2-terminate srtla_send/bcrpt (which share the unit cgroup while streaming
and do not handle SIGUSR2). systemctl kill is safe from a udev RUN+= rule: it
sends the signal via a synchronous PID-1 D-Bus call and creates no job, so the
deadlock caveats that apply to start/stop/restart do not apply.

main.ts's SIGUSR2 handler is unchanged; only the delivery mechanism is fixed.
Adds a static regression test over the shipped rule files.
@andrescera
andrescera force-pushed the fix/udev-sigusr2-scoped-kill branch from ff2bcdd to 0831743 Compare July 16, 2026 23:17
@andrescera
andrescera merged commit f44bcbe into main Jul 16, 2026
12 checks passed
@andrescera
andrescera deleted the fix/udev-sigusr2-scoped-kill branch July 16, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant