From e382fa1e33ba15c784cd8f221fabf14706a7c34e Mon Sep 17 00:00:00 2001 From: wayne Date: Mon, 27 Apr 2026 18:45:09 -0400 Subject: [PATCH 1/6] v2.1: add imagemagick, chafa, jp2a for ASCII workflow --- CLAUDE.md | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 1 + README.md | 133 +++++++++++++++++++++------------------------- 3 files changed, 215 insertions(+), 72 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..11f4621 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,153 @@ +# arch-dev — Project Context for Claude Code + +## What this is +A portable, stateless Arch Linux development environment running in Docker. +Headless, riced, mobile-aware. Built for serious dev work from any box — +including from a phone via Termius. + +The tagline that stuck: **"Riced Neovim IDE"** + +--- + +## The Human + +Wayne. Works primarily from mobile (Android + Termius). +Prefers vim keybindings everywhere. Wants things clean and professional — +"Apple good" was the benchmark set early on. Doesn't want walls of text. +Will call you out if you guess instead of reading docs. +Has a Gitea instance at https://code.waynehayesdevelopment.com + +--- + +## Architecture + +``` +arch-dev/ +├── Dockerfile # Arch rolling release, pacman + AUR (yay) +├── docker-compose.yml # Named volume for stateful /home/dev +├── entrypoint.sh # First-run seeding + dotfile update detection +├── .gitignore +├── workspace/ # Bind mount — host-visible project files +└── dotfiles/ # Baked into /etc/skel-arch-dev/ in image + ├── .zshrc # 256-color only, mobile detection, snapshot fns + ├── .aliases + ├── .screenrc # Mobile multiplexer + ├── .config/ + │ ├── starship.toml # Desktop: 256-color Kanagawa Wave approx + │ ├── starship-mobile.toml # Mobile: single line, no icons + │ ├── tmux/tmux.conf # Desktop mux, Ctrl+Space prefix + │ └── nvim/ # Full LSP/lint/format setup +``` + +--- + +## The Stateful Home System (v1.7) + +`/home/dev` is a **named Docker volume** (`arch-dev-home`). Persists across +`--rm` container exits. NOT wiped on exit — only on `docker volume rm`. + +On first run, entrypoint seeds the volume from `/etc/skel-arch-dev/` +(baked into the image). Subsequent runs detect if image dotfiles are newer +and auto-update (with auto-snapshot first). + +### Git-backed snapshots + +`~/.arch-dev-state/` is a separate-git-dir tracking `/home/dev`. +No `.git` folder in `~` — avoids nested git conflicts with project repos. + +Shell functions (defined in .zshrc): +- `snapshot [msg]` / `snap` — tag current state +- `snapshots` / `snaps` — list tags + recent commits +- `rollback ` / `snapr` — reset to tag (confirms first, auto-snaps) +- `diff-state` / `snapd` — what's changed since last commit +- `show-snapshot ` — inspect a tag +- `unsnapshot ` — delete a tag +- `_archdev_git` — raw git wrapper (separate git dir) + +--- + +## The Color Problem (solved in v1.4+) + +Termius on Android cannot render truecolor RGB escape codes. +They show as **purple/magenta backgrounds** on everything. + +**Rules:** +- No `COLORTERM=truecolor` in compose or shell +- No hex colors (`#7E9CD8`) in starship, zsh-syntax-highlighting, tmux +- All shell-visible configs use 256-color ANSI codes (`fg=110` etc.) +- `termguicolors` in neovim is conditional: `vim.env.MOBILE ~= "1"` +- Mobile colorscheme: `habamax` (built-in, 256-color clean) +- Desktop colorscheme: `kanagawa-wave` (needs termguicolors) +- Bufferline disabled on mobile (emits RGB for tab backgrounds) +- noice.nvim removed entirely (bled colors into terminal) + +**Kanagawa Wave 256-color approximations:** +``` +110 = crystalBlue 106 = springGreen 139 = oniViolet +173 = boatYellow 167 = peachRed 66 = waveAqua +242 = fujiGray 250 = fujiWhite 236 = waveBlue +``` + +--- + +## Mobile Detection + +`MOBILE=1` env var (set in Termius host profile) activates: +- Minimal single-line starship prompt (no icons) +- screen auto-attach instead of tmux +- habamax colorscheme in neovim +- termguicolors disabled +- Bufferline disabled +- Compact aliases (`g` for git, etc.) + +--- + +## Plugin API Changes (hard-won knowledge) + +Every one of these bit us. Read docs before touching: + +| Plugin | Issue | Fix | +|--------|-------|-----| +| `leap.nvim` | Moved GitHub→Codeberg | `url = "https://codeberg.org/andyg/leap.nvim"` | +| `leap.nvim` | `add_default_mappings()` removed | Use `(leap)` keymaps directly | +| `nvim-treesitter` | `.configs` module gone | `require("nvim-treesitter").setup{}`, `branch="main"`, `lazy=false` | +| `nvim-surround` | v4 removed `keymaps` table | `require("nvim-surround").setup()` defaults only | +| `nvim-lspconfig` | Framework deprecated in 0.11 | `vim.lsp.config()` + `vim.lsp.enable()` | +| `vim.lsp.with()` | Deprecated | Handlers in `vim.lsp.config("*", { handlers = {} })` | +| `on_attach` | Old pattern | `LspAttach` autocmd | + +--- + +## What's NOT in v1 (coming in v2) + +- nvm + nodejs +- Claude CLI +- Gemini CLI +- Aider +- nvim AI plugin (copilot/avante/codecompanion) + +v2 plan: install AI tools in the stateful container, snapshot as `ai-tools`, +then layer nodejs on top. + +--- + +## Git / Repo + +- Gitea: https://code.waynehayesdevelopment.com/wayne/neovim-ide +- `main` branch = current stable (v1.7) +- `v2` branch = active development +- `v1.7` tag = frozen reference + +Wayne is new to multi-branch git workflow — be explicit about which branch +to be on before any git operations. + +--- + +## Tone / Style Notes + +- Short responses. Wayne reads on mobile. +- No bullet-point walls. Prose or tight tables. +- Don't guess at APIs — fetch the docs first. +- When something breaks, ask for the actual error before debugging. +- Wayne will test each version and report back with screenshots. +- "Apple good" is the visual quality bar. diff --git a/Dockerfile b/Dockerfile index a7ce3b1..15a654c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,7 @@ RUN pacman -S --noconfirm --needed \ man-db man-pages \ jq tree wget \ rsync \ + imagemagick chafa jp2a \ && pacman -Scc --noconfirm # ── Crown Jewel #2: AUR ─────────────────────────────────────────────────────── diff --git a/README.md b/README.md index b7a7d2b..ecf1e42 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,49 @@ -# arch-dev v1.7 +# arch-dev ### Riced Neovim IDE · Arch Linux · Stateful · Mobile-Aware > *"Like Gentoo without the compiling."* -> Kanagawa Wave · rolling release · AUR-powered · git-snapshotted home +> *I use Arch BTW* + +Kanagawa Wave · rolling release · AUR-powered · git-snapshotted home --- -## What's new in v1.7 +## Branches -- **Stateful `/home/dev`** — your shell history, plugin state, and config tweaks survive container exit -- **Git-backed snapshot system** — commit good states, roll back when things break -- **Auto-snapshot on dotfile updates** — image upgrades preserve your history -- **Mobile improvements** — bufferline disabled on mobile, dashboard uses thin-line ASCII +| Branch | Purpose | +|---|---| +| `main` | Latest stable | +| `v2` | Active development | +| `v1.7` (tag) | Frozen v1.7 reference | --- -## The Snapshot System +## What's in v2 -Your `/home/dev` is a git repo (state in `~/.arch-dev-state/`). When something works, snapshot it. When something breaks, roll back. +### v2.0 — AI tooling +- Claude Code CLI (snapshot after install + login) +- Gemini CLI +- Aider +- nvm + Node.js (for any AI tool that needs it) -```bash -# Working state — save it -snap node-working "NodeJS env with nvm + pnpm" +### v2.1 — Image & ASCII tooling +- ImageMagick — image manipulation +- chafa — modern terminal image rendering (truecolor + sixel) +- jp2a — fast JPEG/PNG to ASCII art -# List your snapshots -snaps +Workflow: AI rough sketch → `chafa --symbols ascii` → hand-tweak -# See what's changed since last snapshot -snapd +--- -# Try something risky, breaks things... no problem -snapr node-working +## v1.7 — What's there now -# Show what's in a snapshot -show-snapshot node-working - -# Remove a snapshot you don't need -unsnapshot old-thing -``` - -What's tracked: dotfiles, configs, neovim plugins, anything in `~`. -What's ignored: `.cache`, `.zsh_history`, build artifacts, log files. - -Snapshots use real git so you can: -- Branch (`_archdev_git checkout -b experimenting`) -- Push to remote (`_archdev_git remote add origin git@...`) -- Diff between snapshots (`_archdev_git diff snap-a snap-b`) +- Stateful `/home/dev` via named Docker volume +- Git-backed snapshot/rollback system (`snap`, `snaps`, `rollback`) +- Auto-snapshot on dotfile updates +- Mobile detection (`MOBILE=1` for Termius) +- Kanagawa Wave colorscheme (desktop) / habamax (mobile) +- LSP/lint/format for Python, Bash, Lua +- Telescope, oil, lazygit, leap, treesitter --- @@ -56,7 +54,22 @@ docker compose build docker compose run --rm arch-dev ``` -First run seeds the home volume from a baked-in skeleton and creates a `skeleton` tag you can always roll back to. +First run seeds `/home/dev` from the baked-in skeleton and creates a +`skeleton` snapshot you can always roll back to. + +--- + +## Snapshot System + +Your home is a git repo (state in `~/.arch-dev-state/`). Save good states, +roll back when things break. + +```bash +snap node-working "NodeJS env with nvm + pnpm" # save state +snaps # list snapshots +snapd # diff vs last snapshot +rollback node-working # reset to snapshot +``` --- @@ -65,54 +78,30 @@ First run seeds the home volume from a baked-in skeleton and creates a `skeleton | Path | Type | Purpose | |---|---|---| | `/workspace` | bind mount → `./workspace` | Project files, host-visible | -| `/home/dev` | named volume `arch-dev-home` | Stateful user home, survives `--rm` | -| `/etc/skel-arch-dev/` | image layer | Read-only template, used to seed and update | +| `/home/dev` | named volume | Stateful user home | +| `/etc/skel-arch-dev/` | image layer | Read-only template | -**Reset home to factory:** -```bash -docker volume rm arch-dev_arch-dev-home -docker compose run --rm arch-dev -``` - -**Or roll back inside the container:** -```bash -rollback skeleton -``` +Reset home to factory: `docker volume rm _arch-dev-home` --- -## Image Updates +## State Tracking — Two Systems -When you rebuild the image with new dotfiles: +| System | What | Where | +|---|---|---| +| **git on v2 branch** | Dockerfile, dotfiles, build recipe | Gitea repo | +| **`snap` inside container** | Runtime state, installed tools, auth | Docker volume | -1. Container starts, entrypoint compares image dotfiles to home -2. If image is newer, takes an auto-snapshot of current home -3. Updates dotfiles, leaving user data alone (history, project state) -4. You can `rollback` to the auto-snapshot if you don't like the new dotfiles - ---- - -## Known Caveats - -- **AUR/pacman packages** are NOT in snapshots (they live in `/usr/`, not `~`) -- **Docker volume size** grows with snapshots — `_archdev_git gc` periodically -- First `snap` after major changes can take a few seconds (lots to hash) +Both required for full reproducibility — Dockerfile builds the OS, +snapshots restore the user state on top of it. --- ## Mobile (Termius) -`MOBILE=1` activates: -- Minimal starship prompt +Set `MOBILE=1` in Termius host profile env vars to activate: +- Single-line minimal starship prompt - Auto-attach screen on connect -- Bufferline disabled (was showing as purple bar) -- Habamax colorscheme (kanagawa needs truecolor which Termius mangles) - ---- - -## v2 Roadmap - -- nvm + nodejs (snapshot it after install!) -- gemini-cli for AI in the terminal -- copilot.nvim or avante.nvim -- nvim-dap (debugger) +- habamax colorscheme (kanagawa needs truecolor) +- termguicolors disabled in neovim +- Bufferline disabled -- 2.45.2 From 942014eeb85a3503e79d9aea04a611f73b8e25d3 Mon Sep 17 00:00:00 2001 From: wayne Date: Mon, 27 Apr 2026 19:38:11 -0400 Subject: [PATCH 2/6] fix: restore sudo capabilities (SETUID/SETGID/AUDIT_WRITE) --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 659a4d9..c93901a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,9 @@ services: - ALL cap_add: - NET_BIND_SERVICE + - SETUID + - SETGID + - AUDIT_WRITE volumes: arch-dev-home: -- 2.45.2 From d67ae30a991a9ab387d611cf2a40cee8d91328b3 Mon Sep 17 00:00:00 2001 From: wayne Date: Mon, 27 Apr 2026 22:34:44 -0400 Subject: [PATCH 3/6] v2.2: bake in tailscale (AUR) + tun device + NET_ADMIN cap --- Dockerfile | 1 + README.md | 40 +++++++++++++++++++++++++++++++++++++++- docker-compose.yml | 5 +++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 15a654c..afb28a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,6 +42,7 @@ RUN sudo -u aurbuild yay -S --noconfirm --needed \ eza \ wl-clipboard \ trash-cli \ + tailscale \ && sudo -u aurbuild yay -Scc --noconfirm # ── Dev user ────────────────────────────────────────────────────────────────── diff --git a/README.md b/README.md index ecf1e42..8f4f151 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Kanagawa Wave · rolling release · AUR-powered · git-snapshotted home - Claude Code CLI (snapshot after install + login) - Gemini CLI - Aider -- nvm + Node.js (for any AI tool that needs it) +- nvm + Node.js (for AI tools that need it) ### v2.1 — Image & ASCII tooling - ImageMagick — image manipulation @@ -33,6 +33,26 @@ Kanagawa Wave · rolling release · AUR-powered · git-snapshotted home Workflow: AI rough sketch → `chafa --symbols ascii` → hand-tweak +### v2.2 — Tailscale +- Tailscale baked in via AUR +- `/dev/net/tun` device pass-through for kernel-mode networking +- `NET_ADMIN` + `NET_RAW` capabilities granted +- First-run auth, then `snap tailscale` for persistence + +```bash +# Inside container, first time: +sudo tailscaled & +sudo tailscale up # follow auth URL +snap tailscale "authenticated to tailnet" +``` + +### v2.3 — Companion plugin (separate repo) +**`tailscale.nvim`** — original FOSS work, no equivalent exists yet: +- Lualine status component +- Telescope peer picker +- Quick IP copy + status window +- Repo: github.com//tailscale.nvim + --- ## v1.7 — What's there now @@ -85,6 +105,21 @@ Reset home to factory: `docker volume rm _arch-dev-home` --- +## Container Capabilities + +The container drops ALL capabilities then re-adds only what's needed: + +| Cap | Why | +|---|---| +| `NET_BIND_SERVICE` | Bind to ports < 1024 (mosh) | +| `SETUID` / `SETGID` | sudo | +| `AUDIT_WRITE` | sudoers_audit plugin | +| `NET_ADMIN` / `NET_RAW` | Tailscale | + +Plus device pass-through for `/dev/net/tun` (Tailscale kernel mode). + +--- + ## State Tracking — Two Systems | System | What | Where | @@ -105,3 +140,6 @@ Set `MOBILE=1` in Termius host profile env vars to activate: - habamax colorscheme (kanagawa needs truecolor) - termguicolors disabled in neovim - Bufferline disabled + +With Tailscale (v2.2+), you can reach arch-dev from any device on your +tailnet without exposing ports — perfect for mobile dev anywhere. diff --git a/docker-compose.yml b/docker-compose.yml index c93901a..4234734 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,11 @@ services: - SETUID - SETGID - AUDIT_WRITE + - NET_ADMIN + - NET_RAW + + devices: + - /dev/net/tun:/dev/net/tun volumes: arch-dev-home: -- 2.45.2 From 0504461aaa043aa09f3f7aed615b016f85d70b40 Mon Sep 17 00:00:00 2001 From: wayne Date: Sun, 10 May 2026 23:03:49 -0400 Subject: [PATCH 4/6] Restored sudo permissions --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 4234734..4055215 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,6 +32,9 @@ services: - AUDIT_WRITE - NET_ADMIN - NET_RAW + - CHOWN # pacman temp dir ownership + - DAC_OVERRIDE # likely also needed for pacman lock files + - FOWNER # pacman devices: - /dev/net/tun:/dev/net/tun -- 2.45.2 From 53bc6f759021f949b115bce8ab74c7f53c43c7a0 Mon Sep 17 00:00:00 2001 From: wayne Date: Sun, 10 May 2026 23:07:03 -0400 Subject: [PATCH 5/6] v2.0: UID/GID matching, nvm, go, gh, full cap set --- .env.example | 14 +++++ .gitignore | 3 +- Dockerfile | 35 +++++++++-- README.md | 142 +++++++++++++++++++++++++++------------------ docker-compose.yml | 34 +++++++---- nvm.zsh.snippet | 33 +++++++++++ 6 files changed, 185 insertions(+), 76 deletions(-) create mode 100644 .env.example create mode 100644 nvm.zsh.snippet diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a76bc9c --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# Copy to .env (gitignored). Source recommended: +# echo "UID=$(id -u)" > .env +# echo "GID=$(id -g)" >> .env + +# Match host UID/GID for /workspace permissions +UID=1000 +GID=1000 + +# Set MOBILE=1 to activate mobile-optimized prompt + screen autoattach +MOBILE=0 + +# Git identity used inside the container +GIT_NAME=Your Name +GIT_EMAIL=you@example.com diff --git a/.gitignore b/.gitignore index 2fea26c..6be0b0d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,8 @@ # Environment / secrets .env -.env.* +.env.local +.env.*.local !.env.example # Editor diff --git a/Dockerfile b/Dockerfile index afb28a7..c9d46c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,11 @@ FROM archlinux:latest +# ── Build args: UID/GID matching for clean /workspace permissions ───────────── +# Override at build: UID=$(id -u) GID=$(id -g) docker compose build +# Or set in .env file +ARG USER_UID=1000 +ARG USER_GID=1000 + # ── Rolling release: full system update first, always ───────────────────────── RUN pacman -Syu --noconfirm @@ -25,6 +31,9 @@ RUN pacman -S --noconfirm --needed \ jq tree wget \ rsync \ imagemagick chafa jp2a \ + go \ + github-cli \ + libnewt \ && pacman -Scc --noconfirm # ── Crown Jewel #2: AUR ─────────────────────────────────────────────────────── @@ -45,22 +54,38 @@ RUN sudo -u aurbuild yay -S --noconfirm --needed \ tailscale \ && sudo -u aurbuild yay -Scc --noconfirm -# ── Dev user ────────────────────────────────────────────────────────────────── -RUN useradd -m -s /bin/zsh -G wheel dev && \ +# ── Dev user with host-matching UID/GID ─────────────────────────────────────── +# UID/GID match host so /workspace bind mount has clean permissions both sides +RUN groupadd -g ${USER_GID} dev && \ + useradd -m -s /bin/zsh -u ${USER_UID} -g ${USER_GID} -G wheel dev && \ echo 'dev ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/dev # ── Skeleton: bake dotfiles into /etc/skel-arch-dev/ ────────────────────────── -# This is the SOURCE OF TRUTH. The volume gets seeded from here on first run. COPY --chown=dev:dev dotfiles/ /etc/skel-arch-dev/ # ── Initial seed of /home/dev so plugin bake works at build time ────────────── RUN cp -an /etc/skel-arch-dev/. /home/dev/ && \ chown -R dev:dev /home/dev +# ── nvm + LTS Node (as dev user) ────────────────────────────────────────────── +# nvm install script writes to ~/.zshrc — we sandbox it then merge cleanly +RUN sudo -u dev bash -c '\ + export PROFILE=/dev/null && \ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash && \ + export NVM_DIR="/home/dev/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && \ + nvm install --lts && \ + nvm alias default node \ +' + +# Persist nvm install into the skel template so volume seeding includes it +RUN cp -an /home/dev/.nvm /etc/skel-arch-dev/ && \ + chown -R dev:dev /etc/skel-arch-dev/.nvm + # ── Python tools ────────────────────────────────────────────────────────────── RUN pip install --break-system-packages pynvim httpx requests -# ── Bake neovim plugins into /etc/skel-arch-dev so they seed too ───────────── +# ── Bake neovim plugins ─────────────────────────────────────────────────────── RUN sudo -u dev HOME=/home/dev XDG_DATA_HOME=/home/dev/.local/share \ nvim --headless +"Lazy! sync" +qa 2>/dev/null; exit 0 @@ -69,7 +94,7 @@ RUN sudo -u dev HOME=/home/dev XDG_DATA_HOME=/home/dev/.local/share \ +"TSUpdateSync python bash lua json yaml toml markdown vim vimdoc regex" \ +qa 2>/dev/null; exit 0 -# Copy the fully-baked /home/dev back into the skel template +# Copy fully-baked /home/dev back into the skel template RUN cp -an /home/dev/.local /etc/skel-arch-dev/ && \ cp -an /home/dev/.cache /etc/skel-arch-dev/ 2>/dev/null || true && \ chown -R dev:dev /etc/skel-arch-dev diff --git a/README.md b/README.md index 8f4f151..c235c71 100644 --- a/README.md +++ b/README.md @@ -18,59 +18,31 @@ Kanagawa Wave · rolling release · AUR-powered · git-snapshotted home --- -## What's in v2 +## What's new in v2.0 -### v2.0 — AI tooling -- Claude Code CLI (snapshot after install + login) -- Gemini CLI -- Aider -- nvm + Node.js (for AI tools that need it) - -### v2.1 — Image & ASCII tooling -- ImageMagick — image manipulation -- chafa — modern terminal image rendering (truecolor + sixel) -- jp2a — fast JPEG/PNG to ASCII art - -Workflow: AI rough sketch → `chafa --symbols ascii` → hand-tweak - -### v2.2 — Tailscale -- Tailscale baked in via AUR -- `/dev/net/tun` device pass-through for kernel-mode networking -- `NET_ADMIN` + `NET_RAW` capabilities granted -- First-run auth, then `snap tailscale` for persistence - -```bash -# Inside container, first time: -sudo tailscaled & -sudo tailscale up # follow auth URL -snap tailscale "authenticated to tailnet" -``` - -### v2.3 — Companion plugin (separate repo) -**`tailscale.nvim`** — original FOSS work, no equivalent exists yet: -- Lualine status component -- Telescope peer picker -- Quick IP copy + status window -- Repo: github.com//tailscale.nvim - ---- - -## v1.7 — What's there now - -- Stateful `/home/dev` via named Docker volume -- Git-backed snapshot/rollback system (`snap`, `snaps`, `rollback`) -- Auto-snapshot on dotfile updates -- Mobile detection (`MOBILE=1` for Termius) -- Kanagawa Wave colorscheme (desktop) / habamax (mobile) -- LSP/lint/format for Python, Bash, Lua -- Telescope, oil, lazygit, leap, treesitter +- **Host UID/GID matching** — `/workspace` permissions just work, no more chowning +- **Tailscale** baked in (AUR) with `tun` device + `NET_ADMIN`/`NET_RAW` +- **Image tooling** — ImageMagick, chafa, jp2a +- **Go** — for go-based tooling +- **github-cli** (`gh`) — GitHub from terminal +- **libnewt** — provides `whiptail` for shell TUI scripts (sysmenu) +- **nvm + LTS Node** — lazy-loaded, baked into skel template +- **Capabilities settled** — pacman/sudo/tailscale all working post-`cap_drop ALL` --- ## Quick Start ```bash +# First time: create .env with your UID/GID +cp .env.example .env +echo "UID=$(id -u)" >> .env +echo "GID=$(id -g)" >> .env + +# Build (UID/GID picked up from .env) docker compose build + +# Run docker compose run --rm arch-dev ``` @@ -85,14 +57,32 @@ Your home is a git repo (state in `~/.arch-dev-state/`). Save good states, roll back when things break. ```bash -snap node-working "NodeJS env with nvm + pnpm" # save state -snaps # list snapshots -snapd # diff vs last snapshot -rollback node-working # reset to snapshot +snap claude-code "Claude Code installed and authed" +snaps # list snapshots +snapd # diff vs last snapshot +rollback claude-code # reset to snapshot ``` --- +## Tailscale + +```bash +# Inside container, first time: +sudo tailscaled & +sudo tailscale up # follow auth URL +snap tailscale "authenticated to tailnet" +``` + +After that, tailscale state persists in the named volume. + +**Reminder learned the hard way**: Tailscale default-denies all tailnet +traffic. ACLs grant exceptions, not restrictions. Check ACLs FIRST when +peer connections fail silently. (Also: `tailscale ping ` rules out +ACL issues before you start blaming nftables/routes.) + +--- + ## Volume Architecture | Path | Type | Purpose | @@ -115,20 +105,28 @@ The container drops ALL capabilities then re-adds only what's needed: | `SETUID` / `SETGID` | sudo | | `AUDIT_WRITE` | sudoers_audit plugin | | `NET_ADMIN` / `NET_RAW` | Tailscale | +| `CHOWN` / `DAC_OVERRIDE` / `FOWNER` | pacman | Plus device pass-through for `/dev/net/tun` (Tailscale kernel mode). --- -## State Tracking — Two Systems +## Multi-Window Workflow -| System | What | Where | -|---|---|---| -| **git on v2 branch** | Dockerfile, dotfiles, build recipe | Gitea repo | -| **`snap` inside container** | Runtime state, installed tools, auth | Docker volume | +`docker exec` does NOT inherit cap_add from compose — it gets default +capabilities. That means pacman/sudo work in the original `docker compose +run` window but not in `docker exec` windows. -Both required for full reproducibility — Dockerfile builds the OS, -snapshots restore the user state on top of it. +**Best practice:** Use `tmux` inside the container for multiple panes. +All panes inherit the original session's full caps. + +```bash +tmux new -s work +# Ctrl+Space " split horizontal +# Ctrl+Space % split vertical +# Ctrl+Space d detach (container keeps running) +# tmux attach -t work +``` --- @@ -141,5 +139,33 @@ Set `MOBILE=1` in Termius host profile env vars to activate: - termguicolors disabled in neovim - Bufferline disabled -With Tailscale (v2.2+), you can reach arch-dev from any device on your -tailnet without exposing ports — perfect for mobile dev anywhere. +With Tailscale, you can reach arch-dev from any device on your tailnet +without exposing ports — perfect for mobile dev anywhere. + +--- + +## State Tracking — Two Systems + +| System | What | Where | +|---|---|---| +| **git on branch** | Dockerfile, dotfiles, build recipe | Gitea repo | +| **`snap` inside container** | Runtime state, installed tools, auth | Docker volume | + +Both required for full reproducibility — Dockerfile builds the OS, +snapshots restore the user state on top of it. + +--- + +## Roadmap + +### v2.1 (next) +- ASCIInator integration with chafa workflow refinement +- Custom neovim dashboard ASCII (using arch-dev's own logo) + +### v2.2 (in design) +- Snapshot auto-push to remote (`snap` does `git push`, with override flag) +- Audit-clean .gitignore for credential paths + +### v2.3+ (separate repos) +- **tailscale.nvim** — original FOSS neovim plugin for Tailscale interaction +- **ASCIInator** — image-to-ASCII tool diff --git a/docker-compose.yml b/docker-compose.yml index 4055215..a76c31d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,13 @@ services: arch-dev: - build: . + build: + context: . + 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_GID: ${GID:-1000} image: arch-dev:latest container_name: arch-dev hostname: arch-dev @@ -8,11 +15,11 @@ services: tty: true volumes: - # Project files — bind mount, host-visible + # Project files — bind mount, host-visible, host-UID-owned - ./workspace:/workspace # Stateful home — named volume, survives --rm - # Reset with: docker volume rm arch-dev_arch-dev-home + # Reset with: docker volume rm _arch-dev-home - arch-dev-home:/home/dev environment: @@ -23,19 +30,22 @@ services: - GIT_COMMITTER_NAME=${GIT_NAME:-dev} - GIT_COMMITTER_EMAIL=${GIT_EMAIL:-dev@localhost} + # Capability set built up through testing — + # cap_drop ALL then re-add only what's needed. cap_drop: - ALL cap_add: - - NET_BIND_SERVICE - - SETUID - - SETGID - - AUDIT_WRITE - - NET_ADMIN - - NET_RAW - - CHOWN # pacman temp dir ownership - - DAC_OVERRIDE # likely also needed for pacman lock files - - FOWNER # pacman + - NET_BIND_SERVICE # mosh, bind <1024 + - SETUID # sudo + - SETGID # sudo + - AUDIT_WRITE # sudoers_audit plugin + - NET_ADMIN # tailscale + - NET_RAW # tailscale + - CHOWN # pacman temp dir ownership + - DAC_OVERRIDE # pacman lock files + - FOWNER # pacman package ownership + # Tailscale needs tun device for kernel-mode networking devices: - /dev/net/tun:/dev/net/tun diff --git a/nvm.zsh.snippet b/nvm.zsh.snippet new file mode 100644 index 0000000..e5b8949 --- /dev/null +++ b/nvm.zsh.snippet @@ -0,0 +1,33 @@ +# ── nvm (added in v2.0) ─────────────────────────────────────────────────────── +# Lazy-load nvm to keep shell startup fast +export NVM_DIR="$HOME/.nvm" +if [[ -s "$NVM_DIR/nvm.sh" ]]; then + # Stub functions that load nvm on first use + nvm() { + unset -f nvm node npm npx + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + nvm "$@" + } + node() { + unset -f nvm node npm npx + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + node "$@" + } + npm() { + unset -f nvm node npm npx + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + npm "$@" + } + npx() { + unset -f nvm node npm npx + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + npx "$@" + } + # Add default node bin to PATH so node-installed CLIs work without lazy trigger + if [[ -d "$NVM_DIR/alias" ]] && [[ -f "$NVM_DIR/alias/default" ]]; then + DEFAULT_NODE="$(cat "$NVM_DIR/alias/default" 2>/dev/null)" + [[ -d "$NVM_DIR/versions/node/v$DEFAULT_NODE/bin" ]] && \ + PATH="$NVM_DIR/versions/node/v$DEFAULT_NODE/bin:$PATH" + fi +fi -- 2.45.2 From f146b35e184d30340c801027d329b272b14f46a4 Mon Sep 17 00:00:00 2001 From: wayne Date: Sat, 30 May 2026 21:12:37 -0400 Subject: [PATCH 6/6] latest --- Dockerfile | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index c9d46c2..ddc5a32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,7 @@ RUN pacman -S --noconfirm --needed \ && pacman -Scc --noconfirm # ── Crown Jewel #2: AUR ─────────────────────────────────────────────────────── -RUN useradd -m -s /bin/zsh -G wheel aurbuild && \ +RUN useradd -m -s /bin/zsh -u 9001 -G wheel aurbuild && \ echo 'aurbuild ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/aurbuild RUN cd /tmp && \ @@ -55,9 +55,16 @@ RUN sudo -u aurbuild yay -S --noconfirm --needed \ && sudo -u aurbuild yay -Scc --noconfirm # ── Dev user with host-matching UID/GID ─────────────────────────────────────── -# UID/GID match host so /workspace bind mount has clean permissions both sides -RUN groupadd -g ${USER_GID} dev && \ - useradd -m -s /bin/zsh -u ${USER_UID} -g ${USER_GID} -G wheel dev && \ +# 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. +RUN set -e; \ + # Group: handle pre-existing GID gracefully (Arch base has users:1000) + if getent group ${USER_GID} >/dev/null; then \ + groupmod -n dev "$(getent group ${USER_GID} | cut -d: -f1)"; \ + else \ + groupadd -g ${USER_GID} dev; \ + fi; \ + useradd -m -s /bin/zsh -u ${USER_UID} -g ${USER_GID} -G wheel dev; \ echo 'dev ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/dev # ── Skeleton: bake dotfiles into /etc/skel-arch-dev/ ────────────────────────── -- 2.45.2