mudados tawasuyu.net y gioser.net — y los padres que inventa bwrap eran 0700
Los dos sitios que quedaban con trafico real sirven desde la caja `takana`, con TLS publico y
verificados desde fuera. Los servicios viejos SIGUEN corriendo en gioser a proposito (decision del
usuario: mover el DNS y dejar el origen vivo, para poder volver en un minuto).
· `tawasuyu.net` + `www`: la raiz en gioser era EL MONOREPO ENTERO (145 G) servido por HTTP. El log
de accesos dice que las unicas rutas con trafico son `/`, `/descargas` y `/web`, asi que se
replicaron solo los dos subarboles que el sitio usa (1,2 M + 3,8 G) con la MISMA estructura, para
que cada `rewrite` siga siendo el mismo.
· `gioser.net` + `www`: 428 M de estaticos + `/reencuentro`, que necesita PHP. `/hooks/*` NO se
muda: lo atiende `webhook-deploy.py`, que redespliega aura/sigma/summa/brahman — los cuatro
FOSILES del §6.19 — y tiene cero peticiones. Muere con la caja vieja.
· `sergio` dejo de ser `CNAME -> www` y tiene su A propio (apuntando a gioser, sin cambio visible).
Sin eso, mover `www` se lo llevaba puesto: en esa zona casi todo cuelga de `www`.
PHP no entra al corpus y no hace falta: `php-fpm 8.5.10` corre en la instancia `gioser-php` (ADR
0015), supervisado por arje. Control contra el original en los dos sentidos: POST con la trampa
anti-robots da `{"ok":true}` 200 y GET da 405, igual que en gioser.
EL MURO, que es general y no de PHP: para montar en `/work/www/gioser-web`, bwrap crea `/work` y
`/work/www` —que la imagen no trae— y los crea **0700 root**. Adentro somos root y a mano todo
funciona; pero un servicio que BAJA de privilegio (php-fpm a `http`, o cualquier instancia con
`run_as`) no puede ni atravesarlos, y el sintoma es un «File not found» sobre un fichero que ESTA.
Medido con el control que lo separa de un problema de permisos del anfitrion: como `http`, leer un
fichero de la IMAGEN funciona y leer el directorio CONCEDIDO da Permission denied.
`grants_to_args` crea ahora los ancestros que faltan con `--perms 0755 --dir`, y SOLO los que la
imagen no trae: hacerlo sobre `/etc` o `/home` le cambiaria los modos a la imagen. Con test, y
probado rompiendolo a proposito (sale `["/etc","/etc/php"]` en vez de `["/etc/php"]`).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1211,9 +1211,30 @@ fn provision(root: &Path, id: &str, force: bool, dry_run: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Los directorios INTERMEDIOS de un punto de montaje que la imagen no trae, de arriba hacia abajo.
|
||||
///
|
||||
/// No incluye el punto de montaje en sí: ése lo tapa el `--bind` y hereda los modos del anfitrión.
|
||||
/// Sólo devuelve los que faltan en la imagen, porque `--dir` sobre uno existente le cambiaría los
|
||||
/// permisos a la imagen. Ver el comentario largo en `grants_to_args`.
|
||||
fn ancestros_a_crear(dentro: &str, tree: &Path) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let mut acc = String::new();
|
||||
let partes: Vec<&str> = dentro.trim_matches('/').split('/').collect();
|
||||
// `partes.len() - 1`: el último es el punto de montaje, no un ancestro.
|
||||
for p in partes.iter().take(partes.len().saturating_sub(1)) {
|
||||
if p.is_empty() { continue; }
|
||||
acc.push('/');
|
||||
acc.push_str(p);
|
||||
if !tree.join(acc.trim_start_matches('/')).is_dir() {
|
||||
out.push(acc.clone());
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Traduce las concesiones a argumentos de bwrap. Devuelve también las advertencias que el usuario
|
||||
/// tiene que LEER, no descubrir: el ADR pide que el socket de Wayland se diga en la cara.
|
||||
fn grants_to_args(g: &Grants, destino_xdg: &str, wl: Option<&Path>) -> (Vec<String>, Vec<String>) {
|
||||
fn grants_to_args(g: &Grants, destino_xdg: &str, wl: Option<&Path>, tree: &Path) -> (Vec<String>, Vec<String>) {
|
||||
let mut a: Vec<String> = Vec::new();
|
||||
let mut warn: Vec<String> = Vec::new();
|
||||
// Sin dep de libc: el dueño de /proc/self ES el uid efectivo.
|
||||
@@ -1255,6 +1276,22 @@ fn grants_to_args(g: &Grants, destino_xdg: &str, wl: Option<&Path>) -> (Vec<Stri
|
||||
let host = if let Some(rest) = d.host.strip_prefix('~') {
|
||||
format!("{}{}", std::env::var("HOME").unwrap_or_default(), rest)
|
||||
} else { d.host.clone() };
|
||||
// ── LOS PADRES QUE INVENTA bwrap SON 0700, Y ESO DEJA LA CONCESIÓN FUERA DE ALCANCE ─────
|
||||
// MEDIDO el 2026-09-15 con `php-fpm` en la instancia `gioser-php`: la concesión estaba
|
||||
// montada, visible y con sus modos correctos, y el servicio contestaba «File not found».
|
||||
// La causa no está en el bind sino en el CAMINO: para montar en `/work/www/gioser-web`,
|
||||
// bwrap crea `/work` y `/work/www` —que no existen en la imagen— y los crea **0700 root**.
|
||||
// Adentro somos root, así que a mano todo se lee; pero un servicio que BAJA de privilegio
|
||||
// (php-fpm a `http`, o cualquier instancia con `run_as`) no puede ni atravesarlos. El
|
||||
// síntoma no se parece a la causa: dice que falta el fichero, y el fichero está.
|
||||
//
|
||||
// Se crean explícitamente a 0755 **sólo los que la imagen no trae**: hacerlo sobre un
|
||||
// directorio que ya existe (`/etc`, `/usr`, `/home`) le cambiaría los modos a la imagen,
|
||||
// que es justo lo que no se quiere. Misma familia que el `--perms 1777` del `--tmpfs /tmp`.
|
||||
for ancestro in ancestros_a_crear(&d.inside, tree) {
|
||||
a.push("--perms".into()); a.push("0755".into());
|
||||
a.push("--dir".into()); a.push(ancestro);
|
||||
}
|
||||
a.push(if d.mode == "rw" { "--bind".into() } else { "--ro-bind".into() });
|
||||
a.push(host); a.push(d.inside.clone());
|
||||
}
|
||||
@@ -1498,7 +1535,7 @@ fn run_instance(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (grant_args, warns) = grants_to_args(&inst.grants, &runtime_dir, proxy_sock.as_deref());
|
||||
let (grant_args, warns) = grants_to_args(&inst.grants, &runtime_dir, proxy_sock.as_deref(), &tree);
|
||||
|
||||
// El orden importa: `--overlay-src` describe la capa BAJA del `--overlay` que viene justo
|
||||
// después. Y `--clearenv` va primero porque el entorno del host también es una concesión: lo
|
||||
@@ -2742,21 +2779,42 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn sin_concesiones_la_red_queda_fuera_y_con_red_entra_el_resolv_conf() {
|
||||
let (a, w) = grants_to_args(&Grants::default(), "/run/user/0", None);
|
||||
let (a, w) = grants_to_args(&Grants::default(), "/run/user/0", None, Path::new("/nonexistent"));
|
||||
assert!(a.contains(&"--unshare-net".to_string()));
|
||||
assert!(w.is_empty());
|
||||
|
||||
let g = Grants { network: true, ..Default::default() };
|
||||
let (a, _) = grants_to_args(&g, "/run/user/0", None);
|
||||
let (a, _) = grants_to_args(&g, "/run/user/0", None, Path::new("/nonexistent"));
|
||||
assert!(!a.contains(&"--unshare-net".to_string()));
|
||||
// El segundo fallo clásico de la primera corrida, según el ADR.
|
||||
assert!(a.iter().any(|x| x == "/etc/resolv.conf"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn los_padres_que_faltan_se_crean_y_los_de_la_imagen_no_se_tocan() {
|
||||
// El control que importa es el NEGATIVO: si esto devolviera los directorios que la imagen
|
||||
// ya trae, `--dir` les cambiaría los permisos a `/etc` y `/home` de la imagen.
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let tree = d.path();
|
||||
std::fs::create_dir_all(tree.join("etc")).unwrap();
|
||||
std::fs::create_dir_all(tree.join("home")).unwrap();
|
||||
|
||||
// `/work` y `/work/www` no existen en la imagen ⇒ hay que crearlos, de arriba hacia abajo.
|
||||
assert_eq!(
|
||||
ancestros_a_crear("/work/www/gioser-web", tree),
|
||||
vec!["/work".to_string(), "/work/www".to_string()]
|
||||
);
|
||||
// `/etc` SÍ existe ⇒ sólo el que falta.
|
||||
assert_eq!(ancestros_a_crear("/etc/php/php-fpm.d", tree), vec!["/etc/php".to_string()]);
|
||||
// Montar directamente bajo un directorio de la imagen no necesita crear NADA.
|
||||
assert!(ancestros_a_crear("/home/sergio", tree).is_empty());
|
||||
assert!(ancestros_a_crear("/srv", tree).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn el_socket_de_wayland_avisa_que_es_un_borde_de_privilegio() {
|
||||
let g = Grants { sockets: vec!["wayland".into()], ..Default::default() };
|
||||
let (_, w) = grants_to_args(&g, "/run/user/0", None);
|
||||
let (_, w) = grants_to_args(&g, "/run/user/0", None, Path::new("/nonexistent"));
|
||||
assert_eq!(w.len(), 1);
|
||||
assert!(w[0].contains("CAPTURAR LA PANTALLA"), "el aviso no puede ser tibio: {}", w[0]);
|
||||
}
|
||||
|
||||
@@ -1517,6 +1517,82 @@ cron de la cosecha, y pinear HEAD re-hashearía la receta cada media hora sin qu
|
||||
| **lo que falta de `sergio.gioser.net`** | el frontend ya está copiado en `/work/www/sergioh`; falta `/shuma/*` → `shuma-gateway` (:7378), que es glibc dinámico y **cabe en esta misma jaula** (el binario está rescatado en `/work/rescate-binarios/`) pero cuelga de `shuma-daemon`, que es un sistema entero, no un binario |
|
||||
| **y los otros dos vivos** | `gioser.net`/`www` (estáticos + `/hooks/*` en :8770 + `/reencuentro` con php-fpm) y `tawasuyu.net` (estático, raíz en `/mnt/vvv/tawasuyu`) |
|
||||
|
||||
### 6.26 ✅ `tawasuyu.net` y `gioser.net` MUDADOS — y el PHP que no existe en el corpus *(2026-09-15)*
|
||||
|
||||
**Los dos sitios que quedaban con tráfico real sirven desde la caja `takana`**, con TLS público y
|
||||
verificados desde fuera. Los servicios viejos **siguen corriendo en gioser a propósito** (decisión del
|
||||
usuario: mudar el DNS y dejar el origen vivo, para poder volver en un minuto).
|
||||
|
||||
| dominio | qué es | estado |
|
||||
|---|---|---|
|
||||
| `tawasuyu.net`, `www.` | landing + `/pkg` wasm + `/descargas` (3,8 G) | **200 desde 2.29.29.217** |
|
||||
| `gioser.net`, `www.` | 5 raíces estáticas (428 M) + `/reencuentro` con PHP | **200 desde 2.29.29.217** |
|
||||
| `sergio.gioser.net` | frontend + `/shuma/*` | **se queda en gioser**: su consola no tiene camino todavía |
|
||||
|
||||
**`sergio` dejó de ser un `CNAME → www` y tiene su A propio** (apuntando a gioser, sin cambio
|
||||
visible). Sin eso, mover `www` se lo habría llevado puesto — es la trampa del §6.14 vista a tiempo:
|
||||
en esta zona **casi todo cuelga de `www`**. Lo que sí quedó colgando son tres fósiles (`api`,
|
||||
`mail.sigma`, `monitor`): antes daban 502 desde gioser y ahora fallan el TLS contra la caja. Están
|
||||
muertos hace meses; se anotan, no se mudan.
|
||||
|
||||
**Dos cosas que el log de accesos decidió mejor que cualquier intuición:**
|
||||
- La raíz de `tawasuyu.net` en gioser era **el monorepo entero (145 G) servido por HTTP**. El log
|
||||
dice que las únicas rutas con tráfico son `/`, `/descargas` y `/web` ⇒ se replicaron **sólo** los
|
||||
dos subárboles que el sitio usa (1,2 M + 3,8 G), con la MISMA estructura para que cada `rewrite`
|
||||
siga siendo el mismo.
|
||||
- **`/hooks/*` NO se muda**: lo atiende `webhook-deploy.py`, que redespliega `aura_*`, `sigma_*`,
|
||||
`summa_*` y `brahman` — **los cuatro son fósiles del §6.19**, y tiene cero peticiones. Muere con la
|
||||
caja vieja. Un servicio vivo que sólo sirve a muertos es basura, no deuda.
|
||||
|
||||
#### PHP: la segunda jaula, y por qué no entra al corpus
|
||||
|
||||
`/reencuentro/` es una página con **un** `guardar.php`. PHP no está en el corpus y no tiene por qué
|
||||
estar: lo sirve **`php-fpm 8.5.10` dentro de la instancia `gioser-php`** (ADR 0015), por
|
||||
`127.0.0.1:9000`, supervisado por arje. Control contra el original, en los dos sentidos: `POST` con la
|
||||
trampa anti-robots → `{"ok":true}` 200 igual que en gioser; `GET` → **405** igual que en gioser.
|
||||
|
||||
⚠ **El webroot entra a la jaula EN LA MISMA RUTA** (`/work/www/gioser-web`): el `SCRIPT_FILENAME` de
|
||||
FastCGI es una ruta absoluta del anfitrión, y si adentro viviera en otro sitio php-fpm contesta
|
||||
«File not found» sin decir cuál. Y la config de php-fpm vive en el ANFITRIÓN
|
||||
(`/work/etc/gioser-php/php-fpm.d/`) y entra por concesión: el `upper` de la instancia es caché y
|
||||
`recreate` se la llevaría.
|
||||
|
||||
#### 🧨 Los padres que inventa bwrap son **0700** — y eso deja la concesión fuera de alcance
|
||||
|
||||
El muro real del día, y es general, no de PHP:
|
||||
|
||||
$ curl -X POST https://gioser.net/reencuentro/guardar.php
|
||||
File not found. ← y el fichero ESTÁ, montado, con sus modos correctos
|
||||
|
||||
Para montar en `/work/www/gioser-web`, bwrap tiene que crear `/work` y `/work/www` —que la imagen de
|
||||
Arch no trae— y **los crea `drwx------ root`**. Adentro somos root, así que todo a mano funciona; pero
|
||||
**un servicio que baja de privilegio** (php-fpm a `http`, o cualquier instancia con `run_as`) no puede
|
||||
ni atravesarlos. Medido con el control que lo separa de una confusión de permisos del anfitrión: como
|
||||
`http`, leer un fichero **de la imagen** funciona y leer el **directorio concedido** da
|
||||
`Permission denied`.
|
||||
|
||||
⇒ `grants_to_args` crea ahora los ancestros que faltan con `--perms 0755 --dir`, y **sólo los que la
|
||||
imagen no trae**: hacerlo sobre `/etc` o `/home` le cambiaría los modos a la imagen. Con test que
|
||||
falla a propósito si la condición se rompe (`left: ["/etc","/etc/php"]` contra `right: ["/etc/php"]`).
|
||||
Misma familia que el `--perms 1777` que ya hacía falta antes del `--tmpfs /tmp`.
|
||||
|
||||
⚠ La caja corre el `takana` del `commit` pineado en `recipes/takana.toml`, así que **allá el arreglo
|
||||
todavía no está**: el camino se abrió a mano en el `upper` de la instancia (`chmod 755 /work
|
||||
/work/www`). Es exactamente lo que el arreglo vuelve innecesario en el próximo `takana upgrade`.
|
||||
|
||||
#### Y el estado de `rust` y de `claude`, que no es el que parece
|
||||
|
||||
- **`rust` está en la caja pero no instalado**: `rust-toolchain-bin` está **sellado en el store** y
|
||||
declarado en `perfil.servidor`, y `rustc 1.97.0` corre invocándolo por su ruta del store — pero
|
||||
**no está en el `PATH`** porque la caja se instaló antes de esa declaración. Proyectarlo son 798 M
|
||||
sobre una raíz de **5,8 G con 2,9 G libres**: entra, pero el layout de la imagen (§6.24) merece la
|
||||
revisión primero.
|
||||
- **`claude` no está mudado**, y es otro caso de jaula: el binario de gioser es **glibc dinámico**
|
||||
(`/lib64/ld-linux-x86-64.so.2`), o sea el mismo montón que el backend de `sergio`. Lo que pesa no
|
||||
es el binario sino su estado: **6,8 G de `~/.claude`**, de los cuales 5,3 G son `jobs` y 976 M
|
||||
`projects` — y ahí vive la memoria, **indexada POR RUTA del repo**. Mudarlo es una decisión sobre
|
||||
qué se lleva, no una copia.
|
||||
|
||||
## 7. Reusar los scripts que ya existen, y no escribir de nuevo
|
||||
|
||||
Pedido explícito del usuario. El inventario de lo que ya hace el trabajo:
|
||||
|
||||
Reference in New Issue
Block a user