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