feat: add show-password CLI subcommand

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: <pw>' 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
This commit is contained in:
Brian Krabach
2026-03-28 22:53:29 -07:00
parent 159b2c3618
commit 0d691eab12
2 changed files with 70 additions and 0 deletions
+19
View File
@@ -5,10 +5,25 @@ import os
import sys import sys
from pathlib import Path from pathlib import Path
from muxplex.auth import load_password, pam_available
# Module-level path constants (overridable in tests via monkeypatch) # Module-level path constants (overridable in tests via monkeypatch)
_system_service_path = Path("/etc/systemd/system/muxplex.service") _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( def serve(
host: str = "127.0.0.1", host: str = "127.0.0.1",
port: int = 8088, port: int = 8088,
@@ -101,10 +116,14 @@ def main() -> None:
"--system", action="store_true", help="System-wide (requires sudo)" "--system", action="store_true", help="System-wide (requires sudo)"
) )
sub.add_parser("show-password", help="Show the current muxplex password")
args = parser.parse_args() args = parser.parse_args()
if args.command == "install-service": if args.command == "install-service":
install_service(system=args.system) install_service(system=args.system)
elif args.command == "show-password":
show_password()
else: else:
serve( serve(
host=args.host, port=args.port, auth=args.auth, session_ttl=args.session_ttl host=args.host, port=args.port, auth=args.auth, session_ttl=args.session_ttl
+51
View File
@@ -160,6 +160,57 @@ def test_install_service_strips_wsl_mnt_paths_from_environment(tmp_path, monkeyp
assert "/usr/bin" in env_line 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(): def test_dunder_main_calls_main():
"""python -m muxplex must call cli.main().""" """python -m muxplex must call cli.main()."""
import importlib.util import importlib.util