aboutsummaryrefslogtreecommitdiff
path: root/scripts/install-multi-user.sh
diff options
context:
space:
mode:
authorBen Burdette <bburdette@gmail.com>2021-11-25 08:53:59 -0700
committerBen Burdette <bburdette@gmail.com>2021-11-25 08:53:59 -0700
commit64c4ba8f66c7569478fd5f19ebb72c9590cc2b45 (patch)
tree65d874c35432e81c3d244caadd7c467eccd0b87d /scripts/install-multi-user.sh
parent69e26c5c4ba106bd16f60bfaac88ccf888b4383f (diff)
parentca82967ee3276e2aa8b02ea7e6d19cfd4fa75f4c (diff)
Merge branch 'master' into debug-merge
Diffstat (limited to 'scripts/install-multi-user.sh')
-rw-r--r--scripts/install-multi-user.sh338
1 files changed, 262 insertions, 76 deletions
diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh
index 66825f9de..0dba36f51 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.
@@ -293,19 +387,28 @@ EOF
fi
for profile_target in "${PROFILE_TARGETS[@]}"; do
+ # TODO: I think it would be good to accumulate a list of all
+ # of the copies so that people don't hit this 2 or 3x in
+ # a row for different files.
if [ -e "$profile_target$PROFILE_BACKUP_SUFFIX" ]; then
+ # this backup process first released in Nix 2.1
failure <<EOF
-When this script runs, it backs up the current $profile_target to
-$profile_target$PROFILE_BACKUP_SUFFIX. This backup file already exists, though.
+I back up shell profile/rc scripts before I add Nix to them.
+I need to back up $profile_target to $profile_target$PROFILE_BACKUP_SUFFIX,
+but the latter already exists.
-Please follow these instructions to clean up the old backup file:
+Here's how to clean up the old backup file:
-1. Copy $profile_target and $profile_target$PROFILE_BACKUP_SUFFIX to another place, just
-in case.
+1. Back up (copy) $profile_target and $profile_target$PROFILE_BACKUP_SUFFIX
+ to another location, just in case.
-2. Take care to make sure that $profile_target$PROFILE_BACKUP_SUFFIX doesn't look like
-it has anything nix-related in it. If it does, something is probably
-quite wrong. Please open an issue or get in touch immediately.
+2. Ensure $profile_target$PROFILE_BACKUP_SUFFIX does not have anything
+ Nix-related in it. If it does, something is probably quite
+ wrong. Please open an issue or get in touch immediately.
+
+3. Once you confirm $profile_target is backed up and
+ $profile_target$PROFILE_BACKUP_SUFFIX doesn't mention Nix, run:
+ mv $profile_target$PROFILE_BACKUP_SUFFIX $profile_target
EOF
fi
done
@@ -444,18 +547,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,9 +606,9 @@ 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
+2. Show you what I am going to install and where. Then I will ask
if you are ready to continue.
3. Create the system users and groups that the Nix daemon uses to run
@@ -492,14 +623,14 @@ manager. This will happen in a few stages:
EOF
- if ui_confirm "Would you like to see a more detailed list of what we will do?"; then
+ if ui_confirm "Would you like to see a more detailed list of what I will do?"; then
cat <<EOF
-We will:
+I will:
- make sure your computer doesn't already have Nix files
(if it does, I will tell you how to clean them up.)
- - create local users (see the list above for the users we'll make)
+ - create local users (see the list above for the users I'll make)
- create a local group ($NIX_BUILD_GROUP_NAME)
- install Nix in to $NIX_ROOT
- create a configuration file in /etc/nix
@@ -534,7 +665,7 @@ run in a headless fashion, like this:
$ curl -L https://nixos.org/nix/install | sh
-or maybe in a CI pipeline. Because of that, we're going to skip the
+or maybe in a CI pipeline. Because of that, I'm going to skip the
verbose output in the interest of brevity.
If you would like to
@@ -548,7 +679,7 @@ EOF
fi
cat <<EOF
-This script is going to call sudo a lot. Every time we do, it'll
+This script is going to call sudo a lot. Every time I do, it'll
output exactly what it'll do, and why.
Just like this:
@@ -560,25 +691,29 @@ EOF
cat <<EOF
This might look scary, but everything can be undone by running just a
-few commands. We used to ask you to confirm each time sudo ran, but it
+few commands. I used to ask you to confirm each time sudo ran, but it
was too many times. Instead, I'll just ask you this one time:
EOF
- if ui_confirm "Can we use sudo?"; then
+ if ui_confirm "Can I use sudo?"; then
ok "Yay! Thanks! Let's get going!"
else
failure <<EOF
-That is okay, but we can't install.
+That is okay, but I can't install.
EOF
fi
}
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 +724,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 +744,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 +764,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 +793,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 +814,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. I will call sudo
+when I 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 +838,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 +865,7 @@ main() {
configure_shell_profile
set +eu
+ # shellcheck disable=SC1091
. /etc/profile
set -eu
@@ -706,5 +877,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