tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Definiciones

⎇ main scripts/servidor/alta-usuario.sh Shell · 111 líneas · 6.5 KB Historia crudo

red en metal: de udhcpc/ifconfig/route a dhcpcd/ip — y el adduser que NO se migra 87d34b95f · Sergio ·

1#!/bin/sh
2# Da de alta una PERSONA en la caja: cuenta, clave, sudo, subids y su jaula de Claude.
3#
4# ── ¿POR QUÉ NO ALCANZA CON `adduser`? Porque casi alcanza, y lo que falta tumbó la caja ────────
5# `adduser` está (es el de busybox) y crea la cuenta. El problema es **dónde queda la contraseña**:
6# esta caja **NO USA `/etc/shadow`** — el hash vive en el campo 2 de `/etc/passwd`, y el `shadow`
7# que hubo está aparcado como `/etc/shadow.el-que-bloqueo-root`. El nombre no es broma: al crear un
8# `/etc/shadow` con todas las cuentas bloqueadas (`!`), **sshd dejó de aceptar hasta la clave de
9# root** y la caja se quedó sin administración con los ocho dominios caídos ~20 min (SDD 28 §6.51).
10# Un `adduser` a secas puede crear ese fichero sin avisar.
11#
12# Entonces el alta correcta acá es: crear la cuenta, dejar el hash donde el login lo lee, y
13# **comprobar que quien podía entrar sigue pudiendo** antes de darse por satisfecho.
14#
15# ⚠ Y por eso este `adduser` NO se migra a `useradd` (2026-09-22). El plan de botar busybox migra
16# los llamadores de applets a las herramientas declaradas —`ip` por `ifconfig`, `dhcpcd` por
17# `udhcpc`— porque esos usos son básicos. Éste no lo es: `useradd` es de `shadow`, y su modelo
18# NATIVO es escribir en `/etc/shadow`, que es justo el fichero que tumbó la caja. Cambiar el applet
19# por la herramienta «buena» reintroduciría el fallo que el guardián de abajo existe para cazar.
20# `adduser` queda INDULTADO en el defconfig de busybox por esta razón, junto a `setuidgid`,
21# `switch_root`, `halt`, `poweroff` y `reboot`. El día que la caja adopte shadow de verdad, esto se
22# revisa entero — no antes, y no de paso.
23#
24# Lo demás es lo que hace falta para «tener poder sobre la máquina, a mano y con claude»:
25#   · `/etc/sudoers.d/<u>`   — root a mano (el `sudo` de acá SÍ es setuid; `doas` no funciona)
26#   · `/etc/sub{u,g}id`      — sin rango, un userns anidado no puede mapear uid 0 y `takana build`
27#                              no corre dentro de la jaula
28#   · instancia `claude-<u>` — con `root = false`, o el CLI se ve uid 0 y RECHAZA
29#                              `--dangerously-skip-permissions`
30#
31# Uso:  alta-usuario.sh <usuario> [clave]        (sin clave: la cuenta queda SIN contraseña, sólo ssh)
32set -eu
33
34U=${1:?uso: alta-usuario.sh <usuario> [clave]}
35CLAVE=${2:-}
36HOME_U=/home/$U
37
38[ "$(id -u)" = "0" ] || { echo "esto se corre como root" >&2; exit 1; }
39
40# Foto de quién puede entrar ANTES de tocar nada: es contra esto que se compara al final.
41ANTES=$(awk -F: '$2 != "" && $2 != "!" && $2 != "*" {print $1}' /etc/passwd | sort | tr '\n' ' ')
42HABIA_SHADOW=no; [ -f /etc/shadow ] && HABIA_SHADOW=si
43
44if id "$U" >/dev/null 2>&1; then
45  echo "==> $U ya existe (uid $(id -u "$U")) — sigo con el resto"
46else
47  echo "==> creando $U"
48  adduser -D -h "$HOME_U" -s /bin/bash "$U"
49fi
50
51# ── la contraseña, donde el login la lee ────────────────────────────────────────────────────────
52if [ -n "$CLAVE" ]; then
53  H=$(openssl passwd -6 "$CLAVE" 2>/dev/null || busybox cryptpw -m sha512 "$CLAVE")
54  # En passwd, campo 2. Se usa `awk` y no `sed` porque el hash trae `/` y `$` a montones.
55  awk -F: -v u="$U" -v h="$H" 'BEGIN{OFS=":"} $1==u {$2=h} {print}' /etc/passwd > /etc/passwd.nuevo
56  # Nunca dejar un passwd a medias: se comprueba que el fichero nuevo tenga las mismas cuentas.
57  [ "$(wc -l < /etc/passwd.nuevo)" -eq "$(wc -l < /etc/passwd)" ] || { echo "passwd nuevo con distinto número de cuentas — aborto" >&2; rm -f /etc/passwd.nuevo; exit 1; }
58  mv /etc/passwd.nuevo /etc/passwd; chmod 644 /etc/passwd
59  echo "  clave puesta en /etc/passwd (esta caja no usa shadow)"
60fi
61
62# ⚠ GUARDIÁN DEL BLOQUEO: si `adduser` creó un /etc/shadow que no había, cualquiera que tuviera su
63# hash en passwd queda con `x` y bloqueado. Es literalmente cómo se perdió la caja.
64if [ "$HABIA_SHADOW" = "no" ] && [ -f /etc/shadow ]; then
65  mv /etc/shadow /etc/shadow.creado-por-el-alta-$(date +%s)
66  echo "  ⚠ adduser creó /etc/shadow donde no había: lo aparté (así se perdió la caja el 2026-09-17)"
67fi
68
69echo "==> poder: sudo, subids"
70[ -f "/etc/sudoers.d/$U" ] || { echo "$U ALL=(ALL:ALL) ALL" > "/etc/sudoers.d/$U"; chmod 440 "/etc/sudoers.d/$U"; }
71for f in /etc/subuid /etc/subgid; do
72  grep -q "^$U:" "$f" 2>/dev/null && continue
73  # Rango propio, detrás del último declarado, sin solaparse.
74  ult=$(awk -F: '{print $2+$3}' "$f" 2>/dev/null | sort -n | tail -1); : "${ult:=100000}"
75  echo "$U:$ult:65536" >> "$f"
76done
77# Sin estos dos setuid, el rango de arriba no se puede aplicar: son los que escriben el uid_map.
78for b in /usr/bin/newuidmap /usr/bin/newgidmap; do
79  [ -u "$b" ] || { chmod 4755 "$b"; echo "  setuid puesto en $b"; }
80done
81
82echo "==> ssh"
83mkdir -p "$HOME_U/.ssh"; touch "$HOME_U/.ssh/authorized_keys"
84chmod 700 "$HOME_U/.ssh"; chmod 600 "$HOME_U/.ssh/authorized_keys"
85chown -R "$U:$U" "$HOME_U/.ssh"
86[ -s "$HOME_U/.ssh/authorized_keys" ] || echo "  (authorized_keys vacío: poné la clave pública o no entra por ssh)"
87
88echo "==> jaula de claude"
89BASE=/var/lib/hammer/qorpa/instances
90if [ -d "$BASE/claude" ] && [ ! -d "$BASE/claude-$U" ]; then
91  mkdir -p "$BASE/claude-$U"
92  sed -e "s|^root = true$|root = false|" \
93      -e "s|/home/sergio|$HOME_U|g" -e "s|/work/sergio|/work/$U|g" \
94      "$BASE/claude/instance.toml" > "$BASE/claude-$U/instance.toml"
95  mkdir -p "/work/$U"
96  chown -R "$U:$U" "$BASE/claude-$U" "/work/$U"
97  echo "  instancia claude-$U creada (root = false: si no, el CLI se ve uid 0 y rechaza la bandera)"
98else
99  echo "  (ya existía, o no hay instancia `claude` de la que copiar)"
100fi
101
102# ── CONTROLES ───────────────────────────────────────────────────────────────────────────────────
103echo "==> control"
104id "$U" | sed 's/^/  /'
105DESPUES=$(awk -F: '$2 != "" && $2 != "!" && $2 != "*" {print $1}' /etc/passwd | sort | tr '\n' ' ')
106perdidos=""
107for a in $ANTES; do case " $DESPUES " in *" $a "*) ;; *) perdidos="$perdidos $a";; esac; done
108[ -z "$perdidos" ] || { echo "  ✗ estas cuentas PERDIERON su contraseña:$perdidos" >&2; exit 1; }
109echo "  ✓ nadie perdió el acceso que tenía (antes:$ANTES)"
110[ -f /etc/shadow ] && echo "  ⚠ hay /etc/shadow: revisá que root NO esté bloqueado ahí" || echo "  ✓ sin /etc/shadow (como esta caja espera)"
111echo "==> $U dado de alta"