aboutsummaryrefslogtreecommitdiff
path: root/scripts/install-multi-user.sh
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/install-multi-user.sh')
-rw-r--r--scripts/install-multi-user.sh295
1 files changed, 236 insertions, 59 deletions
diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh
index 66825f9de..513127a62 100644
--- a/scripts/install-multi-user.sh
+++ b/scripts/install-multi-user.sh
@@ -33,7 +33,7 @@ NIX_BUILD_USER_NAME_TEMPLATE="nixbld%d"
readonly NIX_ROOT="/nix"
readonly NIX_EXTRA_CONF=${NIX_EXTRA_CONF:-}
-readonly PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshenv" "/etc/bash.bashrc" "/etc/zsh/zshenv")
+readonly PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshrc" "/etc/bash.bashrc" "/etc/zsh/zshrc")
readonly PROFILE_BACKUP_SUFFIX=".backup-before-nix"
readonly PROFILE_NIX_FILE="$NIX_ROOT/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"
@@ -43,7 +43,7 @@ readonly NIX_INSTALLED_CACERT="@cacert@"
#readonly NIX_INSTALLED_CACERT="/nix/store/7dxhzymvy330i28ii676fl1pqwcahv2f-nss-cacert-3.49.2"
readonly EXTRACTED_NIX_PATH="$(dirname "$0")"
-readonly ROOT_HOME=$(echo ~root)
+readonly ROOT_HOME=~root
if [ -t 0 ]; then
readonly IS_HEADLESS='no'
@@ -59,14 +59,19 @@ headless() {
fi
}
-contactme() {
- echo "We'd love to help if you need it."
+contact_us() {
+ echo "You can open an issue at https://github.com/nixos/nix/issues"
echo ""
- echo "If you can, open an issue at https://github.com/nixos/nix/issues"
+ echo "Or feel free to contact the team:"
+ echo " - Matrix: #nix:nixos.org"
+ echo " - IRC: in #nixos on irc.libera.chat"
+ echo " - twitter: @nixos_org"
+ echo " - forum: https://discourse.nixos.org"
+}
+get_help() {
+ echo "We'd love to help if you need it."
echo ""
- echo "Or feel free to contact the team,"
- echo " - on IRC #nixos on irc.freenode.net"
- echo " - on twitter @nixos_org"
+ contact_us
}
uninstall_directions() {
@@ -102,7 +107,6 @@ $step. Delete the files Nix added to your system:
and that is it.
EOF
-
}
nix_user_for_core() {
@@ -170,7 +174,7 @@ failure() {
header "oh no!"
_textout "$RED" "$@"
echo ""
- _textout "$RED" "$(contactme)"
+ _textout "$RED" "$(get_help)"
trap finish_cleanup EXIT
exit 1
}
@@ -201,6 +205,95 @@ ui_confirm() {
return 1
}
+printf -v _UNCHANGED_GRP_FMT "%b" $'\033[2m%='"$ESC" # "dim"
+# bold+invert+red and bold+invert+green just for the +/- below
+# red/green foreground for rest of the line
+printf -v _OLD_LINE_FMT "%b" $'\033[1;7;31m-'"$ESC ${RED}%L${ESC}"
+printf -v _NEW_LINE_FMT "%b" $'\033[1;7;32m+'"$ESC ${GREEN}%L${ESC}"
+
+_diff() {
+ # simple colorized diff comatible w/ pre `--color` versions
+ diff --unchanged-group-format="$_UNCHANGED_GRP_FMT" --old-line-format="$_OLD_LINE_FMT" --new-line-format="$_NEW_LINE_FMT" --unchanged-line-format=" %L" "$@"
+}
+
+confirm_rm() {
+ local path="$1"
+ if ui_confirm "Can I remove $path?"; then
+ _sudo "to remove $path" rm "$path"
+ fi
+}
+
+confirm_edit() {
+ local path="$1"
+ local edit_path="$2"
+ cat <<EOF
+
+Nix isn't the only thing in $path,
+but I think I know how to edit it out.
+Here's the diff:
+EOF
+
+ # could technically test the diff, but caller should do it
+ _diff "$path" "$edit_path"
+ if ui_confirm "Does the change above look right?"; then
+ _sudo "remove nix from $path" cp "$edit_path" "$path"
+ fi
+}
+
+_SERIOUS_BUSINESS="${RED}%s:${ESC} "
+password_confirm() {
+ local do_something_consequential="$1"
+ if ui_confirm "Can I $do_something_consequential?"; then
+ # shellcheck disable=SC2059
+ sudo -kv --prompt="$(printf "${_SERIOUS_BUSINESS}" "Enter your password to $do_something_consequential")"
+ else
+ return 1
+ fi
+}
+
+# Support accumulating reminders over the course of a run and showing
+# them at the end. An example where this helps: the installer changes
+# something, but it won't work without a reboot. If you tell the user
+# when you do it, they may miss it in the stream. The value of the
+# setting isn't enough to decide whether to message because you only
+# need to message if you *changed* it.
+
+# reminders stored in array delimited by empty entry; if ! headless,
+# user is asked to confirm after each delimiter.
+_reminders=()
+((_remind_num=1))
+
+remind() {
+ # (( arithmetic expression ))
+ if (( _remind_num > 1 )); then
+ header "Reminders"
+ for line in "${_reminders[@]}"; do
+ echo "$line"
+ if ! headless && [ "${#line}" = 0 ]; then
+ if read -r -p "Press enter/return to acknowledge."; then
+ printf $'\033[A\33[2K\r'
+ fi
+ fi
+ done
+ fi
+}
+
+reminder() {
+ printf -v label "${BLUE}[ %d ]${ESC}" "$_remind_num"
+ _reminders+=("$label")
+ if [[ "$*" = "" ]]; then
+ while read -r line; do
+ _reminders+=("$line")
+ done
+ else
+ # this expands each arg to an array entry (and each entry will
+ # ultimately be a separate line in the output)
+ _reminders+=("$@")
+ fi
+ _reminders+=("")
+ ((_remind_num++))
+}
+
__sudo() {
local expl="$1"
local cmd="$2"
@@ -221,18 +314,18 @@ _sudo() {
local expl="$1"
shift
if ! headless; then
- __sudo "$expl" "$*"
+ __sudo "$expl" "$*" >&2
fi
sudo "$@"
}
-readonly SCRATCH=$(mktemp -d -t tmp.XXXXXXXXXX)
-function finish_cleanup {
+readonly SCRATCH=$(mktemp -d "${TMPDIR:-/tmp/}tmp.XXXXXXXXXX")
+finish_cleanup() {
rm -rf "$SCRATCH"
}
-function finish_fail {
+finish_fail() {
finish_cleanup
failure <<EOF
@@ -244,45 +337,46 @@ EOF
}
trap finish_fail EXIT
-channel_update_failed=0
-function finish_success {
- finish_cleanup
-
+finish_success() {
ok "Alright! We're done!"
- if [ "x$channel_update_failed" = x1 ]; then
- echo ""
- echo "But fetching the nixpkgs channel failed. (Are you offline?)"
- echo "To try again later, run \"sudo -i nix-channel --update nixpkgs\"."
- fi
cat <<EOF
-
-Before Nix will work in your existing shells, you'll need to close
-them and open them again. Other than that, you should be ready to go.
-
Try it! Open a new terminal, and type:
$(poly_extra_try_me_commands)
$ nix-shell -p nix-info --run "nix-info -m"
-$(poly_extra_setup_instructions)
-Thank you for using this installer. If you have any feedback, don't
-hesitate:
-$(contactme)
-EOF
+Thank you for using this installer. If you have any feedback or need
+help, don't hesitate:
+$(contact_us)
+EOF
+ remind
+ finish_cleanup
}
+finish_uninstall_success() {
+ ok "Alright! Nix should be removed!"
-validate_starting_assumptions() {
- poly_validate_assumptions
+ cat <<EOF
+If you spot anything this uninstaller missed or have feedback,
+don't hesitate:
- if [ $EUID -eq 0 ]; then
- failure <<EOF
-Please do not run this script with root privileges. We will call sudo
-when we need to.
+$(contact_us)
EOF
- fi
+ remind
+ finish_cleanup
+}
+remove_nix_artifacts() {
+ failure "Not implemented yet"
+}
+
+cure_artifacts() {
+ poly_cure_artifacts
+ # remove_nix_artifacts (LATER)
+}
+
+validate_starting_assumptions() {
if type nix-env 2> /dev/null >&2; then
warning <<EOF
Nix already appears to be installed. This installer may run into issues.
@@ -444,18 +538,46 @@ create_build_users() {
create_directories() {
# FIXME: remove all of this because it duplicates LocalStore::LocalStore().
-
+ task "Setting up the basic directory structure"
+ if [ -d "$NIX_ROOT" ]; then
+ # if /nix already exists, take ownership
+ #
+ # Caution: notes below are macOS-y
+ # This is a bit of a goldilocks zone for taking ownership
+ # if there are already files on the volume; the volume is
+ # now mounted, but we haven't added a bunch of new files
+
+ # this is probably a bit slow; I've been seeing 3.3-4s even
+ # when promptly installed over a fresh single-user install.
+ # In case anyone's aware of a shortcut.
+ # `|| true`: .Trashes errors w/o full disk perm
+
+ # rumor per #4488 that macOS 11.2 may not have
+ # sbin on path, and that's where chown is, but
+ # since this bit is cross-platform:
+ # - first try with `command -vp` to try and find
+ # chown in the usual places
+ # - fall back on `command -v` which would find
+ # any chown on path
+ # if we don't find one, the command is already
+ # hiding behind || true, and the general state
+ # should be one the user can repair once they
+ # figure out where chown is...
+ local get_chr_own="$(command -vp chown)"
+ if [[ -z "$get_chr_own" ]]; then
+ get_chr_own="$(command -v chown)"
+ fi
+ _sudo "to take root ownership of existing Nix store files" \
+ "$get_chr_own" -R "root:$NIX_BUILD_GROUP_NAME" "$NIX_ROOT" || true
+ fi
_sudo "to make the basic directory structure of Nix (part 1)" \
- mkdir -pv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool} /nix/var/nix/{gcroots,profiles}/per-user
+ install -dv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool} /nix/var/nix/{gcroots,profiles}/per-user
_sudo "to make the basic directory structure of Nix (part 2)" \
- mkdir -pv -m 1775 /nix/store
-
- _sudo "to make the basic directory structure of Nix (part 3)" \
- chgrp "$NIX_BUILD_GROUP_NAME" /nix/store
+ install -dv -g "$NIX_BUILD_GROUP_NAME" -m 1775 /nix/store
_sudo "to place the default nix daemon configuration (part 1)" \
- mkdir -pv -m 0555 /etc/nix
+ install -dv -m 0555 /etc/nix
}
place_channel_configuration() {
@@ -475,7 +597,7 @@ This installation tool will set up your computer with the Nix package
manager. This will happen in a few stages:
1. Make sure your computer doesn't already have Nix. If it does, I
- will show you instructions on how to clean up your old one.
+ will show you instructions on how to clean up your old install.
2. Show you what we are going to install and where. Then we will ask
if you are ready to continue.
@@ -574,11 +696,15 @@ EOF
}
install_from_extracted_nix() {
+ task "Installing Nix"
(
cd "$EXTRACTED_NIX_PATH"
_sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \
- rsync -rlpt --chmod=-w ./store/* "$NIX_ROOT/store/"
+ cp -RLp ./store/* "$NIX_ROOT/store/"
+
+ _sudo "to make the new store non-writable at $NIX_ROOT/store" \
+ chmod -R ugo-w "$NIX_ROOT/store/"
if [ -d "$NIX_INSTALLED_NIX" ]; then
echo " Alright! We have our first nix at $NIX_INSTALLED_NIX"
@@ -589,9 +715,8 @@ $NIX_INSTALLED_NIX.
EOF
fi
- cat ./.reginfo \
- | _sudo "to load data for the first time in to the Nix Database" \
- "$NIX_INSTALLED_NIX/bin/nix-store" --load-db
+ _sudo "to load data for the first time in to the Nix Database" \
+ "$NIX_INSTALLED_NIX/bin/nix-store" --load-db < ./.reginfo
echo " Just finished getting the nix database ready."
)
@@ -610,6 +735,7 @@ EOF
}
configure_shell_profile() {
+ task "Setting up shell profiles: ${PROFILE_TARGETS[*]}"
for profile_target in "${PROFILE_TARGETS[@]}"; do
if [ -e "$profile_target" ]; then
_sudo "to back up your current $profile_target to $profile_target$PROFILE_BACKUP_SUFFIX" \
@@ -629,14 +755,27 @@ configure_shell_profile() {
tee -a "$profile_target"
fi
done
+ # TODO: should we suggest '. $PROFILE_NIX_FILE'? It would get them on
+ # their way less disruptively, but a counter-argument is that they won't
+ # immediately notice if something didn't get set up right?
+ reminder "Nix won't work in active shell sessions until you restart them."
+}
+
+cert_in_store() {
+ # in a subshell
+ # - change into the cert-file dir
+ # - get the phyiscal pwd
+ # and test if this path is in the Nix store
+ [[ "$(cd -- "$(dirname "$NIX_SSL_CERT_FILE")" && exec pwd -P)" == "$NIX_ROOT/store/"* ]]
}
setup_default_profile() {
- _sudo "to installing a bootstrapping Nix in to the default Profile" \
+ task "Setting up the default profile"
+ _sudo "to install a bootstrapping Nix in to the default profile" \
HOME="$ROOT_HOME" "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_NIX"
- if [ -z "${NIX_SSL_CERT_FILE:-}" ] || ! [ -f "${NIX_SSL_CERT_FILE:-}" ]; then
- _sudo "to installing a bootstrapping SSL certificate just for Nix in to the default Profile" \
+ if [ -z "${NIX_SSL_CERT_FILE:-}" ] || ! [ -f "${NIX_SSL_CERT_FILE:-}" ] || cert_in_store; then
+ _sudo "to install a bootstrapping SSL certificate just for Nix in to the default profile" \
HOME="$ROOT_HOME" "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_CACERT"
export NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt
fi
@@ -645,9 +784,13 @@ setup_default_profile() {
# Have to explicitly pass NIX_SSL_CERT_FILE as part of the sudo call,
# otherwise it will be lost in environments where sudo doesn't pass
# all the environment variables by default.
- _sudo "to update the default channel in the default profile" \
- HOME="$ROOT_HOME" NIX_SSL_CERT_FILE="$NIX_SSL_CERT_FILE" "$NIX_INSTALLED_NIX/bin/nix-channel" --update nixpkgs \
- || channel_update_failed=1
+ if ! _sudo "to update the default channel in the default profile" \
+ HOME="$ROOT_HOME" NIX_SSL_CERT_FILE="$NIX_SSL_CERT_FILE" "$NIX_INSTALLED_NIX/bin/nix-channel" --update nixpkgs; then
+ reminder <<EOF
+I had trouble fetching the nixpkgs channel (are you offline?)
+To try again later, run: sudo -i nix-channel --update nixpkgs
+EOF
+ fi
fi
}
@@ -662,6 +805,17 @@ EOF
}
main() {
+ # TODO: I've moved this out of validate_starting_assumptions so we
+ # can fail faster in this case. Sourcing install-darwin... now runs
+ # `touch /` to detect Read-only root, but it could update times on
+ # pre-Catalina macOS if run as root user.
+ if [ $EUID -eq 0 ]; then
+ failure <<EOF
+Please do not run this script with root privileges. We will call sudo
+when we need to.
+EOF
+ fi
+
if [ "$(uname -s)" = "Darwin" ]; then
# shellcheck source=./install-darwin-multi-user.sh
. "$EXTRACTED_NIX_PATH/install-darwin-multi-user.sh"
@@ -675,17 +829,24 @@ main() {
welcome_to_nix
chat_about_sudo
+ cure_artifacts
+ # TODO: there's a tension between cure and validate. I moved the
+ # the sudo/root check out of validate to the head of this func.
+ # Cure is *intended* to subsume the validate-and-abort approach,
+ # so it may eventually obsolete it.
validate_starting_assumptions
setup_report
if ! ui_confirm "Ready to continue?"; then
ok "Alright, no changes have been made :)"
- contactme
+ get_help
trap finish_cleanup EXIT
exit 1
fi
+ poly_prepare_to_install
+
create_build_group
create_build_users
create_directories
@@ -695,6 +856,7 @@ main() {
configure_shell_profile
set +eu
+ # shellcheck disable=SC1091
. /etc/profile
set -eu
@@ -706,5 +868,20 @@ main() {
trap finish_success EXIT
}
+# set an empty initial arg for bare invocations in case we need to
+# disambiguate someone directly invoking this later.
+if [ "${#@}" = 0 ]; then
+ set ""
+fi
-main
+# ACTION for override
+case "${1-}" in
+ # uninstall)
+ # shift
+ # uninstall "$@";;
+ # install == same as the no-arg condition for now (but, explicit)
+ ""|install)
+ main;;
+ *) # holding space for future options (like uninstall + install?)
+ failure "install-multi-user: invalid argument";;
+esac