runv-server

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

commit 9e010b7150f86e8eafbcdcaee5d45c686d2077e8
parent 74505af9870bc3f30a9645e4c5ae41fd32cdefdb
Author: Pablo Murad <pablo@pablomurad.com>
Date:   Sat, 21 Mar 2026 16:38:35 -0300

fix(alt-protocols): align Gemini URLs with Molly resolvePath (v0.11)

Molly Brown maps home capsules from path segment ~username (tilde + name), not
/~/username/. Replace TempRedirects with /~/... -> /~...; update docs, README,
skel, and create_runv_user copy. Note DynamicUser molly-brown in alt_protocols.

Made-with: Cursor

Diffstat:
MREADME.md | 2+-
Mscripts/admin/create_runv_user.py | 4++--
Mscripts/admin/setup_alt_protocols.py | 17+++++++----------
Mscripts/docs/alt_protocols.md | 18+++++++++---------
Mtools/skel/public_gemini/index.gmi | 2+-
5 files changed, 20 insertions(+), 23 deletions(-)

diff --git a/README.md b/README.md @@ -4,4 +4,4 @@ Repositório de automação e documentação para **runv.club** (pubnix Debian). ## Gemini (Molly Brown) -O capsule de cada utilizador **não** está em `gemini://runv.club/USERNAME` (isso seria o path `/USERNAME`, que no servidor não corresponde à home). O formato correcto é **`gemini://runv.club/~/USERNAME/`** (path **`/~/USERNAME/`**), ou **`gemini://runv.club/~USERNAME/`** (redirect para o anterior). Requer Molly a correr, symlink em `/var/gemini/users/USERNAME`, home e `public_gemini` atravessáveis pelo utilizador do serviço — ver [`scripts/docs/alt_protocols.md`](scripts/docs/alt_protocols.md). +O capsule de cada utilizador **não** está em `gemini://runv.club/USERNAME` (path `/USERNAME`). O formato correcto no Molly Brown é **`gemini://runv.club/~USERNAME/`** (path **`/~USERNAME/`**, tilde **colado** ao nome). Links com slash extra (`gemini://runv.club/~/USERNAME/`) devem redireccionar após **`setup_alt_protocols.py` v0.11+** com **`--force`** no servidor. Requer Molly a correr, symlink em `/var/gemini/users/USERNAME`, home e `public_gemini` atravessáveis — ver [`scripts/docs/alt_protocols.md`](scripts/docs/alt_protocols.md). diff --git a/scripts/admin/create_runv_user.py b/scripts/admin/create_runv_user.py @@ -418,7 +418,7 @@ Tudo o que colocares em **`public_html`** pode ser lido pelo mundo via HTTP no e - **Gopher:** edita `~/public_gopher/gophermap` (e outros ficheiros nessa pasta). URL típica: `gopher://{DEFAULT_GEMINI_HOST_PUBLIC}/1/~{username}` (o caminho exacto depende do servidor). -- **Gemini:** edita `~/public_gemini/index.gmi`. URL canónica: `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/~/{username}/` (path `/~/{username}/`, **com barra final**); `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/~{username}/` também funciona (redirect). `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/{username}` **não** é o teu capsule. +- **Gemini:** edita `~/public_gemini/index.gmi`. URL canónica: `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/~{username}/` (path **`/~{username}/`**, tilde colado ao nome); `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/~/{username}/` redirecciona no servidor (v0.11+). `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/{username}` **não** é o teu capsule. - Mantém **755** nas pastas públicas e **644** nos ficheiros, para o servidor conseguir ler. ## Comandos úteis na shell @@ -478,7 +478,7 @@ iDocumentação: man gophermap (no pacote gophernicus). fake NULL 0 def default_gemini_index_gmi(username: str) -> str: return f"""# ~{username} — runv.club (Gemini) -Bem-vindo ao teu capsule em `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/~/{username}/` (canónica; barra final). `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/~{username}/` também funciona. `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/{username}` não aponta para aqui. +Bem-vindo ao teu capsule em `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/~{username}/` (canónica Molly: segmento **`~{username}`**). `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/~/{username}/` redirecciona. `gemini://{DEFAULT_GEMINI_HOST_PUBLIC}/{username}` não aponta para aqui. Edita este ficheiro em `~/public_gemini/index.gmi`. Mantém pastas **755** e ficheiros **644**. diff --git a/scripts/admin/setup_alt_protocols.py b/scripts/admin/setup_alt_protocols.py @@ -7,7 +7,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.10 — runv.club +Versão 0.11 — runv.club """ from __future__ import annotations @@ -32,7 +32,7 @@ from typing import Any, Final # Constantes # --------------------------------------------------------------------------- -VERSION: Final[str] = "0.10" +VERSION: Final[str] = "0.11" LETSENCRYPT_LIVE: Final[Path] = Path("/etc/letsencrypt/live") LETSENCRYPT_ARCHIVE: Final[Path] = Path("/etc/letsencrypt/archive") @@ -73,7 +73,7 @@ iEdita este ficheiro em ~/public_gopher/gophermap. fake NULL 0 DEFAULT_USER_INDEX_GMI: Final[str] = """# ~{username} — runv.club (Gemini) -Bem-vindo ao teu capsule em `gemini://runv.club/~/{username}/` (URL canónica Molly: path `/~/{username}/` com **barra final**). O endereço `gemini://runv.club/~{username}/` também funciona (redirect). **Não** confundas com `gemini://runv.club/{username}` — esse path **não** é o capsule (no Molly só há homes em `/~/…`). +Bem-vindo ao teu capsule em `gemini://runv.club/~{username}/` (canónico Molly: um só segmento de path **`/~{username}/`**, tilde **colado** ao nome). O formato `gemini://runv.club/~/{username}/` (slash extra) redirecciona para o canónico. **Não** uses `gemini://runv.club/{username}` — não é capsule. Edita este ficheiro em `~/public_gemini/index.gmi`. Mantém pastas **755** e ficheiros **644** para o servidor ler o conteúdo. @@ -293,14 +293,11 @@ ErrorLog = "{error_log.as_posix()}" GeminiExt = "gmi" ReadMollyFiles = true -# Molly Brown resolve espaços de utilizador em paths /~/user/… (HomeDocBase). -# A documentação oficial exige o prefixo ~/username/ *com* barra final; sem ela, -# /~/user não casa e devolve 51 — redireccionamos para /~/user/. -# URLs estilo Apache /~user/… redireccionam sem tocar em ficheiros no disco. +# Molly Brown (Go): resolvePath usa o *primeiro* segmento após / como ~NOME — ou seja +# path canónico /~username/… (tilde colado ao utilizador). O formato /~/username/ +# deixa o nome vazio e devolve 51; redireccionamos /~/… -> /~… antes do Stat. [TempRedirects] -"^/~/([^/]+)$" = "/~/$1/" -"^/~([^/]+)(/.+)$" = "/~/$1$2" -"^/~([^/]+)/?$" = "/~/$1/" +"^/~/([^/]+)(/.*)?$" = "/~$1$2" """ diff --git a/scripts/docs/alt_protocols.md b/scripts/docs/alt_protocols.md @@ -8,25 +8,25 @@ Script em **`scripts/admin/setup_alt_protocols.py`**: instala e configura **goph |-----------|---------------|------------------|------------| | **HTTP** (já existente) | `~/public_html/` | `index.html` | `http://runv.club/~user/` | | **Gopher** | `~/public_gopher/` | `gophermap` | `gopher://runv.club/1/~user` | -| **Gemini** | `~/public_gemini/` | `index.gmi` | `gemini://runv.club/~/user/` (canónico Molly); `gemini://runv.club/~user/` (redirect) | +| **Gemini** | `~/public_gemini/` | `index.gmi` | `gemini://runv.club/~user/` (canónico: path **`/~user/`**, tilde colado ao nome); `gemini://runv.club/~/user/` redirecciona (**v0.11+**) | **Gemini (molly-brown):** `DocBase = /var/gemini`, `HomeDocBase = users`, symlinks **`/var/gemini/users/<user>` → `~/public_gemini`**. ### Gopher vs Gemini: formato do endereço - **Gopher (gophernicus):** selectors **`~username/…`** (tilde **colado** ao nome), alinhado com URLs como **`gopher://runv.club/1/~user`**. Não há o mesmo «split» de path que no Molly. -- **Gemini (Molly Brown):** o servidor resolve caps em **`/~/username/…`**. URLs estilo Apache **`/~username/…`** são aceites graças a **`[TempRedirects]`** no `.conf` gerado pelo script (**v0.09+**). Pode usar indistintamente **`gemini://runv.club/~/user/`** (canónico) ou **`gemini://runv.club/~user/`** (compatível). +- **Gemini (Molly Brown):** o código Go do Molly trata o primeiro segmento do path como **`~username`** (tilde **colado** ao nome), p.ex. **`/~pmurad/`** → `DocBase/users/pmurad/`. O formato **`/~/pmurad/`** (slash entre `~` e o nome) faz o utilizador ficar **vazio** e devolve **51 Not found**. O `.conf` gerado (**v0.11+**) inclui **`[TempRedirects]`** que redirecciona **`/~/user…` → `/~user…`** para compatibilidade com links antigos. ### URLs Gemini que *não* são capsules de utilizador -O Molly **não** espelha o HTTP `mod_userdir` no mesmo path: **`gemini://runv.club/pmurad`** (path **`/pmurad`**) **não** aponta para a home — não existe ficheiro em `/var/gemini/pmurad`. O capsule está em **`gemini://runv.club/~/pmurad/`** (path **`/~/pmurad/`**, com **`~/`** e **barra final**). Sem a barra final, **`gemini://runv.club/~/pmurad`** era comum falhar com **51 Not found**; a partir do **v0.10** o `.conf` inclui redirect **`/~/user` → `/~/user/`**. +O Molly **não** espelha o HTTP `mod_userdir` no mesmo path: **`gemini://runv.club/pmurad`** (path **`/pmurad`**) **não** aponta para a home. O capsule correcto é **`gemini://runv.club/~pmurad/`** (path **`/~pmurad/`**). **`gemini://runv.club/~/pmurad/`** (slash extra) **não** é o formato que o `resolvePath` entende sozinho; com **v0.11+**, o `.conf` redirecciona (**30**) esse pedido para **`gemini://runv.club/~pmurad/`** (o cliente deve seguir o redirect). ## Travessia da home (`755` na política runv) -Apache (`mod_userdir`), **gophernicus** e **molly-brown** precisam de **execução para «others»** (`o+x`, mínimo) em **cada** componente do caminho até a pasta pública (`~/public_html`, `~/public_gopher`, `~/public_gemini`). O utilizador de runtime **não é o mesmo** em todos: no Debian o Molly costuma correr como **`www-data`**; o **gophernicus** usa o **`User=`** do unit (tipicamente `gophernicus`) — veja `/lib/systemd/system/gophernicus@.service`. Uma home em **`700`** impede a travessia: **HTTP, Gopher e Gemini** deixam de servir conteúdo (p.ex. Gemini **«Not found»** com `index.gmi` presente). +Apache (`mod_userdir`), **gophernicus** e **molly-brown** precisam de **execução para «others»** (`o+x`, mínimo) em **cada** componente do caminho até a pasta pública (`~/public_html`, `~/public_gopher`, `~/public_gemini`). O utilizador de runtime **não é o mesmo** em todos: no Debian o pacote **molly-brown** usa **`User=molly-brown`** com **`DynamicUser=yes`** (não `www-data`); o **gophernicus** usa o **`User=`** do unit (tipicamente `gophernicus`) — veja `/lib/systemd/system/gophernicus@.service`. Uma home em **`700`** impede a travessia: **HTTP, Gopher e Gemini** deixam de servir conteúdo (p.ex. Gemini **«Not found»** com `index.gmi` presente). - **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 bloco LE deixa de saltar incorrectamente). O **v0.09** adiciona redirects Molly `~user` → `~/user` e validação **`test -r`** do `gophermap` com o utilizador do serviço gophernicus. O **v0.10** adiciona redirect **`/~/user` → `/~/user/`** (barra final exigida pelo Molly para `HomeDocBase`). +- **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). Validação **`test -r`** do `gophermap` com o utilizador do serviço gophernicus mantém-se. - **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+) @@ -52,7 +52,7 @@ 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 symlink **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. Falha → **WARNING** (`public_gemini` `755`, `index.gmi` `644`, symlink `/var/gemini/users/<user>`). +- 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»). Falha → **WARNING** (`public_gemini` `755`, `index.gmi` `644`, symlink `/var/gemini/users/<user>`). Em **`--dry-run`**, só regista os comandos. Sem **`runuser`** (util-linux), estes passos são omitidos. @@ -115,7 +115,7 @@ Raro se o `.conf` aponta para `/var/lib/molly-brown/` e não há override que de - **Estado e porta:** `sudo systemctl status molly-brown@runv.club.service --no-pager` e `sudo ss -tlnp | grep 1965` (deve haver um processo a escutar em **1965/tcp**). - **Permissões TLS (frequente):** o Molly corre como utilizador não-root; se `privkey.pem` for só `root:root` `0600`, o arranque falha. Verifique `sudo namei -l /etc/letsencrypt/live/runv.club/privkey.pem` e compare com o utilizador do unit (`systemctl cat molly-brown@runv.club.service`). Soluções típicas: grupo `ssl-cert`, ACL, ou certificados num path legível pelo utilizador do serviço (mantendo segurança). - **Teste local:** `openssl s_client -connect 127.0.0.1:1965 -servername runv.club </dev/null 2>/dev/null | head -20` -- **Cliente (Lagrange, etc.):** teste `gemini://runv.club/~/user/` ou `gemini://runv.club/~user/` **depois** de `systemctl is-active molly-brown@runv.club.service` devolver `active`. +- **Cliente (Lagrange, etc.):** teste **`gemini://runv.club/~user/`** (canónico); `gemini://runv.club/~/user/` deve redireccionar (**v0.11+** no `.conf`) **depois** de `systemctl is-active molly-brown@runv.club.service` devolver `active`. ## Execução (root) @@ -133,7 +133,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.09+ e redirect **`/~/user/`** v0.10) 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/`. | | `--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. | @@ -170,6 +170,6 @@ Depois aplicam-se as mesmas exclusões que em **`patches/patch_irc.py`** (`IRC_P 6. Verificar `/etc/skel/public_gopher` e `public_gemini` após `tools.py` 7. Criar utilizador de teste com `create_runv_user.py` 8. `ls -la /home/teste/public_gopher/gophermap /home/teste/public_gemini/index.gmi` e `ls -la /var/gemini/users/teste` -9. Cliente Gopher/Gemini: `gopher://runv.club/1/~teste` e `gemini://runv.club/~/teste/` (ou `gemini://runv.club/~teste/` com redirect) +9. Cliente Gopher/Gemini: `gopher://runv.club/1/~teste` e **`gemini://runv.club/~teste/`** (canónico); opcionalmente `gemini://runv.club/~/teste/` → redirect **30** para o canónico (**v0.11+**) Versão do script: ver `python3 scripts/admin/setup_alt_protocols.py --version`. diff --git a/tools/skel/public_gemini/index.gmi b/tools/skel/public_gemini/index.gmi @@ -1,6 +1,6 @@ # Capsule Gemini — runv.club -O endereço canónico é `gemini://runv.club/~/NOME_UTILIZADOR/` (substitui pelo teu username Unix; **barra final**). `gemini://runv.club/~NOME_UTILIZADOR/` também funciona (redirect). `gemini://runv.club/NOME_UTILIZADOR` (sem `~/`) **não** é este capsule. Gopher usa selector `~NOME` sem barra extra — ver documentação do projeto. +O endereço canónico é `gemini://runv.club/~NOME_UTILIZADOR/` (tilde **colado** ao username Unix; **barra final**). `gemini://runv.club/~/NOME_UTILIZADOR/` redirecciona no servidor (setup_alt_protocols v0.11+). `gemini://runv.club/NOME_UTILIZADOR` **não** é este capsule. Gopher usa selector `~NOME` — ver documentação do projeto. Edita este ficheiro em `~/public_gemini/index.gmi`. Ficheiros `.gmi` são Texto Gemini; mantém pastas 755 e ficheiros 644 para o servidor conseguir ler.