headroom/README.md
atagen c65c75bb9f 7: packaging — systemd user unit + Nix modules + README
Ships the daemon as a real installable, not just `cargo build`.

Artifacts

  - `contrib/systemd/headroom.service` — user-scope unit. Type=simple
    (the daemon doesn't fork), After=pipewire.service, Restart=on-
    failure with a 2 s back-off so a crash loop doesn't spam stderr,
    StandardOutput/Error=journal, LimitRTPRIO=20 / LimitNICE=-11 to
    match the rtkit-style grant PipeWire's own unit carries. The
    file is templated with `@bindir@` so the build derivation can
    substitute in an absolute store path at install time, without
    the unit having to rely on whatever `headroom` happens to be on
    PATH.

  - `nix/home-module.nix` — `services.headroom.enable`. Installs the
    package on the user's PATH, symlinks the shipped profiles into
    `$XDG_CONFIG_HOME/headroom/profiles/`, and writes the systemd
    user unit (start After=pipewire.service Requires=pipewire.service
    Wants=wireplumber.service WantedBy=pipewire.service). Knobs:
    `installDefaultProfiles` for users who maintain their own set,
    `extraProfiles` (attrset of filename → path) to drop in personal
    profiles that override shipped ones by name.

  - `nix/nixos-module.nix` — `programs.headroom.enable`. Narrow scope:
    binary on global PATH, the package's `lib/systemd/user/*.service`
    is materialised under `/etc/systemd/user/` via `systemd.packages`,
    and an assertion fires if pipewire isn't enabled (clearer than a
    runtime crash). Per-user defaults (profile install, RT priority
    tuning) live in the Home Manager module; the two compose.

Build derivation

  `postInstall` now installs the unit (with `@bindir@` substituted to
  `$out/bin`) and copies `profiles/*.toml` to
  `$out/share/headroom/profiles/`. The flake's version lookup moved
  from `crates/headroom-cli/Cargo.toml` (where `version.workspace =
  true` evaluates to a table, not a string) to the workspace
  `Cargo.toml`. Modules exposed under `nixosModules.default` and
  `homeModules.default`.

README

  Rewrote the install section: Nix flake-based install with both
  Home Manager and NixOS module examples, plus a from-scratch
  `cargo install` + `install`/`sed` recipe for non-Nix users. Added
  a usage section with the common `headroom` subcommands and bumped
  the status banner from "pre-alpha" to "alpha" (signal chain,
  routing, IPC, monitor TUI, profile reload, and packaging all work
  end-to-end now).

Verified

  - `nix flake check` passes; NixOS module type-checks under
    nixpkgs eval.
  - `nix build .#headroom` produces `bin/headroom`,
    `lib/systemd/user/headroom.service` with the absolute store-path
    ExecStart baked in, and all five shipped profiles under
    `share/headroom/profiles/`.
  - `systemd-analyze verify --user` accepts the unit.
  - 185 workspace tests still pass; clippy clean at -D warnings
    --all-targets; `nix fmt` happy.
2026-05-21 17:00:25 +10:00

130 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# headroom
AGC + compressor + true-peak limiter daemon for PipeWire, in Rust.
Headroom puts a per-application audio safety net between noisy sources
(browsers, voice chat, random video) and your speakers, while leaving
the things you *don't* want compressed (music players, games, DAWs)
untouched.
- **Hard 0.1 dBTP ceiling on the processed route**, with proper
inter-sample-peak handling, enforced inline so the contract holds
regardless of control-plane state. Streams routed `bypass` ride the
real sink directly and are not in scope of the contract — that's the
trade-off that makes the per-app exclusion useful.
- **Per-app exclusion** with profile-driven rules.
- **Layer A per-app level control** (peak + RMS detector → smoothed
`channelVolumes` writes) for taming individual streams without
touching the bus path. Zero added signal-path latency; safe to use
on bypass-routed streams.
- **Single binary** daemon + CLI, controlled over a Unix-domain socket
with a documented JSON wire protocol (see [`IPC.md`](IPC.md)).
- **First-party Rust crate** (`headroom-client`) for programmatic use;
third-party clients (Qt panels, status bars, …) target the wire
protocol directly.
- **Live profile reload** — edit a TOML file in
`$XDG_CONFIG_HOME/headroom/profiles/` and the daemon picks up
changes within ~500 ms; the audio thread doesn't glitch.
See [`PLAN.md`](PLAN.md) for the full design and roadmap.
## Status
Alpha. The signal chain (AGC, compressor, two-tier limiter, Layer A
per-app), the routing engine (explicit-link enforcement, sink hotplug,
sticky default sink), the IPC server with topic subscriptions, the
`headroom monitor` TUI, and live profile reload all work end-to-end.
Packaging exposes a systemd user unit and Nix modules. What's missing
is real-world soak time on multi-rate / Bluetooth setups and other
distros' init systems.
## Installing
### Nix (flake)
This repo is a flake; the daemon plus its systemd user unit and the
canonical profiles are exposed as a package.
```sh
nix run github:amaanq/headroom -- daemon # one-shot run
nix profile install github:amaanq/headroom # add to $PATH
```
For **Home Manager**, add the flake as an input and enable the module:
```nix
{
inputs.headroom.url = "github:amaanq/headroom";
# In your Home Manager configuration:
imports = [ inputs.headroom.homeModules.default ];
services.headroom.enable = true;
}
```
The module symlinks the shipped profiles into
`$XDG_CONFIG_HOME/headroom/profiles/`, drops the systemd user unit
into the user's services dir, and the unit starts after PipeWire and
WirePlumber come up. `services.headroom.extraProfiles` lets you add
your own.
For **NixOS** (system-wide binary install + systemd-user discovery):
```nix
{
inputs.headroom.url = "github:amaanq/headroom";
# In your NixOS configuration:
imports = [ inputs.headroom.nixosModules.default ];
programs.headroom.enable = true;
}
```
Then any user can `systemctl --user enable --now headroom`.
### Other distros (manual)
```sh
cargo install --path crates/headroom-cli # or: cargo build --release
# Profiles
mkdir -p ~/.config/headroom/profiles
cp profiles/*.toml ~/.config/headroom/profiles/
# systemd user unit (edit the ExecStart path to point at your binary)
install -Dm644 contrib/systemd/headroom.service \
~/.config/systemd/user/headroom.service
sed -i "s|@bindir@|$(dirname "$(command -v headroom)")|" \
~/.config/systemd/user/headroom.service
systemctl --user daemon-reload
systemctl --user enable --now headroom
```
## Usage
Once the daemon is running:
```sh
headroom status # JSON snapshot — sinks, streams, active profile
headroom profile list # available profiles
headroom profile use night # activate one
headroom monitor # full-screen TUI (bus gauges + per-stream)
headroom monitor --json meters # line-delimited JSON, for scripting
headroom route set firefox processed
headroom set compressor.threshold_db -28
headroom bypass on # kill switch — straight to the real sink
```
See `headroom --help` for the full surface.
## Building
```sh
nix develop # toolchain + pipewire dev libs + helpers
cargo build # iterate
cargo test --workspace
nix build # final packaged headroom binary
```
## License
GPL-3.0-or-later for the daemon and CLI. `headroom-dsp` and `headroom-ipc`
are MPL-2.0 so they can be reused by non-GPL plugin hosts and clients.