# Homescale ### Your fleet, one word away · a Tailscale-native roaming home > *"I'd far rather be happy than right any day."* And you're far more likely to > be both when your whole fleet answers from a single container you `ssh` into. **Homescale** turns a riced Arch dev container into a **roaming home for your fleet**: one stable Tailscale node you SSH into, from which every host on your tailnet is a single word away. It holds the keyring; you carry nothing but a terminal. Run a coding agent inside it and it becomes a fleet control plane with hands. The IDE is still under the hood — colorscheme, LSP, a git-snapshotted home. It's just no longer the point. The point is **home**. *(Tailscale today → Headscale later: the "home" you scale.)* --- ## Use it ```bash ssh @..ts.net ``` You land in your full stateful home. Each registered host is then just a verb: ```bash web uptime # runs on the host named "web", over the tailnet db sudo systemctl status pg # remote sudo prompts — the boundary holds edge journalctl -u caddy # any registered host mount_host db # sshfs its filesystem → /workspace/db roaming-status # what's registered ``` Each host becomes a shell function. Pipes/redirects run **locally**, exactly like plain ssh — quote the whole pipeline to run it remote. --- ## Onboard a host ```bash add_new_machine.sh NAME NAME..ts.net / roaming-reload ``` Three things that bite, up front: - **Key-only hosts** (`PasswordAuthentication no`) can't be bootstrapped by `ssh-copy-id`. Pre-install Homescale's `~/.ssh/id_ed25519.pub` via a path the host already trusts — keyless `tailscale ssh` is ideal. - **Custom ssh port?** Add a `Host … Port ` block to `~/.ssh/config` (the roaming engine uses plain ssh). - **Aliases don't travel** — `NAME ` is non-interactive ssh. Use real binaries or `~/bin` scripts. --- ## How it's built | Piece | What | |---|---| | `ts-nvimide` | Tailscale sidecar — **stable** node (no `?ephemeral`), `tag:nvimide` | | `nvimide` | the dev image sharing the sidecar's netns, running **sshd as init** | | home volume | stateful home (snapshots, tools, `.ssh`) | | auth | declarative via `.env` (`NVIMIDE_USER` / `NVIMIDE_PASSWORD` / `NVIMIDE_SSH_PUBKEY`) | ```bash cp .env.example .env # fill TS_OAUTH_CLIENT_SECRET, NVIMIDE_SSH_PUBKEY, TS_TAILNET … docker compose up -d --build ``` > The committed compose uses a **managed** home volume so it works out of the box. > To pin it to an existing/migrated volume, override `nvimide-home` in a local > (gitignored) `docker-compose.override.yml`. ### Tailnet ACL (lives in your Tailscale console, not this repo) A **grant** for plain-TCP reach + **two `ssh` rules** — a separate section from grants, needed in **both directions**: ```jsonc { "src": ["tag:nvimide"], "dst": ["*"], "ip": ["*"] } // reach (plain ssh) { "src": ["tag:nvimide"], "dst": ["*"], "users": [""], "action": "accept" } // tailscale ssh OUT { "src": ["*"], "dst": ["tag:nvimide"], "users": [""], "action": "accept" } // tailscale ssh IN — "Go Home!" ``` --- ## Deeper - **`ROAMING.md`** — the roaming engine: registry format, host-functions, sshfs mounts, the gotchas (host-key persistence, the `CAP_SYS_CHROOT` sshd needs, the sidecar healthcheck that must not gate on the app's `:22`). --- *MIT — fork it, name your node something silly, make it home.*