commit 218609e87600e485395e428fb7666e593432edad
parent e067fb197ff0aba583ad1af9b093b88cb5aa9dea
Author: Pablo Murad <pablo@pablomurad.com>
Date: Sun, 22 Mar 2026 21:58:54 -0300
closed app
Diffstat:
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}