1#! @runtimeShell@
2# shellcheck shell=bash
3
4set -e
5shopt -s nullglob
6
7export PATH=@path@:$PATH
8
9# Ensure a consistent umask.
10umask 0022
11
12# Parse the command line for the -I flag
13extraBuildFlags=()
14flakeFlags=()
15
16mountPoint=/mnt
17channelPath=
18system=
19verbosity=()
20
21while [ "$#" -gt 0 ]; do
22 i="$1"; shift 1
23 case "$i" in
24 --max-jobs|-j|--cores|-I|--substituters)
25 j="$1"; shift 1
26 extraBuildFlags+=("$i" "$j")
27 ;;
28 --option)
29 j="$1"; shift 1
30 k="$1"; shift 1
31 extraBuildFlags+=("$i" "$j" "$k")
32 ;;
33 --root)
34 mountPoint="$1"; shift 1
35 ;;
36 --system|--closure)
37 system="$1"; shift 1
38 ;;
39 --flake)
40 flake="$1"
41 flakeFlags=(--experimental-features 'nix-command flakes')
42 shift 1
43 ;;
44 --recreate-lock-file|--no-update-lock-file|--no-write-lock-file|--no-registries|--commit-lock-file)
45 lockFlags+=("$i")
46 ;;
47 --update-input)
48 j="$1"; shift 1
49 lockFlags+=("$i" "$j")
50 ;;
51 --override-input)
52 j="$1"; shift 1
53 k="$1"; shift 1
54 lockFlags+=("$i" "$j" "$k")
55 ;;
56 --channel)
57 channelPath="$1"; shift 1
58 ;;
59 --no-channel-copy)
60 noChannelCopy=1
61 ;;
62 --no-root-password|--no-root-passwd)
63 noRootPasswd=1
64 ;;
65 --no-bootloader)
66 noBootLoader=1
67 ;;
68 --show-trace|--impure|--keep-going)
69 extraBuildFlags+=("$i")
70 ;;
71 --help)
72 exec man nixos-install
73 exit 1
74 ;;
75 --debug)
76 set -x
77 ;;
78 -v*|--verbose)
79 verbosity+=("$i")
80 ;;
81 *)
82 echo "$0: unknown option \`$i'"
83 exit 1
84 ;;
85 esac
86done
87
88if ! test -e "$mountPoint"; then
89 echo "mount point $mountPoint doesn't exist"
90 exit 1
91fi
92
93# Verify permissions are okay-enough
94checkPath="$(realpath "$mountPoint")"
95while [[ "$checkPath" != "/" ]]; do
96 mode="$(stat -c '%a' "$checkPath")"
97 if [[ "${mode: -1}" -lt "5" ]]; then
98 echo "path $checkPath should have permissions 755, but had permissions $mode. Consider running 'chmod o+rx $checkPath'."
99 exit 1
100 fi
101 checkPath="$(dirname "$checkPath")"
102done
103
104# Get the path of the NixOS configuration file.
105if [[ -z $NIXOS_CONFIG ]]; then
106 NIXOS_CONFIG=$mountPoint/etc/nixos/configuration.nix
107fi
108
109if [[ ${NIXOS_CONFIG:0:1} != / ]]; then
110 echo "$0: \$NIXOS_CONFIG is not an absolute path"
111 exit 1
112fi
113
114if [[ -n $flake ]]; then
115 if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then
116 flake="${BASH_REMATCH[1]}"
117 flakeAttr="${BASH_REMATCH[2]}"
118 fi
119 if [[ -z "$flakeAttr" ]]; then
120 echo "Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri."
121 echo "For example, to use the output nixosConfigurations.foo from the flake.nix, append \"#foo\" to the flake-uri."
122 exit 1
123 fi
124 flakeAttr="nixosConfigurations.\"$flakeAttr\""
125fi
126
127# Resolve the flake.
128if [[ -n $flake ]]; then
129 flake=$(nix "${flakeFlags[@]}" flake metadata --json "${extraBuildFlags[@]}" "${lockFlags[@]}" -- "$flake" | jq -r .url)
130fi
131
132if [[ ! -e $NIXOS_CONFIG && -z $system && -z $flake ]]; then
133 echo "configuration file $NIXOS_CONFIG doesn't exist"
134 exit 1
135fi
136
137# A place to drop temporary stuff.
138tmpdir="$(mktemp -d -p "$mountPoint")"
139trap 'rm -rf $tmpdir' EXIT
140
141# store temporary files on target filesystem by default
142export TMPDIR=${TMPDIR:-$tmpdir}
143
144sub="auto?trusted=1"
145
146# Copy the NixOS/Nixpkgs sources to the target as the initial contents
147# of the NixOS channel.
148if [[ -z $noChannelCopy ]]; then
149 if [[ -z $channelPath ]]; then
150 channelPath="$(nix-env -p /nix/var/nix/profiles/per-user/root/channels -q nixos --no-name --out-path 2>/dev/null || echo -n "")"
151 fi
152 if [[ -n $channelPath ]]; then
153 echo "copying channel..."
154 mkdir -p "$mountPoint"/nix/var/nix/profiles/per-user/root
155 nix-env --store "$mountPoint" "${extraBuildFlags[@]}" --extra-substituters "$sub" \
156 -p "$mountPoint"/nix/var/nix/profiles/per-user/root/channels --set "$channelPath" --quiet \
157 "${verbosity[@]}"
158 install -m 0700 -d "$mountPoint"/root/.nix-defexpr
159 ln -sfn /nix/var/nix/profiles/per-user/root/channels "$mountPoint"/root/.nix-defexpr/channels
160 fi
161fi
162
163# Build the system configuration in the target filesystem.
164if [[ -z $system ]]; then
165 outLink="$tmpdir/system"
166 if [[ -z $flake ]]; then
167 echo "building the configuration in $NIXOS_CONFIG..."
168 nix-build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \
169 --extra-substituters "$sub" \
170 '<nixpkgs/nixos>' -A system -I "nixos-config=$NIXOS_CONFIG" "${verbosity[@]}"
171 else
172 echo "building the flake in $flake..."
173 nix "${flakeFlags[@]}" build "$flake#$flakeAttr.config.system.build.toplevel" \
174 --store "$mountPoint" --extra-substituters "$sub" "${verbosity[@]}" \
175 "${extraBuildFlags[@]}" "${lockFlags[@]}" --out-link "$outLink"
176 fi
177 system=$(readlink -f "$outLink")
178fi
179
180# Set the system profile to point to the configuration. TODO: combine
181# this with the previous step once we have a nix-env replacement with
182# a progress bar.
183nix-env --store "$mountPoint" "${extraBuildFlags[@]}" \
184 --extra-substituters "$sub" \
185 -p "$mountPoint"/nix/var/nix/profiles/system --set "$system" "${verbosity[@]}"
186
187# Mark the target as a NixOS installation, otherwise switch-to-configuration will chicken out.
188mkdir -m 0755 -p "$mountPoint/etc"
189touch "$mountPoint/etc/NIXOS"
190
191# Switch to the new system configuration. This will install Grub with
192# a menu default pointing at the kernel/initrd/etc of the new
193# configuration.
194if [[ -z $noBootLoader ]]; then
195 echo "installing the boot loader..."
196 # Grub needs an mtab.
197 ln -sfn /proc/mounts "$mountPoint"/etc/mtab
198 export mountPoint
199 NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root "$mountPoint" -c "$(cat <<'EOF'
200 # Create a bind mount for each of the mount points inside the target file
201 # system. This preserves the validity of their absolute paths after changing
202 # the root with `nixos-enter`.
203 # Without this the bootloader installation may fail due to options that
204 # contain paths referenced during evaluation, like initrd.secrets.
205 # when not root, re-execute the script in an unshared namespace
206 mount --rbind --mkdir / "$mountPoint"
207 mount --make-rslave "$mountPoint"
208 /run/current-system/bin/switch-to-configuration boot
209 umount -R "$mountPoint" && rmdir "$mountPoint"
210EOF
211)"
212fi
213
214# Ask the user to set a root password, but only if the passwd command
215# exists (i.e. when mutable user accounts are enabled).
216if [[ -z $noRootPasswd ]] && [ -t 0 ]; then
217 if nixos-enter --root "$mountPoint" -c 'test -e /nix/var/nix/profiles/system/sw/bin/passwd'; then
218 set +e
219 nixos-enter --root "$mountPoint" -c 'echo "setting root password..." && /nix/var/nix/profiles/system/sw/bin/passwd'
220 exit_code=$?
221 set -e
222
223 if [[ $exit_code != 0 ]]; then
224 echo "Setting a root password failed with the above printed error."
225 echo "You can set the root password manually by executing \`nixos-enter --root ${mountPoint@Q}\` and then running \`passwd\` in the shell of the new system."
226 exit $exit_code
227 fi
228 fi
229fi
230
231echo "installation finished!"