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…

⎇ 6995dbb44eb733794d29bc8298cba3ac465ac783 recipes/pass.toml TOML · 130 líneas · 7.9 KB Historia crudo

pass: el gestor de contraseñas, y el hueco que el censo nombraba sin rodeos 6995dbb44 · Sergio ·

1# pass 1.7.4 — el gestor de contraseñas de Unix. Cierra un hueco que el censo nombra sin rodeos.
2#
3# ══ POR QUÉ ENTRA ══════════════════════════════════════════════════════════════════════════════
4# `censo-distro.py` tenía la fila «contraseñas» en **2 de 3 ausente** con este contexto: *«nada: la
5# gente guarda sus claves en un .txt o no usa la distro»*. Es de los pocos huecos donde no hay
6# ningún camino alternativo en el corpus — no es que falte el nombre que alguien busca, es que no
7# hay con qué.
8#
9# Y es el más barato de los tres que faltaban: **`pass` no compila nada**, es un script de bash
10# sobre `gpg`, y toda su cadena ya estaba sellada (`gnupg`, `libgcrypt`, `libassuan`, `npth`,
11# `pinentry`, `libksba`, `tree`, `util-linux`, `coreutils`, `git`, `wl-clipboard`, `qrencode`).
12# Coste de construcción: cero. Lo que cuesta es declararlo bien, y de eso va el resto de esta nota.
13#
14# ══ ⚠ LO QUE DA VALOR A ESTA RECETA SON LAS DEPS DE RUNTIME, NO LAS DE BUILD ══════════════════
15# `pass` se «construye» con un `make install` que copia un script. Si sólo se declarara lo que hace
16# falta para eso —`make` y nada más— la receta **sellaría perfectamente y el paquete no serviría**:
17# el script invoca `gpg` 31 veces, `git` 25, `tree`, `getopt`, `base64`, `qrencode` y `wl-copy`, y
18# ninguna de esas aparece como dependencia de enlace porque NO HAY ENLACE QUE MIRAR.
19#
20# Es la misma figura que `autoconf` (que invoca `perl` y `m4` al ejecutarse) y la razón por la que
21# existe `runtime`: un script no tiene `NEEDED`, así que el vigía de sonames no lo puede ayudar y
22# **la única red es escribir la lista a mano leyendo la fuente**. Lo de arriba sale de contar las
23# invocaciones en `src/password-store.sh`, no de los `depends` de otra distro.
24#
25# ══ ME CORRIJO: NO HACE FALTA PARCHEAR NADA PARA WAYLAND ═══════════════════════════════════════
26# Venía a parchear `xclip` → `wl-copy`, porque esta distro es Wayland-only (X11 descartado para
27# todo el corpus) y `pass -c` sin portapapeles sería «sellado y sin la función que promete».
28# **Falso, y se ve abriendo el fichero**: 1.7.4 ya trae Wayland de PRIMERA opción
29# (`src/password-store.sh:158`):
30#
31#     clip() {
32#         if   [[ -n $WAYLAND_DISPLAY ]]; then copy_cmd=( wl-copy ); paste_cmd=( wl-paste -n )
33#         elif [[ -n $DISPLAY ]];         then copy_cmd=( xclip … )
34#         else die "Error: No X11 or Wayland display detected"
35#
36# ⇒ `wl-clipboard` es dep de runtime y `xclip` no hace falta; la rama X11 queda muerta por elección
37# de la distro, no por avería. Y sin ningún display `pass -c` **muere con un mensaje claro**, que es
38# el comportamiento correcto. Lección repetida: *leer la fuente antes de parchear lo que uno cree.*
39#
40# ══ ⚠ `WITH_ALLCOMP=yes` ES UNA CUESTIÓN DE REPRODUCIBILIDAD, NO DE GUSTO ══════════════════════
41# El Makefile decide **qué ficheros instala** preguntando si existen directorios DEL SISTEMA DE
42# BUILD, sin `DESTDIR` delante (líneas 18-33):
43#
44#     ifeq ($(WITH_BASHCOMP),)
45#     ifneq ($(strip $(wildcard $(BASHCOMPDIR))),)   # ⇐ /usr/share/bash-completion/completions
46#     WITH_BASHCOMP := yes
47#
48# O sea que el CONTENIDO del artefacto depende de cómo esté amueblado el lab. Y el lab **no entra en
49# `hash_inputs`**: dos labs distintos sellarían bytes distintos con la misma receta y el mismo hash,
50# que es la avería documentada en la memoria `lab-fuera-de-hash-inputs`. Forzando las tres a `yes` la
51# pregunta desaparece — se instalan siempre, y el artefacto no depende de quién lo construya.
52# (No se midió el lab a propósito: el punto es no DEPENDER de esa medición.)
53name    = "pass"
54version = "1.7.4"
55license = "GPL-2.0-or-later"
56
57[source]
58tarball = "https://git.zx2c4.com/password-store/snapshot/password-store-1.7.4.tar.xz"
59sha256  = "4c2d0a8b99df8915a87099607a8d912fd05d30651b6f014745c14e4ca8dbbfb7"
60
61[build]
62compiler = "zig-cc"
63target   = "x86_64-linux-musl"
64link     = "dynamic"
65flags    = []
66
67[build.phases]
68# Sin configure ni compilación: `make all` de upstream literalmente imprime «Password store is a
69# shell script, so there is nothing to do».
70configure = "true"
71compile   = "true"
72install = '''
73set -e
74make install DESTDIR=/out PREFIX=/usr WITH_ALLCOMP=yes
75
76[ -x /out/usr/bin/pass ] || { echo "pass: no quedó el ejecutable — NO sello" >&2; exit 1; }
77[ -f /out/usr/share/man/man1/pass.1 ] || { echo "pass: falta la página de manual" >&2; exit 1; }
78for c in /out/usr/share/bash-completion/completions/pass \
79         /out/usr/share/zsh/site-functions/_pass \
80         /out/usr/share/fish/vendor_completions.d/pass.fish; do
81  [ -f "$c" ] || { echo "pass: falta $c — WITH_ALLCOMP no surtió efecto y el artefacto quedaría a merced del lab" >&2; exit 1; }
82done
83
84# ⚠ GUARDIÁN FUNCIONAL: que GUARDE Y DEVUELVA un secreto de verdad.
85#
86# Para un script, «existe y es ejecutable» no prueba NADA: el fichero se copia siempre. Lo que puede
87# fallar es que no encuentre `gpg`, o que `getopt` sea el de BSD y no acepte opciones largas, o que
88# el `tree` que necesita no esté. Todo eso sella y revienta al primer uso.
89#
90# Así que acá se hace el ciclo completo: clave GPG de usar y tirar → `pass init` → `pass insert` →
91# `pass show`, y se compara el secreto devuelto con el guardado. Si no coincide, no se sella.
92export GNUPGHOME=/tmp/g-pass
93export PASSWORD_STORE_DIR=/tmp/pass-store
94rm -rf "$GNUPGHOME" "$PASSWORD_STORE_DIR"
95mkdir -p "$GNUPGHOME" "$PASSWORD_STORE_DIR"
96chmod 700 "$GNUPGHOME"
97export PATH="/out/usr/bin:$PATH"
98
99gpg --batch --pinentry-mode loopback --passphrase '' \
100    --quick-generate-key 'Guardian Takana <guardian@takana.invalid>' default default never \
101    >/tmp/gpg.log 2>&1 || { echo "pass: no se pudo crear la clave GPG de prueba — el guardián no puede concluir" >&2; tail -12 /tmp/gpg.log >&2; exit 1; }
102FPR=$(gpg --batch --with-colons --list-secret-keys 2>/dev/null | awk -F: '/^fpr:/{print $10; exit}')
103[ -n "$FPR" ] || { echo "pass: la clave se creó y no se puede leer su huella" >&2; exit 1; }
104
105pass init "$FPR" >/tmp/init.log 2>&1 || { echo "pass init FALLÓ" >&2; cat /tmp/init.log >&2; exit 1; }
106SECRETO="s3creto-de-guardian-$$"
107printf '%s\n' "$SECRETO" | pass insert --echo prueba/clave >/tmp/ins.log 2>&1 \
108  || { echo "pass insert FALLÓ — mirá si es getopt o gpg" >&2; cat /tmp/ins.log >&2; exit 1; }
109VUELTA=$(pass show prueba/clave 2>/tmp/show.log)
110[ "$VUELTA" = "$SECRETO" ] || { echo "pass: lo que devolvió NO es lo que se guardó ('$VUELTA' != '$SECRETO')" >&2; cat /tmp/show.log >&2; exit 1; }
111
112# `pass ls` usa `tree`: se comprueba aparte porque es la dep que más fácil se olvida y su ausencia
113# no rompe `show`, o sea que pasaría desapercibida.
114pass ls >/tmp/ls.log 2>&1 && grep -q 'prueba' /tmp/ls.log \
115  || { echo "pass ls no listó la entrada — probablemente falta 'tree'" >&2; cat /tmp/ls.log >&2; exit 1; }
116
117rm -rf "$GNUPGHOME" "$PASSWORD_STORE_DIR"
118echo "pass: ciclo completo OK — init + insert + show devolvió el mismo secreto, y ls lo listó"
119'''
120
121[deps]
122# Para construir: sólo `make` copia ficheros. Lo demás está acá porque el GUARDIÁN los ejecuta.
123build = ["make", "bash", "gnupg", "tree", "util-linux", "coreutils", "pinentry"]
124# ⚠ Ésta es la lista que hace que el paquete SIRVA, y sale de contar invocaciones en la fuente:
125# gpg ×31, git ×25, sed, dirname, mv, find, tail, qrencode ×4, grep, cat, tree ×3, mktemp, cp,
126# base64, wl-copy. `util-linux` por `getopt` (el script exige el ENHANCED, el de BSD no vale);
127# `bash` porque el shebang es bash y no sh; `pinentry` porque sin él gpg no puede pedir la frase de
128# paso en un uso interactivo real.
129runtime = ["bash", "gnupg", "pinentry", "tree", "util-linux", "coreutils", "git",
130           "wl-clipboard", "qrencode"]