Installing padctl on Bazzite / Immutable Linux
This guide covers installing padctl on immutable Linux distributions where /usr is read-only (Bazzite, Fedora Atomic, Universal Blue, etc.).
Quick Install
Run the bootstrap script — it handles everything automatically:
curl -fsSL https://raw.githubusercontent.com/BANANASJIM/padctl/main/scripts/bazzite-setup.sh \
| bash -s -- --mapping vader5
Replace vader5 with the mapping for your controller, or omit --mapping to install without a mapping. Available mappings are in the mappings/ directory.
When run locally (not via curl), the script prompts for mapping selection interactively if --mapping is not provided:
bash scripts/bazzite-setup.sh
What the script does:
- Detects immutable OS (checks for ostree or read-only
/usr) - Installs dependencies via Homebrew (Zig compiler, libusb) — no system reboot needed
- Clones and builds padctl from source with
ReleaseSafeoptimization - Installs the daemon, systemd service, udev rules, and reconnect scripts
- Persists the selected mapping as a device binding in
/etc/padctl/config.toml(auto-applies on every boot) - Applies the mapping to the current session
- Verifies the installation
Safe to re-run for updates — it rebuilds and reinstalls while preserving your mapping configs in ~/.config/padctl/mappings/.
Script Options
| Flag | Description |
|---|---|
--mapping <name> | Install a mapping and auto-apply on boot |
--repo-url <url> | Use a fork or alternative repo URL |
--branch <name> | Clone/checkout a specific branch |
<path> | Use an existing local repo instead of cloning |
What the Install Does
Why /usr is a Problem
On immutable distros, /usr is a read-only filesystem overlay. The standard padctl install places systemd service files and udev rules under /usr/lib/, which works on regular Linux but fails silently on immutable systems — the files exist but systemd can't resolve them through symlinks during boot.
How --immutable Fixes It
The padctl install --immutable flag changes where system files are placed:
| File | Standard (/usr) | Immutable (--immutable) |
|---|---|---|
| Binaries | /usr/bin/ | /usr/local/bin/ |
| Service file | /usr/lib/systemd/user/ | /etc/systemd/user/ |
| Service drop-in | (not created) | /etc/systemd/user/padctl.service.d/immutable.conf |
| udev rules | /usr/lib/udev/rules.d/ | /etc/udev/rules.d/ |
| Device configs | /usr/share/padctl/devices/ | /usr/local/share/padctl/devices/ |
padctl-resume.servicewas removed (issue #131-B); udev hotplug handles post-suspend reconnect.
Files in /etc/ persist across system updates on immutable distros.
The immutable.conf Drop-in
The immutable install creates a systemd user-service drop-in override with these changes:
| Directive | Purpose |
|---|---|
DeviceAllow= | Clears any inherited device allowlist so libusb can open USB bus nodes when permissions allow it |
ProtectHome=read-only | Allows reading user mapping configs from ~/.config/padctl/mappings/ |
ReadWritePaths=/run/user/%U | Keeps the daemon socket writable when ProtectHome=read-only also covers runtime paths |
TimeoutStopSec=3 | Short stop timeout for processes stuck in uninterruptible I/O |
KillMode=mixed | SIGTERM to main process + SIGKILL to stuck worker threads |
Security note on DeviceAllow=: The immutable drop-in does not grant file
permissions by itself. Device access still comes from the installed udev rules,
desktop uaccess ACLs, or input-group membership for headless sessions. Clearing
the systemd device allowlist is needed so libusb can use USB bus nodes
(/dev/bus/usb/) for vendor-specific control transfers once normal file
permissions allow access.
Managing Mappings
Mapping configs can live in two places:
| Location | Priority | Editable without sudo |
|---|---|---|
~/.config/padctl/mappings/ | First (highest) | Yes |
/etc/padctl/mappings/ | Second | No (requires sudo) |
padctl switch <name> searches ~/.config/ first, so you can customize mappings without root:
# Edit your personal mapping
nano ~/.config/padctl/mappings/vader5.toml
# Apply changes immediately (no sudo needed)
padctl switch vader5
The /etc/padctl/mappings/ copy is used as a fallback by the hotplug reconnect script (which runs as root).
Auto-apply on boot
When you install with --mapping, the installer writes a device binding to /etc/padctl/config.toml:
version = 1
[[device]]
name = "Flydigi Vader 5 Pro"
default_mapping = "vader5"
The daemon reads this file at startup and auto-applies the mapping — no manual padctl switch needed after reboot. User-level overrides in ~/.config/padctl/config.toml take priority when available.
You can also persist a mapping change after switching at runtime:
padctl switch vader5 --persist
This copies your user mapping and config to /etc/padctl/ via sudo, so the change survives reboots without re-running the installer.
Uninstalling
sudo padctl uninstall --immutable --prefix /usr/local --mapping vader5
This removes all installed files including the /etc/systemd/user/ service files and the specified mapping. User configs in ~/.config/padctl/ are never touched.