Start on Boot
Loopwire should be available before users open recording, meeting, or broadcast tools.
The desktop app has user-scoped startup controls for opening the GUI and restoring audio in the background. It writes only under ~/.config/autostart and ~/.config/systemd/user, and it can remove the same files again. It never writes system files.
The installed tarball/package entrypoint and source checkout helpers both support user-scoped startup and background restore. They are dry-run friendly and keep startup files user-scoped.
Current Safe Path: Desktop App
For today's GUI app, use XDG desktop autostart. This starts Loopwire when the graphical session starts on desktops and WMs that honor ~/.config/autostart/*.desktop.
In the desktop shell, open Settings (Ctrl+,) and use the Startup section:
- Start with desktop session manages the XDG autostart entry that launches the GUI.
- Restore audio in background manages a user systemd unit that runs the packaged
loopwire --background --mode livelauncher against the persisted state file.
If the packaged background launcher is unavailable, no backend is selected, or the saved backend is no longer detected, the enable path is blocked instead of writing a unit that cannot restore audio. Existing restore units can still be disabled from that blocked state.
Browser preview does not write startup files. It shows the paths and asks you to use the desktop shell. Source-checkout Tauri runs do not install a background-capable GUI binary; use the source checkout CLI path below until you test an installed package or set LOOPWIRE_BACKGROUND_BINARY to a compatible launcher. The restore-on-boot status card stays readable when that launcher is missing: it reports the unit path, marks restore as blocked, and disables only the unsafe enable action. If a unit already exists, the desktop can still remove it.
CLI Fallback
Preview the entry:
bash scripts/manage-autostart.sh render --mode desktop --binary "$HOME/.local/bin/loopwire"Install the entry:
bash scripts/manage-autostart.sh install --mode desktop --binary "$HOME/.local/bin/loopwire"Check status:
bash scripts/manage-autostart.sh status --mode desktopRemove it:
bash scripts/manage-autostart.sh uninstall --mode desktopThis path launches the app and the desktop shell restores the selected configuration through the same startup runtime plan it uses after local state is loaded.
Packaged Background Restore Path
Release tarballs install loopwire as a launcher. Normal invocations start the GUI. loopwire --background runs the bundled restore engine from the installed support files:
loopwire --background \
--state-file "${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.json" \
--mode preview \
--prettyRender a user-scoped systemd service for the installed launcher:
bash scripts/manage-autostart.sh render \
--mode systemd \
--binary "$HOME/.local/bin/loopwire" \
--restore-mode previewThe generated packaged service shape is:
[Unit]
Description=Loopwire audio routing restore
[Service]
Type=simple
ExecStart="/home/me/.local/bin/loopwire" --background --state-file "%h/.config/loopwire/state.json" --mode preview
Restart=on-failure
RestartSec=2Packaged background restore requires node on PATH because the release artifact bundles the same JavaScript core/audio-host restore engine used by source checkouts. The curl installer reports that dependency after installing the tarball. If it warns that Node.js is missing, install the distro nodejs package before enabling background restore. AUR and Nix package paths declare or wrap Node.js for the packaged launchers.
The desktop shell resolves the packaged launcher from the installed GUI path before writing this service. It refuses to install a background unit if it can only find the GUI binary, because --background belongs to the loopwire launcher. The status check also preflights loopwire --background --help for packaged launchers, so missing Node.js or missing bundled restore assets block enable before a broken unit is written. The check remains non-destructive when that launcher is missing or cannot run, so users can see why enable is blocked instead of getting a failed status check. When the launcher is available, the shell writes the unit and a user-scoped default.target.wants/loopwire.service link. It does not run sudo or modify /etc/systemd.
Source Background Restore Path
The source checkout can also run a user-scoped systemd service that restores the persisted state file without opening the UI. User-scoped startup avoids system daemons and keeps rollback simple.
The Tauri desktop shell writes the same serialized state to:
${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.jsonIf that file is missing, unreadable, corrupt, or incompatible, open Loopwire once, choose the device you want restored at login, and enable Restore audio in background again. The background runner will not invent a startup configuration from an empty or incompatible state file.
Preview the background restore transaction:
pnpm restore:background -- \
--state-file "${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.json" \
--mode preview \
--pretty--mode preview validates the persisted state and runs dry-run backend adapters. --mode live is the explicit host mutation path and should only be used after the selected backend and routes are correct.
If background restore reports multiple available backends, or that the saved backend is unavailable, open Loopwire and use Settings > Audio Backend to save a verified backend before enabling background restore again. Boot restore is intentionally fail-closed instead of guessing between audio systems at login.
For PulseAudio compatibility routes, background restore keeps normal switch verification strict but reports missing app streams as pending until those apps launch. That lets Loopwire-owned sinks remain ready at login without claiming that an app stream was already moved.
Live PulseAudio restore can also retry those pending routes for a bounded window without recreating the virtual sinks:
pnpm restore:background -- \
--state-file "${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.json" \
--backend pulseaudio \
--mode live \
--retry-pending-ms 10000 \
--retry-interval-ms 1000 \
--prettyEach retry refresh only moves and controls newly visible matching sink inputs, then verifies again. If apps are still absent when the window closes, the JSON output keeps pendingStreamRefresh.cleared false instead of pretending the streams were present.
For JACK restore, a persisted configuration can use deterministic Loopwire-owned ports only after a JACK provider creates them. The background runner can call an explicit provider command before connecting those ports:
pnpm restore:background -- \
--state-file "${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.json" \
--backend jack \
--mode live \
--jack-provider-command loopwire-jack-ports \
--jack-provider-delegate-mode detached \
--jack-provider-ready-delay-ms 750 \
--prettyThe provider command receives stable ensure --configuration-id ... --requirement ... --port ... arguments from the runtime. Loopwire re-runs jack_lsp after the provider exits and still fails closed if the expected ports are missing. Release artifacts install loopwire-jack-ports, a bundled provider wrapper that records those arguments in a loopwire.jack-ports.provision-plan manifest and returns nonzero unless LOOPWIRE_JACK_PORTS_DELEGATE or --delegate-command points at a live JACK client provider. If the provider is a long-running JACK client, set LOOPWIRE_JACK_PORTS_DELEGATE_MODE=detached for the wrapper process or pass --delegate-mode detached directly to the wrapper. The first-class restore flags --jack-provider-delegate-mode detached and --jack-provider-ready-delay-ms append those wrapper options for packaged and source-checkout services; the background restore still verifies the resulting ports with jack_lsp before connecting routes.
The desktop Settings → Providers section now persists the JACK provider command, timeout, delegate mode, and readiness delay, and enabling Restore audio in background with JACK selected writes the matching --jack-provider-* flags into the user-scoped restore unit. The CLI/systemd flags above remain for headless setups. Leave the command blank when your saved configuration targets pre-existing JACK ports.
For graph-edge DSP restore, a provider command can own source capture and output injection while Loopwire owns the configuration transaction, per-edge gain/mute math, and verification sequence:
pnpm restore:background -- \
--state-file "${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.json" \
--backend dsp \
--mode preview \
--dsp-provider-command loopwire-dsp-provider \
--dsp-frame-count 480 \
--prettyThe provider command receives read-source as stable arguments and returns JSON channel buffers on stdout. Loopwire sends rendered output buffers to write-output and verify-output as JSON stdin, with --configuration-id on output write, verify, and clear operations. verify-output must return explicit JSON; empty stdout is a verification failure even when the provider exits successfully. The source checkout and packaged systemd helpers can pass the same --backend dsp, --dsp-provider-command, --dsp-provider-timeout-ms, --dsp-provider-mode, and --dsp-frame-count flags. Run pnpm dsp:plan first to inspect the bounded provider operations, then run pnpm dsp:verify with the provider command before enabling boot restore. Execute-mode preflight writes rendered outputs, verifies them, and then clears those outputs so the provider cleanup path is tested before startup restore uses it. Release artifacts install loopwire-dsp-provider, a bundled file-backed provider for contract smoke and local restore preflight. It stores seeded source buffers and configuration-scoped rendered output buffers under LOOPWIRE_DSP_PROVIDER_DIR or ${XDG_STATE_HOME:-$HOME/.local/state}/loopwire/dsp-provider; it is not a live PipeWire/JACK capture or playback provider. Persisted selectedBackend: "dsp" state is honored for startup restore, but it still requires an explicit --dsp-provider-command after the persisted backend is resolved. The desktop Settings → Providers card can persist the DSP command, mode, timeout, and frame count, while these CLI/systemd flags remain the headless restore surface. --mode live --backend dsp or persisted DSP live restore requires --dsp-provider-mode live and a provider capabilities result with supportsLiveGraph:true plus read-source, write-output, verify-output, and clear-output in its operations list; the provider capabilities.operations field is the operations list checked by restore preflight. The bundled file-backed provider declares supportsLiveGraph:false by default and is rejected for live restore. Its LOOPWIRE_DSP_PROVIDER_LIVE_SMOKE=1 mode is reserved for isolated shell/provider contract proof; use production live mode only with a real provider that captures from and writes to the host audio graph.
Seed every source your configuration routes before running execute-mode preflight:
loopwire-dsp-provider seed-source --source-id mic --channels 2 --frames 480 --value 1
loopwire-dsp-provider seed-source --source-id browser --channels 2 --frames 480 --value 0.25Preview the service:
bash scripts/manage-autostart.sh render \
--mode systemd \
--source-dir "$PWD" \
--state-file "${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.json" \
--restore-mode preview \
--dsp-provider-command loopwire-dsp-provider \
--dsp-provider-timeout-ms 5000 \
--dsp-frame-count 480For a real live DSP provider, use the provider command supplied by that integration and declare the trust boundary:
pnpm dsp:verify -- \
--configuration exported-loopwire-config.json \
--provider-command loopwire-live-dsp-provider \
--require-live-capability \
--frame-count 480 \
--prettybash scripts/manage-autostart.sh render \
--mode systemd \
--source-dir "$PWD" \
--state-file "${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.json" \
--restore-mode live \
--dsp-provider-command loopwire-live-dsp-provider \
--dsp-provider-mode live \
--dsp-provider-timeout-ms 5000 \
--dsp-frame-count 480Dry-run enabling it:
bash scripts/manage-autostart.sh enable \
--mode systemd \
--source-dir "$PWD" \
--state-file "${XDG_CONFIG_HOME:-$HOME/.config}/loopwire/state.json" \
--restore-mode preview \
--dry-runThe generated service shape is:
[Unit]
Description=Loopwire audio routing restore
After=graphical-session.target pipewire.service pipewire-pulse.service wireplumber.service
Wants=pipewire.service wireplumber.service
[Service]
Type=simple
ExecStart=pnpm --dir /path/to/loopwire restore:background -- --state-file %h/.config/loopwire/state.json --mode preview
Restart=on-failure
RestartSec=2
[Install]
WantedBy=default.targetFor JACK boot restore, the helper and desktop shell append --backend jack, --jack-provider-command, and --jack-provider-timeout-ms to the generated ExecStart line only when a JACK provider command is configured. For DSP provider restore, the helper appends --backend dsp plus the DSP provider flags. Packaged services pass those flags after loopwire --background; source-checkout services pass them after pnpm restore:background --, so both boot paths keep the same runtime contract. The packaged JACK wrapper is a preflight/delegation surface, not a native JACK client creator. The packaged DSP provider is likewise a file-backed preflight provider unless a separate live provider is configured with --dsp-provider-command and --dsp-provider-mode live.
The helper supports both the packaged launcher path through --binary "$HOME/.local/bin/loopwire" and the source checkout path through --source-dir "$PWD".
Manual Systemd Commands
systemctl --user daemon-reload
systemctl --user enable --now loopwire.service
systemctl --user status loopwire.serviceStartup work must always include a disable path:
systemctl --user disable --now loopwire.servicePrefer the helper over hand-written unit files so docs, tests, and packaging stay aligned.