# 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)) ' }