From 2bf26aad60850a85b094ca054ee712cb63f469e9 Mon Sep 17 00:00:00 2001 From: Cooper Maruyama Date: Wed, 9 Sep 2026 17:41:55 -0700 Subject: [PATCH 1/3] fix: preserve existing stackpanel changes Preserves existing working-copy changes for review during DARK-20. --- .envrc | 18 ++- .stack/config.nix | 3 +- .stack/title.txt | 3 + apps/stackpanel-go/cmd/cli/envrc.go | 2 +- apps/stackpanel-go/cmd/cli/root.go | 7 +- apps/stackpanel-go/cmd/cli/version.go | 71 ++++++++++- apps/stackpanel-go/cmd/cli/version_test.go | 118 ++++++++++++++++++ flake.lock | 70 +++++------ nix/flake/default.nix | 1 + nix/flake/integrations/treefmt/default.nix | 5 +- nix/flake/packages.nix | 3 +- nix/flake/per-system-outputs.nix | 1 + nix/flake/per-system/eval.nix | 2 + nix/internal/flake/default.nix | 1 + nix/stackpanel/core/cli.nix | 12 +- nix/stackpanel/core/options/doctor.nix | 14 +++ .../services/security-healthchecks.nix | 9 +- .../packages/stackpanel-cli/default.nix | 26 +++- prelude.nix | 15 ++- 19 files changed, 320 insertions(+), 61 deletions(-) create mode 100644 .stack/title.txt create mode 100644 apps/stackpanel-go/cmd/cli/version_test.go diff --git a/.envrc b/.envrc index d9ac7a9b..5634722d 100644 --- a/.envrc +++ b/.envrc @@ -14,6 +14,14 @@ if [[ -n "${__STACKPANEL_CLEAN_ENV+x}" ]]; then exit 0 fi +# ---------------------------------------------------------------------------- +# Nix +# ---------------------------------------------------------------------------- +if ! has nix; then + echo "nix is required, install it: https://install.determinate.systems" >&2 + exit 1 +fi + # ---------------------------------------------------------------------------- # Binary cache # ---------------------------------------------------------------------------- @@ -35,11 +43,19 @@ fi # source_url "$NIX_DIRENV_URL" "$NIX_DIRENV_SHA" # fi +__stack=stack +if ! has stack; then + echo "installing stackpanel..." >&2 + # nix profile add github:darkmatter/stackpanel + # In stackcpanel itself we run off the local project + __stack='nix run . --' +fi + # ---------------------------------------------------------------------------- # Stackpanel preflight + pure mode # ---------------------------------------------------------------------------- -eval "$(stack envrc)" +eval "$($__stack envrc)" # Export STACKPANEL_ROOT for preflight command export STACKPANEL_ROOT="$GIT_ROOT" diff --git a/.stack/config.nix b/.stack/config.nix index 7a98ab7e..49cb8092 100644 --- a/.stack/config.nix +++ b/.stack/config.nix @@ -670,6 +670,7 @@ prelude = { tagline = "Stackpanel devshell"; subtitle = "your environment is ready"; + settings.motd.title.text = ./title.txt; }; # --------------------------------------------------------------------------- @@ -932,7 +933,7 @@ ca-url = "https://ca.internal:443"; cert-name = "device"; enable = true; - prompt-on-shell = true; + prompt-on-shell = false; provisioner = "Authentik"; }; diff --git a/.stack/title.txt b/.stack/title.txt new file mode 100644 index 00000000..b0f9a906 --- /dev/null +++ b/.stack/title.txt @@ -0,0 +1,3 @@ +____ ___ ____ ____ _ _ ___ ____ _ _ ____ _ +[__ | |__| | |_/ |__] |__| |\ | |___ | +___] | | | |___ | \_ | | | | \| |___ |___ diff --git a/apps/stackpanel-go/cmd/cli/envrc.go b/apps/stackpanel-go/cmd/cli/envrc.go index 6f7e1b48..2f972a3c 100644 --- a/apps/stackpanel-go/cmd/cli/envrc.go +++ b/apps/stackpanel-go/cmd/cli/envrc.go @@ -288,7 +288,7 @@ _nix_argsum_suffix() { nix_direnv_watch_file() { # shellcheck disable=2016 - log_error '` + `"` + `nix_direnv_watch_file` + "`" + `is deprecated - use ` + "`" + `watch_file` + "`" + `'' + log_error '` + "`" + `nix_direnv_watch_file` + "`" + ` is deprecated - use ` + "`" + `watch_file` + "`" + `' watch_file "$@" } diff --git a/apps/stackpanel-go/cmd/cli/root.go b/apps/stackpanel-go/cmd/cli/root.go index 0cc27b38..a635cc4c 100644 --- a/apps/stackpanel-go/cmd/cli/root.go +++ b/apps/stackpanel-go/cmd/cli/root.go @@ -20,9 +20,12 @@ import ( ) var ( - // Version and BuildDate are set at build time via -ldflags. - // The "dev" default lets you identify local/untagged builds. + // Version, GitCommit, and BuildDate are set at build time via -ldflags. + // The "dev"/"unknown" defaults let you identify local/untagged builds. + // When ldflags are omitted (go run, air), applyEmbeddedBuildInfo fills + // GitCommit and BuildDate from Go's embedded VCS data. Version = "dev" + GitCommit = "unknown" BuildDate = "unknown" ) diff --git a/apps/stackpanel-go/cmd/cli/version.go b/apps/stackpanel-go/cmd/cli/version.go index 3cec042c..ce6f5dc3 100644 --- a/apps/stackpanel-go/cmd/cli/version.go +++ b/apps/stackpanel-go/cmd/cli/version.go @@ -3,6 +3,9 @@ package cmd import ( "fmt" + "io" + "runtime/debug" + "strings" "github.com/spf13/cobra" ) @@ -12,13 +15,73 @@ var versionCmd = &cobra.Command{ Short: "Show version information", Long: `Display the version and build information for Stackpanel.`, Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Stackpanel %s\n", Version) - if BuildDate != "unknown" { - fmt.Printf("Built: %s\n", BuildDate) - } + fmt.Fprint(cmd.OutOrStdout(), formatVersion(rootCmd.Name(), Version, GitCommit, BuildDate)) }, } func init() { + applyEmbeddedBuildInfo() + rootCmd.SetVersionTemplate(versionCobraTemplate(GitCommit, BuildDate)) rootCmd.AddCommand(versionCmd) } + +func isSetBuildMeta(s string) bool { + return s != "" && s != "unknown" +} + +// formatVersion is the human-readable version block used by `stack version` +// and (via versionCobraTemplate) by `stack --version`. +func formatVersion(name, version, commit, date string) string { + var b strings.Builder + fmt.Fprintf(&b, "%s version %s\n", name, version) + writeBuildMeta(&b, commit, date) + return b.String() +} + +func versionCobraTemplate(commit, date string) string { + var b strings.Builder + // Keep cobra v1.9's default first line, then append build metadata. + b.WriteString(`{{with .DisplayName}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}}`) + b.WriteByte('\n') + writeBuildMeta(&b, commit, date) + return b.String() +} + +func writeBuildMeta(w io.StringWriter, commit, date string) { + if isSetBuildMeta(commit) { + _, _ = w.WriteString(fmt.Sprintf("commit: %s\n", commit)) + } + if isSetBuildMeta(date) { + _, _ = w.WriteString(fmt.Sprintf("built: %s\n", date)) + } +} + +// applyEmbeddedBuildInfo fills GitCommit and BuildDate from Go's embedded +// VCS settings when they were not injected via -ldflags (local go build). +func applyEmbeddedBuildInfo() { + info, ok := debug.ReadBuildInfo() + if !ok { + return + } + vcsRevision := "" + modified := false + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + vcsRevision = s.Value + case "vcs.time": + if !isSetBuildMeta(BuildDate) { + BuildDate = s.Value + } + case "vcs.modified": + modified = s.Value == "true" + } + } + if isSetBuildMeta(GitCommit) || vcsRevision == "" { + return + } + GitCommit = vcsRevision + if modified { + GitCommit += "-dirty" + } +} diff --git a/apps/stackpanel-go/cmd/cli/version_test.go b/apps/stackpanel-go/cmd/cli/version_test.go new file mode 100644 index 00000000..4a95f641 --- /dev/null +++ b/apps/stackpanel-go/cmd/cli/version_test.go @@ -0,0 +1,118 @@ +package cmd + +import ( + "bytes" + "strings" + "testing" +) + +func TestFormatVersion(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cmd string + version string + commit string + date string + want []string + hide []string + }{ + { + name: "version only", + cmd: "stack", + version: "0.1.0", + commit: "unknown", + date: "unknown", + want: []string{"stack version 0.1.0\n"}, + hide: []string{"commit:", "built:"}, + }, + { + name: "commit and date", + cmd: "stack", + version: "0.1.0", + commit: "abc123def", + date: "2026-09-08T05:23:00Z", + want: []string{ + "stack version 0.1.0\n", + "commit: abc123def\n", + "built: 2026-09-08T05:23:00Z\n", + }, + }, + { + name: "empty meta omitted", + cmd: "stack", + version: "dev", + commit: "", + date: "", + want: []string{"stack version dev\n"}, + hide: []string{"commit:", "built:"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := formatVersion(tt.cmd, tt.version, tt.commit, tt.date) + for _, want := range tt.want { + if !strings.Contains(got, want) { + t.Errorf("formatVersion() = %q, want to contain %q", got, want) + } + } + for _, hide := range tt.hide { + if strings.Contains(got, hide) { + t.Errorf("formatVersion() = %q, should omit %q", got, hide) + } + } + }) + } +} + +func resetRootFlags() { + rootCmd.SetArgs(nil) + rootCmd.SetOut(nil) + rootCmd.SetErr(nil) + for _, name := range []string{"version", "help"} { + if f := rootCmd.Flags().Lookup(name); f != nil { + _ = f.Value.Set("false") + f.Changed = false + } + } +} + +func TestVersionFlagIncludesBuildMetadata(t *testing.T) { + prevVersion, prevCommit, prevDate := Version, GitCommit, BuildDate + t.Cleanup(func() { + Version, GitCommit, BuildDate = prevVersion, prevCommit, prevDate + rootCmd.Version = Version + rootCmd.SetVersionTemplate(versionCobraTemplate(GitCommit, BuildDate)) + resetRootFlags() + }) + + Version = "0.1.0" + GitCommit = "deadbeefcafebabe" + BuildDate = "2026-09-08T05:23:00Z" + rootCmd.Version = Version + rootCmd.SetVersionTemplate(versionCobraTemplate(GitCommit, BuildDate)) + resetRootFlags() + + buf := &bytes.Buffer{} + rootCmd.SetOut(buf) + rootCmd.SetErr(buf) + rootCmd.SetArgs([]string{"--version"}) + + if err := rootCmd.Execute(); err != nil { + t.Fatalf("stack --version should succeed: %v", err) + } + + got := buf.String() + for _, want := range []string{ + "stack version 0.1.0", + "commit: deadbeefcafebabe", + "built: 2026-09-08T05:23:00Z", + } { + if !strings.Contains(got, want) { + t.Errorf("--version output %q, want to contain %q", got, want) + } + } +} diff --git a/flake.lock b/flake.lock index 70d40b7d..f1d0dda5 100644 --- a/flake.lock +++ b/flake.lock @@ -82,11 +82,11 @@ "stable": "stable" }, "locked": { - "lastModified": 1784448989, - "narHash": "sha256-XC0OkWCiTM91msCp0o7zfM+iBv06mLlCzFxkP8vf+ac=", + "lastModified": 1786742289, + "narHash": "sha256-r61K9svFDQkI5jONYksneDtDT5c4SkWrZVD3ni5O6Ak=", "owner": "zhaofengli", "repo": "colmena", - "rev": "e5eda626e459f7043868fd2e3b3bf8ce0d1992e6", + "rev": "dc22786a43315b212eeafe13409a7203328e5a30", "type": "github" }, "original": { @@ -253,11 +253,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1782949081, - "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", + "lastModified": 1788450739, + "narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", + "rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993", "type": "github" }, "original": { @@ -309,12 +309,12 @@ ] }, "locked": { - "lastModified": 1784288435, - "narHash": "sha256-ReRHaLgr/uVqdD8afFSn+myXIfpHeOhP0yYe0TJqAA8=", - "rev": "43b3c1ab9d40fb1dbb008f451988a91e375825e9", - "revCount": 1231, + "lastModified": 1787424939, + "narHash": "sha256-O2tBn84NNuHrnqNVxx/XqsXwfYvS1YwBh+7CBnbCYsk=", + "rev": "809414f0cdadf82cf11b06c2b29ba9b3168b3297", + "revCount": 1233, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/cachix/git-hooks.nix/0.1.1231%2Brev-43b3c1ab9d40fb1dbb008f451988a91e375825e9/019f7135-8fdf-76f0-b1a1-d2c67e91af8d/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/cachix/git-hooks.nix/0.1.1233%2Brev-809414f0cdadf82cf11b06c2b29ba9b3168b3297/01a02d24-e252-7832-9731-8e97d3060f3c/source.tar.gz" }, "original": { "type": "tarball", @@ -438,11 +438,11 @@ "spectrum": "spectrum" }, "locked": { - "lastModified": 1784666190, - "narHash": "sha256-xgfS6slV7J3baMooNN1UuBi51RIgg9y0DbCxfSA0668=", + "lastModified": 1788636433, + "narHash": "sha256-iCLUJO5V2ZlEAlYxlkCZ5YLkyQrpkyXOMUTkPXjsdPk=", "owner": "astro", "repo": "microvm.nix", - "rev": "fa5340ac684cdce8a22b6d4a0bcebb0cc999275e", + "rev": "804cbac7a462aa0fa8bb60c3d2fc4ead0a62060f", "type": "github" }, "original": { @@ -501,11 +501,11 @@ ] }, "locked": { - "lastModified": 1775487831, - "narHash": "sha256-2lguQpLPQaxpQCJjXhmEEAfabwsAhkP29Z7fgLzHARA=", + "lastModified": 1788758950, + "narHash": "sha256-b3zONUcYXZHeoeYwDZdSDCzMj0s6ubmD6NT3D7sS+jU=", "owner": "nlewo", "repo": "nix2container", - "rev": "76be9608a7f4d6c985d28b0e7be903ae2547df3e", + "rev": "b6ac40ef110c12ab1651fce5ea563f7837236439", "type": "github" }, "original": { @@ -530,11 +530,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1782614948, - "narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=", + "lastModified": 1788057806, + "narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c", + "rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c", "type": "github" }, "original": { @@ -545,11 +545,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1784872115, - "narHash": "sha256-THPEF2po0fsoH8gNtp+Ae0XFDJH3N/ol7xO3v6VMTJU=", + "lastModified": 1788746152, + "narHash": "sha256-RaKngusl5I8pNi8RfrVvsHq3NZe7eFWzDlb01+hEVqY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "335f0738cb2fa9708f3f428e39d2eae975d1338d", + "rev": "42f17a57f4f6e33b3de3dca0a2a5ea5233169d02", "type": "github" }, "original": { @@ -624,11 +624,11 @@ "treefmt-nix": "treefmt-nix_3" }, "locked": { - "lastModified": 1784725586, - "narHash": "sha256-srAfht5eD3egv+EoC5BMJYkaQZoIqZYIRl/dHY6du/Q=", + "lastModified": 1788826650, + "narHash": "sha256-Hw+BWngN0RHEnSyX09g8BRN+THyd2upFyHqzkM3za8k=", "owner": "darkmatter", "repo": "prelude", - "rev": "fe66e24c40b48494d69bb43d8d65b757c251732a", + "rev": "313b3c07125a9c75a168a4b8bac8b499c1b5553a", "type": "github" }, "original": { @@ -678,11 +678,11 @@ "spectrum": { "flake": false, "locked": { - "lastModified": 1783694892, - "narHash": "sha256-xO8f7Qng+18FK2UlB9vcrkxCaQMCt5WjCH24aW/11eg=", + "lastModified": 1785761586, + "narHash": "sha256-MWOMVqJJERwjQGgRIv8d6rlEBqbLM3VZWxHkNKIIZNw=", "ref": "refs/heads/main", - "rev": "24c4346e30fdea8d8e80f34aec3554a15a667d24", - "revCount": 1410, + "rev": "a7762d6f54b40560dd5255ce902e6e6a5d980fe9", + "revCount": 1416, "type": "git", "url": "https://spectrum-os.org/git/spectrum" }, @@ -834,12 +834,12 @@ ] }, "locked": { - "lastModified": 1784369104, - "narHash": "sha256-47cxbcZODibHv3rELFQ9vZly0vUNkND/atn/U7HLeb0=", - "rev": "df3c0640565d04a0261253cdd89fce78ec50168a", - "revCount": 553, + "lastModified": 1786901030, + "narHash": "sha256-WSFCsDSE5ffgD2MqzkM2CYjeFiKhRF/dJUN8uedb6YE=", + "rev": "27b3b12a8e6375f28ebe122f07d230ca5459bbfa", + "revCount": 557, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/numtide/treefmt-nix/0.1.553%2Brev-df3c0640565d04a0261253cdd89fce78ec50168a/019f765a-822a-7a83-8dc1-c19f2adba321/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/numtide/treefmt-nix/0.1.557%2Brev-27b3b12a8e6375f28ebe122f07d230ca5459bbfa/01a00ba7-ea6f-7822-ab05-cbad971fc7ca/source.tar.gz" }, "original": { "type": "tarball", diff --git a/nix/flake/default.nix b/nix/flake/default.nix index 97b1aa8f..b3099f28 100644 --- a/nix/flake/default.nix +++ b/nix/flake/default.nix @@ -82,6 +82,7 @@ in pkgs self inputs + localFlake stackpanelImports flakeLevelStackpanelConfig perSystemStackpanelConfig diff --git a/nix/flake/integrations/treefmt/default.nix b/nix/flake/integrations/treefmt/default.nix index 8ac81b46..3231e354 100644 --- a/nix/flake/integrations/treefmt/default.nix +++ b/nix/flake/integrations/treefmt/default.nix @@ -24,7 +24,10 @@ in }: lib.mkIf (available && includeRootOutputs) ( let - rootPackages = import ../../packages.nix { inherit pkgs; }; + rootPackages = import ../../packages.nix { + inherit pkgs; + flake = localFlake; + }; treefmtEval = localInputs.treefmt-nix.lib.evalModule pkgs { projectRootFile = "flake.nix"; programs = { diff --git a/nix/flake/packages.nix b/nix/flake/packages.nix index 80057a4a..bac6d2d3 100644 --- a/nix/flake/packages.nix +++ b/nix/flake/packages.nix @@ -9,10 +9,11 @@ # ============================================================================== { pkgs, + flake ? null, }: let # Unified CLI + Agent package - stackpanel = pkgs.callPackage ../stackpanel/packages/stackpanel-cli { }; + stackpanel = pkgs.callPackage ../stackpanel/packages/stackpanel-cli { inherit flake; }; in { # Main stackpanel package (CLI + agent unified) diff --git a/nix/flake/per-system-outputs.nix b/nix/flake/per-system-outputs.nix index 2da75f6a..1de5ccc1 100644 --- a/nix/flake/per-system-outputs.nix +++ b/nix/flake/per-system-outputs.nix @@ -85,6 +85,7 @@ let inputs self ; + localFlake = self; }; }; diff --git a/nix/flake/per-system/eval.nix b/nix/flake/per-system/eval.nix index b76cb7fa..28865a2b 100644 --- a/nix/flake/per-system/eval.nix +++ b/nix/flake/per-system/eval.nix @@ -8,6 +8,7 @@ pkgs, self, inputs, + localFlake ? null, stackpanelImports, flakeLevelStackpanelConfig, perSystemStackpanelConfig, @@ -46,6 +47,7 @@ let lib inputs self + localFlake ; }; }; diff --git a/nix/internal/flake/default.nix b/nix/internal/flake/default.nix index 64faebde..53448b40 100644 --- a/nix/internal/flake/default.nix +++ b/nix/internal/flake/default.nix @@ -173,6 +173,7 @@ in lib inputs self + localFlake ; }; }; diff --git a/nix/stackpanel/core/cli.nix b/nix/stackpanel/core/cli.nix index 5a87ffc5..09ec9670 100644 --- a/nix/stackpanel/core/cli.nix +++ b/nix/stackpanel/core/cli.nix @@ -22,6 +22,8 @@ config, pkgs, inputs ? { }, + localFlake ? null, + self ? { }, ... }: let @@ -55,8 +57,14 @@ let config = ./.; }; - # Import the stackpanel CLI package - stackpanel-cli = pkgs.callPackage ../packages/stackpanel-cli { }; + # Stamp stack --version with the Stackpanel flake revision when available. + # localFlake is the Stackpanel source flake (not the consumer repo). + sourceFlake = if localFlake != null then localFlake else self; + stackpanel-cli = pkgs.callPackage ../packages/stackpanel-cli ( + lib.optionalAttrs (sourceFlake ? lastModifiedDate || sourceFlake ? rev || sourceFlake ? dirtyRev) { + flake = sourceFlake; + } + ); # Extract serializable package info from devshell packages # This avoids slow nix eval at runtime by pre-computing during shell entry diff --git a/nix/stackpanel/core/options/doctor.nix b/nix/stackpanel/core/options/doctor.nix index 05dc164c..97a5518f 100644 --- a/nix/stackpanel/core/options/doctor.nix +++ b/nix/stackpanel/core/options/doctor.nix @@ -206,6 +206,18 @@ let ''; }; + runtimeInputs = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + description = '' + Packages added to PATH for `script` and `path` checks via + `writeShellApplication`. Use this for tools the check invokes by bare + name (e.g. openssl, curl, jq). Ignored when `scriptPackage` or + `scriptRef` supplies the executable. + ''; + example = lib.literalExpression "[ pkgs.openssl pkgs.curl ]"; + }; + nixExpr = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; @@ -401,11 +413,13 @@ let else if hasPath then pkgs.writeShellApplication { name = "healthcheck-${checkName'}"; + inherit (check) runtimeInputs; text = builtins.readFile check.path; } else if hasScript then pkgs.writeShellApplication { name = "healthcheck-${checkName'}"; + inherit (check) runtimeInputs; text = check.script; } else diff --git a/nix/stackpanel/integrations/services/security-healthchecks.nix b/nix/stackpanel/integrations/services/security-healthchecks.nix index d6e35eeb..03753f5e 100644 --- a/nix/stackpanel/integrations/services/security-healthchecks.nix +++ b/nix/stackpanel/integrations/services/security-healthchecks.nix @@ -9,13 +9,13 @@ # These healthchecks validate that the security infrastructure is properly # configured and functional, providing traffic light indicators in the UI. # -# NOTE: Scripts use PATH commands (not Nix store paths) since they run -# via `sh -c` in the Go agent. Commands like openssl, curl, aws, jq, sops -# must be available in the devshell PATH. +# Script checks are packaged with writeShellApplication. Tools invoked by bare +# name must be listed in runtimeInputs so they land on that script's PATH. # ============================================================================== { lib, config, + pkgs, ... }: let @@ -122,6 +122,7 @@ in type = "script"; severity = "critical"; timeout = 10; + runtimeInputs = [ pkgs.openssl ]; script = '' STACKPANEL_STATE_DIR="''${STACKPANEL_STATE_DIR:-''${STACKPANEL_ROOT:-.}/.stack/profile}" CERT_PATH="$STACKPANEL_STATE_DIR/step/device-root.chain.crt" @@ -151,6 +152,7 @@ in type = "script"; severity = "warning"; timeout = 10; + runtimeInputs = [ pkgs.openssl ]; script = '' STACKPANEL_STATE_DIR="''${STACKPANEL_STATE_DIR:-''${STACKPANEL_ROOT:-.}/.stack/profile}" CERT_PATH="$STACKPANEL_STATE_DIR/step/device-root.chain.crt" @@ -254,6 +256,7 @@ in type = "script"; severity = "critical"; timeout = 30; + runtimeInputs = [ pkgs.openssl ]; script = '' STACKPANEL_STATE_DIR="''${STACKPANEL_STATE_DIR:-''${STACKPANEL_ROOT:-.}/.stack/profile}" CERT_PATH="''${AWS_CERT_PATH:-$STACKPANEL_STATE_DIR/step/device-root.chain.crt}" diff --git a/nix/stackpanel/packages/stackpanel-cli/default.nix b/nix/stackpanel/packages/stackpanel-cli/default.nix index a21a22db..04a92114 100644 --- a/nix/stackpanel/packages/stackpanel-cli/default.nix +++ b/nix/stackpanel/packages/stackpanel-cli/default.nix @@ -20,6 +20,10 @@ { pkgs, lib, + # Stackpanel flake (`localFlake` / `self`) whose rev and lastModifiedDate + # are stamped into `stack --version`. Optional: omitted builds keep + # GitCommit/BuildDate as "unknown" (local `go build` fills them from VCS). + flake ? null, ... }: let @@ -27,10 +31,26 @@ let # Source path - the unified stackpanel-go app srcPath = repoRoot + "/apps/stackpanel-go"; + + version = "0.1.0"; + + # Must match the Go import path of the package that declares Version/GitCommit/BuildDate. + ldflagPkg = "github.com/darkmatter/stackpanel/stackpanel-go/cmd/cli"; + + formatBuildDate = + d: + if builtins.isString d && builtins.stringLength d >= 14 then + "${builtins.substring 0 4 d}-${builtins.substring 4 2 d}-${builtins.substring 6 2 d}T${builtins.substring 8 2 d}:${builtins.substring 10 2 d}:${builtins.substring 12 2 d}Z" + else + "unknown"; + + gitCommit = if flake == null then "unknown" else flake.rev or flake.dirtyRev or "unknown"; + + buildDate = if flake == null then "unknown" else formatBuildDate (flake.lastModifiedDate or ""); in pkgs.buildGoApplication { pname = "stackpanel"; - version = "0.1.0"; + inherit version; # Use repo root as src so local replace directives (../../packages/proto/gen/gopb) # are available in the source tree. pwd points to the app's go.mod location. @@ -47,7 +67,9 @@ pkgs.buildGoApplication { ldflags = [ "-s" "-w" - "-X github.com/darkmatter/stackpanel/apps/stackpanel-go/cmd/cli.Version=0.1.0" + "-X ${ldflagPkg}.Version=${version}" + "-X ${ldflagPkg}.GitCommit=${gitCommit}" + "-X ${ldflagPkg}.BuildDate=${buildDate}" ]; # Go names the binary after the module's last path component (stackpanel-go). diff --git a/prelude.nix b/prelude.nix index 3efd21f6..5d63a879 100644 --- a/prelude.nix +++ b/prelude.nix @@ -28,7 +28,7 @@ { ... }: { prelude = { - theme = "mono"; # color theme for all components; default "prelude" + theme = "mono"; # color theme for all components; default "prelude" # Per-token overrides on top of the theme (null keeps the theme token). # Values: null | hex string | ANSI-256 index. @@ -48,9 +48,9 @@ # palette.surface = null; # palette.secondary = null; - colorProfile = "auto"; # "auto" | "truecolor" | "ansi256"; default "auto" + colorProfile = "auto"; # "auto" | "truecolor" | "ansi256"; default "auto" - project = "stackpanel"; # shown in the MOTD banner and menu header; default "acme" + project = "stackpanel"; # shown in the MOTD banner and menu header; default "acme" # Project commands keyed by public `x` name. The first colon infers the # menu group; the complete key stays callable (e.g. `x db:migrate`). @@ -59,7 +59,7 @@ commands = { dev = { - exec = "dev"; # defaults to the key suffix after the first colon + exec = "dev"; # defaults to the key suffix after the first colon description = "run the dev server"; # group inferred from key: develop # first colon segment; builtins land in "prelude" # key = null; # single-key accelerator (`x `) @@ -68,7 +68,7 @@ # examples = [ "dev" ]; # worked example invocations # args = [ ]; # arg-entry mode: { token, description?, required?, boolean?, options? } # invocation = null; # canonical shell text for duplicate detection; defaults to exec - motd = 1; # MOTD Getting Started sort order; null hides from MOTD + motd = 1; # MOTD Getting Started sort order; null hides from MOTD }; }; @@ -76,14 +76,13 @@ motd = { enable = true; - title = { - text = ./title.txt; # multiline title file; null uses the project-name wordmark + text = ./title.txt; # multiline title file; null uses the project-name wordmark # align = "center"; # left|center|right; default "center" # style = "spine"; # wordmark when text is null: plain|spine|bracketed|label|inline|inverted }; - # background = false; # block fill: null/false | true (theme bg) | color | { relative } | { blend } + background = false; # block fill: null/false | true (theme bg) | color | { relative } | { blend } # windowBackground = false; # full-width window fill; same value forms as background # clearScreen = true; # clear the terminal before rendering # align = "center"; # horizontal placement of the MOTD block: left|center|right From f0eb3e4c116180a5b73ac4bca30ed4135c512686 Mon Sep 17 00:00:00 2001 From: Cooper Maruyama Date: Wed, 9 Sep 2026 17:51:07 -0700 Subject: [PATCH 2/3] chore: preserve existing title-file move Preserves existing working-copy changes for review during DARK-20. --- title.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 title.txt diff --git a/title.txt b/title.txt deleted file mode 100644 index b0f9a906..00000000 --- a/title.txt +++ /dev/null @@ -1,3 +0,0 @@ -____ ___ ____ ____ _ _ ___ ____ _ _ ____ _ -[__ | |__| | |_/ |__] |__| |\ | |___ | -___] | | | |___ | \_ | | | | \| |___ |___ From a3016f9ce48406777d9825bac8ceac289b0accb5 Mon Sep 17 00:00:00 2001 From: cooper Date: Wed, 9 Sep 2026 22:39:17 -0700 Subject: [PATCH 3/3] fix: repair prelude title path and docs placeholder Co-authored-by: multica-agent --- apps/docs/content/docs/reference/modules.mdx | 2 +- nix/stackpanel/core/options/modules/types.nix | 2 +- prelude.nix | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/docs/reference/modules.mdx b/apps/docs/content/docs/reference/modules.mdx index 0dc1b7d8..50fa830b 100644 --- a/apps/docs/content/docs/reference/modules.mdx +++ b/apps/docs/content/docs/reference/modules.mdx @@ -312,7 +312,7 @@ Flake URL (e.g., "github:author/my-module") ## `modules..healthcheckModule` -Name of the doctor module that provides health checks for this module. This links to stackpanel.doctor.. +Name of the doctor module that provides health checks for this module. This links to `stackpanel.doctor.`. | Property | Value | |----------|-------| diff --git a/nix/stackpanel/core/options/modules/types.nix b/nix/stackpanel/core/options/modules/types.nix index f6713818..f79c1b37 100644 --- a/nix/stackpanel/core/options/modules/types.nix +++ b/nix/stackpanel/core/options/modules/types.nix @@ -412,7 +412,7 @@ let default = null; description = '' Name of the doctor module that provides health checks for this module. - This links to stackpanel.doctor.. + This links to `stackpanel.doctor.`. ''; }; }; diff --git a/prelude.nix b/prelude.nix index 5d63a879..8d5ec0c3 100644 --- a/prelude.nix +++ b/prelude.nix @@ -77,7 +77,7 @@ enable = true; title = { - text = ./title.txt; # multiline title file; null uses the project-name wordmark + text = ./.stack/title.txt; # multiline title file; null uses the project-name wordmark # align = "center"; # left|center|right; default "center" # style = "spine"; # wordmark when text is null: plain|spine|bracketed|label|inline|inverted };