Skip to content

Ssh remote fix - #35

Closed
tommyrharper wants to merge 130 commits into
kunchenguid:mainfrom
tommyrharper:ssh-remote-fix
Closed

Ssh remote fix#35
tommyrharper wants to merge 130 commits into
kunchenguid:mainfrom
tommyrharper:ssh-remote-fix

Conversation

@tommyrharper

Copy link
Copy Markdown

Thanks for the interest

Thanks for taking an interest in these dotfiles.
This repo is public so you can read it, learn from it, and fork it freely, but it is Kun's personal setup and does not accept pull requests or feature requests.
If you want to adapt something, please fork the repo and make it yours.
If you found a bug, please open a bug report issue instead of sending a patch.
Pull requests are auto-closed here so the repo can stay personal and predictable.

tommyrharper and others added 30 commits July 29, 2026 10:33
…strap

hostLabel now lives as a single let-binding in flake.nix (mirroring the
existing `user` pattern) instead of being hardcoded separately in
flake.nix, rebuild.sh, and bootstrap.sh. Both shell scripts now read the
value back out of flake.nix via sed, the same way bootstrap.sh already
does for the username.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
refactor: single-source hostLabel definition across flake.nix, rebuild.sh, bootstrap.sh
Alternative to PR #3's --basic CLI flag approach, opened separately so
both can be compared side by side before deciding which to keep.

- configuration.nix: includePersonalCasks module arg splits casks into
  basicCasks (dev tooling) and personalCasks (this Mac's GUI apps).
- flake.nix: a single darwinConfigurations.${hostLabel} output computes
  includePersonalCasks = hostLabel != "basic". hostLabel does double
  duty as both the host's display name (from the already-merged PR #1)
  and the profile switch: any value except the literal "basic" gets
  personal casks too.
- bootstrap.sh/rebuild.sh are unchanged from main - they already just
  read hostLabel and switch to it, so no flag or extra variable is
  needed at all.

This design went through two earlier iterations in conversation before
landing here: first a --basic CLI flag with an interactive confirm
prompt, then a separate activeProfile variable alongside a second,
fixed-name "basic" darwinConfigurations output. That second approach
was rejected by review because it reintroduced the exact collision the
user had found by testing hostLabel = "basic" against the original
single-host flake (dynamic attribute "basic" already defined) - keeping
both a ${hostLabel} output and a literal "basic" output meant renaming
hostLabel to "basic" still crashed evaluation. Collapsing to one
dynamically-keyed output removes that collision by construction, and
matches what the user actually wanted: hostLabel = "mac" or
hostLabel = "basic" as the one thing you edit.

Verified via nix build --dry-run and nix eval .config.homebrew.casks
with hostLabel set to both "mac" (all 7 casks) and "basic" (wezterm/
claude-code only, confirmed by temporarily flipping the value in place
and reverting), and bash -n on the untouched bootstrap.sh/rebuild.sh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per user feedback: hostLabel should stay purely cosmetic (the host's
display name) and not also serve as the personal/basic profile switch.
Replaces includePersonalCasks = hostLabel != "basic" with a separate,
plain includePersonalCasks = true; boolean in flake.nix - toggle it
directly instead of relying on hostLabel's value meaning something
special.

configuration.nix is unchanged (already just takes includePersonalCasks
as a required arg). bootstrap.sh/rebuild.sh are unaffected either way,
since they only ever read hostLabel for naming, never for the cask
decision.

Verified via nix build --dry-run and nix eval .config.homebrew.casks
with includePersonalCasks set to both true (all 7 casks) and false
(wezterm/claude-code only), and confirmed hostLabel = "basic" now has
zero special meaning - with includePersonalCasks left at true it still
installs all 7 casks, proving the two are fully decoupled.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Trims verbosity and iteration-history framing from the docs/comments
added for the hostLabel/includePersonalCasks split, so they read as a
deliberate design rather than a fix-up trail.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat: split homebrew casks into basic and personal profiles
Adds basicBrews (herdr, thefuck, skills) and personalBrews (empty for
now) to configuration.nix, following the same pattern as
basicCasks/personalCasks. Both brews and casks are gated by the single
existing includePersonalCasks toggle in flake.nix - no separate
includePersonalBrews, per explicit request.

Behavior is unchanged today since personalBrews is empty; the split
just makes it structurally ready for personal-only brew formulas.

Verified via nix build --dry-run and nix eval .config.homebrew.{brews,casks}
with includePersonalCasks set to both true (all 3 brews, all 7 casks)
and false (still all 3 brews since personalBrews is empty, 2 casks).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Now that the toggle gates both brews and casks, "Casks" in the name
was misleading. usePersonalSetup is more accurate and leaves room for
future personal-only additions beyond Homebrew.

Also trims the doc callouts explaining that hostLabel and this toggle
are separate concerns - that's implicit from the code, no need to
spell it out.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat: split brews into basic/personal, rename usePersonalSetup toggle
…c brew

homebrew.onActivation.cleanup is "zap", so anything installed with a bare
`brew install`/`brew install --cask` outside this config gets removed
(app data included) on the next rebuild. Declared everything currently
on this machine that was missing from configuration.nix:

- basicCasks: add codex (already used via the `co` alias in home.nix),
  alongside the existing claude-code.
- personalCasks: add altair-graphql-client, mongodb-compass, todoist,
  anki, ipfs, ipfs-desktop, iterm2, vlc, zoom. Keep just "todoist" (drop
  the legacy "todoist-app" cask name it was renamed from). Drop the
  Spotify cask - no longer wanted.
- personalBrews: add the smart-contract toolchain (echidna, solidity,
  solc-select, plz-cli, ocaml, opam, tenderly, memo), the Python/Postgres
  toolchain (postgresql@15, libpq, pyenv, python@3.11), and the
  remaining installed CLI leaves (asdf, bzip2, cmake, ekhtml, ffmpeg,
  gh, git-filter-repo, gnu-tar, lcov, libusb, tree, wget, yarn).
  Left out jq and neovim - already declared via nix in home.nix, so the
  brew copies were pure duplicates. Left out spotify-tui/spotifyd along
  with the Spotify cask.

Verified with `nix flake check --no-build` that darwinConfigurations.mac
still evaluates after the change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat: declare personal-Mac brews and casks to prevent zap cleanup
home/.config/herdr/plugins.json and .plugins.lock are written by herdr
itself at runtime (its plugin_registry.rs persistence module), not
user-authored config, so they belong alongside the other herdr runtime
artifacts already ignored in this block.
chore: ignore herdr plugin-registry files
…vish-axi

Each tool's own setup installer already applied this change live on the
machine (via the home-manager symlink aliasing ~/.claude/settings.json to
this repo file), but it never landed as a reviewed, committed change. Also
adds skipDangerousModePermissionPrompt to match the live configuration.
tommyrharper and others added 28 commits August 24, 2026 14:11
Points future agents at the shared shape (gitignored real file +
committed .example, Include/read by path) instead of reinventing it
for the next per-machine secret.
Colima and other tools need to keep rewriting ~/.ssh/config freely, so
whole-file management via programs.ssh fought that. Instead home.nix now
symlinks two dotfiles-owned fragments (a per-platform public file with safe
defaults, a gitignored private file for per-host secrets) and an activation
script idempotently prepends Include lines for them, leaving the rest of
~/.ssh/config untouched.
Completes the fragment + auto-Include model: removes programs.ssh from
home.nix in favor of mkOutOfStoreSymlink for the two fragments plus a
home.activation script that idempotently prepends their Include lines to
~/.ssh/config (respecting $DRY_RUN_CMD, never touching existing content).
Updates .gitignore, README, AGENTS.md, and the SSH tests for the new
dotfiles.config.public/.private naming; re-pins the Ubuntu drvPath test
since removing programs.ssh changes the evaluated darwin derivation.
…roach

feat(home): manage SSH config as included fragments
Captain reported herdr missing after bootstrap.sh + rebuild.sh on Ubuntu.
Reproduced end-to-end in a clean Ubuntu 22.04 container: home-manager
switch completed successfully but herdr was never installed anywhere.
Root cause: tool-selection.nix's useNative correctly classified herdr
(and claude-code/codex/skills/pi-coding-agent) as needing a non-Nix
installer, but tool-selection.nix's nativeTools output was never
consumed by home.nix - no installer ever ran for any of them, matching
what the docs already said but confirming it end-to-end rather than
trusting the docs alone.

Adds a nativeInstallUrl field to tools.nix (set for herdr only, pointing
at its own verified non-interactive install.sh from herdr.dev/docs/install),
a nativeInstallTools helper in tool-selection.nix filtering useNative's
output down to entries with that field, and a home.activation script in
home.nix that runs it on rebuild (skipping if already installed) with
~/.local/bin added to home.sessionPath so the binary is reachable
afterward. claude-code/codex/pi-coding-agent are left without the field
since their installers are interactive or rewrite shell rc files
themselves, fighting home-manager's declarative config - forcing them
through the same unattended mechanism isn't safe, so they still need
manual installation per their own docs.

Verified post-fix in the same container: herdr installs during
home-manager switch and is reachable on PATH in both bash and zsh login
shells. Added regression coverage in tests/ubuntu-support.test.sh
(test_linux_herdr_native_install_wired, test_darwin_native_install_absent)
that would have caught this - it checks the activation script actually
mentions herdr and its install URL, not just that useNative classifies it
correctly, and that macOS's darwinConfigurations.mac evaluation is
unaffected. Also re-pinned EXPECTED_DARWIN_DRVPATH, which had already
drifted stale on main before this branch (confirmed via git stash that
the old pin fails identically without this branch's diff).
…tallers on Ubuntu

Finishes the native-install mechanism PR #25 built for herdr: each remaining
useNative tools.nix entry now has a verified non-interactive Linux install
path. claude-code, codex, and pi-coding-agent get a nativeInstallUrl (their
own official install.sh, confirmed non-interactive with no /dev/tty attached
by running each live in a clean Ubuntu 22.04 container); skills has no
install script upstream at all, so it gets a new nativeInstallNpmPackage
field instead, plumbed through the same activation-script mechanism. Adds
nativeInstallBinName for the two tools (claude-code, pi-coding-agent) whose
real ~/.local/bin launcher name differs from their tools.nix entry name, so
the "already installed" skip check doesn't re-run their installer every
rebuild. Adds pkgs.nodejs to home.packages on Linux, since skills and
pi-coding-agent's npm-backed launchers need node on PATH at runtime, not
just during install - verified this also works with node's own directory
read-only, matching how it lives in the Nix store. Extends
tests/ubuntu-support.test.sh accordingly and confirms
darwinConfigurations.mac's drvPath stays byte-for-byte unchanged.
Reproduced on a genuinely minimal jammy container (Docker Hub's ubuntu:22.04
happens to ship tar, masking this in prior testing): codex's installer fails
with "tar is required to install Codex." on a base image that lacks tar, and
because installNativeTools concatenates every tool's install block into one
activation script with no per-tool error isolation, that failure (under
set -e/pipefail) aborted the script before it ever reached herdr - next in
tools.nix's ordering - or skills/pi-coding-agent after it.

- Add gnutar to home.packages (Ubuntu-only) and to installNativeTools' own
  curated PATH export, so tar is Nix-guaranteed rather than assumed from the
  base image.
- Wrap each tool's install subshell with `|| echo "WARNING: ..." >&2` so one
  tool's failure is reported loudly but no longer blocks the rest.
- Add regression coverage: gnutar presence, and a network-free fault-isolation
  test that patches the real generated activation script with local stand-ins
  to prove one broken installer doesn't block the other four.
…e-installer

fix: harden Ubuntu native tool installation
Ubuntu remotes with no desktop/gnome-keyring had no persistent ssh-agent
process: AddKeysToAgent had nothing long-lived to add keys to, so each
git/ssh invocation re-prompted for the key passphrase. Even Ubuntu's own
bundled ssh-agent user unit no-ops here since it's gated on an X11 session
marker file that doesn't exist on a headless server.

Enable home-manager's native services.ssh-agent module on Linux only: a
systemd --user unit keeps one agent alive until logout/reboot, with
SSH_AUTH_SOCK wired into zsh automatically. macOS is untouched (already
covered by launchd + Keychain); darwinConfigurations.mac's drvPath is
byte-for-byte unchanged.

Verified end-to-end in a systemd-PID1 Ubuntu 22.04 container: reproduced
the bug (fresh login, agent running but no key cached, prompts every
time), applied the fix, added a passphrase-protected key once, then
confirmed ssh-add -l lists it across multiple independent fresh login
sessions with no re-prompt, and that it survives with no active session
via loginctl linger.
The prior fix enabled home-manager's services.ssh-agent systemd --user unit
but never enabled lingering. Verified on a real Ubuntu VM (Lima, genuine
independent SSH login sessions, not docker exec) that without
loginctl enable-linger, systemd-logind tears down the user's whole
systemd --user instance - agent and any cached key included - the moment
the last SSH session closes, so every fresh connection gets an empty agent
and the passphrase prompt returns every time, exactly as reported.

home.activation.enableSshAgentLinger now runs loginctl enable-linger on
every Linux rebuild (no sudo needed, takes effect immediately). It's a
no-op on Darwin but still shifts the generated activation script text, so
tests/ubuntu-support.test.sh's pinned EXPECTED_DARWIN_DRVPATH moved too.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for taking an interest in these dotfiles.
This repo is public so you can read it, learn from it, and fork it freely, but it is Kun's personal setup and does not accept pull requests or feature requests.
If you want to adapt something, please fork the repo and make it yours.
If you found a bug, please open a bug report issue instead of sending a patch.
Pull requests are auto-closed here so the repo can stay personal and predictable.

@github-actions github-actions Bot closed this Aug 24, 2026
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.

1 participant