chore: auto-commit (97 archivos)

- .claude/CLAUDE.md
- .claude/agents/fn-recopilador/SKILL.md
- .claude/rules/INDEX.md
- .claude/rules/cpp_apps.md
- bash/functions/infra/build_cpp_windows.sh
- cpp/CMakeLists.txt
- cpp/PATTERNS.md
- cpp/framework/app_base.cpp
- cpp/framework/app_base.h
- dev/issues/README.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 18:11:24 +02:00
parent 852322a708
commit 750b7abcd5
99 changed files with 7879 additions and 73 deletions
+43 -26
View File
@@ -1,34 +1,51 @@
#!/usr/bin/env bash
# build_cpp_windows — Cross-compila apps C++ del registry para Windows con
# mingw-w64. Configura el build dir cpp/build/windows/ con la toolchain la
# primera vez y construye el target indicado (o todos).
#
# Uso (funcion via source):
# source bash/functions/infra/build_cpp_windows.sh
# build_cpp_windows my_app # construye target especifico
# build_cpp_windows # construye todos
#
# Uso (script directo):
# bash bash/functions/infra/build_cpp_windows.sh my_app
set -euo pipefail
REGISTRY_ROOT="${FN_REGISTRY_ROOT:-$(cd "$(dirname "$0")/../../.." && pwd)}"
CPP_ROOT="$REGISTRY_ROOT/cpp"
BUILD_DIR="$CPP_ROOT/build/windows"
TOOLCHAIN="$CPP_ROOT/toolchains/mingw-w64.cmake"
TARGET="${1:-}"
build_cpp_windows() {
local target="${1:-}"
local registry_root="${FN_REGISTRY_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}"
local cpp_root="$registry_root/cpp"
local build_dir="${BUILD_WIN:-$cpp_root/build/windows}"
local toolchain="$cpp_root/toolchains/mingw-w64.cmake"
# Check mingw is available
if ! command -v x86_64-w64-mingw32-g++ &>/dev/null; then
echo "[build_cpp_windows] Error: mingw-w64 not found. Install with: sudo apt install mingw-w64"
exit 1
fi
if ! command -v x86_64-w64-mingw32-g++ &>/dev/null; then
echo "[build_cpp_windows] Error: mingw-w64 not found. Install with: sudo apt install mingw-w64" >&2
return 1
fi
# Configure if needed
if [ ! -f "$BUILD_DIR/CMakeCache.txt" ]; then
echo "[build_cpp_windows] Configuring cmake with mingw-w64 toolchain..."
cmake -B "$BUILD_DIR" -S "$CPP_ROOT" -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN"
fi
if [ ! -f "$build_dir/CMakeCache.txt" ]; then
echo "[build_cpp_windows] Configuring cmake with mingw-w64 toolchain..." >&2
cmake -B "$build_dir" -S "$cpp_root" -DCMAKE_TOOLCHAIN_FILE="$toolchain"
else
# Re-run cmake to pick up new add_subdirectory entries cuando se anade
# una app nueva al CMakeLists.txt (no rompe builds incrementales).
cmake "$build_dir" >/dev/null
fi
# Build
if [ -n "$TARGET" ]; then
echo "[build_cpp_windows] Cross-compiling target: $TARGET"
cmake --build "$BUILD_DIR" --target "$TARGET" -- -j"$(nproc)"
else
echo "[build_cpp_windows] Cross-compiling all targets..."
cmake --build "$BUILD_DIR" -- -j"$(nproc)"
fi
if [ -n "$target" ]; then
echo "[build_cpp_windows] Cross-compiling target: $target" >&2
cmake --build "$build_dir" --target "$target" -- -j"$(nproc)"
else
echo "[build_cpp_windows] Cross-compiling all targets..." >&2
cmake --build "$build_dir" -- -j"$(nproc)"
fi
echo "[build_cpp_windows] Done. Windows binaries in $BUILD_DIR"
if [ -n "$TARGET" ]; then
file "$BUILD_DIR"/**/"$TARGET".exe 2>/dev/null || file "$BUILD_DIR/$TARGET".exe 2>/dev/null || true
echo "[build_cpp_windows] Done. Windows binaries in $build_dir" >&2
}
# Invocacion directa como script (compatibilidad).
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
build_cpp_windows "$@"
fi
@@ -0,0 +1,48 @@
---
name: e2e_run_cpp_windows
lang: bash
domain: infra
description: "Cross-compila una app C++ del registry para Windows con mingw-w64, deploy al Desktop\\apps de Windows (matando instancia previa con taskkill.exe), lanza el .exe nativamente desde WSL y devuelve stdout + exit code. Pensado para tests headless tipo altsnap_jitter_test."
tags: [windows, e2e, cross-compile, test, mingw]
purity: impure
kind: function
signature: "e2e_run_cpp_windows(target string, --no-build, --no-deploy) int"
params:
- name: target
desc: "Nombre del target CMake del registry (ej. altsnap_jitter_test)"
- name: --no-build
desc: "Saltar cross-compile (usa el .exe ya construido en cpp/build/windows/)"
- name: --no-deploy
desc: "Saltar copia a Desktop\\apps (asume que ya esta deployed)"
output: "Exit code del .exe (0 = pass, no-cero = fail). stdout/stderr del .exe se imprimen tal cual."
uses_functions:
- build_cpp_windows_bash_infra
uses_types: []
returns: ""
returns_optional: false
error_type: "exit_code_bash_core"
imports: []
example: |
source bash/functions/infra/e2e_run_cpp_windows.sh
e2e_run_cpp_windows altsnap_jitter_test
# cross-compila, taskkill previo, copia a /mnt/c/Users/lucas/Desktop/apps/altsnap_jitter_test/
# ejecuta y devuelve exit code
tested: false
file_path: "bash/functions/infra/e2e_run_cpp_windows.sh"
---
Lanzador para tests e2e de apps C++ en Windows desde WSL. Workflow:
1. **Cross-compile** via `build_cpp_windows_bash_infra` (skipable con `--no-build`).
2. **Localiza** `${target}.exe` bajo `cpp/build/windows/apps/<target>/` o el arbol completo de build.
3. **Mata instancia previa** con `taskkill.exe /IM <target>.exe /F` (evita `Permission denied` al copiar el exe).
4. **Deploy** a `/mnt/c/Users/lucas/Desktop/apps/<target>/` con sidecars (`assets/`, `runtime/`, `enrichers/`, `*.dll`).
5. **Run** nativamente desde WSL (`./target.exe` con cwd en deploy_dir, asi `local_files/` se crea ahi).
6. **Exit code** del .exe propaga al return de la funcion.
Variables de entorno:
- `FN_REGISTRY_ROOT` — raiz del registry (auto-detectado).
- `BUILD_WIN` — directorio de build cross (default `cpp/build/windows`).
- `WIN_DESKTOP_APPS` — root de deploy en Windows (default `/mnt/c/Users/lucas/Desktop/apps`).
Requiere WSL2 con interop a Windows (cmd.exe, taskkill.exe en PATH) y mingw-w64.
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# e2e_run_cpp_windows — Cross-compile a C++ app del registry para Windows
# con mingw-w64, deploy al Desktop de Windows (matando una posible instancia
# previa con taskkill.exe), lanzar el .exe nativamente desde WSL y devolver
# stdout + exit code. Pensado para apps tipo headless smoke / regression
# test (ej. altsnap_jitter_test) que arrancan, ejecutan un guion y salen.
#
# Uso (funcion via source):
# source bash/functions/infra/e2e_run_cpp_windows.sh
# e2e_run_cpp_windows altsnap_jitter_test # build + deploy + run
# e2e_run_cpp_windows altsnap_jitter_test --no-build # solo deploy + run
# e2e_run_cpp_windows altsnap_jitter_test --no-deploy # solo run (asume ya esta en Desktop)
#
# Requisitos:
# - WSL2 con interop a Windows habilitado (cmd.exe / taskkill.exe en PATH).
# - mingw-w64 instalado: sudo apt install mingw-w64
# - cpp/build/windows/ configurable via build_cpp_windows.sh.
# - C:\Users\lucas\Desktop accesible bajo /mnt/c/Users/lucas/Desktop.
#
# Salida:
# - stdout/stderr del .exe se imprimen tal cual.
# - Exit code de la funcion = exit code del .exe (0 = pass).
e2e_run_cpp_windows() {
set -euo pipefail
local target="${1:-}"
if [ -z "$target" ]; then
echo "[e2e_run_cpp_windows] Uso: e2e_run_cpp_windows <app_name> [--no-build] [--no-deploy]" >&2
return 2
fi
shift
local do_build=1
local do_deploy=1
while [ $# -gt 0 ]; do
case "$1" in
--no-build) do_build=0 ;;
--no-deploy) do_deploy=0 ;;
*) echo "[e2e_run_cpp_windows] Flag desconocida: $1" >&2; return 2 ;;
esac
shift
done
local registry_root="${FN_REGISTRY_ROOT:-}"
if [ -z "$registry_root" ]; then
# Walk up from cwd looking for the registry.db sentinel.
local d="$PWD"
while [ "$d" != "/" ]; do
if [ -f "$d/registry.db" ] && [ -d "$d/cpp" ]; then
registry_root="$d"; break
fi
d="$(dirname "$d")"
done
fi
if [ -z "$registry_root" ]; then
echo "[e2e_run_cpp_windows] No se localiza la raiz del registry. Exporta FN_REGISTRY_ROOT." >&2
return 2
fi
local cpp_root="$registry_root/cpp"
local build_dir="${BUILD_WIN:-$cpp_root/build/windows}"
local desktop_root="${WIN_DESKTOP_APPS:-/mnt/c/Users/lucas/Desktop/apps}"
local deploy_dir="$desktop_root/$target"
# 1. Cross-compile.
if [ "$do_build" -eq 1 ]; then
echo "[e2e_run_cpp_windows] cross-compile target=$target" >&2
# Propagate registry_root so build_cpp_windows doesn't trip over its
# own BASH_SOURCE-based detection.
export FN_REGISTRY_ROOT="$registry_root"
# shellcheck source=./build_cpp_windows.sh
source "$registry_root/bash/functions/infra/build_cpp_windows.sh"
build_cpp_windows "$target"
fi
# 2. Locate built .exe.
local exe_src
exe_src="$(find "$build_dir/apps/$target" -maxdepth 2 -name "${target}.exe" -type f 2>/dev/null | head -1 || true)"
if [ -z "$exe_src" ]; then
# Fallback: search the whole build tree (some targets land elsewhere).
exe_src="$(find "$build_dir" -name "${target}.exe" -type f 2>/dev/null | head -1 || true)"
fi
if [ -z "$exe_src" ]; then
echo "[e2e_run_cpp_windows] No se encontro ${target}.exe en $build_dir" >&2
return 1
fi
echo "[e2e_run_cpp_windows] exe: $exe_src" >&2
# 3. Deploy a Desktop\apps\<target>.
if [ "$do_deploy" -eq 1 ]; then
# Mata instancia previa si esta corriendo (evita "Permission denied" al cp).
if command -v taskkill.exe &>/dev/null; then
taskkill.exe /IM "${target}.exe" /F >/dev/null 2>&1 || true
fi
mkdir -p "$deploy_dir"
cp -f "$exe_src" "$deploy_dir/"
# Copia assets si existen junto al exe (TTFs, runtime, ...).
local exe_dir
exe_dir="$(dirname "$exe_src")"
for sidecar in assets runtime enrichers; do
if [ -d "$exe_dir/$sidecar" ]; then
cp -rf "$exe_dir/$sidecar" "$deploy_dir/"
fi
done
# DLLs sueltos (mingw runtime, sqlite, etc.) si los hubiera.
find "$exe_dir" -maxdepth 1 -name "*.dll" -exec cp -f {} "$deploy_dir/" \; 2>/dev/null || true
echo "[e2e_run_cpp_windows] deployed -> $deploy_dir" >&2
fi
# 4. Run desde WSL. cd al deploy_dir para que exe_dir() apunte al sitio
# correcto (local_files/imgui.ini se crea ahi).
if [ ! -x "$deploy_dir/${target}.exe" ]; then
echo "[e2e_run_cpp_windows] No hay ${target}.exe en $deploy_dir" >&2
return 1
fi
echo "[e2e_run_cpp_windows] launch $deploy_dir/${target}.exe" >&2
(
cd "$deploy_dir"
./"${target}.exe"
)
local rc=$?
echo "[e2e_run_cpp_windows] exit=$rc" >&2
return "$rc"
}
# Invocacion directa como script.
if [ "${BASH_SOURCE[0]:-}" = "${0:-}" ] && [ -n "${BASH_SOURCE[0]:-}" ]; then
e2e_run_cpp_windows "$@"
fi
+36
View File
@@ -0,0 +1,36 @@
---
name: keepass_delete
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "keepass_delete(entry: string)"
description: "Elimina una entry del KeePassXC database via keepassxc-cli rm. La entry pasa a la papelera dentro del .kdbx (no se borra fisicamente)."
tags: [keepass, keepassxc, secret, credential, delete]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: entry
desc: "path de la entry a eliminar"
output: "ninguno (exit 0 si OK)"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/keepass_delete.sh"
---
## Ejemplo
```bash
source keepass_delete.sh
keepass_delete "Servers/old-server"
```
## Notas
- KeePassXC mueve a Recycle Bin interno por defecto. Vaciar manualmente desde GUI si quieres borrado fisico.
+44
View File
@@ -0,0 +1,44 @@
# keepass_delete
# --------------
# Elimina una entry del KeePassXC database.
#
# REQUIERE:
# - keepassxc-cli instalado
# - KEEPASS_DB (env): ruta absoluta al .kdbx
# - master password en pass o env KEEPASS_PASSWORD
#
# USO (sourced):
# source keepass_delete.sh
# keepass_delete "Servers/old-server"
keepass_delete() {
local entry="$1"
if [ -z "$entry" ]; then
echo "keepass_delete: se requiere entry" >&2
return 1
fi
local db="${KEEPASS_DB:-}"
if [ -z "$db" ] || [ ! -f "$db" ]; then
echo "keepass_delete: KEEPASS_DB no valida: $db" >&2
return 1
fi
local master
if [ -n "${KEEPASS_PASSWORD:-}" ]; then
master="$KEEPASS_PASSWORD"
else
master=$(pass show "${KEEPASS_MASTER_ENTRY:-meta/keepassxc-master}" 2>/dev/null | head -n1)
if [ -z "$master" ]; then
echo "keepass_delete: no master pass" >&2
return 1
fi
fi
printf '%s\n' "$master" | keepassxc-cli rm -q "$db" "$entry" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "keepass_delete: fallo al borrar '$entry'" >&2
return 1
fi
}
+46
View File
@@ -0,0 +1,46 @@
---
name: keepass_dump
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "keepass_dump() -> json"
description: "Exporta toda la BD KeePassXC como array JSON. Una sola apertura del .kdbx via keepassxc-cli export -f xml + python3 etree para parsear. Cada elemento: {path,title,username,password,url,notes}."
tags: [keepass, keepassxc, dump, export, batch]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params: []
output: "array JSON de objetos {path,title,username,password,url,notes}"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/keepass_dump.sh"
---
## Ejemplo
```bash
source keepass_dump.sh
data=$(keepass_dump)
# Filtrar por grupo
echo "$data" | jq '.[] | select(.path | startswith("Servers/"))'
# Solo passwords no vacios
echo "$data" | jq '.[] | select(.password != "")'
# Contar
echo "$data" | jq 'length'
```
## Notas
- KeePassXC 2.6.x export solo soporta `xml` y `csv` (no JSON nativo). Por eso pasamos por python3.
- 2.7.0+ tiene `-f json` directo; este wrapper sigue funcionando.
- Output ya descifrado (master password aplicada en export). El atributo `Protected="True"` del XML solo es marker.
- El leading "Root" del KDBX se omite en `path`.
+91
View File
@@ -0,0 +1,91 @@
# keepass_dump
# ------------
# Exporta toda la BD KeePassXC como array JSON. Una sola apertura del .kdbx.
# Cada elemento: {path, title, username, password, url, notes}.
#
# REQUIERE:
# - keepassxc-cli instalado
# - python3 (stdlib xml.etree)
# - KEEPASS_DB (env): ruta absoluta al .kdbx
# - master password en pass o env KEEPASS_PASSWORD
#
# USO (sourced):
# source keepass_dump.sh
# data=$(keepass_dump)
# echo "$data" | jq '.[] | select(.path | startswith("Servers/"))'
keepass_dump() {
local db="${KEEPASS_DB:-}"
if [ -z "$db" ] || [ ! -f "$db" ]; then
echo "keepass_dump: KEEPASS_DB no valida: $db" >&2
return 1
fi
local master
if [ -n "${KEEPASS_PASSWORD:-}" ]; then
master="$KEEPASS_PASSWORD"
else
master=$(pass show "${KEEPASS_MASTER_ENTRY:-meta/keepassxc-master}" 2>/dev/null | head -n1)
if [ -z "$master" ]; then
echo "keepass_dump: no master pass" >&2
return 1
fi
fi
local xml
xml=$(printf '%s\n' "$master" | keepassxc-cli export -q -f xml "$db" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$xml" ]; then
echo "keepass_dump: export xml fallo (master incorrecta?)" >&2
return 1
fi
printf '%s' "$xml" | python3 -c '
import sys, json, re
import xml.etree.ElementTree as ET
root = ET.fromstring(sys.stdin.read())
out = []
def clean(s):
if not s:
return ""
s = s.strip().rstrip("/")
s = s.replace("/", "_")
s = re.sub(r"\s+", "_", s)
s = re.sub(r"_+", "_", s)
s = s.strip("_")
return s
def walk(group, path):
name_el = group.find("Name")
raw_name = name_el.text if name_el is not None and name_el.text else ""
name = clean(raw_name)
new_path = path + [name] if name and name != "Root" else path
for entry in group.findall("Entry"):
rec = {}
for s in entry.findall("String"):
k_el = s.find("Key")
v_el = s.find("Value")
if k_el is None or k_el.text is None:
continue
rec[k_el.text] = (v_el.text if v_el is not None and v_el.text else "")
title = clean(rec.get("Title", ""))
full = "/".join(new_path + [title]) if title else "/".join(new_path)
out.append({
"path": full,
"title": title,
"username": rec.get("UserName", ""),
"password": rec.get("Password", ""),
"url": rec.get("URL", ""),
"notes": rec.get("Notes", ""),
})
for sub in group.findall("Group"):
walk(sub, new_path)
root_grp = root.find("Root/Group")
if root_grp is not None:
walk(root_grp, [])
print(json.dumps(out, ensure_ascii=False))
'
}
+45
View File
@@ -0,0 +1,45 @@
---
name: keepass_generate
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "keepass_generate(entry: string, length?: int, username?: string, url?: string) -> string"
description: "Genera un password aleatorio (lower+upper+digits+special), lo almacena en una entry nueva y lo imprime a stdout. Length default 24."
tags: [keepass, keepassxc, secret, credential, generate, random]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: entry
desc: "path de la entry a crear"
- name: length
desc: "longitud del password (default 24)"
- name: username
desc: "username opcional"
- name: url
desc: "url opcional"
output: "password generado en texto plano"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/keepass_generate.sh"
---
## Ejemplo
```bash
source keepass_generate.sh
pwd=$(keepass_generate "Servers/new-vps" 32 "deploy" "https://vps.example.com")
echo "Generated: $pwd"
```
## Notas
- Genera con `keepassxc-cli generate -l -U -n -s` (lower, upper, numbers, special).
- Inserta con `keepassxc-cli add -p` reusando la misma sesion.
- El grupo padre debe existir.
+63
View File
@@ -0,0 +1,63 @@
# keepass_generate
# ----------------
# Genera un password aleatorio, lo almacena en una entry nueva del KeePassXC database
# y lo imprime a stdout.
#
# REQUIERE:
# - keepassxc-cli instalado
# - KEEPASS_DB (env): ruta absoluta al .kdbx
# - master password en pass o env KEEPASS_PASSWORD
#
# USO (sourced):
# source keepass_generate.sh
# pwd=$(keepass_generate "Servers/new-server")
# pwd=$(keepass_generate "Servers/new-server" 32)
# pwd=$(keepass_generate "Servers/new-server" 32 "admin" "https://new.example.com")
keepass_generate() {
local entry="$1"
local length="${2:-24}"
local username="${3:-}"
local url="${4:-}"
if [ -z "$entry" ]; then
echo "keepass_generate: se requiere entry" >&2
return 1
fi
local db="${KEEPASS_DB:-}"
if [ -z "$db" ] || [ ! -f "$db" ]; then
echo "keepass_generate: KEEPASS_DB no valida: $db" >&2
return 1
fi
local master
if [ -n "${KEEPASS_PASSWORD:-}" ]; then
master="$KEEPASS_PASSWORD"
else
master=$(pass show "${KEEPASS_MASTER_ENTRY:-meta/keepassxc-master}" 2>/dev/null | head -n1)
if [ -z "$master" ]; then
echo "keepass_generate: no master pass" >&2
return 1
fi
fi
local pwd
pwd=$(keepassxc-cli generate -L "$length" -l -U -n -s 2>/dev/null)
if [ -z "$pwd" ]; then
echo "keepass_generate: fallo al generar password" >&2
return 1
fi
local args=(-q -p)
[ -n "$username" ] && args+=(-u "$username")
[ -n "$url" ] && args+=(--url "$url")
printf '%s\n%s\n' "$master" "$pwd" | keepassxc-cli add "${args[@]}" "$db" "$entry" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "keepass_generate: fallo al insertar '$entry'" >&2
return 1
fi
printf '%s' "$pwd"
}
+51
View File
@@ -0,0 +1,51 @@
---
name: keepass_get
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "keepass_get(entry: string, attr?: string) -> string"
description: "Lee un atributo (Password por defecto) de una entry del KeePassXC database via keepassxc-cli. Resuelve master password desde pass (meta/keepassxc-master) o env KEEPASS_PASSWORD."
tags: [keepass, keepassxc, secret, credential, get]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: entry
desc: "path de la entry dentro del .kdbx (ej. 'Servers/prod-mysql')"
- name: attr
desc: "atributo a leer (Password, UserName, URL, Notes, Title); default Password"
output: "valor del atributo en texto plano (sin newline final)"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/keepass_get.sh"
---
## Ejemplo
```bash
export KEEPASS_DB="/mnt/d/Tr4Shhh_FOLDER/Sync/PssDtbs/PassDataBase.kdbx"
source keepass_get.sh
pwd=$(keepass_get "Servers/prod-mysql")
user=$(keepass_get "Servers/prod-mysql" UserName)
```
## Setup
Master password se guarda una vez en pass:
```bash
pass insert meta/keepassxc-master
```
## Notas
- Wrappea `keepassxc-cli show -s -a <attr>`.
- Cada call reabre la BD (CLI stateless). Para batch, usa `keepass_dump`.
- `KEEPASS_PASSWORD` env tiene prioridad sobre `pass`.
+57
View File
@@ -0,0 +1,57 @@
# keepass_get
# -----------
# Lee un atributo de una entry del KeePassXC database.
# Atributo por defecto: Password. Tambien admite UserName, URL, Notes, Title, etc.
#
# REQUIERE:
# - keepassxc-cli instalado
# - KEEPASS_DB (env): ruta absoluta al .kdbx
# - master password en pass: `pass insert meta/keepassxc-master`
# o env KEEPASS_PASSWORD
# - override pass entry con KEEPASS_MASTER_ENTRY
#
# USO (sourced):
# source keepass_get.sh
# pwd=$(keepass_get "Servers/prod-mysql")
# user=$(keepass_get "Servers/prod-mysql" UserName)
# url=$(keepass_get "Servers/prod-mysql" URL)
keepass_get() {
local entry="$1"
local attr="${2:-Password}"
if [ -z "$entry" ]; then
echo "keepass_get: se requiere path de entry" >&2
return 1
fi
local db="${KEEPASS_DB:-}"
if [ -z "$db" ]; then
echo "keepass_get: KEEPASS_DB no definida" >&2
return 1
fi
if [ ! -f "$db" ]; then
echo "keepass_get: db no existe: $db" >&2
return 1
fi
local master
if [ -n "${KEEPASS_PASSWORD:-}" ]; then
master="$KEEPASS_PASSWORD"
else
master=$(pass show "${KEEPASS_MASTER_ENTRY:-meta/keepassxc-master}" 2>/dev/null | head -n1)
if [ -z "$master" ]; then
echo "keepass_get: no master pass (set KEEPASS_PASSWORD o pass insert ${KEEPASS_MASTER_ENTRY:-meta/keepassxc-master})" >&2
return 1
fi
fi
local value
value=$(printf '%s\n' "$master" | keepassxc-cli show -q -s -a "$attr" "$db" "$entry" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$value" ]; then
echo "keepass_get: no se pudo leer '$entry' attr '$attr'" >&2
return 1
fi
printf '%s' "$value"
}
+42
View File
@@ -0,0 +1,42 @@
---
name: keepass_list
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "keepass_list(prefix?: string) -> json"
description: "Lista paths de entries del KeePassXC database como array JSON. Filtra opcionalmente por prefijo de grupo. Internamente usa keepass_dump y proyecta solo los paths."
tags: [keepass, keepassxc, list]
uses_functions:
- keepass_dump_bash_infra
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: prefix
desc: "prefijo de path para filtrar (ej. 'Servers/'); vacio = todas"
output: "array JSON de strings con paths"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/keepass_list.sh"
---
## Ejemplo
```bash
source keepass_dump.sh
source keepass_list.sh
all=$(keepass_list)
servers=$(keepass_list "Servers/")
echo "$servers" | jq -r '.[]'
```
## Notas
- Auto-sourcea `keepass_dump.sh` desde el mismo directorio si no esta cargado.
- Para acceder a campos completos (password, username, url) usa `keepass_dump` directo.
+39
View File
@@ -0,0 +1,39 @@
# keepass_list
# ------------
# Lista paths de entries del KeePassXC database como array JSON.
# Filtra opcionalmente por prefijo de grupo.
#
# REQUIERE:
# - keepass_dump (sourced o en PATH del registry)
# - jq instalado
#
# USO (sourced):
# source keepass_dump.sh
# source keepass_list.sh
# all=$(keepass_list)
# servers=$(keepass_list "Servers/")
keepass_list() {
local prefix="$1"
if ! declare -F keepass_dump >/dev/null 2>&1; then
local here
here=$(dirname "${BASH_SOURCE[0]}")
if [ -f "$here/keepass_dump.sh" ]; then
# shellcheck source=keepass_dump.sh
source "$here/keepass_dump.sh"
else
echo "keepass_list: keepass_dump no disponible" >&2
return 1
fi
fi
local dump
dump=$(keepass_dump) || return 1
if [ -n "$prefix" ]; then
printf '%s' "$dump" | jq --arg p "$prefix" '[.[] | .path | select(startswith($p))]'
else
printf '%s' "$dump" | jq '[.[] | .path]'
fi
}
+41
View File
@@ -0,0 +1,41 @@
---
name: keepass_search
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "keepass_search(term: string) -> json"
description: "Busca entries en el KeePassXC database por substring. Devuelve array JSON de paths que matchean (title/username/url/notes)."
tags: [keepass, keepassxc, search, query]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: term
desc: "substring a buscar (case-insensitive)"
output: "array JSON de strings con paths matched, ej: [\"Servers/prod\", \"Web/github\"]"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/keepass_search.sh"
---
## Ejemplo
```bash
source keepass_search.sh
matches=$(keepass_search "github")
# [
# "Web/github-personal",
# "Web/github-work"
# ]
```
## Notas
- Wrappea `keepassxc-cli search`.
- El leading `/` del CLI se quita antes de devolver.
+50
View File
@@ -0,0 +1,50 @@
# keepass_search
# --------------
# Busca entries en el KeePassXC database por substring (en title, username, url, notes).
# Devuelve un array JSON de paths que matchean.
#
# REQUIERE:
# - keepassxc-cli instalado
# - jq instalado
# - KEEPASS_DB (env): ruta absoluta al .kdbx
# - master password en pass o env KEEPASS_PASSWORD
#
# USO (sourced):
# source keepass_search.sh
# matches=$(keepass_search "github")
# echo "$matches" | jq .
keepass_search() {
local term="$1"
if [ -z "$term" ]; then
echo "keepass_search: se requiere termino de busqueda" >&2
return 1
fi
local db="${KEEPASS_DB:-}"
if [ -z "$db" ] || [ ! -f "$db" ]; then
echo "keepass_search: KEEPASS_DB no valida: $db" >&2
return 1
fi
local master
if [ -n "${KEEPASS_PASSWORD:-}" ]; then
master="$KEEPASS_PASSWORD"
else
master=$(pass show "${KEEPASS_MASTER_ENTRY:-meta/keepassxc-master}" 2>/dev/null | head -n1)
if [ -z "$master" ]; then
echo "keepass_search: no master pass" >&2
return 1
fi
fi
local out
out=$(printf '%s\n' "$master" | keepassxc-cli locate -q "$db" "$term" 2>/dev/null)
if [ $? -ne 0 ]; then
echo "keepass_search: keepassxc-cli locate fallo" >&2
return 1
fi
printf '%s\n' "$out" | grep -v '^$' | sed 's|^/||' | jq -R . | jq -s .
}
+44
View File
@@ -0,0 +1,44 @@
---
name: keepass_set
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "keepass_set(entry: string, password: string, username?: string, url?: string)"
description: "Crea o sobreescribe una entry en el KeePassXC database. Auto-detecta si existe (edit) o no (add). Soporta username y url opcionales."
tags: [keepass, keepassxc, secret, credential, set, write]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: entry
desc: "path de la entry (ej. 'Servers/prod-mysql'); si el grupo no existe falla"
- name: password
desc: "password en texto plano a almacenar"
- name: username
desc: "username opcional"
- name: url
desc: "url opcional"
output: "ninguno (exit 0 si OK)"
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/keepass_set.sh"
---
## Ejemplo
```bash
source keepass_set.sh
keepass_set "Servers/prod-mysql" "secret123" "admin" "https://prod.example.com"
```
## Notas
- Add: `keepassxc-cli add -p`. Edit: `keepassxc-cli edit -p`.
- Existencia detectada via `show -q` (exit code).
- El grupo (parte antes del ultimo `/`) debe existir; KeePassXC no auto-crea jerarquia.
+58
View File
@@ -0,0 +1,58 @@
# keepass_set
# -----------
# Crea o sobreescribe una entry en el KeePassXC database.
# Auto-detecta si existe (edit) o no (add).
#
# REQUIERE:
# - keepassxc-cli instalado
# - KEEPASS_DB (env): ruta absoluta al .kdbx
# - master password en pass o env KEEPASS_PASSWORD
#
# USO (sourced):
# source keepass_set.sh
# keepass_set "Servers/prod-mysql" "secret123"
# keepass_set "Servers/prod-mysql" "secret123" "admin" "https://prod.example.com"
keepass_set() {
local entry="$1"
local password="$2"
local username="${3:-}"
local url="${4:-}"
if [ -z "$entry" ] || [ -z "$password" ]; then
echo "keepass_set: se requieren entry y password" >&2
return 1
fi
local db="${KEEPASS_DB:-}"
if [ -z "$db" ] || [ ! -f "$db" ]; then
echo "keepass_set: KEEPASS_DB no valida: $db" >&2
return 1
fi
local master
if [ -n "${KEEPASS_PASSWORD:-}" ]; then
master="$KEEPASS_PASSWORD"
else
master=$(pass show "${KEEPASS_MASTER_ENTRY:-meta/keepassxc-master}" 2>/dev/null | head -n1)
if [ -z "$master" ]; then
echo "keepass_set: no master pass" >&2
return 1
fi
fi
local cmd="add"
if printf '%s\n' "$master" | keepassxc-cli show -q "$db" "$entry" >/dev/null 2>&1; then
cmd="edit"
fi
local args=(-q -p)
[ -n "$username" ] && args+=(-u "$username")
[ -n "$url" ] && args+=(--url "$url")
printf '%s\n%s\n' "$master" "$password" | keepassxc-cli "$cmd" "${args[@]}" "$db" "$entry" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "keepass_set: fallo al $cmd '$entry'" >&2
return 1
fi
}