gnome-backup 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #!/usr/bin/env bash
  2. # Exit immediately if a command exits with a non-zero status
  3. set -e
  4. function show_usage() {
  5. echo "Usage:"
  6. echo " $0 [--dry-run] backup <archive_name.tgz>"
  7. echo " $0 [--dry-run] restore <archive_name.tgz> <target_home_directory> <target_username>"
  8. echo ""
  9. echo "Options:"
  10. echo " --dry-run Show what would be copied/extracted without making any actual changes."
  11. echo ""
  12. echo "Example:"
  13. echo " $0 --dry-run backup /tmp/my_gnome_setup.tgz"
  14. echo " sudo $0 restore /tmp/my_gnome_setup.tgz /home/guest guest"
  15. exit 1
  16. }
  17. # Check for dry-run flag
  18. DRY_RUN=0
  19. if [[ "$1" == "--dry-run" ]]; then
  20. DRY_RUN=1
  21. shift
  22. fi
  23. if [ "$#" -lt 2 ]; then
  24. show_usage
  25. fi
  26. ACTION="$1"
  27. ARCHIVE="$2"
  28. # Define the exact paths we care about (relative to the home directory)
  29. GNOME_PATHS=(
  30. ".config/monitors.xml"
  31. ".config/mimeapps.list"
  32. ".config/gtk-3.0"
  33. ".config/gtk-4.0"
  34. ".config/fontconfig"
  35. ".config/user-dirs.dirs"
  36. ".config/autostart"
  37. ".local/share/gnome-shell/extensions"
  38. ".local/share/backgrounds"
  39. ".local/share/fonts"
  40. ".local/share/themes"
  41. ".local/share/icons"
  42. ".themes"
  43. ".icons"
  44. ".fonts"
  45. )
  46. if [ "$ACTION" == "backup" ]; then
  47. if [ "$DRY_RUN" -eq 1 ]; then
  48. echo "==> [DRY RUN] Starting GNOME backup simulation..."
  49. echo "--> [DRY RUN] Would dump dconf database to dconf_backup.ini"
  50. echo "--> [DRY RUN] Would copy the following paths from $HOME:"
  51. for path in "${GNOME_PATHS[@]}"; do
  52. if [ -e "$HOME/$path" ]; then
  53. echo " [DRY RUN] MATCH: $path"
  54. else
  55. echo " [DRY RUN] MISSING (Skipping): $path"
  56. fi
  57. done
  58. echo "--> [DRY RUN] Would aggressively scrub any 'keyrings' directories."
  59. echo "--> [DRY RUN] Would compress all matched files to: $ARCHIVE"
  60. echo "==> [DRY RUN] Backup simulation complete. No files were actually touched."
  61. exit 0
  62. fi
  63. echo "==> Starting GNOME backup..."
  64. # Create a staging area in /tmp
  65. STAGING_DIR="/tmp/gnome_backup_$$"
  66. mkdir -p "$STAGING_DIR"
  67. echo "--> Dumping dconf database..."
  68. dconf dump / > "$STAGING_DIR/dconf_backup.ini"
  69. echo "--> Gathering visual assets and system configs..."
  70. for path in "${GNOME_PATHS[@]}"; do
  71. if [ -e "$HOME/$path" ]; then
  72. # Create the parent directory structure in the staging area
  73. mkdir -p "$STAGING_DIR/$(dirname "$path")"
  74. # Copy the files over, preserving their structure
  75. cp -a "$HOME/$path" "$STAGING_DIR/$(dirname "$path")/"
  76. echo " Saved: $path"
  77. fi
  78. done
  79. echo "--> Scrubbing keyrings and password data..."
  80. # Explicitly find and annihilate any keyrings that managed to sneak into the staging area
  81. find "$STAGING_DIR" -type d \( -name "keyrings" -o -name "kwalletd" \) -exec rm -rf {} + 2>/dev/null || true
  82. echo "--> Compressing to $ARCHIVE..."
  83. # Pack it all up from the staging directory
  84. tar -czf "$ARCHIVE" -C "$STAGING_DIR" .
  85. # Clean up the bloody mess in /tmp
  86. rm -rf "$STAGING_DIR"
  87. echo "==> Backup complete! Your setup is safe at $ARCHIVE"
  88. elif [ "$ACTION" == "restore" ]; then
  89. TARGET_DIR="$3"
  90. TARGET_USER="$4"
  91. if [ -z "$TARGET_DIR" ] || [ ! -d "$TARGET_DIR" ]; then
  92. echo "Error: You must provide a valid target directory."
  93. show_usage
  94. fi
  95. if [ -z "$TARGET_USER" ]; then
  96. echo "Error: You must provide the exact target username as the 4th argument."
  97. show_usage
  98. fi
  99. # We need root to properly chown the files to the new user
  100. if [ "$EUID" -ne 0 ] && [ "$DRY_RUN" -eq 0 ]; then
  101. echo "Warning: You must run the 'restore' command with sudo,"
  102. echo "otherwise fixing the file ownership for the new profile will fail."
  103. exit 1
  104. fi
  105. # Deduce the primary group for the target user (usually matches the username)
  106. TARGET_GROUP=$(id -gn "$TARGET_USER" 2>/dev/null || echo "$TARGET_USER")
  107. if [ "$DRY_RUN" -eq 1 ]; then
  108. echo "==> [DRY RUN] Starting GNOME restore simulation to $TARGET_DIR..."
  109. if [ ! -f "$ARCHIVE" ]; then
  110. echo "Error: Backup archive $ARCHIVE does not exist. Cannot simulate restore."
  111. exit 1
  112. fi
  113. echo "--> [DRY RUN] Target user explicitly set to: $TARGET_USER:$TARGET_GROUP"
  114. echo "--> [DRY RUN] Would extract the following contents from $ARCHIVE to $TARGET_DIR:"
  115. tar -tf "$ARCHIVE" | sed 's/^/ [DRY RUN] /'
  116. echo "--> [DRY RUN] Would exclude 'keyrings' and 'dconf_backup.ini' from file sync."
  117. echo "--> [DRY RUN] Would scaffold standard XDG user directories (Desktop, Downloads, etc.)"
  118. echo "--> [DRY RUN] Would run a blanket chown on $TARGET_DIR to $TARGET_USER:$TARGET_GROUP"
  119. echo "--> [DRY RUN] Would deploy helper script $TARGET_DIR/load_gnome_settings.sh"
  120. echo "==> [DRY RUN] Restore simulation complete. No files were actually deployed or altered."
  121. exit 0
  122. fi
  123. echo "==> Starting GNOME restore to $TARGET_DIR..."
  124. STAGING_DIR="/tmp/gnome_restore_$$"
  125. mkdir -p "$STAGING_DIR"
  126. echo "--> Extracting archive..."
  127. tar -xzf "$ARCHIVE" -C "$STAGING_DIR"
  128. echo "--> Deploying files into $TARGET_DIR..."
  129. # Copy everything EXCEPT the dconf dump and any leftover keyrings into the target directory
  130. rsync -a "$STAGING_DIR/" "$TARGET_DIR/" --exclude "dconf_backup.ini" --exclude "keyrings" --exclude "kwalletd"
  131. echo "--> Scaffolding standard XDG user directories..."
  132. # Create the empty default folders so GNOME doesn't shit a brick
  133. for dir in Desktop Documents Downloads Music Pictures Public Templates Videos; do
  134. mkdir -p "$TARGET_DIR/$dir"
  135. done
  136. echo "--> Ensuring .cache/dconf exists..."
  137. # Pre-create the dconf cache directory so the helper script never throws a 'Permission denied'
  138. mkdir -p "$TARGET_DIR/.cache/dconf"
  139. echo "--> Running a blanket ownership fix across the entire home directory to $TARGET_USER:$TARGET_GROUP..."
  140. # Force absolute ownership of every single file and folder in the home directory to the new user
  141. chown -R "$TARGET_USER:$TARGET_GROUP" "$TARGET_DIR"
  142. echo "--> Prepping the headless dconf restore script..."
  143. DCONF_SCRIPT="$TARGET_DIR/load_gnome_settings.sh"
  144. cp "$STAGING_DIR/dconf_backup.ini" "$TARGET_DIR/"
  145. cat << 'EOF' > "$DCONF_SCRIPT"
  146. #!/usr/bin/env bash
  147. echo "Loading GNOME settings into dconf..."
  148. # If there is no active display/Wayland session, we spawn a temporary headless D-Bus
  149. # so dconf can write the local database file without throwing a fit.
  150. if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
  151. echo "No graphical D-Bus session detected. Firing up a headless session..."
  152. dbus-run-session dconf load / < "$HOME/dconf_backup.ini"
  153. else
  154. dconf load / < "$HOME/dconf_backup.ini"
  155. fi
  156. echo "Done! You can safely delete this script and dconf_backup.ini."
  157. EOF
  158. chmod +x "$DCONF_SCRIPT"
  159. chown "$TARGET_USER:$TARGET_GROUP" "$DCONF_SCRIPT" "$TARGET_DIR/dconf_backup.ini"
  160. # Clean up /tmp
  161. rm -rf "$STAGING_DIR"
  162. echo "==> Files restored successfully!"
  163. echo "====================================================================="
  164. echo "IMPORTANT: Log into the new profile ($TARGET_USER) or switch to a TTY."
  165. echo "Run: ./load_gnome_settings.sh"
  166. echo "====================================================================="
  167. else
  168. echo "Error: Invalid action. Use 'backup' or 'restore'."
  169. show_usage
  170. fi