diff --git a/internal/devbox/devbox.go b/internal/devbox/devbox.go index 8ac001233d3..e68fbb25329 100644 --- a/internal/devbox/devbox.go +++ b/internal/devbox/devbox.go @@ -629,10 +629,19 @@ func (d *Devbox) execPrintDevEnv(ctx context.Context, usePrintDevEnvCache bool) spinny.Start() } + // Allow unfree/insecure packages to evaluate when the user opts in via + // devbox.json's "env" (the process environment is handled separately + // inside PrintDevEnv). See https://github.com/jetify-com/devbox/issues/2196. + configEnv := d.cfg.Env() + allowUnfree, _ := strconv.ParseBool(configEnv["NIXPKGS_ALLOW_UNFREE"]) + allowInsecure, _ := strconv.ParseBool(configEnv["NIXPKGS_ALLOW_INSECURE"]) + vaf, err := d.nix.PrintDevEnv(ctx, &nix.PrintDevEnvArgs{ FlakeDir: d.flakeDir(), PrintDevEnvCachePath: d.nixPrintDevEnvCachePath(), UsePrintDevEnvCache: usePrintDevEnvCache, + AllowUnfree: allowUnfree, + AllowInsecure: allowInsecure, }) if spinny != nil { spinny.Stop() diff --git a/internal/nix/eval.go b/internal/nix/eval.go index 81b827b833b..ffcf3da8e35 100644 --- a/internal/nix/eval.go +++ b/internal/nix/eval.go @@ -59,3 +59,10 @@ func IsInsecureAllowed() bool { allowed, _ := strconv.ParseBool(os.Getenv("NIXPKGS_ALLOW_INSECURE")) return allowed } + +// IsUnfreeAllowed reports whether the user has opted into unfree packages by +// setting the NIXPKGS_ALLOW_UNFREE environment variable to a truthy value. +func IsUnfreeAllowed() bool { + allowed, _ := strconv.ParseBool(os.Getenv("NIXPKGS_ALLOW_UNFREE")) + return allowed +} diff --git a/internal/nix/nix.go b/internal/nix/nix.go index c25444adb4e..2fa330e8a4e 100644 --- a/internal/nix/nix.go +++ b/internal/nix/nix.go @@ -45,6 +45,13 @@ type PrintDevEnvArgs struct { FlakeDir string PrintDevEnvCachePath string UsePrintDevEnvCache bool + + // AllowUnfree and AllowInsecure report whether the user has opted into + // unfree or insecure packages through the resolved devbox config (for + // example via NIXPKGS_ALLOW_UNFREE in devbox.json's "env"). They are in + // addition to the same variables read from the process environment. + AllowUnfree bool + AllowInsecure bool } // PrintDevEnv calls `nix print-dev-env -f ` and returns its output. The output contains @@ -75,9 +82,27 @@ func (*NixInstance) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*Pr ref := flake.Ref{Type: flake.TypePath, Path: flakeDirResolved} if len(data) == 0 { + // Honor unfree/insecure opt-in from either the process environment + // or the resolved devbox config. + allowUnfree := args.AllowUnfree || IsUnfreeAllowed() + allowInsecure := args.AllowInsecure || IsInsecureAllowed() + cmd := Command("print-dev-env", "--json") - if featureflag.ImpurePrintDevEnv.Enabled() { + if usePrintDevEnvImpure(allowUnfree, allowInsecure) { cmd.Args = append(cmd.Args, "--impure") + // Only inject the allow-* variables the user actually opted + // into. When impure mode is enabled solely via the feature + // flag, cmd.Env is left inheriting the parent environment. + if allowUnfree || allowInsecure { + env := os.Environ() + if allowUnfree { + env = allowUnfreeEnv(env) + } + if allowInsecure { + env = allowInsecureEnv(env) + } + cmd.Env = env + } } cmd.Args = append(cmd.Args, ref) slog.Debug("running print-dev-env cmd", "cmd", cmd) @@ -100,6 +125,18 @@ func (*NixInstance) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*Pr return &out, nil } +// usePrintDevEnvImpure reports whether `nix print-dev-env` should be run with +// the --impure flag. By default print-dev-env runs in pure mode, which ignores +// the NIXPKGS_ALLOW_UNFREE and NIXPKGS_ALLOW_INSECURE environment variables. +// When the user has opted into unfree or insecure packages, we run with +// --impure so those variables take effect, matching what devbox already does +// when building and installing packages. Pure mode is kept otherwise because +// --impure disables Nix's evaluation caching, which makes the command slower. +// See https://github.com/jetify-com/devbox/issues/2196. +func usePrintDevEnvImpure(allowUnfree, allowInsecure bool) bool { + return featureflag.ImpurePrintDevEnv.Enabled() || allowUnfree || allowInsecure +} + func savePrintDevEnvCache(path string, out PrintDevEnvOut) error { data, err := json.Marshal(out) if err != nil { diff --git a/internal/nix/nix_test.go b/internal/nix/nix_test.go index f43c405346d..8eb8328c8a1 100644 --- a/internal/nix/nix_test.go +++ b/internal/nix/nix_test.go @@ -4,6 +4,62 @@ import ( "testing" ) +func TestIsUnfreeAllowed(t *testing.T) { + cases := map[string]bool{ + "": false, + "0": false, + "false": false, + "1": true, + "true": true, + } + for value, want := range cases { + t.Run(value, func(t *testing.T) { + t.Setenv("NIXPKGS_ALLOW_UNFREE", value) + if got := IsUnfreeAllowed(); got != want { + t.Errorf("IsUnfreeAllowed() with NIXPKGS_ALLOW_UNFREE=%q = %v, want %v", value, got, want) + } + }) + } +} + +// TestUsePrintDevEnvImpure verifies the fix for +// https://github.com/jetify-com/devbox/issues/2196: print-dev-env must run +// with --impure whenever the user opts into unfree or insecure packages so +// those environment variables are honored. +func TestUsePrintDevEnvImpure(t *testing.T) { + cases := []struct { + name string + allowUnfree bool + allowInsecure bool + want bool + }{ + {name: "neither opted in", want: false}, + {name: "unfree opted in", allowUnfree: true, want: true}, + {name: "insecure opted in", allowInsecure: true, want: true}, + {name: "both opted in", allowUnfree: true, allowInsecure: true, want: true}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + // Keep the result independent of any ambient feature-flag env. + t.Setenv("DEVBOX_FEATURE_IMPURE_PRINT_DEV_ENV", "0") + if got := usePrintDevEnvImpure(tt.allowUnfree, tt.allowInsecure); got != tt.want { + t.Errorf("usePrintDevEnvImpure(%v, %v) = %v, want %v", + tt.allowUnfree, tt.allowInsecure, got, tt.want) + } + }) + } +} + +// TestUsePrintDevEnvImpureFeatureFlag verifies that the IMPURE_PRINT_DEV_ENV +// feature flag forces impure mode even when the user hasn't opted into unfree +// or insecure packages. +func TestUsePrintDevEnvImpureFeatureFlag(t *testing.T) { + t.Setenv("DEVBOX_FEATURE_IMPURE_PRINT_DEV_ENV", "1") + if !usePrintDevEnvImpure(false, false) { + t.Error("usePrintDevEnvImpure(false, false) = false with feature flag set, want true") + } +} + func TestParseInsecurePackagesFromExitError(t *testing.T) { errorText := ` at /nix/store/xwl0am98klc8mz074jdyvpnyc6vwzlla-source/lib/customisation.nix:267:17: