mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-22 22:16:16 +02:00
Merge pull request #4289 from abathur/encrypt_darwin_volume
darwin: encrypt nix volume if filevault is enabled
This commit is contained in:
commit
d15a1962cb
6 changed files with 1186 additions and 434 deletions
|
@ -1,18 +1,26 @@
|
|||
# Installing a Binary Distribution
|
||||
|
||||
If you are using Linux or macOS versions up to 10.14 (Mojave), the
|
||||
easiest way to install Nix is to run the following command:
|
||||
The easiest way to install Nix is to run the following command:
|
||||
|
||||
```console
|
||||
$ sh <(curl -L https://nixos.org/nix/install)
|
||||
```
|
||||
|
||||
If you're using macOS 10.15 (Catalina) or newer, consult [the macOS
|
||||
installation instructions](#macos-installation) before installing.
|
||||
This will run the installer interactively (causing it to explain what
|
||||
it is doing more explicitly), and perform the default "type" of install
|
||||
for your platform:
|
||||
- single-user on Linux
|
||||
- multi-user on macOS
|
||||
|
||||
As of Nix 2.1.0, the Nix installer will always default to creating a
|
||||
single-user installation, however opting in to the multi-user
|
||||
installation is highly recommended.
|
||||
> **Notes on read-only filesystem root in macOS 10.15 Catalina +**
|
||||
>
|
||||
> - It took some time to support this cleanly. You may see posts,
|
||||
> examples, and tutorials using obsolete workarounds.
|
||||
> - Supporting it cleanly made macOS installs too complex to qualify
|
||||
> as single-user, so this type is no longer supported on macOS.
|
||||
|
||||
We recommend the multi-user install if it supports your platform and
|
||||
you can authenticate with `sudo`.
|
||||
|
||||
# Single User Installation
|
||||
|
||||
|
@ -50,9 +58,9 @@ $ rm -rf /nix
|
|||
The multi-user Nix installation creates system users, and a system
|
||||
service for the Nix daemon.
|
||||
|
||||
- Linux running systemd, with SELinux disabled
|
||||
|
||||
- macOS
|
||||
**Supported Systems**
|
||||
- Linux running systemd, with SELinux disabled
|
||||
- macOS
|
||||
|
||||
You can instruct the installer to perform a multi-user installation on
|
||||
your system:
|
||||
|
@ -96,165 +104,28 @@ sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
|||
There may also be references to Nix in `/etc/profile`, `/etc/bashrc`,
|
||||
and `/etc/zshrc` which you may remove.
|
||||
|
||||
# macOS Installation
|
||||
# macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a>
|
||||
<!-- Note: anchors above to catch permalinks to old explanations -->
|
||||
|
||||
Starting with macOS 10.15 (Catalina), the root filesystem is read-only.
|
||||
This means `/nix` can no longer live on your system volume, and that
|
||||
you'll need a workaround to install Nix.
|
||||
We believe we have ironed out how to cleanly support the read-only root
|
||||
on modern macOS. New installs will do this automatically, and you can
|
||||
also re-run a new installer to convert your existing setup.
|
||||
|
||||
The recommended approach, which creates an unencrypted APFS volume for
|
||||
your Nix store and a "synthetic" empty directory to mount it over at
|
||||
`/nix`, is least likely to impair Nix or your system.
|
||||
This section previously detailed the situation, options, and trade-offs,
|
||||
but it now only outlines what the installer does. You don't need to know
|
||||
this to run the installer, but it may help if you run into trouble:
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> With all separate-volume approaches, it's possible something on your
|
||||
> system (particularly daemons/services and restored apps) may need
|
||||
> access to your Nix store before the volume is mounted. Adding
|
||||
> additional encryption makes this more likely.
|
||||
|
||||
If you're using a recent Mac with a [T2
|
||||
chip](https://www.apple.com/euro/mac/shared/docs/Apple_T2_Security_Chip_Overview.pdf),
|
||||
your drive will still be encrypted at rest (in which case "unencrypted"
|
||||
is a bit of a misnomer). To use this approach, just install Nix with:
|
||||
|
||||
```console
|
||||
$ sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume
|
||||
```
|
||||
|
||||
If you don't like the sound of this, you'll want to weigh the other
|
||||
approaches and tradeoffs detailed in this section.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> All of the known workarounds have drawbacks, but we hope better
|
||||
> solutions will be available in the future. Some that we have our eye
|
||||
> on are:
|
||||
>
|
||||
> 1. A true firmlink would enable the Nix store to live on the primary
|
||||
> data volume without the build problems caused by the symlink
|
||||
> approach. End users cannot currently create true firmlinks.
|
||||
>
|
||||
> 2. If the Nix store volume shared FileVault encryption with the
|
||||
> primary data volume (probably by using the same volume group and
|
||||
> role), FileVault encryption could be easily supported by the
|
||||
> installer without requiring manual setup by each user.
|
||||
|
||||
## Change the Nix store path prefix
|
||||
|
||||
Changing the default prefix for the Nix store is a simple approach which
|
||||
enables you to leave it on your root volume, where it can take full
|
||||
advantage of FileVault encryption if enabled. Unfortunately, this
|
||||
approach also opts your device out of some benefits that are enabled by
|
||||
using the same prefix across systems:
|
||||
|
||||
- Your system won't be able to take advantage of the binary cache
|
||||
(unless someone is able to stand up and support duplicate caching
|
||||
infrastructure), which means you'll spend more time waiting for
|
||||
builds.
|
||||
|
||||
- It's harder to build and deploy packages to Linux systems.
|
||||
|
||||
It would also possible (and often requested) to just apply this change
|
||||
ecosystem-wide, but it's an intrusive process that has side effects we
|
||||
want to avoid for now.
|
||||
|
||||
## Use a separate encrypted volume
|
||||
|
||||
If you like, you can also add encryption to the recommended approach
|
||||
taken by the installer. You can do this by pre-creating an encrypted
|
||||
volume before you run the installer--or you can run the installer and
|
||||
encrypt the volume it creates later.
|
||||
|
||||
In either case, adding encryption to a second volume isn't quite as
|
||||
simple as enabling FileVault for your boot volume. Before you dive in,
|
||||
there are a few things to weigh:
|
||||
|
||||
1. The additional volume won't be encrypted with your existing
|
||||
FileVault key, so you'll need another mechanism to decrypt the
|
||||
volume.
|
||||
|
||||
2. You can store the password in Keychain to automatically decrypt the
|
||||
volume on boot--but it'll have to wait on Keychain and may not mount
|
||||
before your GUI apps restore. If any of your launchd agents or apps
|
||||
depend on Nix-installed software (for example, if you use a
|
||||
Nix-installed login shell), the restore may fail or break.
|
||||
|
||||
On a case-by-case basis, you may be able to work around this problem
|
||||
by using `wait4path` to block execution until your executable is
|
||||
available.
|
||||
|
||||
It's also possible to decrypt and mount the volume earlier with a
|
||||
login hook--but this mechanism appears to be deprecated and its
|
||||
future is unclear.
|
||||
|
||||
3. You can hard-code the password in the clear, so that your store
|
||||
volume can be decrypted before Keychain is available.
|
||||
|
||||
If you are comfortable navigating these tradeoffs, you can encrypt the
|
||||
volume with something along the lines of:
|
||||
|
||||
```console
|
||||
$ diskutil apfs enableFileVault /nix -user disk
|
||||
```
|
||||
|
||||
## Symlink the Nix store to a custom location
|
||||
|
||||
Another simple approach is using `/etc/synthetic.conf` to symlink the
|
||||
Nix store to the data volume. This option also enables your store to
|
||||
share any configured FileVault encryption. Unfortunately, builds that
|
||||
resolve the symlink may leak the canonical path or even fail.
|
||||
|
||||
Because of these downsides, we can't recommend this approach.
|
||||
|
||||
## Notes on the recommended approach
|
||||
|
||||
This section goes into a little more detail on the recommended approach.
|
||||
You don't need to understand it to run the installer, but it can serve
|
||||
as a helpful reference if you run into trouble.
|
||||
|
||||
1. In order to compose user-writable locations into the new read-only
|
||||
system root, Apple introduced a new concept called `firmlinks`,
|
||||
which it describes as a "bi-directional wormhole" between two
|
||||
filesystems. You can see the current firmlinks in
|
||||
`/usr/share/firmlinks`. Unfortunately, firmlinks aren't (currently?)
|
||||
user-configurable.
|
||||
|
||||
For special cases like NFS mount points or package manager roots,
|
||||
[synthetic.conf(5)](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man5/synthetic.conf.5.html)
|
||||
supports limited user-controlled file-creation (of symlinks, and
|
||||
synthetic empty directories) at `/`. To create a synthetic empty
|
||||
directory for mounting at `/nix`, add the following line to
|
||||
`/etc/synthetic.conf` (create it if necessary):
|
||||
|
||||
nix
|
||||
|
||||
2. This configuration is applied at boot time, but you can use
|
||||
`apfs.util` to trigger creation (not deletion) of new entries
|
||||
without a reboot:
|
||||
|
||||
```console
|
||||
$ /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -B
|
||||
```
|
||||
|
||||
3. Create the new APFS volume with diskutil:
|
||||
|
||||
```console
|
||||
$ sudo diskutil apfs addVolume diskX APFS 'Nix Store' -mountpoint /nix
|
||||
```
|
||||
|
||||
4. Using `vifs`, add the new mount to `/etc/fstab`. If it doesn't
|
||||
already have other entries, it should look something like:
|
||||
|
||||
#
|
||||
# Warning - this file should only be modified with vifs(8)
|
||||
#
|
||||
# Failure to do so is unsupported and may be destructive.
|
||||
#
|
||||
LABEL=Nix\040Store /nix apfs rw,nobrowse
|
||||
|
||||
The nobrowse setting will keep Spotlight from indexing this volume,
|
||||
and keep it from showing up on your desktop.
|
||||
- create a new APFS volume for your Nix store
|
||||
- update `/etc/synthetic.conf` to direct macOS to create a "synthetic"
|
||||
empty root directory to mount your volume
|
||||
- specify mount options for the volume in `/etc/fstab`
|
||||
- if you have FileVault enabled
|
||||
- generate an encryption password
|
||||
- put it in your system Keychain
|
||||
- use it to encrypt the volume
|
||||
- create a system LaunchDaemon to mount this volume early enough in the
|
||||
boot process to avoid problems loading or restoring any programs that
|
||||
need access to your Nix store
|
||||
|
||||
# Installing a pinned Nix version from a URL
|
||||
|
||||
|
|
|
@ -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}"
|
||||
|
||||
# i.e., "disk1"
|
||||
_sudo() {
|
||||
shift # throw away the 'explanation'
|
||||
/usr/bin/sudo "$@"
|
||||
}
|
||||
failure() {
|
||||
if [ "$*" = "" ]; then
|
||||
cat
|
||||
else
|
||||
echo "$@"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
task() {
|
||||
echo "$@"
|
||||
}
|
||||
fi
|
||||
|
||||
# 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]*}"
|
||||
}
|
||||
|
||||
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
|
||||
# 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
|
||||
}
|
||||
|
||||
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,570 @@ create_synthetic_objects(){
|
|||
}
|
||||
|
||||
test_nix() {
|
||||
test -d "/nix"
|
||||
test -d "$NIX_ROOT"
|
||||
}
|
||||
|
||||
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_voldaemon() {
|
||||
test -f "$NIX_VOLUME_MOUNTD_DEST"
|
||||
}
|
||||
|
||||
test_filevault_in_use() {
|
||||
fdesetup isactive >/dev/null
|
||||
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[@]}"
|
||||
}
|
||||
|
||||
# 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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
_eat_bootout_err() {
|
||||
/usr/bin/grep -v "Boot-out failed: 36: Operation now in progress"
|
||||
}
|
||||
|
||||
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
|
||||
# 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
|
||||
|
||||
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
|
||||
# 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
|
||||
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"
|
||||
}
|
||||
|
||||
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
|
||||
# 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
|
||||
fi
|
||||
|
||||
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
|
||||
row "encrypted" "no"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
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
|
||||
task "Configuring /etc/fstab to specify volume mount options" >&2
|
||||
add_nix_vol_fstab_line "$volume_uuid" /usr/sbin/vifs
|
||||
fi
|
||||
}
|
||||
|
||||
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
|
||||
/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
|
||||
# DOING: I'm tempted to wrap this call in a grep to get the new disk special without doing anything too complex, but this sudo wrapper *is* a little complex, so it'll be a PITA unless maybe we can skip sudo on this. Let's just try it without.
|
||||
|
||||
use_special="${NIX_VOLUME_USE_SPECIAL:-$(create_volume)}"
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
|
|
|
@ -3,59 +3,99 @@
|
|||
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"
|
||||
|
||||
dsclattr() {
|
||||
/usr/bin/dscl . -read "$1" \
|
||||
| awk "/$2/ { print \$2 }"
|
||||
# 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: /: 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)
|
||||
[[ "$(/usr/bin/touch / 2>&1)" = *"Read-only file system" ]]
|
||||
|
||||
# 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])" -
|
||||
}
|
||||
|
||||
poly_validate_assumptions() {
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
failure "This script is for use with macOS!"
|
||||
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" \
|
||||
| /usr/bin/awk "/$2/ { print \$2 }"
|
||||
}
|
||||
|
||||
test_nix_daemon_installed() {
|
||||
test -e "$NIX_DAEMON_DEST"
|
||||
}
|
||||
|
||||
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
|
||||
darwin_volume_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 +136,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 +163,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 +193,17 @@ 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
|
||||
}
|
||||
|
|
|
@ -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,18 @@ headless() {
|
|||
fi
|
||||
}
|
||||
|
||||
contactme() {
|
||||
contact_us() {
|
||||
echo "You can open an issue at https://github.com/nixos/nix/issues"
|
||||
echo ""
|
||||
echo "Or feel free to contact the team:"
|
||||
echo " - IRC: in #nixos on irc.freenode.net"
|
||||
echo " - twitter: @nixos_org"
|
||||
echo " - forum: https://discourse.nixos.org"
|
||||
}
|
||||
get_help() {
|
||||
echo "We'd love to help if you need it."
|
||||
echo ""
|
||||
echo "If you can, open an issue at https://github.com/nixos/nix/issues"
|
||||
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 +106,6 @@ $step. Delete the files Nix added to your system:
|
|||
and that is it.
|
||||
|
||||
EOF
|
||||
|
||||
}
|
||||
|
||||
nix_user_for_core() {
|
||||
|
@ -170,7 +173,7 @@ failure() {
|
|||
header "oh no!"
|
||||
_textout "$RED" "$@"
|
||||
echo ""
|
||||
_textout "$RED" "$(contactme)"
|
||||
_textout "$RED" "$(get_help)"
|
||||
trap finish_cleanup EXIT
|
||||
exit 1
|
||||
}
|
||||
|
@ -201,6 +204,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 +313,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 +336,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)
|
||||
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!"
|
||||
|
||||
cat <<EOF
|
||||
If you spot anything this uninstaller missed or have feedback,
|
||||
don't hesitate:
|
||||
|
||||
$(contact_us)
|
||||
EOF
|
||||
remind
|
||||
finish_cleanup
|
||||
}
|
||||
|
||||
remove_nix_artifacts() {
|
||||
failure "Not implemented yet"
|
||||
}
|
||||
|
||||
cure_artifacts() {
|
||||
poly_cure_artifacts
|
||||
# remove_nix_artifacts (LATER)
|
||||
}
|
||||
|
||||
validate_starting_assumptions() {
|
||||
poly_validate_assumptions
|
||||
|
||||
if [ $EUID -eq 0 ]; then
|
||||
failure <<EOF
|
||||
Please do not run this script with root privileges. We will call sudo
|
||||
when we need to.
|
||||
EOF
|
||||
fi
|
||||
|
||||
if type nix-env 2> /dev/null >&2; then
|
||||
warning <<EOF
|
||||
Nix already appears to be installed. This installer may run into issues.
|
||||
|
@ -444,18 +537,46 @@ create_build_users() {
|
|||
|
||||
create_directories() {
|
||||
# FIXME: remove all of this because it duplicates LocalStore::LocalStore().
|
||||
task "Setting up the basic directory structure"
|
||||
if [ -d "$NIX_ROOT" ]; then
|
||||
# if /nix already exists, take ownership
|
||||
#
|
||||
# Caution: notes below are macOS-y
|
||||
# This is a bit of a goldilocks zone for taking ownership
|
||||
# if there are already files on the volume; the volume is
|
||||
# now mounted, but we haven't added a bunch of new files
|
||||
|
||||
# this is probably a bit slow; I've been seeing 3.3-4s even
|
||||
# when promptly installed over a fresh single-user install.
|
||||
# In case anyone's aware of a shortcut.
|
||||
# `|| true`: .Trashes errors w/o full disk perm
|
||||
|
||||
# rumor per #4488 that macOS 11.2 may not have
|
||||
# sbin on path, and that's where chown is, but
|
||||
# since this bit is cross-platform:
|
||||
# - first try with `command -vp` to try and find
|
||||
# chown in the usual places
|
||||
# - fall back on `command -v` which would find
|
||||
# any chown on path
|
||||
# if we don't find one, the command is already
|
||||
# hiding behind || true, and the general state
|
||||
# should be one the user can repair once they
|
||||
# figure out where chown is...
|
||||
local get_chr_own="$(command -vp chown)"
|
||||
if [[ -z "$get_chr_own" ]]; then
|
||||
get_chr_own="$(command -v chown)"
|
||||
fi
|
||||
_sudo "to take root ownership of existing Nix store files" \
|
||||
"$get_chr_own" -R "root:$NIX_BUILD_GROUP_NAME" "$NIX_ROOT" || true
|
||||
fi
|
||||
_sudo "to make the basic directory structure of Nix (part 1)" \
|
||||
mkdir -pv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool} /nix/var/nix/{gcroots,profiles}/per-user
|
||||
install -dv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool} /nix/var/nix/{gcroots,profiles}/per-user
|
||||
|
||||
_sudo "to make the basic directory structure of Nix (part 2)" \
|
||||
mkdir -pv -m 1775 /nix/store
|
||||
|
||||
_sudo "to make the basic directory structure of Nix (part 3)" \
|
||||
chgrp "$NIX_BUILD_GROUP_NAME" /nix/store
|
||||
install -dv -g "$NIX_BUILD_GROUP_NAME" -m 1775 /nix/store
|
||||
|
||||
_sudo "to place the default nix daemon configuration (part 1)" \
|
||||
mkdir -pv -m 0555 /etc/nix
|
||||
install -dv -m 0555 /etc/nix
|
||||
}
|
||||
|
||||
place_channel_configuration() {
|
||||
|
@ -475,7 +596,7 @@ This installation tool will set up your computer with the Nix package
|
|||
manager. This will happen in a few stages:
|
||||
|
||||
1. Make sure your computer doesn't already have Nix. If it does, I
|
||||
will show you instructions on how to clean up your old one.
|
||||
will show you instructions on how to clean up your old install.
|
||||
|
||||
2. Show you what we are going to install and where. Then we will ask
|
||||
if you are ready to continue.
|
||||
|
@ -574,6 +695,7 @@ EOF
|
|||
}
|
||||
|
||||
install_from_extracted_nix() {
|
||||
task "Installing Nix"
|
||||
(
|
||||
cd "$EXTRACTED_NIX_PATH"
|
||||
|
||||
|
@ -589,9 +711,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 +731,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 +751,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 +780,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 +801,17 @@ EOF
|
|||
}
|
||||
|
||||
main() {
|
||||
# TODO: I've moved this out of validate_starting_assumptions so we
|
||||
# can fail faster in this case. Sourcing install-darwin... now runs
|
||||
# `touch /` to detect Read-only root, but it could update times on
|
||||
# pre-Catalina macOS if run as root user.
|
||||
if [ $EUID -eq 0 ]; then
|
||||
failure <<EOF
|
||||
Please do not run this script with root privileges. We will call sudo
|
||||
when we need to.
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
# shellcheck source=./install-darwin-multi-user.sh
|
||||
. "$EXTRACTED_NIX_PATH/install-darwin-multi-user.sh"
|
||||
|
@ -675,17 +825,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 +852,7 @@ main() {
|
|||
configure_shell_profile
|
||||
|
||||
set +eu
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/profile
|
||||
set -eu
|
||||
|
||||
|
@ -706,5 +864,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
|
||||
|
|
|
@ -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 ""
|
||||
|
@ -101,45 +116,16 @@ while [ $# -gt 0 ]; do
|
|||
if [ -n "${INVOKED_FROM_INSTALL_IN:-}" ]; then
|
||||
echo " --tarball-url-prefix URL: Base URL to download the Nix tarball from."
|
||||
fi
|
||||
) >&2
|
||||
} >&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
|
||||
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
|
||||
|
||||
|
@ -194,6 +180,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
|
||||
|
|
|
@ -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() {
|
||||
:
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue