From 0d691eab1281811589fa0bce7c44fc3cadda0684 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Sat, 28 Mar 2026 22:53:29 -0700 Subject: [PATCH] feat: add show-password CLI subcommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 'show-password' subcommand to the muxplex CLI that: - Prints 'Auth mode: PAM — no password file used' when MUXPLEX_AUTH is not 'password' and PAM is available - Prints 'Password: ' when a password file exists - Prints guidance to auto-generate when no password file found Also imports load_password and pam_available at module level in cli.py to enable testable patching via muxplex.cli namespace. Tests added: - test_show_password_prints_password_from_file - test_show_password_no_file - test_show_password_pam_mode --- muxplex/cli.py | 19 +++++++++++++++ muxplex/tests/test_cli.py | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/muxplex/cli.py b/muxplex/cli.py index 38c7a1a..d97861c 100644 --- a/muxplex/cli.py +++ b/muxplex/cli.py @@ -5,10 +5,25 @@ import os import sys from pathlib import Path +from muxplex.auth import load_password, pam_available + # Module-level path constants (overridable in tests via monkeypatch) _system_service_path = Path("/etc/systemd/system/muxplex.service") +def show_password() -> None: + """Print the current muxplex password or indicate PAM mode.""" + auth_mode = os.environ.get("MUXPLEX_AUTH", "").lower() + if auth_mode != "password" and pam_available(): + print("Auth mode: PAM — no password file used") + return + pw = load_password() + if pw: + print(f"Password: {pw}") + else: + print("No password file found. Start muxplex to auto-generate one.") + + def serve( host: str = "127.0.0.1", port: int = 8088, @@ -101,10 +116,14 @@ def main() -> None: "--system", action="store_true", help="System-wide (requires sudo)" ) + sub.add_parser("show-password", help="Show the current muxplex password") + args = parser.parse_args() if args.command == "install-service": install_service(system=args.system) + elif args.command == "show-password": + show_password() else: serve( host=args.host, port=args.port, auth=args.auth, session_ttl=args.session_ttl diff --git a/muxplex/tests/test_cli.py b/muxplex/tests/test_cli.py index 33877d7..af208c2 100644 --- a/muxplex/tests/test_cli.py +++ b/muxplex/tests/test_cli.py @@ -160,6 +160,57 @@ def test_install_service_strips_wsl_mnt_paths_from_environment(tmp_path, monkeyp assert "/usr/bin" in env_line +def test_show_password_prints_password_from_file(tmp_path, monkeypatch, capsys): + """show_password() prints the password when MUXPLEX_AUTH=password and file exists.""" + from muxplex.cli import show_password + + # Set up fake home with password file + fake_home = tmp_path / "home" + pw_dir = fake_home / ".config" / "muxplex" + pw_dir.mkdir(parents=True) + pw_file = pw_dir / "password" + pw_file.write_text("my-test-password\n") + + monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) + monkeypatch.setenv("MUXPLEX_AUTH", "password") + + show_password() + + captured = capsys.readouterr() + assert "my-test-password" in captured.out + + +def test_show_password_no_file(tmp_path, monkeypatch, capsys): + """show_password() tells user no file found when in password mode with no file.""" + from muxplex.cli import show_password + + # Set up fake home WITHOUT password file + fake_home = tmp_path / "home" + fake_home.mkdir(parents=True) + + monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) + monkeypatch.setenv("MUXPLEX_AUTH", "password") + + show_password() + + captured = capsys.readouterr() + output_lower = captured.out.lower() + assert "no password" in output_lower or "not found" in output_lower + + +def test_show_password_pam_mode(monkeypatch, capsys): + """show_password() reports PAM mode when pam_available() is True and not password mode.""" + from muxplex.cli import show_password + + monkeypatch.delenv("MUXPLEX_AUTH", raising=False) + + with patch("muxplex.cli.pam_available", return_value=True): + show_password() + + captured = capsys.readouterr() + assert "pam" in captured.out.lower() + + def test_dunder_main_calls_main(): """python -m muxplex must call cli.main().""" import importlib.util