feat(init): macOS launchd + shellrc fallback + --start-daemon#19
Merged
Conversation
Closes the v0.4.1 UX gap where `muxa init` would finish cleanly on
macOS but `muxa status` would fail because nothing had ever started
muxad. The Linux `muxad-systemd` component had no equivalent on any
other platform.
Three pieces:
1. `muxad-launchd` — macOS analogue of `muxad-systemd`. Writes
`~/Library/LaunchAgents/dev.open330.muxad.plist`, runs
`launchctl bootstrap gui/<uid>` + `kickstart -k`. Uninstall does
the inverse. Same outcome / dry-run / marker semantics as the
systemd path.
2. `muxad-shellrc` — cross-platform fallback for hosts with no
service manager (containers, BSDs, WSL1, minimal Linux, CI). One
marker block in `~/.zshrc` / `~/.bashrc` / fish config / fallback
`~/.profile` that lazy-starts muxad from the next interactive
shell. Shell pick is auto from `$SHELL`.
3. `--start-daemon` flag (default true) — appended action at the
end of every install run that spawns muxad detached if
`pgrep -x muxad` doesn't find it. Belt-and-suspenders alongside
the manager components: if systemd's enable-now or launchd's
bootstrap silently no-ops, the explicit start guarantees the
wizard leaves muxad responsive when it finishes.
Smart selection in Preset::Standard / Preset::Full + the wizard's
multi-select: exactly one of the three managers shows up, picked
from `std::env::consts::OS`, with `Detection::recommended_daemon_manager`
further degrading to shellrc when systemctl/launchctl is missing on
a host that would normally support them. The "Standard preset = a
working install" invariant holds even in stripped-down envs.
Internals:
- `files/launchd.rs` (~120 LOC) mirrors `files/systemd.rs`'s shape:
pure plist content layer + thin shell-out driver. New
`EnableLaunchdUnit { plist_path }` / `DisableLaunchdUnit` actions.
- `files/shellrc.rs` (~120 LOC) reuses the existing `marker::upsert`
/ `marker::remove` so removal is surgical the same way every other
marker-block file works.
- `apply.rs::apply_one` was getting too long (clippy 116/100); split
EditFile / DeleteFile / SourceTmuxConf / StartDaemon out as named
helpers.
Tests: launchd plist content + outcome decision; shellrc round-trip
+ shell→rc mapping; updated default_selection assertion (now picks
the OS-fallback manager). 361 → 365 workspace tests, all green.
Clippy clean with `-D warnings`, rustfmt clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the v0.4.1 UX gap where
muxa initfinished cleanly on macOS butmuxa statusfailed because nothing had ever started muxad. Three additive pieces, no breaking changes:muxad-launchd— macOS analogue ofmuxad-systemd.muxa initwrites~/Library/LaunchAgents/dev.open330.muxad.plist, runslaunchctl bootstrap gui/<uid>+kickstart -k. Uninstall does the inverse. Same outcome / dry-run / marker semantics as the systemd path.muxad-shellrc— cross-platform fallback for hosts with no service manager (containers, BSDs, WSL1, minimal Linux, CI). Marker block in~/.zshrc/~/.bashrc/ fish config / fallback~/.profile, lazy-starting muxad from the next interactive shell. Shell pick is auto from$SHELL.--start-daemonflag (defaulttrue) — final action that spawns muxad detached ifpgrep -x muxaddoesn't find it. Belt-and-suspenders alongside the manager components: if systemd'senable --nowor launchd'sbootstrapsilently no-ops, the explicit start still leaves muxad responsive when the wizard finishes.Preset::Standard/Preset::Fulland the wizard's multi-select pick exactly one of the three managers fromstd::env::consts::OS.Detection::recommended_daemon_managerfurther degrades toshellrcwhen systemctl/launchctl is missing on a host that'd normally support them — the "Standard preset = working install" invariant holds even in stripped-down envs.Why now
Reported by a user on macOS (after the v0.4.1 fix landed):
muxa initsucceeded butmuxa statusreturneddaemon not reachable at /tmp/muxa-501.sock. Pre-flightpgrepmatched a stale process; IPC socket wasn't bound.muxad-launchdis the macOS-native fix;muxad-shellrc+--start-daemonclose the gap on every other systemd-less platform too.Module layout
files/launchd.rs(new, ~120 LOC)launchctl bootstrap/bootoutdriverfiles/shellrc.rs(new, ~120 LOC)$SHELL-aware path selectioncomponents.rsMuxadLaunchd/MuxadShellrcvariants +recommended_daemon_manager()+applicable_here()filterdetect.rslaunchctl_availablefield +recommended_daemon_manager()host degraderplan.rsEnableLaunchdUnit { plist_path }/DisableLaunchdUnit/StartDaemonIfNeededactions;plan_systemd/plan_launchd/plan_shellrchelpersapply.rsmuxadspawner; refactoredapply_oneinto named helpers (was at clippy's line cap)mod.rs--start-daemonflag + appender; new step linesui.rsapplicable_here()onlyTest plan
cargo test --workspace— 365 / 365 pass (was 361 + 4 new)cargo clippy --workspace --all-targets -- -D warnings— cleancargo fmt --all -- --check— cleanmuxa init --dry-run --preset standard --yes→ systemd, no launchd, no shellrc;start muxad if not runningline appearsmuxa init --preset standard --yeson a real macOS host — confirms plist written, agent loaded,muxa statusresponsive without manual interventionmuxa init --preset standard --yes→ falls back tomuxad-shellrc, daemon startsmuxa init --preset standard --yesthenmuxa init --uninstallleaves a clean stateRelease
Ship as v0.4.2 (minor — additive new feature).
🤖 Generated with Claude Code