Skip to content

VM setup: shell detection, dedicated GitHub key, in-script sign-ins, Remote Control service - #28

Merged
modem7 merged 2 commits into
masterfrom
feature/vm-setup-shell-detect
Sep 26, 2026
Merged

modem7 merged 2 commits into
masterfrom
feature/vm-setup-shell-detect

Conversation

@modem7

@modem7 modem7 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

Follow-ups to #26 for Bash/AI/Claude/claude-code-vm-setup.sh: login-shell detection, a dedicated GitHub SSH key, and making it a "run once, walk away, connect from Desktop" script (in-script sign-ins, two-phase run, always-on Remote Control, end-of-run health check).

Detect the login shell (zsh vs bash)

Previously ~/.bashrc was always treated as primary, and ~/.zshrc was created whenever zsh happened to be installed, so a zsh user was told to source ~/.bashrc.

  • The login shell is read from the passwd entry (getent passwd), falling back to $SHELL. That's more reliable than $SHELL, which can be inherited, and the script itself always runs under bash.
  • The env block (PATH, aliases, auto-cd) goes in the login shell's rc file, plus the other shell's rc file only if it already exists. zsh users still get ~/.bashrc, since Ubuntu's skel always creates it. bash users no longer get an unwanted ~/.zshrc.
  • Unsupported shells (fish, dash, …) get a warning listing the PATH entries to add by hand; ~/.bashrc is still set up.
  • The summary's source … hint and a new Shell line in the generated CLAUDE.md reflect the detected shell.

Dedicated GitHub SSH key

  • Generates ~/.ssh/id_ed25519_github (override with GITHUB_SSH_KEY) instead of creating or reusing the default ~/.ssh/id_ed25519, so it can be revoked on its own and is never offered to other hosts.
  • Wires it up with a managed Host github.com block in ~/.ssh/config (IdentitiesOnly yes, file mode 600).
  • gh auth login now uses --skip-ssh-key -s admin:public_key, so gh doesn't upload whichever key it finds first. The dedicated key is uploaded explicitly instead, with a gh auth refresh for the scope if an existing login lacks it.
  • Registration is confirmed with ssh -T using only that key, which works whether or not gh is authenticated.
  • Safe migration: until GitHub is confirmed to accept the new key, the old default key stays in the block as a fallback so existing remotes keep working. The next re-run removes the fallback.
  • Port 22 blocked: the probe falls back to ssh.github.com:443 (with HostKeyAlias github.com) and writes the block for whichever endpoint is reachable.
  • A Host github.com entry the user already wrote is left alone, with a warning saying which IdentityFile to point it at.

Sign in to Claude Code in the script

  • Checks claude auth status --json (.loggedIn); if not signed in and a terminal is attached, runs claude auth login --claudeai. --claudeai because Remote Control only works with a subscription login. CLAUDE_EMAIL pre-fills the sign-in page. New prompt/env: CLAUDE_LOGIN.

Two phases: interaction first, then unattended

  • Phase 1 (about a minute, the only part needing you): minimal prerequisites, git identity, gh + dedicated SSH key + gh auth login, Claude Code install + sign-in.
  • Phase 2: locale, apt upgrade, build tools, Node/Go/Rust, Docker, config (sync or local), skills, shell, cron, service, health check. No prompts, so it's safe to walk away. Previously gh auth login interrupted about 5 minutes in, partway through the apt installs.

Always-on Remote Control (systemd user service)

  • New prompt/env REMOTE_CONTROL_SERVICE: writes ~/.config/systemd/user/claude-remote-control.service running claude remote-control --name <hostname> in the project dir (extra flags via REMOTE_CONTROL_ARGS, e.g. --spawn worktree). Also runs loginctl enable-linger, so it starts at boot without a login session.
  • Restart=always / RestartSec=60: covers server mode exiting after about 10 minutes offline, and also means the service comes up by itself within a minute of claude auth login if you weren't signed in yet.
  • Pre-accepts workspace trust for the project dir in ~/.claude.json, because a headless server can't answer the trust dialog.
  • Skips cleanly (with a warning) where systemd isn't running (containers/WSL). The unit is only rewritten/restarted when it changes.

Health check instead of "go check X" steps

  • At the end the script verifies each piece and prints ✔/!/✘ with a fix for anything not OK (de-duplicated): Claude installed + signed in; gh authenticated; GitHub SSH through ~/.ssh/config (i.e. exactly what git uses); claude-sync.sh doctor; Remote Control service actually staying up; Docker daemon; /var/run/reboot-required.

Test plan

  • Full run twice in a systemd-enabled ubuntu:24.04 container (zsh user, no TTY): phases in the right order; Claude sign-in skipped with a warning; linger enabled; unit enabled from ~/.config/systemd/user; --name correctly quoted; project dir trusted in ~/.claude.json; with no sign-in the service sits in activating (auto-restart); health check reports sign-in ✘ and Remote Control "starts once you sign in"; exit 0
  • Generated unit passes systemd-analyze --user verify
  • Summary unit test: shared fixes (claude auth login) listed once; failure count correct
  • Signed-in Remote Control service and interactive claude auth login not exercised (need a real Pro/Max login)
  • shellcheck clean
  • detect_shell unit cases: zsh ± existing .bashrc, bash ± existing .zshrc, fish
  • Full run twice in ubuntu:24.04 as a zsh login user: block in .zshrc + .bashrc, one block each; interactive zsh gets PATH/aliases/claude and lands in ~/project; summary says source ~/.zshrc
  • Full unattended run twice with SETUP_GITHUB=y, no TTY: dedicated key generated then reused, host keys pinned, ~/.ssh/config block written (600), no default key created, exit 0
  • Live probe against GitHub: registered key → accepted; throwaway key → reachable but denied
  • Container with outbound port 22 rejected by iptables: probe falls back to ssh.github.com:443
  • ~/.ssh/config writer: fallback line present while unregistered and removed after registration; existing user hosts preserved; user-owned Host github.com detected (multi-pattern, case-insensitive) and left alone; HostName github.com under another host not mistaken for one
  • Interactive gh auth login / gh auth refresh upload path not exercised (needs a real GitHub login)

- Write the env block to the login shell's rc file (zsh or bash, from
  the passwd entry), plus the other shell's rc only if it already exists
- Generate ~/.ssh/id_ed25519_github instead of the default key and wire
  it to github.com via a managed ~/.ssh/config block; keep the old key as
  a fallback until ssh -T confirms GitHub accepts the new one
- Fall back to ssh.github.com:443 when port 22 is blocked
…alth check

- Split into an interactive phase (prereqs, git, gh + SSH key, Claude
  install + claude auth login) and a fully unattended phase
- Sign in to Claude Code with claude auth login --claudeai when needed
- Optional always-on claude remote-control systemd user service with
  lingering, Restart=always, and pre-accepted workspace trust
- End-of-run health check replaces the manual verification steps
@modem7 modem7 changed the title VM setup: detect login shell and use a dedicated GitHub SSH key VM setup: shell detection, dedicated GitHub key, in-script sign-ins, Remote Control service Sep 26, 2026
@modem7
modem7 merged commit 4bf3400 into master Sep 26, 2026
1 check passed
@modem7
modem7 deleted the feature/vm-setup-shell-detect branch September 26, 2026 20:41
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