diff options
author | Ben Burdette <bburdette@gmail.com> | 2021-11-25 08:53:59 -0700 |
---|---|---|
committer | Ben Burdette <bburdette@gmail.com> | 2021-11-25 08:53:59 -0700 |
commit | 64c4ba8f66c7569478fd5f19ebb72c9590cc2b45 (patch) | |
tree | 65d874c35432e81c3d244caadd7c467eccd0b87d /scripts | |
parent | 69e26c5c4ba106bd16f60bfaac88ccf888b4383f (diff) | |
parent | ca82967ee3276e2aa8b02ea7e6d19cfd4fa75f4c (diff) |
Merge branch 'master' into debug-merge
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/create-darwin-volume.sh | 926 | ||||
-rw-r--r-- | scripts/install-darwin-multi-user.sh | 123 | ||||
-rw-r--r-- | scripts/install-multi-user.sh | 338 | ||||
-rw-r--r-- | scripts/install-nix-from-closure.sh | 116 | ||||
-rwxr-xr-x | scripts/install-systemd-multi-user.sh | 27 | ||||
-rwxr-xr-x | scripts/install.in | 33 | ||||
-rw-r--r-- | scripts/local.mk | 4 | ||||
-rwxr-xr-x | scripts/nix-http-export.cgi.in | 51 | ||||
-rw-r--r-- | scripts/nix-profile-daemon.sh.in | 6 | ||||
-rwxr-xr-x | scripts/nix-reduce-build.in | 171 | ||||
-rwxr-xr-x | scripts/prepare-installer-for-github-actions | 2 |
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>
</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 |