#!/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