feat: create self-contained activation command#94
feat: create self-contained activation command#94water-sucks merged 34 commits intonix-community:mainfrom
Conversation
c4aabeb to
ed4805e
Compare
b418b18 to
12ec38c
Compare
dc48ec7 to
97c8aa8
Compare
5f4d7ce to
f007657
Compare
84d1d44 to
036b860
Compare
WalkthroughAdds a new "nixos activate" command and Linux-only activation implementation: CLI wiring and option parsing, activation runtime (locking, pre-checks, bootloader, interface validation), unit/file parsing and classification, user re-exec path, docs, module wiring, and dependency updates. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant CLI as "nixos activate"
participant Env as Env/Validation
participant Lock as Activation Lock
participant DBus as systemd/logind (DBus)
participant Units as Unit Manager
participant Script as Activation Scripts
U->>CLI: run `nixos activate ACTION`
CLI->>Env: parse ACTION & required vars
Env->>Lock: acquire activation lock
alt unsupported (non-Linux)
CLI-->>U: error (unsupported)
else supported (Linux)
Lock-->>CLI: lock acquired
CLI->>DBus: init system DBus
CLI->>Script: run pre-switch checks & install bootloader
alt dry/check mode
CLI->>Units: classify units (read-only)
Units-->>CLI: planned actions
CLI-->>U: display plan
else real switch
CLI->>Units: classify modified & resolve templated units
Units-->>CLI: action lists
CLI->>DBus: stop/reload/restart/start units
DBus-->>CLI: action results
CLI->>Script: run activation scripts (post-activation)
CLI-->>U: activation result
end
CLI->>Lock: release lock
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
[pre_merge_check_pass] The PR comprehensively addresses all coding-related requirements from issue #55. It implements the new ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
036b860 to
a33d5d4
Compare
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
internal/activation/activation.go (3)
51-56: Simplify MkdirAll error handling.os.MkdirAll never returns os.ErrExist; the nested check is misleading. Return errors directly.
- err := os.MkdirAll(constants.NixSystemProfileDirectory, 0o755) - if err != nil { - if err != os.ErrExist { - return fmt.Errorf("failed to create nix system profile directory: %w", err) - } - } + if err := os.MkdirAll(constants.NixSystemProfileDirectory, 0o755); err != nil { + return fmt.Errorf("failed to create nix system profile directory: %w", err) + }
61-68: Likely inverted condition for ensuring the system profile directory.You call EnsureSystemProfileDirectoryExists() when profile != "system". This seems backwards.
- if profile != "system" { + if profile == "system" { err := EnsureSystemProfileDirectoryExists() if err != nil { return err } }Apply in both AddNewNixProfile and SetNixProfileGeneration.
Also applies to: 84-91
119-129: Avoid panic on unexpected generation link format.Return an error instead of panicking to prevent crashing callers.
- } else { - panic("current link format does not match 'profile-generation-link' format") - } + } + return 0, fmt.Errorf("current link format does not match 'profile-generation-link' format")
🧹 Nitpick comments (17)
internal/cmd/utils/utils.go (1)
48-68: Consider using strings.Builder for better performance.The current implementation concatenates strings in a loop (line 64), which creates multiple string allocations. While this is acceptable for small option maps (help text), using
strings.Builderwould be more efficient.Apply this diff to improve performance:
func AlignedOptions(options map[string]string) string { maxLen := 0 for cmd := range options { if len(cmd) > maxLen { maxLen = len(cmd) } } - result := "" + var result strings.Builder format := fmt.Sprintf(" %%-%ds %%s\n", maxLen) keys := slices.Collect(maps.Keys(options)) sort.Strings(keys) for _, cmd := range keys { desc := options[cmd] - result += fmt.Sprintf(format, cmd, desc) + result.WriteString(fmt.Sprintf(format, cmd, desc)) } - return result + return result.String() }cmd/activate/user.go (1)
15-39: Consider attaching stdin to the child process.Lines 35-36 attach
StdoutandStderr, butStdinis not attached. If the re-executed process needs to read from stdin (e.g., for confirmation prompts), it will fail.Apply this diff to attach stdin:
cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin return cmd.Run()cmd/activate/fs.go (1)
29-74: Consider logging malformed fstab entries.The
parseFstabfunction silently skips malformed lines (lines with fewer than 3 fields). While this is lenient and prevents activation failures due to minor fstab issues, it could also hide configuration errors that users should be aware of.Consider adding optional debug logging for skipped lines:
fields := strings.Fields(line) if len(fields) < 3 { + // Optionally log: "skipping malformed fstab line: %s", line continue }This would help users diagnose why expected filesystems or swap devices aren't being processed, especially during troubleshooting.
cmd/activate/activate.go (2)
28-45: Tighten arg validation (reject extra positional args).Currently only checks for presence of ACTION. Enforce exactly one positional arg when not under parent exe to avoid accepting unintended args.
Apply:
- Args: func(cmd *cobra.Command, args []string) error { + Args: func(cmd *cobra.Command, args []string) error { if os.Getenv(NIXOS_STC_PARENT_EXE) != "" { return nil } - if len(args) == 0 { + if len(args) == 0 { return fmt.Errorf("missing required argument <ACTION>") } + if len(args) > 1 { + return fmt.Errorf("too many arguments; expected exactly one <ACTION>") + } a, err := activation.ParseSwitchToConfigurationAction(args[0]) if err != nil { return err } opts.Action = a return nil },
16-22: Deterministic help ordering for actions.Map iteration is random; help/completion order will vary. Sort keys before assembling results and AlignedOptions.
- results := make([]string, 0, len(commands)) - for command, desc := range commands { - results = append(results, fmt.Sprintf("%v\t%v", command, desc)) - } + keys := make([]string, 0, len(commands)) + for k := range commands { keys = append(keys, k) } + sort.Strings(keys) + results := make([]string, 0, len(keys)) + for _, command := range keys { + results = append(results, fmt.Sprintf("%v\t%v", command, commands[command])) + }- cmd.SetHelpTemplate(cmd.HelpTemplate() + "\nActions:\n" + cmdUtils.AlignedOptions(commands)) + cmd.SetHelpTemplate(cmd.HelpTemplate() + "\nActions:\n" + cmdUtils.AlignedOptions(cmdUtils.SortOptions(commands)))(Assumes a SortOptions helper; otherwise sort locally before formatting.)
Also applies to: 47-53, 63-65
internal/activation/activation.go (1)
159-174: Prefer returning "unknown" over panicking in String().Panics in String() make diagnostics harder. Consider returning "unknown".
- default: - panic("unknown switch to configuration action type") + default: + return "unknown"module.nix (2)
48-66: Polish option docs and line breaks.Minor grammar/formatting fixes; avoid breaking a sentence across lines “switchable. system.”.
- own, but it will serve solely as a shim to run the switch script on a switchable. - system. + own, but it will serve solely as a shim to run the switch script on a + switchable system.
144-154: Ensure wrapper works with paths containing spaces and missing vars.Even if unlikely in Nix store, quote $out and guard unset variables (like toplevelVar). Also consider exporting NIXOS_ACTION in wrapper for parity.
- mkdir $out/bin + mkdir -p "$out/bin" - ln -sf ${lib.getExe cfg.package} $out/bin/switch-to-configuration + ln -sf ${lib.getExe cfg.package} "$out/bin/switch-to-configuration" - wrapProgram $out/bin/switch-to-configuration \ + wrapProgram "$out/bin/switch-to-configuration" \ --add-flags activate \ --set NIXOS_CLI_ATTEMPTING_ACTIVATION 1 \ - --set OUT $out \ - --set TOPLEVEL ''${!toplevelVar} \ + --set OUT "$out" \ + --set TOPLEVEL ''${!toplevelVar:-} \cmd/activate/run.go (2)
171-186: Handle missing init-interface-version files gracefully.Older generations may not have this file. Treat missing files as mismatch (exit 100) without printing a read error, or warn and continue per intended compatibility.
- currentInitInterfaceVersion, err := os.ReadFile(currentInitInterfaceVersionFile) - if err != nil { - return fmt.Errorf("failed to read %s: %v", currentInitInterfaceVersionFile, err) - } + currentInitInterfaceVersion, err := os.ReadFile(currentInitInterfaceVersionFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return ErrMismatchedInterfaceVersion + } + return fmt.Errorf("failed to read %s: %w", currentInitInterfaceVersionFile, err) + } - newInitInterfaceVersion, err := os.ReadFile(newInitInterfaceVersionFile) - if err != nil { - return fmt.Errorf("failed to read %s: %v", newInitInterfaceVersionFile, err) - } + newInitInterfaceVersion, err := os.ReadFile(newInitInterfaceVersionFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return ErrMismatchedInterfaceVersion + } + return fmt.Errorf("failed to read %s: %w", newInitInterfaceVersionFile, err) + }And keep the os.Exit(100) branch as-is for a clear user-facing outcome.
Also applies to: 431-437
331-335: Nit: wording.“This command must be ran as root” → “run”.
- err := fmt.Errorf("this command must be ran as root") + err := fmt.Errorf("this command must be run as root")internal/systemd/unit.go (3)
125-133: Make ParseBool robust to common forms.Support 0/1 and mixed/upper case to match systemd’s lenient booleans.
-func ParseBool(value string) bool { - switch value { - case "1", "yes", "true", "on": - return true - default: - return false - } -} +func ParseBool(value string) bool { + v := strings.TrimSpace(strings.ToLower(value)) + switch v { + case "1", "yes", "true", "on": + return true + default: + return false + } +}(Remember to import "strings".)
20-58: Consider parsing override unit file itself (not only .d).When unitFilePath differs from baseUnitFilePath, you parse only its .d/*.conf and not the unitFilePath file. If that path can be a complete override file, include it.
if unitFilePath != baseUnitFilePath { - if _, err := os.Stat(unitFilePath); err == nil { + if _, err := os.Stat(unitFilePath); err == nil { + if err := parseAndMerge(unitFilePath); err != nil { return nil, err } matches, _ := filepath.Glob(fmt.Sprintf("%s.d/*.conf", unitFilePath)) for _, path := range matches { _ = parseAndMerge(path) } } }
60-82: Doc nit: complete the comment.Finish the “Consider using …” sentence or remove it to avoid confusion.
cmd/activate/unit.go (4)
192-200: Sorted() mutates case of unit names in output.Lowercasing before returning changes values. Sort case-insensitively but return originals.
Apply:
- keys := make([]string, 0, len(l)) - for unit := range l { - keys = append(keys, strings.ToLower(unit)) - } - sort.Strings(keys) - return keys + keys := make([]string, 0, len(l)) + for unit := range l { + keys = append(keys, unit) + } + sort.Slice(keys, func(i, j int) bool { + a, b := strings.ToLower(keys[i]), strings.ToLower(keys[j]) + if a == b { + return keys[i] < keys[j] + } + return a < b + }) + return keys
27-29: Template regex may reject valid instances with dots; prefer last-dot split for extension.Instances can contain dots; current
[^\.]*disallows them. Use a pattern that captures up to the last dot as the extension while allowing dots in the instance.Apply:
- templateUnitPattern = regexp.MustCompile(`^(.*)@([^\.]*)\.(.*)$`) + // <name>@<instance>.<ext> — allow dots in <instance>, ext has no dots + templateUnitPattern = regexp.MustCompile(`^([^@]+)@(.+)\.([^.]+)$`)
493-505: resolveUnit only falls back to base when both instance files are missing.If either current or new instance file exists but the other doesn’t, we still want base resolution for templated units. Otherwise CurrentBaseFile may point to a non-existent instance file and upstream checks will skip active templated units.
Apply:
- if matches := templateUnitPattern.FindStringSubmatch(unit); len(matches) > 3 { - templateName, _, unitType := matches[1], matches[2], matches[3] - - if _, err := os.Stat(currentUnitFile); os.IsNotExist(err) { - if _, err := os.Stat(newUnitFile); os.IsNotExist(err) { - baseUnit = fmt.Sprintf("%s@.%s", templateName, unitType) - currentBaseFile = filepath.Join("/etc/systemd/system", baseUnit) - newBaseFile = filepath.Join(toplevel, "etc/systemd/system", baseUnit) - } - } - } + if matches := templateUnitPattern.FindStringSubmatch(unit); len(matches) > 3 { + templateName, _, unitType := matches[1], matches[2], matches[3] + // Prefer base unit for templates unless both instance files are present. + if _, err := os.Stat(currentUnitFile); os.IsNotExist(err) { + baseUnit = fmt.Sprintf("%s@.%s", templateName, unitType) + } else if _, err := os.Stat(newUnitFile); os.IsNotExist(err) { + baseUnit = fmt.Sprintf("%s@.%s", templateName, unitType) + } + if baseUnit != unit { + currentBaseFile = filepath.Join("/etc/systemd/system", baseUnit) + newBaseFile = filepath.Join(toplevel, "etc/systemd/system", baseUnit) + } + }
554-569: Make list-file writes idempotent to avoid duplicate entries.recordUnit can append the same unit multiple times; unrecordUnit then rewrites the whole file. Consider de-duping on write for stability and smaller files.
Example approach:
- Keep an in-memory recorded set per file to skip duplicates; or
- Read once and append only if not present; or
- Write list files from the in-memory UnitList at the end of classification.
Also applies to: 595-596
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (22)
cmd/activate/activate.go(1 hunks)cmd/activate/fs.go(1 hunks)cmd/activate/run.go(1 hunks)cmd/activate/unit.go(1 hunks)cmd/activate/unsupported.go(1 hunks)cmd/activate/user.go(1 hunks)cmd/generation/delete/delete.go(2 hunks)cmd/generation/delete/resolver.go(2 hunks)cmd/root/root.go(2 hunks)doc/man/nixos-cli-activate.1.scd(1 hunks)doc/man/nixos-cli-env.5.scd(1 hunks)go.mod(4 hunks)internal/activation/activation.go(2 hunks)internal/cmd/opts/opts.go(1 hunks)internal/cmd/utils/utils.go(2 hunks)internal/system/local.go(2 hunks)internal/systemd/time.go(1 hunks)internal/systemd/time_test.go(1 hunks)internal/systemd/unit.go(1 hunks)internal/systemd/unit_test.go(1 hunks)module.nix(2 hunks)package.nix(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build/Test
🔇 Additional comments (18)
package.nix (2)
16-16: LGTM!The
vendorHashupdate aligns with the new dependencies added ingo.mod(coreos/go-systemd, google/shlex, etc.).
59-59: LGTM!Adding
mainProgrammetadata is appropriate for packages with a primary executable and helps Nix properly identify the main binary.internal/system/local.go (2)
50-50: LGTM!The regex pattern correctly matches the NixOS distro ID with or without quotes, which is appropriate for parsing
/etc/os-releasevalues.
52-69: LGTM!The improved NixOS detection logic correctly prioritizes the canonical
/etc/NIXOSmarker, then falls back to parsing/etc/os-release. Error handling is appropriate with graceful degradation.go.mod (3)
6-23: LGTM!The new dependencies align well with the activation command requirements:
carapace: Shell completion supportgo-systemd: Systemd integration for activationgoogle/shlex: Command-line parsingoptnix: Nix options handlinggolang.org/x/sys: Linux system calls
3-3: Original review comment is based on outdated information.Go 1.25.3 is the latest stable release as of October 20, 2025, making the
go 1.25specification in go.mod correct and current. The code change requires no modifications.
94-94: Verify the necessity and plan for the go-systemd fork.The review comment accurately identifies a legitimate concern. Verification confirms:
- The fork from
water-sucks/go-systemdis present in the codebase and actively used (ininternal/systemd/unit.go,cmd/activate/user.go,cmd/activate/unit.go,cmd/activate/run.go)- The upstream
coreos/go-systemdv22.6.0 was recently released on August 20, 2025- No documentation or inline comments in
go.modexplain why the fork is necessary- The fork uses a pseudo-version from October 14, 2025, newer than the upstream release
The fork introduces maintenance and security risks by depending on a custom fork instead of the actively maintained upstream. The original review comment's request for clarification is valid and should be addressed by explaining:
- Why the fork is necessary (what features or fixes are missing from upstream)
- The plan for contributing changes back upstream
- Any security considerations
cmd/generation/delete/resolver.go (1)
12-12: LGTM!The refactor from
timeUtilstosystemdUtilsis consistent with the package reorganization seen across the PR.Also applies to: 96-96
cmd/generation/delete/delete.go (1)
24-24: LGTM!The refactor from the previous time utilities package to
systemdUtilsis consistent with the package reorganization.Also applies to: 45-45
internal/systemd/time_test.go (1)
1-1: LGTM!The package rename to
systemdUtilsaligns with the refactored time utilities module.cmd/root/root.go (1)
18-18: LGTM! Clean integration of the new activate command.The import and command wiring follow the established patterns in this file and properly integrate the new activation functionality into the CLI.
Also applies to: 141-141
internal/cmd/opts/opts.go (1)
3-6: LGTM! Well-structured options type for the activate command.The new
ActivateOptsstruct follows the established pattern in this file and properly references theSwitchToConfigurationActiontype from the internal activation package.Also applies to: 13-16
cmd/activate/unsupported.go (1)
1-18: LGTM! Appropriate non-Linux fallback handler.The build tag ensures this only compiles on non-Linux systems, and the error message clearly communicates that activation is unsupported. The unused
actionparameter is correctly indicated with the blank identifier.doc/man/nixos-cli-activate.1.scd (1)
1-65: LGTM! Comprehensive and well-structured manual page.The documentation clearly explains the command's purpose, available actions, and options. The note about backwards compatibility with
switch-to-configuration-ngand the warning that this is a low-level command are helpful context for users.doc/man/nixos-cli-env.5.scd (1)
93-113: LGTM! Excellent documentation structure for development variables.Separating development-mode variables into their own section with a clear warning is a good practice. The documentation for the recursion-prevention variables is particularly helpful for maintainers.
internal/systemd/unit_test.go (1)
1-94: LGTM! Well-structured unit tests with good coverage.The table-driven test structure is idiomatic Go and provides clear coverage of the
CompareUnitsfunction's behavior across different scenarios. The test names are descriptive and the assertions provide helpful error messages.cmd/activate/fs.go (1)
80-91: LGTM! Proper syscall wrapper implementation.The
swapofffunction correctly wraps theSYS_SWAPOFFsyscall using the appropriateunsafe.Pointerconversion and error handling. This is the standard approach for syscalls not directly exposed by theunixpackage.internal/systemd/time.go (1)
1-1: Package rename verification passed.All references to the old package name have been successfully updated. The ripgrep searches confirmed:
- No stale references to the old
internal/systemd.*time\.pattern remain- No old function call syntax (
time.DurationFromTimeSpan) remains- The new package name is correctly used in all locations (
systemdUtils.DurationFromTimeSpanin 2 files)
fbc2e48 to
a7a9996
Compare
bf280c6 to
cb18de5
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (4)
internal/systemd/unit.go (1)
48-55: Critical: ParseUnit still ignores the instance file (unitFilePath).Only the base file and .d drop-ins are merged; the main unitFilePath is never parsed, so instance-level unit content is lost and comparisons can be wrong.
The code parses
baseUnitFilePath(line 39) and its drop-ins (lines 43-46), then parses drop-ins forunitFilePath(lines 50-52), but never parsesunitFilePathitself. This causes instance-specific unit settings to be silently ignored, leading to incorrect unit comparisons and potentially failed activations.Apply this diff to parse the instance file:
if unitFilePath != baseUnitFilePath { if _, err := os.Stat(unitFilePath); err == nil { + // Parse the instance file itself before its drop-ins + if err := parseAndMerge(unitFilePath); err != nil { + return nil, err + } matches, _ := filepath.Glob(fmt.Sprintf("%s.d/*.conf", unitFilePath)) for _, path := range matches { _ = parseAndMerge(path) } } }cmd/activate/activate.go (1)
28-31: Undefined identifier on non-Linux builds (use literal or move const).activate.go references NIXOS_STC_PARENT_EXE but it’s defined only in run.go with a linux build tag, causing compile errors on other platforms. Use the literal here or move the const to a non-OS‑specific file.
- if os.Getenv(NIXOS_STC_PARENT_EXE) != "" { + if os.Getenv("__NIXOS_SWITCH_TO_CONFIGURATION_PARENT_EXE") != "" { return nil }module.nix (1)
102-112: Scope sudo env_keep to the specific command; avoid global leakage.Defaults env_keep applies to all sudo sessions, expanding attack surface. Restrict to the nixos-cli/switch wrapper path via a Cmnd_Alias and Defaults!cmd form.
Example:
security.sudo.extraConfig = '' Cmnd_Alias NIXOS_SWITCH = ${lib.getExe cfg.package} *, ${lib.getExe cfg.package} activate * Defaults!${lib.getExe cfg.package} env_keep += "NIXOS_CONFIG NIXOS_GENERATION_TAG NIXOS_CLI_CONFIG NIXOS_CLI_DISABLE_STEPS NIXOS_CLI_DEBUG_MODE NIXOS_CLI_SUPPRESS_NO_SETTINGS_WARNING" '';Would you like a PR-ready Nix snippet tailored to your exact wrapper path?
cmd/activate/run.go (1)
117-121: Fix specialisation existence check and error.Formats wrong value and returns a possibly stale err. Return a new error that includes the requested specialisation.
- if !slices.Contains(specialisations, specialisation) { - err = fmt.Errorf("specialisation '%v' does not exist", specialisations) - log.Error(err) - return err - } + if !slices.Contains(specialisations, specialisation) { + return fmt.Errorf("specialisation defined in $NIXOS_SPECIALISATION %q does not exist", specialisation) + }Also guard the check behind err == nil to avoid false negatives when listing fails:
- if specialisation != "" { - specialisations, err := generation.CollectSpecialisations(constants.CurrentSystem) - if err != nil { - log.Warnf("unable to access specialisations: %v", err) - } - if !slices.Contains(specialisations, specialisation) { + if specialisation != "" { + specialisations, err := generation.CollectSpecialisations(constants.CurrentSystem) + if err == nil && !slices.Contains(specialisations, specialisation) { ... } }
🧹 Nitpick comments (7)
internal/systemd/unit.go (1)
126-133: Consider documenting boolean parsing semantics.The
ParseBoolfunction implements systemd's boolean parsing logic but doesn't document which values map to true vs false. This is important for maintainability since systemd has specific semantics.Add a function comment:
// Parse a systemd boolean value string into a Go boolean. +// Returns true for "1", "yes", "true", "on" (case-sensitive). +// All other values, including "0", "no", "false", "off", return false. func ParseBool(value string) bool {cmd/activate/user.go (2)
15-39: Consider validating UID/GID parameters.The
execUserSwitchProcessfunction acceptsuidandgidparameters but doesn't validate they are non-zero or represent valid system users. While the caller may be trusted, defensive validation can prevent subtle bugs.Consider adding basic validation:
func execUserSwitchProcess(uid uint32, gid uint32, runtimePath string) error { + if uid == 0 || gid == 0 { + return fmt.Errorf("uid (%d) and gid (%d) must be non-zero for user switch", uid, gid) + } + exe, err := filepath.EvalSymlinks("/proc/self/exe") if err != nil { return err }
64-68: Document why ReexecuteContext errors are ignored.The comment explains that the systemd user session doesn't send a Reloaded signal and that dbus session bus errors are expected, but it would be helpful to clarify under what conditions errors are expected and whether any errors should be propagated.
Consider enhancing the comment:
// The systemd user session seems to not send a Reloaded signal, // so we don't have anything to wait on here. // // Also, ignore errors, since the dbus session bus will probably -// return an error here due to it running in the user's context. +// return an error here due to it running in the user's context +// and not having sufficient privileges for certain operations. +// These errors are harmless and can be safely ignored. _ = systemd.ReexecuteContext(ctx)module.nix (1)
144-154: Minor polish: wrapProgram arg quoting and comment accuracy.
- Quote/escape SYSTEMD like others for consistency (paths are safe but consistency helps).
- The comment mentions “nixos apply” but this module wires “nixos activate”.
- --set SYSTEMD ${config.systemd.package} + --set SYSTEMD ${lib.escapeShellArg config.systemd.package}- # Preserve NIXOS_CONFIG and NIXOS_CLI_CONFIG in sudo invocations of - # `nixos apply`. This is required in order to keep ownership across + # Preserve NIXOS_CONFIG and NIXOS_CLI_CONFIG in sudo invocations of + # `nixos activate`. This is required in order to keep ownership acrossAlso applies to: 151-154
cmd/activate/run.go (1)
168-186: Be robust to newline/whitespace in init-interface-version files.Trim file contents before comparing to avoid false mismatches.
- if string(currentInitInterfaceVersion) != string(newInitInterfaceVersion) { + if strings.TrimSpace(string(currentInitInterfaceVersion)) != strings.TrimSpace(string(newInitInterfaceVersion)) { return ErrMismatchedInterfaceVersion }cmd/activate/unit.go (2)
192-200: Keep original unit names while sorting case-insensitively.Current Sorted() lowercases outputs. Sort by Lower but return original strings.
func (l UnitList) Sorted() []string { - keys := make([]string, 0, len(l)) - for unit := range l { - keys = append(keys, strings.ToLower(unit)) - } - sort.Strings(keys) - return keys + keys := make([]string, 0, len(l)) + for unit := range l { + keys = append(keys, unit) + } + sort.Slice(keys, func(i, j int) bool { + return strings.ToLower(keys[i]) < strings.ToLower(keys[j]) + }) + return keys }
450-459: Avoid panic on Variant type assertion.Be defensive when extracting ActiveState.
- state := prop.Value.Value().(string) - return state == "active" || state == "activating", nil + v, ok := prop.Value.Value().(string) + if !ok { + return false, fmt.Errorf("ActiveState is not a string") + } + return v == "active" || v == "activating", nil
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (17)
cmd/activate/activate.go(1 hunks)cmd/activate/fs.go(1 hunks)cmd/activate/run.go(1 hunks)cmd/activate/unit.go(1 hunks)cmd/activate/user.go(1 hunks)cmd/generation/delete/delete.go(2 hunks)cmd/generation/delete/resolver.go(2 hunks)doc/man/nixos-cli-activate.1.scd(1 hunks)doc/man/nixos-cli-env.5.scd(1 hunks)go.mod(4 hunks)internal/system/local.go(2 hunks)internal/systemd/time.go(1 hunks)internal/systemd/time_test.go(1 hunks)internal/systemd/unit.go(1 hunks)internal/systemd/unit_test.go(1 hunks)module.nix(2 hunks)package.nix(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- package.nix
- internal/systemd/unit_test.go
- internal/systemd/time_test.go
- internal/system/local.go
- cmd/activate/fs.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-20T02:59:26.104Z
Learnt from: water-sucks
PR: nix-community/nixos-cli#94
File: cmd/activate/run.go:245-281
Timestamp: 2025-10-20T02:59:26.104Z
Learning: Go 1.25 introduced the sync.WaitGroup.Go(f func()) method, which starts f in a new goroutine and automatically handles Add(1) and defer Done(). This is valid Go 1.25+ syntax and should not be flagged as an error.
Applied to files:
cmd/activate/run.go
🧬 Code graph analysis (6)
cmd/generation/delete/delete.go (1)
internal/systemd/time.go (1)
DurationFromTimeSpan(11-89)
cmd/activate/run.go (9)
internal/system/runner.go (3)
CommandRunner(10-13)NewCommand(24-33)Command(15-22)internal/logger/logger.go (1)
Logger(11-20)internal/activation/activation.go (3)
SwitchToConfigurationAction(131-131)SwitchToConfiguration(182-206)SwitchToConfigurationOptions(176-180)internal/generation/specialisations.go (1)
CollectSpecialisations(17-34)internal/constants/constants.go (1)
CurrentSystem(7-7)cmd/activate/unit.go (4)
UnitList(36-36)START_LIST_FILE(21-21)RELOAD_LIST_FILE(23-23)RESTART_LIST_FILE(22-22)internal/cmd/opts/opts.go (1)
ActivateOpts(13-16)internal/system/local.go (1)
NewLocalSystem(17-21)internal/systemd/unit.go (1)
ParseUnit(20-58)
cmd/activate/activate.go (4)
internal/cmd/opts/opts.go (1)
ActivateOpts(13-16)cmd/activate/run.go (1)
NIXOS_STC_PARENT_EXE(42-42)internal/activation/activation.go (1)
ParseSwitchToConfigurationAction(142-157)internal/cmd/utils/utils.go (3)
CommandErrorHandler(25-31)SetHelpFlagText(15-17)AlignedOptions(48-68)
cmd/activate/user.go (2)
cmd/activate/run.go (1)
NIXOS_STC_PARENT_EXE(42-42)internal/logger/logger.go (1)
Logger(11-20)
cmd/generation/delete/resolver.go (1)
internal/systemd/time.go (1)
DurationFromTimeSpan(11-89)
cmd/activate/unit.go (2)
internal/systemd/unit.go (5)
ParseUnit(20-58)CompareUnits(159-262)UnitComparisonNeedsRestart(139-139)UnitComparisonNeedsReload(140-140)UnitInfo(12-12)cmd/activate/fs.go (1)
Filesystem(14-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build/Test
🔇 Additional comments (10)
internal/systemd/time.go (1)
1-1: LGTM! Package rename avoids stdlib conflict.The package declaration was updated from
package timetopackage systemdUtilsto avoid naming conflicts with Go's standard librarytimepackage. This is a sensible refactoring decision.cmd/generation/delete/resolver.go (1)
12-12: LGTM! Import path correctly updated.The import path and function call have been correctly updated to reflect the package reorganization from
internal/timetointernal/systemd. The usage ofsystemdUtils.DurationFromTimeSpanis consistent with the new package structure.Also applies to: 96-96
cmd/generation/delete/delete.go (1)
24-24: LGTM! Consistent import path update.The import and usage have been correctly updated to use the new
internal/systemdpackage path, maintaining consistency across the codebase.Also applies to: 45-45
doc/man/nixos-cli-activate.1.scd (1)
1-65: LGTM! Comprehensive documentation for the new activate command.The manual page provides clear documentation for the
nixos activatecommand, covering all action types, options, and usage patterns. The description appropriately notes this is a low-level command and explains the relationship with the switch-to-configuration script.doc/man/nixos-cli-env.5.scd (2)
83-92: LGTM! Clear documentation for activation environment variables.The new activation-related environment variables (
NIXOS_NO_CHECK,NIXOS_NO_SYNC,STC_DISPLAY_ALL_UNITS) are well-documented with clear descriptions of their behavior and impact.
93-113: LGTM! Development variables properly segregated.The new "DEVELOPMENT MODE VARIABLES" section appropriately separates internal/development-only variables from user-facing environment variables, with clear warnings not to set them directly. This improves documentation clarity.
cmd/activate/user.go (1)
78-83: Past review issue resolved: Error handling is now correct.The previous review comment flagged that lines 78-81 were returning
nilon activation failures. The code now correctly returns an error when the service fails to restart, properly propagating failures to the caller.go.mod (2)
3-3: Dependency versions verified as secure.Verification confirms:
- carapace v1.8.6: No known vulnerabilities
- google/shlex: No known vulnerabilities
- golang.org/x/sys v0.34.0: Not vulnerable—the MODERATE CVE-2022-29526 privilege reporting bug was fixed before v0.34.0 release
All dependency versions are current and free from known security advisories. No action required.
9-9: Verify the purpose and version strategy of the go-systemd fork.Fork verification shows: The fork (water-sucks/go-systemd) is maintained as of Oct 15, 2025, and no security vulnerabilities were found in the go-systemd package. However, several concerns warrant developer verification:
- Minimal adoption: The fork has 0 stars and 0 forks, suggesting it's an internal/organizational fork with no community visibility.
- No documentation: The fork repository lacks a README or accessible changelog explaining why it's needed.
- Version mismatch: The replace directive pins v22.6.0 (current upstream) to a v22.0.0 pseudo-version (Oct 14, 2025), suggesting specific changes or patches. Verify these changes are intentional and compatible.
- Upstream more active: The upstream coreos/go-systemd was updated Oct 20 (5 days after the fork), indicating it remains actively maintained. Confirm the fork does not fall behind critical upstream fixes.
Ensure the fork's purpose, patches, and compatibility with the current codebase are documented and justified.
cmd/activate/unit.go (1)
83-92: LGTM on classification fixes.
- Active-state check now uses State correctly.
- .path suffix handled.
- Socket-activation guard prevents touching services directly.
Also applies to: 223-229, 357-375
cb18de5 to
dde565a
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
module.nix (1)
102-112: Scope down sudo env_keep to nixos-cli only; avoid global leakage.Keep env across sudo only for the nixos-cli/switch command, not globally. Also consider dropping DEBUG_MODE from env_keep.
Example (command-scoped Defaults):
- security.sudo.extraConfig = '' - Defaults env_keep += "NIXOS_CONFIG" - Defaults env_keep += "NIXOS_GENERATION_TAG" - Defaults env_keep += "NIXOS_CLI_CONFIG" - Defaults env_keep += "NIXOS_CLI_DISABLE_STEPS" - Defaults env_keep += "NIXOS_CLI_DEBUG_MODE" - Defaults env_keep += "NIXOS_CLI_SUPPRESS_NO_SETTINGS_WARNING" - ''; + security.sudo.extraConfig = '' + # Restrict preserved env to nixos-cli binary + Defaults!${lib.getExe cfg.package} env_keep += "NIXOS_CONFIG NIXOS_GENERATION_TAG NIXOS_CLI_CONFIG NIXOS_CLI_DISABLE_STEPS NIXOS_CLI_SUPPRESS_NO_SETTINGS_WARNING" + '';cmd/activate/run.go (2)
332-336: Grammar: “must be run as root.”- err := fmt.Errorf("this command must be ran as root") + err := fmt.Errorf("this command must be run as root")
117-121: Fix specialisation error: wrong arg and unclear message.Use the requested specialisation in the error; no need to log first if caller logs.
- if !slices.Contains(specialisations, specialisation) { - err = fmt.Errorf("specialisation '%v' does not exist", specialisations) - log.Error(err) - return err - } + if !slices.Contains(specialisations, specialisation) { + return fmt.Errorf("specialisation defined in $NIXOS_SPECIALISATION %q does not exist", specialisation) + }
🧹 Nitpick comments (9)
internal/system/local.go (3)
50-50: Consider a more precise regex pattern.The current pattern
^\"?nixos\"?$will match edge cases like"nixos(quote only at start) ornixos"(quote only at end). While unlikely in practice for the os-release ID field, a more precise pattern would be:var nixosDistroIDRegex = regexp.MustCompile(`^(?:"nixos"|nixos)$`)This explicitly matches either quoted or unquoted variants without allowing mismatched quotes.
82-82: Simplify the defer close pattern.The anonymous function wrapper is unnecessarily verbose. The idiomatic Go pattern for deferring a close is simply:
defer osRelease.Close()For read-only files, the close error is typically not critical, and the standard pattern doesn't require explicit error suppression.
Apply this diff to simplify:
- defer func() { _ = osRelease.Close() }() + defer osRelease.Close()
87-95: Check scanner error after loop.After scanning completes, the scanner's
Err()method should be checked to detect any errors that occurred during scanning (e.g., I/O errors).Apply this diff to add error checking:
for s.Scan() { key, value, found := strings.Cut(s.Text(), "=") if !found { continue } values[key] = value } + if err := s.Err(); err != nil { + return nil, err + } + return values, nil }doc/man/nixos-cli-env.5.scd (3)
83-92: Align env var semantics with code (presence vs “1”).Docs say “set to 1,” but run.go currently treats any non-empty value as “set.” Either:
- Change code to check for value == "1" (recommended; see my run.go diff), or
- Update these lines to say “if set (non-empty)”.
Please pick one for consistency.
90-92: Clarify accepted values.State explicitly “Set to 1 to show all units; unset or other values hide non-essential units.” Matches current usage in code.
103-106: Grammar nit.“Only useful for during development.” → “Only useful during development.”
- Show log messages for when developing TUIs. Only useful for during - development. + Show log messages when developing TUIs. Only useful during development.cmd/activate/run.go (1)
749-753: Document checkpoint/resume intent for list files.Consider adding a brief comment where you record/unrecord units and where you RemoveAll to explain the resilience pattern (persist before action; clean on success). Reduces future confusion.
Also applies to: 757-758, 772-785
cmd/activate/unit.go (2)
192-200: Preserve original unit names while sorting case-insensitively.Current implementation lowercases returned names. Keep original strings for readability.
-func (l UnitList) Sorted() []string { - keys := make([]string, 0, len(l)) - for unit := range l { - keys = append(keys, strings.ToLower(unit)) - } - sort.Strings(keys) - return keys -} +func (l UnitList) Sorted() []string { + keys := make([]string, 0, len(l)) + for unit := range l { + keys = append(keys, unit) + } + sort.Slice(keys, func(i, j int) bool { + return strings.ToLower(keys[i]) < strings.ToLower(keys[j]) + }) + return keys +}
533-542: Handle scanner error (optional).Capture scanner.Err() to avoid silently ignoring read errors.
s := bufio.NewScanner(file) for s.Scan() { // ... } - -return units +if err := s.Err(); err != nil { + // best-effort: ignore content on error + return units +} +return units
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
cmd/activate/activate.go(1 hunks)cmd/activate/run.go(1 hunks)cmd/activate/unit.go(1 hunks)cmd/activate/user.go(1 hunks)doc/man/nixos-cli-activate.1.scd(1 hunks)doc/man/nixos-cli-env.5.scd(1 hunks)go.mod(4 hunks)internal/system/local.go(2 hunks)module.nix(2 hunks)package.nix(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- doc/man/nixos-cli-activate.1.scd
🚧 Files skipped from review as they are similar to previous changes (3)
- cmd/activate/activate.go
- cmd/activate/user.go
- package.nix
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-20T02:59:26.104Z
Learnt from: water-sucks
PR: nix-community/nixos-cli#94
File: cmd/activate/run.go:245-281
Timestamp: 2025-10-20T02:59:26.104Z
Learning: Go 1.25 introduced the sync.WaitGroup.Go(f func()) method, which starts f in a new goroutine and automatically handles Add(1) and defer Done(). This is valid Go 1.25+ syntax and should not be flagged as an error.
Applied to files:
cmd/activate/run.go
📚 Learning: 2025-10-20T20:32:37.692Z
Learnt from: water-sucks
PR: nix-community/nixos-cli#94
File: cmd/activate/run.go:752-761
Timestamp: 2025-10-20T20:32:37.692Z
Learning: In cmd/activate/run.go, the global list files (START_LIST_FILE, RELOAD_LIST_FILE, RESTART_LIST_FILE) implement a checkpoint/recovery pattern for activation resilience: they are read at start (unit.go), written during processing before actions are taken (for persistence across interruptions), and removed after successful completion (for cleanup). If the activation process is interrupted, the next run loads these files to complete unfinished unit actions.
Applied to files:
cmd/activate/run.gocmd/activate/unit.go
🧬 Code graph analysis (2)
cmd/activate/run.go (8)
internal/system/runner.go (3)
CommandRunner(10-13)NewCommand(24-33)Command(15-22)internal/activation/activation.go (3)
SwitchToConfigurationAction(131-131)SwitchToConfiguration(182-206)SwitchToConfigurationOptions(176-180)internal/generation/specialisations.go (1)
CollectSpecialisations(17-34)internal/constants/constants.go (1)
CurrentSystem(7-7)cmd/activate/unit.go (4)
UnitList(36-36)START_LIST_FILE(21-21)RELOAD_LIST_FILE(23-23)RESTART_LIST_FILE(22-22)internal/cmd/opts/opts.go (1)
ActivateOpts(13-16)internal/system/local.go (1)
NewLocalSystem(17-21)internal/systemd/unit.go (1)
ParseUnit(20-58)
cmd/activate/unit.go (2)
internal/systemd/unit.go (5)
ParseUnit(20-58)CompareUnits(159-262)UnitComparisonNeedsRestart(139-139)UnitComparisonNeedsReload(140-140)UnitInfo(12-12)cmd/activate/fs.go (1)
Filesystem(14-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build/Test
🔇 Additional comments (7)
go.mod (4)
3-3: Verify Go version 1.25 is intentional.Go 1.25 has not been released as of the project's knowledge cutoff. Confirm whether this is a forward-declaration for planned support or should be pinned to 1.24.
13-13: Verify shlex version strategy.The commit-based pseudo-version
v0.0.0-20191202100458-e7afc7fbc510dates to 2019 and does not reference a release tag. Confirm this is intentional (no newer releases available) or consider pinning to an available release tag if one exists.Based on learnings, shlex is a stable, minimal library; the pseudo-version is safe but may indicate a maintenance gap in the upstream.
94-94: Clarify the go-systemd fork strategy and pseudo-version date.The replace directive substitutes the official
coreos/go-systemdwith a fork (water-sucks/go-systemd) pinned to a commit-based pseudo-version. Two concerns:
Fork maintenance risk: Using a fork introduces a maintenance burden and divergence from upstream. Document why this fork is required (custom patches, compatibility fixes, etc.) and ensure it is actively maintained.
Pseudo-version date inconsistency: The pseudo-version date
20251014(2025-10-14) appears to be after the PR creation date (2025-07-27). Verify this is not a data entry error or confirm the commit date is correct.
52-52: Confirm glamour is no longer directly imported.The classification change from direct to indirect (line 52) suggests that
charmbracelet/glamouris no longer explicitly imported in the application code but is pulled in transitively. Verify this is intentional and that any CLI features relying on glamour (markdown rendering) remain functional.internal/system/local.go (2)
4-4: LGTM!The new imports are appropriate for the os-release parsing functionality.
Also applies to: 7-8
52-69: LGTM!The IsNixOS detection logic is well-structured with a fast path for the
/etc/NIXOSmarker file and a fallback to parsing/etc/os-release. Error handling is appropriate.cmd/activate/run.go (1)
246-252: No action needed—toolchain already at Go 1.25.The go.mod file already declares
go 1.25, which satisfies the minimum version requirement forsync.WaitGroup.Go(). The code change is compatible.
dde565a to
0401cde
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
cmd/activate/run.go (3)
387-395: Enforce explicit "1" value for NIXOS_NO_CHECK.The current logic skips checks when
NIXOS_NO_CHECKis empty, but the environment variable semantics should require the literal value "1" to disable checks (any other value, including empty, should run checks).Apply this diff:
- if skipCheck := os.Getenv("NIXOS_NO_CHECK"); skipCheck == "" { + if os.Getenv("NIXOS_NO_CHECK") != "1" { log.Info("running pre-switch checks") err = runPreSwitchCheck(s, vars.PreSwitchCheckCmd, vars.Toplevel, opts.Action)
410-426: Enforce explicit "1" value for NIXOS_NO_SYNC.The current logic syncs when
NIXOS_NO_SYNCis empty, but the environment variable semantics should require the literal value "1" to skip syncing (any other value, including empty, should perform the sync).Apply this diff:
- if skipSync := os.Getenv("NIXOS_NO_SYNC"); skipSync == "" { + if os.Getenv("NIXOS_NO_SYNC") != "1" { log.Info("syncing /nix/store to disk") dir, err := os.Open("/nix/store")
332-336: Fix grammar in error message.Apply this diff:
if os.Geteuid() != 0 { - err := fmt.Errorf("this command must be ran as root") + err := fmt.Errorf("this command must be run as root") log.Errorf("%s", err) return err }module.nix (1)
102-112: Security concern: Global sudo env_keep scope remains unaddressed.The previous review raised a security concern about preserving
NIXOS_*variables globally across all sudo invocations. This issue has not been addressed in the current changes. These variables can leak control to arbitrary sudo sessions beyondnixos activateornixos apply.As suggested in the previous review, restrict the
env_keepsettings to a specific command alias:
- Define a
Cmnd_Aliasfor the nixos-cli commands- Apply
env_keeponly to that alias via a Runas_SpecExample approach:
security.sudo.extraConfig = '' # Define command alias for nixos-cli activation Cmnd_Alias NIXOS_CLI = ${lib.getExe cfg.package} # Preserve NIXOS_* variables only for nixos-cli commands Defaults!NIXOS_CLI env_keep += "NIXOS_CONFIG NIXOS_GENERATION_TAG NIXOS_CLI_CONFIG NIXOS_CLI_DISABLE_STEPS NIXOS_CLI_DEBUG_MODE NIXOS_CLI_SUPPRESS_NO_SETTINGS_WARNING" '';This minimizes the blast radius by ensuring these variables are preserved only for the specific nixos-cli executable.
🧹 Nitpick comments (1)
go.mod (1)
13-13: Consider migrating to carapace-shlex to avoid relying on an archived library.The
google/shlexrepository is archived and hasn't received updates since 2019, meaning it won't receive security patches or bug fixes. The codebase already hascarapace-shlexv1.0.1 as an indirect dependency, which is an actively maintained fork with the sameSplit()API. Sincegoogle/shlexis only used twice incmd/activate/run.go(lines 137 and 155), migration is straightforward.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
cmd/activate/activate.go(1 hunks)cmd/activate/run.go(1 hunks)doc/man/nixos-cli-activate.1.scd(1 hunks)doc/man/nixos-cli-env.5.scd(1 hunks)go.mod(4 hunks)internal/system/local.go(2 hunks)module.nix(2 hunks)package.nix(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- doc/man/nixos-cli-env.5.scd
- package.nix
- internal/system/local.go
- doc/man/nixos-cli-activate.1.scd
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-20T02:59:26.104Z
Learnt from: water-sucks
PR: nix-community/nixos-cli#94
File: cmd/activate/run.go:245-281
Timestamp: 2025-10-20T02:59:26.104Z
Learning: Go 1.25 introduced the sync.WaitGroup.Go(f func()) method, which starts f in a new goroutine and automatically handles Add(1) and defer Done(). This is valid Go 1.25+ syntax and should not be flagged as an error.
Applied to files:
cmd/activate/run.go
📚 Learning: 2025-10-20T20:32:37.692Z
Learnt from: water-sucks
PR: nix-community/nixos-cli#94
File: cmd/activate/run.go:752-761
Timestamp: 2025-10-20T20:32:37.692Z
Learning: In cmd/activate/run.go, the global list files (START_LIST_FILE, RELOAD_LIST_FILE, RESTART_LIST_FILE) implement a checkpoint/recovery pattern for activation resilience: they are read at start (unit.go), written during processing before actions are taken (for persistence across interruptions), and removed after successful completion (for cleanup). If the activation process is interrupted, the next run loads these files to complete unfinished unit actions.
Applied to files:
cmd/activate/run.go
🧬 Code graph analysis (2)
cmd/activate/activate.go (4)
internal/cmd/opts/opts.go (1)
ActivateOpts(13-16)cmd/activate/run.go (1)
NIXOS_STC_PARENT_EXE(42-42)internal/activation/activation.go (1)
ParseSwitchToConfigurationAction(142-157)internal/cmd/utils/utils.go (3)
CommandErrorHandler(25-31)SetHelpFlagText(15-17)AlignedOptions(48-68)
cmd/activate/run.go (8)
internal/system/runner.go (3)
CommandRunner(10-13)NewCommand(24-33)Command(15-22)internal/activation/activation.go (7)
SwitchToConfigurationAction(131-131)SwitchToConfiguration(182-206)SwitchToConfigurationOptions(176-180)SwitchToConfigurationActionDryActivate(139-139)SwitchToConfigurationActionChecksOnly(135-135)SwitchToConfigurationActionBoot(137-137)SwitchToConfigurationActionSwitch(136-136)internal/generation/specialisations.go (1)
CollectSpecialisations(17-34)internal/constants/constants.go (1)
CurrentSystem(7-7)cmd/activate/unit.go (4)
UnitList(36-36)START_LIST_FILE(21-21)RELOAD_LIST_FILE(23-23)RESTART_LIST_FILE(22-22)internal/cmd/opts/opts.go (1)
ActivateOpts(13-16)internal/system/local.go (1)
NewLocalSystem(17-21)internal/systemd/unit.go (1)
ParseUnit(20-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build/Test
🔇 Additional comments (7)
cmd/activate/activate.go (1)
13-67: LGTM! Well-structured command with context-aware validation.The command implementation is solid:
- Context-sensitive Args validation correctly allows omitting ACTION when
NIXOS_STC_PARENT_EXEis set (line 29)- Shell completion provides user-friendly descriptions
- Conditional flag exposure prevents
--specialisationin activation context- Help template enhancement via
AlignedOptionsimproves UXgo.mod (2)
3-3: LGTM: Go 1.25 upgrade aligns with dependency requirements.The upgrade to Go 1.25 is appropriate, especially given that golang.org/x/sys v0.34.0 may require a recent toolchain.
94-94: Clarify necessity and stability of the go-systemd fork.The replace directive uses water-sucks/go-systemd commit fe9dbfc225a6 (from feature branch add-reexecute-method) instead of a stable release. Investigation shows:
- Fork adds two new dbus methods:
ReexecuteContext()andResetFailedContext()—not present in official v22.6.0- A related upstream PR #430 ("Bugfix/dbus methods context", opened Oct 2023) remains unmerged and unreviewed for 2 years, suggesting low upstream priority or design concerns
- Fork created Oct 14, 2025, without tags or releases
Concerns:
- Using a commit hash from a feature branch lacks stability guarantees
- Fork maintenance becomes project responsibility with unclear timeline
- No evidence of upstream contribution plans or temporary status
Verification needed:
- Are
ReexecuteContext()andResetFailedContext()required for this project's core functionality, or are they optional enhancements?- Is this fork temporary (pending upstream merge) or permanent? If permanent, tag a stable release instead of using a commit hash.
- Have you considered contributing these methods to coreos/go-systemd, or is there a reason upstream would not accept them?
module.nix (4)
48-65: LGTM: Well-documented experimental option.The
useActivationInterfaceoption is properly structured with clear documentation, appropriate defaults, and an explicit experimental status warning. The conservative default (false) is suitable for a new activation interface.
81-100: LGTM: Clean config restructuring and metadata injection.The restructured configuration using
mkMergeis well-organized. Moving the config to/etc/nixos-cli/config.tomland injectingnixos-version.jsonvia system builder commands are appropriate changes that support the activation interface.
114-128: LGTM: Option cache generation is well-structured.The conditional option cache block properly filters visible, non-internal options and places the cache in a consistent location (
/etc/nixos-cli/options-cache.json). The conditional oncfg.prebuildOptionCacheallows users to control eval-time tradeoffs.
129-156: Code changes are well-isolated; verify nixpkgs integration.The
system.switch.enable = lib.mkForce falsechange is contained to this module with no breaking references found in the codebase. The existing code comment already documents the intended behavior: disabling switch-to-configuration-ng while preserving system switchability.Verify that this configuration integrates correctly with your nixpkgs version and that system rollback and activation operations work as expected in your environment.
|
#94 (comment) from @water-sucks :
Can you explain wny? |
Description
This is a re-implementation of the
switch-to-configurationscript in Go. Originally, it was implemented a separate script in Perl, but now is a standalone Rust application.Both are not suitable for the purposes of a self-contained binary, so this is the same functionality, exposed slightly differently.
It is exposed as a low-level command in
nixos activate, and generally must be ran in the context of the corresponding NixOS module that uses it as the active switch application, similar to how theenableNgoption was used for the Rust application.Running
nixos activateas a user directly serves as a shorthand for executing the/run/current-system/bin/switch-to-configurationscript instead, which will either wrap a proper invocation ofnixos activatewith the required variables, or run the originalnixpkgsimplementation of it in Rust.Closes #55.
Summary by CodeRabbit