Feature testing flakes / nix-command is more robust over configuration sniffing. Ultimately, the deciding factor should be if flakes work -- not if the config looks like they will / won't work. This alternative test both asserts that the `nix` command is enabled, and that flakes are enabled, without depending on whether or not flakes are emitted as an experimental feature. This is both repairing support for Determinate Nix 3, and prepares for a potential future where Nix itself considers Flakes stable. Closes #6702
1126 lines
34 KiB
Bash
1126 lines
34 KiB
Bash
#!@bash@/bin/bash
|
|
|
|
# Prepare to use tools from Nixpkgs.
|
|
PATH=@DEP_PATH@${PATH:+:}$PATH
|
|
|
|
set -euo pipefail
|
|
|
|
export TEXTDOMAIN=home-manager
|
|
export TEXTDOMAINDIR=@OUT@/share/locale
|
|
|
|
# shellcheck disable=1091
|
|
source @HOME_MANAGER_LIB@
|
|
|
|
function errMissingOptArg() {
|
|
# translators: For example: "home-manager: missing argument for --cores"
|
|
_iError "%s: missing argument for %s" "$0" "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
function setNixProfileCommands() {
|
|
if [[ -e $HOME/.nix-profile/manifest.json \
|
|
|| -e ${XDG_STATE_HOME:-$HOME/.local/state}/nix/profile/manifest.json ]] ; then
|
|
|
|
LIST_OUTPATH_CMD="nix profile list"
|
|
else
|
|
LIST_OUTPATH_CMD="nix-env -q --out-path"
|
|
fi
|
|
}
|
|
|
|
function setVerboseArg() {
|
|
if [[ -v VERBOSE ]]; then
|
|
export VERBOSE_ARG="--verbose"
|
|
else
|
|
export VERBOSE_ARG=""
|
|
fi
|
|
}
|
|
|
|
function setWorkDir() {
|
|
if [[ ! -v WORK_DIR ]]; then
|
|
WORK_DIR="$(mktemp --tmpdir -d home-manager-build.XXXXXXXXXX)"
|
|
# shellcheck disable=2064
|
|
trap "rm -r '$WORK_DIR'" EXIT
|
|
fi
|
|
}
|
|
|
|
# Check to see if flakes are functionally available.
|
|
function hasFlakeSupport() {
|
|
nix eval --expr 'builtins.getFlake' > /dev/null 2>&1
|
|
}
|
|
|
|
# Escape string for use in Nix files.
|
|
function escapeForNix() {
|
|
printf %s "$1" | sed 's/["$\\]/\\\0/g'
|
|
}
|
|
|
|
# Attempts to set the HOME_MANAGER_CONFIG global variable.
|
|
#
|
|
# If no configuration file can be found then this function will print
|
|
# an error message and exit with an error code.
|
|
function setConfigFile() {
|
|
if [[ -v HOME_MANAGER_CONFIG ]] ; then
|
|
if [[ -e "$HOME_MANAGER_CONFIG" ]] ; then
|
|
HOME_MANAGER_CONFIG="$(realpath "$HOME_MANAGER_CONFIG")"
|
|
else
|
|
_i 'No configuration file found at %s' \
|
|
"$HOME_MANAGER_CONFIG" >&2
|
|
exit 1
|
|
fi
|
|
elif [[ ! -v HOME_MANAGER_CONFIG ]]; then
|
|
local configHome="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
local hmConfigHome="$configHome/home-manager"
|
|
local nixpkgsConfigHome="$configHome/nixpkgs"
|
|
local defaultConfFile="$hmConfigHome/home.nix"
|
|
local configFile
|
|
|
|
if [[ -e "$defaultConfFile" ]]; then
|
|
configFile="$defaultConfFile"
|
|
elif [[ -e "$nixpkgsConfigHome/home.nix" ]]; then
|
|
configFile="$nixpkgsConfigHome/home.nix"
|
|
# translators: The first '%s' specifier will be replaced by either
|
|
# 'home.nix' or 'flake.nix'.
|
|
_iWarn $'Keeping your Home Manager %s in %s is deprecated,\nplease move it to %s' \
|
|
'home.nix' "$nixpkgsConfigHome" "$hmConfigHome" >&2
|
|
elif [[ -e "$HOME/.nixpkgs/home.nix" ]]; then
|
|
configFile="$HOME/.nixpkgs/home.nix"
|
|
_iWarn $'Keeping your Home Manager %s in %s is deprecated,\nplease move it to %s' \
|
|
'home.nix' "$HOME/.nixpkgs" "$hmConfigHome" >&2
|
|
fi
|
|
|
|
if [[ -v configFile ]]; then
|
|
HOME_MANAGER_CONFIG="$(realpath "$configFile")"
|
|
else
|
|
_i 'No configuration file found. Please create one at %s' \
|
|
"$defaultConfFile" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function setHomeManagerNixPath() {
|
|
local path="@HOME_MANAGER_PATH@"
|
|
|
|
if [[ -n "$path" ]] ; then
|
|
if [[ -e "$path" || "$path" =~ ^https?:// ]] ; then
|
|
EXTRA_NIX_PATH+=("home-manager=$path")
|
|
return
|
|
else
|
|
_iWarn 'Home Manager not found at %s.' "$path"
|
|
fi
|
|
fi
|
|
|
|
for p in "${XDG_CONFIG_HOME:-$HOME/.config}/nixpkgs/home-manager" \
|
|
"$HOME/.nixpkgs/home-manager" ; do
|
|
if [[ -e "$p" ]] ; then
|
|
# translators: This message will be seen by very few users that likely are familiar with English. So feel free to leave this untranslated.
|
|
_iWarn $'The fallback Home Manager path %s has been deprecated and a file/directory was found there.' \
|
|
"$p"
|
|
# translators: This message will be seen by very few users that likely are familiar with English. So feel free to leave this untranslated.
|
|
_i $'To remove this warning, do one of the following.
|
|
|
|
1. Explicitly tell Home Manager to use the path, for example by adding
|
|
|
|
{ programs.home-manager.path = "%s"; }
|
|
|
|
to your configuration.
|
|
|
|
If you import Home Manager directly, you can use the `path` parameter
|
|
|
|
pkgs.callPackage /path/to/home-manager-package { path = "%s"; }
|
|
|
|
when calling the Home Manager package.
|
|
|
|
2. Remove the deprecated path.
|
|
|
|
$ rm -r "%s"' "$p" "$p" "$p"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Sets some useful Home Manager related paths as global read-only variables.
|
|
function setHomeManagerPathVariables() {
|
|
# If called twice then just exit early.
|
|
if [[ -v HM_DATA_HOME ]]; then
|
|
return
|
|
fi
|
|
|
|
_iVerbose "Sanity checking Nix"
|
|
nix-build --quiet --expr '{}' --no-out-link > /dev/null 2>&1 || true
|
|
nix-env -q > /dev/null 2>&1 || true
|
|
|
|
declare -r globalNixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
|
|
declare -r globalProfilesDir="$globalNixStateDir/profiles/per-user/$USER"
|
|
declare -r globalGcrootsDir="$globalNixStateDir/gcroots/per-user/$USER"
|
|
|
|
declare -r stateHome="${XDG_STATE_HOME:-$HOME/.local/state}"
|
|
declare -r userNixStateDir="$stateHome/nix"
|
|
|
|
declare -gr HM_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/home-manager"
|
|
declare -gr HM_STATE_DIR="$stateHome/home-manager"
|
|
declare -gr HM_GCROOT_LEGACY_PATH="$globalGcrootsDir/current-home"
|
|
|
|
if [[ -d $userNixStateDir/profiles ]]; then
|
|
declare -gr HM_PROFILE_DIR="$userNixStateDir/profiles"
|
|
elif [[ -d $globalProfilesDir ]]; then
|
|
declare -gr HM_PROFILE_DIR="$globalProfilesDir"
|
|
else
|
|
_iError 'Could not find suitable profile directory, tried %s and %s' \
|
|
"$userNixStateDir/profiles" "$globalProfilesDir" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
function setFlakeAttribute() {
|
|
if [[ -z $FLAKE_ARG && ! -v HOME_MANAGER_CONFIG ]]; then
|
|
local configHome="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
local hmConfigHome="$configHome/home-manager"
|
|
local nixpkgsConfigHome="$configHome/nixpkgs"
|
|
|
|
local configFlake
|
|
|
|
if [[ -e "$hmConfigHome/flake.nix" ]]; then
|
|
configFlake="$hmConfigHome/flake.nix"
|
|
elif [[ -e "$nixpkgsConfigHome/flake.nix" ]]; then
|
|
configFlake="$nixpkgsConfigHome/flake.nix"
|
|
_iWarn $'Keeping your Home Manager %s in %s is deprecated,\nplease move it to %s' \
|
|
'flake.nix' "$nixpkgsConfigHome" "$hmConfigHome" >&2
|
|
fi
|
|
|
|
if [[ -v configFlake ]]; then
|
|
FLAKE_ARG="$(dirname "$(readlink -f "$configFlake")")"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$FLAKE_ARG" ]]; then
|
|
local flake="${FLAKE_ARG%#*}"
|
|
case $FLAKE_ARG in
|
|
*#*)
|
|
local name="${FLAKE_ARG#*#}"
|
|
;;
|
|
*)
|
|
local name="$USER"
|
|
# Check FQDN, long, and short hostnames; long first to preserve
|
|
# pre-existing behaviour in case both happen to be defined.
|
|
for n in "$USER@$(hostname -f)" "$USER@$(hostname)" "$USER@$(hostname -s)"; do
|
|
if [[ "$(nix eval "$flake#homeConfigurations" --apply "x: x ? \"$(escapeForNix "$n")\"")" == "true" ]]; then
|
|
name="$n"
|
|
if [[ -v VERBOSE ]]; then
|
|
echo "Using flake homeConfiguration for $name"
|
|
fi
|
|
fi
|
|
done
|
|
;;
|
|
esac
|
|
export FLAKE_CONFIG_URI="$flake#homeConfigurations.\"$(printf %s "$name" | jq -sRr @uri)\""
|
|
fi
|
|
}
|
|
|
|
function doInspectOption() {
|
|
setFlakeAttribute
|
|
if [[ -v FLAKE_CONFIG_URI ]]; then
|
|
# translators: Here "flake" is a noun that refers to the Nix Flakes feature.
|
|
_iError "Can't inspect options of a flake configuration"
|
|
exit 1
|
|
fi
|
|
setConfigFile
|
|
|
|
local extraArgs=("$@")
|
|
|
|
for p in "${EXTRA_NIX_PATH[@]}"; do
|
|
extraArgs=("${extraArgs[@]}" "-I" "$p")
|
|
done
|
|
|
|
if [[ -v VERBOSE ]]; then
|
|
extraArgs=("${extraArgs[@]}" "--show-trace")
|
|
fi
|
|
|
|
local HOME_MANAGER_CONFIG_NIX HOME_MANAGER_CONFIG_ATTRIBUTE_NIX
|
|
HOME_MANAGER_CONFIG_NIX=${HOME_MANAGER_CONFIG//'\'/'\\'}
|
|
HOME_MANAGER_CONFIG_NIX=${HOME_MANAGER_CONFIG_NIX//'"'/'\"'}
|
|
HOME_MANAGER_CONFIG_NIX=${HOME_MANAGER_CONFIG_NIX//$'\n'/$'\\n'}
|
|
HOME_MANAGER_CONFIG_ATTRIBUTE_NIX=${HOME_MANAGER_CONFIG_ATTRIBUTE//'\'/'\\'}
|
|
HOME_MANAGER_CONFIG_ATTRIBUTE_NIX=${HOME_MANAGER_CONFIG_ATTRIBUTE_NIX//'"'/'\"'}
|
|
HOME_MANAGER_CONFIG_ATTRIBUTE_NIX=${HOME_MANAGER_CONFIG_ATTRIBUTE_NIX//$'\n'/$'\\n'}
|
|
local modulesExpr
|
|
modulesExpr="let confPath = \"${HOME_MANAGER_CONFIG_NIX}\"; "
|
|
modulesExpr+="confAttr = \"${HOME_MANAGER_CONFIG_ATTRIBUTE_NIX}\"; in "
|
|
modulesExpr+="(import <home-manager/modules> {"
|
|
modulesExpr+=" configuration = if confAttr == \"\" then confPath else (import confPath).\${confAttr};"
|
|
modulesExpr+=" pkgs = import <nixpkgs> {}; check = true; })"
|
|
|
|
nixos-option \
|
|
--options_expr "$modulesExpr.options" \
|
|
--config_expr "$modulesExpr.config" \
|
|
"${extraArgs[@]}" \
|
|
"${PASSTHROUGH_OPTS[@]}"
|
|
}
|
|
|
|
function doInit() {
|
|
# The directory where we should place the initial configuration.
|
|
local confDir
|
|
|
|
# Whether we should immediate activate the configuration.
|
|
local switch
|
|
|
|
# Whether we should create a flake file.
|
|
local withFlake
|
|
|
|
if hasFlakeSupport; then
|
|
withFlake=1
|
|
fi
|
|
|
|
local homeManagerUrl="github:nix-community/home-manager"
|
|
local nixpkgsUrl="github:nixos/nixpkgs/nixos-unstable"
|
|
|
|
while (( $# > 0 )); do
|
|
local opt="$1"
|
|
shift
|
|
|
|
case $opt in
|
|
--no-flake)
|
|
unset withFlake
|
|
;;
|
|
--switch)
|
|
switch=1
|
|
;;
|
|
--home-manager-url)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
homeManagerUrl="$1"
|
|
shift
|
|
;;
|
|
--nixpkgs-url)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
nixpkgsUrl="$1"
|
|
shift
|
|
;;
|
|
-*)
|
|
_iError "%s: unknown option '%s'" "$0" "$opt" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
if [[ -v confDir ]]; then
|
|
_i "Run '%s --help' for usage help" "$0" >&2
|
|
exit 1
|
|
else
|
|
confDir="$opt"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ ! -v confDir ]]; then
|
|
confDir="${XDG_CONFIG_HOME:-$HOME/.config}/home-manager"
|
|
fi
|
|
|
|
if [[ ! -e $confDir ]]; then
|
|
mkdir -p "$confDir"
|
|
fi
|
|
|
|
if [[ ! -d $confDir ]]; then
|
|
_iError "%s: unknown option '%s'" "$0" "$opt" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local confFile="$confDir/home.nix"
|
|
local flakeFile="$confDir/flake.nix"
|
|
|
|
if [[ -e $confFile ]]; then
|
|
_i 'The file %s already exists, leaving it unchanged...' "$confFile"
|
|
else
|
|
_i 'Creating %s...' "$confFile"
|
|
local nl=$'\n'
|
|
local xdgVars=""
|
|
if [[ -v XDG_CACHE_HOME && $XDG_CACHE_HOME != "$HOME/.cache" ]]; then
|
|
xdgVars="$xdgVars xdg.cacheHome = \"$XDG_CACHE_HOME\";$nl"
|
|
fi
|
|
if [[ -v XDG_CONFIG_HOME && $XDG_CONFIG_HOME != "$HOME/.config" ]]; then
|
|
xdgVars="$xdgVars xdg.configHome = \"$XDG_CONFIG_HOME\";$nl"
|
|
fi
|
|
if [[ -v XDG_DATA_HOME && $XDG_DATA_HOME != "$HOME/.local/share" ]]; then
|
|
xdgVars="$xdgVars xdg.dataHome = \"$XDG_DATA_HOME\";$nl"
|
|
fi
|
|
if [[ -v XDG_STATE_HOME && $XDG_STATE_HOME != "$HOME/.local/state" ]]; then
|
|
xdgVars="$xdgVars xdg.stateHome = \"$XDG_STATE_HOME\";$nl"
|
|
fi
|
|
|
|
mkdir -p "$confDir"
|
|
cat > "$confFile" <<EOF
|
|
{ config, pkgs, ... }:
|
|
|
|
{
|
|
# Home Manager needs a bit of information about you and the paths it should
|
|
# manage.
|
|
home.username = "$(escapeForNix "$USER")";
|
|
home.homeDirectory = "$(escapeForNix "$HOME")";
|
|
$xdgVars
|
|
# This value determines the Home Manager release that your configuration is
|
|
# compatible with. This helps avoid breakage when a new Home Manager release
|
|
# introduces backwards incompatible changes.
|
|
#
|
|
# You should not change this value, even if you update Home Manager. If you do
|
|
# want to update the value, then make sure to first check the Home Manager
|
|
# release notes.
|
|
home.stateVersion = "24.11"; # Please read the comment before changing.
|
|
|
|
# The home.packages option allows you to install Nix packages into your
|
|
# environment.
|
|
home.packages = [
|
|
# # Adds the 'hello' command to your environment. It prints a friendly
|
|
# # "Hello, world!" when run.
|
|
# pkgs.hello
|
|
|
|
# # It is sometimes useful to fine-tune packages, for example, by applying
|
|
# # overrides. You can do that directly here, just don't forget the
|
|
# # parentheses. Maybe you want to install Nerd Fonts with a limited number of
|
|
# # fonts?
|
|
# (pkgs.nerdfonts.override { fonts = [ "FantasqueSansMono" ]; })
|
|
|
|
# # You can also create simple shell scripts directly inside your
|
|
# # configuration. For example, this adds a command 'my-hello' to your
|
|
# # environment:
|
|
# (pkgs.writeShellScriptBin "my-hello" ''
|
|
# echo "Hello, \${config.home.username}!"
|
|
# '')
|
|
];
|
|
|
|
# Home Manager is pretty good at managing dotfiles. The primary way to manage
|
|
# plain files is through 'home.file'.
|
|
home.file = {
|
|
# # Building this configuration will create a copy of 'dotfiles/screenrc' in
|
|
# # the Nix store. Activating the configuration will then make '~/.screenrc' a
|
|
# # symlink to the Nix store copy.
|
|
# ".screenrc".source = dotfiles/screenrc;
|
|
|
|
# # You can also set the file content immediately.
|
|
# ".gradle/gradle.properties".text = ''
|
|
# org.gradle.console=verbose
|
|
# org.gradle.daemon.idletimeout=3600000
|
|
# '';
|
|
};
|
|
|
|
# Home Manager can also manage your environment variables through
|
|
# 'home.sessionVariables'. These will be explicitly sourced when using a
|
|
# shell provided by Home Manager. If you don't want to manage your shell
|
|
# through Home Manager then you have to manually source 'hm-session-vars.sh'
|
|
# located at either
|
|
#
|
|
# ~/.nix-profile/etc/profile.d/hm-session-vars.sh
|
|
#
|
|
# or
|
|
#
|
|
# ~/.local/state/nix/profiles/profile/etc/profile.d/hm-session-vars.sh
|
|
#
|
|
# or
|
|
#
|
|
# /etc/profiles/per-user/$USER/etc/profile.d/hm-session-vars.sh
|
|
#
|
|
home.sessionVariables = {
|
|
# EDITOR = "emacs";
|
|
};
|
|
|
|
# Let Home Manager install and manage itself.
|
|
programs.home-manager.enable = true;
|
|
}
|
|
EOF
|
|
fi
|
|
|
|
if [[ ! -v withFlake ]]; then
|
|
HOME_MANAGER_CONFIG="$confFile"
|
|
else
|
|
FLAKE_ARG="$confDir"
|
|
|
|
if [[ -e $flakeFile ]]; then
|
|
_i 'The file %s already exists, leaving it unchanged...' "$flakeFile"
|
|
else
|
|
_i 'Creating %s...' "$flakeFile"
|
|
|
|
local nixSystem
|
|
nixSystem=$(nix eval --expr builtins.currentSystem --raw --impure)
|
|
|
|
mkdir -p "$confDir"
|
|
cat > "$flakeFile" <<EOF
|
|
{
|
|
description = "Home Manager configuration of $(escapeForNix "$USER")";
|
|
|
|
inputs = {
|
|
# Specify the source of Home Manager and Nixpkgs.
|
|
nixpkgs.url = "$nixpkgsUrl";
|
|
home-manager = {
|
|
url = "$homeManagerUrl";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
};
|
|
|
|
outputs = { nixpkgs, home-manager, ... }:
|
|
let
|
|
system = "$nixSystem";
|
|
pkgs = nixpkgs.legacyPackages.\${system};
|
|
in {
|
|
homeConfigurations."$(escapeForNix "$USER")" = home-manager.lib.homeManagerConfiguration {
|
|
inherit pkgs;
|
|
|
|
# Specify your home configuration modules here, for example,
|
|
# the path to your home.nix.
|
|
modules = [ ./home.nix ];
|
|
|
|
# Optionally use extraSpecialArgs
|
|
# to pass through arguments to home.nix
|
|
};
|
|
};
|
|
}
|
|
EOF
|
|
fi
|
|
fi
|
|
|
|
if [[ -v switch ]]; then
|
|
echo
|
|
_i "Creating initial Home Manager generation..."
|
|
echo
|
|
|
|
if doSwitch; then
|
|
# translators: The "%s" specifier will be replaced by a file path.
|
|
_i $'All done! The home-manager tool should now be installed and you can edit\n\n %s\n\nto configure Home Manager. Run \'man home-configuration.nix\' to\nsee all available options.' \
|
|
"$confFile"
|
|
exit 0
|
|
else
|
|
# translators: The "%s" specifier will be replaced by a URL.
|
|
_i $'Uh oh, the installation failed! Please create an issue at\n\n %s\n\nif the error seems to be the fault of Home Manager.' \
|
|
"https://github.com/nix-community/home-manager/issues"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function doInstantiate() {
|
|
setFlakeAttribute
|
|
if [[ -v FLAKE_CONFIG_URI ]]; then
|
|
# translators: Here "flake" is a noun that refers to the Nix Flakes feature.
|
|
_i "Can't instantiate a flake configuration" >&2
|
|
exit 1
|
|
fi
|
|
setConfigFile
|
|
|
|
local extraArgs=()
|
|
|
|
for p in "${EXTRA_NIX_PATH[@]}"; do
|
|
extraArgs=("${extraArgs[@]}" "-I" "$p")
|
|
done
|
|
|
|
if [[ -v VERBOSE ]]; then
|
|
extraArgs=("${extraArgs[@]}" "--show-trace")
|
|
fi
|
|
|
|
nix-instantiate \
|
|
"<home-manager/home-manager/home-manager.nix>" \
|
|
"${extraArgs[@]}" \
|
|
"${PASSTHROUGH_OPTS[@]}" \
|
|
--argstr confPath "$HOME_MANAGER_CONFIG" \
|
|
--argstr confAttr "$HOME_MANAGER_CONFIG_ATTRIBUTE"
|
|
}
|
|
|
|
function doBuildAttr() {
|
|
setConfigFile
|
|
|
|
local extraArgs=("$@")
|
|
|
|
for p in "${EXTRA_NIX_PATH[@]}"; do
|
|
extraArgs=("${extraArgs[@]}" "-I" "$p")
|
|
done
|
|
|
|
if [[ -v VERBOSE ]]; then
|
|
extraArgs=("${extraArgs[@]}" "--show-trace")
|
|
fi
|
|
|
|
nix-build \
|
|
"<home-manager/home-manager/home-manager.nix>" \
|
|
"${extraArgs[@]}" \
|
|
"${PASSTHROUGH_OPTS[@]}" \
|
|
--argstr confPath "$HOME_MANAGER_CONFIG" \
|
|
--argstr confAttr "$HOME_MANAGER_CONFIG_ATTRIBUTE"
|
|
}
|
|
|
|
function doBuildFlake() {
|
|
local extraArgs=("$@")
|
|
|
|
if [[ -v VERBOSE ]]; then
|
|
extraArgs=("${extraArgs[@]}" "--verbose")
|
|
fi
|
|
|
|
nix build \
|
|
"${extraArgs[@]}" \
|
|
"${PASSTHROUGH_OPTS[@]}"
|
|
}
|
|
|
|
# Presents news to the user as specified by the `news.display` option.
|
|
function presentNews() {
|
|
local newsNixFile="$WORK_DIR/news.nix"
|
|
buildNews "$newsNixFile"
|
|
|
|
local newsDisplay
|
|
newsDisplay="$(nix-instantiate --eval --expr "(import ${newsNixFile}).meta.display" | xargs)"
|
|
|
|
local newsNumUnread
|
|
newsNumUnread="$(nix-instantiate --eval --expr "(import ${newsNixFile}).meta.numUnread" | xargs)"
|
|
|
|
# shellcheck disable=2154
|
|
if [[ $newsNumUnread -eq 0 ]]; then
|
|
return
|
|
elif [[ "$newsDisplay" == "silent" ]]; then
|
|
return
|
|
elif [[ "$newsDisplay" == "notify" ]]; then
|
|
local cmd msg
|
|
cmd="$(basename "$0")"
|
|
msg="$(_ip \
|
|
$'There is %d unread and relevant news item.\nRead it by running the command "%s news".' \
|
|
$'There are %d unread and relevant news items.\nRead them by running the command "%s news".' \
|
|
"$newsNumUnread" "$newsNumUnread" "$cmd")"
|
|
|
|
# Not actually an error but here stdout is reserved for
|
|
# nix-build output.
|
|
echo $'\n'"$msg"$'\n' >&2
|
|
|
|
if [[ -v DISPLAY ]] && type -P notify-send > /dev/null; then
|
|
notify-send "Home Manager" "$msg" > /dev/null 2>&1 || true
|
|
fi
|
|
elif [[ "$newsDisplay" == "show" ]]; then
|
|
doShowNews --unread
|
|
else
|
|
_i 'Unknown "news.display" setting "%s".' "$newsDisplay" >&2
|
|
fi
|
|
}
|
|
|
|
function doEdit() {
|
|
if [[ ! -v VISUAL || -z $VISUAL ]]; then
|
|
if [[ ! -v EDITOR || -z $EDITOR ]]; then
|
|
# shellcheck disable=2016
|
|
_i 'Please set the $EDITOR or $VISUAL environment variable' >&2
|
|
return 1
|
|
fi
|
|
else
|
|
EDITOR=$VISUAL
|
|
fi
|
|
|
|
setConfigFile
|
|
|
|
# Don't quote $EDITOR in order to support values including options, e.g.,
|
|
# "code --wait".
|
|
#
|
|
# shellcheck disable=2086
|
|
exec $EDITOR "$HOME_MANAGER_CONFIG"
|
|
}
|
|
|
|
function doBuild() {
|
|
if [[ ! -w . ]]; then
|
|
_i 'Cannot run build in read-only directory' >&2
|
|
return 1
|
|
fi
|
|
|
|
setWorkDir
|
|
|
|
setFlakeAttribute
|
|
if [[ -v FLAKE_CONFIG_URI ]]; then
|
|
doBuildFlake \
|
|
"$FLAKE_CONFIG_URI.activationPackage" \
|
|
${DRY_RUN+--dry-run} \
|
|
${NO_OUT_LINK+--no-link} \
|
|
${PRINT_BUILD_LOGS+--print-build-logs} \
|
|
|| return
|
|
else
|
|
doBuildAttr \
|
|
${NO_OUT_LINK+--no-out-link} \
|
|
--attr activationPackage \
|
|
|| return
|
|
fi
|
|
|
|
presentNews
|
|
}
|
|
|
|
function doSwitch() {
|
|
setWorkDir
|
|
|
|
local generation
|
|
|
|
# Build the generation and run the activate script. Note, we
|
|
# specify an output link so that it is treated as a GC root. This
|
|
# prevents an unfortunately timed GC from removing the generation
|
|
# before activation completes.
|
|
generation="$WORK_DIR/generation"
|
|
|
|
setFlakeAttribute
|
|
if [[ -v FLAKE_CONFIG_URI ]]; then
|
|
doBuildFlake \
|
|
"$FLAKE_CONFIG_URI.activationPackage" \
|
|
--out-link "$generation" \
|
|
${PRINT_BUILD_LOGS+--print-build-logs} \
|
|
&& "$generation/activate" || return
|
|
else
|
|
doBuildAttr \
|
|
--out-link "$generation" \
|
|
--attr activationPackage \
|
|
&& "$generation/activate" || return
|
|
fi
|
|
|
|
presentNews
|
|
}
|
|
|
|
function doListGens() {
|
|
setHomeManagerPathVariables
|
|
|
|
# Whether to colorize the generations output.
|
|
local color="never"
|
|
if [[ ! -v NO_COLOR && -t 1 ]]; then
|
|
color="always"
|
|
fi
|
|
|
|
pushd "$HM_PROFILE_DIR" > /dev/null
|
|
# shellcheck disable=2012
|
|
ls --color=$color -gG --time-style=long-iso --sort time home-manager-*-link \
|
|
| cut -d' ' -f 4- \
|
|
| sed -E 's/home-manager-([[:digit:]]*)-link/: id \1/'
|
|
popd > /dev/null
|
|
}
|
|
|
|
# Removes linked generations. Takes as arguments identifiers of
|
|
# generations to remove.
|
|
function doRmGenerations() {
|
|
setHomeManagerPathVariables
|
|
setVerboseArg
|
|
|
|
pushd "$HM_PROFILE_DIR" > /dev/null
|
|
|
|
for generationId in "$@"; do
|
|
local linkName="home-manager-$generationId-link"
|
|
|
|
if [[ ! -e $linkName ]]; then
|
|
_i 'No generation with ID %s' "$generationId" >&2
|
|
elif [[ $linkName == $(readlink home-manager) ]]; then
|
|
_i 'Cannot remove the current generation %s' "$generationId" >&2
|
|
else
|
|
_i 'Removing generation %s' "$generationId"
|
|
run rm $VERBOSE_ARG $linkName
|
|
fi
|
|
done
|
|
|
|
popd > /dev/null
|
|
}
|
|
|
|
function doExpireGenerations() {
|
|
setHomeManagerPathVariables
|
|
|
|
local generations
|
|
generations="$( \
|
|
find "$HM_PROFILE_DIR" -name 'home-manager-*-link' -not -newermt "$1" \
|
|
| sed 's/^.*-\([0-9]*\)-link$/\1/' \
|
|
)"
|
|
|
|
if [[ -n $generations ]]; then
|
|
# shellcheck disable=2086
|
|
doRmGenerations $generations
|
|
elif [[ -v VERBOSE ]]; then
|
|
_i "No generations to expire"
|
|
fi
|
|
}
|
|
|
|
function doListPackages() {
|
|
setNixProfileCommands
|
|
local outPath
|
|
outPath="$($LIST_OUTPATH_CMD | grep -o '/.*home-manager-path$')"
|
|
if [[ -n "$outPath" ]] ; then
|
|
nix-store -q --references "$outPath" | sed 's/[^-]*-//' | sort --ignore-case
|
|
else
|
|
_i 'No home-manager packages seem to be installed.' >&2
|
|
fi
|
|
}
|
|
|
|
function newsReadIdsFile() {
|
|
local dataDir="${XDG_DATA_HOME:-$HOME/.local/share}/home-manager"
|
|
local path="$dataDir/news-read-ids"
|
|
|
|
# If the path doesn't exist then we should create it, otherwise
|
|
# Nix will error out when we attempt to use builtins.readFile.
|
|
if [[ ! -f "$path" ]]; then
|
|
mkdir -p "$dataDir"
|
|
touch "$path"
|
|
fi
|
|
|
|
# Remove duplicate slashes in case $HOME or $XDG_DATA_HOME have a trailing
|
|
# slash. Double slashes causes Nix to error out with
|
|
#
|
|
# error: syntax error, unexpected PATH_END, expecting DOLLAR_CURLY".
|
|
echo "$path" | tr -s /
|
|
}
|
|
|
|
# Builds the Home Manager news data file.
|
|
#
|
|
# Note, we suppress build output to remove unnecessary verbosity. We
|
|
# put the output in the work directory to avoid the risk of an
|
|
# unfortunately timed GC removing it.
|
|
function buildNews() {
|
|
local newsNixFile="$1"
|
|
local newsJsonFile="$WORK_DIR/news.json"
|
|
|
|
if [[ -v FLAKE_CONFIG_URI ]]; then
|
|
# TODO: Use check=false to make it more likely that the build succeeds.
|
|
doBuildFlake \
|
|
"$FLAKE_CONFIG_URI.config.news.json.output" \
|
|
--quiet \
|
|
--out-link "$newsJsonFile" \
|
|
|| return
|
|
else
|
|
doBuildAttr \
|
|
--out-link "$newsJsonFile" \
|
|
--arg check false \
|
|
--attr config.news.json.output \
|
|
> /dev/null \
|
|
|| return
|
|
fi
|
|
|
|
local extraArgs=()
|
|
|
|
for p in "${EXTRA_NIX_PATH[@]}"; do
|
|
extraArgs=("${extraArgs[@]}" "-I" "$p")
|
|
done
|
|
|
|
local readIdsFile
|
|
readIdsFile="$(newsReadIdsFile)"
|
|
|
|
nix-instantiate \
|
|
--no-build-output --strict \
|
|
--eval '<home-manager/home-manager/build-news.nix>' \
|
|
--arg newsJsonFile "\"$(escapeForNix "$newsJsonFile")\"" \
|
|
--arg newsReadIdsFile "\"$(escapeForNix "$readIdsFile")\"" \
|
|
"${extraArgs[@]}" \
|
|
> "$newsNixFile"
|
|
}
|
|
|
|
function doShowNews() {
|
|
setWorkDir
|
|
setFlakeAttribute
|
|
|
|
local newsNixFile="$WORK_DIR/news.nix"
|
|
buildNews "$newsNixFile"
|
|
|
|
local readIdsFile
|
|
readIdsFile="$(newsReadIdsFile)"
|
|
|
|
local newsAttr
|
|
|
|
case $1 in
|
|
--all)
|
|
newsAttr="all"
|
|
;;
|
|
--unread)
|
|
newsAttr="unread"
|
|
;;
|
|
*)
|
|
_i 'Unknown argument %s' "$1"
|
|
return 1
|
|
esac
|
|
|
|
nix-instantiate --quiet --eval --json --expr "(import ${newsNixFile}).news.$newsAttr" \
|
|
| jq -r . \
|
|
| ${PAGER:-less}
|
|
|
|
local allIds
|
|
allIds="$(nix-instantiate --quiet --eval --expr "(import ${newsNixFile}).meta.ids")"
|
|
allIds="${allIds:1:-1}" # Trim surrounding quotes.
|
|
|
|
local readIdsFileNew="$WORK_DIR/news-read-ids.new"
|
|
{
|
|
cat "$readIdsFile"
|
|
echo -e "$allIds"
|
|
} | sort | uniq > "$readIdsFileNew"
|
|
|
|
mv -f "$readIdsFileNew" "$readIdsFile"
|
|
}
|
|
|
|
function doUninstall() {
|
|
setHomeManagerPathVariables
|
|
setNixProfileCommands
|
|
|
|
_i 'This will remove Home Manager from your system.'
|
|
|
|
if [[ -v DRY_RUN ]]; then
|
|
_i 'This is a dry run, nothing will actually be uninstalled.'
|
|
fi
|
|
|
|
local confirmation
|
|
read -r -n 1 -p "$(_i 'Really uninstall Home Manager?') [y/n] " confirmation
|
|
echo
|
|
|
|
# shellcheck disable=2086
|
|
case $confirmation in
|
|
y|Y)
|
|
_i "Switching to empty Home Manager configuration..."
|
|
HOME_MANAGER_CONFIG="$(mktemp --tmpdir home-manager.XXXXXXXXXX)"
|
|
cat > "$HOME_MANAGER_CONFIG" <<EOF
|
|
{
|
|
uninstall = true;
|
|
home.username = "$(escapeForNix "$USER")";
|
|
home.homeDirectory = "$(escapeForNix "$HOME")";
|
|
home.stateVersion = "24.11";
|
|
}
|
|
EOF
|
|
# shellcheck disable=2064
|
|
trap "rm '$HOME_MANAGER_CONFIG'" EXIT
|
|
doSwitch --switch
|
|
;;
|
|
*)
|
|
_i "Yay!"
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
_i "Home Manager is uninstalled but your home.nix is left untouched."
|
|
}
|
|
|
|
function doHelp() {
|
|
echo "Usage: $0 [OPTION] COMMAND"
|
|
echo
|
|
echo "Options"
|
|
echo
|
|
echo " -f FILE The home configuration file."
|
|
echo " Default is '~/.config/nixpkgs/home.nix'."
|
|
echo " -A ATTRIBUTE Optional attribute that selects a configuration"
|
|
echo " expression in the configuration file."
|
|
echo " -I PATH Add a path to the Nix expression search path."
|
|
echo " --flake flake-uri Use Home Manager configuration at flake-uri"
|
|
echo " Default is '~/.config/home-manager'."
|
|
echo " -b EXT Move existing files to new path rather than fail."
|
|
echo " -v Verbose output"
|
|
echo " -n Do a dry run, only prints what actions would be taken"
|
|
echo " -h Print this help"
|
|
echo " --version Print the Home Manager version"
|
|
echo
|
|
echo "Options passed on to nix-build(1)"
|
|
echo
|
|
echo " --arg(str) NAME VALUE Override inputs passed to home-manager.nix"
|
|
echo " --cores NUM"
|
|
echo " --debug"
|
|
echo " --impure"
|
|
echo " --keep-failed"
|
|
echo " --keep-going"
|
|
echo " -j, --max-jobs NUM"
|
|
echo " --option NAME VALUE"
|
|
echo " -L, --print-build-logs"
|
|
echo " --log-format FORMAT"
|
|
echo " --show-trace"
|
|
echo " --(no-)substitute"
|
|
echo " --no-out-link Do not create a symlink to the output path"
|
|
echo " --no-write-lock-file"
|
|
echo " --builders VALUE"
|
|
echo " --refresh Consider all previously downloaded files out-of-date"
|
|
echo
|
|
echo "Commands"
|
|
echo
|
|
echo " help Print this help"
|
|
echo
|
|
echo " edit Open the home configuration in \$VISUAL or \$EDITOR"
|
|
echo
|
|
echo " option OPTION.NAME"
|
|
echo " Inspect configuration option named OPTION.NAME."
|
|
echo
|
|
echo " build Build configuration into result directory"
|
|
echo
|
|
echo " init [--switch] [DIR]"
|
|
echo " Initializes a configuration in the given directory. If the directory"
|
|
echo " does not exist, then it will be created. The default directory is"
|
|
echo " '~/.config/home-manager'."
|
|
echo
|
|
echo " --switch Immediately activate the generated configuration."
|
|
echo
|
|
echo " instantiate Instantiate the configuration and print the resulting derivation"
|
|
echo
|
|
echo " switch Build and activate configuration"
|
|
echo
|
|
echo " generations List all home environment generations"
|
|
echo
|
|
echo " remove-generations ID..."
|
|
echo " Remove indicated generations. Use 'generations' command to"
|
|
echo " find suitable generation numbers."
|
|
echo
|
|
echo " expire-generations TIMESTAMP"
|
|
echo " Remove generations older than TIMESTAMP where TIMESTAMP is"
|
|
echo " interpreted as in the -d argument of the date tool. For"
|
|
echo " example \"-30 days\" or \"2018-01-01\"."
|
|
echo
|
|
echo " packages List all packages installed in home-manager-path"
|
|
echo
|
|
echo " news Show news entries in a pager"
|
|
echo
|
|
echo " uninstall Remove Home Manager"
|
|
}
|
|
|
|
EXTRA_NIX_PATH=()
|
|
HOME_MANAGER_CONFIG_ATTRIBUTE=""
|
|
PASSTHROUGH_OPTS=()
|
|
COMMAND=""
|
|
COMMAND_ARGS=()
|
|
FLAKE_ARG=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
opt="$1"
|
|
shift
|
|
case $opt in
|
|
build|init|instantiate|option|edit|expire-generations|generations|help|news|packages|remove-generations|switch|uninstall)
|
|
COMMAND="$opt"
|
|
;;
|
|
-A)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
HOME_MANAGER_CONFIG_ATTRIBUTE="$1"
|
|
shift
|
|
;;
|
|
-I)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
EXTRA_NIX_PATH+=("$1")
|
|
shift
|
|
;;
|
|
-b)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
export HOME_MANAGER_BACKUP_EXT="$1"
|
|
shift
|
|
;;
|
|
-f|--file)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
HOME_MANAGER_CONFIG="$1"
|
|
shift
|
|
;;
|
|
--flake)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
FLAKE_ARG="$1"
|
|
shift
|
|
;;
|
|
--recreate-lock-file|--no-update-lock-file|--no-write-lock-file|--no-registries|--commit-lock-file|--refresh)
|
|
PASSTHROUGH_OPTS+=("$opt")
|
|
;;
|
|
--update-input)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
PASSTHROUGH_OPTS+=("$opt" "$1")
|
|
shift
|
|
;;
|
|
--override-input)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
[[ -v 2 && $2 != -* ]] || errMissingOptArg "$opt $1"
|
|
PASSTHROUGH_OPTS+=("$opt" "$1" "$2")
|
|
shift 2
|
|
;;
|
|
--experimental-features)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
PASSTHROUGH_OPTS+=("$opt" "$1")
|
|
shift
|
|
;;
|
|
--extra-experimental-features)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
PASSTHROUGH_OPTS+=("$opt" "$1")
|
|
shift
|
|
;;
|
|
--no-out-link)
|
|
NO_OUT_LINK=1
|
|
;;
|
|
-L|--print-build-logs)
|
|
PRINT_BUILD_LOGS=1
|
|
;;
|
|
-h|--help)
|
|
doHelp
|
|
exit 0
|
|
;;
|
|
-n|--dry-run)
|
|
export DRY_RUN=1
|
|
;;
|
|
--option|--arg|--argstr)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
[[ -v 2 ]] || errMissingOptArg "$opt $1"
|
|
PASSTHROUGH_OPTS+=("$opt" "$1" "$2")
|
|
shift 2
|
|
;;
|
|
-j|--max-jobs|--cores|--builders|--log-format)
|
|
[[ -v 1 && $1 != -* ]] || errMissingOptArg "$opt"
|
|
PASSTHROUGH_OPTS+=("$opt" "$1")
|
|
shift
|
|
;;
|
|
--debug|--keep-failed|--keep-going|--show-trace\
|
|
|--substitute|--no-substitute|--impure)
|
|
PASSTHROUGH_OPTS+=("$opt")
|
|
;;
|
|
-v|--verbose)
|
|
export VERBOSE=1
|
|
;;
|
|
--version)
|
|
echo 25.05-pre
|
|
exit 0
|
|
;;
|
|
*)
|
|
case $COMMAND in
|
|
init|expire-generations|remove-generations|option)
|
|
COMMAND_ARGS+=("$opt")
|
|
;;
|
|
*)
|
|
_iError "%s: unknown option '%s'" "$0" "$opt" >&2
|
|
_i "Run '%s --help' for usage help" "$0" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
done
|
|
|
|
setHomeManagerNixPath
|
|
|
|
if [[ -z $COMMAND ]]; then
|
|
doHelp >&2
|
|
exit 1
|
|
fi
|
|
|
|
case $COMMAND in
|
|
edit)
|
|
doEdit
|
|
;;
|
|
build)
|
|
doBuild
|
|
;;
|
|
init)
|
|
doInit "${COMMAND_ARGS[@]}"
|
|
;;
|
|
instantiate)
|
|
doInstantiate
|
|
;;
|
|
switch)
|
|
doSwitch
|
|
;;
|
|
generations)
|
|
doListGens
|
|
;;
|
|
remove-generations)
|
|
doRmGenerations "${COMMAND_ARGS[@]}"
|
|
;;
|
|
expire-generations)
|
|
if [[ ${#COMMAND_ARGS[@]} != 1 ]]; then
|
|
_i 'expire-generations expects one argument, got %d.' "${#COMMAND_ARGS[@]}" >&2
|
|
exit 1
|
|
else
|
|
doExpireGenerations "${COMMAND_ARGS[@]}"
|
|
fi
|
|
;;
|
|
option)
|
|
doInspectOption "${COMMAND_ARGS[@]}"
|
|
;;
|
|
packages)
|
|
doListPackages
|
|
;;
|
|
news)
|
|
doShowNews --all
|
|
;;
|
|
uninstall)
|
|
doUninstall
|
|
;;
|
|
help)
|
|
doHelp
|
|
;;
|
|
*)
|
|
_iError 'Unknown command: %s' "$COMMAND" >&2
|
|
doHelp >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# vim: ft=bash
|