Skip to content
60 changes: 52 additions & 8 deletions src/nixos-anywhere.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1036,16 +1036,60 @@ main() {
sshConnection="root@${sshHost}"
fi

# Get substituters from the machine and add them to the installer
if [[ ${machineSubstituters} == "y" && -n ${flake} ]]; then
substituters=$(nix --extra-experimental-features 'nix-command flakes' eval --apply toString "${flake}"#"${flakeAttr}".nix.settings.substituters)
trustedPublicKeys=$(nix --extra-experimental-features 'nix-command flakes' eval --apply toString "${flake}"#"${flakeAttr}".nix.settings.trusted-public-keys)

runSsh sh <<SSH || true
if [[ -n ${flake} ]]; then
system_features=$(runSshNoTty -o ConnectTimeout=10 nix --extra-experimental-features 'nix-command' config show system-features)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the list of current system-features important? Should this not be overruled by what the user has specified in the machine?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes you may not want to explicitly provide system-features and only want to configure gcc.arch, in that case the default target platform feature set needs to append gccarch-${cpu-arch} to it. If you don't do this and only set the gccarch feature, it will override (not append) the whole default features and the build will fail.

# Escape the bash variable for safe interpolation into Nix
system_features="$(printf '%s' "$system_features" | sed 's/\\/\\\\/g; s/"/\\"/g')"
# First, try to evaluate all nix settings from the flake in one go
Comment on lines +1039 to +1043
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use consistent variable naming and add error handling for remote SSH command.

Two issues:

  1. Naming inconsistency: system_features uses snake_case, while the rest of the script uses camelCase (e.g., sshConnection, flakeAttr, nixConfContent).

  2. Missing error handling: Line 1040 will cause the script to abort under set -euo pipefail if the SSH command fails (e.g., network issue, nix not found on remote).

Apply this diff:

   if [[ -n ${flake} ]]; then
-    system_features=$(runSshNoTty -o ConnectTimeout=10 nix --extra-experimental-features 'nix-command' config show system-features)
+    systemFeatures=$(runSshNoTty -o ConnectTimeout=10 nix --extra-experimental-features 'nix-command' config show system-features 2>/dev/null || echo "")
     # Escape the bash variable for safe interpolation into Nix
-    system_features="$(printf '%s' "$system_features" | sed 's/\\/\\\\/g; s/"/\\"/g')"
+    escapedSystemFeatures="$(printf '%s' "$systemFeatures" | sed 's/\\/\\\\/g; s/"/\\"/g')"

Then update line 1055 to use the renamed variable:

         remoteFeatures = let
-            remoteFeaturesStr = \"${system_features}\";
+            remoteFeaturesStr = \"${escapedSystemFeatures}\";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [[ -n ${flake} ]]; then
system_features=$(runSshNoTty -o ConnectTimeout=10 nix --extra-experimental-features 'nix-command' config show system-features)
# Escape the bash variable for safe interpolation into Nix
system_features="$(printf '%s' "$system_features" | sed 's/\\/\\\\/g; s/"/\\"/g')"
# First, try to evaluate all nix settings from the flake in one go
if [[ -n ${flake} ]]; then
# Fetch remote system-features, but never fail under set -euo pipefail
systemFeatures=$(runSshNoTty -o ConnectTimeout=10 \
nix --extra-experimental-features 'nix-command' config show system-features \
2>/dev/null || echo "")
# Escape the bash variable for safe interpolation into Nix
escapedSystemFeatures="$(printf '%s' "$systemFeatures" \
| sed 's/\\/\\\\/g; s/"/\\"/g')"
# First, try to evaluate all nix settings from the flake in one go
nixConfContent=$(
nix --extra-experimental-features 'nix-command flakes' eval --raw --apply "
config:
let
settings = config.nix.settings or {};
gccArch = config.nixpkgs.hostPlatform.gcc.arch or null;
# Check if system-features are defined in configuration
configFeatures = settings.system-features or null;
hasConfig = configFeatures != null && configFeatures != [];
# Fallback to the escaped remote features if none in the flake
remoteFeatures = let
remoteFeaturesStr = \"${escapedSystemFeatures}\";
remoteFeaturesList = if remoteFeaturesStr != \"\" then
builtins.filter (x: builtins.isString x && x != \"\")
(builtins.split \" +\" remoteFeaturesStr)
else
[];
in remoteFeaturesList;
in
# … rest of the nixConfContent construction …
" "${flake}#${flakeAttr}"
)
🤖 Prompt for AI Agents
In src/nixos-anywhere.sh around lines 1039-1043, rename the snake_case variable
system_features to camelCase systemFeatures for consistency with
sshConnection/flakeAttr/nixConfContent, and preserve the escaping step for
quotes/backslashes; additionally, run the remote SSH command with explicit error
handling: capture both its stdout and exit code, and if the SSH command fails
log a clear error including the exit code/output and exit non-zero to avoid an
unhelpful abrupt failure under set -euo pipefail. After renaming, update the
later usage on line 1055 to reference systemFeatures.

nixConfContent=$(nix --extra-experimental-features 'nix-command flakes' eval --raw --apply "
config:
let
settings = config.nix.settings or {};
gccArch = config.nixpkgs.hostPlatform.gcc.arch or null;

# Check if system-features are defined in configuration
configFeatures = settings.system-features or null;
hasConfigFeatures = configFeatures != null && configFeatures != [];

remoteFeatures = let
remoteFeaturesStr = \"${system_features}\";
# Parse remote features string (space-separated) into list
remoteFeaturesList = if remoteFeaturesStr != \"\" then
builtins.filter (x: builtins.isString x && x != \"\") (builtins.split \" +\" remoteFeaturesStr)
else [];
in remoteFeaturesList;

# Combine base features (config or remote) with platform-specific features
baseFeatures = if hasConfigFeatures then configFeatures else remoteFeatures;
# At least one of nix.settings.system-features or nixpkgs.hostPlatform.gcc.arch has been explicitly defined
allFeatures = if (gccArch != null) || hasConfigFeatures then baseFeatures ++ (if gccArch != null then [\"gccarch-\${gccArch}\"] else []) else [];

# Deduplicate using listToAttrs trick
uniqueFeatures = builtins.attrNames (builtins.listToAttrs (map (f: { name = f; value = true; }) allFeatures));

substituters = builtins.toString (settings.substituters or []);
trustedPublicKeys = builtins.toString (settings.trusted-public-keys or []);
systemFeatures = builtins.toString uniqueFeatures;

# Helper function for optional config lines
optionalLine = cond: line: if cond then line + \"\n\" else \"\";
useSubstituters = \"${machineSubstituters}\" == \"y\";
in
optionalLine (useSubstituters && substituters != \"\") \"extra-substituters = \${substituters}\"
+ optionalLine (useSubstituters && trustedPublicKeys != \"\") \"extra-trusted-public-keys = \${trustedPublicKeys}\"
+ optionalLine (systemFeatures != \"\") \"system-features = \${systemFeatures}\"
" "${flake}#${flakeAttr}")

# Write to nix.conf if we have any content
if [[ -n ${nixConfContent} ]]; then
runSsh sh <<SSH
mkdir -p ~/.config/nix
echo "extra-substituters = ${substituters}" >> ~/.config/nix/nix.conf
echo "extra-trusted-public-keys = ${trustedPublicKeys}" >> ~/.config/nix/nix.conf
printf '%s\n' "\$(cat <<'CONTENT'
${nixConfContent}
CONTENT
)" >> ~/.config/nix/nix.conf
SSH
fi
Comment on lines +1083 to +1092
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for nix.conf write operation.

The SSH command that writes to ~/.config/nix/nix.conf has no error handling. Failures (SSH connection issues, permission problems, disk full) will be silently ignored, leaving the system in an inconsistent state.

Apply this diff:

     # Write to nix.conf if we have any content
     if [[ -n ${nixConfContent} ]]; then
-      runSsh sh <<SSH
+      if ! runSsh sh <<SSH; then
+        echo "Warning: Failed to write nix.conf in installer environment" >&2
+      fi
 mkdir -p ~/.config/nix
 printf '%s\n' "\$(cat <<'CONTENT'
 ${nixConfContent}
 CONTENT
 )" >> ~/.config/nix/nix.conf
 SSH
     fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Write to nix.conf if we have any content
if [[ -n ${nixConfContent} ]]; then
runSsh sh <<SSH
mkdir -p ~/.config/nix
echo "extra-substituters = ${substituters}" >> ~/.config/nix/nix.conf
echo "extra-trusted-public-keys = ${trustedPublicKeys}" >> ~/.config/nix/nix.conf
printf '%s\n' "\$(cat <<'CONTENT'
${nixConfContent}
CONTENT
)" >> ~/.config/nix/nix.conf
SSH
fi
# Write to nix.conf if we have any content
if [[ -n ${nixConfContent} ]]; then
if ! runSsh sh <<SSH; then
echo "Warning: Failed to write nix.conf in installer environment" >&2
fi
mkdir -p ~/.config/nix
printf '%s\n' "\$(cat <<'CONTENT'
${nixConfContent}
CONTENT
)" >> ~/.config/nix/nix.conf
SSH
fi

fi

if [[ ${phases[disko]} == 1 ]]; then
Expand Down
Loading