runv-server

server tooling for runv.club
Log | Files | Refs | README

commit 218609e87600e485395e428fb7666e593432edad
parent e067fb197ff0aba583ad1af9b093b88cb5aa9dea
Author: Pablo Murad <pablo@pablomurad.com>
Date:   Sun, 22 Mar 2026 21:58:54 -0300

closed app

Diffstat:
M.gitignore | 6++++--
Aterminal/close_entre.py | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aterminal/closed.txt | 7+++++++
Aterminal/closed_app.py | 32++++++++++++++++++++++++++++++++
Mterminal/setup_entre.py | 8++++++--
Mterminal/templates/confirm.txt | 4++--
6 files changed, 135 insertions(+), 6 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -24,4 +24,7 @@ email/testmail*.py # Cópias locais de segredos de email (nunca versionar) runv-email.secrets.json -**/runv-email.secrets.json -\ No newline at end of file +**/runv-email.secrets.json + +.agent/ +.cursor/ diff --git a/terminal/close_entre.py b/terminal/close_entre.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +""" +Script para fechar temporariamente os registros do terminal `entre`. +Ele edita o ficheiro de drop-in do SSH para usar `closed_app.py` em vez de `entre_app.py`. + +Executar como root no servidor Debian. +""" +import os +import sys +import subprocess +from pathlib import Path + +def eprint(msg: str) -> None: + print(msg, file=sys.stderr) + +def require_root() -> None: + if os.geteuid() != 0: + eprint("Execute como root (sudo).") + raise SystemExit(1) + +def run(cmd: list[str]) -> None: + r = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + if r.returncode != 0: + err = (r.stderr or r.stdout or "").strip() + raise RuntimeError(f"Falhou: {' '.join(cmd)}\n{err}") + +def main() -> int: + require_root() + + dropin_path = Path("/etc/ssh/sshd_config.d/runv-entre.conf") + if not dropin_path.is_file(): + eprint(f"Erro: o ficheiro de configuração SSH {dropin_path} não foi encontrado.") + eprint("Parece que o setup do terminal `entre` ainda não foi executado ou está corrompido.") + return 1 + + content = dropin_path.read_text(encoding="utf-8") + + if "closed_app.py" in content: + print("Os registros já parecem estar fechados (closed_app.py detetado na config).") + return 0 + + if "entre_app.py" not in content: + eprint("Aviso: 'entre_app.py' não encontrado na configuração. Modificação ignorada por segurança.") + return 1 + + # Substitui a app principal pela fechada + new_content = content.replace("entre_app.py", "closed_app.py") + + # Grava novamente + dropin_path.write_text(new_content, encoding="utf-8") + print(f"Modificado {dropin_path.name}: entre_app.py -> closed_app.py") + + # Testa as confiugrações do ssh + print("Testando a configuração com 'sshd -t'...") + try: + run(["sshd", "-t"]) + except RuntimeError as e: + # Tenta reverter + eprint(f"Erro de sshd detectado: {e}") + eprint("Revertendo as mudanças.") + dropin_path.write_text(content, encoding="utf-8") + return 1 + + print("sshd -t: OK.") + + # Recarrega o SSH + try: + run(["systemctl", "reload", "ssh"]) + print("Serviço SSH recarregado (reload).") + except RuntimeError: + try: + run(["systemctl", "reload", "sshd"]) + print("Serviço SSH recarregado (reload).") + except RuntimeError as e: + eprint(f"Aviso: Não foi possível fazer o recarregamento do SSH de forma automática: {e}") + eprint("Por favor recarregue manualmente: systemctl reload ssh") + + print("\n[+] Fechado com sucesso! Agora as ligações serão encaminhadas para o ecrã CLOSED.") + print("Para reabrir os registros no futuro, basta correr 'sudo ./setup_entre.py' novamente.") + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/terminal/closed.txt b/terminal/closed.txt @@ -0,0 +1,6 @@ + + + ▄▄▄▄ ▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄ +██▀▀▀ ██ ██▀██ ███▄▄ ██▄▄ ██▀██ +▀████ ██▄▄▄ ▀███▀ ▄▄██▀ ██▄▄▄ ████▀ + +\ No newline at end of file diff --git a/terminal/closed_app.py b/terminal/closed_app.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +Aplicação de aviso quando os registos no runv.club estão fechados. +Operado via ForceCommand no OpenSSH. + +ASCII ART CLOSED e mensagem amigável. +""" +import sys + +def main() -> int: + # Cores ANSI + red = "\033[91m" + green = "\033[92m" + reset = "\033[0m" + + ascii_art = f"""{red} + + + ▄▄▄▄ ▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄ +██▀▀▀ ██ ██▀██ ███▄▄ ██▄▄ ██▀██ +▀████ ██▄▄▄ ▀███▀ ▄▄██▀ ██▄▄▄ ████▀ + +{reset}""" + + print(ascii_art) + print(" Olá, aguarde pela abertura dos registros :)\n") + print(f" Qualquer dúvida: {green}admin@runv.club{reset}\n") + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/terminal/setup_entre.py b/terminal/setup_entre.py @@ -59,7 +59,7 @@ import time from pathlib import Path from typing import Final -from gen_config_toml import write_terminal_config_toml +from gen_config_toml import write_terminal_config_toml # type: ignore VERSION: Final[str] = "0.11" ENTRE_USER: Final[str] = "entre" @@ -232,7 +232,9 @@ def install_pam_empty_password_rule( break insert_at = i + 1 - new_body = "".join(lines[:insert_at]) + block + "".join(lines[insert_at:]) + part1 = [lines[j] for j in range(insert_at)] + part2 = [lines[j] for j in range(insert_at, len(lines))] + new_body = "".join(part1) + block + "".join(part2) PAM_SSHD.write_text(new_body, encoding="utf-8") print(f"Inserida regra PAM em {PAM_SSHD} (antes da auth padrão).") @@ -635,6 +637,8 @@ def copy_module(dest: Path, *, dry_run: bool) -> None: files = [ "entre_app.py", "entre_core.py", + "closed_app.py", + "close_entre.py", "config.example.toml", "gen_config_toml.py", "README.md", diff --git a/terminal/templates/confirm.txt b/terminal/templates/confirm.txt @@ -1,11 +1,11 @@ Confirmar pedido ──────────────── - Revisa os dados antes de enviar. + Revise os dados antes de continuar. Nome desejado : {username} Email : {email} - Onde apareces online: + Onde podemos te ver: {online_presence} Fingerprint SHA256 : {fingerprint}