feat: add 'muxplex generate-federation-key' CLI command
This commit is contained in:
@@ -125,6 +125,19 @@ def _check_for_update(info: dict) -> tuple[bool, str]:
|
|||||||
return True, "unknown install source — upgrading to be safe"
|
return True, "unknown install source — upgrading to be safe"
|
||||||
|
|
||||||
|
|
||||||
|
def generate_federation_key() -> None:
|
||||||
|
"""Generate a random federation key and write it to FEDERATION_KEY_PATH."""
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
|
path = settings_mod.FEDERATION_KEY_PATH
|
||||||
|
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
|
key = _secrets.token_urlsafe(32)
|
||||||
|
path.write_text(key + "\n")
|
||||||
|
path.chmod(0o600)
|
||||||
|
print(f"Federation key written to {path}")
|
||||||
|
print(f"Key: {key}")
|
||||||
|
|
||||||
|
|
||||||
def reset_secret() -> None:
|
def reset_secret() -> None:
|
||||||
"""Regenerate the signing secret and warn that all sessions are now invalid."""
|
"""Regenerate the signing secret and warn that all sessions are now invalid."""
|
||||||
path = get_secret_path()
|
path = get_secret_path()
|
||||||
@@ -675,6 +688,11 @@ def main() -> None:
|
|||||||
"reset-secret", help="Regenerate signing secret (invalidates sessions)"
|
"reset-secret", help="Regenerate signing secret (invalidates sessions)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sub.add_parser(
|
||||||
|
"generate-federation-key",
|
||||||
|
help="Generate a random federation key and write it to disk",
|
||||||
|
)
|
||||||
|
|
||||||
sub.add_parser("doctor", help="Check dependencies and system status")
|
sub.add_parser("doctor", help="Check dependencies and system status")
|
||||||
|
|
||||||
upgrade_parser = sub.add_parser(
|
upgrade_parser = sub.add_parser(
|
||||||
@@ -707,6 +725,8 @@ def main() -> None:
|
|||||||
show_password()
|
show_password()
|
||||||
elif args.command == "reset-secret":
|
elif args.command == "reset-secret":
|
||||||
reset_secret()
|
reset_secret()
|
||||||
|
elif args.command == "generate-federation-key":
|
||||||
|
generate_federation_key()
|
||||||
elif args.command == "doctor":
|
elif args.command == "doctor":
|
||||||
doctor()
|
doctor()
|
||||||
elif args.command in ("upgrade", "update"):
|
elif args.command in ("upgrade", "update"):
|
||||||
|
|||||||
@@ -1021,6 +1021,42 @@ def test_config_subcommand_registered():
|
|||||||
assert "reset" in result.stdout
|
assert "reset" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# task-3: generate-federation-key subcommand tests
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_federation_key_creates_file(tmp_path, monkeypatch, capsys):
|
||||||
|
"""generate_federation_key() creates key file with mode 0600 and prints key info."""
|
||||||
|
import muxplex.settings as settings_mod
|
||||||
|
|
||||||
|
key_file = tmp_path / ".config" / "muxplex" / "federation_key"
|
||||||
|
monkeypatch.setattr(settings_mod, "FEDERATION_KEY_PATH", key_file)
|
||||||
|
|
||||||
|
from muxplex.cli import generate_federation_key
|
||||||
|
|
||||||
|
generate_federation_key()
|
||||||
|
|
||||||
|
# File must exist
|
||||||
|
assert key_file.exists(), "Federation key file must be created"
|
||||||
|
|
||||||
|
# Content must be longer than 20 chars (stripping the trailing newline)
|
||||||
|
content = key_file.read_text().strip()
|
||||||
|
assert len(content) > 20, f"Key must be > 20 chars, got {len(content)}"
|
||||||
|
|
||||||
|
# File mode must be 0600
|
||||||
|
file_mode = stat.S_IMODE(key_file.stat().st_mode)
|
||||||
|
assert file_mode == 0o600, f"Expected 0o600, got {oct(file_mode)}"
|
||||||
|
|
||||||
|
# Output must include key info
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert "federation" in captured.out.lower() or "key" in captured.out.lower(), (
|
||||||
|
f"Output must mention key info, got: {captured.out!r}"
|
||||||
|
)
|
||||||
|
# The actual key value must appear in output
|
||||||
|
assert content in captured.out, "Key value must appear in output"
|
||||||
|
|
||||||
|
|
||||||
def test_upgrade_uses_service_module_install(monkeypatch, capsys):
|
def test_upgrade_uses_service_module_install(monkeypatch, capsys):
|
||||||
"""upgrade() must call muxplex.service.service_install."""
|
"""upgrade() must call muxplex.service.service_install."""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|||||||
Reference in New Issue
Block a user