cli: add first-party launchd install/uninstall commands for autostart
Introduce built-in launchd management to remove shell-wrapper indirection and make macOS autostart reliable. - Add `install-launchagent` and `uninstall-launchagent` CLI subcommands. - Create launchd helper module to: - write `~/Library/LaunchAgents/<label>.plist` - invoke app via `sys.executable -m mpd_now_playable.cli` (absolute interpreter path) - set `RunAtLoad`, `KeepAlive` - run `launchctl bootout/bootstrap/kickstart` for install and `bootout` for uninstall - support `--label` and `--force` - Add tests for subcommand dispatch, plist generation, and launchctl flow. - Document the new launchd workflow in README. - Run markdownlint This keeps the setup DRY/KISS, avoids PATH/shell expansion pitfalls in launchd, and gives users a supported and easy autostart path out of the box.
This commit is contained in:
parent
eb5a9d5745
commit
491e7dea1f
6 changed files with 308 additions and 35 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
|
|
@ -7,6 +8,7 @@ from rich import print as rich_print
|
|||
from .__version__ import __version__
|
||||
from .config.load import loadConfig
|
||||
from .config.model import Config
|
||||
from .launchd import DEFAULT_LABEL, install_launchagent, uninstall_launchagent
|
||||
from .mpd.listener import MpdStateListener
|
||||
from .song_receiver import (
|
||||
Receiver,
|
||||
|
|
@ -24,20 +26,63 @@ async def listen(
|
|||
|
||||
|
||||
def print_help() -> None:
|
||||
print("Usage: mpd-now-playable [OPTIONS]")
|
||||
print("")
|
||||
print("Options:")
|
||||
print(" -h, --help Show this help message and exit.")
|
||||
print(" -v, --version Show version and exit.")
|
||||
parser = build_parser()
|
||||
parser.print_help()
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="mpd-now-playable")
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--version",
|
||||
action="store_true",
|
||||
help="Show version and exit.",
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
install_cmd = subparsers.add_parser(
|
||||
"install-launchagent",
|
||||
help="Install and start a per-user launchd service.",
|
||||
)
|
||||
install_cmd.add_argument(
|
||||
"--label",
|
||||
default=DEFAULT_LABEL,
|
||||
help=f"launchd label to install (default: {DEFAULT_LABEL}).",
|
||||
)
|
||||
install_cmd.add_argument(
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Replace an existing plist with the same label.",
|
||||
)
|
||||
|
||||
uninstall_cmd = subparsers.add_parser(
|
||||
"uninstall-launchagent",
|
||||
help="Unload and remove a per-user launchd service.",
|
||||
)
|
||||
uninstall_cmd.add_argument(
|
||||
"--label",
|
||||
default=DEFAULT_LABEL,
|
||||
help=f"launchd label to uninstall (default: {DEFAULT_LABEL}).",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = set(sys.argv[1:])
|
||||
if "-h" in args or "--help" in args:
|
||||
print_help()
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if args.version:
|
||||
rich_print(f"mpd-now-playable v{__version__}")
|
||||
return
|
||||
if "-v" in args or "--version" in args:
|
||||
print(f"mpd-now-playable v{__version__}")
|
||||
|
||||
if args.command == "install-launchagent":
|
||||
path = install_launchagent(label=args.label, force=args.force)
|
||||
rich_print(f"Installed LaunchAgent at {path}")
|
||||
return
|
||||
|
||||
if args.command == "uninstall-launchagent":
|
||||
path = uninstall_launchagent(label=args.label)
|
||||
rich_print(f"Uninstalled LaunchAgent at {path}")
|
||||
return
|
||||
|
||||
rich_print(f"mpd-now-playable v{__version__}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue