Skip to content

feat(metal-agent): default the model store and state dir under XDG_DATA_HOME off macOS - #2528

Merged
Defilan merged 1 commit into
defilantech:mainfrom
cnuss:split/2519-xdg-default-paths
Nov 13, 2026
Merged

Defilan merged 1 commit into
defilantech:mainfrom
cnuss:split/2519-xdg-default-paths

Conversation

@cnuss

@cnuss cnuss commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

What

Off macOS, the agent's model store and state directory now default to $XDG_DATA_HOME/llmkube/models and $XDG_DATA_HOME/llmkube/metal-agent. When XDG_DATA_HOME is unset or not an absolute path, the defaults are ~/.local/share/llmkube/models and ~/.local/share/llmkube/metal-agent. macOS keeps ~/Library/Application Support/llmkube/....

  • New datadir.Dir(home) in pkg/datadir. resolveModelStorePath (cmd/metal-agent/main.go), resolveStateDir (pkg/agent/ingress_wiring.go) and selfupdate.ResolveInstallRoot (the foreman-agent install root) all use it, which replaces selfupdate's per-OS install_root_*.go files. A relative XDG_DATA_HOME used to give the foreman-agent a relative install root; it is now ignored there too (see Miscellaneous fixes).
  • stop-engines logs the state directory it resolved (stateDir on stopping recorded engines, next to engineRecords, which replaces that line's records field), and the agent's engine records directory startup line also carries stateDir. If the two run with different environments, comparing the lines shows whether they resolved different directories. The README's stop-engines section says so.
  • The help text for --model-store, --state-dir and stop-engines --state-dir now lists both defaults.
  • deployment/macos/README.md: the --state-dir row in the flag table and the "Model store" section now give the default used off macOS as well.

--model-store and --state-dir still override the defaults. The ownership and world-writable checks on the store are unchanged.

Why

Both defaults were hard-coded to ~/Library/Application Support. On Linux that creates a Library directory in the user's home, which no Linux tool expects. This is part of #2481. #2520 and #2525 build on it.

Fixes #2519

How

dir(goos, home, xdgDataHome) holds the logic, and datadir.Dir calls it with runtime.GOOS and $XDG_DATA_HOME. A relative XDG_DATA_HOME is ignored, as the XDG Base Directory spec requires. TestDirPerOS is a table test covering darwin, Linux without XDG_DATA_HOME, Linux with an absolute one, and Linux with a relative one. TestDefaultStateDir, TestResolveModelStorePath and TestResolveInstallRoot pin concrete paths for each runtime.GOOS, with XDG_DATA_HOME both unset and set. pkg/agent's TestMain unsets XDG_DATA_HOME next to its temporary HOME, so no test writes under the real data directory.

The issue's open question stays open: this PR puts the state directory, which holds the ingress TLS key, under $XDG_DATA_HOME rather than $XDG_STATE_HOME.

What I ran:

  • make gate BASE_REF=origin/main on the final commit: every step passed.
  • GOOS=linux bin/golangci-lint run ./... and GOOS=darwin bin/golangci-lint run ./...: 0 issues.
  • make lint-deadcode and GOOS=linux make lint-deadcode: 0 issues.
  • I ran the new and changed tests on Linux in a golang:1.27.4 container, both with XDG_DATA_HOME unset and with XDG_DATA_HOME=/xdg. pkg/datadir, pkg/selfupdate and cmd/metal-agent passed in both runs, and so did TestDefaultStateDir and the TestRunStopEngines tests in pkg/agent. With XDG_DATA_HOME pointed at an empty directory, the full pkg/agent suite wrote nothing there. (Four envtest tests could not start in that container because the mounted bin/ holds a darwin etcd; they pass in the gate.)

Assisted-by: Claude Code (Claude Opus 5.5) split this out of #2482, wrote the description and ran the checks above in my session.

Miscellaneous fixes

  • selfupdate.ResolveInstallRoot (foreman-agent) now ignores a relative XDG_DATA_HOME per the XDG spec and falls back to ~/.local/share/llmkube/<binary>. Before, a relative value gave an install root relative to the working directory. Only applies when --install-root is unset.
  • Shared code that assumed the host is a Mac now says "host": the model cache slot refusal ("to serve a file already on this host ..."), the --legacy-direct-endpoints warning ("anyone who can reach this host ..."), and a test comment in executor_sha256_test.go.

Checklist

  • Tests added/updated
  • make test passes locally
  • make lint passes locally
  • Commit messages follow conventional commits
  • All commits are signed off (git commit -s) per DCO
  • AI assistance (if any) is disclosed above, per CONTRIBUTING.md
  • Documentation updated (if user-facing change)

@cnuss
cnuss requested a review from Defilan as a code owner November 13, 2026 02:27
@codecov

codecov Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.16667% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cmd/metal-agent/main.go 20.00% 4 Missing ⚠️
pkg/selfupdate/updater.go 80.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

…TA_HOME off macOS

Off macOS the model store and state directory default to
$XDG_DATA_HOME/llmkube/models and $XDG_DATA_HOME/llmkube/metal-agent,
or ~/.local/share/llmkube/... when XDG_DATA_HOME is unset or not
absolute. macOS keeps ~/Library/Application Support/llmkube.

The lookup lives in a new leaf package, pkg/datadir. The foreman-agent's
self-update install root uses it too, replacing its per-OS files, so a
relative XDG_DATA_HOME is now ignored there as well instead of giving a
path relative to the working directory.

The default-path tests pin concrete paths per OS, and pkg/agent's
TestMain unsets XDG_DATA_HOME so no test writes under the developer's
data directory.

stop-engines logs the state directory it resolved, as stateDir next to
engineRecords (formerly records), and the agent's engine records
startup line carries stateDir too, so the two can be compared when
their environments differ. Flag help and the macOS README give both
defaults.

The model cache slot refusal, the legacy direct endpoints warning and a
test comment say "host" instead of "Mac".

Fixes defilantech#2519

Signed-off-by: Christian Nuss <cnuss@users.noreply.github.com>
@cnuss
cnuss force-pushed the split/2519-xdg-default-paths branch from 70e7053 to dfaf02a Compare November 13, 2026 03:12
@cnuss

cnuss commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review findings in the squashed commit (dfaf02a):

  • Duplicate XDG logic. The helper moved to a leaf package, pkg/datadir, and selfupdate.ResolveInstallRoot uses it in place of its per-OS files. A relative XDG_DATA_HOME is now ignored there too, per the XDG spec; this is the one visible change for the foreman-agent.
  • Tests that couldn't fail. TestDefaultStateDir, TestResolveModelStorePath and TestResolveInstallRoot pin concrete paths per OS. With the darwin branch removed, all three fail.
  • Test isolation. pkg/agent's TestMain unsets XDG_DATA_HOME. A full Linux run with it pointed at an empty directory wrote nothing there.
  • Agent and stop-engines resolving different dirs. Both now log stateDir, so the two lines can be compared. The README's stop-engines section says to pass --state-dir when they differ.

@Defilan Defilan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

macOS resolves exactly as before, a relative XDG_DATA_HOME is ignored in all three callers, and ResolveInstallRoot still errors when it has no absolute root. llmkube metal join keeping the macOS path is fine while join refuses non-darwin hosts.

Small doc nits, none blocking:

  • pkg/agent/agent.go around line 287: the StateDir comment still names only the macOS default.
  • pkg/selfupdate/updater.go around line 70: the InstallRoot field comment omits the XDG_DATA_HOME rule that ResolveInstallRoot's doc now states.
  • cmd/metal-agent/stopengines.go: the --state-dir help drops the ~/.local/share/llmkube/metal-agent fallback the agent's two help strings give.

@Defilan
Defilan added this pull request to the merge queue Nov 13, 2026
Merged via the queue into defilantech:main with commit 883e1f8 Nov 13, 2026
32 checks passed
@cnuss

cnuss commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks. The three doc nits (the StateDir comment, the InstallRoot field comment and the stop-engines --state-dir help) are filed for #2520, the PR that makes these paths reachable on Linux: comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Raspberry Pi] Default the agent's model store and state directory under XDG_DATA_HOME off macOS

2 participants