runv-server

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

commit 291ff8fe1a57461580be413f656e1c9a98a81941
parent 87b35e300f02aeb310889a3609deb781a4bf171d
Author: Pablo Murad <pblmrd@gmail.com>
Date:   Mon, 23 Mar 2026 14:09:22 -0300

fix rss

Diffstat:
Mdocs/06-site-and-apache.md | 4++--
Mdocs/13-troubleshooting.md | 3++-
Msite/genlanding.py | 54+++++++++++++++++++++++++++++++++++++++++++-----------
3 files changed, 47 insertions(+), 14 deletions(-)

diff --git a/docs/06-site-and-apache.md b/docs/06-site-and-apache.md @@ -15,8 +15,8 @@ - Opcional: `--certbot` (incompatível com `--dev`). - Após cópia, por omissão chama `build_directory.py` para gravar `data/members.json` no DocumentRoot (`--no-refresh-members` para omitir). - **`--sync-public-only`:** só copia `site/public/` → DocumentRoot, `chown www-data` e regenera `members.json`; **não** altera Apache (uso típico após `create_runv_user.py` e disponível para correr à mão). -- O VirtualHost HTTP gerado inclui **`ForceType text/xml`** para **`/news/feed.rss`**, para o browser mostrar o XML do feed em vez de forçar descarga (Chromium com outros MIME). Se o **Certbot** criou um VirtualHost **HTTPS** separado, esse ficheiro não é regenerado pelo `genlanding` — pode ser necessário acrescentar o mesmo bloco `<Directory …/news>` com `<Files "feed.rss">` nesse vhost ou voltar a alinhar a configuração manualmente. -- Versão actual do script: constante `VERSION` no ficheiro (ex.: `0.06`). +- **RSS (`/news/feed.rss`):** o `genlanding` completo (sem `--sync-public-only`) grava `/etc/apache2/conf-available/runv-landing-rss-mime.conf` com **`ForceType text/xml`** e activa com **`a2enconf runv-landing-rss-mime`**. Esse snippet é **global** ao Apache, por isso aplica-se a **:80 e :443** sem editar o VirtualHost SSL que o Certbot gerou. Após mudar o DocumentRoot (ex. `--dev` vs produção), volte a correr o `genlanding` completo para actualizar o snippet. +- Versão actual do script: constante `VERSION` no ficheiro (ex.: `0.07`). ## TLS e DNS diff --git a/docs/13-troubleshooting.md b/docs/13-troubleshooting.md @@ -24,7 +24,8 @@ ## Feed RSS descarrega em vez de abrir no browser -- O vhost HTTP gerado pelo `genlanding` força `text/xml` em `/news/feed.rss`. Se só o vhost **HTTPS** (ex.: Certbot) servir o site, confira se esse ficheiro inclui o mesmo bloco ou volte a correr `genlanding` e funda alterações com o SSL existente (ver [06-site-and-apache.md](06-site-and-apache.md)). +- Com `genlanding` ≥ 0.07: confirme que existe **`/etc/apache2/conf-available/runv-landing-rss-mime.conf`**, que o symlink em `conf-enabled` está activo (`a2enconf runv-landing-rss-mime`) e que o `<Directory>` aponta ao **DocumentRoot correcto**; depois `sudo systemctl reload apache2`. O snippet cobre **HTTPS** sem tocar no ficheiro do Certbot (ver [06-site-and-apache.md](06-site-and-apache.md)). +- Instalações antigas só com `ForceType` no `:80`: acrescente o mesmo bloco `<Directory …/news><Files "feed.rss">ForceType text/xml</Files></Directory>` no VirtualHost **:443** ou migre correr o `genlanding` completo de novo. ## Quotas diff --git a/site/genlanding.py b/site/genlanding.py @@ -5,12 +5,13 @@ 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 e, em produção, -tenta ``genlanding --sync-public-only`` no fim (DocumentRoot existente). O VirtualHost abaixo -força ``text/xml`` em ``/news/feed.rss`` para o browser mostrar o feed em vez de descarregar. +tenta ``genlanding --sync-public-only`` no fim (DocumentRoot existente). O MIME ``text/xml`` para +``/news/feed.rss`` fica em ``conf-available/runv-landing-rss-mime.conf`` (``a2enconf``), aplicável +a :80 e :443 sem editar o vhost SSL do Certbot. Executar como root (excepto --dry-run). Apenas biblioteca padrão Python 3. -Versão 0.06 — runv.club +Versão 0.07 — runv.club """ from __future__ import annotations @@ -27,7 +28,7 @@ import sys from pathlib import Path from typing import Final -VERSION: Final[str] = "0.06" +VERSION: Final[str] = "0.07" EXIT_OK: Final[int] = 0 EXIT_USAGE: Final[int] = 1 EXIT_ERROR: Final[int] = 2 @@ -45,6 +46,10 @@ DEV_DOCUMENT_ROOT: Final[Path] = Path("/var/www/runv-dev/html") DEV_SITE_CONF: Final[str] = "runv-dev.conf" APACHE_SITES_AVAILABLE: Final[Path] = Path("/etc/apache2/sites-available") +APACHE_CONF_AVAILABLE: Final[Path] = Path("/etc/apache2/conf-available") +# Snippet global: aplica ForceType ao feed em todos os vhosts (:80 e :443), sem tocar no SSL do Certbot. +RSS_MIME_CONF_FILE: Final[str] = "runv-landing-rss-mime.conf" +RSS_MIME_CONF_STEM: Final[str] = "runv-landing-rss-mime" APACHE_CTL: Final[str] = "/usr/sbin/apache2ctl" DEFAULT_SITE: Final[str] = "000-default.conf" @@ -70,6 +75,21 @@ def log_tag_from_domain(domain: str) -> str: return re.sub(r"[^\w.-]+", "-", domain).strip("-") or "runv" +def render_rss_mime_conf_contents(document_root: Path) -> str: + """Snippet em conf-available: vale para :80 e :443 (evita editar o vhost SSL do Certbot à mão).""" + root = document_root.as_posix() + return f"""# Gerado por genlanding.py v{VERSION} — runv.club +# Chromium descarrega feed.rss com application/rss+xml; text/xml mostra o XML na aba. +# Global ao servidor para o caminho actual do DocumentRoot (volte a correr genlanding se mudar). + +<Directory {root}/news> + <Files "feed.rss"> + ForceType text/xml + </Files> +</Directory> +""" + + def render_vhost( *, server_name: str, @@ -79,6 +99,7 @@ def render_vhost( www_alias = f"www.{server_name}" return f"""# Gerado por genlanding.py v{VERSION} — runv.club # Não editar à mão sem saber o que faz; volte a correr o script ou ajuste e recarregue o Apache. +# MIME do feed RSS: conf-available/{RSS_MIME_CONF_FILE} (a2enconf {RSS_MIME_CONF_STEM}). <VirtualHost *:80> ServerName {server_name} @@ -96,13 +117,6 @@ def render_vhost( Require all granted </Directory> - # Chromium descarrega feed.rss com alguns MIME; text/xml mostra o XML na aba. - <Directory {document_root}/news> - <Files "feed.rss"> - ForceType text/xml - </Files> - </Directory> - ErrorLog ${{APACHE_LOG_DIR}}/{log_tag}-error.log CustomLog ${{APACHE_LOG_DIR}}/{log_tag}-access.log combined </VirtualHost> @@ -398,6 +412,24 @@ def main(argv: list[str] | None = None) -> int: run_cmd(["a2enmod", "userdir"], dry_run=args.dry_run) run_cmd(["a2enmod", "rewrite"], dry_run=args.dry_run) + rss_conf_path = APACHE_CONF_AVAILABLE / RSS_MIME_CONF_FILE + rss_body = render_rss_mime_conf_contents(document_root) + if args.dry_run: + print("--- conf-available (RSS MIME, :80 e :443) ---") + print(rss_body) + print(f" [dry-run] escreveria {rss_conf_path} ; a2enconf {RSS_MIME_CONF_STEM}") + else: + if not APACHE_CONF_AVAILABLE.is_dir(): + APACHE_CONF_AVAILABLE.mkdir(parents=True, exist_ok=True) + rss_conf_path.write_text(rss_body, encoding="utf-8") + os.chmod(rss_conf_path, 0o644) + print(f" [ok] RSS MIME: {rss_conf_path}") + run_cmd_allow_fail( + ["a2enconf", RSS_MIME_CONF_STEM], + dry_run=args.dry_run, + ok_hint="conf já activo", + ) + copy_landing(source, document_root, dry_run=args.dry_run) if not args.dry_run: chown_www_data(document_root, dry_run=False)