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
13 changes: 9 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
`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.
ceilings into inline comments.
- Added a `use_tools` helper in `share/dvm/config.sh`. The bundled VM template calls
it, so every general-purpose recipe (excluding service recipes like `llama` and
`cloudflared`) is selected from a single global location. Define more helpers
(`use_data_tools`, …) and mix them per VM as needed.
- 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 `# Description: <one line>` to bundled recipes as a one-liner docstring.
- Moved `DVM_CHEZMOI_REPO`, `DVM_CHEZMOI_SIGNING_KEY`, and `DVM_CHEZMOI_DEPLOY_KEY` to
global config; per-VM config only opts in via `use chezmoi` (per-VM override still
works through the source order).
- 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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,11 @@ dvm init app

`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.
empty `DVM_PORTS`. The template shows the host's CPU and memory ceilings as inline
comments and calls `use_tools`, a helper defined in your global config that holds the
recipes shared across every app VM. Edit `~/.config/dvm/config.sh` once to choose
your toolset; new VMs pick it up automatically. Per-VM configs can add extra
recipes, define more helpers, or comment the `use_tools` line for a minimal VM.

`~` 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 Down
35 changes: 19 additions & 16 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,27 @@ dvm init myapp
dvm sync myapp
```

`dvm init` writes a fully commented template that lists every recipe (auto-detected
from `share/dvm/recipes` and `~/.config/dvm/recipes`) and shows host CPU/memory limits.
Uncomment what you need:
`dvm init` writes a fully commented template that shows host CPU/memory limits and
calls `use_tools` (the helper defined in global config). Uncomment what you need:

```bash
# DVM_CPUS=4 # default 2 (host max: 14)
# DVM_MEMORY=8GiB # default 2GiB (host max: 64GiB)
# DVM_DISK=80GiB # default 10GiB
# DVM_CODE_DIR="~/code/$DVM_NAME"
# DVM_PORTS="3000:3000 5173:5173"
# DVM_CHEZMOI_REPO="https://github.com/YOUR_USER/dotfiles.git"

# use node # Node.js, npm, corepack
# use python # Python and uv
# use agent-user # dvm-agent user with Bubblewrap sandbox for AI tools
# use codex # Codex CLI
# use claude # Claude Code CLI
# use chezmoi # public dotfiles via chezmoi over HTTPS

use_tools
```

## Toolsets via Helpers

`share/dvm/config.sh` ships a `use_tools` function with every general-purpose recipe
listed and commented out. Uncomment the recipes you want every app VM to install.
Define more helpers (`use_data_tools`, `use_ml_tools`, …) for groups of VMs and call
them from per-VM configs. Service recipes (`llama`, `cloudflared`) are not in
`use_tools`; they belong in their dedicated VM templates.

## Variables

- `DVM_CPUS`, `DVM_MEMORY`, `DVM_DISK`: Lima VM sizing. Default `2`, `2GiB`, `10GiB`.
Expand All @@ -102,12 +103,14 @@ Uncomment what you need:
Set `DVM_CLAUDE_BYPASS=0` to leave Claude permission prompts enabled.
- `DVM_COREPACK_VERSION`: Corepack npm package version for the `node` recipe, normally
`0.34.0`.
- `DVM_CHEZMOI_REPO`: public HTTPS dotfiles repo.
- `DVM_CHEZMOI_REPO`: public HTTPS dotfiles repo. Required when any VM uses the
`chezmoi` recipe. Usually set in global config; per-VM config can override.
- `DVM_CHEZMOI_ROLE`, `DVM_CHEZMOI_NAME`, `DVM_CHEZMOI_EMAIL`: optional shared chezmoi
`[data]` values.
- `DVM_CHEZMOI_SIGNING_KEY`, `DVM_CHEZMOI_DEPLOY_KEY`: optional per-VM chezmoi
`[data]` key path overrides. When unset, generated chezmoi data uses
`~/.ssh/id_ed25519_dvm_signing.pub` and `~/.ssh/id_ed25519_dvm.pub`.
`[data]` values. Usually set in global config; per-VM config can override.
- `DVM_CHEZMOI_SIGNING_KEY`, `DVM_CHEZMOI_DEPLOY_KEY`: optional chezmoi `[data]` key
path overrides. Usually set in global config when `dvm ssh-key` is configured with
custom key names; per-VM config can override. When unset, generated chezmoi data
uses `~/.ssh/id_ed25519_dvm_signing.pub` and `~/.ssh/id_ed25519_dvm.pub`.
- `DVM_CHEZMOI_CONFIG_TOML`: optional full chezmoi config written to
`~/.config/chezmoi/chezmoi.toml`; when set, it takes over the generated chezmoi data
config rather than merging with it.
Expand Down
22 changes: 13 additions & 9 deletions docs/dotfiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,36 @@ for public dotfiles. If dotfiles must be private, add a separate SSH recipe late

## Config

Put chezmoi settings in global config (`~/.config/dvm/config.sh`) since the dotfiles
identity is usually the same across every VM. A per-VM config can still override any
of these when needed (per-VM config sources last and wins).

```bash
DVM_CHEZMOI_REPO="https://github.com/YOUR_USER/dotfiles.git"
DVM_CHEZMOI_ROLE="vm"
DVM_CHEZMOI_NAME="Your Name"
DVM_CHEZMOI_EMAIL="you@example.com"
```

Then opt VMs in via the per-VM config:

```bash
use chezmoi
```

The recipe installs `chezmoi`, writes `~/.config/chezmoi/chezmoi.toml` through a
temporary file when configured, initializes `~/.local/share/chezmoi` when missing,
pulls updates when already initialized, and runs `chezmoi apply`.

For common dotfiles template data, put shared identity values in global DVM config:

```bash
DVM_CHEZMOI_ROLE="vm"
DVM_CHEZMOI_NAME="Your Name"
DVM_CHEZMOI_EMAIL="you@example.com"
```

The generated data uses the default key paths created by `dvm ssh-key <name>`:

```toml
signingKey = "~/.ssh/id_ed25519_dvm_signing.pub"
deployKey = "~/.ssh/id_ed25519_dvm.pub"
```

Override the paths in VM config only when you use custom key names:
Override (in global config, or per-VM if a single VM needs different keys) only when
`dvm ssh-key` is configured with custom names:

```bash
DVM_CHEZMOI_SIGNING_KEY="~/.ssh/id_ed25519_project_signing.pub"
Expand Down
24 changes: 12 additions & 12 deletions docs/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ that boundary clear.
## Host Config

Host config lives in `~/.config/dvm/vms/<name>.sh`. `dvm init` writes a fully commented
template (built-in defaults: `DVM_CPUS=2`, `DVM_MEMORY=2GiB`, `DVM_DISK=10GiB`).
Uncomment what you need:
template (built-in defaults: `DVM_CPUS=2`, `DVM_MEMORY=2GiB`, `DVM_DISK=10GiB`) that
calls `use_tools`, a helper defined in `~/.config/dvm/config.sh`. Uncomment recipes
in `use_tools` once and every app VM picks them up. Per-VM configs can add extra
`use <name>` lines for VM-specific recipes:

```bash
# DVM_CPUS=4 # default 2 (host max shown in template)
# DVM_MEMORY=8GiB # default 2GiB
# DVM_CODE_DIR="~/code/$DVM_NAME"
# DVM_PORTS="3000:3000"

# use node # Node.js, npm, corepack
# use agent-user # dvm-agent user with Bubblewrap sandbox for AI tools
# use codex # Codex CLI
use_tools
use codex # extra recipe just for this VM
```

`use <name>` only selects `recipes/<name>.sh`; it does not run the recipe on the host.
Expand All @@ -43,8 +44,7 @@ Rules:
- Use `DVM_CODE_DIR` for project code.
- Keep tool-specific config close to the recipe that uses it.
- Do not add recipe metadata, dependency graphs, registries, or versioning.
- Add `# Description: <one line>` near the top of a recipe so `dvm init` shows it in
the auto-generated "available recipes" comment block.
- Add `# Description: <one line>` near the top of a recipe as a one-liner docstring.

## Built-In Recipes

Expand Down Expand Up @@ -172,16 +172,16 @@ $DVM_CODE_DIR/.dvm/sync.sh

That hook runs after baseline and selected recipes, inside the guest.

`chezmoi` applies public dotfiles over HTTPS:
`chezmoi` applies public dotfiles over HTTPS. Put `DVM_CHEZMOI_REPO` and the optional
identity values in `~/.config/dvm/config.sh` (they are the same across all VMs); the
per-VM config only opts in:

```bash
DVM_CHEZMOI_REPO="https://github.com/YOUR_USER/dotfiles.git"
use chezmoi
```

Shared chezmoi template data such as `DVM_CHEZMOI_ROLE`, `DVM_CHEZMOI_NAME`, and
`DVM_CHEZMOI_EMAIL` usually belongs in `~/.config/dvm/config.sh`; generated key data
uses the default paths from `dvm ssh-key <name>` unless overridden globally or per VM.
Generated key data uses the default paths from `dvm ssh-key <name>` unless
`DVM_CHEZMOI_SIGNING_KEY` / `DVM_CHEZMOI_DEPLOY_KEY` are set in global config.

`llama` installs the llama service. Configure a dedicated VM:

Expand Down
34 changes: 33 additions & 1 deletion share/dvm/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,39 @@
# Set to 0 to enable Claude permission prompts.
# DVM_CLAUDE_BYPASS=1

# Optional chezmoi [data] values for VMs that use the chezmoi recipe:
# Settings for the chezmoi recipe. DVM_CHEZMOI_REPO is required when any VM uses
# `use chezmoi`. The signing/deploy key paths default to those created by
# `dvm ssh-key <name>`; override only if you use custom key names.
# DVM_CHEZMOI_REPO="https://github.com/YOUR_USER/dotfiles.git"
# DVM_CHEZMOI_ROLE="vm"
# DVM_CHEZMOI_NAME="Your Name"
# DVM_CHEZMOI_EMAIL="you@example.com"
# DVM_CHEZMOI_SIGNING_KEY="~/.ssh/id_ed25519_dvm_signing.pub"
# DVM_CHEZMOI_DEPLOY_KEY="~/.ssh/id_ed25519_dvm.pub"

# Default toolset shared across app VMs. The bundled VM template calls
# `use_tools`, so any recipe uncommented here is installed in every VM that
# keeps that call. Define more helpers (e.g. `use_data_tools`) and call them
# from per-VM configs to mix and match.
use_tools() {
: # placeholder; remove once at least one `use` line below is uncommented
# use zsh # zsh shell (sets as login shell)
# use git # git
# use helix # Helix editor
# use lazygit # lazygit TUI
# use starship # starship prompt
# use fzf # fzf fuzzy finder
# use bat # bat (cat with syntax highlighting)
# use git-delta # delta diff pager
# use just # just task runner
# use tmux # tmux terminal multiplexer
# use yazi # yazi file manager
# use node # Node.js, npm, corepack
# use python # Python and uv
# use agent-user # dvm-agent user with Bubblewrap sandbox for AI tools
# use codex # Codex CLI
# use claude # Claude Code CLI
# use opencode # OpenCode CLI
# use mistral # Mistral CLI
# use chezmoi # public dotfiles via chezmoi over HTTPS
}
33 changes: 1 addition & 32 deletions share/dvm/lib/core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,43 +141,12 @@ host_max_memory() {
printf '?\n'
}

available_recipes_block() {
local file name desc dir
{
for dir in "$DVM_SHARE/recipes" "$DVM_CONFIG/recipes"; do
[ -d "$dir" ] || continue
for file in "$dir"/*.sh; do
[ -f "$file" ] || continue
name="$(basename "$file" .sh)"
case "$name" in
_* | baseline) continue ;;
esac
printf '%s\n' "$name"
done
done
} | sort -u | while IFS= read -r name; do
file="$DVM_CONFIG/recipes/$name.sh"
[ -f "$file" ] || file="$DVM_SHARE/recipes/$name.sh"
desc="$(awk '/^# Description:/ {sub(/^# Description: */, ""); print; exit}' "$file" 2>/dev/null || true)"
if [ -n "$desc" ]; then
printf '# use %-12s # %s\n' "$name" "$desc"
else
printf '# use %s\n' "$name"
fi
done
}

render_vm_template() {
local src="$1" dst="$2"
local max_cpus max_memory recipes_block line
local max_cpus max_memory line
max_cpus="$(host_max_cpus)"
max_memory="$(host_max_memory)"
recipes_block="$(available_recipes_block)"
while IFS= read -r line || [ -n "$line" ]; do
if [ "$line" = "__DVM_AVAILABLE_RECIPES__" ]; then
printf '%s\n' "$recipes_block"
continue
fi
case "$line" in
*__DVM_HOST_MAX_CPUS__*) line="${line//__DVM_HOST_MAX_CPUS__/$max_cpus}" ;;
esac
Expand Down
12 changes: 4 additions & 8 deletions share/dvm/vms/app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@
# Forwarded host:guest ports, space-separated. Empty by default.
# DVM_PORTS="3000:3000 5173:5173"

# Public dotfiles repo for the chezmoi recipe.
# DVM_CHEZMOI_REPO="https://github.com/YOUR_USER/dotfiles.git"
# Default toolset (defined in ~/.config/dvm/config.sh). Comment the line below
# for a minimal VM, or define more helpers globally and call them here.
use_tools

# Override SSH key paths only when ssh-key was created with custom names:
# DVM_CHEZMOI_SIGNING_KEY="~/.ssh/id_ed25519_dvm_signing.pub"
# DVM_CHEZMOI_DEPLOY_KEY="~/.ssh/id_ed25519_dvm.pub"

# Available recipes. Uncomment to enable.
__DVM_AVAILABLE_RECIPES__
# Per-VM additions go below.
7 changes: 1 addition & 6 deletions tests/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,11 @@ grep -Fq 'dvm init <name> [template]' "$TMP/install-help.out"
"$ROOT/bin/dvm" init newapp
[ -f "$TMP/config/vms/newapp.sh" ]
grep -Fq 'DVM_CODE_DIR="~/code/$DVM_NAME"' "$TMP/config/vms/newapp.sh"
grep -Fq '# use chezmoi' "$TMP/config/vms/newapp.sh"
grep -Fq '# use node' "$TMP/config/vms/newapp.sh"
grep -Eq '^use_tools$' "$TMP/config/vms/newapp.sh"
if grep -Fq '__DVM_HOST_MAX_CPUS__' "$TMP/config/vms/newapp.sh"; then
printf 'init left __DVM_HOST_MAX_CPUS__ placeholder unsubstituted\n' >&2
exit 1
fi
if grep -Fq '__DVM_AVAILABLE_RECIPES__' "$TMP/config/vms/newapp.sh"; then
printf 'init left __DVM_AVAILABLE_RECIPES__ placeholder unsubstituted\n' >&2
exit 1
fi
"$ROOT/bin/dvm" init llama llama
[ -f "$TMP/config/vms/llama.sh" ]
grep -Fq 'use llama' "$TMP/config/vms/llama.sh"
Expand Down