- Implement [MainWindow] section stripping in backup and restore scripts to prevent screen geometry/monitor layout noise across devices - Add robust hostname detection and customizable archive naming in backup.sh - Add optional interactive git commit and push prompt upon successful backup - Update restore.sh diff preview and extraction to respect sanitized konsolerc - Add AGENTS.md documentation detailing script architecture and conventions
170 lines
5.2 KiB
Bash
Executable File
170 lines
5.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Script directory and backups directory setup
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
BACKUP_DIR="${SCRIPT_DIR}/backups"
|
|
mkdir -p "${BACKUP_DIR}"
|
|
|
|
# Pre-flight requirements check for core tools
|
|
for cmd in zip awk find mktemp; do
|
|
if ! command -v "${cmd}" >/dev/null 2>&1; then
|
|
echo "Error: Required tool '${cmd}' is not installed or not in PATH." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Determine device hostname using robust fallbacks (no inetutils required)
|
|
HOSTNAME_STR=""
|
|
if command -v hostname >/dev/null 2>&1; then
|
|
HOSTNAME_STR="$(hostname -s 2>/dev/null || hostname 2>/dev/null || true)"
|
|
fi
|
|
if [ -z "${HOSTNAME_STR}" ]; then
|
|
HOSTNAME_STR="${HOST:-${HOSTNAME:-}}"
|
|
fi
|
|
if [ -z "${HOSTNAME_STR}" ] && [ -r /proc/sys/kernel/hostname ]; then
|
|
HOSTNAME_STR="$(cat /proc/sys/kernel/hostname 2>/dev/null || true)"
|
|
fi
|
|
if [ -z "${HOSTNAME_STR}" ] && [ -r /etc/hostname ]; then
|
|
HOSTNAME_STR="$(cat /etc/hostname 2>/dev/null || true)"
|
|
fi
|
|
HOSTNAME_STR="$(echo "${HOSTNAME_STR:-}" | xargs)"
|
|
[ -z "${HOSTNAME_STR}" ] && HOSTNAME_STR="host"
|
|
|
|
TIMESTAMP="$(date +"%Y%m%d_%H%M%S")"
|
|
DEFAULT_FILENAME="konsole_backup_${HOSTNAME_STR}_${TIMESTAMP}.zip"
|
|
|
|
# Temporary staging directory
|
|
TEMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "${TEMP_DIR}"' EXIT
|
|
|
|
echo "=== Starting Konsole Configuration Backup ==="
|
|
|
|
# Define target paths relative to HOME
|
|
CONFIG_PATHS=(
|
|
".config/konsolerc"
|
|
".local/share/konsole"
|
|
".local/share/kxmlgui5/konsole"
|
|
".config/konsole.notifyrc"
|
|
".config/konsolesshconfig"
|
|
)
|
|
|
|
COPIED_COUNT=0
|
|
|
|
for rel_path in "${CONFIG_PATHS[@]}"; do
|
|
src_path="${HOME}/${rel_path}"
|
|
dest_path="${TEMP_DIR}/${rel_path}"
|
|
|
|
if [ -e "${src_path}" ]; then
|
|
mkdir -p "$(dirname "${dest_path}")"
|
|
if [ "${rel_path}" = ".config/konsolerc" ]; then
|
|
# Filter out [MainWindow] section to avoid machine-specific geometry/monitor noise
|
|
awk '
|
|
/^\[MainWindow\]/ { skip=1; next }
|
|
/^\[.*\]/ { skip=0 }
|
|
!skip { print }
|
|
' "${src_path}" > "${dest_path}"
|
|
echo " [+] Staged & Sanitized: ~/${rel_path}"
|
|
else
|
|
cp -a "${src_path}" "${dest_path}"
|
|
echo " [+] Staged: ~/${rel_path}"
|
|
fi
|
|
COPIED_COUNT=$((COPIED_COUNT + 1))
|
|
else
|
|
echo " [-] Skipped (not found): ~/${rel_path}"
|
|
fi
|
|
done
|
|
|
|
if [ "${COPIED_COUNT}" -eq 0 ]; then
|
|
echo "Error: No Konsole configuration files found to backup."
|
|
exit 1
|
|
fi
|
|
|
|
# Determine appropriate interactive input source
|
|
get_input() {
|
|
local prompt_msg="$1"
|
|
local var_name="$2"
|
|
local default_val="${3:-}"
|
|
|
|
if [ -t 0 ]; then
|
|
read -r -p "${prompt_msg}" "$var_name" || true
|
|
elif [ -c /dev/tty ] && [ -r /dev/tty ] && { : < /dev/tty; } 2>/dev/null; then
|
|
read -r -p "${prompt_msg}" "$var_name" < /dev/tty || true
|
|
else
|
|
read -r -p "${prompt_msg}" "$var_name" || true
|
|
fi
|
|
|
|
if [ -z "${!var_name:-}" ] && [ -n "${default_val}" ]; then
|
|
eval "${var_name}=\"${default_val}\""
|
|
fi
|
|
}
|
|
|
|
echo ""
|
|
# Prompt user for backup filename (like ssh-keygen)
|
|
CHOSEN_NAME=""
|
|
get_input "Enter backup filename [default: ${DEFAULT_FILENAME}]: " CHOSEN_NAME "${DEFAULT_FILENAME}"
|
|
CHOSEN_NAME="$(echo "${CHOSEN_NAME:-}" | xargs)"
|
|
|
|
if [ -z "${CHOSEN_NAME}" ]; then
|
|
ZIP_FILENAME="${DEFAULT_FILENAME}"
|
|
else
|
|
if [[ "${CHOSEN_NAME}" != *.zip ]]; then
|
|
ZIP_FILENAME="${CHOSEN_NAME}.zip"
|
|
else
|
|
ZIP_FILENAME="${CHOSEN_NAME}"
|
|
fi
|
|
fi
|
|
|
|
ZIP_FILEPATH="${BACKUP_DIR}/${ZIP_FILENAME}"
|
|
|
|
# Create zip file from staging area
|
|
echo "Creating backup archive: ${ZIP_FILENAME}..."
|
|
(
|
|
cd "${TEMP_DIR}"
|
|
zip -q -r "${ZIP_FILEPATH}" .
|
|
)
|
|
|
|
echo "Backup successfully saved to: ${ZIP_FILEPATH}"
|
|
|
|
# Retention Policy: keep at most 10 recent backup zip files
|
|
MAX_BACKUPS=10
|
|
|
|
mapfile -t BACKUP_FILES < <(find "${BACKUP_DIR}" -maxdepth 1 -name "konsole_backup_*.zip" | sort -r)
|
|
|
|
TOTAL_BACKUPS="${#BACKUP_FILES[@]}"
|
|
if [ "${TOTAL_BACKUPS}" -gt "${MAX_BACKUPS}" ]; then
|
|
echo "Cleaning up older backups (keeping top ${MAX_BACKUPS})..."
|
|
for (( i=MAX_BACKUPS; i<TOTAL_BACKUPS; i++ )); do
|
|
file_to_remove="${BACKUP_FILES[$i]}"
|
|
echo " Deleting old backup: $(basename "${file_to_remove}")"
|
|
if git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
git rm -f "${file_to_remove}" 2>/dev/null || rm -f "${file_to_remove}"
|
|
else
|
|
rm -f "${file_to_remove}"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
echo "=== Backup Complete (${TOTAL_BACKUPS} backup(s) currently stored) ==="
|
|
echo ""
|
|
|
|
# Optional Git Commit & Push
|
|
if git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
git_choice=""
|
|
get_input "Do you want to commit and push this backup to git? [y/N]: " git_choice "n"
|
|
case "${git_choice,,}" in
|
|
y|yes)
|
|
echo "Staging backup file in git..."
|
|
git add "${ZIP_FILEPATH}"
|
|
COMMIT_MSG="chore(konsole): backup config from ${HOSTNAME_STR} at ${TIMESTAMP}"
|
|
echo "Committing: \"${COMMIT_MSG}\"..."
|
|
git commit -m "${COMMIT_MSG}" || true
|
|
echo "Pushing to remote repository..."
|
|
git push || echo "Warning: git push failed. Please check your git remote configuration."
|
|
;;
|
|
*)
|
|
echo "Skipping git push."
|
|
;;
|
|
esac
|
|
fi
|