homescale/docker-compose.yml
Wayne Hayes c349fe488c Homescale — a roaming home for your fleet
SSH into one stable Tailscale node and reach your whole tailnet from it via
short per-host functions + sshfs mounts. A riced Arch dev container with sshd,
a stateful home, and declarative .env auth. See README.md and ROAMING.md.
2026-06-29 00:17:18 -04:00

115 lines
4.2 KiB
YAML

# nvimide — roaming dev IDE (arch-dev v3). Its OWN thing; follows the
# Tailscale sidecar pattern and rides your tailnet, but is
# not part of that stack.
#
# You SSH into this node from any device; it holds the fleet keyring + roaming
# engine and reaches every other host. It is a STABLE tailnet node (not
# ephemeral) — always reachable, clean MagicDNS name (no -N tombstones) — so
# TS_AUTHKEY omits ?ephemeral=true.
#
# Declarative auth (NixOS-style): user, password, and ssh pubkey all come from
# .env — nothing baked into the image, nothing mounted. Copy .env.example to
# .env and fill it in.
#
# Bring up:
# 1. ACL: add `tag:nvimide` + authorize the OAuth client to mint it.
# 2. cp .env.example .env && fill NVIMIDE_SSH_PUBKEY / NVIMIDE_PASSWORD / TS_*
# 3. docker compose up -d --build
# 4. ssh ${NVIMIDE_USER}@nvimide
name: nvimide
services:
ts-nvimide:
image: tailscale/tailscale:${TAILSCALE_VERSION:-v1.98.4}
hostname: ${NVIMIDE_MAGIC_NAME:-nvimide}
environment:
TS_AUTHKEY: ${TS_OAUTH_CLIENT_SECRET} # NO ?ephemeral=true → stable node
TS_EXTRA_ARGS: --advertise-tags=tag:nvimide
TS_HOSTNAME: ${NVIMIDE_MAGIC_NAME:-nvimide}
TS_ACCEPT_DNS: "true"
TS_AUTH_ONCE: "true"
TS_USERSPACE: "false"
TS_ENABLE_HEALTH_CHECK: "true"
TS_LOCAL_ADDR_PORT: "127.0.0.1:9009"
dns: [1.1.1.1, 1.0.0.1]
devices:
- /dev/net/tun:/dev/net/tun
cap_add: [NET_ADMIN, NET_RAW]
healthcheck: # green only once the IDE's sshd is accepting on :22 in this netns
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9009/healthz"]
interval: 10s
timeout: 15s
retries: 6
start_period: 30s
restart: unless-stopped
nvimide:
build:
context: .
args:
USER_UID: ${UID:-1000}
USER_GID: ${GID:-1000}
image: nvimide:latest
network_mode: "service:ts-nvimide" # share the sidecar's tailnet netns
user: root # sshd needs root (privsep + bind :22); drops to the user on login
environment:
- TERM=xterm-256color
- MOBILE=0
- NVIMIDE_USER=${NVIMIDE_USER:-dev}
- NVIMIDE_PASSWORD=${NVIMIDE_PASSWORD:-}
- NVIMIDE_SSH_PUBKEY=${NVIMIDE_SSH_PUBKEY:-}
# entrypoint seeds /home, then exec's this: provision auth declaratively from
# env (password enables sudo + fallback; pubkey is the real login), host
# keys, sshd in foreground.
command:
- /bin/bash
- -lc
- |
u="${NVIMIDE_USER:-dev}"
[ -n "$$NVIMIDE_PASSWORD" ] && echo "$$u:$$NVIMIDE_PASSWORD" | chpasswd
if [ -n "$$NVIMIDE_SSH_PUBKEY" ]; then
install -d -o "$$u" -g "$$u" -m 700 "/home/$$u/.ssh"
printf '%s\n' "$$NVIMIDE_SSH_PUBKEY" > "/home/$$u/.ssh/authorized_keys"
chown "$$u:$$u" "/home/$$u/.ssh/authorized_keys"; chmod 600 "/home/$$u/.ssh/authorized_keys"
fi
# Persist host keys on the home volume so client known_hosts stays valid
hk="/home/$$u/.ssh/host_keys"
install -d -o "$$u" -g "$$u" -m 700 "$$hk"
if [ ! -e "$$hk/ssh_host_ed25519_key" ]; then
ssh-keygen -q -t ed25519 -f "$$hk/ssh_host_ed25519_key" -N ""
ssh-keygen -q -t rsa -b 4096 -f "$$hk/ssh_host_rsa_key" -N ""
fi
exec /usr/bin/sshd -D -e \
-h "$$hk/ssh_host_ed25519_key" \
-h "$$hk/ssh_host_rsa_key"
volumes:
- nvimide-home:/home/dev # stateful home (roaming engine, history, dotfiles). Phase 2: rclone-sync to object storage.
- nvimide-workspace:/workspace # sshfs roaming mount points land here
cap_drop: [ALL]
cap_add:
- SETUID # sudo / sshd privsep
- SETGID
- AUDIT_WRITE
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SYS_ADMIN # sshfs / FUSE roaming mounts
- NET_BIND_SERVICE # sshd binds :22
- SYS_CHROOT # sshd privilege-separation chroot (/usr/share/empty.sshd)
devices:
- /dev/fuse:/dev/fuse
security_opt:
- apparmor:unconfined
depends_on:
ts-nvimide:
condition: service_healthy
restart: unless-stopped
volumes:
nvimide-home:
external: true
name: nvimide-ts_arch-dev-home
nvimide-workspace: