chore: auto-commit (4 archivos)

- .gitignore
- CAPABILITIES_TODO.md
- CHROMIUM_SYSTEM.md
- hoppscotch/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 00:16:47 +02:00
parent 0b3a1313c2
commit 65ca2b3d43
12 changed files with 745 additions and 1 deletions
+89
View File
@@ -98,6 +98,95 @@ Dos caminos (regla 9 de `CONVENTIONS.md`):
Dos procesos chromium **no** pueden compartir el mismo `--remote-debugging-port`. El `--port` en
cmdline sobreescribe al del fragmento global.
## Navegador gestionado por systemd — sesión persistente (cierre limpio)
**Problema resuelto (2026-06-10):** el navegador diario perdía la sesión de Google (y de cualquier
sitio) cada vez que se cerraba y reabría — aparecía deslogueado al volver a abrirlo.
### Causa raíz: el cierre sucio, NO el CDP
Cuando el proceso **master** de Chromium se mata con `SIGKILL` (en vez de `SIGTERM`), no llega a
vaciar a disco las cookies de sesión recién escritas, marca `profile.exit_type = "Crashed"` y al
reabrir la sesión está perdida. Esto pasaba porque el navegador, lanzado por rofi/wrapper, no estaba
atado a nada que le mandara `SIGTERM` con margen: al logout/apagado de XFCE o al morir el proceso
lanzador recibía `SIGKILL` directo, sin tiempo de flush.
Descartado tras experimento controlado (reusando la sesión real del usuario, sin re-login):
- Con CDP activo (`--remote-debugging-port=9222 --remote-allow-origins=*`) **y** cliente CDP
conectado, `myaccount.google.com` seguía **logueado**. No hay deslogueo por tener CDP.
- `navigator.webdriver` es **`false`** con CDP → no hay detección de automatización por JS ni
server-side. El flag `--disable-blink-features=AutomationControlled` no cambia nada aquí.
- Tras un cierre **limpio** (`SIGTERM`, `exit_type=SessionEnded`), las cookies de auth
(`SID`, `SAPISID`, `__Secure-1PSID`, `__Secure-3PSID`) **persisten** a disco con CDP puesto.
Callejones descartados (no eran la causa, no repetir): un supuesto "borrar cookies al cerrar" (no
existía), `restore_on_startup` (se aplicó policy `RestoreOnStartup:1`, no resolvió), y el cifrado
OSCrypt fallando en XFCE (`os_crypt.portal.prev_init_success:false`; se aplicó
`--password-store=basic` vía `/etc/chromium.d/password-store`, no resolvió — inofensivo, se deja).
### Solución: systemd user service `chromium-personal`
`~/.config/systemd/user/chromium-personal.service` mantiene el navegador como servicio gestionado,
de modo que **cada cierre es SIGTERM con margen de flush**. Claves del unit:
| Directiva | Valor | Por qué |
|---|---|---|
| `ExecStart` | `/usr/lib/chromium/chromium --user-data-dir=%h/.config/chromium-cdp --profile-directory=Personal --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 --remote-allow-origins=* --no-first-run --no-default-browser-check` | Binario **real** (controlamos los flags). CDP en **loopback** explícito. |
| `KillSignal=SIGTERM` + `KillMode=mixed` | — | SIGTERM solo al master; él apaga sus hijos y vacía cookies. |
| `TimeoutStopSec=30` | 30 s | Margen para flushear antes de cualquier SIGKILL. |
| `PartOf=graphical-session.target` | — | systemd lo para ordenado en el **logout**, antes de que XFCE mate la sesión. |
| `Restart=always` + `RestartSec=3` | — | Siempre activo; se recupera de crashes. |
| `[Install] WantedBy=default.target` (`enable`) | — | Arranca al iniciar sesión. |
`DISPLAY=:0` y `XAUTHORITY=%h/.Xauthority` van en `Environment=` (sin ellos no conecta al X server).
**Seguridad:** el CDP escucha **solo en `127.0.0.1:9222`** (verificado con `ss`: `127.0.0.1:9222`,
nunca `0.0.0.0`). El `--remote-allow-origins=*` se mantiene porque lo necesita el cliente MCP local
para el websocket; no expone nada a la red porque el bind es loopback.
### Todos los perfiles heredan el cierre limpio
Chromium usa **un único master por `user-data-dir`**. Como el service mantiene ese master
(perfil inicial `Personal`), cualquier otro perfil que se abra (rofi, menú, selector de Chromium) se
**engancha al mismo master**. Por eso `systemctl --user stop` (o el logout) hace un shutdown global
que vacía las cookies de **todos** los perfiles abiertos. No hace falta un service por perfil (de
hecho no funcionaría: no pueden coexistir dos masters sobre el mismo `user-data-dir`).
### rofi y escritorio usan el service
- Script `~/.local/bin/chromium-launch`: hace `systemctl --user start chromium-personal`, espera a
que el CDP loopback responda y reenvía al master con el binario real
(`/usr/lib/chromium/chromium --user-data-dir=$HOME/.config/chromium-cdp "$@"`). Así nunca se crea
un master no gestionado.
- Override `~/.local/share/applications/chromium.desktop` (precedencia sobre
`/usr/share/applications/`): su `Exec` apunta a ese script. rofi y el menú XFCE lo usan
automáticamente. Trae `Actions` para nueva ventana / incógnito.
### Uso
```bash
systemctl --user start chromium-personal # abrir (o desde rofi/menú "Chromium")
systemctl --user stop chromium-personal # cerrar de verdad (no dispara Restart)
systemctl --user status chromium-personal # estado
```
Cerrar la ventana con la `X` o `Ctrl+Q` también es limpio (SIGTERM normal de Chromium). **Gotcha
UX de `Restart=always`:** si se cierran TODAS las ventanas, el master sale y systemd lo relanza en
~3 s (reabre `Personal`); para apagarlo del todo usar `stop`. Si molesta, cambiar a
`Restart=on-failure`.
### Control por el MCP browser (lifecycle por perfil)
El MCP `browser` (`apps/browser_mcp`) tiene tres tools para gestionar instancias por perfil sin
comandos a mano (añadidas 2026-06-10, rama `quick/browser-lifecycle`):
- `browser_list` — lista los masters chromium corriendo: `{pid, profile, user_data_dir, cdp_port, has_cdp}`.
- `browser_launch_profile` — lanza por perfil; `cdp:false` usa el binario real **sin** remote-debugging
(navegador "humano" que Google no toca), `cdp:true` añade el puerto. Desacopla con `setsid` para
sobrevivir a la muerte del MCP.
- `browser_close` — cierra limpio por `profile`/`cdp_port`/`pid` (SIGTERM; SIGKILL solo como último recurso).
## Proxy / captura mitm (web_proxy)
- App: `projects/web_scraping/apps/web_proxy` (sub-repo Gitea `dataforge/web_proxy`). Compone