diff options
author | Eelco Dolstra <edolstra@gmail.com> | 2021-09-15 20:33:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-15 20:33:44 +0200 |
commit | 79152e307e7eef667c3de9c21571d017654a7c32 (patch) | |
tree | 67fd413bcf0b42c5ada7eddc41a04f7bd99df3a8 /scripts/install-multi-user.sh | |
parent | 7349f257da8278af9aae35544b15c9a204e2a57b (diff) | |
parent | 3b82c1a5fef521ebadea5df12384390c8c24100c (diff) |
Merge pull request #5212 from mkenigs/auto-uid-allocation
Merge master into #3600
Diffstat (limited to 'scripts/install-multi-user.sh')
-rw-r--r-- | scripts/install-multi-user.sh | 334 |
1 files changed, 255 insertions, 79 deletions
diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index 00c9d540b..513127a62 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -25,21 +25,25 @@ readonly RED='\033[31m' readonly NIX_USER_COUNT=${NIX_USER_COUNT:-32} readonly NIX_BUILD_GROUP_ID="30000" readonly NIX_BUILD_GROUP_NAME="nixbld" -readonly NIX_FIRST_BUILD_UID="30001" +# darwin installer needs to override these +NIX_FIRST_BUILD_UID="30001" +NIX_BUILD_USER_NAME_TEMPLATE="nixbld%d" # Please don't change this. We don't support it, because the # default shell profile that comes with Nix doesn't support it. readonly NIX_ROOT="/nix" readonly NIX_EXTRA_CONF=${NIX_EXTRA_CONF:-} -readonly PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/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" readonly NIX_INSTALLED_NIX="@nix@" readonly NIX_INSTALLED_CACERT="@cacert@" +#readonly NIX_INSTALLED_NIX="/nix/store/j8dbv5w6jl34caywh2ygdy88knx1mdf7-nix-2.3.6" +#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' @@ -55,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() { @@ -98,11 +107,10 @@ $step. Delete the files Nix added to your system: and that is it. EOF - } nix_user_for_core() { - printf "nixbld%d" "$1" + printf "$NIX_BUILD_USER_NAME_TEMPLATE" "$1" } nix_uid_for_core() { @@ -166,7 +174,7 @@ failure() { header "oh no!" _textout "$RED" "$@" echo "" - _textout "$RED" "$(contactme)" + _textout "$RED" "$(get_help)" trap finish_cleanup EXIT exit 1 } @@ -197,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" @@ -217,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 @@ -240,43 +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. + cat <<EOF Try it! Open a new terminal, and type: - +$(poly_extra_try_me_commands) $ nix-shell -p nix-info --run "nix-info -m" -Thank you for using this installer. If you have any feedback, don't -hesitate: +Thank you for using this installer. If you have any feedback or need +help, don't hesitate: -$(contactme) +$(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. @@ -438,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() { @@ -469,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. @@ -568,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" @@ -583,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." ) @@ -604,37 +735,47 @@ EOF } configure_shell_profile() { - # If there is an /etc/profile.d directory, we want to ensure there - # is a nix.sh within it, so we can use the following loop to add - # the source lines to it. Note that I'm _not_ adding the source - # lines here, because we want to be using the regular machinery. - # - # If we go around that machinery, it becomes more complicated and - # adds complications to the uninstall instruction generator and - # old instruction sniffer as well. - if [ -d /etc/profile.d ]; then - _sudo "create a stub /etc/profile.d/nix.sh which will be updated" \ - touch /etc/profile.d/nix.sh - fi - + 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" \ cp "$profile_target" "$profile_target$PROFILE_BACKUP_SUFFIX" + else + # try to create the file if its directory exists + target_dir="$(dirname "$profile_target")" + if [ -d "$target_dir" ]; then + _sudo "to create a stub $profile_target which will be updated" \ + touch "$profile_target" + fi + fi + if [ -e "$profile_target" ]; then shell_source_lines \ | _sudo "extend your $profile_target with nix-daemon settings" \ 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 @@ -643,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 } @@ -660,16 +805,23 @@ 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" elif [ "$(uname -s)" = "Linux" ]; then - if [ -e /run/systemd/system ]; then - # shellcheck source=./install-systemd-multi-user.sh - . "$EXTRACTED_NIX_PATH/install-systemd-multi-user.sh" - else - failure "Sorry, the multi-user installation requires systemd on Linux (detected using /run/systemd/system)" - fi + # shellcheck source=./install-systemd-multi-user.sh + . "$EXTRACTED_NIX_PATH/install-systemd-multi-user.sh" # most of this works on non-systemd distros also else failure "Sorry, I don't know what to do on $(uname)" fi @@ -677,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 @@ -697,15 +856,32 @@ main() { configure_shell_profile set +eu + # shellcheck disable=SC1091 . /etc/profile set -eu setup_default_profile place_nix_configuration + poly_configure_nix_daemon_service 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 |