feat: TLS nudge in doctor and service install output
When host is set to network access (not 127.0.0.1) and TLS is not configured, doctor shows 'Run: muxplex setup-tls' and service install shows a tip line. Hidden on localhost-only setups since clipboard works without HTTPS there.
This commit is contained in:
+6
-1
@@ -369,9 +369,14 @@ def doctor() -> None:
|
|||||||
else:
|
else:
|
||||||
print(f" {warn_mark} TLS: configured but cert not readable ({tls_cert})")
|
print(f" {warn_mark} TLS: configured but cert not readable ({tls_cert})")
|
||||||
else:
|
else:
|
||||||
|
# Only show TLS warning if host is not localhost
|
||||||
|
host = cfg.get("host", "127.0.0.1")
|
||||||
|
if host != "127.0.0.1":
|
||||||
|
# Network host without TLS: show nudge
|
||||||
print(
|
print(
|
||||||
f" {warn_mark} TLS: disabled (clipboard requires HTTPS on non-localhost)"
|
f" {warn_mark} TLS: disabled — clipboard won't work on remote devices"
|
||||||
)
|
)
|
||||||
|
print(" Run: muxplex setup-tls")
|
||||||
|
|
||||||
# Auth status
|
# Auth status
|
||||||
pw_path = get_password_path()
|
pw_path = get_password_path()
|
||||||
|
|||||||
@@ -116,6 +116,18 @@ def _prompt_host_if_localhost() -> None:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _show_tls_nudge_if_needed() -> None:
|
||||||
|
"""Show TLS setup nudge if host is network and TLS is not configured."""
|
||||||
|
from muxplex.settings import load_settings
|
||||||
|
|
||||||
|
settings = load_settings()
|
||||||
|
host = settings.get("host", "127.0.0.1")
|
||||||
|
tls_cert = settings.get("tls_cert", "")
|
||||||
|
|
||||||
|
if host != "127.0.0.1" and not tls_cert:
|
||||||
|
print(" Tip: Enable HTTPS for clipboard support: muxplex setup-tls")
|
||||||
def _systemd_install() -> None:
|
def _systemd_install() -> None:
|
||||||
muxplex_bin = _resolve_muxplex_bin()
|
muxplex_bin = _resolve_muxplex_bin()
|
||||||
safe_path = os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin")
|
safe_path = os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin")
|
||||||
@@ -128,6 +140,7 @@ def _systemd_install() -> None:
|
|||||||
subprocess.run(["systemctl", "--user", "daemon-reload"], check=True)
|
subprocess.run(["systemctl", "--user", "daemon-reload"], check=True)
|
||||||
subprocess.run(["systemctl", "--user", "enable", "--now", "muxplex"], check=True)
|
subprocess.run(["systemctl", "--user", "enable", "--now", "muxplex"], check=True)
|
||||||
_prompt_host_if_localhost()
|
_prompt_host_if_localhost()
|
||||||
|
_show_tls_nudge_if_needed()
|
||||||
|
|
||||||
|
|
||||||
def _systemd_uninstall() -> None:
|
def _systemd_uninstall() -> None:
|
||||||
@@ -179,6 +192,7 @@ def _launchd_install() -> None:
|
|||||||
["launchctl", "bootstrap", f"gui/{uid}", str(_LAUNCHD_PLIST_PATH)], check=True
|
["launchctl", "bootstrap", f"gui/{uid}", str(_LAUNCHD_PLIST_PATH)], check=True
|
||||||
)
|
)
|
||||||
_prompt_host_if_localhost()
|
_prompt_host_if_localhost()
|
||||||
|
_show_tls_nudge_if_needed()
|
||||||
|
|
||||||
|
|
||||||
def _launchd_uninstall() -> None:
|
def _launchd_uninstall() -> None:
|
||||||
|
|||||||
@@ -1618,11 +1618,14 @@ def test_doctor_shows_tls_enabled(tmp_path, monkeypatch, capsys):
|
|||||||
|
|
||||||
|
|
||||||
def test_doctor_shows_tls_clipboard_warning(tmp_path, monkeypatch, capsys):
|
def test_doctor_shows_tls_clipboard_warning(tmp_path, monkeypatch, capsys):
|
||||||
"""doctor() mentions clipboard or https when TLS is disabled."""
|
"""doctor() mentions clipboard or https when TLS is disabled on network host."""
|
||||||
|
import json
|
||||||
|
|
||||||
import muxplex.settings as settings_mod
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
settings_file = tmp_path / "settings.json"
|
settings_file = tmp_path / "settings.json"
|
||||||
settings_file.write_text("{}")
|
# Set host to network to trigger the TLS warning (not localhost)
|
||||||
|
settings_file.write_text(json.dumps({"host": "0.0.0.0"}))
|
||||||
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
from muxplex.cli import doctor
|
from muxplex.cli import doctor
|
||||||
@@ -1632,7 +1635,7 @@ def test_doctor_shows_tls_clipboard_warning(tmp_path, monkeypatch, capsys):
|
|||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
out_lower = out.lower()
|
out_lower = out.lower()
|
||||||
assert "clipboard" in out_lower or "https" in out_lower, (
|
assert "clipboard" in out_lower or "https" in out_lower, (
|
||||||
f"Expected 'clipboard' or 'https' in doctor TLS-disabled output, got: {out!r}"
|
f"Expected 'clipboard' or 'https' in doctor TLS-disabled output for network host, got: {out!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -2005,3 +2008,48 @@ def test_setup_tls_regenerates_on_eof(tmp_path, monkeypatch, capsys):
|
|||||||
assert "keeping" in out_lower, (
|
assert "keeping" in out_lower, (
|
||||||
f"Expected 'keeping' in output after EOFError (default 'n'), got: {out!r}"
|
f"Expected 'keeping' in output after EOFError (default 'n'), got: {out!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# task: TLS nudge hints in doctor and service install
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_tls_nudge_shows_run_command_on_network_host(capsys, tmp_path, monkeypatch):
|
||||||
|
"""doctor must show 'Run: muxplex setup-tls' when host is network and TLS disabled."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
|
settings_file = tmp_path / "settings.json"
|
||||||
|
settings_file.write_text(json.dumps({"host": "0.0.0.0", "tls_cert": "", "tls_key": ""}))
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
|
from muxplex.cli import doctor
|
||||||
|
|
||||||
|
doctor()
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "muxplex setup-tls" in out, (
|
||||||
|
f"Expected 'muxplex setup-tls' in doctor output when host is 0.0.0.0 and TLS disabled, got: {out!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_tls_nudge_hidden_on_localhost(capsys, tmp_path, monkeypatch):
|
||||||
|
"""doctor must NOT show TLS nudge when host is 127.0.0.1."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
|
settings_file = tmp_path / "settings.json"
|
||||||
|
settings_file.write_text(json.dumps({"host": "127.0.0.1", "tls_cert": "", "tls_key": ""}))
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
|
from muxplex.cli import doctor
|
||||||
|
|
||||||
|
doctor()
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "muxplex setup-tls" not in out, (
|
||||||
|
f"TLS nudge must NOT appear in doctor output when host is 127.0.0.1, got: {out!r}"
|
||||||
|
)
|
||||||
|
|||||||
@@ -586,3 +586,80 @@ def test_launchd_logs_handles_keyboard_interrupt(monkeypatch):
|
|||||||
monkeypatch.setattr(subprocess, "run", mock_run)
|
monkeypatch.setattr(subprocess, "run", mock_run)
|
||||||
# Should not raise
|
# Should not raise
|
||||||
svc._launchd_logs()
|
svc._launchd_logs()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# task: TLS nudge hints in service install
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_install_shows_tls_tip_on_network_host(capsys, tmp_path, monkeypatch):
|
||||||
|
"""service install must show TLS tip when host is network and TLS disabled."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
import muxplex.service as svc
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
|
# Setup paths
|
||||||
|
unit_dir = tmp_path / "systemd" / "user"
|
||||||
|
unit_path = unit_dir / "muxplex.service"
|
||||||
|
settings_file = tmp_path / "settings.json"
|
||||||
|
|
||||||
|
monkeypatch.setattr(svc, "_SYSTEMD_UNIT_DIR", unit_dir)
|
||||||
|
monkeypatch.setattr(svc, "_SYSTEMD_UNIT_PATH", unit_path)
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
|
# Setup settings with network host and no TLS
|
||||||
|
settings_file.write_text(json.dumps({"host": "0.0.0.0", "tls_cert": "", "tls_key": ""}))
|
||||||
|
|
||||||
|
# Mock subprocess to avoid actual systemctl calls
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setattr(subprocess, "run", lambda cmd, **kw: calls.append(list(cmd)))
|
||||||
|
|
||||||
|
# Mock the prompt function
|
||||||
|
monkeypatch.setattr(svc, "_prompt_host_if_localhost", lambda: None)
|
||||||
|
|
||||||
|
from muxplex.service import service_install
|
||||||
|
|
||||||
|
service_install()
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "muxplex setup-tls" in out, (
|
||||||
|
f"Expected 'muxplex setup-tls' in service install output when host is 0.0.0.0 and TLS disabled, got: {out!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_install_hides_tls_tip_on_localhost(capsys, tmp_path, monkeypatch):
|
||||||
|
"""service install must NOT show TLS tip when host is 127.0.0.1."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
import muxplex.service as svc
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
|
# Setup paths
|
||||||
|
unit_dir = tmp_path / "systemd" / "user"
|
||||||
|
unit_path = unit_dir / "muxplex.service"
|
||||||
|
settings_file = tmp_path / "settings.json"
|
||||||
|
|
||||||
|
monkeypatch.setattr(svc, "_SYSTEMD_UNIT_DIR", unit_dir)
|
||||||
|
monkeypatch.setattr(svc, "_SYSTEMD_UNIT_PATH", unit_path)
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", settings_file)
|
||||||
|
|
||||||
|
# Setup settings with localhost
|
||||||
|
settings_file.write_text(json.dumps({"host": "127.0.0.1", "tls_cert": "", "tls_key": ""}))
|
||||||
|
|
||||||
|
# Mock subprocess
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setattr(subprocess, "run", lambda cmd, **kw: calls.append(list(cmd)))
|
||||||
|
|
||||||
|
# Mock the prompt function
|
||||||
|
monkeypatch.setattr(svc, "_prompt_host_if_localhost", lambda: None)
|
||||||
|
|
||||||
|
from muxplex.service import service_install
|
||||||
|
|
||||||
|
service_install()
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "muxplex setup-tls" not in out, (
|
||||||
|
f"TLS tip must NOT appear in service install output when host is 127.0.0.1, got: {out!r}"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user