Add README summarizing the design and known gotchas
Compacts the design discussion into a reference: the workflow model (pure Remote-SSH by default, opportunistic local clones for self-contained python+uv projects only), how to run the script, the home-machine prerequisite (Tailscale SSH), and the gotchas hit while building it (PATH issues after su, stale sudo group membership, the token-paste-into-shell bug, the spurious-space host alias bug, and the tailnet-wide MagicDNS requirement) so they don't need re-debugging. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
20af6b67a8
commit
9999e1da41
1 changed files with 128 additions and 0 deletions
128
README.md
Normal file
128
README.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# travel laptop bootstrap
|
||||||
|
|
||||||
|
A disposable, extremely light Debian laptop for working on the road. If it's
|
||||||
|
lost, stolen, or dropped in the sea: install Debian, fetch `bootstrap.sh`,
|
||||||
|
run it, give it a couple of credentials, and you're back to full working
|
||||||
|
order in minutes.
|
||||||
|
|
||||||
|
## The idea
|
||||||
|
|
||||||
|
- The travel laptop stays minimal: vim, tmux, mosh, tailscale, VS Code
|
||||||
|
(+ Remote-SSH extension), `uv`, and a locked-down browser. No language
|
||||||
|
runtimes, no Docker, no databases.
|
||||||
|
- **Default workflow is pure Remote-SSH, nothing local.** VS Code connects
|
||||||
|
over Tailscale to your home machine (or another tailnet box) and edits
|
||||||
|
that machine's checkout directly. Actually running/building/testing code
|
||||||
|
always happens there too - never on the travel laptop - so a project that
|
||||||
|
generates tens of GB of logs/DB state on a "heavy" run never becomes the
|
||||||
|
travel laptop's problem, regardless of whether that particular project
|
||||||
|
is big or small.
|
||||||
|
- **Local clones are opportunistic, not default.** Only migrate a repo to
|
||||||
|
the laptop itself if it's genuinely self-contained (Python + `uv`, no
|
||||||
|
services/DB/heavy runtime state) and you specifically want it editable
|
||||||
|
offline. Everything else (e.g. anything needing a distrobox container,
|
||||||
|
a full toolchain, Docker) stays remote-only, always.
|
||||||
|
- Offline mode = edit + commit only. Building/running resumes once you're
|
||||||
|
back online and reconnect via Remote-SSH.
|
||||||
|
- Idempotent, not transactional: every step checks its own state before
|
||||||
|
acting, so the script is safe to run any number of times. If a step
|
||||||
|
fails, fix the underlying issue and re-run - already-done steps skip,
|
||||||
|
nothing needs to be undone.
|
||||||
|
|
||||||
|
## Running it
|
||||||
|
|
||||||
|
On a fresh Debian install, once you're on the network:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update && sudo apt install -y curl
|
||||||
|
curl -fsSL https://git.brainpaingames.com/tommy/infra/raw/branch/main/bootstrap.sh -o bootstrap.sh
|
||||||
|
chmod +x bootstrap.sh
|
||||||
|
./bootstrap.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
It prompts for:
|
||||||
|
- **Forgejo instance URL** and a **device/key label** (remembered across
|
||||||
|
runs in `~/.config/travel-bootstrap/config` and offered as the default
|
||||||
|
next time - just hit Enter to reuse them).
|
||||||
|
- **Tailscale login** (interactive, opens a URL to approve the device).
|
||||||
|
- A **Forgejo access token**, used once to register a fresh SSH keypair via
|
||||||
|
the API, never stored. Get one at: your Forgejo instance -> avatar (top
|
||||||
|
right) -> Settings -> Applications -> Manage Access Tokens -> grant
|
||||||
|
`write:user` scope -> Generate Token -> copy it immediately, it's shown
|
||||||
|
once. When prompted, paste it and press **Ctrl-D** (not Enter) to submit.
|
||||||
|
|
||||||
|
What it does, in order: installs packages, joins Tailscale, regenerates
|
||||||
|
`~/.ssh/config` from the live tailnet peer list (a wildcard entry covering
|
||||||
|
the whole tailnet's DNS suffix, plus one named `Host` alias per peer -
|
||||||
|
regenerated fresh every run, so new peers just show up next time), registers
|
||||||
|
the new SSH key to Forgejo, and hardens Firefox (forced incognito, no
|
||||||
|
history, forced uBlock Origin). Ends with a summary and, if everything
|
||||||
|
succeeded, a concrete "here's what to do next" block listing the tailnet
|
||||||
|
hosts it found.
|
||||||
|
|
||||||
|
## Connecting to a project
|
||||||
|
|
||||||
|
Open VS Code -> `Ctrl+Shift+P` -> **Remote-SSH: Connect to Host...** -> pick
|
||||||
|
the home machine's alias from the list -> `File > Open Folder`, same as
|
||||||
|
opening a folder locally, just on the remote filesystem.
|
||||||
|
|
||||||
|
For a project living inside a **distrobox** container on the remote:
|
||||||
|
Remote-SSH gets you the host's shell, not the container - run
|
||||||
|
`distrobox enter <name>` in the terminal same as you would locally.
|
||||||
|
Nothing about arriving over SSH changes that. Tighter integration (VS Code
|
||||||
|
extensions actually running inside the container) is possible later via
|
||||||
|
the Dev Containers extension's "Attach to Running Container," launched
|
||||||
|
from within an already-open Remote-SSH window - not set up yet.
|
||||||
|
|
||||||
|
## On the home machine side
|
||||||
|
|
||||||
|
One prerequisite that lives outside this script, on whichever machine you
|
||||||
|
Remote-SSH into:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo tailscale set --ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
This lets Tailscale itself authorize SSH logins via your tailnet identity,
|
||||||
|
so the travel laptop's key never needs to be manually added to
|
||||||
|
`~/.ssh/authorized_keys` there. (The SSH key this script registers to
|
||||||
|
Forgejo is for a *different* purpose - git operations against Forgejo, a
|
||||||
|
separate trust relationship from logging into the home machine.)
|
||||||
|
|
||||||
|
## Gotchas hit while building this (so they don't get re-debugged)
|
||||||
|
|
||||||
|
- **`usermod`/`visudo`: command not found** after `su` - not missing, just
|
||||||
|
not on `PATH`. Use `su -` (with the dash) for a proper root login shell,
|
||||||
|
or call by full path (`/usr/sbin/usermod`, `/usr/sbin/visudo`).
|
||||||
|
- **Group membership looks correct but `sudo` still refuses** - group
|
||||||
|
changes only apply to *new* login sessions. Check with plain `id` (no
|
||||||
|
args) in the actual session you're testing in; if `sudo` isn't listed
|
||||||
|
there even though `id <user>` shows it, the session is stale - reboot or
|
||||||
|
fully re-login.
|
||||||
|
- **Forgejo API call fails with a vague error** - the script reports the
|
||||||
|
real HTTP status now (401 = bad/expired token, 403 = missing
|
||||||
|
`write:user` scope, 404 = check the instance URL).
|
||||||
|
- **A pasted token ends up executed as a shell command** - some "copy
|
||||||
|
token" buttons include a trailing newline. A plain `read` stops at that
|
||||||
|
newline and leaves the rest of the paste sitting in the terminal's input
|
||||||
|
buffer, which the shell then runs once the script exits. Fixed by
|
||||||
|
reading raw stdin until EOF (paste, then Ctrl-D) instead of stopping at
|
||||||
|
the first newline.
|
||||||
|
- **A generated `Host` alias had a space in it** - Tailscale's raw
|
||||||
|
`HostName` field is whatever the OS reports (an Android phone's name can
|
||||||
|
be e.g. `"Pixel 9"`), which breaks unquoted in an SSH config `Host` line.
|
||||||
|
Fixed by deriving the alias from the already-sanitized `DNSName` instead.
|
||||||
|
- **`.ts.net` names don't resolve, even a device resolving its own name**
|
||||||
|
- MagicDNS has to be switched on tailnet-wide (Tailscale admin console ->
|
||||||
|
DNS tab), separately from any per-device setting. Each device always
|
||||||
|
gets a `.ts.net` name assigned regardless of whether this is on - it
|
||||||
|
just won't actually resolve until it is.
|
||||||
|
|
||||||
|
## Deliberately deferred / manual
|
||||||
|
|
||||||
|
- **Disk encryption (LUKS)** - a Debian-installer-time choice, not
|
||||||
|
something this script can retrofit. Worth doing given this machine holds
|
||||||
|
an SSH key, even if the disk otherwise stays close to empty.
|
||||||
|
- **Browser choice**: Firefox, for its declarative `policies.json`.
|
||||||
|
- **Window manager**: left as whatever Debian's installer gives you; a
|
||||||
|
minimal WM (sway/i3) would cut RAM/disk further if that becomes worth it.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue