1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00

home-manager: add support for custom backup command (#6424) (#7153)

This adds a new backupCommand option to allow users to specify a
custom command to run on existing files during activation, as an
alternative to the existing backupFileExtension mechanism.

Adds backupCommand option to NixOS and nix-darwin modules.

Exports HOME_MANAGER_BACKUP_COMMAND environment variable when set.

Updates file activation logic to use the custom backup command if
provided, falling back to the existing backup extension logic.

Updates collision checking and user-facing instructions to
mention the new option.

This enables advanced backup workflows, such as moving files to
trash or archiving with custom tools, before managing them with
Home Manager.
This commit is contained in:
Christina O'Donnell 2025-10-30 17:26:13 +00:00 committed by GitHub
parent c6d4cb31d7
commit ce76393bb7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 40 additions and 11 deletions

View file

@ -1132,6 +1132,11 @@ while [[ $# -gt 0 ]]; do
EXTRA_NIX_PATH+=("$1") EXTRA_NIX_PATH+=("$1")
shift shift
;; ;;
-B)
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
export HOME_MANAGER_BACKUP_COMMAND="$1"
shift
;;
-b) -b)
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt" [[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
export HOME_MANAGER_BACKUP_EXT="$1" export HOME_MANAGER_BACKUP_EXT="$1"

View file

@ -146,13 +146,18 @@ in
for sourcePath in "$@" ; do for sourcePath in "$@" ; do
relativePath="''${sourcePath#$newGenFiles/}" relativePath="''${sourcePath#$newGenFiles/}"
targetPath="$HOME/$relativePath" targetPath="$HOME/$relativePath"
if [[ -e "$targetPath" && ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then if [[ -e "$targetPath" && ! -L "$targetPath" ]] ; then
# The target exists, back it up if [[ -n "$HOME_MANAGER_BACKUP_COMMAND" ]] ; then
backup="$targetPath.$HOME_MANAGER_BACKUP_EXT" verboseEcho "Running $HOME_MANAGER_BACKUP_COMMAND $targetPath."
if [[ -e "$backup" && -n "$HOME_MANAGER_BACKUP_OVERWRITE" ]]; then run $HOME_MANAGER_BACKUP_COMMAND "$targetPath" || errorEcho "Running `$HOME_MANAGER_BACKUP_COMMAND` on '$targetPath' failed."
run rm $VERBOSE_ARG "$backup" elif [[ -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then
# The target exists, back it up
backup="$targetPath.$HOME_MANAGER_BACKUP_EXT"
if [[ -e "$backup" && -n "$HOME_MANAGER_BACKUP_OVERWRITE" ]]; then
run rm $VERBOSE_ARG "$backup"
fi
run mv $VERBOSE_ARG "$targetPath" "$backup" || errorEcho "Moving '$targetPath' failed!"
fi fi
run mv $VERBOSE_ARG "$targetPath" "$backup" || errorEcho "Moving '$targetPath' failed!"
fi fi
if [[ -e "$targetPath" && ! -L "$targetPath" ]] && cmp -s "$sourcePath" "$targetPath" ; then if [[ -e "$targetPath" && ! -L "$targetPath" ]] && cmp -s "$sourcePath" "$targetPath" ; then

View file

@ -30,6 +30,9 @@ for sourcePath in "$@" ; do
if cmp -s "$sourcePath" "$targetPath"; then if cmp -s "$sourcePath" "$targetPath"; then
# First compare the files' content. If they're equal, we're fine. # First compare the files' content. If they're equal, we're fine.
warnEcho "Existing file '$targetPath' is in the way of '$sourcePath', will be skipped since they are the same" warnEcho "Existing file '$targetPath' is in the way of '$sourcePath', will be skipped since they are the same"
elif [[ ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_COMMAND" ]] ; then
# Next, try to run the custom backup command. Assume this always succeeds.
verboseEcho "Existing file '$targetPath' exists and differs from '$sourcePath'. `$HOME_MANAGER_BACKUP_COMMAND` will be used to backup the file."
elif [[ ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then elif [[ ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then
# Next, try to move the file to a backup location if configured and possible # Next, try to move the file to a backup location if configured and possible
backup="$targetPath.$HOME_MANAGER_BACKUP_EXT" backup="$targetPath.$HOME_MANAGER_BACKUP_EXT"
@ -49,12 +52,13 @@ done
if [[ ${#collisionErrors[@]} -gt 0 ]] ; then if [[ ${#collisionErrors[@]} -gt 0 ]] ; then
errorEcho "Please do one of the following: errorEcho "Please do one of the following:
- Move or remove the files below and try again.
- In standalone mode, use 'home-manager switch -b backup' to back up"\ - In standalone mode, use 'home-manager switch -b backup' to back up"\
" files automatically. " files automatically.
- When used as a NixOS or nix-darwin module, set"\ - When used as a NixOS or nix-darwin module, set either
" 'home-manager.backupFileExtension'"\ - 'home-manager.backupFileExtension', or
" to, for example, 'backup' and rebuild. - 'home-manager.backupCommand',
to move the file to a new location in the same directory, or run a"\
" custom command.
- Set 'force = true' on the related file options to forcefully overwrite"\ - Set 'force = true' on the related file options to forcefully overwrite"\
" the files below. eg. 'xdg.configFile.\"mimeapps.list\".force = true'" " the files below. eg. 'xdg.configFile.\"mimeapps.list\".force = true'"

View file

@ -28,6 +28,9 @@ in
${lib.optionalString ( ${lib.optionalString (
cfg.backupFileExtension != null cfg.backupFileExtension != null
) "export HOME_MANAGER_BACKUP_EXT=${lib.escapeShellArg cfg.backupFileExtension}"} ) "export HOME_MANAGER_BACKUP_EXT=${lib.escapeShellArg cfg.backupFileExtension}"}
${lib.optionalString (
cfg.backupCommand != null
) "export HOME_MANAGER_BACKUP_COMMAND=${lib.escapeShellArg cfg.backupCommand}"}
${lib.optionalString cfg.overwriteBackup "export HOME_MANAGER_BACKUP_OVERWRITE=1"} ${lib.optionalString cfg.overwriteBackup "export HOME_MANAGER_BACKUP_OVERWRITE=1"}
${lib.optionalString cfg.verbose "export VERBOSE=1"} ${lib.optionalString cfg.verbose "export VERBOSE=1"}
exec ${usercfg.home.activationPackage}/activate --driver-version ${driverVersion} >&2 exec ${usercfg.home.activationPackage}/activate --driver-version ${driverVersion} >&2

View file

@ -81,6 +81,16 @@ in
argument in Home Manager. This disables the Home Manager argument in Home Manager. This disables the Home Manager
options {option}`nixpkgs.*`''; options {option}`nixpkgs.*`'';
backupCommand = mkOption {
type = types.nullOr (types.either types.str types.path);
default = null;
example = lib.literalExpression "''${pkgs.trash-cli}/bin/trash";
description = ''
On activation run this command on each existing file
rather than exiting with an error.
'';
};
backupFileExtension = mkOption { backupFileExtension = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;

View file

@ -12,7 +12,9 @@ let
serviceEnvironment = lib.mkMerge [ serviceEnvironment = lib.mkMerge [
(mkIf cfg.verbose { VERBOSE = "1"; }) (mkIf cfg.verbose { VERBOSE = "1"; })
(mkIf (cfg.backupCommand != null) {
HOME_MANAGER_BACKUP_COMMAND = cfg.backupCommand;
})
(mkIf (cfg.backupFileExtension != null) { (mkIf (cfg.backupFileExtension != null) {
HOME_MANAGER_BACKUP_EXT = cfg.backupFileExtension; HOME_MANAGER_BACKUP_EXT = cfg.backupFileExtension;
}) })