Compare commits
No commits in common. "v3" and "main" have entirely different histories.
10
Dockerfile
10
Dockerfile
@ -34,7 +34,6 @@ RUN pacman -S --noconfirm --needed \
|
|||||||
go \
|
go \
|
||||||
github-cli \
|
github-cli \
|
||||||
libnewt \
|
libnewt \
|
||||||
openssh sshfs \
|
|
||||||
&& pacman -Scc --noconfirm
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
# ── Crown Jewel #2: AUR ───────────────────────────────────────────────────────
|
# ── Crown Jewel #2: AUR ───────────────────────────────────────────────────────
|
||||||
@ -55,14 +54,11 @@ RUN sudo -u aurbuild yay -S --noconfirm --needed \
|
|||||||
tailscale \
|
tailscale \
|
||||||
&& sudo -u aurbuild yay -Scc --noconfirm
|
&& sudo -u aurbuild yay -Scc --noconfirm
|
||||||
|
|
||||||
# ── FUSE config: allow_other for sshfs mounts ─────────────────────────────────
|
|
||||||
RUN sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf 2>/dev/null || \
|
|
||||||
echo 'user_allow_other' >> /etc/fuse.conf
|
|
||||||
|
|
||||||
# ── Dev user with host-matching UID/GID ───────────────────────────────────────
|
# ── Dev user with host-matching UID/GID ───────────────────────────────────────
|
||||||
# UID/GID match host so /workspace bind mount has clean permissions both sides.
|
# UID/GID match host so /workspace bind mount has clean permissions both sides.
|
||||||
# aurbuild is parked at UID 9001 so there's no collision with host UID.
|
# aurbuild is parked at UID 9001 so there's no collision with host UID.
|
||||||
RUN set -e; \
|
RUN set -e; \
|
||||||
|
# Group: handle pre-existing GID gracefully (Arch base has users:1000)
|
||||||
if getent group ${USER_GID} >/dev/null; then \
|
if getent group ${USER_GID} >/dev/null; then \
|
||||||
groupmod -n dev "$(getent group ${USER_GID} | cut -d: -f1)"; \
|
groupmod -n dev "$(getent group ${USER_GID} | cut -d: -f1)"; \
|
||||||
else \
|
else \
|
||||||
@ -74,10 +70,6 @@ RUN set -e; \
|
|||||||
# ── Skeleton: bake dotfiles into /etc/skel-arch-dev/ ──────────────────────────
|
# ── Skeleton: bake dotfiles into /etc/skel-arch-dev/ ──────────────────────────
|
||||||
COPY --chown=dev:dev dotfiles/ /etc/skel-arch-dev/
|
COPY --chown=dev:dev dotfiles/ /etc/skel-arch-dev/
|
||||||
|
|
||||||
# ── Roaming toolkit: add_new_machine.sh onto PATH ─────────────────────────────
|
|
||||||
COPY scripts/add_new_machine.sh /usr/local/bin/add_new_machine.sh
|
|
||||||
RUN chmod +x /usr/local/bin/add_new_machine.sh
|
|
||||||
|
|
||||||
# ── Initial seed of /home/dev so plugin bake works at build time ──────────────
|
# ── Initial seed of /home/dev so plugin bake works at build time ──────────────
|
||||||
RUN cp -an /etc/skel-arch-dev/. /home/dev/ && \
|
RUN cp -an /etc/skel-arch-dev/. /home/dev/ && \
|
||||||
chown -R dev:dev /home/dev
|
chown -R dev:dev /home/dev
|
||||||
|
|||||||
132
ROAMING.md
132
ROAMING.md
@ -1,132 +0,0 @@
|
|||||||
# Roaming ~ — arch-dev v3
|
|
||||||
|
|
||||||
Turn neovim-ide into your **roaming home directory**. SSH in from any device,
|
|
||||||
and every server on your tailnet feels like home: one keyring, one shell
|
|
||||||
history, one set of dotfiles, reachable from anywhere.
|
|
||||||
|
|
||||||
This is what makes neovim-ide more than another riced nvim install — it's a
|
|
||||||
control plane for your whole fleet.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The Model
|
|
||||||
|
|
||||||
```
|
|
||||||
You (phone / tablet / chromebook)
|
|
||||||
↓ ssh
|
|
||||||
nvimide.tailnet.ts ← your roaming home (this container)
|
|
||||||
↓ ssh / sshfs over tailnet
|
|
||||||
┌──────────┬──────────┬──────────┐
|
|
||||||
mail proxy dev (any host)
|
|
||||||
```
|
|
||||||
|
|
||||||
You live in nvimide. Files live on remote hosts (mounted via sshfs when you
|
|
||||||
want them). Commands run on remote hosts via short per-host functions.
|
|
||||||
|
|
||||||
Keys never leave nvimide — agent auth handles remote login, remote `sudo`
|
|
||||||
still prompts (the security boundary stays intact).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| `~/.config/roaming/hosts` | Registry — one line per host (source of truth) |
|
|
||||||
| `~/.config/roaming/roaming.zsh` | Engine — generates host functions, mount helpers |
|
|
||||||
| `add_new_machine.sh` | Onboard a new host (key + register + verify) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Run commands on a host
|
|
||||||
|
|
||||||
Each registry entry becomes a command. Dumb by design — pipes and redirects
|
|
||||||
run **locally**, exactly like plain ssh (predictable, no surprises).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mail # interactive shell on mail
|
|
||||||
mail tail -f /var/log/mail.log # TTY allocated — -f works
|
|
||||||
mail sudo systemctl restart postfix # remote sudo prompts for password
|
|
||||||
proxy sudo systemctl reload caddy
|
|
||||||
dev docker compose logs -f
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mount a host's filesystem
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mount_host mail # sshfs the registered path → /workspace/mail
|
|
||||||
ls /workspace/mail/etc/postfix/
|
|
||||||
unmount_host mail # clean unmount (handles stale mounts)
|
|
||||||
```
|
|
||||||
|
|
||||||
Mounts use `reconnect,ServerAliveInterval=15,ServerAliveCountMax=3` so a
|
|
||||||
network blip errors out in ~45s instead of freezing your shell forever.
|
|
||||||
|
|
||||||
### Status & reload
|
|
||||||
|
|
||||||
```bash
|
|
||||||
roaming-status # list hosts + which are mounted
|
|
||||||
roaming-reload # re-read registry after editing hosts by hand
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add a new machine
|
|
||||||
|
|
||||||
```bash
|
|
||||||
add_new_machine.sh mail mail.tailnet.ts root /
|
|
||||||
# 1. ssh-copy-id (one password prompt)
|
|
||||||
# 2. appends to ~/.config/roaming/hosts
|
|
||||||
# 3. verifies key-based login
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, `mail` works in the next shell (or after `roaming-reload`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Registry Format
|
|
||||||
|
|
||||||
```
|
|
||||||
# name fqdn user mount
|
|
||||||
mail mail.tailnet.ts root /
|
|
||||||
proxy proxy.tailnet.ts admin /etc/caddy
|
|
||||||
dev dev-server.tailnet.ts wayne
|
|
||||||
```
|
|
||||||
|
|
||||||
- `user` optional — defaults to `$ROAMING_DEFAULT_USER` or current user
|
|
||||||
- `mount` optional — omit if you only run commands, never mount
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Onboarding a Fresh Client
|
|
||||||
|
|
||||||
The whole point: a new device needs almost nothing.
|
|
||||||
|
|
||||||
1. Install your SSH key to reach nvimide.tailnet.ts
|
|
||||||
2. SSH in — you're home. Dotfiles, history (atuin), keys, the works.
|
|
||||||
|
|
||||||
No per-device key sprawl. nvimide holds the fleet keyring; clients only need
|
|
||||||
to reach nvimide.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Gotchas (learned, baked in)
|
|
||||||
|
|
||||||
- **Pipes are local.** `mail tail -f log | grep err` greps locally. Want it
|
|
||||||
remote? Quote the whole pipeline: `mail 'tail -f log | grep err'`.
|
|
||||||
- **Stale mount frozen?** `fusermount3 -uz /workspace/<name>` force-clears it.
|
|
||||||
- **`tail -f` / `htop` / sudo need a TTY** — handled (`ssh -t` always).
|
|
||||||
- **Agent forwarding (`-A`) is intentionally OFF** — a compromised remote
|
|
||||||
could abuse a forwarded agent. Add per-host later only if you need to hop.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Roadmap (future hardening)
|
|
||||||
|
|
||||||
- **SSH CA + OIDC (Authelia)** — replace `authorized_keys` with short-lived
|
|
||||||
certs issued after Authelia auth. Collapses key management fleet-wide.
|
|
||||||
Deferred: keys stay as the stable fallback until the cert flow is proven.
|
|
||||||
- **autofs** — system-level lazy mount/unmount (60s idle auto-unmount) as an
|
|
||||||
alternative to manual `mount_host`.
|
|
||||||
- **atuin** — single shell-history brain in nvimide, DB in the postgres
|
|
||||||
cluster; retire atuin-server.
|
|
||||||
@ -3,6 +3,9 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
|
# Match host UID/GID for clean /workspace permissions
|
||||||
|
# Set via: UID=$(id -u) GID=$(id -g) docker compose build
|
||||||
|
# Or .env file in repo root
|
||||||
USER_UID: ${UID:-1000}
|
USER_UID: ${UID:-1000}
|
||||||
USER_GID: ${GID:-1000}
|
USER_GID: ${GID:-1000}
|
||||||
image: arch-dev:latest
|
image: arch-dev:latest
|
||||||
@ -12,7 +15,11 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
# Project files — bind mount, host-visible, host-UID-owned
|
||||||
- ./workspace:/workspace
|
- ./workspace:/workspace
|
||||||
|
|
||||||
|
# Stateful home — named volume, survives --rm
|
||||||
|
# Reset with: docker volume rm <project>_arch-dev-home
|
||||||
- arch-dev-home:/home/dev
|
- arch-dev-home:/home/dev
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@ -23,7 +30,8 @@ services:
|
|||||||
- GIT_COMMITTER_NAME=${GIT_NAME:-dev}
|
- GIT_COMMITTER_NAME=${GIT_NAME:-dev}
|
||||||
- GIT_COMMITTER_EMAIL=${GIT_EMAIL:-dev@localhost}
|
- GIT_COMMITTER_EMAIL=${GIT_EMAIL:-dev@localhost}
|
||||||
|
|
||||||
# Capability set — cap_drop ALL then re-add only what's needed
|
# Capability set built up through testing —
|
||||||
|
# cap_drop ALL then re-add only what's needed.
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
@ -33,20 +41,13 @@ services:
|
|||||||
- AUDIT_WRITE # sudoers_audit plugin
|
- AUDIT_WRITE # sudoers_audit plugin
|
||||||
- NET_ADMIN # tailscale
|
- NET_ADMIN # tailscale
|
||||||
- NET_RAW # tailscale
|
- NET_RAW # tailscale
|
||||||
- CHOWN # pacman
|
- CHOWN # pacman temp dir ownership
|
||||||
- DAC_OVERRIDE # pacman
|
- DAC_OVERRIDE # pacman lock files
|
||||||
- FOWNER # pacman
|
- FOWNER # pacman package ownership
|
||||||
- SYS_ADMIN # sshfs/FUSE mounts (roaming ~)
|
|
||||||
|
|
||||||
# Devices: tun for tailscale, fuse for sshfs
|
# Tailscale needs tun device for kernel-mode networking
|
||||||
devices:
|
devices:
|
||||||
- /dev/net/tun:/dev/net/tun
|
- /dev/net/tun:/dev/net/tun
|
||||||
- /dev/fuse:/dev/fuse
|
|
||||||
|
|
||||||
# FUSE mounts need apparmor unconfined on many hosts
|
|
||||||
# (remove if your host doesn't use apparmor)
|
|
||||||
security_opt:
|
|
||||||
- apparmor:unconfined
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
arch-dev-home:
|
arch-dev-home:
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
# ── arch-dev roaming :: host registry ─────────────────────────────────────────
|
|
||||||
# One host per line. Lines starting with # are ignored.
|
|
||||||
#
|
|
||||||
# Fields (whitespace-separated):
|
|
||||||
# name short alias used for the shell function (e.g. `mail`)
|
|
||||||
# fqdn tailnet hostname or any reachable host (e.g. mail.tailnet.ts)
|
|
||||||
# user ssh user (optional, defaults to $ROAMING_DEFAULT_USER or current user)
|
|
||||||
# mount remote path to mount via sshfs (optional, e.g. / or /srv)
|
|
||||||
#
|
|
||||||
# Examples (delete these, add your own):
|
|
||||||
#
|
|
||||||
# name fqdn user mount
|
|
||||||
# mail mail.tailnet.ts root /
|
|
||||||
# proxy proxy.tailnet.ts admin /etc/caddy
|
|
||||||
# dev dev-server.tailnet.ts wayne /srv
|
|
||||||
|
|
||||||
# name fqdn user mount
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
# ╔══════════════════════════════════════════════════════════════════╗
|
|
||||||
# ║ arch-dev roaming :: engine ║
|
|
||||||
# ║ Roaming ~ over the tailnet. Source this from .zshrc. ║
|
|
||||||
# ║ ║
|
|
||||||
# ║ Reads ~/.config/roaming/hosts and generates a function per ║
|
|
||||||
# ║ host so you can run: ║
|
|
||||||
# ║ mail → interactive shell on mail ║
|
|
||||||
# ║ mail tail -f /var/log/x → run with TTY (works for -f, sudo) ║
|
|
||||||
# ║ mount_host mail → sshfs the host's mount at ║
|
|
||||||
# ║ /workspace/mail ║
|
|
||||||
# ╚══════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
export ROAMING_DIR="${ROAMING_DIR:-$HOME/.config/roaming}"
|
|
||||||
export ROAMING_HOSTS="${ROAMING_HOSTS:-$ROAMING_DIR/hosts}"
|
|
||||||
export ROAMING_MOUNT_BASE="${ROAMING_MOUNT_BASE:-/workspace}"
|
|
||||||
export ROAMING_DEFAULT_USER="${ROAMING_DEFAULT_USER:-$USER}"
|
|
||||||
|
|
||||||
# sshfs options that prevent the dreaded indefinite-freeze on network blips.
|
|
||||||
# ServerAliveInterval=15 + CountMax=3 → I/O errors out after ~45s instead of
|
|
||||||
# hanging forever. reconnect re-establishes when the host comes back.
|
|
||||||
export ROAMING_SSHFS_OPTS="reconnect,ServerAliveInterval=15,ServerAliveCountMax=3,follow_symlinks"
|
|
||||||
|
|
||||||
# ── Internal: resolve a host name to its registry fields ──────────────────────
|
|
||||||
# Sets globals: _RH_NAME _RH_FQDN _RH_USER _RH_MOUNT
|
|
||||||
# Returns 1 if not found.
|
|
||||||
_roaming_lookup() {
|
|
||||||
local want="$1"
|
|
||||||
[[ -f "$ROAMING_HOSTS" ]] || return 1
|
|
||||||
local name fqdn user mount
|
|
||||||
while read -r name fqdn user mount; do
|
|
||||||
[[ -z "$name" || "$name" == \#* ]] && continue
|
|
||||||
if [[ "$name" == "$want" ]]; then
|
|
||||||
_RH_NAME="$name"
|
|
||||||
_RH_FQDN="$fqdn"
|
|
||||||
_RH_USER="${user:-$ROAMING_DEFAULT_USER}"
|
|
||||||
_RH_MOUNT="$mount"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done < "$ROAMING_HOSTS"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Internal: the actual remote-exec logic ────────────────────────────────────
|
|
||||||
# No args → interactive shell. Args → run with a TTY (so tail -f / sudo work).
|
|
||||||
# "Dumb" by design: pipes/redirects run LOCALLY, exactly like plain ssh.
|
|
||||||
_roaming_exec() {
|
|
||||||
local name="$1"; shift
|
|
||||||
if ! _roaming_lookup "$name"; then
|
|
||||||
echo "roaming: unknown host '$name' (check $ROAMING_HOSTS)" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
local target="${_RH_USER}@${_RH_FQDN}"
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
ssh -t "$target"
|
|
||||||
else
|
|
||||||
ssh -t "$target" "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── mount_host <name> ─────────────────────────────────────────────────────────
|
|
||||||
mount_host() {
|
|
||||||
local name="$1"
|
|
||||||
if [[ -z "$name" ]]; then
|
|
||||||
echo "usage: mount_host <name>" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if ! _roaming_lookup "$name"; then
|
|
||||||
echo "roaming: unknown host '$name'" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [[ -z "$_RH_MOUNT" ]]; then
|
|
||||||
echo "roaming: '$name' has no mount path in registry" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
local mp="$ROAMING_MOUNT_BASE/$name"
|
|
||||||
# Already mounted?
|
|
||||||
if mountpoint -q "$mp" 2>/dev/null; then
|
|
||||||
echo "roaming: $name already mounted at $mp"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
mkdir -p "$mp"
|
|
||||||
echo "roaming: mounting ${_RH_USER}@${_RH_FQDN}:${_RH_MOUNT} → $mp"
|
|
||||||
sshfs "${_RH_USER}@${_RH_FQDN}:${_RH_MOUNT}" "$mp" \
|
|
||||||
-o "$ROAMING_SSHFS_OPTS" \
|
|
||||||
&& echo "roaming: ✓ mounted" \
|
|
||||||
|| echo "roaming: ✗ mount failed" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── unmount_host <name> ───────────────────────────────────────────────────────
|
|
||||||
unmount_host() {
|
|
||||||
local name="$1"
|
|
||||||
if [[ -z "$name" ]]; then
|
|
||||||
echo "usage: unmount_host <name>" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
local mp="$ROAMING_MOUNT_BASE/$name"
|
|
||||||
if ! mountpoint -q "$mp" 2>/dev/null; then
|
|
||||||
echo "roaming: $name not mounted"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
# fusermount3 first; -z (lazy) as fallback for stale mounts
|
|
||||||
fusermount3 -u "$mp" 2>/dev/null \
|
|
||||||
|| fusermount3 -uz "$mp" 2>/dev/null \
|
|
||||||
|| umount "$mp" 2>/dev/null
|
|
||||||
if mountpoint -q "$mp" 2>/dev/null; then
|
|
||||||
echo "roaming: ✗ failed to unmount $name (stale? try: fusermount3 -uz $mp)" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
rmdir "$mp" 2>/dev/null
|
|
||||||
echo "roaming: ✓ unmounted $name"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── roaming-status ────────────────────────────────────────────────────────────
|
|
||||||
roaming-status() {
|
|
||||||
echo "── registered hosts ──"
|
|
||||||
if [[ -f "$ROAMING_HOSTS" ]]; then
|
|
||||||
local name fqdn user mount
|
|
||||||
while read -r name fqdn user mount; do
|
|
||||||
[[ -z "$name" || "$name" == \#* ]] && continue
|
|
||||||
local mp="$ROAMING_MOUNT_BASE/$name"
|
|
||||||
local mflag=""
|
|
||||||
mountpoint -q "$mp" 2>/dev/null && mflag=" [mounted: $mp]"
|
|
||||||
printf " %-10s %s@%s%s\n" "$name" "${user:-$ROAMING_DEFAULT_USER}" "$fqdn" "$mflag"
|
|
||||||
done < "$ROAMING_HOSTS"
|
|
||||||
else
|
|
||||||
echo " (no registry at $ROAMING_HOSTS)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Generate a function per host ──────────────────────────────────────────────
|
|
||||||
# After this loop, each registry entry `mail` becomes a `mail` command.
|
|
||||||
_roaming_generate() {
|
|
||||||
[[ -f "$ROAMING_HOSTS" ]] || return 0
|
|
||||||
local name fqdn user mount
|
|
||||||
while read -r name fqdn user mount; do
|
|
||||||
[[ -z "$name" || "$name" == \#* ]] && continue
|
|
||||||
# Skip if it would clobber an existing command (safety)
|
|
||||||
if command -v "$name" >/dev/null 2>&1 && ! typeset -f "$name" >/dev/null 2>&1; then
|
|
||||||
echo "roaming: skipping '$name' — conflicts with existing command" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
eval "$name() { _roaming_exec '$name' \"\$@\"; }"
|
|
||||||
done < "$ROAMING_HOSTS"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Reload registry + regenerate functions (call after editing hosts)
|
|
||||||
roaming-reload() {
|
|
||||||
_roaming_generate
|
|
||||||
echo "roaming: reloaded $(grep -cvE '^\s*#|^\s*$' "$ROAMING_HOSTS" 2>/dev/null || echo 0) host(s)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate on source
|
|
||||||
_roaming_generate
|
|
||||||
@ -201,7 +201,3 @@ if [[ -s "$NVM_DIR/nvm.sh" ]]; then
|
|||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Roaming ~ (v3) ────────────────────────────────────────────────────────────
|
|
||||||
# Fleet-wide remote control + sshfs mounts over the tailnet.
|
|
||||||
# Registry: ~/.config/roaming/hosts Add hosts: add_new_machine.sh
|
|
||||||
[[ -f "$HOME/.config/roaming/roaming.zsh" ]] && source "$HOME/.config/roaming/roaming.zsh"
|
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
# ── Roaming ~ (v3) ────────────────────────────────────────────────────────────
|
|
||||||
# Fleet-wide remote control + sshfs mounts over the tailnet.
|
|
||||||
# Registry: ~/.config/roaming/hosts Add hosts: add_new_machine.sh
|
|
||||||
[[ -f "$HOME/.config/roaming/roaming.zsh" ]] && source "$HOME/.config/roaming/roaming.zsh"
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# ╔══════════════════════════════════════════════════════════════════╗
|
|
||||||
# ║ arch-dev roaming :: add_new_machine ║
|
|
||||||
# ║ Onboard a new host into the roaming fleet: ║
|
|
||||||
# ║ 1. push your SSH key (ssh-copy-id) ║
|
|
||||||
# ║ 2. register it in ~/.config/roaming/hosts ║
|
|
||||||
# ║ 3. optionally test the connection + mount ║
|
|
||||||
# ╚══════════════════════════════════════════════════════════════════╝
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROAMING_DIR="${ROAMING_DIR:-$HOME/.config/roaming}"
|
|
||||||
ROAMING_HOSTS="${ROAMING_HOSTS:-$ROAMING_DIR/hosts}"
|
|
||||||
ROAMING_DEFAULT_USER="${ROAMING_DEFAULT_USER:-$USER}"
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<EOF
|
|
||||||
Usage: add_new_machine.sh <name> <fqdn> [user] [mount]
|
|
||||||
|
|
||||||
name short alias for the host (e.g. mail)
|
|
||||||
fqdn reachable hostname (e.g. mail.tailnet.ts)
|
|
||||||
user ssh user (default: $ROAMING_DEFAULT_USER)
|
|
||||||
mount remote path to expose via sshfs (optional, e.g. / or /srv)
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
add_new_machine.sh mail mail.tailnet.ts root /
|
|
||||||
add_new_machine.sh proxy proxy.tailnet.ts admin /etc/caddy
|
|
||||||
add_new_machine.sh dev dev-server.tailnet.ts wayne
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
[[ $# -lt 2 ]] && usage
|
|
||||||
|
|
||||||
NAME="$1"
|
|
||||||
FQDN="$2"
|
|
||||||
USER_ARG="${3:-$ROAMING_DEFAULT_USER}"
|
|
||||||
MOUNT="${4:-}"
|
|
||||||
|
|
||||||
mkdir -p "$ROAMING_DIR"
|
|
||||||
touch "$ROAMING_HOSTS"
|
|
||||||
|
|
||||||
# ── Guard: already registered? ────────────────────────────────────────────────
|
|
||||||
if grep -qE "^\s*${NAME}\s" "$ROAMING_HOSTS" 2>/dev/null; then
|
|
||||||
echo "✗ '$NAME' is already in $ROAMING_HOSTS"
|
|
||||||
echo " edit the file directly to change it"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Step 1: SSH key ───────────────────────────────────────────────────────────
|
|
||||||
echo "── Step 1: pushing SSH key to ${USER_ARG}@${FQDN} ──"
|
|
||||||
echo " (you'll be prompted for the remote password once)"
|
|
||||||
if ssh-copy-id "${USER_ARG}@${FQDN}"; then
|
|
||||||
echo " ✓ key installed"
|
|
||||||
else
|
|
||||||
echo " ✗ ssh-copy-id failed — aborting, nothing registered"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Step 2: register ──────────────────────────────────────────────────────────
|
|
||||||
echo "── Step 2: registering in $ROAMING_HOSTS ──"
|
|
||||||
printf "%-10s %-26s %-8s %s\n" "$NAME" "$FQDN" "$USER_ARG" "$MOUNT" >> "$ROAMING_HOSTS"
|
|
||||||
echo " ✓ registered"
|
|
||||||
|
|
||||||
# ── Step 3: verify ────────────────────────────────────────────────────────────
|
|
||||||
echo "── Step 3: verifying connection ──"
|
|
||||||
if ssh -o BatchMode=yes -o ConnectTimeout=8 "${USER_ARG}@${FQDN}" 'echo ok' >/dev/null 2>&1; then
|
|
||||||
echo " ✓ key-based SSH works"
|
|
||||||
else
|
|
||||||
echo " ⚠ key-based SSH test failed (host may still be fine — check manually)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✓ Done. In a new shell (or after 'roaming-reload'):"
|
|
||||||
echo " $NAME # interactive shell"
|
|
||||||
echo " $NAME sudo systemctl status # run a command"
|
|
||||||
[[ -n "$MOUNT" ]] && echo " mount_host $NAME # sshfs ${MOUNT} → /workspace/$NAME"
|
|
||||||
Loading…
Reference in New Issue
Block a user