diff --git a/scripts/utils/find_keepassxc.sh b/scripts/utils/find_keepassxc.sh new file mode 100755 index 0000000..f80dcba --- /dev/null +++ b/scripts/utils/find_keepassxc.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# Find all KeePassXC database files (.kdbx) on the system and move them to a single location +# Uses 'fd' for fast searching - install with: sudo pacman -S fd +# +# Usage: ./find_keepassxc.sh [destination_directory] +# Default destination: ~/Keepass + +set -euo pipefail + +# Source common library if available +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then + # shellcheck source=../lib/common.sh + source "$SCRIPT_DIR/../lib/common.sh" +else + log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; } +fi + +# Configuration +DEST_DIR="${1:-$HOME/Keepass}" +SEARCH_ROOT="/" +TIMEOUT_SECONDS=30 + +# Ensure fd is installed +if ! command -v fd &>/dev/null; then + log "ERROR: 'fd' is not installed. Install with: sudo pacman -S fd" + exit 1 +fi + +# Create destination directory if it doesn't exist +mkdir -p "$DEST_DIR" +log "Destination directory: $DEST_DIR" + +# Find all .kdbx files using fd (very fast, respects .gitignore by default, but we use -u for unrestricted) +# -e kdbx: search by extension +# -u: unrestricted (include hidden and ignored files) +# -a: absolute paths +# --one-file-system: don't cross filesystem boundaries (optional, remove if you want to search mounted drives) +log "Searching for .kdbx files across the system (timeout: ${TIMEOUT_SECONDS}s)..." + +# Use timeout to ensure the search doesn't take too long +# Exclude /proc, /sys, /dev, /run, /tmp, /var/cache, /var/tmp for speed +FOUND_FILES=$(timeout "$TIMEOUT_SECONDS" fd \ + -e kdbx \ + -u \ + -a \ + --exclude '/proc' \ + --exclude '/sys' \ + --exclude '/dev' \ + --exclude '/run' \ + --exclude '/tmp' \ + --exclude '/var/cache' \ + --exclude '/var/tmp' \ + --exclude '/snap' \ + --exclude '/.snapshots' \ + --exclude '/lost+found' \ + . "$SEARCH_ROOT" 2>/dev/null || true) + +if [[ -z "$FOUND_FILES" ]]; then + log "No .kdbx files found." + exit 0 +fi + +# Count and display found files +FILE_COUNT=$(echo "$FOUND_FILES" | wc -l) +log "Found $FILE_COUNT .kdbx file(s):" +echo "$FOUND_FILES" | while read -r file; do + echo " - $file" +done + +# Move files to destination +log "Moving files to $DEST_DIR..." +MOVED_COUNT=0 +SKIPPED_COUNT=0 + +while IFS= read -r src_file; do + [[ -z "$src_file" ]] && continue + + # Skip if file is already in destination + if [[ "$(dirname "$src_file")" == "$DEST_DIR" ]]; then + log "Skipping (already in destination): $src_file" + ((SKIPPED_COUNT++)) || true + continue + fi + + # Get the base filename + base_name=$(basename "$src_file") + dest_file="$DEST_DIR/$base_name" + + # Handle filename conflicts by adding a number suffix + if [[ -f "$dest_file" ]]; then + # Check if it's the exact same file (by content) + if cmp -s "$src_file" "$dest_file"; then + log "Skipping (identical file exists): $src_file" + # Remove the duplicate source file + rm -v "$src_file" + ((SKIPPED_COUNT++)) || true + continue + fi + + # Different file with same name - add suffix + counter=1 + name_without_ext="${base_name%.kdbx}" + while [[ -f "$dest_file" ]]; do + dest_file="$DEST_DIR/${name_without_ext} ($counter).kdbx" + ((counter++)) + done + log "Renaming to avoid conflict: $base_name -> $(basename "$dest_file")" + fi + + # Move the file + if mv -v "$src_file" "$dest_file"; then + ((MOVED_COUNT++)) || true + else + log "ERROR: Failed to move $src_file" + fi +done <<<"$FOUND_FILES" + +log "Done! Moved $MOVED_COUNT file(s), skipped $SKIPPED_COUNT file(s)." +log "All KeePassXC databases are now in: $DEST_DIR" + +# List final contents +log "Contents of $DEST_DIR:" +ls -la "$DEST_DIR"/*.kdbx 2>/dev/null || log "No .kdbx files in destination" diff --git a/scripts/utils/sync_keepassxc.sh b/scripts/utils/sync_keepassxc.sh new file mode 100755 index 0000000..16f5670 --- /dev/null +++ b/scripts/utils/sync_keepassxc.sh @@ -0,0 +1,173 @@ +#!/bin/bash +# Merge all KeePassXC database files in a directory into a single database +# Merges databases one by one, deleting the source after each successful merge +# until only ONE database remains. +# +# IMPORTANT: You will be prompted for the master password for each database! +# Make sure all databases use the same master password, or know each password. +# +# Usage: ./sync_keepassxc.sh [directory] +# Default directory: ~/Keepass + +set -euo pipefail + +# Source common library if available +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then + # shellcheck source=../lib/common.sh + source "$SCRIPT_DIR/../lib/common.sh" +else + log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; } +fi + +# Configuration +KEEPASS_DIR="${1:-$HOME/Keepass}" +BACKUP_DIR="$KEEPASS_DIR/.backup_$(date +%Y%m%d_%H%M%S)" + +# Ensure keepassxc-cli is installed +if ! command -v keepassxc-cli &>/dev/null; then + log "ERROR: 'keepassxc-cli' is not installed. Install with: sudo pacman -S keepassxc" + exit 1 +fi + +# Check if directory exists +if [[ ! -d "$KEEPASS_DIR" ]]; then + log "ERROR: Directory does not exist: $KEEPASS_DIR" + exit 1 +fi + +# Find all .kdbx files +mapfile -t KDBX_FILES < <(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | sort) + +if [[ ${#KDBX_FILES[@]} -eq 0 ]]; then + log "No .kdbx files found in $KEEPASS_DIR" + exit 0 +fi + +if [[ ${#KDBX_FILES[@]} -eq 1 ]]; then + log "Only one .kdbx file found. Nothing to merge." + log "File: ${KDBX_FILES[0]}" + exit 0 +fi + +log "Found ${#KDBX_FILES[@]} .kdbx files in $KEEPASS_DIR:" +for f in "${KDBX_FILES[@]}"; do + echo " - $(basename "$f")" +done + +# Create backup directory +mkdir -p "$BACKUP_DIR" +log "Creating backups in: $BACKUP_DIR" + +# Backup all files before any operation +for f in "${KDBX_FILES[@]}"; do + cp -v "$f" "$BACKUP_DIR/" +done +log "All files backed up successfully." + +echo "" +echo "==============================================" +echo "WARNING: This will merge all databases into ONE" +echo "and DELETE the source files after each merge." +echo "" +echo "Backups are stored in: $BACKUP_DIR" +echo "==============================================" +echo "" +read -rp "Do you want to continue? (yes/no): " CONFIRM +if [[ "$CONFIRM" != "yes" ]]; then + log "Aborted by user." + exit 1 +fi + +# Select the target database (the one to merge INTO) +# We'll use the first one alphabetically, or you could let user choose +TARGET_DB="${KDBX_FILES[0]}" +log "Target database (will contain all merged data): $(basename "$TARGET_DB")" + +echo "" +echo "You will need to enter the master password for the databases." +echo "" + +# Read target database password +read -rsp "Enter master password for TARGET database ($(basename "$TARGET_DB")): " TARGET_PASSWORD +echo "" + +# Verify target password works +if ! echo "$TARGET_PASSWORD" | keepassxc-cli ls "$TARGET_DB" &>/dev/null; then + log "ERROR: Failed to open target database. Wrong password?" + exit 1 +fi +log "Target database password verified." + +# Ask if all databases share the same password +echo "" +read -rp "Do ALL databases share the same master password? (y/n): " SAME_PASSWORD +SAME_PASSWORD="${SAME_PASSWORD,,}" # lowercase + +# Merge each source database into the target +MERGE_COUNT=0 +for ((i = 1; i < ${#KDBX_FILES[@]}; i++)); do + SOURCE_DB="${KDBX_FILES[$i]}" + log "" + log "Merging $(basename "$SOURCE_DB") into $(basename "$TARGET_DB")..." + + # Reuse target password if user confirmed all are the same + if [[ "$SAME_PASSWORD" == "y" || "$SAME_PASSWORD" == "yes" ]]; then + SOURCE_PASSWORD="$TARGET_PASSWORD" + else + # Ask for source password (might be different) + echo "" + read -rsp "Enter master password for SOURCE database ($(basename "$SOURCE_DB")): " SOURCE_PASSWORD + echo "" + fi + + # Verify source password + if ! echo "$SOURCE_PASSWORD" | keepassxc-cli ls "$SOURCE_DB" &>/dev/null; then + log "ERROR: Failed to open source database $(basename "$SOURCE_DB"). Wrong password?" + log "Skipping this database. You can try again later." + continue + fi + + # Perform the merge + # keepassxc-cli merge requires: target_db source_db + # It will prompt for passwords + if echo -e "${TARGET_PASSWORD}\n${SOURCE_PASSWORD}" | keepassxc-cli merge "$TARGET_DB" "$SOURCE_DB"; then + log "Successfully merged $(basename "$SOURCE_DB")" + + # Delete the source database after successful merge + log "Deleting source database: $(basename "$SOURCE_DB")" + rm -v "$SOURCE_DB" + ((MERGE_COUNT++)) || true + else + log "ERROR: Failed to merge $(basename "$SOURCE_DB")" + log "Source database NOT deleted. Check the backup and try manually." + fi +done + +echo "" +log "==============================================" +log "Merge complete!" +log "Merged $MERGE_COUNT database(s) into: $(basename "$TARGET_DB")" +log "Backups are preserved in: $BACKUP_DIR" +log "==============================================" + +# Show final state +log "" +log "Remaining .kdbx files in $KEEPASS_DIR:" +find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f -exec basename {} \; + +# Rename to clean name if desired +FINAL_COUNT=$(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | wc -l) +if [[ $FINAL_COUNT -eq 1 ]]; then + log "" + FINAL_NAME="$KEEPASS_DIR/Passwords.kdbx" + if [[ "$TARGET_DB" != "$FINAL_NAME" ]]; then + read -rp "Rename final database to 'Passwords.kdbx'? (y/n): " RENAME_CONFIRM + if [[ "$RENAME_CONFIRM" == "y" ]]; then + mv -v "$TARGET_DB" "$FINAL_NAME" + log "Final database: $FINAL_NAME" + fi + fi + log "" + log "SUCCESS: You now have exactly ONE KeePassXC database!" +fi