v2.0: UID/GID matching, nvm, go, gh, full cap set
This commit is contained in:
parent
0504461aaa
commit
53bc6f7590
14
.env.example
Normal file
14
.env.example
Normal file
@ -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
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
# Environment / secrets
|
# Environment / secrets
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.local
|
||||||
|
.env.*.local
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
|
|||||||
35
Dockerfile
35
Dockerfile
@ -1,5 +1,11 @@
|
|||||||
FROM archlinux:latest
|
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 ─────────────────────────
|
# ── Rolling release: full system update first, always ─────────────────────────
|
||||||
RUN pacman -Syu --noconfirm
|
RUN pacman -Syu --noconfirm
|
||||||
|
|
||||||
@ -25,6 +31,9 @@ RUN pacman -S --noconfirm --needed \
|
|||||||
jq tree wget \
|
jq tree wget \
|
||||||
rsync \
|
rsync \
|
||||||
imagemagick chafa jp2a \
|
imagemagick chafa jp2a \
|
||||||
|
go \
|
||||||
|
github-cli \
|
||||||
|
libnewt \
|
||||||
&& pacman -Scc --noconfirm
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
# ── Crown Jewel #2: AUR ───────────────────────────────────────────────────────
|
# ── Crown Jewel #2: AUR ───────────────────────────────────────────────────────
|
||||||
@ -45,22 +54,38 @@ RUN sudo -u aurbuild yay -S --noconfirm --needed \
|
|||||||
tailscale \
|
tailscale \
|
||||||
&& sudo -u aurbuild yay -Scc --noconfirm
|
&& sudo -u aurbuild yay -Scc --noconfirm
|
||||||
|
|
||||||
# ── Dev user ──────────────────────────────────────────────────────────────────
|
# ── Dev user with host-matching UID/GID ───────────────────────────────────────
|
||||||
RUN useradd -m -s /bin/zsh -G wheel dev && \
|
# 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
|
echo 'dev ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/dev
|
||||||
|
|
||||||
# ── Skeleton: bake dotfiles into /etc/skel-arch-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/
|
COPY --chown=dev:dev dotfiles/ /etc/skel-arch-dev/
|
||||||
|
|
||||||
# ── 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
|
||||||
|
|
||||||
|
# ── 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 ──────────────────────────────────────────────────────────────
|
# ── Python tools ──────────────────────────────────────────────────────────────
|
||||||
RUN pip install --break-system-packages pynvim httpx requests
|
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 \
|
RUN sudo -u dev HOME=/home/dev XDG_DATA_HOME=/home/dev/.local/share \
|
||||||
nvim --headless +"Lazy! sync" +qa 2>/dev/null; exit 0
|
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" \
|
+"TSUpdateSync python bash lua json yaml toml markdown vim vimdoc regex" \
|
||||||
+qa 2>/dev/null; exit 0
|
+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/ && \
|
RUN cp -an /home/dev/.local /etc/skel-arch-dev/ && \
|
||||||
cp -an /home/dev/.cache /etc/skel-arch-dev/ 2>/dev/null || true && \
|
cp -an /home/dev/.cache /etc/skel-arch-dev/ 2>/dev/null || true && \
|
||||||
chown -R dev:dev /etc/skel-arch-dev
|
chown -R dev:dev /etc/skel-arch-dev
|
||||||
|
|||||||
138
README.md
138
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
|
- **Host UID/GID matching** — `/workspace` permissions just work, no more chowning
|
||||||
- Claude Code CLI (snapshot after install + login)
|
- **Tailscale** baked in (AUR) with `tun` device + `NET_ADMIN`/`NET_RAW`
|
||||||
- Gemini CLI
|
- **Image tooling** — ImageMagick, chafa, jp2a
|
||||||
- Aider
|
- **Go** — for go-based tooling
|
||||||
- nvm + Node.js (for AI tools that need it)
|
- **github-cli** (`gh`) — GitHub from terminal
|
||||||
|
- **libnewt** — provides `whiptail` for shell TUI scripts (sysmenu)
|
||||||
### v2.1 — Image & ASCII tooling
|
- **nvm + LTS Node** — lazy-loaded, baked into skel template
|
||||||
- ImageMagick — image manipulation
|
- **Capabilities settled** — pacman/sudo/tailscale all working post-`cap_drop ALL`
|
||||||
- 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/<you>/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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```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
|
docker compose build
|
||||||
|
|
||||||
|
# Run
|
||||||
docker compose run --rm arch-dev
|
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.
|
roll back when things break.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
snap node-working "NodeJS env with nvm + pnpm" # save state
|
snap claude-code "Claude Code installed and authed"
|
||||||
snaps # list snapshots
|
snaps # list snapshots
|
||||||
snapd # diff vs last snapshot
|
snapd # diff vs last snapshot
|
||||||
rollback node-working # reset to 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 <peer>` rules out
|
||||||
|
ACL issues before you start blaming nftables/routes.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Volume Architecture
|
## Volume Architecture
|
||||||
|
|
||||||
| Path | Type | Purpose |
|
| Path | Type | Purpose |
|
||||||
@ -115,20 +105,28 @@ The container drops ALL capabilities then re-adds only what's needed:
|
|||||||
| `SETUID` / `SETGID` | sudo |
|
| `SETUID` / `SETGID` | sudo |
|
||||||
| `AUDIT_WRITE` | sudoers_audit plugin |
|
| `AUDIT_WRITE` | sudoers_audit plugin |
|
||||||
| `NET_ADMIN` / `NET_RAW` | Tailscale |
|
| `NET_ADMIN` / `NET_RAW` | Tailscale |
|
||||||
|
| `CHOWN` / `DAC_OVERRIDE` / `FOWNER` | pacman |
|
||||||
|
|
||||||
Plus device pass-through for `/dev/net/tun` (Tailscale kernel mode).
|
Plus device pass-through for `/dev/net/tun` (Tailscale kernel mode).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## State Tracking — Two Systems
|
## Multi-Window Workflow
|
||||||
|
|
||||||
| System | What | Where |
|
`docker exec` does NOT inherit cap_add from compose — it gets default
|
||||||
|---|---|---|
|
capabilities. That means pacman/sudo work in the original `docker compose
|
||||||
| **git on v2 branch** | Dockerfile, dotfiles, build recipe | Gitea repo |
|
run` window but not in `docker exec` windows.
|
||||||
| **`snap` inside container** | Runtime state, installed tools, auth | Docker volume |
|
|
||||||
|
|
||||||
Both required for full reproducibility — Dockerfile builds the OS,
|
**Best practice:** Use `tmux` inside the container for multiple panes.
|
||||||
snapshots restore the user state on top of it.
|
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
|
- termguicolors disabled in neovim
|
||||||
- Bufferline disabled
|
- Bufferline disabled
|
||||||
|
|
||||||
With Tailscale (v2.2+), you can reach arch-dev from any device on your
|
With Tailscale, you can reach arch-dev from any device on your tailnet
|
||||||
tailnet without exposing ports — perfect for mobile dev anywhere.
|
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
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
services:
|
services:
|
||||||
arch-dev:
|
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
|
image: arch-dev:latest
|
||||||
container_name: arch-dev
|
container_name: arch-dev
|
||||||
hostname: arch-dev
|
hostname: arch-dev
|
||||||
@ -8,11 +15,11 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
# Project files — bind mount, host-visible
|
# Project files — bind mount, host-visible, host-UID-owned
|
||||||
- ./workspace:/workspace
|
- ./workspace:/workspace
|
||||||
|
|
||||||
# Stateful home — named volume, survives --rm
|
# Stateful home — named volume, survives --rm
|
||||||
# Reset with: docker volume rm arch-dev_arch-dev-home
|
# Reset with: docker volume rm <project>_arch-dev-home
|
||||||
- arch-dev-home:/home/dev
|
- arch-dev-home:/home/dev
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@ -23,19 +30,22 @@ 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 built up through testing —
|
||||||
|
# cap_drop ALL then re-add only what's needed.
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_BIND_SERVICE
|
- NET_BIND_SERVICE # mosh, bind <1024
|
||||||
- SETUID
|
- SETUID # sudo
|
||||||
- SETGID
|
- SETGID # sudo
|
||||||
- AUDIT_WRITE
|
- AUDIT_WRITE # sudoers_audit plugin
|
||||||
- NET_ADMIN
|
- NET_ADMIN # tailscale
|
||||||
- NET_RAW
|
- NET_RAW # tailscale
|
||||||
- CHOWN # pacman temp dir ownership
|
- CHOWN # pacman temp dir ownership
|
||||||
- DAC_OVERRIDE # likely also needed for pacman lock files
|
- DAC_OVERRIDE # pacman lock files
|
||||||
- FOWNER # pacman
|
- FOWNER # pacman package ownership
|
||||||
|
|
||||||
|
# Tailscale needs tun device for kernel-mode networking
|
||||||
devices:
|
devices:
|
||||||
- /dev/net/tun:/dev/net/tun
|
- /dev/net/tun:/dev/net/tun
|
||||||
|
|
||||||
|
|||||||
33
nvm.zsh.snippet
Normal file
33
nvm.zsh.snippet
Normal file
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user