Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

## Unreleased

- Renamed commands for shorter typing: `apply` → `sync`, `enter` → `sh`, `logs` →
`log`, `list` → `ls`. The per-project hook moved from `.dvm/apply.sh` to
`.dvm/sync.sh`; rename the file in projects that use it.
- Added built-in defaults for `DVM_CPUS=2`, `DVM_MEMORY=2GiB`, `DVM_DISK=10GiB`,
`DVM_USER=${USER:-developer}`. Bundled `share/dvm/config.sh` is now fully
commented; uncomment values to override defaults.
- Changed `dvm init` to render the bundled VM template, substituting host CPU/memory
ceilings into inline comments and listing every available recipe (auto-detected
from `share/dvm/recipes` and `~/.config/dvm/recipes`) commented out by default.
- Rewrote `share/dvm/vms/app.sh` as a fully commented self-documenting template.
- Added `# Description: <one line>` to bundled recipes so `dvm init` can show them in
the available-recipes block.
- Added a `bat` recipe that installs bat from Fedora and runs `bat cache --build`.
- Added VM config validation before Lima template rendering for VM names, users, sizing,
code directories, host IPs, and port forwards.
Expand Down
60 changes: 24 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,30 @@ Install the wrapper:
This installs a small launcher into `~/.local/bin` and copies defaults into
`~/.config/dvm` without overwriting existing files. The launcher runs each invocation
from a temporary snapshot of `bin/dvm` and its shell libraries, so editing or pulling
this repo cannot corrupt a long-running `dvm apply`. Bundled recipes, the Lima
this repo cannot corrupt a long-running `dvm sync`. Bundled recipes, the Lima
template, and example VM configs stay in the repo under `share/dvm`.

## Commands

```bash
dvm init app
dvm apply app
dvm apply --all
dvm enter app
dvm sync app
dvm sync --all
dvm sh app
dvm ssh app -- pwd
dvm cp ./plan.md app:.
dvm logs cloudflared
dvm log cloudflared
dvm ssh-key app
dvm gpg-key app
dvm list
dvm ls
dvm stop app
dvm rm app --yes
```

`dvm apply <name>` creates the Lima VM if missing, starts it, runs
`dvm sync <name>` creates the Lima VM if missing, starts it, runs
`recipes/baseline.sh`, runs recipes selected by `~/.config/dvm/vms/<name>.sh`, then
runs `~/code/<name>/.dvm/apply.sh` inside the guest if that file exists.
Use `dvm apply --all` after recipe changes or when you want to update recipe-managed
runs `~/code/<name>/.dvm/sync.sh` inside the guest if that file exists.
Use `dvm sync --all` after recipe changes or when you want to update recipe-managed
tools such as AI CLIs across every active VM.

`dvm rm` requires `--yes` and checks nested Git repos for dirty work before deleting.
Expand Down Expand Up @@ -83,23 +83,11 @@ Create a VM config from the bundled app example:
dvm init app
```

Example:

```bash
DVM_CPUS=4
DVM_MEMORY=8GiB
DVM_DISK=80GiB
DVM_CODE_DIR="~/code/app"
DVM_PORTS="3000:3000 5173:5173"
DVM_CHEZMOI_REPO="https://github.com/YOUR_USER/dotfiles.git"

use node
use python
use agent-user
use codex
use claude
use chezmoi
```
`dvm init` writes a fully commented template; uncomment what you need. Defaults are
`DVM_CPUS=2`, `DVM_MEMORY=2GiB`, `DVM_DISK=10GiB`, `DVM_CODE_DIR=~/code/$DVM_NAME`,
empty `DVM_PORTS`. The template also lists every available recipe (auto-detected from
bundled and user recipes) with a one-line description, and shows the host's CPU and
memory ceilings as inline comments.

`~` in DVM variables means the guest user's home. Host project directories are not
mounted into the VM. VM names use lowercase letters, numbers, and hyphens, starting
Expand All @@ -112,16 +100,16 @@ New app VM:

```bash
dvm init myapp
dvm apply myapp
dvm enter myapp
dvm sync myapp
dvm sh myapp
```

Dedicated llama VM:

```bash
dvm init llama llama
dvm apply llama
dvm logs llama -f
dvm sync llama
dvm log llama -f
```

The bundled llama VM opens port `8080` for host access at `http://127.0.0.1:8080` and
Expand All @@ -132,11 +120,11 @@ Cloudflared tunnel VM:

```bash
dvm init cloudflared cloudflared
CLOUDFLARED_TOKEN="..." dvm apply cloudflared
dvm logs cloudflared -f
CLOUDFLARED_TOKEN="..." dvm sync cloudflared
dvm log cloudflared -f
```

The cloudflared token is staged through a mode `0600` guest temp file during `apply`
The cloudflared token is staged through a mode `0600` guest temp file during `sync`
instead of being passed as a `limactl shell env` argument.

## Recipes
Expand Down Expand Up @@ -171,9 +159,9 @@ approval prompts and sandboxing.
## Dedicated Service VMs

```bash
dvm apply llama
CLOUDFLARED_TOKEN="..." dvm apply cloudflared
dvm logs cloudflared
dvm sync llama
CLOUDFLARED_TOKEN="..." dvm sync cloudflared
dvm log cloudflared
```

Example service configs live in `share/dvm/vms`. Copy one into `~/.config/dvm/vms`
Expand Down
10 changes: 5 additions & 5 deletions bin/dvm
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,20 @@ main() {
[ "$#" -eq 0 ] || shift
case "$cmd" in
init) init_vm "$@" ;;
apply)
sync)
case "${1:-}" in
--all) apply_all ;;
'') die "apply requires a VM name or --all" ;;
'') die "sync requires a VM name or --all" ;;
*) apply_one "$1" ;;
esac
;;
enter) [ -n "${1:-}" ] || die "enter requires a VM name"; enter_vm "$1" ;;
sh) [ -n "${1:-}" ] || die "sh requires a VM name"; enter_vm "$1" ;;
ssh) [ -n "${1:-}" ] || die "ssh requires a VM name"; ssh_vm "$@" ;;
cp) cp_vm "$@" ;;
logs) logs_vm "$@" ;;
log) logs_vm "$@" ;;
ssh-key) ssh_key_vm "$@" ;;
gpg-key) gpg_key_vm "$@" ;;
list) list_vms ;;
ls) list_vms ;;
stop) stop_vm "$@" ;;
rm) rm_vm "$@" ;;
help | -h | --help) usage ;;
Expand Down
12 changes: 6 additions & 6 deletions docs/ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Wrappers are installed in `/usr/local/bin`, clamp the host-side working director
Authenticate inside the VM:

```bash
dvm enter app
dvm sh app
codex
claude
opencode
Expand All @@ -72,7 +72,7 @@ GPG keys, and common token/config paths are not mounted into the sandbox.

Project ACLs are set recursively and as defaults on directories under `DVM_CODE_DIR`.
Files created by the VM user or by AI tools should remain editable by both sides. Re-run
`dvm apply <name>` if project permissions are changed manually or restored from an
`dvm sync <name>` if project permissions are changed manually or restored from an
archive.

Set these in a VM config when you want tool-native approval prompts and sandboxing:
Expand All @@ -94,19 +94,19 @@ claude --permission-mode plan
Re-run recipes to update AI tools:

```bash
dvm apply app
dvm apply --all
dvm sync app
dvm sync --all
```

`codex` and `opencode` install `@latest` from npm. `mistral` runs `uv tool upgrade`.
`claude` uses Anthropic's `latest` RPM channel and runs `dnf5 --refresh upgrade
claude-code`. If Claude reports a version before the RPM repository publishes it, wait
and re-run `dvm apply`.
and re-run `dvm sync`.

## Security Practice

- Keep provider tokens out of host shell history.
- Keep project secrets out of dotfiles.
- Prefer repo-scoped keys or service tokens over account-wide credentials.
- Treat AI output as untrusted code until reviewed.
- Re-run `dvm apply app` after recipe changes.
- Re-run `dvm sync app` after recipe changes.
36 changes: 18 additions & 18 deletions docs/commands.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Commands

DVM keeps the command surface small. Most day-to-day work should still be `apply`,
`enter`, and `ssh`; the extra helpers cover logs and VM-local keys.
DVM keeps the command surface small. Most day-to-day work should still be `sync`,
`sh`, and `ssh`; the extra helpers cover logs and VM-local keys.

Use public project names in DVM commands: `app`, `eshlox-net`, `llama`. The `dvm-`
prefix is reserved for internal Lima instance names.
Expand All @@ -18,35 +18,35 @@ dvm init cloudflared cloudflared
`~/.config/dvm/vms/<name>.sh` and opens it in `$EDITOR`, falling back to `$VISUAL` and
then `vi`. The template defaults to `app`. Existing configs are not overwritten.

## Apply
## Sync

```bash
dvm apply app
dvm apply --all
dvm sync app
dvm sync --all
```

`apply` creates the VM if missing, starts it, runs `baseline`, runs recipes selected in
`~/.config/dvm/vms/app.sh`, then runs `$DVM_CODE_DIR/.dvm/apply.sh` inside the guest if
`sync` creates the VM if missing, starts it, runs `baseline`, runs recipes selected in
`~/.config/dvm/vms/app.sh`, then runs `$DVM_CODE_DIR/.dvm/sync.sh` inside the guest if
present. Before running guest scripts, DVM prints the expanded recipe list so helper
functions such as `use_app_tools` are easy to verify.

If the VM already exists, `apply` updates Lima port forwards from `DVM_PORTS` without
If the VM already exists, `sync` updates Lima port forwards from `DVM_PORTS` without
recreating the VM. Lima may restart the instance when ports change.

`apply --all` applies every active VM config in `~/.config/dvm/vms/*.sh`, continues
`sync --all` syncs every active VM config in `~/.config/dvm/vms/*.sh`, continues
after failures, and exits non-zero if any VM failed. Use it after recipe changes or to
update recipe-managed tools across all VMs.

## Enter
## Shell

```bash
dvm enter app
dvm sh app
```

Starts the VM and opens an interactive shell in `DVM_CODE_DIR`. DVM reads the guest
user's login shell from `/etc/passwd`, so recipes can switch it with `usermod --shell`;
the `zsh` recipe sets it to zsh. DVM also exports `SHELL` to that login shell before
starting it, so tools see the same shell that `enter` launches. If the host terminal
starting it, so tools see the same shell that `sh` launches. If the host terminal
advertises a terminfo name that the guest does not know, such as `xterm-ghostty`, DVM
falls back to `xterm-256color` for the guest shell.

Expand Down Expand Up @@ -81,12 +81,12 @@ host files into `DVM_CODE_DIR`, DVM refreshes `dvm-agent` ACLs on the copied pat
the AI wrappers can read and write them. It supports `-r`/`--recursive`,
`-v`/`--verbose`, and `--backend auto|scp|rsync`.

## Logs
## Log

```bash
dvm logs cloudflared
dvm logs cloudflared -f
dvm logs app nginx.service -f
dvm log cloudflared
dvm log cloudflared -f
dvm log app nginx.service -f
```

Shows `journalctl` output from inside the VM. When the VM config uses exactly one known
Expand Down Expand Up @@ -125,7 +125,7 @@ plus fingerprint. Neither command copies host private keys into the VM.
## List

```bash
dvm list
dvm ls
```

Shows DVM-managed Lima VMs. The displayed names are the DVM public names without the
Expand All @@ -150,4 +150,4 @@ Deletes the Lima VM. `--yes` is required. Before deleting, DVM starts the VM and
nested Git repos under `DVM_CODE_DIR`; dirty repos stop deletion. `--force` skips that
scan. If the Lima VM exists but the DVM config file is missing, DVM warns that it is
deleting an orphan and skips the dirty check because it does not know `DVM_CODE_DIR`.
Recreate is intentionally `dvm rm app --yes` followed by `dvm apply app`.
Recreate is intentionally `dvm rm app --yes` followed by `dvm sync app`.
Loading