runv-server

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

commit 2382aed227d159ba94d2eda6ce363b64a495b9c6
parent dbceb051f1c56926ea66aeed690fec8aeea5e036
Author: Pablo Murad <pablo@pablomurad.com>
Date:   Sat, 21 Mar 2026 18:36:47 -0300

minor changes

Diffstat:
M.gitignore | 11+++++++++--
Adev-notes/ssh | 3+++
Mpatches/patch_irc.py | 5+++++
Mscripts/admin/create_runv_user.py | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/admin/setup_alt_protocols.py | 194++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mscripts/create_runv_user.md | 9+++++++++
Mscripts/docs/alt_protocols.md | 18++++++++++++------
Msite/README.md | 29++++++++++++++++++-----------
Msite/build_directory.md | 19+++++++++----------
Msite/genlanding.md | 20+++++++++++++++-----
Msite/genlanding.py | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Asite/news/README.md | 25+++++++++++++++++++++++++
Asite/news/publish_news.py | 292+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msite/public/assets/app.js | 46++++++++++++++++++++++++++++++++++------------
Asite/public/assets/news-page.js | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msite/public/assets/style.css | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/public/faq/index.html | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msite/public/index.html | 21++++++++++++++++++---
Msite/public/junte-se/index.html | 93+++++++++++++++++++++++++------------------------------------------------------
Asite/public/news/data/.gitkeep | 0
Asite/public/news/data/news.json.example | 3+++
Msite/public/news/index.html | 31+++++++++++++++++++++++++------
Asite/public/robots.txt | 4++++
Asite/public/sitemap.xml | 38++++++++++++++++++++++++++++++++++++++
Asite/public/wiki/contas-e-acesso.html | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/public/wiki/faq.html | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msite/public/wiki/index.html | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Asite/public/wiki/privacidade-e-seguranca.html | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/public/wiki/punicoes-e-moderacao.html | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/public/wiki/regras-da-comunidade.html | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/public/wiki/visao-geral.html | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/wiki/01_index.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asite/wiki/02_visao-geral.txt | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/wiki/03_contas-e-acesso.txt | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/wiki/04_regras-da-comunidade.txt | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/wiki/05_punicoes-e-moderacao.txt | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/wiki/06_privacidade-e-seguranca.txt | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/wiki/07_faq.txt | 46++++++++++++++++++++++++++++++++++++++++++++++
Asite/wiki/build_wiki.py | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
39 files changed, 2667 insertions(+), 150 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -6,4 +6,11 @@ __pycache__/ venv/ .env .env.local -guide.md -\ No newline at end of file +guide.md + +# Gerados no servidor / localmente (evita conflitos em git pull) +site/public/news/data/news.json +site/public/news/feed.rss + +# Wiki: bytecode do gerador local +site/wiki/__pycache__/ +\ No newline at end of file diff --git a/dev-notes/ssh b/dev-notes/ssh @@ -0,0 +1,2 @@ +mkdir $HOME\.ssh -Force +ssh-keygen -t ed25519 -a 100 -f "$HOME\.ssh\yourkey" +\ No newline at end of file diff --git a/patches/patch_irc.py b/patches/patch_irc.py @@ -2,6 +2,10 @@ """ Provisiona a rede IRC da casa (estilo tilde.club) e o comando «chat» para utilizadores. +O conjunto ``IRC_PATCH_SKIP_USERS`` também é usado por ``resolve_all_users`` para o +backfill Gopher/Gemini (``setup_alt_protocols.py``): contas listadas não recebem +bind mount em ``/var/gemini/users/<user>`` nem entram no menu Gopher/Gemini raiz. + - Config em ~/.config/weechat (XDG), servidor interno «runv», autoconnect. - Aplicação **só** via binário ``weechat-headless`` (-a, -r, --stdout); não usar cliente interactivo no patch. - Instala /usr/local/bin/chat (launcher) salvo --skip-launcher. @@ -68,6 +72,7 @@ IRC_PATCH_SKIP_USERS: Final[frozenset[str]] = frozenset( "_apt", "nobody", "entre", + "pmurad-admin", "admin", "postmaster", } diff --git a/scripts/admin/create_runv_user.py b/scripts/admin/create_runv_user.py @@ -60,6 +60,7 @@ from typing import Any, Final, NoReturn # Com python3 -P ou PYTHONSAFEPATH=1 o diretório deste script não entra em sys.path; # necessário para «from runv_mount» dentro das funções de quota/mount. _SCRIPT_DIR = Path(__file__).resolve().parent +_REPO_ROOT = _SCRIPT_DIR.parent.parent if str(_SCRIPT_DIR) not in sys.path: sys.path.insert(0, str(_SCRIPT_DIR)) @@ -559,6 +560,9 @@ def ensure_gemini_user_symlink( GEMINI_USERS_DIR, ) return + if username in alt.irc_patch_skip_users(log): + log.info("bind Gemini omitido (IRC_PATCH_SKIP_USERS): %s", username) + return alt.ensure_gemini_bind_mount( username, home.parent, @@ -1076,6 +1080,54 @@ def try_apply_quota( # --------------------------------------------------------------------------- +def try_refresh_landing_members_json( + *, + document_root: Path, + users_json: Path, + homes_root: Path | None, + log: logging.Logger, +) -> bool: + """ + Regenera public/data/members.json no DocumentRoot da landing (build_directory.py). + Falhas são apenas registadas — não aborta o provisionamento. + """ + script = _REPO_ROOT / "site" / "build_directory.py" + if not script.is_file(): + log.warning( + "build_directory.py não encontrado em %s; members.json da landing não atualizado", + script, + ) + return False + out = document_root / "data" / "members.json" + cmd = [ + sys.executable, + str(script), + "--users-json", + str(users_json), + "-o", + str(out), + ] + if homes_root is not None: + cmd.extend(["--homes-root", str(homes_root)]) + try: + r = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + err_tail = (r.stderr or r.stdout or "").strip() + if r.returncode != 0: + log.warning( + "build_directory terminou com código %s: %s", + r.returncode, + err_tail[:2000] if err_tail else "(sem saída)", + ) + return False + log.info("members.json da landing actualizado em %s", out) + if r.stderr and r.stderr.strip(): + log.debug("build_directory stderr: %s", r.stderr.strip()[:1500]) + return True + except (OSError, subprocess.TimeoutExpired) as e: + log.warning("falha ao executar build_directory: %s", e) + return False + + def print_banner() -> None: print() print(" create_runv_user — provisionamento runv.club") @@ -1273,6 +1325,26 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: help=f"URL base para o resumo (padrão: {DEFAULT_BASE_URL})", ) p.add_argument( + "--landing-document-root", + type=Path, + default=Path("/var/www/runv.club/html"), + help=( + "DocumentRoot da landing Apache; após criar o utilizador, executa site/build_directory.py " + "para gravar data/members.json (bolinhas no site). Se a pasta não existir, o passo é ignorado." + ), + ) + p.add_argument( + "--no-refresh-landing-members", + action="store_true", + help="não regenerar data/members.json na landing após gravar metadados", + ) + p.add_argument( + "--members-homes-root", + type=Path, + default=None, + help="se definido (ex. /home), passa --homes-root a build_directory.py (homepage_mtime)", + ) + p.add_argument( "--no-quota", action="store_true", help="não aplica quota de disco (ignora setquota)", @@ -1522,6 +1594,25 @@ def main(argv: list[str] | None = None) -> int: log.info("=== fase: gravação de metadados JSON (%s)", args.metadata_file) append_user_metadata(args.metadata_file, args.lock_file, record, log) + members_refreshed = False + if not args.no_refresh_landing_members and args.landing_document_root: + root = args.landing_document_root.resolve() + if root.is_dir(): + log.info("=== fase: actualizar members.json da landing (%s)", root) + members_refreshed = try_refresh_landing_members_json( + document_root=root, + users_json=args.metadata_file, + homes_root=args.members_homes_root.resolve() + if args.members_homes_root + else None, + log=log, + ) + else: + log.info( + "landing document root inexistente (%s); omitindo build_directory.py", + root, + ) + log.info( "=== resultado final: status=%s quota_status=%s (operação concluída)", overall_status, @@ -1538,6 +1629,17 @@ def main(argv: list[str] | None = None) -> int: print(f" URL prevista: {args.base_url.rstrip('/')}/~{user}/") print(f" fingerprint: {fingerprint}") print(f" metadados: {args.metadata_file}") + if members_refreshed: + print( + f" landing members: {args.landing_document_root.resolve() / 'data' / 'members.json'}", + ) + elif not args.no_refresh_landing_members and args.landing_document_root: + dr = args.landing_document_root.resolve() + if dr.is_dir(): + print( + " landing members: (falha ao regenerar; ver log — corra build_directory.py manualmente)", + file=sys.stderr, + ) if args.no_quota: print(" quota: omitida (--no-quota)") else: diff --git a/scripts/admin/setup_alt_protocols.py b/scripts/admin/setup_alt_protocols.py @@ -8,7 +8,7 @@ Infraestrutura Gopher (gophernicus) e Gemini (molly-brown) para runv.club. Idempotente, dry-run, subprocess sem shell. Executar como root no Debian. -Versão 0.13 — runv.club +Versão 0.14 — runv.club """ from __future__ import annotations @@ -33,7 +33,7 @@ from typing import Any, Final # Constantes # --------------------------------------------------------------------------- -VERSION: Final[str] = "0.13" +VERSION: Final[str] = "0.14" LETSENCRYPT_LIVE: Final[Path] = Path("/etc/letsencrypt/live") LETSENCRYPT_ARCHIVE: Final[Path] = Path("/etc/letsencrypt/archive") @@ -70,10 +70,6 @@ MOLLY_LOGS_DROPIN_PATH: Final[Path] = Path( PACKAGES_GOPHER: Final[tuple[str, ...]] = ("gophernicus",) PACKAGES_GEMINI: Final[tuple[str, ...]] = ("molly-brown",) -DEFAULT_ROOT_GOPHERMAP: Final[str] = """iBem-vindo ao Gopher em runv.club — pubnix. fake NULL 0 -iCada utilizador com ~/public_gopher aparece como ~user no menu do servidor. fake NULL 0 -""" - DEFAULT_USER_GOPHERMAP: Final[str] = """iBem-vindo ao teu espaço Gopher no runv.club. fake NULL 0 iEdita este ficheiro em ~/public_gopher/gophermap. fake NULL 0 """ @@ -327,6 +323,17 @@ def load_patch_irc_module(log: logging.Logger) -> Any: return mod +_IRC_PATCH_SKIP_USERS_CACHE: frozenset[str] | None = None + + +def irc_patch_skip_users(log: logging.Logger) -> frozenset[str]: + """Contas em ``IRC_PATCH_SKIP_USERS`` (sem IRC / sem bind Gemini / fora dos índices raiz).""" + global _IRC_PATCH_SKIP_USERS_CACHE + if _IRC_PATCH_SKIP_USERS_CACHE is None: + _IRC_PATCH_SKIP_USERS_CACHE = load_patch_irc_module(log).IRC_PATCH_SKIP_USERS + return _IRC_PATCH_SKIP_USERS_CACHE + + def resolve_backfill_users( users_json: Path, homes_root: Path, @@ -647,6 +654,112 @@ def _ensure_gemini_fstab_line( log.info("fstab: bind persistido %s -> %s", src_s, mp_s) +def _remove_gemini_fstab_lines_for_mountpoint(mountpoint: Path, log: logging.Logger) -> None: + """Remove todas as linhas ``bind`` do fstab cujo segundo campo é ``mountpoint``.""" + if not FSTAB_PATH.is_file(): + return + try: + text = FSTAB_PATH.read_text(encoding="utf-8", errors="replace") + except OSError as e: + log.warning("ler fstab: %s", e) + return + new_lines: list[str] = [] + removed = False + for line in text.splitlines(keepends=True): + stripped = line.strip() + if stripped.startswith("#") or not stripped: + new_lines.append(line) + continue + m = _GEMINI_BIND_FSTAB_RE.match(stripped) + if m and Path(_unescape_fstab_path(m.group(2))) == mountpoint: + removed = True + continue + new_lines.append(line) + if not removed: + return + new_content = "".join(new_lines) + backup_if_exists(FSTAB_PATH, log, dry_run=False) + FSTAB_PATH.write_text(new_content, encoding="utf-8") + log.info("fstab: removida(s) linha(s) bind para %s", mountpoint) + + +def remove_gemini_bind_mount( + username: str, + *, + dry_run: bool, + log: logging.Logger, +) -> None: + """Desmonta ``/var/gemini/users/<user>``, limpa fstab, symlink ou directório vazio.""" + mountpoint = GEMINI_USERS / username + if dry_run: + log.info("[dry-run] removeria bind Gemini / fstab em %s", mountpoint) + return + if _is_dir_mountpoint(mountpoint): + ru = run_cmd(["umount", str(mountpoint)], dry_run=False, log=log) + if ru is not None and ru.returncode != 0: + log.warning( + "umount %s: %s", + mountpoint, + (ru.stderr or ru.stdout or "").strip(), + ) + _remove_gemini_fstab_lines_for_mountpoint(mountpoint, log) + if mountpoint.is_symlink(): + try: + mountpoint.unlink() + log.info("symlink Gemini removido: %s", mountpoint) + except OSError as e: + log.warning("unlink %s: %s", mountpoint, e) + if mountpoint.is_dir(): + try: + if not any(mountpoint.iterdir()): + mountpoint.rmdir() + log.info("directório Gemini vazio removido: %s", mountpoint) + except OSError as e: + log.warning("%s: %s", mountpoint, e) + + +def build_root_gophermap_text( + hostname: str, + homes_root: Path, + users: list[str], +) -> str: + """Menu raiz com links ``1~user`` só para contas com ``~/public_gopher`` (exclui IRC_PATCH_SKIP).""" + tab = "\t" + lines: list[str] = [ + "!runv.club — Gopher", + f"iBem-vindo ao Gopher em {hostname} — pubnix.{tab}fake{tab}NULL{tab}0", + f"iMembros com espaço público (selector ~utilizador/).{tab}fake{tab}NULL{tab}0", + "#", + ] + for u in sorted(users): + if not (homes_root / u / "public_gopher").is_dir(): + continue + lines.append(f"1~{u}{tab}~{u}/{tab}{hostname}{tab}70") + return "\n".join(lines) + "\n" + + +def build_root_gemini_index_gmi( + hostname: str, + homes_root: Path, + users: list[str], +) -> str: + """Índice Gemtext na raiz do DocBase; mesmos membros que no menu Gopher raiz.""" + lines: list[str] = [ + f"# {hostname} — Gemini", + "", + f"Bem-vindo ao **Gemini** do **{hostname}**.", + "", + "## Capsules dos membros", + "", + ] + for u in sorted(users): + if not (homes_root / u / "public_gopher").is_dir(): + continue + lines.append(f"=> gemini://{hostname}/~{u}/ Capsule ~{u}") + lines.append("") + return "\n".join(lines) + + def ensure_gemini_bind_mount( username: str, homes_root: Path, @@ -658,12 +771,34 @@ def ensure_gemini_bind_mount( """ Expõe ~/public_gemini em /var/gemini/users/<user> com mount --bind + fstab. O Molly Debian recusa symlinks cujo destino fica fora de DocBase (/var/gemini). + Contas em IRC_PATCH_SKIP_USERS não recebem bind; com force remove-se mount/fstab. """ _ = homes_root # API compatível com o backfill (getpwnam fornece a home) try: pw = pwd.getpwnam(username) except KeyError: return + + sk = irc_patch_skip_users(log) + if username in sk: + if dry_run: + log.info("[dry-run] %s em IRC_PATCH_SKIP_USERS — bind Gemini omitido", username) + return + if force: + remove_gemini_bind_mount(username, dry_run=False, log=log) + else: + mp = GEMINI_USERS / username + if _is_dir_mountpoint(mp) or mp.is_symlink(): + log.warning( + "%s está em IRC_PATCH_SKIP_USERS mas há mount ou symlink em %s — " + "use --force para remover", + username, + mp, + ) + else: + log.debug("skip bind Gemini (IRC_PATCH_SKIP_USERS): %s", username) + return + home = Path(pw.pw_dir) target = home / "public_gemini" if not target.is_dir(): @@ -921,7 +1056,9 @@ def validate_final( log_systemd_unit_failed_hint(molly_unit, log) if usernames: - sample = usernames[0] + sk = irc_patch_skip_users(log) + visible = [u for u in usernames if u not in sk] + sample = visible[0] if visible else usernames[0] try: pw = pwd.getpwnam(sample) home = Path(pw.pw_dir) @@ -1019,6 +1156,12 @@ def main(argv: list[str] | None = None) -> int: cert = args.gemini_cert or DEFAULT_LE_CERT key = args.gemini_key or DEFAULT_LE_KEY + try: + backfill_users = resolve_backfill_users(args.users_json, args.homes_root, log) + except (FileNotFoundError, ImportError) as e: + log.error("%s", e) + return 1 + if not args.skip_gemini: ensure_le_tls_readable_for_molly(cert, dry_run=args.dry_run, log=log) @@ -1053,12 +1196,18 @@ def main(argv: list[str] | None = None) -> int: GOPHER_ROOT.mkdir(parents=True, exist_ok=True) os.chmod(GOPHER_ROOT, 0o755) root_map = GOPHER_ROOT / "gophermap" + gmap_body = build_root_gophermap_text( + args.gemini_hostname, + args.homes_root, + backfill_users, + ) if not root_map.exists() or args.force: if root_map.exists() and args.force: backup_if_exists(root_map, log, dry_run=False) - root_map.write_text(DEFAULT_ROOT_GOPHERMAP, encoding="utf-8") + root_map.write_text(gmap_body, encoding="utf-8") os.chmod(root_map, 0o644) - log.info("gophermap raiz: %s", root_map) + n_menu = sum(1 for ln in gmap_body.splitlines() if ln.startswith("1~")) + log.info("gophermap raiz: %s (%d entradas ~user)", root_map, n_menu) if not args.dry_run: GEMINI_ROOT.mkdir(parents=True, exist_ok=True) @@ -1071,6 +1220,24 @@ def main(argv: list[str] | None = None) -> int: except OSError as e: log.warning("chown /var/gemini: %s", e) + if not args.skip_gemini: + gemi_root = GEMINI_ROOT / "index.gmi" + gemi_body = build_root_gemini_index_gmi( + args.gemini_hostname, + args.homes_root, + backfill_users, + ) + if not gemi_root.exists() or args.force: + if gemi_root.exists() and args.force: + backup_if_exists(gemi_root, log, dry_run=False) + gemi_root.write_text(gemi_body, encoding="utf-8") + os.chmod(gemi_root, 0o644) + try: + os.chown(gemi_root, 0, 0) + except OSError as e: + log.warning("chown %s: %s", gemi_root, e) + log.info("index.gmi DocBase raiz: %s", gemi_root) + if not args.skip_gemini: if not cert.is_file() or not key.is_file(): log.error( @@ -1117,13 +1284,8 @@ def main(argv: list[str] | None = None) -> int: skip_firewall=args.skip_firewall, ) - try: - users = resolve_backfill_users(args.users_json, args.homes_root, log) - except (FileNotFoundError, ImportError) as e: - log.error("%s", e) - return 1 if not args.skip_backfill: - for u in users: + for u in backfill_users: ensure_user_public_dirs( u, args.homes_root, @@ -1163,7 +1325,7 @@ def main(argv: list[str] | None = None) -> int: delay_s=1.0, ) - validate_final(users, log, dry_run=args.dry_run) + validate_final(backfill_users, log, dry_run=args.dry_run) log.info("Concluído.") return 0 diff --git a/scripts/create_runv_user.md b/scripts/create_runv_user.md @@ -195,6 +195,14 @@ sudo mkdir -p /var/lib/runv Log padrão: `/var/log/runv-user-provision.log` Metadados: `/var/lib/runv/users.json` +### Landing (`members.json`) + +Após gravar metadados, o script executa por omissão [`site/build_directory.py`](../site/build_directory.md) (via `python3` no repositório ao lado de `site/`) para actualizar **`data/members.json`** no DocumentRoot da landing (padrão **`/var/www/runv.club/html`**), desde que essa pasta exista. Assim a constelação na página reflecte a nova conta **sem cron**. + +- **`--no-refresh-landing-members`** — não chama `build_directory`. +- **`--landing-document-root PATH`** — outro DocumentRoot (default: `/var/www/runv.club/html`). +- **`--members-homes-root PATH`** — passa `--homes-root` ao `build_directory` (ex. `/home` para `homepage_mtime`). + ## Opções úteis (CLI) - `--dry-run` — valida tudo e mostra o plano sem criar usuário @@ -208,6 +216,7 @@ Metadados: `/var/lib/runv/users.json` - `--quota-soft-mb`, `--quota-hard-mb`, `--quota-inode-soft`, `--quota-inode-hard` — limites (MiB para blocos) - `--metadata-file`, `--lock-file`, `--log-file` — caminhos alternativos (ex.: testes em VM) - `--base-url` — URL base no resumo (padrão `http://runv.club`) +- `--landing-document-root`, `--no-refresh-landing-members`, `--members-homes-root` — ver secção *Landing* acima ## Metadados JSON (campos de quota) diff --git a/scripts/docs/alt_protocols.md b/scripts/docs/alt_protocols.md @@ -28,7 +28,7 @@ Apache (`mod_userdir`), **gophernicus** e **molly-brown** precisam de **execuç - **ACL (POSIX):** se `ls -l` mostrar **`+`** nos modos, há entradas **`getfacl`** além do `chmod`. Uma **mask** restritiva ou ausência de leitura efectiva para «other» / utilizador do serviço pode bloquear o Apache, gophernicus ou Molly mesmo com **`644`/`755`** aparentes. Diagnóstico: `getfacl ~/public_gemini/index.gmi` (e directórios no caminho). - **Novas contas:** [`create_runv_user.py`](../admin/create_runv_user.py) aplica **`755`** na home em `apply_runv_permissions`. -- **Backfill:** a partir do **v0.07**, [`setup_alt_protocols.py`](../admin/setup_alt_protocols.py) repõe a home do utilizador para **`755`** quando o modo actual é outro (com registo em log). O **v0.08** corrige a detecção de caminhos Let's Encrypt quando `live`/`archive` são **symlinks**. O **v0.09** introduziu redirects Molly baseados numa leitura incorrecta do README upstream. O **v0.11** corrige **`[TempRedirects]`** para **`/~/user…` → `/~user…`** (alinhado ao `resolvePath` em Go). O **v0.12** documenta **ACL POSIX** na travessia e alarga o **WARNING** do `test -r` do `index.gmi` com indicação a `getfacl` quando `ls` mostra `+`. O **v0.13** substitui **symlinks** Gemini por **bind mounts** + **`fstab`** (compatível com o Molly Debian). Validação **`test -r`** do `gophermap` com o utilizador do serviço gophernicus mantém-se. +- **Backfill:** a partir do **v0.07**, [`setup_alt_protocols.py`](../admin/setup_alt_protocols.py) repõe a home do utilizador para **`755`** quando o modo actual é outro (com registo em log). O **v0.08** corrige a detecção de caminhos Let's Encrypt quando `live`/`archive` são **symlinks**. O **v0.09** introduziu redirects Molly baseados numa leitura incorrecta do README upstream. O **v0.11** corrige **`[TempRedirects]`** para **`/~/user…` → `/~user…`** (alinhado ao `resolvePath` em Go). O **v0.12** documenta **ACL POSIX** na travessia e alarga o **WARNING** do `test -r` do `index.gmi` com indicação a `getfacl` quando `ls` mostra `+`. O **v0.13** substitui **symlinks** Gemini por **bind mounts** + **`fstab`** (compatível com o Molly Debian). Validação **`test -r`** do `gophermap` com o utilizador do serviço gophernicus mantém-se. O **v0.14** gera **`/var/gopher/gophermap`** (menu com **`1~user`**) e **`/var/gemini/index.gmi`** na raiz do DocBase para utilizadores com **`~/public_gopher`**, alinhando **`IRC_PATCH_SKIP_USERS`** (incl. **`entre`**, **`pmurad-admin`**) ao bind Gemini e à limpeza com **`--force`**. - **Conflito:** [`patches/patch_permissions.py`](../../patches/patch_permissions.py) pode aplicar **`chmod 700`** em cada `/home/<user>` por política de privacidade — isso **quebra** a hospedagem em `public_*` até voltar a alinhar permissões (provisionamento ou `chmod` manual). ## Let's Encrypt e chave TLS (v0.07+; symlinks v0.08+) @@ -53,8 +53,8 @@ Se o grupo **`ssl-cert`** não existir no sistema, o script regista **WARNING** No fim da execução, além de verificar ficheiros e **bind mount** Gemini **como root**: -- Se **`gophernicus.socket`** estiver **`active`**, o script tenta **`runuser -u <User=do_unit> -- test -r`** no **`gophermap`** da primeira conta da lista (o `User=` lê-se de `/lib/systemd/system/gophernicus@.service`; fallback **`gophernicus`**). Falha → **WARNING** (home `755`/`o+x`, `public_gopher` `755`, `gophermap` `644`). -- Se **`molly-brown@`** estiver **`active`**, tenta **`runuser -u www-data -- test -r`** no **`index.gmi`** da amostra (heurística: o unit Debian usa **`molly-brown`** dinâmico; ficheiros **`644`** e pastas **`755`** devem permitir leitura a «others» — ou **ACL** compatível; ver nota **ACL** na secção de travessia). Falha → **WARNING** (`public_gemini` `755`, `index.gmi` `644`, bind `/var/gemini/users/<user>`). Se a amostra ainda for **symlink**, regista **WARNING** de migração (**`--force`**). +- Se **`gophernicus.socket`** estiver **`active`**, o script tenta **`runuser -u <User=do_unit> -- test -r`** no **`gophermap`** da primeira conta da lista **fora** de **`IRC_PATCH_SKIP_USERS`** (o `User=` lê-se de `/lib/systemd/system/gophernicus@.service`; fallback **`gophernicus`**). Falha → **WARNING** (home `755`/`o+x`, `public_gopher` `755`, `gophermap` `644`). +- Se **`molly-brown@`** estiver **`active`**, tenta **`runuser -u www-data -- test -r`** no **`index.gmi`** da mesma amostra (heurística: o unit Debian usa **`molly-brown`** dinâmico; ficheiros **`644`** e pastas **`755`** devem permitir leitura a «others» — ou **ACL** compatível; ver nota **ACL** na secção de travessia). Falha → **WARNING** (`public_gemini` `755`, `index.gmi` `644`, bind `/var/gemini/users/<user>`). Se a amostra ainda for **symlink**, regista **WARNING** de migração (**`--force`**). Em **`--dry-run`**, só regista os comandos. Sem **`runuser`** (util-linux), estes passos são omitidos. @@ -142,7 +142,7 @@ sudo python3 scripts/admin/setup_alt_protocols.py --verbose |------|--------| | `--dry-run` | Simula; não grava (validação de root ignorada em alguns passos só se documentado). | | `--verbose` | Log detalhado. | -| `--force` | Sobrescreve configs de sistema (com backup com timestamp) e ficheiros modelo no backfill (exceto **`~/public_gemini/index.gmi`** se já existir). Necessário para **regravar** `/etc/molly-brown/runv.club.conf` (incl. **`[TempRedirects]`** v0.11: **`/~/…` → `/~…`**) e remover o drop-in obsoleto **`50-runv-logs.conf`** (v0.05) ao migrar logs para `/var/lib/molly-brown/`. | +| `--force` | Sobrescreve configs de sistema (com backup com timestamp) e ficheiros modelo no backfill (exceto **`~/public_gemini/index.gmi`** se já existir). Necessário para **regravar** `/etc/molly-brown/runv.club.conf` (incl. **`[TempRedirects]`** v0.11: **`/~/…` → `/~…`**) e remover o drop-in obsoleto **`50-runv-logs.conf`** (v0.05) ao migrar logs para `/var/lib/molly-brown/`. **v0.14+:** também **regrava** o **`gophermap` raiz** e **`/var/gemini/index.gmi`**; para utilizadores em **`IRC_PATCH_SKIP_USERS`**, remove **bind mount** + linha **`fstab`** + legado em **`/var/gemini/users/<user>`** se ainda existirem. | | `--skip-install` | Não corre `apt-get`. | | `--skip-gopher` / `--skip-gemini` | Ignora pacote, config e serviço desse protocolo. | | `--skip-firewall` | Não altera UFW. | @@ -161,11 +161,17 @@ A lista de contas para criar `~/public_gopher`, `~/public_gemini` e **bind mount 1. Usernames em **`users.json`** (lista de objetos com campo `username`), quando o ficheiro existe e o JSON é válido; e 2. Nomes em **`--homes-root`** com UID ≥ 1000 e entrada em `passwd`. -Depois aplicam-se as mesmas exclusões que em **`patches/patch_irc.py`** (`IRC_PATCH_SKIP_USERS` — contas de sistema, `entre`, etc.; **não** exclui `pmurad-admin` por defeito). Para só pastas/bind Gemini sem reinstalar serviços, pode usar **`patches/yetgg.py`**. +Depois aplicam-se as mesmas exclusões que em **`patches/patch_irc.py`** (**`IRC_PATCH_SKIP_USERS`** — contas de sistema, **`entre`**, **`pmurad-admin`**, etc.; **v0.14+**). Esse conjunto governa também a **lista pública** Gopher/Gemini (menu raiz e **`index.gmi`** DocBase) e impede **bind mount** para esses nomes. Para só pastas/bind Gemini sem reinstalar serviços, pode usar **`patches/yetgg.py`**. + +## Índice raiz Gopher e Gemini (**v0.14+**) + +- **`/var/gopher/gophermap`:** menu explícito (linhas **`i`** de boas-vindas + **`1~username ~username/ host 70`**) só para utilizadores do backfill que tenham **`~/public_gopher`** como directório. Não usa a directiva global **`~`** do gophernicus, para poder **respeitar** **`IRC_PATCH_SKIP_USERS`**. +- **`/var/gemini/index.gmi`:** página Gemtext na raiz do DocBase com links **`=> gemini://<hostname>/~user/`** para o **mesmo** conjunto (backfill + `public_gopher`). Quem está em **`IRC_PATCH_SKIP_USERS`** **não** recebe bind em **`/var/gemini/users/`** e **não** aparece nesse menu. +- **Limpeza:** após remover um nome de **`IRC_PATCH_SKIP_USERS`** ou corrigir mounts antigos, correr **`setup_alt_protocols.py --verbose --force`**. Verificar: `findmnt /var/gemini/users/<user>` vazio para contas excluídas. ## Relação com outros scripts -- **`create_runv_user.py`**: após `public_html`, cria `public_gopher`, `public_gemini` e aplica **bind mount** em `/var/gemini/users/<user>` (via `setup_alt_protocols`). +- **`create_runv_user.py`**: após `public_html`, cria `public_gopher`, `public_gemini` e aplica **bind mount** em `/var/gemini/users/<user>` (via `setup_alt_protocols`) **excepto** se o username estiver em **`IRC_PATCH_SKIP_USERS`**. - **`del-user.py`**: **umount**, remove linha **`fstab`** de bind e remove symlink legado ou directório vazio em `/var/gemini/users/<user>`. - **`tools/tools.py`**: copia modelos para `/etc/skel` (só contas futuras). diff --git a/site/README.md b/site/README.md @@ -1,6 +1,6 @@ # Site público (landing runv.club) -Conteúdo estático inspirado em [tilde.town](https://tilde.town) e [tilde.club](https://tilde.club): landing com constelação de links por membro (`members.json`), rotas **`/news/`** e **`/wiki/`** (placeholders por agora), e **`/junte-se/`** — guia de chave SSH (Linux, macOS, Windows) e acesso a **`entre@runv.club`**. +Conteúdo estático inspirado em [tilde.town](https://tilde.town) e [tilde.club](https://tilde.club): landing com constelação de links por membro (`members.json`), **`/news/`** (lista dinâmica via `data/news.json` + RSS), **`/wiki/`**, e **`/junte-se/`** — guia de chave SSH (Linux, macOS, Windows) e acesso a **`entre@runv.club`**. ## O que significa “membro” na página @@ -16,11 +16,11 @@ Conteúdo estático inspirado em [tilde.town](https://tilde.town) e [tilde.club] - **HTML/CSS/JS** estáticos em `public/`. - **Rodapé:** em todas as páginas HTML em `public/` deve constar o **contato** da administração — `admin@runv.club` (bloco `<footer class="site-footer">` como em `index.html`). -- **Geração de dados**: Python 3 (stdlib) — adequado a **cron** no servidor; sem CGI. +- **Geração de dados**: Python 3 (stdlib); `members.json` é regenerado por `create_runv_user.py` e por `genlanding.py` (sem cron); sem CGI. ## Gerar `public/data/members.json` -**No Git**, `public/data/members.json` fica **`[]`**: a landing não deve mostrar utilizadores fictícios. Quem aparece na constelação vem **só** de `build_directory.py` a ler **`/var/lib/runv/users.json`** (produção, via cron) ou, em desenvolvimento, uma cópia de teste com **`--users-json site/example-users.json`** — sem commit do JSON gerado como se fosse produção. Se **`users.json` ainda não existir** no servidor, o `build_directory.py` assume **zero membros** (aviso em stderr) em vez de falhar. +**No Git**, `public/data/members.json` fica **`[]`**: a landing não deve mostrar utilizadores fictícios. Quem aparece na constelação vem **só** de `build_directory.py` a ler **`/var/lib/runv/users.json`**, invocado automaticamente após **`create_runv_user.py`** e após **`genlanding.py`** (podes ainda correr o script à mão). Em desenvolvimento, usa uma cópia de teste com **`--users-json site/example-users.json`**. Se **`users.json` ainda não existir** no servidor, o `build_directory.py` assume **zero membros** (aviso em stderr) em vez de falhar. Manual detalhado do script: **[`build_directory.md`](build_directory.md)**. @@ -47,6 +47,14 @@ Dry-run: python3 site/build_directory.py --users-json site/example-users.json --dry-run ``` +## Publicar notícias (`news/publish_news.py`) + +Coloque um `.md` em **`site/news/`** (linha 1 = título; resto = corpo), execute `python3 site/news/publish_news.py`. Isto gera **`public/news/data/news.json`**, **`public/news/feed.rss`** e actualiza **`lastmod`** de `/news/` no `sitemap.xml`. O `.md` é removido após publicar. + +`news.json` **não** é versionado (`.gitignore`) para não conflitar com `git pull` no servidor. Manual: **[`news/README.md`](news/README.md)**. + +Depois, volte a copiar `public/` para o `DocumentRoot` (`genlanding.py` ou deploy manual). + ## Configurar Apache (`genlanding.py`) Para **gerar o VirtualHost**, **ativar** `mod_userdir` / `mod_rewrite`, copiar **`public/`** para o `DocumentRoot` e (opcional) rodar **Certbot**, use o script **[`genlanding.py`](genlanding.py)**. Manual completo: **[`genlanding.md`](genlanding.md)**. @@ -74,13 +82,7 @@ Alternativa ao genlanding: copiar o conteúdo de **`public/`** para o `DocumentR **Certifique-se** de que `mod_userdir` continua a servir `~/public_html` para cada **usuário**; a landing é só a **raiz** do site. -### Cron (exemplo) - -```cron -*/15 * * * * root python3 /opt/runv-server/site/build_directory.py --users-json /var/lib/runv/users.json --homes-root /home -o /var/www/runv/html/data/members.json -``` - -(Ajuste os caminhos.) +**`members.json`:** não é necessário cron. Com **`genlanding.py`**, a cópia de `public/` é seguida por `build_directory.py` no `DocumentRoot` (use `--no-refresh-members` para omitir). Com **`create_runv_user.py`**, após criar a conta o mesmo ficheiro é regenerado por omissão (`--no-refresh-landing-members` para omitir). ## Arquivos @@ -88,10 +90,15 @@ Alternativa ao genlanding: copiar o conteúdo de **`public/`** para o `DocumentR |---------|--------| | `genlanding.py` | Configura Apache (vhost, cópia de `public/`, opcional Certbot); ver `genlanding.md` | | `build_directory.py` | Gera `members.json` público; ver **`build_directory.md`** | -| `build_directory.md` | Como usar `build_directory.py` (flags, cron, exemplos) | +| `build_directory.md` | Como usar `build_directory.py` (flags, integração com genlanding/create_runv_user) | | `public/index.html` | Landing | +| `public/faq/index.html` | FAQ (texto estático; link discreto no rodapé) | | `public/junte-se/index.html` | Pedir entrada: gerar chave SSH e `ssh entre@runv.club` | | `public/assets/style.css` | Estilos | | `public/assets/app.js` | Constelação, lista, filtro, shuffle | +| `public/assets/news-page.js` | Lista de notícias a partir de `news/data/news.json` | +| `news/publish_news.py` | Ingere `.md` e gera `news.json`, RSS e `sitemap` | +| `public/news/data/news.json` | Gerado localmente / no servidor (ignorado pelo git) | +| `public/news/feed.rss` | Feed RSS (stub no repo; regerado pelo script) | | `public/data/members.json` | Dados públicos (regenerado; exemplo no repo) | | `example-users.json` | Amostra para testes locais | diff --git a/site/build_directory.md b/site/build_directory.md @@ -3,7 +3,7 @@ O script [`build_directory.py`](build_directory.py) lê o ficheiro interno **`users.json`** (criado pelo [`create_runv_user.py`](../scripts/create_runv_user.md)) e gera um JSON **público** consumido pelo JavaScript da landing (`public/assets/app.js`): posiciona os **pontos** (links para `/~utilizador/`) com base em `username`, `since` e `path`. - **Python 3**, só biblioteca padrão (sem PyPI). -- **Não** é um servidor web: corre na linha de comando ou via **cron**. +- **Não** é um servidor web: corre na linha de comando. Em produção é invocado automaticamente por [`create_runv_user.py`](../scripts/create_runv_user.md) (após criar conta) e por [`genlanding.py`](genlanding.md) (após copiar `public/` para o DocumentRoot), salvo flags para desactivar. Visão geral do `site/`: [README.md](README.md). @@ -104,24 +104,23 @@ A lista aparece na landing; não haverá `homepage_mtime` (o JS deve tolerar cam | `Formato inválido: esperada lista JSON` | O ficheiro não é um array JSON no topo | | Permissão negada ao gravar `-o` | Corre com `sudo` ou escolhe um `-o` onde o teu utilizador possa escrever | | `homepage_mtime` nunca aparece | Falta `--homes-root` ou não existe `~/public_html/index.html` legível para esse user | -| «Escritos N membros» mas a página não mostra pontos | Gravaste em `site/public/data/` no repo; o **site público** usa o **DocumentRoot** do Apache (ex. `/var/www/runv.club/html/`). Usa `-o /var/www/runv.club/html/data/members.json` ou `sudo cp …` para lá, ou volta a correr `genlanding.py` depois de actualizar `members.json` na árvore que ele copia. | +| «Escritos N membros» mas a página não mostra pontos | Gravaste em `site/public/data/` no repo; o **site público** usa o **DocumentRoot** do Apache (ex. `/var/www/runv.club/html/`). Usa `-o` para esse path, ou corre `genlanding.py` (regenera `members.json` por omissão após a cópia). | -## Cron (exemplo) +## Fluxo em produção (sem cron) -Regenerar a cada 15 minutos no servidor (caminhos de exemplo): +Não é necessário agendar `build_directory.py` no cron. A lista pública actualiza-se quando: -```cron -*/15 * * * * root python3 /opt/runv-server/site/build_directory.py --users-json /var/lib/runv/users.json --homes-root /home -o /var/www/runv.club/html/data/members.json -``` +1. **`create_runv_user.py`** cria uma conta — por omissão chama `build_directory.py` com `-o <DocumentRoot>/data/members.json` se `--landing-document-root` existir no disco (padrão `/var/www/runv.club/html`). Use `--no-refresh-landing-members` para omitir. +2. **`genlanding.py`** copia a landing — por omissão volta a executar `build_directory.py` no mesmo DocumentRoot. Use `--no-refresh-members` para omitir. -Garante que o path do `python3`, do script e do `-o` coincidem com a tua instalação. +Podes continuar a correr o script **manualmente** com os exemplos desta página (útil para reparos ou ambientes sem esses passos). ## Relação com outros ficheiros | Ferramenta | Papel | |------------|--------| -| [`create_runv_user.py`](../scripts/create_runv_user.md) | Mantém `/var/lib/runv/users.json` | -| [`genlanding.py`](genlanding.md) | Copia `public/` para o Apache; o **cron** do `build_directory.py` deve escrever `members.json` **dentro** desse DocumentRoot | +| [`create_runv_user.py`](../scripts/create_runv_user.md) | Mantém `/var/lib/runv/users.json`; opcionalmente regenera `members.json` na landing | +| [`genlanding.py`](genlanding.md) | Copia `public/` para o Apache; por omissão regenera `data/members.json` a partir de `users.json` | | `public/assets/app.js` | Faz `fetch` a `data/members.json` (caminho relativo à página) | Depois de alterar `members.json` no servidor, não é obrigatório recarregar o Apache — é ficheiro estático servido como qualquer outro. diff --git a/site/genlanding.md b/site/genlanding.md @@ -8,6 +8,14 @@ Script em [`genlanding.py`](genlanding.py) (Python 3, stdlib) que configura o ** Não substitui o manual de [`scripts/docs/2 - server setup.md`](../scripts/docs/2%20-%20server%20setup.md) para aprender permissões e diagnóstico; automatiza o caminho habitual após DNS e pacotes base. +**SEO:** canonical, Open Graph, Twitter Card, JSON-LD, `robots.txt` e `sitemap.xml` vivem em [`public/`](public/) (sobretudo [`public/index.html`](public/index.html)); o `genlanding.py` apenas copia essa árvore para o `DocumentRoot`. + +**Notícias:** após correr [`news/publish_news.py`](news/publish_news.py) (gera `public/news/data/news.json` e `feed.rss`), execute de novo o `genlanding.py` (ou copie `public/`) para o servidor servir os ficheiros actualizados. + +**FAQ:** conteúdo em [`public/faq/index.html`](public/faq/index.html); o deploy copia `public/` inteiro, logo o FAQ segue automaticamente. Link discreto no rodapé das páginas. + +**Wiki:** ficheiros-fonte em [`wiki/*.txt`](wiki/) (`NN_slug.txt`). Em **local**, antes do deploy, gere o HTML em [`public/wiki/`](public/wiki/) com `python3 site/wiki/build_wiki.py` (actualiza também as entradas da wiki em [`public/sitemap.xml`](public/sitemap.xml) entre os comentários `<!-- wiki:gerado -->`). O `genlanding.py` **só copia** `site/public/` — **não** executa este gerador no servidor; ficheiros em `site/wiki/` (excepto o que estiver dentro de `public/`) **não** entram no `DocumentRoot`. + ## Pré-requisitos - **Debian** com `apache2` instalado (recomendado: [`scripts/admin/starthere.py`](../scripts/admin/starthere.py) antes). @@ -40,6 +48,9 @@ python3 site/genlanding.py --dry-run | `--keep-default-site` | Mantém `000-default.conf` activo (**produção** e **`--dev`**). Com `000-default` activo, pedidos por **IP** não casam com `ServerName` e continuam a mostrar a página Debian; ver secção abaixo. | | `--certbot` | Depois de configurar HTTP, executa `certbot --apache -d <domínio> -d www.<domínio>`. **Incompatível com `--dev`.** | | `--dry-run` | Mostra o VirtualHost e comandos; não exige root. | +| `--no-refresh-members` | Não executar `build_directory.py` após copiar `public/` (omitir `data/members.json`). | +| `--members-users-json PATH` | Fonte para `build_directory` (default: `/var/lib/runv/users.json`). | +| `--members-homes-root PATH` | Opcional: `--homes-root` para `build_directory` (ex. `/home`). | ## Pedidos por IP vs `ServerName` @@ -72,19 +83,18 @@ Se `curl http://runv.local/` não devolver nada na VM, confirma que **`runv.loca 1. `starthere.py` — pacotes, Apache a correr, quotas, etc. 2. `genlanding.py` — VirtualHost + cópia da landing. 3. Opcional: `genlanding.py --certbot` **numa segunda execução** (ou a primeira já com `--certbot` se tudo estiver pronto), **depois** de confirmar HTTP no domínio. -4. **Cron** para [`build_directory.py`](build_directory.py) gerar `members.json` no `DocumentRoot` (ver [README.md](README.md)). +4. Lista de membros: após o passo 2, o script **já** corre [`build_directory.py`](build_directory.py) por omissão (salvo `--no-refresh-members`). Novas contas também disparam o mesmo via [`create_runv_user.py`](../scripts/create_runv_user.md). ## Relação com `build_directory.py` -- `genlanding.py` **copia** o conteúdo actual de `public/` (incluindo `data/members.json` se existir). -- A lista de membros actualizada vem do **cron** que corre `build_directory.py` com `-o` apontando para - `.../html/data/members.json` (o mesmo `DocumentRoot` que usaste no genlanding). +- `genlanding.py` **copia** o conteúdo actual de `public/` e, por omissão, executa `site/build_directory.py` com `-o` em `<DocumentRoot>/data/members.json` e `--users-json` em `/var/lib/runv/users.json`, para a constelação reflectir contas reais **sem** depender de cron. +- **`--no-refresh-members`** omite esse passo (útil se `users.json` ainda não existir e quiseres evitar o aviso, ou fluxos especiais). ### Lista pública (só utilizadores reais) - **`public/data/members.json`** no repositório deve ser **`[]`** (placeholder). **Não** versionar nomes fictícios como membros da comunidade; a única fonte de verdade para quem aparece no site é **`/var/lib/runv/users.json`**, filtrada por `build_directory.py`. - **`site/example-users.json`** existe só para desenvolvimento / testes locais com `build_directory.py --users-json`, não para ship em produção como se fossem contas reais. -- **Atenção:** cada execução de `genlanding.py` **substitui** o `DocumentRoot` pela cópia de `public/`; isso repõe `members.json` para o que está no repo (tipicamente `[]`). Depois de um deploy com `genlanding.py`, volta a correr **`build_directory.py`** (ou espera o cron) para repor a lista gerada a partir de `users.json`. +- **Deploy:** cada `genlanding.py` substitui o `DocumentRoot`; o passo integrado de `build_directory` **repõe** `members.json` a partir de `users.json`, evitando ficar preso ao `[]` do repo. ## O que o script não faz diff --git a/site/genlanding.py b/site/genlanding.py @@ -3,10 +3,13 @@ Configura o Apache (Debian) para servir a landing runv.club: VirtualHost, mod_userdir + mod_rewrite, cópia de site/public para DocumentRoot, redirect www → apex em HTTP. Produção ou modo --dev para testes locais. +Metadados SEO: editar site/public/. FAQ estático: public/faq/ (copiado com o resto). +Notícias: site/news/publish_news.py gera public/news/data/news.json e feed.rss — +depois volte a correr este script para copiar. Executar como root (excepto --dry-run). Apenas biblioteca padrão Python 3. -Versão 0.02 — runv.club +Versão 0.03 — runv.club """ from __future__ import annotations @@ -22,13 +25,14 @@ import sys from pathlib import Path from typing import Final -VERSION: Final[str] = "0.02" +VERSION: Final[str] = "0.03" EXIT_OK: Final[int] = 0 EXIT_USAGE: Final[int] = 1 EXIT_ERROR: Final[int] = 2 SCRIPT_DIR = Path(__file__).resolve().parent DEFAULT_SOURCE: Final[Path] = SCRIPT_DIR / "public" +DEFAULT_MEMBERS_USERS_JSON: Final[Path] = Path("/var/lib/runv/users.json") PROD_DOMAIN: Final[str] = "runv.club" PROD_DOCUMENT_ROOT: Final[Path] = Path("/var/www/runv.club/html") @@ -141,6 +145,50 @@ def copy_landing(source: Path, dest: Path, *, dry_run: bool) -> None: shutil.copytree(source, dest) +def refresh_members_json_in_document_root( + document_root: Path, + *, + users_json: Path, + homes_root: Path | None, + dry_run: bool, +) -> None: + """Regenera data/members.json no DocumentRoot após copiar site/public (stdlib).""" + if dry_run: + print( + " [dry-run] regeneraria data/members.json " + f"({users_json} → {document_root / 'data' / 'members.json'})", + ) + return + script = SCRIPT_DIR / "build_directory.py" + if not script.is_file(): + eprint(f"Aviso: {script} não encontrado; members.json não regenerado.") + return + out = document_root / "data" / "members.json" + cmd = [ + sys.executable, + str(script), + "--users-json", + str(users_json), + "-o", + str(out), + ] + if homes_root is not None: + cmd.extend(["--homes-root", str(homes_root)]) + print(f" $ {' '.join(cmd)}") + r = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + if r.returncode != 0: + tail = (r.stderr or r.stdout or "").strip() + eprint( + f"Aviso: build_directory.py terminou com código {r.returncode}; " + f"members.json pode estar desactualizado. {tail[:800]}" + ) + else: + print(f" [ok] members.json em {out}") + if r.stderr.strip(): + for line in r.stderr.strip().splitlines()[:5]: + print(f" {line}") + + def chown_www_data(path: Path, *, dry_run: bool) -> None: if dry_run: print(f" [dry-run] chown -R www-data:www-data {path}") @@ -195,6 +243,23 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace: action="store_true", help="não desactiva 000-default.conf (produção e --dev: mantém página Debian; pedidos por IP não casam com ServerName)", ) + p.add_argument( + "--no-refresh-members", + action="store_true", + help="não executar site/build_directory.py após copiar public/ (omitir data/members.json)", + ) + p.add_argument( + "--members-users-json", + type=Path, + default=DEFAULT_MEMBERS_USERS_JSON, + help=f"fonte para build_directory.py (default: {DEFAULT_MEMBERS_USERS_JSON})", + ) + p.add_argument( + "--members-homes-root", + type=Path, + default=None, + help="opcional: --homes-root para build_directory.py (ex. /home)", + ) p.add_argument("--version", action="version", version=f"%(prog)s {VERSION} — runv.club") return p.parse_args(argv) @@ -263,6 +328,16 @@ def main(argv: list[str] | None = None) -> int: if not args.dry_run: chown_www_data(document_root, dry_run=False) + if not args.no_refresh_members: + refresh_members_json_in_document_root( + document_root, + users_json=args.members_users_json, + homes_root=args.members_homes_root.resolve() + if args.members_homes_root + else None, + dry_run=args.dry_run, + ) + if args.dry_run: print(f" [dry-run] escreveria {conf_path}") else: @@ -323,9 +398,9 @@ def main(argv: list[str] | None = None) -> int: if args.dev: print(" - Em /etc/hosts (cliente ou VM): 127.0.0.1 runv.local www.runv.local") print( - " - Membros na constelação: só contas reais (provisionadas → /var/lib/runv/users.json). " - "Gerar lista pública com site/build_directory.py no DocumentRoot (cron; ver site/README.md). " - "O public/data/members.json no repo fica [] até esse passo." + " - Membros na constelação: regenerado com build_directory após esta cópia " + "(fonte: /var/lib/runv/users.json). Novas contas: create_runv_user.py também actualiza " + "members.json se o DocumentRoot existir. Use --no-refresh-members para omitir." ) return EXIT_OK diff --git a/site/news/README.md b/site/news/README.md @@ -0,0 +1,25 @@ +# Publicar notícias (`publish_news.py`) + +1. Crie um ficheiro **`.md`** nesta pasta (`site/news/`) com **qualquer nome** (excepto `_*`, que são ignorados). O ficheiro **`README.md`** nunca é publicado. +2. **Linha 1:** título da notícia. +3. **Linhas seguintes:** corpo em Markdown leve: + - `**negrito**` + - `*itálico*` ou `_itálico_` + - `++sublinhado++` +4. No servidor (ou no clone), a partir da raiz do repositório: + + ```bash + python3 site/news/publish_news.py --verbose + ``` + +5. O script: + - acrescenta a notícia a `site/public/news/data/news.json` (data **DD-MM-AAAA**, fuso `America/Sao_Paulo` quando o pacote **tzdata** está disponível; caso contrário usa **UTC−3** fixo); + - regera `site/public/news/feed.rss`; + - actualiza `lastmod` da URL `/news/` em `site/public/sitemap.xml`; + - **apaga** o `.md` processado. + +**Git:** `news.json` está em `.gitignore` para evitar conflitos em `git pull` no servidor. No repositório há só `site/public/news/data/news.json.example` (lista vazia). Em produção, após o primeiro `publish_news.py`, copie o `DocumentRoot` com `genlanding.py` ou mantenha `news.json` só no servidor. + +**Windows:** instale `tzdata` (`pip install tzdata`) para o fuso `America/Sao_Paulo` exacto. + +**Modelo:** veja `_exemplo.md` (não é publicado). diff --git a/site/news/publish_news.py b/site/news/publish_news.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +""" +Lê ficheiros ``*.md`` nesta pasta (``site/news/``), gera entradas em +``site/public/news/data/news.json``, ``site/public/news/feed.rss`` e +actualiza ``lastmod`` da entrada ``/news/`` em ``site/public/sitemap.xml``. + +Formato de cada ``.md``: + - Linha 1: título + - Linhas seguintes: corpo (Markdown leve: **negrito**, *itálico*, _itálico_, ++sublinhado++) + +Os ``.md`` processados são **apagados**. Ficheiros cujo nome começa por ``_`` são ignorados +(ex.: ``_exemplo.md`` para documentação). + +Não versionar notícias no HTML: os dados ficam em ``news.json`` (tipicamente ignorado pelo git +no servidor após gerar conteúdo local). + +Uso:: + python3 site/news/publish_news.py [--dry-run] [--verbose] +""" + +from __future__ import annotations + +import argparse +import html +import json +import re +import sys +import uuid +from xml.sax.saxutils import escape as xml_escape +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any, Final +from zoneinfo import ZoneInfo + +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_SITE = SCRIPT_DIR.parent +PUBLIC_NEWS = REPO_SITE / "public" / "news" +DATA_DIR = PUBLIC_NEWS / "data" +JSON_PATH = DATA_DIR / "news.json" +RSS_PATH = PUBLIC_NEWS / "feed.rss" +SITEMAP_PATH = REPO_SITE / "public" / "sitemap.xml" + +TZ_BR: Final[str] = "America/Sao_Paulo" +# Brasil sem DST: fallback se ``tzdata`` não estiver instalado (ex.: Windows minimal). +BR_FALLBACK_TZ = timezone(timedelta(hours=-3)) +SITE_URL: Final[str] = "https://runv.club" + + +def _apply_underline(s: str) -> str: + parts = s.split("++") + out: list[str] = [] + for i, p in enumerate(parts): + if i % 2 == 0: + out.append(_apply_italic_underscore(p)) + else: + out.append("<u>" + html.escape(p) + "</u>") + return "".join(out) + + +def _apply_italic_underscore(s: str) -> str: + parts = re.split(r"(?<!_)_([^_\n]+)_(?!_)", s) + out: list[str] = [] + for i, p in enumerate(parts): + if i % 2 == 0: + out.append(_apply_italic_star(p)) + else: + out.append("<em>" + html.escape(p) + "</em>") + return "".join(out) + + +def _apply_italic_star(s: str) -> str: + """Itálico com *simples* (não **).""" + result: list[str] = [] + i = 0 + n = len(s) + while i < n: + if s[i] == "*": + j = i + 1 + while j < n and s[j] != "*": + j += 1 + if j < n and s[j] == "*" and j > i + 1: + inner = s[i + 1 : j] + result.append("<em>" + html.escape(inner) + "</em>") + i = j + 1 + continue + result.append(html.escape(s[i])) + i += 1 + return "".join(result) + + +def _apply_bold(s: str) -> str: + parts = s.split("**") + out: list[str] = [] + for i, p in enumerate(parts): + if i % 2 == 0: + out.append(_apply_underline(p)) + else: + out.append("<strong>" + html.escape(p) + "</strong>") + return "".join(out) + + +def markdown_body_to_html(body: str) -> str: + body = body.replace("\r\n", "\n").strip() + if not body: + return "" + blocks = re.split(r"\n\s*\n+", body) + paras: list[str] = [] + for block in blocks: + lines = block.split("\n") + inner = "<br>\n".join(_apply_bold(line) for line in lines) + paras.append(f"<p>{inner}</p>") + return "\n".join(paras) + + +def parse_md_file(path: Path) -> tuple[str, str]: + raw = path.read_text(encoding="utf-8") + lines = raw.splitlines() + if not lines: + raise ValueError(f"{path.name}: ficheiro vazio") + title = lines[0].strip() + if not title: + raise ValueError(f"{path.name}: primeira linha (título) vazia") + body = "\n".join(lines[1:]).lstrip("\n") + return title, body + + +def load_articles() -> list[dict[str, Any]]: + if not JSON_PATH.is_file(): + return [] + data = json.loads(JSON_PATH.read_text(encoding="utf-8")) + arts = data.get("articles") + if not isinstance(arts, list): + return [] + return arts + + +def save_articles(articles: list[dict[str, Any]]) -> None: + DATA_DIR.mkdir(parents=True, exist_ok=True) + JSON_PATH.write_text( + json.dumps({"articles": articles}, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + + +def br_date_display(now: datetime) -> str: + return now.strftime("%d-%m-%Y") + + +def rfc822_date(now: datetime) -> str: + """RFC 822 / RSS pubDate (locale inglês para dia da semana).""" + return now.strftime("%a, %d %b %Y %H:%M:%S %z") + + +def w3c_date(now: datetime) -> str: + if now.tzinfo is None: + now = now.replace(tzinfo=timezone.utc) + return now.isoformat(timespec="seconds") + + +def build_rss(articles: list[dict[str, Any]], now: datetime) -> str: + """RSS 2.0; descriptions em CDATA com HTML seguro gerado pelo script.""" + channel_parts = [ + '<?xml version="1.0" encoding="UTF-8"?>', + '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">', + "<channel>", + f"<title>Notícias — runv.club</title>", + f"<link>{SITE_URL}/news/</link>", + "<description>Comunicados e atualizações da comunidade runv.club</description>", + f"<language>pt-BR</language>", + f"<lastBuildDate>{rfc822_date(now)}</lastBuildDate>", + f'<atom:link href="{SITE_URL}/news/feed.rss" rel="self" type="application/rss+xml"/>', + ] + for art in articles[:50]: + title = xml_escape(str(art["title"])) + aid = xml_escape(str(art["id"])) + link = f"{SITE_URL}/news/#{aid}" + pub = art.get("pub_rfc822") or rfc822_date(now) + body = art.get("body_html") or "" + desc = f"<![CDATA[{body}]]>" + channel_parts.extend( + [ + "<item>", + f"<title>{title}</title>", + f"<link>{link}</link>", + f"<guid isPermaLink=\"false\">{SITE_URL}/news/item-{aid}</guid>", + f"<pubDate>{pub}</pubDate>", + f"<description>{desc}</description>", + "</item>", + ] + ) + channel_parts.extend(["</channel>", "</rss>"]) + return "\n".join(channel_parts) + "\n" + + +def update_sitemap_lastmod(news_lastmod: str) -> None: + """Actualiza ou insere ``<lastmod>`` só no URL ``/news/``, sem reescrever prefixos XML.""" + if not SITEMAP_PATH.is_file(): + return + text = SITEMAP_PATH.read_text(encoding="utf-8") + news_loc = f"<loc>{SITE_URL}/news/</loc>" + if news_loc not in text: + return + lastmod_tag = f"<lastmod>{news_lastmod}</lastmod>" + block_re = re.compile( + rf"(\s*<url>\s*{re.escape(news_loc)})(\s*<lastmod>[^<]*</lastmod>)?(\s*</url>)", + re.DOTALL, + ) + + def repl(m: re.Match[str]) -> str: + return f"{m.group(1)}\n {lastmod_tag}{m.group(3)}" + + new_text, n = block_re.subn(repl, text, count=1) + if n: + SITEMAP_PATH.write_text(new_text, encoding="utf-8") + + +def discover_md_files() -> list[Path]: + out: list[Path] = [] + skip = frozenset({"readme.md", "readme.markdown"}) + for p in sorted(SCRIPT_DIR.glob("*.md")): + if p.name.startswith("_"): + continue + if p.name.lower() in skip: + continue + out.append(p) + return out + + +def main() -> int: + ap = argparse.ArgumentParser(description="Publica notícias a partir de .md em site/news/") + ap.add_argument("--dry-run", action="store_true", help="Só mostra o que faria") + ap.add_argument("--verbose", "-v", action="store_true") + args = ap.parse_args() + + try: + now = datetime.now(timezone.utc).astimezone(ZoneInfo(TZ_BR)) + except Exception: + now = datetime.now(BR_FALLBACK_TZ) + + md_files = discover_md_files() + if not md_files: + print("Nenhum ficheiro .md para processar (ignore _*.md).", file=sys.stderr) + return 0 + + articles = load_articles() + pub_rfc = rfc822_date(now) + date_br = br_date_display(now) + w3c = w3c_date(now) + + new_entries: list[dict[str, Any]] = [] + for path in md_files: + try: + title, body_md = parse_md_file(path) + except ValueError as e: + print(f"Erro em {path.name}: {e}", file=sys.stderr) + return 1 + body_html = markdown_body_to_html(body_md) + entry = { + "id": uuid.uuid4().hex[:12], + "title": title, + "date": date_br, + "body_html": body_html, + "pub_rfc822": pub_rfc, + "w3c_published": w3c, + } + new_entries.append((path, entry)) + if args.verbose: + print(f" + {path.name} -> {title!r}") + + if args.dry_run: + print(f"[dry-run] {len(new_entries)} notícia(s); não gravou nem apagou ficheiros.") + return 0 + + for _path, entry in new_entries: + articles.insert(0, entry) + + save_articles(articles) + RSS_PATH.write_text(build_rss(articles, now), encoding="utf-8") + + update_sitemap_lastmod(w3c) + + for path, _entry in new_entries: + path.unlink() + if args.verbose: + print(f" removido {path.name}") + + print(f"Publicadas {len(new_entries)} notícia(s). Total: {len(articles)}.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/site/public/assets/app.js b/site/public/assets/app.js @@ -1,6 +1,7 @@ /** * Landing runv.club — carrega members.json (só dados públicos) e coloca * pontos clicáveis (links) fora da coluna de texto; brilho ligado à data since. + * Posições fixas no viewport (não recalculam ao scroll); zona central alinhada à .wrap. * Array vazio: sem estrelas até build_directory.py gerar o JSON a partir de users.json. */ @@ -50,7 +51,27 @@ function pointInRect(x, y, rect) { } /** - * Posição para um ponto: fora da coluna `.wrap` (texto), com fallback para + * Rect da coluna de conteúdo fixo no viewport (mesma ideia que .wrap: 46rem + padding). + * Independente do scroll — evita que as bolinhas «saltem» ao rolar a página. + */ +function viewportFixedContentExcludeRect() { + const vw = window.innerWidth; + const vh = window.innerHeight; + const rootFs = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16; + const maxBlock = 46 * rootFs; + const pad = Math.min(Math.max(rootFs, vw * 0.04), 1.35 * rootFs); + const contentW = Math.min(maxBlock, Math.max(0, vw - 2 * pad)); + const left = Math.max(0, (vw - contentW) / 2); + return { + left, + top: 0, + right: left + contentW, + bottom: vh, + }; +} + +/** + * Posição para um ponto: fora da coluna central (viewport), com fallback para * faixas laterais ou cantos quando o ecrã é estreito. */ function findStarPosition(w, h, seed, exclude) { @@ -104,19 +125,26 @@ function validMembers(members) { ); } -function renderStarLinks(container, wrapEl, members) { +/** Viewport estreito: sem «bolinhas» de membros (tocar era difícil e sobrepõe o texto). */ +function isStarfieldMobileViewport() { + return window.matchMedia("(max-width: 768px)").matches; +} + +function renderStarLinks(container, members) { if (!container) return; container.replaceChildren(); + if (isStarfieldMobileViewport()) { + return; + } + const w = window.innerWidth; const h = window.innerHeight; if (w < 32 || h < 32) return; const pad = 36; - const exclude = wrapEl - ? inflateRect(wrapEl.getBoundingClientRect(), pad) - : { left: 0, top: 0, right: w, bottom: h }; + const exclude = inflateRect(viewportFixedContentExcludeRect(), pad); for (const m of validMembers(members)) { const seed = hashUsername(m.username); @@ -140,7 +168,6 @@ function renderStarLinks(container, wrapEl, members) { async function main() { const starRoot = document.getElementById("starfield"); - const wrapEl = document.querySelector(".wrap"); let members = []; @@ -158,18 +185,13 @@ async function main() { if (!starRoot) return; cancelAnimationFrame(starRaf); starRaf = requestAnimationFrame(() => { - renderStarLinks(starRoot, wrapEl, members); + renderStarLinks(starRoot, members); }); }; scheduleStars(); window.addEventListener("resize", scheduleStars, { passive: true }); - window.addEventListener("scroll", scheduleStars, { passive: true, capture: true }); - if (typeof ResizeObserver !== "undefined" && wrapEl) { - const ro = new ResizeObserver(scheduleStars); - ro.observe(wrapEl); - } } document.addEventListener("DOMContentLoaded", main); diff --git a/site/public/assets/news-page.js b/site/public/assets/news-page.js @@ -0,0 +1,65 @@ +/** + * Carrega notícias de data/news.json (gerado por site/news/publish_news.py). + */ +(function () { + async function run() { + const root = document.getElementById("news-feed"); + const empty = document.getElementById("news-empty"); + if (!root) return; + + try { + const r = await fetch("data/news.json", { cache: "no-store" }); + if (!r.ok) throw new Error("news.json indisponível"); + const data = await r.json(); + const articles = Array.isArray(data.articles) ? data.articles : []; + if (articles.length === 0) { + if (empty) { + empty.hidden = false; + empty.textContent = + "Ainda não há entradas publicadas. Quando houver, aparecem aqui em destaque."; + } + return; + } + if (empty) empty.hidden = true; + + const frag = document.createDocumentFragment(); + for (const a of articles) { + const art = document.createElement("article"); + art.className = "news-card"; + if (a.id) art.id = "post-" + a.id; + + const head = document.createElement("header"); + head.className = "news-card__head"; + + const time = document.createElement("time"); + time.className = "news-card__date"; + if (a.w3c_published) time.dateTime = a.w3c_published; + time.textContent = a.date || ""; + + const h2 = document.createElement("h2"); + h2.className = "news-card__title"; + h2.textContent = a.title || ""; + + head.appendChild(time); + head.appendChild(h2); + + const body = document.createElement("div"); + body.className = "news-card__body prose-news"; + body.innerHTML = a.body_html || ""; + + art.appendChild(head); + art.appendChild(body); + frag.appendChild(art); + } + root.appendChild(frag); + } catch (_e) { + if (empty) { + empty.hidden = false; + empty.textContent = + "Não foi possível carregar a lista (ficheiro data/news.json ausente ou indisponível). Use o feed RSS ou tente mais tarde."; + } + } + } + + run(); +})(); diff --git a/site/public/assets/style.css b/site/public/assets/style.css @@ -57,6 +57,13 @@ body { overflow: hidden; } +/* Membros como pontos: só em ecrãs largos (em mobile o texto ocupa o viewport). */ +@media (max-width: 768px) { + .starfield-root { + display: none; + } +} + .star-member { --star-scale: 1; position: fixed; @@ -568,3 +575,190 @@ h2 { text-decoration: underline; } +.footer-sep { + color: var(--muted); + user-select: none; +} + +.footer-link-discrete { + color: var(--muted); + font-size: 0.88em; + font-weight: 400; + text-decoration: none; +} + +.footer-link-discrete:hover, +.footer-link-discrete:focus-visible { + color: var(--accent); + text-decoration: underline; + text-underline-offset: 2px; +} + +/* FAQ */ +.faq-main .faq-item { + margin-bottom: 1.75rem; + padding-bottom: 1.35rem; + border-bottom: 1px solid var(--stroke); +} + +.faq-main .faq-item:last-child { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; +} + +.faq-q { + margin: 0 0 0.5rem; + font-family: "Syne", sans-serif; + font-weight: 700; + font-size: 1.05rem; + line-height: 1.35; + letter-spacing: -0.02em; + color: var(--accent2); +} + +.faq-main .faq-item p { + margin: 0; + color: var(--fg); + max-width: 40rem; +} + +/* Junte-se (versão curta) */ +.join-summary { + margin-bottom: 1.5rem; +} + +.join-note { + font-size: 0.95rem; + color: var(--muted); + margin-top: 0.35rem; +} + +/* Página de notícias (data/news.json + news-page.js) */ +.news-main { + margin-top: 1.25rem; +} + +.news-rss-link { + color: var(--accent2); + text-decoration: none; + font-weight: 600; + white-space: nowrap; +} + +.news-rss-link:hover, +.news-rss-link:focus-visible { + text-decoration: underline; + text-underline-offset: 3px; +} + +.news-feed { + display: flex; + flex-direction: column; + gap: 1.35rem; + max-width: 42rem; +} + +.news-card { + margin: 0; + padding: 1.35rem 1.4rem 1.45rem; + border-radius: calc(var(--radius) + 4px); + border: 1px solid var(--stroke); + background: linear-gradient( + 145deg, + rgba(232, 228, 220, 0.05) 0%, + rgba(12, 11, 15, 0.65) 100% + ); + box-shadow: + 0 1px 0 rgba(232, 228, 220, 0.06) inset, + 0 12px 32px rgba(0, 0, 0, 0.35); +} + +.news-card__head { + margin: 0 0 0.85rem; + padding: 0; + border: none; +} + +.news-card__date { + display: block; + font-family: "IBM Plex Mono", ui-monospace, monospace; + font-size: 0.78rem; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--muted); + margin-bottom: 0.45rem; +} + +.news-card__title { + margin: 0; + font-family: "Syne", sans-serif; + font-weight: 700; + font-size: clamp(1.25rem, 3.5vw, 1.45rem); + line-height: 1.25; + letter-spacing: -0.02em; + color: var(--accent); +} + +.news-card__body { + margin: 0; + font-size: 1.02rem; + line-height: 1.65; + color: var(--fg); +} + +.news-card__body p { + margin: 0 0 0.75rem; +} + +.news-card__body p:last-child { + margin-bottom: 0; +} + +.prose-news strong { + color: var(--fg); + font-weight: 700; +} + +.prose-news em { + font-style: italic; + color: var(--fg); +} + +.prose-news u { + text-decoration: underline; + text-underline-offset: 3px; + text-decoration-color: var(--accent2); +} + +.wiki-hero-nav { + font-size: 0.88rem; +} + +.wiki-toc { + margin: 0 0 2rem; + padding: 1rem 1.25rem; + border: 1px solid var(--stroke); + border-radius: var(--radius); + background: rgba(232, 228, 220, 0.03); +} + +.wiki-toc-title { + margin: 0 0 0.75rem; + font-family: "Syne", sans-serif; + font-weight: 700; + font-size: 0.85rem; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.wiki-toc ul { + margin: 0; + padding-left: 1.25rem; +} + +.wiki-main .wiki-page-title { + margin-top: 0; +} + diff --git a/site/public/faq/index.html b/site/public/faq/index.html @@ -0,0 +1,110 @@ +<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>FAQ — runv.club</title> + <meta name="description" content="Perguntas frequentes sobre o runv.club: cadastro, acesso, suporte e contato."> + <link rel="canonical" href="https://runv.club/faq/"> + <meta name="robots" content="index, follow"> + <meta name="theme-color" content="#0c0b0f"> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="../assets/style.css"> +</head> +<body> + <div class="wrap"> + <nav class="top-nav"><a href="/">← runv.club</a></nav> + + <header> + <p class="eyebrow">runv.club</p> + <nav class="hero-nav" aria-label="Outras páginas"> + <a href="/news/">Notícias</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/">Wiki</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/junte-se/">Junte-se</a> + </nav> + <h1 class="hero-title subpage-title">FAQ</h1> + <p class="subpage-intro">Perguntas frequentes — runv.club</p> + </header> + + <main class="section prose-block subpage-main faq-main"> + <section class="faq-item" aria-labelledby="faq-1"> + <h2 id="faq-1" class="faq-q">1. O que é o runv.club?</h2> + <p>O runv.club é um sistema online de acesso e organização de recursos, pensado para simplificar a experiência do usuário em um ambiente digital único. A proposta é centralizar o acesso, reduzir fricção no uso e facilitar o suporte quando surgir alguma dúvida.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-2"> + <h2 id="faq-2" class="faq-q">2. O sistema é só para alunos do Portal IDEA?</h2> + <p>Não. O runv.club não é exclusivo para alunos do Portal IDEA. O sistema pode ser utilizado por outros públicos, conforme a proposta, disponibilidade de acesso e regras da plataforma.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-3"> + <h2 id="faq-3" class="faq-q">3. Como faço meu cadastro?</h2> + <p>Na tela de acesso, basta escolher a opção de cadastro e preencher os dados solicitados. Depois disso, siga as instruções exibidas na plataforma para concluir a criação da sua conta.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-4"> + <h2 id="faq-4" class="faq-q">4. Já tenho conta. Como faço login?</h2> + <p>Acesse a página de entrada do sistema, informe seu e-mail e senha e prossiga com o login. Se a plataforma oferecer outros métodos de entrada, como login social, eles aparecerão na própria tela.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-5"> + <h2 id="faq-5" class="faq-q">5. Esqueci minha senha. O que devo fazer?</h2> + <p>Use a opção de recuperação de senha disponível na tela de acesso. Se você não receber a mensagem de redefinição em poucos minutos, verifique a caixa de spam ou lixo eletrônico.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-6"> + <h2 id="faq-6" class="faq-q">6. Posso entrar com conta Google?</h2> + <p>Se essa opção estiver habilitada na tela de login, sim. O método disponível sempre será o que estiver exibido oficialmente no acesso da plataforma.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-7"> + <h2 id="faq-7" class="faq-q">7. Não recebi e-mail de confirmação ou recuperação. E agora?</h2> + <p>Primeiro, confira spam, promoções e lixo eletrônico. Depois, confirme se o e-mail informado foi digitado corretamente. Se o problema continuar, entre em contato pelo e-mail <a href="mailto:admin@runv.club">admin@runv.club</a>.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-8"> + <h2 id="faq-8" class="faq-q">8. O sistema funciona no celular?</h2> + <p>Sim. O ideal é que o sistema funcione em navegador atualizado, tanto no celular quanto no computador. Se houver falha de carregamento, atualize a página, teste outro navegador e verifique sua conexão.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-9"> + <h2 id="faq-9" class="faq-q">9. O que fazer se a plataforma estiver lenta, fora do ar ou com erro?</h2> + <p>Antes de assumir que o problema é do sistema, faça o básico: atualize a página, saia e entre novamente, limpe o cache do navegador e teste em outro dispositivo ou rede. Se o erro persistir, envie o máximo de detalhes para <a href="mailto:admin@runv.club">admin@runv.club</a>.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-10"> + <h2 id="faq-10" class="faq-q">10. Como pedir suporte de forma eficiente?</h2> + <p>Envie uma mensagem objetiva com seu nome, e-mail cadastrado, descrição do problema, horário aproximado em que ocorreu e, se possível, captura de tela. Suporte ruim começa com relato ruim.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-11"> + <h2 id="faq-11" class="faq-q">11. Meus dados ficam protegidos?</h2> + <p>O objetivo da plataforma é operar com segurança e controle de acesso. Mesmo assim, a sua parte importa: use senha forte, não compartilhe credenciais e evite entrar em dispositivos públicos ou redes duvidosas.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-12"> + <h2 id="faq-12" class="faq-q">12. Posso acessar o sistema em mais de um dispositivo?</h2> + <p>Em geral, sim, desde que o acesso esteja dentro das regras da plataforma. Se houver limitação de sessões simultâneas, isso deve ser tratado pela própria interface ou pelo suporte.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-13"> + <h2 id="faq-13" class="faq-q">13. Onde acompanho avisos, mudanças ou instabilidades?</h2> + <p>Sempre verifique os canais oficiais ligados ao sistema, como a própria plataforma, telas de aviso e comunicações de suporte. Quando tiver dúvida real, pare de adivinhar e pergunte em <a href="mailto:admin@runv.club">admin@runv.club</a>.</p> + </section> + + <section class="faq-item" aria-labelledby="faq-14"> + <h2 id="faq-14" class="faq-q">14. Qual é o canal oficial de contato?</h2> + <p>O canal oficial informado para dúvidas, suporte e contato geral é: <a href="mailto:admin@runv.club">admin@runv.club</a></p> + </section> + </main> + + <footer class="site-footer"> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete" aria-current="page">FAQ</a></p> + </footer> + </div> +</body> +</html> diff --git a/site/public/index.html b/site/public/index.html @@ -3,8 +3,23 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>runv.club — comunidade brasileira estilo tilde</title> - <meta name="description" content="Comunidade brasileira em estilo tilde: Unix/Linux, página pessoal, terminal, estudo e convívio — menos algoritmo, mais presença."> + <title>runv.club — pubnix brasileira, Unix/Linux e página pessoal</title> + <meta name="description" content="Comunidade brasileira em servidor compartilhado Unix/Linux: conta SSH, página pessoal, terminal e convívio — hospedagem leve, cultura hacker, Portal IDEA. Junte-se."> + <link rel="canonical" href="https://runv.club/"> + <meta name="robots" content="index, follow"> + <meta name="theme-color" content="#0c0b0f"> + <meta property="og:type" content="website"> + <meta property="og:url" content="https://runv.club/"> + <meta property="og:locale" content="pt_BR"> + <meta property="og:site_name" content="runv.club"> + <meta property="og:title" content="runv.club — pubnix brasileira, Unix/Linux e página pessoal"> + <meta property="og:description" content="Comunidade brasileira em servidor compartilhado Unix/Linux: conta SSH, página pessoal, terminal e convívio — hospedagem leve e cultura hacker."> + <meta name="twitter:card" content="summary"> + <meta name="twitter:title" content="runv.club — pubnix brasileira, Unix/Linux e página pessoal"> + <meta name="twitter:description" content="Comunidade brasileira em servidor compartilhado Unix/Linux: conta SSH, página pessoal, terminal e convívio — hospedagem leve e cultura hacker."> + <script type="application/ld+json"> + {"@context":"https://schema.org","@type":"WebSite","name":"runv.club","url":"https://runv.club/","description":"Comunidade brasileira em pubnix Unix/Linux: conta SSH, página pessoal, terminal, estudo e convívio. Projeto do Portal IDEA.","inLanguage":"pt-BR","publisher":{"@type":"Organization","name":"Portal IDEA"}} + </script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> @@ -155,7 +170,7 @@ </div> <footer class="site-footer"> - <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a></p> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> </footer> </div> diff --git a/site/public/junte-se/index.html b/site/public/junte-se/index.html @@ -4,7 +4,7 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Junte-se — runv.club</title> - <meta name="description" content="Como gerar chave SSH no Linux, macOS e Windows e pedir entrada na runv.club com ssh entre@runv.club."> + <meta name="description" content="Como gerar chave SSH no Linux, macOS ou Windows e pedir entrada na runv.club via ssh entre@runv.club."> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> @@ -24,84 +24,49 @@ <span class="hero-nav-current" aria-current="page">Junte-se</span> </nav> <h1 class="hero-title subpage-title">Junte-se</h1> - <p class="subpage-intro">Gere uma chave SSH no seu computador e conecte-se a <span class="ssh-identity">entre@runv.club</span> para fazer seu pedido de entrada na comunidade.</p> + <p class="subpage-intro">Uma chave SSH no seu computador e um primeiro contacto com <span class="ssh-identity">entre@runv.club</span> — em qualquer sistema.</p> </header> <div class="entre-callout" role="status"> - <span class="entre-callout-label">SSH — pedido de entrada</span> + <span class="entre-callout-label">Pedido de entrada</span> <span class="ssh-identity ssh-identity-lg">entre@runv.club</span> </div> - <main class="section prose-block subpage-main"> - <h2>O que vai acontecer</h2> - <p> - A conta Unix <strong>entre</strong> não oferece um shell normal: executa um programa que pede o - <strong>nome de usuário</strong> desejado, <strong>e-mail</strong> e sua <strong>chave pública SSH</strong> - (uma linha). O pedido fica numa <strong>fila</strong> no servidor; a equipe revisa e, se for aceito, - cria a conta com as ferramentas internas do runv. <strong>Não é cadastro automático.</strong> - </p> - <p> - É preciso gerar um par de chaves no seu PC (a privada fica com você; você só envia a <em>pública</em> no fluxo). - Abaixo: passos para <strong>Linux</strong>, <strong>macOS</strong> e <strong>Windows</strong>. - </p> - - <h2>Linux (Terminal)</h2> - <ol class="checklist"> - <li>Abra um terminal.</li> - <li>Gere uma chave Ed25519 (recomendado). Substitua o comentário por algo seu (ex.: e-mail ou nome do PC):</li> + <main class="section prose-block subpage-main join-main"> + <h2>Em resumo</h2> + <ol class="checklist join-summary"> + <li><strong>Gere</strong> um par de chaves SSH no seu PC (fica só a <em>pública</em> para colar no pedido).</li> + <li><strong>Ligue</strong> com <code>ssh</code> a <span class="ssh-identity">entre@runv.club</span> e siga o que aparecer no ecrã.</li> + <li><strong>Aguarde</strong> — a equipa revê a fila; não é cadastro instantâneo.</li> </ol> - <pre class="code-block" tabindex="0"><code>ssh-keygen -t ed25519 -C "seu-email-ou-identificador" -f ~/.ssh/id_ed25519_runv</code></pre> - <p>Quando pedir passphrase, você pode definir uma (mais seguro) ou Enter vazio (mais simples — menos seguro se alguém copiar o arquivo).</p> - <p>Mostre a <strong>chave pública</strong> para copiar no passo final (é uma linha que começa com <code>ssh-ed25519</code>):</p> - <pre class="code-block" tabindex="0"><code>cat ~/.ssh/id_ed25519_runv.pub</code></pre> - <p>Para se conectar depois com essa chave (quando você já tiver conta própria), o cliente SSH usa por padrão <code>~/.ssh/id_ed25519</code> ou o que você definir em <code>~/.ssh/config</code>. Para o primeiro contato com <strong>entre</strong>, basta o comando da seção “Ligar a <span class="ssh-identity">entre@runv.club</span>”.</p> - <h2>macOS (Terminal)</h2> - <ol class="checklist"> - <li>Abra o <strong>Terminal</strong> (Spotlight: “Terminal”).</li> - <li>Os passos são os mesmos que no Linux:</li> - </ol> - <pre class="code-block" tabindex="0"><code>ssh-keygen -t ed25519 -C "seu-email-ou-identificador" -f ~/.ssh/id_ed25519_runv</code></pre> - <pre class="code-block" tabindex="0"><code>cat ~/.ssh/id_ed25519_runv.pub</code></pre> - <p>No macOS recente, o OpenSSH já vem instalado. Se você usar o agente de chaves, pode rodar <code>ssh-add ~/.ssh/id_ed25519_runv</code> após gerar.</p> + <h2>Linux</h2> + <p>Terminal: gere Ed25519 e copie a linha da chave <strong>pública</strong> (<code>.pub</code>).</p> + <pre class="code-block" tabindex="0"><code>ssh-keygen -t ed25519 -C "seu-email-ou-pc" -f ~/.ssh/id_ed25519_runv +cat ~/.ssh/id_ed25519_runv.pub</code></pre> - <h2>Windows (PowerShell com OpenSSH)</h2> - <p> - No Windows 10 e 11, o cliente OpenSSH costuma estar disponível. Abra o <strong>PowerShell</strong> - ou o <strong>Terminal do Windows</strong> e verifique: - </p> - <pre class="code-block" tabindex="0"><code>ssh -V</code></pre> - <p>Se o comando não for encontrado, em <strong>Configurações</strong> → <strong>Aplicativos</strong> → <strong>Recursos opcionais</strong> instale o <strong>Cliente OpenSSH</strong> (ou use o guia oficial da Microsoft).</p> - <p>Gere a chave (caminho típico da pasta <code>.ssh</code> no seu perfil):</p> - <pre class="code-block" tabindex="0"><code>ssh-keygen -t ed25519 -C "seu-email-ou-identificador" -f $env:USERPROFILE\.ssh\id_ed25519_runv</code></pre> - <p>Mostre a chave pública:</p> - <pre class="code-block" tabindex="0"><code>Get-Content $env:USERPROFILE\.ssh\id_ed25519_runv.pub</code></pre> - <p> - <strong>Obs.:</strong> Nunca compartilhe o arquivo <em>sem</em> extensão <code>.pub</code> — esse é o privado. - Só a linha do <code>.pub</code> entra no pedido ao <strong>entre</strong>. - </p> + <h2>macOS</h2> + <p><strong>Terminal</strong> (Spotlight: “Terminal”) — os mesmos comandos que em Linux. OpenSSH já vem no sistema; opcional: <code>ssh-add ~/.ssh/id_ed25519_runv</code>.</p> + <pre class="code-block" tabindex="0"><code>ssh-keygen -t ed25519 -C "seu-email-ou-pc" -f ~/.ssh/id_ed25519_runv +cat ~/.ssh/id_ed25519_runv.pub</code></pre> - <h2>Ligar a <span class="ssh-identity">entre@runv.club</span></h2> - <p>Com a chave já criada, no mesmo terminal (Linux/macOS) ou PowerShell (Windows):</p> + <h2>Windows</h2> + <p><strong>PowerShell</strong> ou Terminal do Windows. Confirme o cliente: <code>ssh -V</code>. Se faltar, instale <em>Cliente OpenSSH</em> em Configurações → Aplicativos → Recursos opcionais.</p> + <pre class="code-block" tabindex="0"><code>ssh-keygen -t ed25519 -C "seu-email-ou-pc" -f $env:USERPROFILE\.ssh\id_ed25519_runv +Get-Content $env:USERPROFILE\.ssh\id_ed25519_runv.pub</code></pre> + <p class="join-note">Nunca partilhe o ficheiro <strong>sem</strong> <code>.pub</code> — esse é o privado.</p> + + <h2>Ligar ao <span class="ssh-identity">entre@runv.club</span></h2> + <p>Linux / macOS:</p> <pre class="code-block" tabindex="0"><code>ssh -i ~/.ssh/id_ed25519_runv entre@runv.club</code></pre> - <p>No Windows PowerShell, equivalente:</p> + <p>Windows (PowerShell):</p> <pre class="code-block" tabindex="0"><code>ssh -i $env:USERPROFILE\.ssh\id_ed25519_runv entre@runv.club</code></pre> - <p> - Se sua chave tiver outro nome ou estiver no caminho padrão (<code>id_ed25519</code> / <code>id_rsa</code>), - você pode omitir <code>-i</code> ou ajustar o caminho. Na <strong>primeira conexão</strong>, o SSH pergunta se você confia - na fingerprint do servidor — confirme se estiver falando com o runv certo. - </p> - <p>Siga as instruções na tela até o fim. Se algo falhar, entre em contato com <a href="mailto:admin@runv.club">admin@runv.club</a>.</p> - - <h2>Tipos de chave aceitas</h2> - <p> - O fluxo do servidor segue a política de provisionamento: em geral <strong>Ed25519</strong> (recomendado), - chaves <strong>ECDSA</strong> NIST e, em alguns casos, <strong>RSA</strong>. Preferência forte por Ed25519. - </p> + <p>Na primeira vez, aceite a fingerprint se confiar no servidor. Se a sua chave tiver outro nome ou estiver no caminho por defeito, pode omitir <code>-i</code>.</p> + <p>Dúvidas ou bloqueios: <a href="mailto:admin@runv.club">admin@runv.club</a>. Tipos de chave: preferência por <strong>Ed25519</strong>; também ECDSA ou RSA conforme política do servidor.</p> </main> <footer class="site-footer"> - <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a></p> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> </footer> </div> </body> diff --git a/site/public/news/data/.gitkeep b/site/public/news/data/.gitkeep diff --git a/site/public/news/data/news.json.example b/site/public/news/data/news.json.example @@ -0,0 +1,3 @@ +{ + "articles": [] +} diff --git a/site/public/news/index.html b/site/public/news/index.html @@ -4,7 +4,20 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Notícias — runv.club</title> - <meta name="description" content="Notícias e atualizações da comunidade runv.club."> + <meta name="description" content="Comunicados oficiais, mudanças no servidor e novidades da comunidade runv.club — pubnix brasileira Unix/Linux."> + <link rel="canonical" href="https://runv.club/news/"> + <meta name="robots" content="index, follow"> + <meta name="theme-color" content="#0c0b0f"> + <link rel="alternate" type="application/rss+xml" title="Notícias runv.club" href="https://runv.club/news/feed.rss"> + <meta property="og:type" content="website"> + <meta property="og:url" content="https://runv.club/news/"> + <meta property="og:locale" content="pt_BR"> + <meta property="og:site_name" content="runv.club"> + <meta property="og:title" content="Notícias — runv.club"> + <meta property="og:description" content="Comunicados e atualizações da comunidade runv.club."> + <meta name="twitter:card" content="summary"> + <meta name="twitter:title" content="Notícias — runv.club"> + <meta name="twitter:description" content="Comunicados e atualizações da comunidade runv.club."> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> @@ -24,18 +37,24 @@ <a href="/junte-se/">Junte-se</a> </nav> <h1 class="hero-title subpage-title">Notícias</h1> - <p class="subpage-intro">Comunicados oficiais, mudanças no servidor e novidades da comunidade.</p> + <p class="subpage-intro">Comunicados oficiais, mudanças no servidor e novidades da comunidade. <a class="news-rss-link" href="feed.rss" type="application/rss+xml">Feed RSS</a></p> </header> - <main class="section prose-block subpage-main"> - <p class="placeholder-block"> - Ainda não há entradas publicadas. Esta página será preenchida quando houver notícias — por exemplo via build estático, feed ou outro fluxo que definirem mais tarde. + <main class="section subpage-main news-main"> + <div id="news-feed" class="news-feed" aria-live="polite"></div> + <p id="news-empty" class="placeholder-block"> + A carregar notícias… </p> + <noscript> + <p class="placeholder-block">Active JavaScript para ver a lista de notícias ou subscreva o <a href="feed.rss">feed RSS</a>.</p> + </noscript> </main> <footer class="site-footer"> - <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a></p> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> </footer> </div> + + <script src="../assets/news-page.js" defer></script> </body> </html> diff --git a/site/public/robots.txt b/site/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://runv.club/sitemap.xml diff --git a/site/public/sitemap.xml b/site/public/sitemap.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> + <url> + <loc>https://runv.club/</loc> + </url> + <url> + <loc>https://runv.club/news/</loc> + </url> + <url> + <loc>https://runv.club/faq/</loc> + </url> + <url> + <loc>https://runv.club/junte-se/</loc> + </url> + <!-- wiki:gerado --> + <url> + <loc>https://runv.club/wiki/</loc> + </url> + <url> + <loc>https://runv.club/wiki/contas-e-acesso.html</loc> + </url> + <url> + <loc>https://runv.club/wiki/faq.html</loc> + </url> + <url> + <loc>https://runv.club/wiki/privacidade-e-seguranca.html</loc> + </url> + <url> + <loc>https://runv.club/wiki/punicoes-e-moderacao.html</loc> + </url> + <url> + <loc>https://runv.club/wiki/regras-da-comunidade.html</loc> + </url> + <url> + <loc>https://runv.club/wiki/visao-geral.html</loc> + </url> + <!-- /wiki:gerado --> +</urlset> diff --git a/site/public/wiki/contas-e-acesso.html b/site/public/wiki/contas-e-acesso.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>RUNV.CLUB - CONTAS E ACESSO — Wiki runv.club</title> + <meta name="description" content="RUNV.CLUB - CONTAS E ACESSO — wiki runv.club."> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="../assets/style.css"> +</head> +<body> + <div class="wrap"> + <nav class="top-nav"><a href="/">← runv.club</a></nav> + + <header> + <p class="eyebrow">runv.club</p> + <nav class="hero-nav wiki-hero-nav" aria-label="Páginas da wiki"> + <a href="/news/">Notícias</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/">Índice</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/visao-geral.html">Visão geral</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <span class="hero-nav-current" aria-current="page">Contas e acesso</span> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/regras-da-comunidade.html">Regras</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/punicoes-e-moderacao.html">Punições</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/privacidade-e-seguranca.html">Privacidade</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/faq.html">FAQ wiki</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/junte-se/">Junte-se</a> + </nav> + </header> + + <main class="section prose-block subpage-main wiki-main"> +<h1 class="hero-title subpage-title wiki-page-title">RUNV.CLUB - CONTAS E ACESSO</h1> + +<h2>CADASTRO</h2> + +<p>O cadastro deve ser feito com informações verdadeiras e minimamente consistentes.<br> +Criar conta com identidade falsa para enganar terceiros, burlar punição, cometer fraude ou simular legitimidade é motivo para ação da moderação.</p> + +<h2>LOGIN</h2> + +<p>O acesso ao sistema é individual. Cada usuário é responsável por proteger seu e-mail, senha e meios de autenticação vinculados à conta.</p> + +<h2>RECUPERAÇÃO DE SENHA</h2> + +<p>Se o usuário perder acesso à conta, deve usar os meios oficiais de recuperação disponíveis no sistema.<br> +Se ainda assim não resolver, deve contatar:<br> +admin@runv.club</p> + +<h2>COMPARTILHAMENTO DE CONTA</h2> + +<p>É proibido compartilhar conta quando isso:<br> +- comprometer a segurança;<br> +- dificultar auditoria de ações;<br> +- permitir fraude, abuso ou evasão de punição;<br> +- gerar uso indevido de permissões.</p> + +<p>A administração pode limitar, suspender ou encerrar contas usadas por múltiplas pessoas de forma irregular.</p> + +<h2>RESPONSABILIDADE PELA CONTA</h2> + +<p>Tudo o que for feito pela conta poderá ser atribuído ao titular até que exista evidência concreta de comprometimento, invasão ou uso indevido por terceiros.</p> + +<h2>BOAS PRÁTICAS DE SEGURANÇA</h2> + +<ul><li>usar senha forte;</li><li>não reutilizar senha vazada em outros serviços;</li><li>não enviar senha por mensagem;</li><li>não clicar em links suspeitos;</li><li>sair da conta em dispositivos públicos;</li><li>reportar acesso estranho imediatamente.</li></ul> + +<h2>ACESSO SUSPENSO OU BLOQUEADO</h2> + +<p>A conta poderá ser temporariamente limitada ou suspensa quando houver:<br> +- indício de invasão;<br> +- atividade automatizada suspeita;<br> +- tentativa de fraude;<br> +- reincidência em infrações;<br> +- risco à integridade do sistema.</p> + +<h2>ERRO COMUM DO USUÁRIO</h2> + +<p>Muita gente trata a própria conta como se fosse descartável. Isso é estupidez operacional.<br> +Se a conta concentra histórico, permissões e suporte, então ela precisa ser tratada como ativo. Quem ignora isso aumenta o próprio risco.</p> + +<h2>SUPORTE DE ACESSO</h2> + +<p>Qualquer problema de login, recuperação, segurança ou suspeita de comprometimento deve ser comunicado para:<br> +admin@runv.club</p> + </main> + + <footer class="site-footer"> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> + </footer> + </div> +</body> +</html> diff --git a/site/public/wiki/faq.html b/site/public/wiki/faq.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>RUNV.CLUB - FAQ — Wiki runv.club</title> + <meta name="description" content="RUNV.CLUB - FAQ — wiki runv.club."> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="../assets/style.css"> +</head> +<body> + <div class="wrap"> + <nav class="top-nav"><a href="/">← runv.club</a></nav> + + <header> + <p class="eyebrow">runv.club</p> + <nav class="hero-nav wiki-hero-nav" aria-label="Páginas da wiki"> + <a href="/news/">Notícias</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/">Índice</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/visao-geral.html">Visão geral</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/contas-e-acesso.html">Contas e acesso</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/regras-da-comunidade.html">Regras</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/punicoes-e-moderacao.html">Punições</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/privacidade-e-seguranca.html">Privacidade</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <span class="hero-nav-current" aria-current="page">FAQ wiki</span> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/junte-se/">Junte-se</a> + </nav> + </header> + + <main class="section prose-block subpage-main wiki-main"> +<h1 class="hero-title subpage-title wiki-page-title">RUNV.CLUB - FAQ</h1> + +<p>1. O que é o runv.club?<br> +O runv.club é um sistema de acesso, organização, suporte e uso em ambiente digital. Ele centraliza informações, regras, canais de contato e recursos operacionais.</p> + +<p>2. O sistema é só para alunos do Portal IDEA?<br> +Não.</p> + +<p>3. Como entro no sistema?<br> +O acesso depende do fluxo disponível no ambiente: cadastro, login e, quando aplicável, recuperação de senha ou outro método oficial de autenticação.</p> + +<p>4. Esqueci minha senha. O que faço?<br> +Use a opção oficial de recuperação de senha. Se o problema continuar, entre em contato por admin@runv.club.</p> + +<p>5. Posso compartilhar minha conta com outra pessoa?<br> +Não é recomendado e pode ser proibido, especialmente quando isso compromete segurança, auditoria, responsabilidade da conta ou regras do sistema.</p> + +<p>6. Minha conta pode ser suspensa?<br> +Sim. Contas podem ser advertidas, limitadas ou suspensas quando houver infração de regras, abuso, fraude, evasão de punição, spam, assédio ou risco à operação.</p> + +<p>7. O que pode causar banimento?<br> +Fraude, golpe, phishing, invasão, exploração maliciosa, conteúdo ilegal grave, ameaça real, assédio grave, evasão de punição e reincidência séria.</p> + +<p>8. Toda punição começa com aviso?<br> +Não. Infrações leves podem começar com orientação ou advertência. Infrações graves podem gerar suspensão imediata ou banimento direto.</p> + +<p>9. Como denuncio abuso ou comportamento suspeito?<br> +Envie relato objetivo com evidências, se houver, para admin@runv.club.</p> + +<p>10. Como recorrer de uma punição?<br> +Entre em contato por admin@runv.club, identifique a conta, explique o caso de forma objetiva e envie qualquer prova relevante.</p> + +<p>11. O sistema guarda informações de uso?<br> +Pode haver registro técnico e histórico operacional para fins de segurança, suporte, moderação e integridade da plataforma.</p> + +<p>12. Posso publicar dados de outras pessoas?<br> +Não. Exposição de dados pessoais, documentos, contatos, prints privados ou informações sensíveis sem autorização pode gerar punição grave.</p> + +<p>13. O que fazer se eu suspeitar que minha conta foi invadida?<br> +Troque a senha imediatamente, encerre sessões suspeitas se isso existir no sistema e comunique o caso para admin@runv.club.</p> + +<p>14. Como falar com a administração?<br> +Pelo e-mail oficial: admin@runv.club</p> + +<p>15. Onde vejo as regras do sistema?<br> +Na wiki oficial do runv.club, especialmente nos arquivos de regras da comunidade, moderação, privacidade e acesso.</p> + </main> + + <footer class="site-footer"> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> + </footer> + </div> +</body> +</html> diff --git a/site/public/wiki/index.html b/site/public/wiki/index.html @@ -4,7 +4,7 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Wiki — runv.club</title> - <meta name="description" content="Documentação e guias da comunidade runv.club."> + <meta name="description" content="Mapa e índice da wiki runv.club."> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> @@ -16,25 +16,88 @@ <header> <p class="eyebrow">runv.club</p> - <nav class="hero-nav" aria-label="Outras páginas"> + <nav class="hero-nav wiki-hero-nav" aria-label="Páginas da wiki"> <a href="/news/">Notícias</a> <span class="hero-nav-sep" aria-hidden="true">·</span> - <span class="hero-nav-current" aria-current="page">Wiki</span> + <span class="hero-nav-current" aria-current="page">Índice</span> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/visao-geral.html">Visão geral</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/contas-e-acesso.html">Contas e acesso</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/regras-da-comunidade.html">Regras</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/punicoes-e-moderacao.html">Punições</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/privacidade-e-seguranca.html">Privacidade</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/faq.html">FAQ wiki</a> <span class="hero-nav-sep" aria-hidden="true">·</span> <a href="/junte-se/">Junte-se</a> </nav> - <h1 class="hero-title subpage-title">Wiki</h1> - <p class="subpage-intro">Guias, como fazer, glossário e referência para quem usa o servidor.</p> </header> - <main class="section prose-block subpage-main"> - <p class="placeholder-block"> - Conteúdo em construção. Aqui poderão entrar páginas em Markdown/HTML, links para documentação do repositório ou um motor de wiki — o que combinarem mais tarde. - </p> + <main class="section prose-block subpage-main wiki-main"> +<nav class="wiki-toc" aria-label="Nesta wiki"><p class="wiki-toc-title">Páginas</p><ul> +<li><a href="/wiki/visao-geral.html">Visão geral</a></li> +<li><a href="/wiki/contas-e-acesso.html">Contas e acesso</a></li> +<li><a href="/wiki/regras-da-comunidade.html">Regras</a></li> +<li><a href="/wiki/punicoes-e-moderacao.html">Punições</a></li> +<li><a href="/wiki/privacidade-e-seguranca.html">Privacidade</a></li> +<li><a href="/wiki/faq.html">FAQ wiki</a></li> +</ul></nav> +<h1 class="hero-title subpage-title wiki-page-title">RUNV.CLUB - WIKI BASE (TXT)</h1> + +<p>Esta pasta reúne uma base inicial de wiki para o sistema runv.club.<br> +O objetivo é organizar, de forma simples e reutilizável, as informações principais de operação, acesso, regras de convivência, moderação, privacidade e FAQ.</p> + +<h2>ARQUIVOS DESTA WIKI</h2> + +<p>1. 01_index.txt<br> + Visão geral da wiki e mapa dos arquivos.</p> + +<p>2. 02_visao-geral.txt<br> + Explica o que é o runv.club, para quem serve e quais são seus objetivos.</p> + +<p>3. 03_contas-e-acesso.txt<br> + Cadastro, login, recuperação de senha, acesso por terceiros e boas práticas de conta.</p> + +<p>4. 04_regras-da-comunidade.txt<br> + Regras de convivência, conduta aceitável e conduta proibida.</p> + +<p>5. 05_punicoes-e-moderacao.txt<br> + Causas para advertência, suspensão, banimento e processo de análise.</p> + +<p>6. 06_privacidade-e-seguranca.txt<br> + Diretrizes gerais de privacidade, segurança e responsabilidade do usuário.</p> + +<p>7. 07_faq.txt<br> + Perguntas frequentes do sistema, incluindo dúvidas de acesso, uso e suporte.</p> + +<h2>CONTATO OFICIAL</h2> + +<p>E-mail de contato: admin@runv.club</p> + +<h2>NOTA IMPORTANTE</h2> + +<p>Esta base foi escrita como um pacote inicial de wiki para o runv.club.<br> +Ela pode ser usada como documentação pública, central de ajuda, base interna de suporte ou material para página de regras.</p> + +<h2>RECOMENDAÇÃO PRÁTICA</h2> + +<p>Se você publicar esta wiki, mantenha a mesma lógica:<br> +- página inicial curta;<br> +- regras objetivas;<br> +- punições previsíveis;<br> +- FAQ sem enrolação;<br> +- contato visível;<br> +- política de segurança clara.</p> + +<p>O erro comum de sistemas pequenos é ser vago nas regras e arbitrário na punição. Isso destrói confiança. Esta wiki foi montada para evitar esse problema.</p> </main> <footer class="site-footer"> - <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a></p> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> </footer> </div> </body> diff --git a/site/public/wiki/privacidade-e-seguranca.html b/site/public/wiki/privacidade-e-seguranca.html @@ -0,0 +1,104 @@ +<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>RUNV.CLUB - PRIVACIDADE E SEGURANÇA — Wiki runv.club</title> + <meta name="description" content="RUNV.CLUB - PRIVACIDADE E SEGURANÇA — wiki runv.club."> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="../assets/style.css"> +</head> +<body> + <div class="wrap"> + <nav class="top-nav"><a href="/">← runv.club</a></nav> + + <header> + <p class="eyebrow">runv.club</p> + <nav class="hero-nav wiki-hero-nav" aria-label="Páginas da wiki"> + <a href="/news/">Notícias</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/">Índice</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/visao-geral.html">Visão geral</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/contas-e-acesso.html">Contas e acesso</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/regras-da-comunidade.html">Regras</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/punicoes-e-moderacao.html">Punições</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <span class="hero-nav-current" aria-current="page">Privacidade</span> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/faq.html">FAQ wiki</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/junte-se/">Junte-se</a> + </nav> + </header> + + <main class="section prose-block subpage-main wiki-main"> +<h1 class="hero-title subpage-title wiki-page-title">RUNV.CLUB - PRIVACIDADE E SEGURANÇA</h1> + +<h2>PRINCÍPIO GERAL</h2> + +<p>Privacidade e segurança não são perfumaria jurídica. São parte da operação básica de qualquer sistema minimamente sério.</p> + +<h2>DADOS E RESPONSABILIDADE</h2> + +<p>O usuário deve fornecer apenas os dados necessários para uso do sistema e manter suas informações atualizadas quando isso for relevante para acesso, suporte e segurança.</p> + +<p>A administração deve tratar os dados com cuidado, limitar acessos internos e reduzir exposição desnecessária.</p> + +<h2>O USUÁRIO TAMBÉM TEM RESPONSABILIDADE</h2> + +<p>Não adianta exigir segurança da plataforma e depois usar senha fraca, compartilhar conta, clicar em golpe e ignorar alertas.<br> +Segurança é responsabilidade dividida.</p> + +<h2>O QUE O USUÁRIO DEVE EVITAR</h2> + +<ul><li>compartilhar credenciais;</li><li>publicar dados pessoais de terceiros;</li><li>usar dispositivos inseguros para acesso sensível;</li><li>ignorar indícios de invasão ou comprometimento;</li><li>instalar extensões suspeitas ou software duvidoso no dispositivo usado para login.</li></ul> + +<h2>INCIDENTES DE SEGURANÇA</h2> + +<p>Qualquer suspeita de:<br> +- acesso indevido;<br> +- roubo de conta;<br> +- fraude;<br> +- link malicioso;<br> +- falha técnica com impacto em segurança;<br> +- vazamento de informação;</p> + +<p>deve ser comunicada imediatamente para:<br> +admin@runv.club</p> + +<h2>ANÁLISE DE EVENTOS</h2> + +<p>Para proteger a plataforma, a administração pode analisar registros técnicos, padrões de uso, histórico de acesso e evidências relacionadas a incidentes, abuso e moderação.</p> + +<h2>LIMITAÇÃO IMPORTANTE</h2> + +<p>Nenhum sistema online é magicamente invulnerável.<br> +Prometer risco zero é propaganda, não verdade.<br> +O que importa é ter prevenção, resposta rápida, registro e correção.</p> + +<h2>BOAS PRÁTICAS OPERACIONAIS</h2> + +<ul><li>revisar acessos suspeitos;</li><li>registrar incidentes relevantes;</li><li>remover permissões desnecessárias;</li><li>corrigir falhas confirmadas;</li><li>orientar usuários afetados quando necessário.</li></ul> + +<h2>PRIVACIDADE DE TERCEIROS</h2> + +<p>É proibido usar o sistema para coletar, copiar, divulgar ou explorar dados de outros usuários sem base legítima e sem autorização.</p> + +<h2>CONTATO OFICIAL</h2> + +<p>Dúvidas sobre segurança, privacidade ou incidente:<br> +admin@runv.club</p> + </main> + + <footer class="site-footer"> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> + </footer> + </div> +</body> +</html> diff --git a/site/public/wiki/punicoes-e-moderacao.html b/site/public/wiki/punicoes-e-moderacao.html @@ -0,0 +1,122 @@ +<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>RUNV.CLUB - PUNIÇÕES E MODERAÇÃO — Wiki runv.club</title> + <meta name="description" content="RUNV.CLUB - PUNIÇÕES E MODERAÇÃO — wiki runv.club."> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="../assets/style.css"> +</head> +<body> + <div class="wrap"> + <nav class="top-nav"><a href="/">← runv.club</a></nav> + + <header> + <p class="eyebrow">runv.club</p> + <nav class="hero-nav wiki-hero-nav" aria-label="Páginas da wiki"> + <a href="/news/">Notícias</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/">Índice</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/visao-geral.html">Visão geral</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/contas-e-acesso.html">Contas e acesso</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/regras-da-comunidade.html">Regras</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <span class="hero-nav-current" aria-current="page">Punições</span> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/privacidade-e-seguranca.html">Privacidade</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/faq.html">FAQ wiki</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/junte-se/">Junte-se</a> + </nav> + </header> + + <main class="section prose-block subpage-main wiki-main"> +<h1 class="hero-title subpage-title wiki-page-title">RUNV.CLUB - PUNIÇÕES E MODERAÇÃO</h1> + +<h2>OBJETIVO DA MODERAÇÃO</h2> + +<p>A moderação existe para proteger usuários, preservar a operação do sistema e impedir que uma minoria destrua a utilidade do ambiente para todos os outros.</p> + +<h2>TIPOS DE MEDIDA</h2> + +<p>1. Orientação informal<br> + Usada em erro leve, isolado e sem dano relevante.</p> + +<p>2. Advertência formal<br> + Usada quando há violação clara de regra, mas ainda cabe correção de conduta.</p> + +<p>3. Limitação temporária<br> + Pode restringir postagem, interação, recurso técnico ou acesso parcial.</p> + +<p>4. Suspensão temporária<br> + Bloqueio por período definido, usado em reincidência ou infração moderada/grave.</p> + +<p>5. Banimento permanente<br> + Encerramento definitivo do acesso, usado quando o risco é alto ou a confiança foi quebrada.</p> + +<h2>CAUSAS COMUNS DE ADVERTÊNCIA</h2> + +<ul><li>linguagem hostil sem ameaça explícita;</li><li>flood leve;</li><li>desorganização recorrente apesar de orientação;</li><li>desrespeito pontual às regras de uso.</li></ul> + +<h2>CAUSAS COMUNS DE SUSPENSÃO</h2> + +<ul><li>reincidência após advertência;</li><li>assédio continuado;</li><li>spam insistente;</li><li>exposição indevida de terceiros;</li><li>tentativa de burlar limitação;</li><li>uso abusivo de recursos da plataforma;</li><li>comportamento que prejudique a operação ou o suporte.</li></ul> + +<h2>CAUSAS COMUNS DE BANIMENTO</h2> + +<ul><li>fraude;</li><li>phishing, golpe ou engenharia social;</li><li>invasão, tentativa de invasão ou exploração maliciosa;</li><li>publicação de conteúdo ilegal grave;</li><li>ameaça real a usuários ou equipe;</li><li>evasão de punição com contas alternativas;</li><li>reincidência grave que demonstre ausência total de boa-fé.</li></ul> + +<h2>CRITÉRIOS DE ANÁLISE</h2> + +<p>A administração pode considerar:<br> +- gravidade da conduta;<br> +- intenção aparente;<br> +- dano causado;<br> +- histórico do usuário;<br> +- risco de repetição;<br> +- cooperação ou má-fé durante a apuração.</p> + +<h2>NEM TUDO PRECISA SEGUIR ESCADA LINEAR</h2> + +<p>Nem toda infração começa em aviso.<br> +Essa fantasia de que todo caso precisa passar por aviso, depois suspensão, depois banimento, independentemente da gravidade, é ingênua.<br> +Fraude séria, ameaça real, golpe ou ataque técnico podem justificar banimento direto.</p> + +<h2>RECURSO</h2> + +<p>O usuário pode contestar medida aplicada por meio do canal oficial:<br> +admin@runv.club</p> + +<p>O recurso deve conter:<br> +- identificação da conta;<br> +- descrição objetiva do caso;<br> +- motivo da contestação;<br> +- provas, se existirem.</p> + +<h2>REGRAS DO RECURSO</h2> + +<ul><li>recurso sem objetividade atrapalha;</li><li>mentira piora a situação;</li><li>pressão emocional não substitui evidência;</li><li>agressividade contra a equipe pode gerar nova penalidade.</li></ul> + +<h2>DECISÃO FINAL</h2> + +<p>A administração pode revisar, manter, reduzir ou ampliar a medida aplicada com base nos elementos disponíveis.</p> + +<h2>POLÍTICA CONTRA IMPUNIDADE</h2> + +<p>Sistema sem execução consistente de regras vira piada.<br> +Se a regra existe, ela precisa produzir consequência real.</p> + </main> + + <footer class="site-footer"> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> + </footer> + </div> +</body> +</html> diff --git a/site/public/wiki/regras-da-comunidade.html b/site/public/wiki/regras-da-comunidade.html @@ -0,0 +1,103 @@ +<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>RUNV.CLUB - REGRAS DA COMUNIDADE — Wiki runv.club</title> + <meta name="description" content="RUNV.CLUB - REGRAS DA COMUNIDADE — wiki runv.club."> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="../assets/style.css"> +</head> +<body> + <div class="wrap"> + <nav class="top-nav"><a href="/">← runv.club</a></nav> + + <header> + <p class="eyebrow">runv.club</p> + <nav class="hero-nav wiki-hero-nav" aria-label="Páginas da wiki"> + <a href="/news/">Notícias</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/">Índice</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/visao-geral.html">Visão geral</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/contas-e-acesso.html">Contas e acesso</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <span class="hero-nav-current" aria-current="page">Regras</span> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/punicoes-e-moderacao.html">Punições</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/privacidade-e-seguranca.html">Privacidade</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/faq.html">FAQ wiki</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/junte-se/">Junte-se</a> + </nav> + </header> + + <main class="section prose-block subpage-main wiki-main"> +<h1 class="hero-title subpage-title wiki-page-title">RUNV.CLUB - REGRAS DA COMUNIDADE</h1> + +<h2>REGRA 1 - RESPEITO BÁSICO</h2> + +<p>É proibido atacar, humilhar, ameaçar, perseguir ou intimidar outros usuários.<br> +Discussão não é desculpa para assédio.<br> +Discordância é normal. Comportamento abusivo não é.</p> + +<h2>REGRA 2 - NADA DE DISCURSO DE ÓDIO</h2> + +<p>É proibido conteúdo que promova discriminação, desumanização, hostilidade ou exclusão com base em raça, cor, origem, etnia, nacionalidade, religião, deficiência, sexo, identidade de gênero ou orientação sexual.</p> + +<h2>REGRA 3 - NADA DE FRAUDE</h2> + +<p>É proibido mentir sobre identidade, cargo, vínculo, autorização, resultado, certificado, permissão, propriedade de conta ou qualquer outro elemento usado para enganar terceiros.</p> + +<h2>REGRA 4 - NADA DE SPAM</h2> + +<p>É proibido flood, propaganda repetitiva, envio massivo de mensagens, links enganosos, automação não autorizada e publicação insistente de conteúdo irrelevante.</p> + +<h2>REGRA 5 - NADA DE CONTEÚDO ILEGAL OU PERIGOSO</h2> + +<p>É proibido usar o sistema para distribuir conteúdo ilegal, instruções maliciosas, golpe, phishing, malware, material de exploração, ameaça real ou incentivo a prática criminosa.</p> + +<h2>REGRA 6 - PRIVACIDADE IMPORTA</h2> + +<p>É proibido expor dados pessoais de terceiros sem autorização.<br> +Também é proibido publicar prints, e-mails, contatos, documentos, dados privados ou informações sensíveis com intenção de constranger, punir ou atacar alguém.</p> + +<h2>REGRA 7 - NADA DE BURLAR MODERAÇÃO</h2> + +<p>É proibido criar conta alternativa para escapar de advertência, suspensão, limitação ou banimento.<br> +Também é proibido usar terceiros para contornar punições.</p> + +<h2>REGRA 8 - USO TÉCNICO RESPONSÁVEL</h2> + +<p>É proibido explorar falhas do sistema, testar vulnerabilidades sem autorização, automatizar ações de forma agressiva, fazer scraping abusivo ou degradar a operação.</p> + +<h2>REGRA 9 - BOA-FÉ</h2> + +<p>Se o comportamento de um usuário não estiver literalmente listado, mas for claramente malicioso, manipulador, abusivo ou prejudicial ao sistema, a administração poderá agir.</p> + +<h2>REGRA 10 - CUMPRIMENTO DAS DECISÕES ADMINISTRATIVAS</h2> + +<p>Discussão e recurso são permitidos.<br> +Tumultuar, ameaçar staff, pressionar outros usuários ou espalhar desinformação sobre ações de moderação não ajuda em nada e pode piorar a situação.</p> + +<h2>CONDUTA ESPERADA</h2> + +<ul><li>comunicar problemas com clareza;</li><li>respeitar usuários e equipe;</li><li>reportar abuso sem espetáculo;</li><li>manter o foco no uso legítimo do sistema;</li><li>agir como adulto funcional.</li></ul> + +<h2>CANAL DE CONTATO</h2> + +<p>Para suporte, denúncia ou recurso:<br> +admin@runv.club</p> + </main> + + <footer class="site-footer"> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> + </footer> + </div> +</body> +</html> diff --git a/site/public/wiki/visao-geral.html b/site/public/wiki/visao-geral.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>RUNV.CLUB - VISÃO GERAL DO SISTEMA — Wiki runv.club</title> + <meta name="description" content="RUNV.CLUB - VISÃO GERAL DO SISTEMA — wiki runv.club."> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="../assets/style.css"> +</head> +<body> + <div class="wrap"> + <nav class="top-nav"><a href="/">← runv.club</a></nav> + + <header> + <p class="eyebrow">runv.club</p> + <nav class="hero-nav wiki-hero-nav" aria-label="Páginas da wiki"> + <a href="/news/">Notícias</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/">Índice</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <span class="hero-nav-current" aria-current="page">Visão geral</span> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/contas-e-acesso.html">Contas e acesso</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/regras-da-comunidade.html">Regras</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/punicoes-e-moderacao.html">Punições</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/privacidade-e-seguranca.html">Privacidade</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/wiki/faq.html">FAQ wiki</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/junte-se/">Junte-se</a> + </nav> + </header> + + <main class="section prose-block subpage-main wiki-main"> +<h1 class="hero-title subpage-title wiki-page-title">RUNV.CLUB - VISÃO GERAL DO SISTEMA</h1> + +<h2>O QUE É O RUNV.CLUB</h2> + +<p>O runv.club é um sistema voltado para acesso, organização, suporte e interação em ambiente digital. Ele pode ser usado como portal de entrada, base de informações, área de usuário, espaço de comunidade e central de apoio operacional.</p> + +<h2>OBJETIVO DO SISTEMA</h2> + +<p>O objetivo principal do runv.club é concentrar recursos, orientações, acesso e comunicação em um único lugar, reduzindo confusão e facilitando o uso do sistema pelos usuários.</p> + +<h2>PRINCÍPIOS BÁSICOS</h2> + +<p>1. Clareza<br> + O usuário precisa entender onde entra, o que faz e onde resolve problemas.</p> + +<p>2. Acesso simples<br> + Cadastro, login, recuperação de senha e suporte devem ser diretos.</p> + +<p>3. Segurança<br> + A conta é individual e deve ser protegida.</p> + +<p>4. Respeito à comunidade<br> + Nenhum sistema sobrevive quando tolera abuso, fraude ou perseguição.</p> + +<p>5. Responsabilidade<br> + Toda ação dentro do sistema pode ser analisada para fins de segurança, suporte e moderação.</p> + +<h2>PARA QUEM O SISTEMA SERVE</h2> + +<p>O runv.club não deve ser tratado como um ambiente fechado para um único perfil, salvo se a administração definir isso formalmente. Na ausência dessa restrição explícita, o sistema pode atender usuários com diferentes perfis, desde que cumpram as regras.</p> + +<h2>O QUE O USUÁRIO ENCONTRA NO SISTEMA</h2> + +<ul><li>área de acesso;</li><li>informações operacionais;</li><li>regras de uso;</li><li>canais de suporte;</li><li>respostas para dúvidas frequentes;</li><li>políticas de moderação e segurança.</li></ul> + +<h2>O QUE O SISTEMA NÃO É</h2> + +<p>O runv.club não é terra sem lei.<br> +Também não é um espaço para spam, fraude, assédio, manipulação de usuários, invasão de conta, publicação de conteúdo ilegal ou sabotagem da operação.</p> + +<h2>SUPORTE</h2> + +<p>Em caso de dúvida, problema técnico, contestação de punição ou necessidade de orientação, o contato oficial é:<br> +admin@runv.club</p> + +<h2>DIRETRIZ FINAL</h2> + +<p>Se uma funcionalidade não estiver explicada, isso não significa que qualquer comportamento esteja liberado. O uso do sistema sempre depende de boa-fé, respeito às regras publicadas e preservação da segurança da plataforma.</p> + </main> + + <footer class="site-footer"> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> + </footer> + </div> +</body> +</html> diff --git a/site/wiki/01_index.txt b/site/wiki/01_index.txt @@ -0,0 +1,48 @@ +RUNV.CLUB - WIKI BASE (TXT) + +Esta pasta reúne uma base inicial de wiki para o sistema runv.club. +O objetivo é organizar, de forma simples e reutilizável, as informações principais de operação, acesso, regras de convivência, moderação, privacidade e FAQ. + +ARQUIVOS DESTA WIKI + +1. 01_index.txt + Visão geral da wiki e mapa dos arquivos. + +2. 02_visao-geral.txt + Explica o que é o runv.club, para quem serve e quais são seus objetivos. + +3. 03_contas-e-acesso.txt + Cadastro, login, recuperação de senha, acesso por terceiros e boas práticas de conta. + +4. 04_regras-da-comunidade.txt + Regras de convivência, conduta aceitável e conduta proibida. + +5. 05_punicoes-e-moderacao.txt + Causas para advertência, suspensão, banimento e processo de análise. + +6. 06_privacidade-e-seguranca.txt + Diretrizes gerais de privacidade, segurança e responsabilidade do usuário. + +7. 07_faq.txt + Perguntas frequentes do sistema, incluindo dúvidas de acesso, uso e suporte. + +CONTATO OFICIAL + +E-mail de contato: admin@runv.club + +NOTA IMPORTANTE + +Esta base foi escrita como um pacote inicial de wiki para o runv.club. +Ela pode ser usada como documentação pública, central de ajuda, base interna de suporte ou material para página de regras. + +RECOMENDAÇÃO PRÁTICA + +Se você publicar esta wiki, mantenha a mesma lógica: +- página inicial curta; +- regras objetivas; +- punições previsíveis; +- FAQ sem enrolação; +- contato visível; +- política de segurança clara. + +O erro comum de sistemas pequenos é ser vago nas regras e arbitrário na punição. Isso destrói confiança. Esta wiki foi montada para evitar esse problema. diff --git a/site/wiki/02_visao-geral.txt b/site/wiki/02_visao-geral.txt @@ -0,0 +1,53 @@ +RUNV.CLUB - VISÃO GERAL DO SISTEMA + +O QUE É O RUNV.CLUB + +O runv.club é um sistema voltado para acesso, organização, suporte e interação em ambiente digital. Ele pode ser usado como portal de entrada, base de informações, área de usuário, espaço de comunidade e central de apoio operacional. + +OBJETIVO DO SISTEMA + +O objetivo principal do runv.club é concentrar recursos, orientações, acesso e comunicação em um único lugar, reduzindo confusão e facilitando o uso do sistema pelos usuários. + +PRINCÍPIOS BÁSICOS + +1. Clareza + O usuário precisa entender onde entra, o que faz e onde resolve problemas. + +2. Acesso simples + Cadastro, login, recuperação de senha e suporte devem ser diretos. + +3. Segurança + A conta é individual e deve ser protegida. + +4. Respeito à comunidade + Nenhum sistema sobrevive quando tolera abuso, fraude ou perseguição. + +5. Responsabilidade + Toda ação dentro do sistema pode ser analisada para fins de segurança, suporte e moderação. + +PARA QUEM O SISTEMA SERVE + +O runv.club não deve ser tratado como um ambiente fechado para um único perfil, salvo se a administração definir isso formalmente. Na ausência dessa restrição explícita, o sistema pode atender usuários com diferentes perfis, desde que cumpram as regras. + +O QUE O USUÁRIO ENCONTRA NO SISTEMA + +- área de acesso; +- informações operacionais; +- regras de uso; +- canais de suporte; +- respostas para dúvidas frequentes; +- políticas de moderação e segurança. + +O QUE O SISTEMA NÃO É + +O runv.club não é terra sem lei. +Também não é um espaço para spam, fraude, assédio, manipulação de usuários, invasão de conta, publicação de conteúdo ilegal ou sabotagem da operação. + +SUPORTE + +Em caso de dúvida, problema técnico, contestação de punição ou necessidade de orientação, o contato oficial é: +admin@runv.club + +DIRETRIZ FINAL + +Se uma funcionalidade não estiver explicada, isso não significa que qualquer comportamento esteja liberado. O uso do sistema sempre depende de boa-fé, respeito às regras publicadas e preservação da segurança da plataforma. diff --git a/site/wiki/03_contas-e-acesso.txt b/site/wiki/03_contas-e-acesso.txt @@ -0,0 +1,58 @@ +RUNV.CLUB - CONTAS E ACESSO + +CADASTRO + +O cadastro deve ser feito com informações verdadeiras e minimamente consistentes. +Criar conta com identidade falsa para enganar terceiros, burlar punição, cometer fraude ou simular legitimidade é motivo para ação da moderação. + +LOGIN + +O acesso ao sistema é individual. Cada usuário é responsável por proteger seu e-mail, senha e meios de autenticação vinculados à conta. + +RECUPERAÇÃO DE SENHA + +Se o usuário perder acesso à conta, deve usar os meios oficiais de recuperação disponíveis no sistema. +Se ainda assim não resolver, deve contatar: +admin@runv.club + +COMPARTILHAMENTO DE CONTA + +É proibido compartilhar conta quando isso: +- comprometer a segurança; +- dificultar auditoria de ações; +- permitir fraude, abuso ou evasão de punição; +- gerar uso indevido de permissões. + +A administração pode limitar, suspender ou encerrar contas usadas por múltiplas pessoas de forma irregular. + +RESPONSABILIDADE PELA CONTA + +Tudo o que for feito pela conta poderá ser atribuído ao titular até que exista evidência concreta de comprometimento, invasão ou uso indevido por terceiros. + +BOAS PRÁTICAS DE SEGURANÇA + +- usar senha forte; +- não reutilizar senha vazada em outros serviços; +- não enviar senha por mensagem; +- não clicar em links suspeitos; +- sair da conta em dispositivos públicos; +- reportar acesso estranho imediatamente. + +ACESSO SUSPENSO OU BLOQUEADO + +A conta poderá ser temporariamente limitada ou suspensa quando houver: +- indício de invasão; +- atividade automatizada suspeita; +- tentativa de fraude; +- reincidência em infrações; +- risco à integridade do sistema. + +ERRO COMUM DO USUÁRIO + +Muita gente trata a própria conta como se fosse descartável. Isso é estupidez operacional. +Se a conta concentra histórico, permissões e suporte, então ela precisa ser tratada como ativo. Quem ignora isso aumenta o próprio risco. + +SUPORTE DE ACESSO + +Qualquer problema de login, recuperação, segurança ou suspeita de comprometimento deve ser comunicado para: +admin@runv.club diff --git a/site/wiki/04_regras-da-comunidade.txt b/site/wiki/04_regras-da-comunidade.txt @@ -0,0 +1,59 @@ +RUNV.CLUB - REGRAS DA COMUNIDADE + +REGRA 1 - RESPEITO BÁSICO + +É proibido atacar, humilhar, ameaçar, perseguir ou intimidar outros usuários. +Discussão não é desculpa para assédio. +Discordância é normal. Comportamento abusivo não é. + +REGRA 2 - NADA DE DISCURSO DE ÓDIO + +É proibido conteúdo que promova discriminação, desumanização, hostilidade ou exclusão com base em raça, cor, origem, etnia, nacionalidade, religião, deficiência, sexo, identidade de gênero ou orientação sexual. + +REGRA 3 - NADA DE FRAUDE + +É proibido mentir sobre identidade, cargo, vínculo, autorização, resultado, certificado, permissão, propriedade de conta ou qualquer outro elemento usado para enganar terceiros. + +REGRA 4 - NADA DE SPAM + +É proibido flood, propaganda repetitiva, envio massivo de mensagens, links enganosos, automação não autorizada e publicação insistente de conteúdo irrelevante. + +REGRA 5 - NADA DE CONTEÚDO ILEGAL OU PERIGOSO + +É proibido usar o sistema para distribuir conteúdo ilegal, instruções maliciosas, golpe, phishing, malware, material de exploração, ameaça real ou incentivo a prática criminosa. + +REGRA 6 - PRIVACIDADE IMPORTA + +É proibido expor dados pessoais de terceiros sem autorização. +Também é proibido publicar prints, e-mails, contatos, documentos, dados privados ou informações sensíveis com intenção de constranger, punir ou atacar alguém. + +REGRA 7 - NADA DE BURLAR MODERAÇÃO + +É proibido criar conta alternativa para escapar de advertência, suspensão, limitação ou banimento. +Também é proibido usar terceiros para contornar punições. + +REGRA 8 - USO TÉCNICO RESPONSÁVEL + +É proibido explorar falhas do sistema, testar vulnerabilidades sem autorização, automatizar ações de forma agressiva, fazer scraping abusivo ou degradar a operação. + +REGRA 9 - BOA-FÉ + +Se o comportamento de um usuário não estiver literalmente listado, mas for claramente malicioso, manipulador, abusivo ou prejudicial ao sistema, a administração poderá agir. + +REGRA 10 - CUMPRIMENTO DAS DECISÕES ADMINISTRATIVAS + +Discussão e recurso são permitidos. +Tumultuar, ameaçar staff, pressionar outros usuários ou espalhar desinformação sobre ações de moderação não ajuda em nada e pode piorar a situação. + +CONDUTA ESPERADA + +- comunicar problemas com clareza; +- respeitar usuários e equipe; +- reportar abuso sem espetáculo; +- manter o foco no uso legítimo do sistema; +- agir como adulto funcional. + +CANAL DE CONTATO + +Para suporte, denúncia ou recurso: +admin@runv.club diff --git a/site/wiki/05_punicoes-e-moderacao.txt b/site/wiki/05_punicoes-e-moderacao.txt @@ -0,0 +1,92 @@ +RUNV.CLUB - PUNIÇÕES E MODERAÇÃO + +OBJETIVO DA MODERAÇÃO + +A moderação existe para proteger usuários, preservar a operação do sistema e impedir que uma minoria destrua a utilidade do ambiente para todos os outros. + +TIPOS DE MEDIDA + +1. Orientação informal + Usada em erro leve, isolado e sem dano relevante. + +2. Advertência formal + Usada quando há violação clara de regra, mas ainda cabe correção de conduta. + +3. Limitação temporária + Pode restringir postagem, interação, recurso técnico ou acesso parcial. + +4. Suspensão temporária + Bloqueio por período definido, usado em reincidência ou infração moderada/grave. + +5. Banimento permanente + Encerramento definitivo do acesso, usado quando o risco é alto ou a confiança foi quebrada. + +CAUSAS COMUNS DE ADVERTÊNCIA + +- linguagem hostil sem ameaça explícita; +- flood leve; +- desorganização recorrente apesar de orientação; +- desrespeito pontual às regras de uso. + +CAUSAS COMUNS DE SUSPENSÃO + +- reincidência após advertência; +- assédio continuado; +- spam insistente; +- exposição indevida de terceiros; +- tentativa de burlar limitação; +- uso abusivo de recursos da plataforma; +- comportamento que prejudique a operação ou o suporte. + +CAUSAS COMUNS DE BANIMENTO + +- fraude; +- phishing, golpe ou engenharia social; +- invasão, tentativa de invasão ou exploração maliciosa; +- publicação de conteúdo ilegal grave; +- ameaça real a usuários ou equipe; +- evasão de punição com contas alternativas; +- reincidência grave que demonstre ausência total de boa-fé. + +CRITÉRIOS DE ANÁLISE + +A administração pode considerar: +- gravidade da conduta; +- intenção aparente; +- dano causado; +- histórico do usuário; +- risco de repetição; +- cooperação ou má-fé durante a apuração. + +NEM TUDO PRECISA SEGUIR ESCADA LINEAR + +Nem toda infração começa em aviso. +Essa fantasia de que todo caso precisa passar por aviso, depois suspensão, depois banimento, independentemente da gravidade, é ingênua. +Fraude séria, ameaça real, golpe ou ataque técnico podem justificar banimento direto. + +RECURSO + +O usuário pode contestar medida aplicada por meio do canal oficial: +admin@runv.club + +O recurso deve conter: +- identificação da conta; +- descrição objetiva do caso; +- motivo da contestação; +- provas, se existirem. + +REGRAS DO RECURSO + +- recurso sem objetividade atrapalha; +- mentira piora a situação; +- pressão emocional não substitui evidência; +- agressividade contra a equipe pode gerar nova penalidade. + +DECISÃO FINAL + +A administração pode revisar, manter, reduzir ou ampliar a medida aplicada com base nos elementos disponíveis. + +POLÍTICA CONTRA IMPUNIDADE + +Sistema sem execução consistente de regras vira piada. +Se a regra existe, ela precisa produzir consequência real. diff --git a/site/wiki/06_privacidade-e-seguranca.txt b/site/wiki/06_privacidade-e-seguranca.txt @@ -0,0 +1,64 @@ +RUNV.CLUB - PRIVACIDADE E SEGURANÇA + +PRINCÍPIO GERAL + +Privacidade e segurança não são perfumaria jurídica. São parte da operação básica de qualquer sistema minimamente sério. + +DADOS E RESPONSABILIDADE + +O usuário deve fornecer apenas os dados necessários para uso do sistema e manter suas informações atualizadas quando isso for relevante para acesso, suporte e segurança. + +A administração deve tratar os dados com cuidado, limitar acessos internos e reduzir exposição desnecessária. + +O USUÁRIO TAMBÉM TEM RESPONSABILIDADE + +Não adianta exigir segurança da plataforma e depois usar senha fraca, compartilhar conta, clicar em golpe e ignorar alertas. +Segurança é responsabilidade dividida. + +O QUE O USUÁRIO DEVE EVITAR + +- compartilhar credenciais; +- publicar dados pessoais de terceiros; +- usar dispositivos inseguros para acesso sensível; +- ignorar indícios de invasão ou comprometimento; +- instalar extensões suspeitas ou software duvidoso no dispositivo usado para login. + +INCIDENTES DE SEGURANÇA + +Qualquer suspeita de: +- acesso indevido; +- roubo de conta; +- fraude; +- link malicioso; +- falha técnica com impacto em segurança; +- vazamento de informação; + +deve ser comunicada imediatamente para: +admin@runv.club + +ANÁLISE DE EVENTOS + +Para proteger a plataforma, a administração pode analisar registros técnicos, padrões de uso, histórico de acesso e evidências relacionadas a incidentes, abuso e moderação. + +LIMITAÇÃO IMPORTANTE + +Nenhum sistema online é magicamente invulnerável. +Prometer risco zero é propaganda, não verdade. +O que importa é ter prevenção, resposta rápida, registro e correção. + +BOAS PRÁTICAS OPERACIONAIS + +- revisar acessos suspeitos; +- registrar incidentes relevantes; +- remover permissões desnecessárias; +- corrigir falhas confirmadas; +- orientar usuários afetados quando necessário. + +PRIVACIDADE DE TERCEIROS + +É proibido usar o sistema para coletar, copiar, divulgar ou explorar dados de outros usuários sem base legítima e sem autorização. + +CONTATO OFICIAL + +Dúvidas sobre segurança, privacidade ou incidente: +admin@runv.club diff --git a/site/wiki/07_faq.txt b/site/wiki/07_faq.txt @@ -0,0 +1,46 @@ +RUNV.CLUB - FAQ + +1. O que é o runv.club? +O runv.club é um sistema de acesso, organização, suporte e uso em ambiente digital. Ele centraliza informações, regras, canais de contato e recursos operacionais. + +2. O sistema é só para alunos do Portal IDEA? +Não. + +3. Como entro no sistema? +O acesso depende do fluxo disponível no ambiente: cadastro, login e, quando aplicável, recuperação de senha ou outro método oficial de autenticação. + +4. Esqueci minha senha. O que faço? +Use a opção oficial de recuperação de senha. Se o problema continuar, entre em contato por admin@runv.club. + +5. Posso compartilhar minha conta com outra pessoa? +Não é recomendado e pode ser proibido, especialmente quando isso compromete segurança, auditoria, responsabilidade da conta ou regras do sistema. + +6. Minha conta pode ser suspensa? +Sim. Contas podem ser advertidas, limitadas ou suspensas quando houver infração de regras, abuso, fraude, evasão de punição, spam, assédio ou risco à operação. + +7. O que pode causar banimento? +Fraude, golpe, phishing, invasão, exploração maliciosa, conteúdo ilegal grave, ameaça real, assédio grave, evasão de punição e reincidência séria. + +8. Toda punição começa com aviso? +Não. Infrações leves podem começar com orientação ou advertência. Infrações graves podem gerar suspensão imediata ou banimento direto. + +9. Como denuncio abuso ou comportamento suspeito? +Envie relato objetivo com evidências, se houver, para admin@runv.club. + +10. Como recorrer de uma punição? +Entre em contato por admin@runv.club, identifique a conta, explique o caso de forma objetiva e envie qualquer prova relevante. + +11. O sistema guarda informações de uso? +Pode haver registro técnico e histórico operacional para fins de segurança, suporte, moderação e integridade da plataforma. + +12. Posso publicar dados de outras pessoas? +Não. Exposição de dados pessoais, documentos, contatos, prints privados ou informações sensíveis sem autorização pode gerar punição grave. + +13. O que fazer se eu suspeitar que minha conta foi invadida? +Troque a senha imediatamente, encerre sessões suspeitas se isso existir no sistema e comunique o caso para admin@runv.club. + +14. Como falar com a administração? +Pelo e-mail oficial: admin@runv.club + +15. Onde vejo as regras do sistema? +Na wiki oficial do runv.club, especialmente nos arquivos de regras da comunidade, moderação, privacidade e acesso. diff --git a/site/wiki/build_wiki.py b/site/wiki/build_wiki.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +""" +Gera HTML estático em site/public/wiki/ a partir dos .txt em site/wiki/. +Executar localmente antes de site/genlanding.py. Não copia para o servidor +por si — só o conteúdo de site/public/ é implantado. + +Apenas biblioteca padrão Python 3. +""" + +from __future__ import annotations + +import html +import re +import sys +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +SITE_DIR = SCRIPT_DIR.parent +OUT_DIR = SITE_DIR / "public" / "wiki" +SITEMAP_PATH = SITE_DIR / "public" / "sitemap.xml" + +TXT_GLOB = "[0-9][0-9]_*.txt" +SLUG_RE = re.compile(r"^(\d+)_(.+)\.txt$") + + +def eprint(*args: object) -> None: + print(*args, file=sys.stderr) + + +def is_heading_line(s: str) -> bool: + s = s.strip() + if not s or len(s) > 120: + return False + letters = [c for c in s if c.isalpha()] + if not letters: + return False + return all(c.isupper() for c in letters) + + +def paragraph_blocks(text: str) -> list[list[str]]: + lines = text.strip().splitlines() + blocks: list[list[str]] = [] + cur: list[str] = [] + for line in lines: + if not line.strip(): + if cur: + blocks.append(cur) + cur = [] + else: + cur.append(line.rstrip()) + if cur: + blocks.append(cur) + return blocks + + +def block_to_html(block: list[str], *, is_first: bool) -> str: + if len(block) == 1: + line = block[0].strip() + if is_first: + return f'<h1 class="hero-title subpage-title wiki-page-title">{html.escape(line)}</h1>' + if is_heading_line(line): + return f"<h2>{html.escape(line)}</h2>" + return f"<p>{html.escape(line)}</p>" + + stripped = [l.strip() for l in block if l.strip()] + if stripped and all( + s.startswith("- ") or s.startswith("– ") or s.startswith("— ") for s in stripped + ): + items = [] + for s in stripped: + for prefix in ("- ", "– ", "— "): + if s.startswith(prefix): + items.append(s[len(prefix) :]) + break + lis = "".join(f"<li>{html.escape(i)}</li>" for i in items) + return f"<ul>{lis}</ul>" + + inner = "<br>\n".join(html.escape(l) for l in block) + return f"<p>{inner}</p>" + + +def txt_to_article_body(raw: str) -> str: + blocks = paragraph_blocks(raw) + parts: list[str] = [] + for i, b in enumerate(blocks): + parts.append(block_to_html(b, is_first=(i == 0))) + return "\n\n".join(parts) + + +def page_shell( + *, + title: str, + description: str, + body_main: str, + nav_pages: list[tuple[str, str]], + current_slug: str | None, +) -> str: + nav_items = [] + for slug, label in nav_pages: + if current_slug is not None and slug == current_slug: + nav_items.append( + f'<span class="hero-nav-current" aria-current="page">{html.escape(label)}</span>' + ) + else: + href = "/wiki/" if slug == "index" else f"/wiki/{slug}.html" + nav_items.append(f'<a href="{html.escape(href, quote=True)}">{html.escape(label)}</a>') + nav_inner = '\n <span class="hero-nav-sep" aria-hidden="true">·</span>\n '.join( + nav_items + ) + return f"""<!DOCTYPE html> +<html lang="pt-BR"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>{html.escape(title)}</title> + <meta name="description" content="{html.escape(description)}"> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400&family=IBM+Plex+Mono:wght@400;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="../assets/style.css"> +</head> +<body> + <div class="wrap"> + <nav class="top-nav"><a href="/">← runv.club</a></nav> + + <header> + <p class="eyebrow">runv.club</p> + <nav class="hero-nav wiki-hero-nav" aria-label="Páginas da wiki"> + <a href="/news/">Notícias</a> + <span class="hero-nav-sep" aria-hidden="true">·</span> + {nav_inner} + <span class="hero-nav-sep" aria-hidden="true">·</span> + <a href="/junte-se/">Junte-se</a> + </nav> + </header> + + <main class="section prose-block subpage-main wiki-main"> +{body_main} + </main> + + <footer class="site-footer"> + <p>Administração: <a href="mailto:admin@runv.club">admin@runv.club</a><span class="footer-sep" aria-hidden="true"> · </span><a href="/faq/" class="footer-link-discrete">FAQ</a></p> + </footer> + </div> +</body> +</html> +""" + + +LABELS: dict[str, str] = { + "index": "Índice", + "visao-geral": "Visão geral", + "contas-e-acesso": "Contas e acesso", + "regras-da-comunidade": "Regras", + "punicoes-e-moderacao": "Punições", + "privacidade-e-seguranca": "Privacidade", + "faq": "FAQ wiki", +} + + +def slug_and_label(path: Path) -> tuple[str, str] | None: + m = SLUG_RE.match(path.name) + if not m: + return None + slug = m.group(2) + label = LABELS.get(slug, slug.replace("-", " ").title()) + return slug, label + + +def first_line_title(raw: str) -> str: + for line in raw.strip().splitlines(): + t = line.strip() + if t: + return t[:70] + ("…" if len(t) > 70 else "") + return "Wiki" + + +def build_nav_order(paths: list[Path]) -> list[tuple[str, str]]: + ordered: list[tuple[str, str]] = [] + for p in sorted(paths): + sl = slug_and_label(p) + if sl: + ordered.append(sl) + # Índice primeiro na nav + idx = next((i for i, (s, _) in enumerate(ordered) if s == "index"), None) + if idx is not None and idx > 0: + ordered.insert(0, ordered.pop(idx)) + return ordered + + +def patch_sitemap(wiki_urls: list[str]) -> None: + if not SITEMAP_PATH.is_file(): + return + text = SITEMAP_PATH.read_text(encoding="utf-8") + marker_start = " <!-- wiki:gerado -->" + marker_end = " <!-- /wiki:gerado -->" + block_lines = [marker_start] + for url in wiki_urls: + block_lines.append(" <url>") + block_lines.append(f" <loc>{html.escape(url)}</loc>") + block_lines.append(" </url>") + block_lines.append(marker_end) + new_block = "\n".join(block_lines) + "\n" + + if marker_start in text and marker_end in text: + before, rest = text.split(marker_start, 1) + _, after = rest.split(marker_end, 1) + text = before + new_block + after.lstrip("\n") + else: + text = text.replace( + "</urlset>", + new_block + "</urlset>", + 1, + ) + SITEMAP_PATH.write_text(text, encoding="utf-8") + + +def main() -> int: + txt_files = sorted(SCRIPT_DIR.glob(TXT_GLOB)) + if not txt_files: + eprint("Nenhum ficheiro", TXT_GLOB, "em", SCRIPT_DIR) + return 1 + + OUT_DIR.mkdir(parents=True, exist_ok=True) + nav_pages = build_nav_order(txt_files) + + base_url = "https://runv.club" + wiki_urls: list[str] = [f"{base_url}/wiki/"] + + for path in txt_files: + sl = slug_and_label(path) + if not sl: + continue + slug, _label = sl + raw = path.read_text(encoding="utf-8") + title_line = first_line_title(raw) + article = txt_to_article_body(raw) + + if slug == "index": + toc = ['<nav class="wiki-toc" aria-label="Nesta wiki"><p class="wiki-toc-title">Páginas</p><ul>'] + for s, lab in nav_pages: + if s == "index": + continue + toc.append( + f'<li><a href="/wiki/{html.escape(s, quote=True)}.html">{html.escape(lab)}</a></li>' + ) + toc.append("</ul></nav>") + body_main = "\n".join(toc) + "\n" + article + out_name = "index.html" + current = "index" + desc = "Mapa e índice da wiki runv.club." + else: + body_main = article + out_name = f"{slug}.html" + current = slug + desc = f"{title_line} — wiki runv.club." + wiki_urls.append(f"{base_url}/wiki/{slug}.html") + + full_title = f"{title_line} — Wiki runv.club" if slug != "index" else "Wiki — runv.club" + html_out = page_shell( + title=full_title, + description=desc, + body_main=body_main, + nav_pages=nav_pages, + current_slug=current, + ) + (OUT_DIR / out_name).write_text(html_out, encoding="utf-8") + print("Wrote", OUT_DIR / out_name) + + wiki_urls = sorted(set(wiki_urls)) + patch_sitemap(wiki_urls) + print("Updated", SITEMAP_PATH) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())