aboutsummaryrefslogtreecommitdiff
path: root/scripts
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
parent69e26c5c4ba106bd16f60bfaac88ccf888b4383f (diff)
parentca82967ee3276e2aa8b02ea7e6d19cfd4fa75f4c (diff)
Merge branch 'master' into debug-merge
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/create-darwin-volume.sh926
-rw-r--r--scripts/install-darwin-multi-user.sh123
-rw-r--r--scripts/install-multi-user.sh338
-rw-r--r--scripts/install-nix-from-closure.sh116
-rwxr-xr-xscripts/install-systemd-multi-user.sh27
-rwxr-xr-xscripts/install.in33
-rw-r--r--scripts/local.mk4
-rwxr-xr-xscripts/nix-http-export.cgi.in51
-rw-r--r--scripts/nix-profile-daemon.sh.in6
-rwxr-xr-xscripts/nix-reduce-build.in171
-rwxr-xr-xscripts/prepare-installer-for-github-actions2
11 files changed, 1250 insertions, 547 deletions
diff --git a/scripts/create-darwin-volume.sh b/scripts/create-darwin-volume.sh
index 32fa577a8..334b75045 100755
--- a/scripts/create-darwin-volume.sh
+++ b/scripts/create-darwin-volume.sh
@@ -1,33 +1,262 @@
-#!/bin/sh
-set -e
+#!/usr/bin/env bash
+set -eu
+set -o pipefail
-root_disk() {
- diskutil info -plist /
-}
+# I'm a little agnostic on the choices, but supporting a wide
+# slate of uses for now, including:
+# - import-only: `. create-darwin-volume.sh no-main[ ...]`
+# - legacy: `./create-darwin-volume.sh` or `. create-darwin-volume.sh`
+# (both will run main())
+# - external alt-routine: `./create-darwin-volume.sh no-main func[ ...]`
+if [ "${1-}" = "no-main" ]; then
+ shift
+ readonly _CREATE_VOLUME_NO_MAIN=1
+else
+ readonly _CREATE_VOLUME_NO_MAIN=0
+ # declare some things we expect to inherit from install-multi-user
+ # I don't love this (because it's a bit of a kludge).
+ #
+ # CAUTION: (Dec 19 2020)
+ # This is a stopgap. It doesn't cover the full slate of
+ # identifiers we inherit--just those necessary to:
+ # - avoid breaking direct invocations of this script (here/now)
+ # - avoid hard-to-reverse structural changes before the call to rm
+ # single-user support is verified
+ #
+ # In the near-mid term, I (personally) think we should:
+ # - decide to deprecate the direct call and add a notice
+ # - fold all of this into install-darwin-multi-user.sh
+ # - intentionally remove the old direct-invocation form (kill the
+ # routine, replace this script w/ deprecation notice and a note
+ # on the remove-after date)
+ #
+ readonly NIX_ROOT="${NIX_ROOT:-/nix}"
+
+ _sudo() {
+ shift # throw away the 'explanation'
+ /usr/bin/sudo "$@"
+ }
+ failure() {
+ if [ "$*" = "" ]; then
+ cat
+ else
+ echo "$@"
+ fi
+ exit 1
+ }
+ task() {
+ echo "$@"
+ }
+fi
-# i.e., "disk1"
+# usually "disk1"
root_disk_identifier() {
- diskutil info -plist / | xmllint --xpath "/plist/dict/key[text()='ParentWholeDisk']/following-sibling::string[1]/text()" -
+ # For performance (~10ms vs 280ms) I'm parsing 'diskX' from stat output
+ # (~diskXsY)--but I'm retaining the more-semantic approach since
+ # it documents intent better.
+ # /usr/sbin/diskutil info -plist / | xmllint --xpath "/plist/dict/key[text()='ParentWholeDisk']/following-sibling::string[1]/text()" -
+ #
+ local special_device
+ special_device="$(/usr/bin/stat -f "%Sd" /)"
+ echo "${special_device%s[0-9]*}"
+}
+
+# make it easy to play w/ 'Case-sensitive APFS'
+readonly NIX_VOLUME_FS="${NIX_VOLUME_FS:-APFS}"
+readonly NIX_VOLUME_LABEL="${NIX_VOLUME_LABEL:-Nix Store}"
+# Strongly assuming we'll make a volume on the device / is on
+# But you can override NIX_VOLUME_USE_DISK to create it on some other device
+readonly NIX_VOLUME_USE_DISK="${NIX_VOLUME_USE_DISK:-$(root_disk_identifier)}"
+NIX_VOLUME_USE_SPECIAL="${NIX_VOLUME_USE_SPECIAL:-}"
+NIX_VOLUME_USE_UUID="${NIX_VOLUME_USE_UUID:-}"
+readonly NIX_VOLUME_MOUNTD_DEST="${NIX_VOLUME_MOUNTD_DEST:-/Library/LaunchDaemons/org.nixos.darwin-store.plist}"
+
+if /usr/bin/fdesetup isactive >/dev/null; then
+ test_filevault_in_use() { return 0; }
+ # no readonly; we may modify if user refuses from cure_volume
+ NIX_VOLUME_DO_ENCRYPT="${NIX_VOLUME_DO_ENCRYPT:-1}"
+else
+ test_filevault_in_use() { return 1; }
+ NIX_VOLUME_DO_ENCRYPT="${NIX_VOLUME_DO_ENCRYPT:-0}"
+fi
+
+should_encrypt_volume() {
+ test_filevault_in_use && (( NIX_VOLUME_DO_ENCRYPT == 1 ))
+}
+
+substep() {
+ printf " %s\n" "" "- $1" "" "${@:2}"
+}
+
+
+volumes_labeled() {
+ local label="$1"
+ xsltproc --novalid --stringparam label "$label" - <(/usr/sbin/ioreg -ra -c "AppleAPFSVolume") <<'EOF'
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text"/>
+ <xsl:template match="/">
+ <xsl:apply-templates select="/plist/array/dict/key[text()='IORegistryEntryName']/following-sibling::*[1][text()=$label]/.."/>
+ </xsl:template>
+ <xsl:template match="dict">
+ <xsl:apply-templates match="string" select="key[text()='BSD Name']/following-sibling::*[1]"/>
+ <xsl:text>=</xsl:text>
+ <xsl:apply-templates match="string" select="key[text()='UUID']/following-sibling::*[1]"/>
+ <xsl:text>&#xA;</xsl:text>
+ </xsl:template>
+</xsl:stylesheet>
+EOF
+ # I cut label out of the extracted values, but here it is for reference:
+ # <xsl:apply-templates match="string" select="key[text()='IORegistryEntryName']/following-sibling::*[1]"/>
+ # <xsl:text>=</xsl:text>
+}
+
+right_disk() {
+ local volume_special="$1" # (i.e., disk1s7)
+ [[ "$volume_special" == "$NIX_VOLUME_USE_DISK"s* ]]
+}
+
+right_volume() {
+ local volume_special="$1" # (i.e., disk1s7)
+ # if set, it must match; otherwise ensure it's on the right disk
+ if [ -z "$NIX_VOLUME_USE_SPECIAL" ]; then
+ if right_disk "$volume_special"; then
+ NIX_VOLUME_USE_SPECIAL="$volume_special" # latch on
+ return 0
+ else
+ return 1
+ fi
+ else
+ [ "$volume_special" = "$NIX_VOLUME_USE_SPECIAL" ]
+ fi
+}
+
+right_uuid() {
+ local volume_uuid="$1"
+ # if set, it must match; otherwise allow
+ if [ -z "$NIX_VOLUME_USE_UUID" ]; then
+ NIX_VOLUME_USE_UUID="$volume_uuid" # latch on
+ return 0
+ else
+ [ "$volume_uuid" = "$NIX_VOLUME_USE_UUID" ]
+ fi
+}
+
+cure_volumes() {
+ local found volume special uuid
+ # loop just in case they have more than one volume
+ # (nothing stops you from doing this)
+ for volume in $(volumes_labeled "$NIX_VOLUME_LABEL"); do
+ # CAUTION: this could (maybe) be a more normal read
+ # loop like:
+ # while IFS== read -r special uuid; do
+ # # ...
+ # done <<<"$(volumes_labeled "$NIX_VOLUME_LABEL")"
+ #
+ # I did it with for to skirt a problem with the obvious
+ # pattern replacing stdin and causing user prompts
+ # inside (which also use read and access stdin) to skip
+ #
+ # If there's an existing encrypted volume we can't find
+ # in keychain, the user never gets prompted to delete
+ # the volume, and the install fails.
+ #
+ # If you change this, a human needs to test a very
+ # specific scenario: you already have an encrypted
+ # Nix Store volume, and have deleted its credential
+ # from keychain. Ensure the script asks you if it can
+ # delete the volume, and then prompts for your sudo
+ # password to confirm.
+ #
+ # shellcheck disable=SC1097
+ IFS== read -r special uuid <<< "$volume"
+ # take the first one that's on the right disk
+ if [ -z "${found:-}" ]; then
+ if right_volume "$special" && right_uuid "$uuid"; then
+ cure_volume "$special" "$uuid"
+ found="${special} (${uuid})"
+ else
+ warning <<EOF
+Ignoring ${special} (${uuid}) because I am looking for:
+disk=${NIX_VOLUME_USE_DISK} special=${NIX_VOLUME_USE_SPECIAL:-${NIX_VOLUME_USE_DISK}sX} uuid=${NIX_VOLUME_USE_UUID:-any}
+EOF
+ # TODO: give chance to delete if ! headless?
+ fi
+ else
+ warning <<EOF
+Ignoring ${special} (${uuid}), already found target: $found
+EOF
+ # TODO reminder? I feel like I want one
+ # idiom that reminds some warnings, or warns
+ # some reminders?
+ # TODO: if ! headless, chance to delete?
+ fi
+ done
+ if [ -z "${found:-}" ]; then
+ readonly NIX_VOLUME_USE_SPECIAL NIX_VOLUME_USE_UUID
+ fi
}
-find_nix_volume() {
- diskutil apfs list -plist "$1" | xmllint --xpath "(/plist/dict/array/dict/key[text()='Volumes']/following-sibling::array/dict/key[text()='Name']/following-sibling::string[starts-with(translate(text(),'N','n'),'nix')]/text())[1]" - 2>/dev/null || true
+volume_encrypted() {
+ local volume_special="$1" # (i.e., disk1s7)
+ # Trying to match the first line of output; known first lines:
+ # No cryptographic users for <special>
+ # Cryptographic user for <special> (1 found)
+ # Cryptographic users for <special> (2 found)
+ /usr/sbin/diskutil apfs listCryptoUsers -plist "$volume_special" | /usr/bin/grep -q APFSCryptoUserUUID
}
test_fstab() {
- grep -q "/nix apfs rw" /etc/fstab 2>/dev/null
+ /usr/bin/grep -q "$NIX_ROOT apfs rw" /etc/fstab 2>/dev/null
}
-test_nix_symlink() {
- [ -L "/nix" ] || grep -q "^nix." /etc/synthetic.conf 2>/dev/null
+test_nix_root_is_symlink() {
+ [ -L "$NIX_ROOT" ]
}
-test_synthetic_conf() {
- grep -q "^nix$" /etc/synthetic.conf 2>/dev/null
+test_synthetic_conf_either(){
+ /usr/bin/grep -qE "^${NIX_ROOT:1}($|\t.{3,}$)" /etc/synthetic.conf 2>/dev/null
+}
+
+test_synthetic_conf_mountable() {
+ /usr/bin/grep -q "^${NIX_ROOT:1}$" /etc/synthetic.conf 2>/dev/null
+}
+
+test_synthetic_conf_symlinked() {
+ /usr/bin/grep -qE "^${NIX_ROOT:1}\t.{3,}$" /etc/synthetic.conf 2>/dev/null
+}
+
+test_nix_volume_mountd_installed() {
+ test -e "$NIX_VOLUME_MOUNTD_DEST"
+}
+
+# current volume password
+test_keychain_by_uuid() {
+ local volume_uuid="$1"
+ # Note: doesn't need sudo just to check; doesn't output pw
+ security find-generic-password -s "$volume_uuid" &>/dev/null
+}
+
+get_volume_pass() {
+ local volume_uuid="$1"
+ _sudo \
+ "to confirm keychain has a password that unlocks this volume" \
+ security find-generic-password -s "$volume_uuid" -w
+}
+
+verify_volume_pass() {
+ local volume_special="$1" # (i.e., disk1s7)
+ local volume_uuid="$2"
+ /usr/sbin/diskutil apfs unlockVolume "$volume_special" -verify -stdinpassphrase -user "$volume_uuid"
+}
+
+volume_pass_works() {
+ local volume_special="$1" # (i.e., disk1s7)
+ local volume_uuid="$2"
+ get_volume_pass "$volume_uuid" | verify_volume_pass "$volume_special" "$volume_uuid"
}
# Create the paths defined in synthetic.conf, saving us a reboot.
-create_synthetic_objects(){
+create_synthetic_objects() {
# Big Sur takes away the -B flag we were using and replaces it
# with a -t flag that appears to do the same thing (but they
# don't behave exactly the same way in terms of return values).
@@ -41,129 +270,578 @@ create_synthetic_objects(){
}
test_nix() {
- test -d "/nix"
-}
-
-test_t2_chip_present(){
- # Use xartutil to see if system has a t2 chip.
- #
- # This isn't well-documented on its own; until it is,
- # let's keep track of knowledge/assumptions.
- #
- # Warnings:
- # - Don't search "xart" if porn will cause you trouble :)
- # - Other xartutil flags do dangerous things. Don't run them
- # naively. If you must, search "xartutil" first.
- #
- # Assumptions:
- # - the "xART session seeds recovery utility"
- # appears to interact with xartstorageremoted
- # - `sudo xartutil --list` lists xART sessions
- # and their seeds and exits 0 if successful. If
- # not, it exits 1 and prints an error such as:
- # xartutil: ERROR: No supported link to the SEP present
- # - xART sessions/seeds are present when a T2 chip is
- # (and not, otherwise)
- # - the presence of a T2 chip means a newly-created
- # volume on the primary drive will be
- # encrypted at rest
- # - all together: `sudo xartutil --list`
- # should exit 0 if a new Nix Store volume will
- # be encrypted at rest, and exit 1 if not.
- sudo xartutil --list >/dev/null 2>/dev/null
-}
-
-test_filevault_in_use() {
- fdesetup isactive >/dev/null
-}
-
-# use after error msg for conditions we don't understand
-suggest_report_error(){
- # ex "error: something sad happened :(" >&2
- echo " please report this @ https://github.com/nixos/nix/issues" >&2
-}
-
-main() {
- (
- echo ""
- echo " ------------------------------------------------------------------ "
- echo " | This installer will create a volume for the nix store and |"
- echo " | configure it to mount at /nix. Follow these steps to uninstall. |"
- echo " ------------------------------------------------------------------ "
- echo ""
- echo " 1. Remove the entry from fstab using 'sudo vifs'"
- echo " 2. Destroy the data volume using 'diskutil apfs deleteVolume'"
- echo " 3. Remove the 'nix' line from /etc/synthetic.conf or the file"
- echo ""
- ) >&2
-
- if test_nix_symlink; then
- echo "error: /nix is a symlink, please remove it and make sure it's not in synthetic.conf (in which case a reboot is required)" >&2
- echo " /nix -> $(readlink "/nix")" >&2
- exit 2
- fi
-
- if ! test_synthetic_conf; then
- echo "Configuring /etc/synthetic.conf..." >&2
- echo nix | sudo tee -a /etc/synthetic.conf
- if ! test_synthetic_conf; then
- echo "error: failed to configure synthetic.conf;" >&2
- suggest_report_error
- exit 1
+ test -d "$NIX_ROOT"
+}
+
+test_voldaemon() {
+ test -f "$NIX_VOLUME_MOUNTD_DEST"
+}
+
+generate_mount_command() {
+ local cmd_type="$1" # encrypted|unencrypted
+ local volume_uuid mountpoint cmd=()
+ printf -v volume_uuid "%q" "$2"
+ printf -v mountpoint "%q" "$NIX_ROOT"
+
+ case "$cmd_type" in
+ encrypted)
+ cmd=(/bin/sh -c "/usr/bin/security find-generic-password -s '$volume_uuid' -w | /usr/sbin/diskutil apfs unlockVolume '$volume_uuid' -mountpoint '$mountpoint' -stdinpassphrase");;
+ unencrypted)
+ cmd=(/usr/sbin/diskutil mount -mountPoint "$mountpoint" "$volume_uuid");;
+ *)
+ failure "Invalid first arg $cmd_type to generate_mount_command";;
+ esac
+
+ printf " <string>%s</string>\n" "${cmd[@]}"
+}
+
+generate_mount_daemon() {
+ local cmd_type="$1" # encrypted|unencrypted
+ local volume_uuid="$2"
+ cat <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>RunAtLoad</key>
+ <true/>
+ <key>Label</key>
+ <string>org.nixos.darwin-store</string>
+ <key>ProgramArguments</key>
+ <array>
+$(generate_mount_command "$cmd_type" "$volume_uuid")
+ </array>
+</dict>
+</plist>
+EOF
+}
+
+_eat_bootout_err() {
+ /usr/bin/grep -v "Boot-out failed: 36: Operation now in progress"
+}
+
+# TODO: remove with --uninstall?
+uninstall_launch_daemon_directions() {
+ local daemon_label="$1" # i.e., org.nixos.blah-blah
+ local daemon_plist="$2" # abspath
+ substep "Uninstall LaunchDaemon $daemon_label" \
+ " sudo launchctl bootout system/$daemon_label" \
+ " sudo rm $daemon_plist"
+}
+
+uninstall_launch_daemon_prompt() {
+ local daemon_label="$1" # i.e., org.nixos.blah-blah
+ local daemon_plist="$2" # abspath
+ local reason_for_daemon="$3"
+ cat <<EOF
+
+The installer adds a LaunchDaemon to $reason_for_daemon: $daemon_label
+EOF
+ if ui_confirm "Can I remove it?"; then
+ _sudo "to terminate the daemon" \
+ launchctl bootout "system/$daemon_label" 2> >(_eat_bootout_err >&2) || true
+ # this can "fail" with a message like:
+ # Boot-out failed: 36: Operation now in progress
+ _sudo "to remove the daemon definition" rm "$daemon_plist"
+ fi
+}
+
+nix_volume_mountd_uninstall_directions() {
+ uninstall_launch_daemon_directions "org.nixos.darwin-store" \
+ "$NIX_VOLUME_MOUNTD_DEST"
+}
+
+nix_volume_mountd_uninstall_prompt() {
+ uninstall_launch_daemon_prompt "org.nixos.darwin-store" \
+ "$NIX_VOLUME_MOUNTD_DEST" \
+ "mount your Nix volume"
+}
+
+# TODO: move nix_daemon to install-darwin-multi-user if/when uninstall_launch_daemon_prompt moves up to install-multi-user
+nix_daemon_uninstall_prompt() {
+ uninstall_launch_daemon_prompt "org.nixos.nix-daemon" \
+ "$NIX_DAEMON_DEST" \
+ "run the nix-daemon"
+}
+
+# TODO: remove with --uninstall?
+nix_daemon_uninstall_directions() {
+ uninstall_launch_daemon_directions "org.nixos.nix-daemon" \
+ "$NIX_DAEMON_DEST"
+}
+
+
+# TODO: remove with --uninstall?
+synthetic_conf_uninstall_directions() {
+ # :1 to strip leading slash
+ substep "Remove ${NIX_ROOT:1} from /etc/synthetic.conf" \
+ " If nix is the only entry: sudo rm /etc/synthetic.conf" \
+ " Otherwise: sudo /usr/bin/sed -i '' -e '/^${NIX_ROOT:1}$/d' /etc/synthetic.conf"
+}
+
+synthetic_conf_uninstall_prompt() {
+ cat <<EOF
+
+During install, I add '${NIX_ROOT:1}' to /etc/synthetic.conf, which instructs
+macOS to create an empty root directory for mounting the Nix volume.
+EOF
+ # make the edit to a copy
+ /usr/bin/grep -vE "^${NIX_ROOT:1}($|\t.{3,}$)" /etc/synthetic.conf > "$SCRATCH/synthetic.conf.edit"
+
+ if test_synthetic_conf_symlinked; then
+ warning <<EOF
+
+/etc/synthetic.conf already contains a line instructing your system
+to make '${NIX_ROOT}' as a symlink:
+ $(/usr/bin/grep -nE "^${NIX_ROOT:1}\t.{3,}$" /etc/synthetic.conf)
+
+This may mean your system has/had a non-standard Nix install.
+
+The volume-creation process in this installer is *not* compatible
+with a symlinked store, so I'll have to remove this instruction to
+continue.
+
+If you want/need to keep this instruction, answer 'n' to abort.
+
+EOF
+ fi
+
+ # ask to rm if this left the file empty aside from comments, else edit
+ if /usr/bin/diff -q <(:) <(/usr/bin/grep -v "^#" "$SCRATCH/synthetic.conf.edit") &>/dev/null; then
+ if confirm_rm "/etc/synthetic.conf"; then
+ if test_nix_root_is_symlink; then
+ failure >&2 <<EOF
+I removed /etc/synthetic.conf, but $NIX_ROOT is already a symlink
+(-> $(readlink "$NIX_ROOT")). The system should remove it when you reboot.
+Once you've rebooted, run the installer again.
+EOF
+ fi
+ return 0
+ fi
+ else
+ if confirm_edit "$SCRATCH/synthetic.conf.edit" "/etc/synthetic.conf"; then
+ if test_nix_root_is_symlink; then
+ failure >&2 <<EOF
+I edited Nix out of /etc/synthetic.conf, but $NIX_ROOT is already a symlink
+(-> $(readlink "$NIX_ROOT")). The system should remove it when you reboot.
+Once you've rebooted, run the installer again.
+EOF
+ fi
+ return 0
fi
fi
+ # fallback instructions
+ echo "Manually remove nix from /etc/synthetic.conf"
+ return 1
+}
- if ! test_nix; then
- echo "Creating mountpoint for /nix..." >&2
- create_synthetic_objects # the ones we defined in synthetic.conf
- if ! test_nix; then
- sudo mkdir -p /nix 2>/dev/null || true
+add_nix_vol_fstab_line() {
+ local uuid="$1"
+ # shellcheck disable=SC1003,SC2026
+ local escaped_mountpoint="${NIX_ROOT/ /'\\\'040}"
+ shift
+ EDITOR="/usr/bin/ex" _sudo "to add nix to fstab" "$@" <<EOF
+:a
+UUID=$uuid $escaped_mountpoint apfs rw,noauto,nobrowse,suid,owners
+.
+:x
+EOF
+ # TODO: preserving my notes on suid,owners above until resolved
+ # There *may* be some issue regarding volume ownership, see nix#3156
+ #
+ # It seems like the cheapest fix is adding "suid,owners" to fstab, but:
+ # - We don't have much info on this condition yet
+ # - I'm not certain if these cause other problems?
+ # - There's a "chown" component some people claim to need to fix this
+ # that I don't understand yet
+ # (Note however that I've had to add a chown step to handle
+ # single->multi-user reinstalls, which may cover this)
+ #
+ # I'm not sure if it's safe to approach this way?
+ #
+ # I think I think the most-proper way to test for it is:
+ # diskutil info -plist "$NIX_VOLUME_LABEL" | xmllint --xpath "(/plist/dict/key[text()='GlobalPermissionsEnabled'])/following-sibling::*[1][name()='true']" -; echo $?
+ #
+ # There's also `sudo /usr/sbin/vsdbutil -c /path` (which is much faster, but is also
+ # deprecated and needs minor parsing).
+ #
+ # If no one finds a problem with doing so, I think the simplest approach
+ # is to just eagerly set this. I found a few imperative approaches:
+ # (diskutil enableOwnership, ~100ms), a cheap one (/usr/sbin/vsdbutil -a, ~40-50ms),
+ # a very cheap one (append the internal format to /var/db/volinfo.database).
+ #
+ # But vsdbutil's deprecation notice suggests using fstab, so I want to
+ # give that a whirl first.
+ #
+ # TODO: when this is workable, poke infinisil about reproducing the issue
+ # and confirming this fix?
+}
+
+delete_nix_vol_fstab_line() {
+ # TODO: I'm scaffolding this to handle the new nix volumes
+ # but it might be nice to generalize a smidge further to
+ # go ahead and set up a pattern for curing "old" things
+ # we no longer do?
+ EDITOR="/usr/bin/patch" _sudo "to cut nix from fstab" "$@" < <(/usr/bin/diff /etc/fstab <(/usr/bin/grep -v "$NIX_ROOT apfs rw" /etc/fstab))
+ # leaving some parts out of the grep; people may fiddle this a little?
+}
+
+# TODO: hope to remove with --uninstall
+fstab_uninstall_directions() {
+ substep "Remove ${NIX_ROOT} from /etc/fstab" \
+ " If nix is the only entry: sudo rm /etc/fstab" \
+ " Otherwise, run 'sudo /usr/sbin/vifs' to remove the nix line"
+}
+
+fstab_uninstall_prompt() {
+ cat <<EOF
+During install, I add '${NIX_ROOT}' to /etc/fstab so that macOS knows what
+mount options to use for the Nix volume.
+EOF
+ cp /etc/fstab "$SCRATCH/fstab.edit"
+ # technically doesn't need the _sudo path, but throwing away the
+ # output is probably better than mostly-duplicating the code...
+ delete_nix_vol_fstab_line patch "$SCRATCH/fstab.edit" &>/dev/null
+
+ # if the patch test edit, minus comment lines, is equal to empty (:)
+ if /usr/bin/diff -q <(:) <(/usr/bin/grep -v "^#" "$SCRATCH/fstab.edit") &>/dev/null; then
+ # this edit would leave it empty; propose deleting it
+ if confirm_rm "/etc/fstab"; then
+ return 0
+ else
+ echo "Remove nix from /etc/fstab (or remove the file)"
fi
- if ! test_nix; then
- echo "error: failed to bootstrap /nix; if a reboot doesn't help," >&2
- suggest_report_error
- exit 1
+ else
+ echo "I might be able to help you make this edit. Here's the diff:"
+ if ! _diff "/etc/fstab" "$SCRATCH/fstab.edit" && ui_confirm "Does the change above look right?"; then
+ delete_nix_vol_fstab_line /usr/sbin/vifs
+ else
+ echo "Remove nix from /etc/fstab (or remove the file)"
fi
fi
+}
- disk="$(root_disk_identifier)"
- volume=$(find_nix_volume "$disk")
- if [ -z "$volume" ]; then
- echo "Creating a Nix Store volume..." >&2
-
- if test_filevault_in_use; then
- # TODO: Not sure if it's in-scope now, but `diskutil apfs list`
- # shows both filevault and encrypted at rest status, and it
- # may be the more semantic way to test for this? It'll show
- # `FileVault: No (Encrypted at rest)`
- # `FileVault: No`
- # `FileVault: Yes (Unlocked)`
- # and so on.
- if test_t2_chip_present; then
- echo "warning: boot volume is FileVault-encrypted, but the Nix store volume" >&2
- echo " is only encrypted at rest." >&2
- echo " See https://nixos.org/nix/manual/#sect-macos-installation" >&2
+remove_volume() {
+ local volume_special="$1" # (i.e., disk1s7)
+ _sudo "to unmount the Nix volume" \
+ /usr/sbin/diskutil unmount force "$volume_special" || true # might not be mounted
+ _sudo "to delete the Nix volume" \
+ /usr/sbin/diskutil apfs deleteVolume "$volume_special"
+}
+
+# aspiration: robust enough to both fix problems
+# *and* update older darwin volumes
+cure_volume() {
+ local volume_special="$1" # (i.e., disk1s7)
+ local volume_uuid="$2"
+ header "Found existing Nix volume"
+ row " special" "$volume_special"
+ row " uuid" "$volume_uuid"
+
+ if volume_encrypted "$volume_special"; then
+ row "encrypted" "yes"
+ if volume_pass_works "$volume_special" "$volume_uuid"; then
+ NIX_VOLUME_DO_ENCRYPT=0
+ ok "Found a working decryption password in keychain :)"
+ echo ""
+ else
+ # - this is a volume we made, and
+ # - the user encrypted it on their own
+ # - something deleted the credential
+ # - this is an old or BYO volume and the pw
+ # just isn't somewhere we can find it.
+ #
+ # We're going to explain why we're freaking out
+ # and prompt them to either delete the volume
+ # (requiring a sudo auth), or abort to fix
+ warning <<EOF
+
+This volume is encrypted, but I don't see a password to decrypt it.
+The quick fix is to let me delete this volume and make you a new one.
+If that's okay, enter your (sudo) password to continue. If not, you
+can ensure the decryption password is in your system keychain with a
+"Where" (service) field set to this volume's UUID:
+ $volume_uuid
+EOF
+ if password_confirm "delete this volume"; then
+ remove_volume "$volume_special"
else
- echo "error: refusing to create Nix store volume because the boot volume is" >&2
- echo " FileVault encrypted, but encryption-at-rest is not available." >&2
- echo " Manually create a volume for the store and re-run this script." >&2
- echo " See https://nixos.org/nix/manual/#sect-macos-installation" >&2
- exit 1
+ # TODO: this is a good design case for a warn-and
+ # remind idiom...
+ failure <<EOF
+Your Nix volume is encrypted, but I couldn't find its password. Either:
+- Delete or rename the volume out of the way
+- Ensure its decryption password is in the system keychain with a
+ "Where" (service) field set to this volume's UUID:
+ $volume_uuid
+EOF
+ fi
+ fi
+ elif test_filevault_in_use; then
+ row "encrypted" "no"
+ warning <<EOF
+FileVault is on, but your $NIX_VOLUME_LABEL volume isn't encrypted.
+EOF
+ # if we're interactive, give them a chance to
+ # encrypt the volume. If not, /shrug
+ if ! headless && (( NIX_VOLUME_DO_ENCRYPT == 1 )); then
+ if ui_confirm "Should I encrypt it and add the decryption key to your keychain?"; then
+ encrypt_volume "$volume_uuid" "$NIX_VOLUME_LABEL"
+ NIX_VOLUME_DO_ENCRYPT=0
+ else
+ NIX_VOLUME_DO_ENCRYPT=0
+ reminder "FileVault is on, but your $NIX_VOLUME_LABEL volume isn't encrypted."
fi
fi
-
- sudo diskutil apfs addVolume "$disk" APFS 'Nix Store' -mountpoint /nix
- volume="Nix Store"
else
- echo "Using existing '$volume' volume" >&2
+ row "encrypted" "no"
+ fi
+}
+
+remove_volume_artifacts() {
+ if test_synthetic_conf_either; then
+ # NIX_ROOT is in synthetic.conf
+ if synthetic_conf_uninstall_prompt; then
+ # TODO: moot until we tackle uninstall, but when we're
+ # actually uninstalling, we should issue:
+ # reminder "macOS will clean up the empty mount-point directory at $NIX_ROOT on reboot."
+ :
+ fi
+ fi
+ if test_fstab; then
+ fstab_uninstall_prompt
+ fi
+
+ if test_nix_volume_mountd_installed; then
+ nix_volume_mountd_uninstall_prompt
+ fi
+}
+
+setup_synthetic_conf() {
+ if test_nix_root_is_symlink; then
+ if ! test_synthetic_conf_symlinked; then
+ failure >&2 <<EOF
+error: $NIX_ROOT is a symlink (-> $(readlink "$NIX_ROOT")).
+Please remove it. If nix is in /etc/synthetic.conf, remove it and reboot.
+EOF
+ fi
+ fi
+ if ! test_synthetic_conf_mountable; then
+ task "Configuring /etc/synthetic.conf to make a mount-point at $NIX_ROOT" >&2
+ # technically /etc/synthetic.d/nix is supported in Big Sur+
+ # but handling both takes even more code...
+ _sudo "to add Nix to /etc/synthetic.conf" \
+ /usr/bin/ex /etc/synthetic.conf <<EOF
+:a
+${NIX_ROOT:1}
+.
+:x
+EOF
+ if ! test_synthetic_conf_mountable; then
+ failure "error: failed to configure synthetic.conf" >&2
+ fi
+ create_synthetic_objects
+ if ! test_nix; then
+ failure >&2 <<EOF
+error: failed to bootstrap $NIX_ROOT
+If you enabled FileVault after booting, this is likely a known issue
+with macOS that you'll have to reboot to fix. If you didn't enable FV,
+though, please open an issue describing how the system that you see
+this error on was set up.
+EOF
+ fi
fi
+}
+setup_fstab() {
+ local volume_uuid="$1"
+ # fstab used to be responsible for mounting the volume. Now the last
+ # step adds a LaunchDaemon responsible for mounting. This is technically
+ # redundant for mounting, but diskutil appears to pick up mount options
+ # from fstab (and diskutil's support for specifying them directly is not
+ # consistent across versions/subcommands).
if ! test_fstab; then
- echo "Configuring /etc/fstab..." >&2
- label=$(echo "$volume" | sed 's/ /\\040/g')
- # shellcheck disable=SC2209
- printf "\$a\nLABEL=%s /nix apfs rw,nobrowse\n.\nwq\n" "$label" | EDITOR=ed sudo vifs
+ task "Configuring /etc/fstab to specify volume mount options" >&2
+ add_nix_vol_fstab_line "$volume_uuid" /usr/sbin/vifs
fi
}
-main "$@"
+encrypt_volume() {
+ local volume_uuid="$1"
+ local volume_label="$2"
+ local password
+ # Note: mount/unmount are late additions to support the right order
+ # of operations for creating the volume and then baking its uuid into
+ # other artifacts; not as well-trod wrt to potential errors, race
+ # conditions, etc.
+
+ /usr/sbin/diskutil mount "$volume_label"
+
+ password="$(/usr/bin/xxd -l 32 -p -c 256 /dev/random)"
+ _sudo "to add your Nix volume's password to Keychain" \
+ /usr/bin/security -i <<EOF
+add-generic-password -a "$volume_label" -s "$volume_uuid" -l "$volume_label encryption password" -D "Encrypted volume password" -j "Added automatically by the Nix installer for use by $NIX_VOLUME_MOUNTD_DEST" -w "$password" -T /System/Library/CoreServices/APFSUserAgent -T /System/Library/CoreServices/CSUserAgent -T /usr/bin/security "/Library/Keychains/System.keychain"
+EOF
+ builtin printf "%s" "$password" | _sudo "to encrypt your Nix volume" \
+ /usr/sbin/diskutil apfs encryptVolume "$volume_label" -user disk -stdinpassphrase
+
+ /usr/sbin/diskutil unmount force "$volume_label"
+}
+
+create_volume() {
+ # Notes:
+ # 1) using `-nomount` instead of `-mountpoint "$NIX_ROOT"` to get
+ # its UUID and set mount opts in fstab before first mount
+ #
+ # 2) system is in some sense less secure than user keychain... (it's
+ # possible to read the password for decrypting the keychain) but
+ # the user keychain appears to be available too late. As far as I
+ # can tell, the file with this password (/var/db/SystemKey) is
+ # inside the FileVault envelope. If that isn't true, it may make
+ # sense to store the password inside the envelope?
+ #
+ # 3) At some point it would be ideal to have a small binary to serve
+ # as the daemon itself, and for it to replace /usr/bin/security here.
+ #
+ # 4) *UserAgent exemptions should let the system seamlessly supply the
+ # password if noauto is removed from fstab entry. This is intentional;
+ # the user will hopefully look for help if the volume stops mounting,
+ # rather than failing over into subtle race-condition problems.
+ #
+ # 5) If we ever get users griping about not having space to do
+ # anything useful with Nix, it is possibly to specify
+ # `-reserve 10g` or something, which will fail w/o that much
+ #
+ # 6) getting special w/ awk may be fragile, but doing it to:
+ # - save time over running slow diskutil commands
+ # - skirt risk we grab wrong volume if multiple match
+ _sudo "to create a new APFS volume '$NIX_VOLUME_LABEL' on $NIX_VOLUME_USE_DISK" \
+ /usr/sbin/diskutil apfs addVolume "$NIX_VOLUME_USE_DISK" "$NIX_VOLUME_FS" "$NIX_VOLUME_LABEL" -nomount | /usr/bin/awk '/Created new APFS Volume/ {print $5}'
+}
+
+volume_uuid_from_special() {
+ local volume_special="$1" # (i.e., disk1s7)
+ # For reasons I won't pretend to fathom, this returns 253 when it works
+ /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -k "$volume_special" || true
+}
+
+# this sometimes clears immediately, and AFAIK clears
+# within about 1s. diskutil info on an unmounted path
+# fails in around 50-100ms and a match takes about
+# 250-300ms. I suspect it's usually ~250-750ms
+await_volume() {
+ # caution: this could, in theory, get stuck
+ until /usr/sbin/diskutil info "$NIX_ROOT" &>/dev/null; do
+ :
+ done
+}
+
+setup_volume() {
+ local use_special use_uuid profile_packages
+ task "Creating a Nix volume" >&2
+
+ use_special="${NIX_VOLUME_USE_SPECIAL:-$(create_volume)}"
+
+ _sudo "to ensure the Nix volume is not mounted" \
+ /usr/sbin/diskutil unmount force "$use_special" || true # might not be mounted
+
+ use_uuid=${NIX_VOLUME_USE_UUID:-$(volume_uuid_from_special "$use_special")}
+
+ setup_fstab "$use_uuid"
+
+ if should_encrypt_volume; then
+ encrypt_volume "$use_uuid" "$NIX_VOLUME_LABEL"
+ setup_volume_daemon "encrypted" "$use_uuid"
+ # TODO: might be able to save ~60ms by caching or setting
+ # this somewhere rather than re-checking here.
+ elif volume_encrypted "$use_special"; then
+ setup_volume_daemon "encrypted" "$use_uuid"
+ else
+ setup_volume_daemon "unencrypted" "$use_uuid"
+ fi
+
+ await_volume
+
+ if [ "$(/usr/sbin/diskutil info -plist "$NIX_ROOT" | xmllint --xpath "(/plist/dict/key[text()='GlobalPermissionsEnabled'])/following-sibling::*[1]" -)" = "<false/>" ]; then
+ _sudo "to set enableOwnership (enabling users to own files)" \
+ /usr/sbin/diskutil enableOwnership "$NIX_ROOT"
+ fi
+
+ # TODO: below is a vague kludge for now; I just don't know
+ # what if any safe action there is to take here. Also, the
+ # reminder isn't very helpful.
+ # I'm less sure where this belongs, but it also wants mounted, pre-install
+ if type -p nix-env; then
+ profile_packages="$(nix-env --query --installed)"
+ # TODO: can probably do below faster w/ read
+ # intentionally unquoted string to eat whitespace in wc output
+ # shellcheck disable=SC2046,SC2059
+ if ! [ $(printf "$profile_packages" | /usr/bin/wc -l) = "0" ]; then
+ reminder <<EOF
+Nix now supports only multi-user installs on Darwin/macOS, and your user's
+Nix profile has some packages in it. These packages may obscure those in the
+default profile, including the Nix this installer will add. You should
+review these packages:
+$profile_packages
+EOF
+ fi
+ fi
+
+}
+
+setup_volume_daemon() {
+ local cmd_type="$1" # encrypted|unencrypted
+ local volume_uuid="$2"
+ if ! test_voldaemon; then
+ task "Configuring LaunchDaemon to mount '$NIX_VOLUME_LABEL'" >&2
+ _sudo "to install the Nix volume mounter" /usr/bin/ex "$NIX_VOLUME_MOUNTD_DEST" <<EOF
+:a
+$(generate_mount_daemon "$cmd_type" "$volume_uuid")
+.
+:x
+EOF
+
+ # TODO: should probably alert the user if this is disabled?
+ _sudo "to launch the Nix volume mounter" \
+ launchctl bootstrap system "$NIX_VOLUME_MOUNTD_DEST" || true
+ # TODO: confirm whether kickstart is necessesary?
+ # I feel a little superstitous, but it can guard
+ # against multiple problems (doesn't start, old
+ # version still running for some reason...)
+ _sudo "to launch the Nix volume mounter" \
+ launchctl kickstart -k system/org.nixos.darwin-store
+ fi
+}
+
+setup_darwin_volume() {
+ setup_synthetic_conf
+ setup_volume
+}
+
+if [ "$_CREATE_VOLUME_NO_MAIN" = 1 ]; then
+ if [ -n "$*" ]; then
+ "$@" # expose functions in case we want multiple routines?
+ fi
+else
+ # no reason to pay for bash to process this
+ main() {
+ {
+ echo ""
+ echo " ------------------------------------------------------------------ "
+ echo " | This installer will create a volume for the nix store and |"
+ echo " | configure it to mount at $NIX_ROOT. Follow these steps to uninstall. |"
+ echo " ------------------------------------------------------------------ "
+ echo ""
+ echo " 1. Remove the entry from fstab using 'sudo /usr/sbin/vifs'"
+ echo " 2. Run 'sudo launchctl bootout system/org.nixos.darwin-store'"
+ echo " 3. Remove $NIX_VOLUME_MOUNTD_DEST"
+ echo " 4. Destroy the data volume using '/usr/sbin/diskutil apfs deleteVolume'"
+ echo " 5. Remove the 'nix' line from /etc/synthetic.conf (or the file)"
+ echo ""
+ } >&2
+
+ setup_darwin_volume
+ }
+
+ main "$@"
+fi
diff --git a/scripts/install-darwin-multi-user.sh b/scripts/install-darwin-multi-user.sh
index f6575ae2f..96eba8310 100644
--- a/scripts/install-darwin-multi-user.sh
+++ b/scripts/install-darwin-multi-user.sh
@@ -3,59 +3,110 @@
set -eu
set -o pipefail
-readonly PLIST_DEST=/Library/LaunchDaemons/org.nixos.nix-daemon.plist
+readonly NIX_DAEMON_DEST=/Library/LaunchDaemons/org.nixos.nix-daemon.plist
+# create by default; set 0 to DIY, use a symlink, etc.
+readonly NIX_VOLUME_CREATE=${NIX_VOLUME_CREATE:-1} # now default
NIX_FIRST_BUILD_UID="301"
NIX_BUILD_USER_NAME_TEMPLATE="_nixbld%d"
+# caution: may update times on / if not run as normal non-root user
+read_only_root() {
+ # this touch command ~should~ always produce an error
+ # as of this change I confirmed /usr/bin/touch emits:
+ # "touch: /: Operation not permitted" Monterey
+ # "touch: /: Read-only file system" Catalina+ and Big Sur
+ # "touch: /: Permission denied" Mojave
+ # (not matching prefix for compat w/ coreutils touch in case using
+ # an explicit path causes problems; its prefix differs)
+ case "$(/usr/bin/touch / 2>&1)" in
+ *"Read-only file system") # Catalina, Big Sur
+ return 0
+ ;;
+ *"Operation not permitted") # Monterey
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+
+ # Avoiding the slow semantic way to get this information (~330ms vs ~8ms)
+ # unless using touch causes problems. Just in case, that approach is:
+ # diskutil info -plist / | <find the Writable or WritableVolume keys>, i.e.
+ # diskutil info -plist / | xmllint --xpath "name(/plist/dict/key[text()='Writable']/following-sibling::*[1])" -
+}
+
+if read_only_root && [ "$NIX_VOLUME_CREATE" = 1 ]; then
+ should_create_volume() { return 0; }
+else
+ should_create_volume() { return 1; }
+fi
+
+# shellcheck source=./create-darwin-volume.sh
+. "$EXTRACTED_NIX_PATH/create-darwin-volume.sh" "no-main"
+
dsclattr() {
/usr/bin/dscl . -read "$1" \
- | awk "/$2/ { print \$2 }"
+ | /usr/bin/awk "/$2/ { print \$2 }"
+}
+
+test_nix_daemon_installed() {
+ test -e "$NIX_DAEMON_DEST"
}
-poly_validate_assumptions() {
- if [ "$(uname -s)" != "Darwin" ]; then
- failure "This script is for use with macOS!"
+poly_cure_artifacts() {
+ if should_create_volume; then
+ task "Fixing any leftover Nix volume state"
+ cat <<EOF
+Before I try to install, I'll check for any existing Nix volume config
+and ask for your permission to remove it (so that the installer can
+start fresh). I'll also ask for permission to fix any issues I spot.
+EOF
+ cure_volumes
+ remove_volume_artifacts
fi
}
poly_service_installed_check() {
- [ -e "$PLIST_DEST" ]
+ if should_create_volume; then
+ test_nix_daemon_installed || test_nix_volume_mountd_installed
+ else
+ test_nix_daemon_installed
+ fi
}
poly_service_uninstall_directions() {
- cat <<EOF
-$1. Delete $PLIST_DEST
-
- sudo launchctl unload $PLIST_DEST
- sudo rm $PLIST_DEST
-
-EOF
+ echo "$1. Remove macOS-specific components:"
+ if should_create_volume && test_nix_volume_mountd_installed; then
+ nix_volume_mountd_uninstall_directions
+ fi
+ if test_nix_daemon_installed; then
+ nix_daemon_uninstall_directions
+ fi
}
poly_service_setup_note() {
- cat <<EOF
- - load and start a LaunchDaemon (at $PLIST_DEST) for nix-daemon
-
-EOF
+ if should_create_volume; then
+ echo " - create a Nix volume and a LaunchDaemon to mount it"
+ fi
+ echo " - create a LaunchDaemon (at $NIX_DAEMON_DEST) for nix-daemon"
+ echo ""
}
-poly_extra_try_me_commands(){
- :
-}
-poly_extra_setup_instructions(){
- :
+poly_extra_try_me_commands() {
+ :
}
poly_configure_nix_daemon_service() {
+ task "Setting up the nix-daemon LaunchDaemon"
_sudo "to set up the nix-daemon as a LaunchDaemon" \
- cp -f "/nix/var/nix/profiles/default$PLIST_DEST" "$PLIST_DEST"
+ /bin/cp -f "/nix/var/nix/profiles/default$NIX_DAEMON_DEST" "$NIX_DAEMON_DEST"
_sudo "to load the LaunchDaemon plist for nix-daemon" \
launchctl load /Library/LaunchDaemons/org.nixos.nix-daemon.plist
_sudo "to start the nix-daemon" \
- launchctl start org.nixos.nix-daemon
-
+ launchctl kickstart -k system/org.nixos.nix-daemon
}
poly_group_exists() {
@@ -96,6 +147,8 @@ poly_user_home_get() {
}
poly_user_home_set() {
+ # This can trigger a permission prompt now:
+ # "Terminal" would like to administer your computer. Administration can include modifying passwords, networking, and system settings.
_sudo "in order to give $1 a safe home directory" \
/usr/bin/dscl . -create "/Users/$1" "NFSHomeDirectory" "$2"
}
@@ -121,7 +174,7 @@ poly_user_shell_set() {
poly_user_in_group_check() {
username=$1
group=$2
- dseditgroup -o checkmember -m "$username" "$group" > /dev/null 2>&1
+ /usr/sbin/dseditgroup -o checkmember -m "$username" "$group" > /dev/null 2>&1
}
poly_user_in_group_set() {
@@ -151,3 +204,21 @@ poly_create_build_user() {
/usr/bin/dscl . create "/Users/$username" \
UniqueID "${uid}"
}
+
+poly_prepare_to_install() {
+ if should_create_volume; then
+ header "Preparing a Nix volume"
+ # intentional indent below to match task indent
+ cat <<EOF
+ Nix traditionally stores its data in the root directory $NIX_ROOT, but
+ macOS now (starting in 10.15 Catalina) has a read-only root directory.
+ To support Nix, I will create a volume and configure macOS to mount it
+ at $NIX_ROOT.
+EOF
+ setup_darwin_volume
+ fi
+
+ if [ "$(diskutil info -plist /nix | xmllint --xpath "(/plist/dict/key[text()='GlobalPermissionsEnabled'])/following-sibling::*[1]" -)" = "<false/>" ]; then
+ failure "This script needs a /nix volume with global permissions! This may require running sudo diskutil enableOwnership /nix."
+ fi
+}
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
diff --git a/scripts/install-nix-from-closure.sh b/scripts/install-nix-from-closure.sh
index 0ee7ce5af..b5e2fea83 100644
--- a/scripts/install-nix-from-closure.sh
+++ b/scripts/install-nix-from-closure.sh
@@ -26,18 +26,9 @@ fi
# macOS support for 10.12.6 or higher
if [ "$(uname -s)" = "Darwin" ]; then
- IFS='.' read macos_major macos_minor macos_patch << EOF
+ IFS='.' read -r macos_major macos_minor macos_patch << EOF
$(sw_vers -productVersion)
EOF
- # TODO: this is a temporary speed-bump to keep people from naively installing Nix
- # on macOS Big Sur (11.0+, 10.16+) until nixpkgs updates are ready for them.
- # *Ideally* this is gone before next Nix release. If you're intentionally working on
- # Nix + Big Sur, just comment out this block and be on your way :)
- if [ "$macos_major" -gt 10 ] || { [ "$macos_major" -eq 10 ] && [ "$macos_minor" -gt 15 ]; }; then
- echo "$0: nixpkgs isn't quite ready to support macOS $(sw_vers -productVersion) yet"
- exit 1
- fi
-
if [ "$macos_major" -lt 10 ] || { [ "$macos_major" -eq 10 ] && [ "$macos_minor" -lt 12 ]; } || { [ "$macos_minor" -eq 12 ] && [ "$macos_patch" -lt 6 ]; }; then
# patch may not be present; command substitution for simplicity
echo "$0: macOS $(sw_vers -productVersion) is not supported, upgrade to 10.12.6 or higher"
@@ -46,21 +37,40 @@ EOF
fi
# Determine if we could use the multi-user installer or not
-if [ "$(uname -s)" = "Darwin" ]; then
- echo "Note: a multi-user installation is possible. See https://nixos.org/nix/manual/#sect-multi-user-installation" >&2
-elif [ "$(uname -s)" = "Linux" ]; then
+if [ "$(uname -s)" = "Linux" ]; then
echo "Note: a multi-user installation is possible. See https://nixos.org/nix/manual/#sect-multi-user-installation" >&2
fi
-INSTALL_MODE=no-daemon
-CREATE_DARWIN_VOLUME=0
+case "$(uname -s)" in
+ "Darwin")
+ INSTALL_MODE=daemon;;
+ *)
+ INSTALL_MODE=no-daemon;;
+esac
+
+# space-separated string
+ACTIONS=
+
# handle the command line flags
while [ $# -gt 0 ]; do
case $1 in
--daemon)
- INSTALL_MODE=daemon;;
+ INSTALL_MODE=daemon
+ ACTIONS="${ACTIONS}install "
+ ;;
--no-daemon)
- INSTALL_MODE=no-daemon;;
+ if [ "$(uname -s)" = "Darwin" ]; then
+ printf '\e[1;31mError: --no-daemon installs are no-longer supported on Darwin/macOS!\e[0m\n' >&2
+ exit 1
+ fi
+ INSTALL_MODE=no-daemon
+ # intentional tail space
+ ACTIONS="${ACTIONS}install "
+ ;;
+ # --uninstall)
+ # # intentional tail space
+ # ACTIONS="${ACTIONS}uninstall "
+ # ;;
--no-channel-add)
export NIX_INSTALLER_NO_CHANNEL_ADD=1;;
--daemon-user-count)
@@ -69,13 +79,18 @@ while [ $# -gt 0 ]; do
--no-modify-profile)
NIX_INSTALLER_NO_MODIFY_PROFILE=1;;
--darwin-use-unencrypted-nix-store-volume)
- CREATE_DARWIN_VOLUME=1;;
+ {
+ echo "Warning: the flag --darwin-use-unencrypted-nix-store-volume"
+ echo " is no longer needed and will be removed in the future."
+ echo ""
+ } >&2;;
--nix-extra-conf-file)
- export NIX_EXTRA_CONF="$(cat $2)"
+ # shellcheck disable=SC2155
+ export NIX_EXTRA_CONF="$(cat "$2")"
shift;;
*)
- (
- echo "Nix Installer [--daemon|--no-daemon] [--daemon-user-count INT] [--no-channel-add] [--no-modify-profile] [--darwin-use-unencrypted-nix-store-volume] [--nix-extra-conf-file FILE]"
+ {
+ echo "Nix Installer [--daemon|--no-daemon] [--daemon-user-count INT] [--no-channel-add] [--no-modify-profile] [--nix-extra-conf-file FILE]"
echo "Choose installation method."
echo ""
@@ -91,55 +106,25 @@ while [ $# -gt 0 ]; do
echo ""
echo " --no-channel-add: Don't add any channels. nixpkgs-unstable is installed by default."
echo ""
- echo " --no-modify-profile: Skip channel installation. When not provided nixpkgs-unstable"
- echo " is installed by default."
+ echo " --no-modify-profile: Don't modify the user profile to automatically load nix."
echo ""
echo " --daemon-user-count: Number of build users to create. Defaults to 32."
echo ""
- echo " --nix-extra-conf-file: Path to nix.conf to prepend when installing /etc/nix.conf"
+ echo " --nix-extra-conf-file: Path to nix.conf to prepend when installing /etc/nix/nix.conf"
echo ""
if [ -n "${INVOKED_FROM_INSTALL_IN:-}" ]; then
echo " --tarball-url-prefix URL: Base URL to download the Nix tarball from."
fi
- ) >&2
-
- # darwin and Catalina+
- if [ "$(uname -s)" = "Darwin" ] && { [ "$macos_major" -gt 10 ] || { [ "$macos_major" -eq 10 ] && [ "$macos_minor" -gt 14 ]; }; }; then
- (
- echo " --darwin-use-unencrypted-nix-store-volume: Create an APFS volume for the Nix"
- echo " store and mount it at /nix. This is the recommended way to create"
- echo " /nix with a read-only / on macOS >=10.15."
- echo " See: https://nixos.org/nix/manual/#sect-macos-installation"
- echo ""
- ) >&2
- fi
+ } >&2
+
exit;;
esac
shift
done
-if [ "$(uname -s)" = "Darwin" ]; then
- if [ "$CREATE_DARWIN_VOLUME" = 1 ]; then
- printf '\e[1;31mCreating volume and mountpoint /nix.\e[0m\n'
- "$self/create-darwin-volume.sh"
- fi
-
- writable="$(diskutil info -plist / | xmllint --xpath "name(/plist/dict/key[text()='Writable']/following-sibling::*[1])" -)"
- if ! [ -e $dest ] && [ "$writable" = "false" ]; then
- (
- echo ""
- echo "Installing on macOS >=10.15 requires relocating the store to an apfs volume."
- echo "Use sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume or run the preparation steps manually."
- echo "See https://nixos.org/nix/manual/#sect-macos-installation"
- echo ""
- ) >&2
- exit 1
- fi
-fi
-
if [ "$INSTALL_MODE" = "daemon" ]; then
printf '\e[1;31mSwitching to the Multi-user Installer\e[0m\n'
- exec "$self/install-multi-user"
+ exec "$self/install-multi-user" $ACTIONS # let ACTIONS split
exit 0
fi
@@ -149,7 +134,7 @@ fi
echo "performing a single-user installation of Nix..." >&2
-if ! [ -e $dest ]; then
+if ! [ -e "$dest" ]; then
cmd="mkdir -m 0755 $dest && chown $USER $dest"
echo "directory $dest does not exist; creating it by running '$cmd' using sudo" >&2
if ! sudo sh -c "$cmd"; then
@@ -158,12 +143,12 @@ if ! [ -e $dest ]; then
fi
fi
-if ! [ -w $dest ]; then
+if ! [ -w "$dest" ]; then
echo "$0: directory $dest exists, but is not writable by you. This could indicate that another user has already performed a single-user installation of Nix on this system. If you wish to enable multi-user support see https://nixos.org/nix/manual/#ssec-multi-user. If you wish to continue with a single-user install for $USER please run 'chown -R $USER $dest' as root." >&2
exit 1
fi
-mkdir -p $dest/store
+mkdir -p "$dest/store"
printf "copying Nix to %s..." "${dest}/store" >&2
# Insert a newline if no progress is shown.
@@ -194,6 +179,7 @@ if ! "$nix/bin/nix-store" --load-db < "$self/.reginfo"; then
exit 1
fi
+# shellcheck source=./nix-profile.sh.in
. "$nix/etc/profile.d/nix.sh"
if ! "$nix/bin/nix-env" -i "$nix"; then
@@ -203,17 +189,17 @@ fi
# Install an SSL certificate bundle.
if [ -z "$NIX_SSL_CERT_FILE" ] || ! [ -f "$NIX_SSL_CERT_FILE" ]; then
- $nix/bin/nix-env -i "$cacert"
+ "$nix/bin/nix-env" -i "$cacert"
export NIX_SSL_CERT_FILE="$HOME/.nix-profile/etc/ssl/certs/ca-bundle.crt"
fi
# Subscribe the user to the Nixpkgs channel and fetch it.
if [ -z "$NIX_INSTALLER_NO_CHANNEL_ADD" ]; then
- if ! $nix/bin/nix-channel --list | grep -q "^nixpkgs "; then
- $nix/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
+ if ! "$nix/bin/nix-channel" --list | grep -q "^nixpkgs "; then
+ "$nix/bin/nix-channel" --add https://nixos.org/channels/nixpkgs-unstable
fi
if [ -z "$_NIX_INSTALLER_TEST" ]; then
- if ! $nix/bin/nix-channel --update nixpkgs; then
+ if ! "$nix/bin/nix-channel" --update nixpkgs; then
echo "Fetching the nixpkgs channel failed. (Are you offline?)"
echo "To try again later, run \"nix-channel --update nixpkgs\"."
fi
@@ -229,7 +215,7 @@ if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then
if [ -w "$fn" ]; then
if ! grep -q "$p" "$fn"; then
echo "modifying $fn..." >&2
- echo -e "\nif [ -e $p ]; then . $p; fi # added by Nix installer" >> "$fn"
+ printf '\nif [ -e %s ]; then . %s; fi # added by Nix installer\n' "$p" "$p" >> "$fn"
fi
added=1
break
@@ -240,7 +226,7 @@ if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then
if [ -w "$fn" ]; then
if ! grep -q "$p" "$fn"; then
echo "modifying $fn..." >&2
- echo -e "\nif [ -e $p ]; then . $p; fi # added by Nix installer" >> "$fn"
+ printf '\nif [ -e %s ]; then . %s; fi # added by Nix installer\n' "$p" "$p" >> "$fn"
fi
added=1
break
diff --git a/scripts/install-systemd-multi-user.sh b/scripts/install-systemd-multi-user.sh
index fda5ef600..f4a2dfc5d 100755
--- a/scripts/install-systemd-multi-user.sh
+++ b/scripts/install-systemd-multi-user.sh
@@ -15,7 +15,7 @@ readonly SERVICE_OVERRIDE=${SERVICE_DEST}.d/override.conf
create_systemd_override() {
header "Configuring proxy for the nix-daemon service"
- _sudo "create directory for systemd unit override" mkdir -p "$(dirname $SERVICE_OVERRIDE)"
+ _sudo "create directory for systemd unit override" mkdir -p "$(dirname "$SERVICE_OVERRIDE")"
cat <<EOF | _sudo "create systemd unit override" tee "$SERVICE_OVERRIDE"
[Service]
$1
@@ -41,10 +41,8 @@ handle_network_proxy() {
fi
}
-poly_validate_assumptions() {
- if [ "$(uname -s)" != "Linux" ]; then
- failure "This script is for use with Linux!"
- fi
+poly_cure_artifacts() {
+ :
}
poly_service_installed_check() {
@@ -72,7 +70,7 @@ poly_service_setup_note() {
EOF
}
-poly_extra_try_me_commands(){
+poly_extra_try_me_commands() {
if [ -e /run/systemd/system ]; then
:
else
@@ -81,19 +79,10 @@ poly_extra_try_me_commands(){
EOF
fi
}
-poly_extra_setup_instructions(){
- if [ -e /run/systemd/system ]; then
- :
- else
- cat <<EOF
-Additionally, you may want to add nix-daemon to your init-system.
-
-EOF
- fi
-}
poly_configure_nix_daemon_service() {
if [ -e /run/systemd/system ]; then
+ task "Setting up the nix-daemon systemd service"
_sudo "to set up the nix-daemon service" \
systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC"
@@ -110,6 +99,8 @@ poly_configure_nix_daemon_service() {
_sudo "to start the nix-daemon.service" \
systemctl restart nix-daemon.service
+ else
+ reminder "I don't support your init system yet; you may want to add nix-daemon manually."
fi
}
@@ -207,3 +198,7 @@ poly_create_build_user() {
--password "!" \
"$username"
}
+
+poly_prepare_to_install() {
+ :
+}
diff --git a/scripts/install.in b/scripts/install.in
index 7d25f7bd7..38d1fb36f 100755
--- a/scripts/install.in
+++ b/scripts/install.in
@@ -40,21 +40,25 @@ case "$(uname -s).$(uname -m)" in
path=@tarballPath_aarch64-linux@
system=aarch64-linux
;;
+ Linux.armv6l_linux)
+ hash=@tarballHash_armv6l-linux@
+ path=@tarballPath_armv6l-linux@
+ system=armv6l-linux
+ ;;
+ Linux.armv7l_linux)
+ hash=@tarballHash_armv7l-linux@
+ path=@tarballPath_armv7l-linux@
+ system=armv7l-linux
+ ;;
Darwin.x86_64)
hash=@tarballHash_x86_64-darwin@
path=@tarballPath_x86_64-darwin@
system=x86_64-darwin
;;
Darwin.arm64|Darwin.aarch64)
- # check for Rosetta 2 support
- if ! [ -f /Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist ]; then
- oops "Rosetta 2 is not installed on this ARM64 macOS machine. Run softwareupdate --install-rosetta then restart installation"
- fi
-
- hash=@binaryTarball_x86_64-darwin@
- path=@tarballPath_x86_64-darwin@
- # eventually maybe: aarch64-darwin
- system=x86_64-darwin
+ hash=@tarballHash_aarch64-darwin@
+ path=@tarballPath_aarch64-darwin@
+ system=aarch64-darwin
;;
*) oops "sorry, there is no binary distribution of Nix for your platform";;
esac
@@ -72,14 +76,21 @@ fi
tarball=$tmpDir/nix-@nixVersion@-$system.tar.xz
-require_util curl "download the binary tarball"
require_util tar "unpack the binary tarball"
if [ "$(uname -s)" != "Darwin" ]; then
require_util xz "unpack the binary tarball"
fi
+if command -v curl > /dev/null 2>&1; then
+ fetch() { curl -L "$1" -o "$2"; }
+elif command -v wget > /dev/null 2>&1; then
+ fetch() { wget "$1" -O "$2"; }
+else
+ oops "you don't have wget or curl installed, which I need to download the binary tarball"
+fi
+
echo "downloading Nix @nixVersion@ binary tarball for $system from '$url' to '$tmpDir'..."
-curl -L "$url" -o "$tarball" || oops "failed to download '$url'"
+fetch "$url" "$tarball" || oops "failed to download '$url'"
if command -v sha256sum > /dev/null 2>&1; then
hash2="$(sha256sum -b "$tarball" | cut -c1-64)"
diff --git a/scripts/local.mk b/scripts/local.mk
index 2a0055852..b8477178e 100644
--- a/scripts/local.mk
+++ b/scripts/local.mk
@@ -1,7 +1,5 @@
nix_noinst_scripts := \
- $(d)/nix-http-export.cgi \
- $(d)/nix-profile.sh \
- $(d)/nix-reduce-build
+ $(d)/nix-profile.sh
noinst-scripts += $(nix_noinst_scripts)
diff --git a/scripts/nix-http-export.cgi.in b/scripts/nix-http-export.cgi.in
deleted file mode 100755
index 19a505af1..000000000
--- a/scripts/nix-http-export.cgi.in
+++ /dev/null
@@ -1,51 +0,0 @@
-#! /bin/sh
-
-export HOME=/tmp
-export NIX_REMOTE=daemon
-
-TMP_DIR="${TMP_DIR:-/tmp/nix-export}"
-
-@coreutils@/mkdir -p "$TMP_DIR" || true
-@coreutils@/chmod a+r "$TMP_DIR"
-
-needed_path="?$QUERY_STRING"
-needed_path="${needed_path#*[?&]needed_path=}"
-needed_path="${needed_path%%&*}"
-#needed_path="$(echo $needed_path | ./unhttp)"
-needed_path="${needed_path//%2B/+}"
-needed_path="${needed_path//%3D/=}"
-
-echo needed_path: "$needed_path" >&2
-
-NIX_STORE="${NIX_STORE_DIR:-/nix/store}"
-
-echo NIX_STORE: "${NIX_STORE}" >&2
-
-full_path="${NIX_STORE}"/"$needed_path"
-
-if [ "$needed_path" != "${needed_path%.drv}" ]; then
- echo "Status: 403 You should create the derivation file yourself"
- echo "Content-Type: text/plain"
- echo
- echo "Refusing to disclose derivation contents"
- exit
-fi
-
-if @bindir@/nix-store --check-validity "$full_path"; then
- if ! [ -e nix-export/"$needed_path".nar.gz ]; then
- @bindir@/nix-store --export "$full_path" | @gzip@ > "$TMP_DIR"/"$needed_path".nar.gz
- @coreutils@/ln -fs "$TMP_DIR"/"$needed_path".nar.gz nix-export/"$needed_path".nar.gz
- fi;
- echo "Status: 301 Moved"
- echo "Location: nix-export/"$needed_path".nar.gz"
- echo
-else
- echo "Status: 404 No such path found"
- echo "Content-Type: text/plain"
- echo
- echo "Path not found:"
- echo "$needed_path"
- echo "checked:"
- echo "$full_path"
-fi
-
diff --git a/scripts/nix-profile-daemon.sh.in b/scripts/nix-profile-daemon.sh.in
index 500a98992..0a47571ac 100644
--- a/scripts/nix-profile-daemon.sh.in
+++ b/scripts/nix-profile-daemon.sh.in
@@ -5,7 +5,7 @@ __ETC_PROFILE_NIX_SOURCED=1
export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile"
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
-if [ ! -z "${NIX_SSL_CERT_FILE:-}" ]; then
+if [ -n "${NIX_SSL_CERT_FILE:-}" ]; then
: # Allow users to override the NIX_SSL_CERT_FILE
elif [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch
export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
@@ -18,14 +18,14 @@ elif [ -e /etc/pki/tls/certs/ca-bundle.crt ]; then # Fedora, CentOS
else
# Fall back to what is in the nix profiles, favouring whatever is defined last.
check_nix_profiles() {
- if [ "$ZSH_VERSION" ]; then
+ if [ -n "$ZSH_VERSION" ]; then
# Zsh by default doesn't split words in unquoted parameter expansion.
# Set local_options for these options to be reverted at the end of the function
# and shwordsplit to force splitting words in $NIX_PROFILES below.
setopt local_options shwordsplit
fi
for i in $NIX_PROFILES; do
- if [ -e $i/etc/ssl/certs/ca-bundle.crt ]; then
+ if [ -e "$i/etc/ssl/certs/ca-bundle.crt" ]; then
export NIX_SSL_CERT_FILE=$i/etc/ssl/certs/ca-bundle.crt
fi
done
diff --git a/scripts/nix-reduce-build.in b/scripts/nix-reduce-build.in
deleted file mode 100755
index 50beb9d10..000000000
--- a/scripts/nix-reduce-build.in
+++ /dev/null
@@ -1,171 +0,0 @@
-#! @bash@
-
-WORKING_DIRECTORY=$(mktemp -d "${TMPDIR:-/tmp}"/nix-reduce-build-XXXXXX);
-cd "$WORKING_DIRECTORY";
-
-if test -z "$1" || test "a--help" = "a$1" ; then
- echo 'nix-reduce-build (paths or Nix expressions) -- (package sources)' >&2
- echo As in: >&2
- echo nix-reduce-build /etc/nixos/nixos -- ssh://user@somewhere.nowhere.example.org >&2
- echo nix-reduce-build /etc/nixos/nixos -- \\
- echo " " \''http://somewhere.nowhere.example.org/nix/nix-http-export.cgi?needed_path='\' >&2
- echo " store path name will be added into the end of the URL" >&2
- echo nix-reduce-build /etc/nixos/nixos -- file://home/user/nar/ >&2
- echo " that should be a directory where gzipped 'nix-store --export' ">&2
- echo " files are located (they should have .nar.gz extension)" >&2
- echo " Or all together: " >&2
- echo -e nix-reduce-build /expr.nix /e2.nix -- \\\\\\\n\
- " ssh://a@b.example.com http://n.example.com/get-nar?q= file://nar/" >&2
- echo " Also supports best-effort local builds of failing expression set:" >&2
- echo "nix-reduce-build /e.nix -- nix-daemon:// nix-self://" >&2
- echo " nix-daemon:// builds using daemon"
- echo " nix-self:// builds directly using nix-store from current installation" >&2
- echo " nix-daemon-fixed:// and nix-self-fixed:// do the same, but only for" >&2;
- echo "derivations with specified output hash (sha256, sha1 or md5)." >&2
- echo " nix-daemon-substitute:// and nix-self-substitute:// try to substitute" >&2;
- echo "maximum amount of paths" >&2;
- echo " nix-daemon-build:// and nix-self-build:// try to build (not substitute)" >&2;
- echo "maximum amount of paths" >&2;
- echo " If no package sources are specified, required paths are listed." >&2;
- exit;
-fi;
-
-while ! test "$1" = "--" || test "$1" = "" ; do
- echo "$1" >> initial; >&2
- shift;
-done
-shift;
-echo Will work on $(cat initial | wc -l) targets. >&2
-
-while read ; do
- case "$REPLY" in
- ${NIX_STORE_DIR:-/nix/store}/*)
- echo "$REPLY" >> paths; >&2
- ;;
- *)
- (
- IFS=: ;
- nix-instantiate $REPLY >> paths;
- );
- ;;
- esac;
-done < initial;
-echo Proceeding $(cat paths | wc -l) paths. >&2
-
-while read; do
- case "$REPLY" in
- *.drv)
- echo "$REPLY" >> derivers; >&2
- ;;
- *)
- nix-store --query --deriver "$REPLY" >>derivers;
- ;;
- esac;
-done < paths;
-echo Found $(cat derivers | wc -l) derivers. >&2
-
-cat derivers | xargs nix-store --query -R > derivers-closure;
-echo Proceeding at most $(cat derivers-closure | wc -l) derivers. >&2
-
-cat derivers-closure | egrep '[.]drv$' | xargs nix-store --query --outputs > wanted-paths;
-cat derivers-closure | egrep -v '[.]drv$' >> wanted-paths;
-echo Prepared $(cat wanted-paths | wc -l) paths to get. >&2
-
-cat wanted-paths | xargs nix-store --check-validity --print-invalid > needed-paths;
-echo We need $(cat needed-paths | wc -l) paths. >&2
-
-egrep '[.]drv$' derivers-closure > critical-derivers;
-
-if test -z "$1" ; then
- cat needed-paths;
-fi;
-
-refresh_critical_derivers() {
- echo "Finding needed derivers..." >&2;
- cat critical-derivers | while read; do
- if ! (nix-store --query --outputs "$REPLY" | xargs nix-store --check-validity &> /dev/null;); then
- echo "$REPLY";
- fi;
- done > new-critical-derivers;
- mv new-critical-derivers critical-derivers;
- echo The needed paths are realized by $(cat critical-derivers | wc -l) derivers. >&2
-}
-
-build_here() {
- cat critical-derivers | while read; do
- echo "Realising $REPLY using nix-daemon" >&2
- @bindir@/nix-store -r "${REPLY}"
- done;
-}
-
-try_to_substitute(){
- cat needed-paths | while read ; do
- echo "Building $REPLY using nix-daemon" >&2
- @bindir@/nix-store -r "${NIX_STORE_DIR:-/nix/store}/${REPLY##*/}"
- done;
-}
-
-for i in "$@"; do
- sshHost="${i#ssh://}";
- httpHost="${i#http://}";
- httpsHost="${i#https://}";
- filePath="${i#file:/}";
- if [ "$i" != "$sshHost" ]; then
- cat needed-paths | while read; do
- echo "Getting $REPLY and its closure over ssh" >&2
- nix-copy-closure --from "$sshHost" --gzip "$REPLY" </dev/null || true;
- done;
- elif [ "$i" != "$httpHost" ] || [ "$i" != "$httpsHost" ]; then
- cat needed-paths | while read; do
- echo "Getting $REPLY over http/https" >&2
- curl ${BAD_CERTIFICATE:+-k} -L "$i${REPLY##*/}" | gunzip | nix-store --import;
- done;
- elif [ "$i" != "$filePath" ] ; then
- cat needed-paths | while read; do
- echo "Installing $REPLY from file" >&2
- gunzip < "$filePath/${REPLY##*/}".nar.gz | nix-store --import;
- done;
- elif [ "$i" = "nix-daemon://" ] ; then
- NIX_REMOTE=daemon try_to_substitute;
- refresh_critical_derivers;
- NIX_REMOTE=daemon build_here;
- elif [ "$i" = "nix-self://" ] ; then
- NIX_REMOTE= try_to_substitute;
- refresh_critical_derivers;
- NIX_REMOTE= build_here;
- elif [ "$i" = "nix-daemon-fixed://" ] ; then
- refresh_critical_derivers;
-
- cat critical-derivers | while read; do
- if egrep '"(md5|sha1|sha256)"' "$REPLY" &>/dev/null; then
- echo "Realising $REPLY using nix-daemon" >&2
- NIX_REMOTE=daemon @bindir@/nix-store -r "${REPLY}"
- fi;
- done;
- elif [ "$i" = "nix-self-fixed://" ] ; then
- refresh_critical_derivers;
-
- cat critical-derivers | while read; do
- if egrep '"(md5|sha1|sha256)"' "$REPLY" &>/dev/null; then
- echo "Realising $REPLY using direct Nix build" >&2
- NIX_REMOTE= @bindir@/nix-store -r "${REPLY}"
- fi;
- done;
- elif [ "$i" = "nix-daemon-substitute://" ] ; then
- NIX_REMOTE=daemon try_to_substitute;
- elif [ "$i" = "nix-self-substitute://" ] ; then
- NIX_REMOTE= try_to_substitute;
- elif [ "$i" = "nix-daemon-build://" ] ; then
- refresh_critical_derivers;
- NIX_REMOTE=daemon build_here;
- elif [ "$i" = "nix-self-build://" ] ; then
- refresh_critical_derivers;
- NIX_REMOTE= build_here;
- fi;
- mv needed-paths wanted-paths;
- cat wanted-paths | xargs nix-store --check-validity --print-invalid > needed-paths;
- echo We still need $(cat needed-paths | wc -l) paths. >&2
-done;
-
-cd /
-rm -r "$WORKING_DIRECTORY"
diff --git a/scripts/prepare-installer-for-github-actions b/scripts/prepare-installer-for-github-actions
index 92d930384..4b994a753 100755
--- a/scripts/prepare-installer-for-github-actions
+++ b/scripts/prepare-installer-for-github-actions
@@ -3,7 +3,7 @@
set -e
script=$(nix-build -A outputs.hydraJobs.installerScriptForGHA --no-out-link)
-installerHash=$(echo $script | cut -b12-43 -)
+installerHash=$(echo "$script" | cut -b12-43 -)
installerURL=https://$CACHIX_NAME.cachix.org/serve/$installerHash/install