From f67325d5e2cafcff17ee3a8fc6fa6b3175c1cd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ko=C5=82odziejczyk?= Date: Wed, 6 May 2026 13:22:31 +0200 Subject: [PATCH 1/2] Update commands --- CHANGELOG.md | 3 +++ README.md | 38 ++++++++++++++++---------------- bin/dvm | 10 ++++----- docs/ai.md | 12 +++++----- docs/commands.md | 36 +++++++++++++++--------------- docs/config.md | 6 ++--- docs/dotfiles.md | 2 +- docs/lima.md | 8 +++---- docs/recipes.md | 18 +++++++-------- docs/security-standards.md | 2 +- docs/services.md | 24 ++++++++++---------- share/dvm/lib/apply.sh | 10 ++++----- share/dvm/lib/core.sh | 10 ++++----- share/dvm/lib/guest.sh | 6 ++--- share/dvm/lib/keys.sh | 4 ++-- share/dvm/recipes/cloudflared.sh | 4 ++-- share/dvm/recipes/llama.sh | 2 +- tests/smoke.sh | 28 +++++++++++------------ 18 files changed, 113 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76b8a61..155c3ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 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 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. diff --git a/README.md b/README.md index e1c9c59..d9daff9 100644 --- a/README.md +++ b/README.md @@ -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 ` creates the Lima VM if missing, starts it, runs +`dvm sync ` creates the Lima VM if missing, starts it, runs `recipes/baseline.sh`, runs recipes selected by `~/.config/dvm/vms/.sh`, then -runs `~/code//.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//.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. @@ -112,16 +112,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 @@ -132,11 +132,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 @@ -171,9 +171,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` diff --git a/bin/dvm b/bin/dvm index 043419a..dedff31 100755 --- a/bin/dvm +++ b/bin/dvm @@ -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 ;; diff --git a/docs/ai.md b/docs/ai.md index 1f12440..0a12126 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -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 @@ -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 ` if project permissions are changed manually or restored from an +`dvm sync ` 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: @@ -94,14 +94,14 @@ 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 @@ -109,4 +109,4 @@ and re-run `dvm apply`. - 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. diff --git a/docs/commands.md b/docs/commands.md index 49a2e84..06dd86b 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -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. @@ -18,35 +18,35 @@ dvm init cloudflared cloudflared `~/.config/dvm/vms/.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. @@ -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 @@ -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 @@ -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`. diff --git a/docs/config.md b/docs/config.md index 4bd4148..8b17f6a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -62,7 +62,7 @@ Create a new app VM: ```bash dvm init myapp -dvm apply myapp +dvm sync myapp ``` Example: @@ -153,7 +153,7 @@ Then call the helper from each app VM that should get those tools: use_app_tools ``` -`dvm apply ` prints the expanded recipe list before running guest scripts. If the +`dvm sync ` prints the expanded recipe list before running guest scripts. If the summary does not include the helper's recipes, check that you are running the current wrapper with `type dvm` and that `DVM_CONFIG` points at the config directory you edited. @@ -179,5 +179,5 @@ DVM_PORTS="127.0.0.1:3000:3000" Avoid `0.0.0.0` unless you want the service reachable from your LAN. -Changing `DVM_PORTS` and running `dvm apply ` updates the existing Lima VM's port +Changing `DVM_PORTS` and running `dvm sync ` updates the existing Lima VM's port forwards. Lima may restart the VM when ports change. diff --git a/docs/dotfiles.md b/docs/dotfiles.md index f91db02..d4118ff 100644 --- a/docs/dotfiles.md +++ b/docs/dotfiles.md @@ -69,5 +69,5 @@ the VM and reapply: ```bash dvm ssh app -- rm -rf ~/.local/share/chezmoi -dvm apply app +dvm sync app ``` diff --git a/docs/lima.md b/docs/lima.md index d46f47e..c02cc77 100644 --- a/docs/lima.md +++ b/docs/lima.md @@ -36,7 +36,7 @@ Important defaults: - user first-boot provision ignores empty or host-looking `/Users/...` code dirs rather than failing cloud-init. -DVM sets the guest system hostname to the public VM name during `dvm apply`, so a VM +DVM sets the guest system hostname to the public VM name during `dvm sync`, so a VM configured as `eshlox-net` presents itself as `eshlox-net` inside the guest even though the internal Lima instance remains `dvm-eshlox-net`. @@ -52,7 +52,7 @@ from Git remotes or created there. This is the central isolation choice. Use: ```bash -dvm enter app +dvm sh app ``` Then edit with guest tools installed by your recipes, such as an editor, AI CLI, or @@ -81,7 +81,7 @@ curl http://host.lima.internal:3000 ## Updating Ports And Template -Editing `DVM_PORTS` in a VM config and running `dvm apply ` updates the existing +Editing `DVM_PORTS` in a VM config and running `dvm sync ` updates the existing Lima VM's `portForwards` without recreating the VM. DVM compares the configured ports with the VM's Lima YAML and asks Lima to edit the VM when they differ. @@ -91,7 +91,7 @@ created configuration for structural settings. For those changes, recreate: ```bash dvm rm app --yes -dvm apply app +dvm sync app ``` If an older VM shows failed `cloud-final.service` or `cloud-init-main.service` because diff --git a/docs/recipes.md b/docs/recipes.md index e49651a..b54ea89 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -159,12 +159,12 @@ For a package or tool that does not exist in DNF, use the same split: For non-DNF tools, prefer the pattern used by `lazygit`, `starship`, and `yazi`: download from an official HTTPS release URL, pin a version, verify sha256 before installing, and avoid `curl | sh` installers. To update, bump the version, URL, and -sha256 in the recipe, then run `dvm apply ` or `dvm apply --all`. +sha256 in the recipe, then run `dvm sync ` or `dvm sync --all`. Project-only setup that belongs in the project repository can also live in: ```text -$DVM_CODE_DIR/.dvm/apply.sh +$DVM_CODE_DIR/.dvm/sync.sh ``` That hook runs after baseline and selected recipes, inside the guest. @@ -207,29 +207,29 @@ DVM_DISK=20GiB use cloudflared ``` -Apply with a token when configuring or recreating the VM: +Sync with a token when configuring or recreating the VM: ```bash -CLOUDFLARED_TOKEN="..." dvm apply cloudflared +CLOUDFLARED_TOKEN="..." dvm sync cloudflared ``` DVM does not pass `CLOUDFLARED_TOKEN` or `DVM_CLOUDFLARED_TOKEN` as `limactl shell env` arguments. For the bundled cloudflared recipe, it writes the token to a mode `0600` -guest temp file during `apply`, the recipe copies it into `/etc/cloudflared/dvm.env`, +guest temp file during `sync`, the recipe copies it into `/etc/cloudflared/dvm.env`, and the temp file is removed. If you want host convenience, store the token in macOS Keychain yourself and pass it at -apply time: +sync time: ```bash security add-generic-password -a dvm -s cloudflared -w "$TOKEN" CLOUDFLARED_TOKEN="$(security find-generic-password -a dvm -s cloudflared -w)" \ - dvm apply cloudflared + dvm sync cloudflared ``` DVM does not provide a secret store command. -`dvm logs llama` and `dvm logs cloudflared` show the default service units for those +`dvm log llama` and `dvm log cloudflared` show the default service units for those dedicated VMs. ## Project Hook @@ -237,7 +237,7 @@ dedicated VMs. After selected recipes run, DVM checks for this guest file: ```text -$DVM_CODE_DIR/.dvm/apply.sh +$DVM_CODE_DIR/.dvm/sync.sh ``` If it exists, DVM runs it inside the VM. Use it for project-local setup that belongs in diff --git a/docs/security-standards.md b/docs/security-standards.md index 5f08792..94e6bc8 100644 --- a/docs/security-standards.md +++ b/docs/security-standards.md @@ -54,7 +54,7 @@ small, but they are the bar for changes. - Bind forwarded ports to `127.0.0.1` by default. - Use `0.0.0.0` only when you intentionally want LAN exposure. -- Re-run `dvm apply ` after editing `DVM_PORTS`; DVM updates existing Lima port +- Re-run `dvm sync ` after editing `DVM_PORTS`; DVM updates existing Lima port forwards without recreating the VM. - Put shared services such as llama and cloudflared in dedicated VMs. - Use Lima internal names for VM-to-VM traffic. diff --git a/docs/services.md b/docs/services.md index aeecb78..dd889f2 100644 --- a/docs/services.md +++ b/docs/services.md @@ -2,7 +2,7 @@ Long-running services should usually get dedicated VMs. That keeps project VMs small and lets other VMs reach services through Lima's internal names. Bundled service VM -examples set `DVM_NO_BASELINE=1`, so service applies install only the service recipe. +examples set `DVM_NO_BASELINE=1`, so service syncs install only the service recipe. ## Llama @@ -10,7 +10,7 @@ Create an active config: ```bash dvm init llama llama -dvm apply llama +dvm sync llama ``` Optional model download: @@ -34,7 +34,7 @@ If no model URL is configured, place a model at: inside the llama VM, then run: ```bash -dvm apply llama +dvm sync llama ``` Other VMs can call: @@ -55,7 +55,7 @@ curl http://lima-dvm-llama.internal:8080 Logs: ```bash -dvm logs llama +dvm log llama ``` ## Cloudflared @@ -64,13 +64,13 @@ Create an active config: ```bash dvm init cloudflared cloudflared -CLOUDFLARED_TOKEN="..." dvm apply cloudflared +CLOUDFLARED_TOKEN="..." dvm sync cloudflared ``` The example config maps `CLOUDFLARED_TOKEN` to `DVM_CLOUDFLARED_TOKEN`. The recipe writes `/etc/cloudflared/dvm.env` with mode `0600` when a token is present and starts `dvm-cloudflared.service`. DVM stages that token through a mode `0600` guest temp file -during `apply`, so the token is not passed as a `limactl shell env` argument on the +during `sync`, so the token is not passed as a `limactl shell env` argument on the host. For host convenience, use macOS Keychain yourself: @@ -78,7 +78,7 @@ For host convenience, use macOS Keychain yourself: ```bash security add-generic-password -a dvm -s cloudflared -w "$TOKEN" CLOUDFLARED_TOKEN="$(security find-generic-password -a dvm -s cloudflared -w)" \ - dvm apply cloudflared + dvm sync cloudflared ``` DVM does not have a secret command. Rotate the token in Cloudflare if the VM is @@ -86,13 +86,13 @@ compromised. ## Logs -DVM has a logs helper for service VMs: +DVM has a log helper for service VMs: ```bash -dvm logs cloudflared -dvm logs cloudflared -f -dvm logs cloudflared dvm-cloudflared.service -f -dvm logs llama +dvm log cloudflared +dvm log cloudflared -f +dvm log cloudflared dvm-cloudflared.service -f +dvm log llama ``` If a VM has no known service recipe or more than one, pass the systemd unit explicitly. diff --git a/share/dvm/lib/apply.sh b/share/dvm/lib/apply.sh index 473cb1e..4d63ba9 100644 --- a/share/dvm/lib/apply.sh +++ b/share/dvm/lib/apply.sh @@ -17,7 +17,7 @@ run_guest_apply() { esac done < <(compgen -A variable | sort) - printf 'dvm: applying recipes for %s:' "$DVM_NAME" >&2 + printf 'dvm: syncing recipes for %s:' "$DVM_NAME" >&2 if [ "${DVM_NO_BASELINE:-0}" != "1" ]; then printf ' baseline' >&2 fi @@ -65,8 +65,8 @@ case "$dvm_code_dir" in "~") dvm_code_dir="$HOME" ;; "~/"*) dvm_code_dir="$HOME/${dvm_code_dir#\~/}" ;; esac -if [ -f "$dvm_code_dir/.dvm/apply.sh" ]; then - bash "$dvm_code_dir/.dvm/apply.sh" +if [ -f "$dvm_code_dir/.dvm/sync.sh" ]; then + bash "$dvm_code_dir/.dvm/sync.sh" fi DVM_PROJECT_HOOK } | limactl shell "$DVM_LIMA_NAME" env "${args[@]}" bash -s @@ -109,10 +109,10 @@ apply_all() { if (apply_one "$name"); then ok=$((ok + 1)) else - printf 'dvm: apply failed: %s\n' "$name" >&2 + printf 'dvm: sync failed: %s\n' "$name" >&2 failed=$((failed + 1)) fi done - printf 'dvm apply --all: %s ok, %s failed\n' "$ok" "$failed" + printf 'dvm sync --all: %s ok, %s failed\n' "$ok" "$failed" [ "$failed" -eq 0 ] } diff --git a/share/dvm/lib/core.sh b/share/dvm/lib/core.sh index 77f4b62..a7c4d7c 100644 --- a/share/dvm/lib/core.sh +++ b/share/dvm/lib/core.sh @@ -4,15 +4,15 @@ usage() { cat <<'HELP' usage: dvm init [template] - dvm apply - dvm apply --all - dvm enter + dvm sync + dvm sync --all + dvm sh dvm ssh -- dvm cp [-r] [-v] [--backend auto|scp|rsync] - dvm logs [unit] [journalctl-args...] + dvm log [unit] [journalctl-args...] dvm ssh-key dvm gpg-key - dvm list + dvm ls dvm stop dvm rm --yes [--force] HELP diff --git a/share/dvm/lib/guest.sh b/share/dvm/lib/guest.sh index ab72385..90184a2 100644 --- a/share/dvm/lib/guest.sh +++ b/share/dvm/lib/guest.sh @@ -39,7 +39,7 @@ ssh_vm() { shift || true [ "${1:-}" != "--" ] || shift load_vm "$name" - vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm apply $name first" + vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm sync $name first" start_vm term="$(guest_term)" limactl shell "$DVM_LIMA_NAME" env "TERM=$term" bash -c "$guest_cd_script" dvm-ssh "$DVM_CODE_DIR" "$@" @@ -218,7 +218,7 @@ cp_vm() { [ "$endpoint_count" -lt "${#operands[@]}" ] || die "cp requires one host path and one VM path" load_vm "$vm_name" - vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm apply $vm_name first" + vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm sync $vm_name first" start_vm limactl shell "$DVM_LIMA_NAME" mkdir -p "$(guest_code_dir_abs)" @@ -301,7 +301,7 @@ logs_vm() { [ -n "$name" ] || die "logs requires a VM name" shift || true load_vm "$name" - vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm apply $name first" + vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm sync $name first" start_vm if [ "$#" -gt 0 ]; then case "$1" in diff --git a/share/dvm/lib/keys.sh b/share/dvm/lib/keys.sh index cfc2a1c..7e0d0bb 100644 --- a/share/dvm/lib/keys.sh +++ b/share/dvm/lib/keys.sh @@ -5,7 +5,7 @@ ssh_key_vm() { name="${1:-}" [ -n "$name" ] || die "ssh-key requires a VM name" load_vm "$name" - vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm apply $name first" + vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm sync $name first" start_vm limactl shell "$DVM_LIMA_NAME" env "DVM_NAME=$DVM_NAME" bash -s <<'DVM_SSH_KEY' set -euo pipefail @@ -66,7 +66,7 @@ gpg_key_vm() { name="${1:-}" [ -n "$name" ] || die "gpg-key requires a VM name" load_vm "$name" - vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm apply $name first" + vm_exists || die "VM does not exist: $DVM_LIMA_NAME; run dvm sync $name first" start_vm limactl shell "$DVM_LIMA_NAME" env "DVM_NAME=$DVM_NAME" bash -s <<'DVM_GPG_KEY' set -euo pipefail diff --git a/share/dvm/recipes/cloudflared.sh b/share/dvm/recipes/cloudflared.sh index bf3bfbd..eb06a3d 100755 --- a/share/dvm/recipes/cloudflared.sh +++ b/share/dvm/recipes/cloudflared.sh @@ -56,8 +56,8 @@ if [ -z "$token" ]; then cat <"$TMP/apply.err" -grep -Fq 'dvm: applying recipes for app: baseline zsh git helix lazygit starship fzf bat git-delta just tmux yazi node agent-user codex claude chezmoi' "$TMP/apply.err" +"$ROOT/bin/dvm" sync app 2>"$TMP/apply.err" +grep -Fq 'dvm: syncing recipes for app: baseline zsh git helix lazygit starship fzf bat git-delta just tmux yazi node agent-user codex claude chezmoi' "$TMP/apply.err" grep -Fq 'create dvm-app' "$TMP/state/log" grep -Fq 'start dvm-app' "$TMP/state/log" grep -Fq 'DVM_CODE_DIR=~/code/app' "$TMP/state/log" @@ -240,7 +240,7 @@ grep -Fq 'hostPort: 3000' "$TMP/state/lima.yaml" bash -n "$TMP/state/guest.sh" : >"$TMP/state/log" -CLOUDFLARED_TOKEN="smoke.Token_123=-" "$ROOT/bin/dvm" apply cloudflared +CLOUDFLARED_TOKEN="smoke.Token_123=-" "$ROOT/bin/dvm" sync cloudflared grep -Fq 'dvm-cloudflared-token.' "$TMP/state/guest.sh" grep -Fq 'token_file="${DVM_CLOUDFLARED_TOKEN_FILE:-}"' "$TMP/state/guest.sh" grep -Fq 'ActiveEnterTimestamp' "$TMP/state/guest.sh" @@ -254,15 +254,15 @@ if grep -Fq 'DVM_CLOUDFLARED_TOKEN=smoke.Token_123=-' "$TMP/state/log"; then fi perl -0pi -e 's/DVM_PORTS="3000:3000"/DVM_PORTS="3000:3000 9000:9000"/' "$TMP/config/vms/app.sh" -"$ROOT/bin/dvm" apply app +"$ROOT/bin/dvm" sync app grep -Fq 'edit --tty=false --set .portForwards' "$TMP/state/log" bash -n "$TMP/state/guest.sh" -"$ROOT/bin/dvm" list >"$TMP/list.out" +"$ROOT/bin/dvm" ls >"$TMP/list.out" grep -Eq '^NAME[[:space:]]+STATUS[[:space:]]+SSH' "$TMP/list.out" grep -Eq '^app[[:space:]]+Running[[:space:]]+127\.0\.0\.1:60022' "$TMP/list.out" if grep -Fq 'dvm-app' "$TMP/list.out"; then - printf 'dvm list leaked internal Lima prefix\n' >&2 + printf 'dvm ls leaked internal Lima prefix\n' >&2 exit 1 fi @@ -322,7 +322,7 @@ mkdir -p "$TMP/state/dvm-race" cp "$TMP/state/lima.yaml" "$TMP/state/dvm-race/lima.yaml" grep -Fxq dvm-race "$TMP/state/created" || printf '%s\n' dvm-race >>"$TMP/state/created" touch "$TMP/state/list_empty_once" -"$ROOT/bin/dvm" apply race +"$ROOT/bin/dvm" sync race grep -Fq 'start dvm-race' "$TMP/state/log" grep -Fq 'shell dvm-race env ' "$TMP/state/log" rm -f "$TMP/config/vms/race.sh" @@ -361,7 +361,7 @@ grep -Fq 'delete dvm-orphan' "$TMP/state/log" : >"$TMP/state/log" rm -f "$TMP/state/created" rm -rf "$TMP/state"/dvm-* -"$ROOT/bin/dvm" apply --all >"$TMP/apply-all.out" +"$ROOT/bin/dvm" sync --all >"$TMP/apply-all.out" grep -Fq 'create dvm-app' "$TMP/state/log" grep -Fq 'create dvm-second' "$TMP/state/log" if grep -F 'shell dvm-second ' "$TMP/state/log" | grep -Fq 'DVM_APP_ONLY='; then @@ -369,10 +369,10 @@ if grep -F 'shell dvm-second ' "$TMP/state/log" | grep -Fq 'DVM_APP_ONLY='; then exit 1 fi -"$ROOT/bin/dvm" logs cloudflared +"$ROOT/bin/dvm" log cloudflared grep -Fq 'shell dvm-cloudflared sudo journalctl -u dvm-cloudflared.service --no-pager -n 100' "$TMP/state/log" -"$ROOT/bin/dvm" logs cloudflared -f +"$ROOT/bin/dvm" log cloudflared -f grep -Fq 'shell dvm-cloudflared sudo journalctl -u dvm-cloudflared.service -f' "$TMP/state/log" cat >"$TMP/config/vms/invalid.sh" <<'VM' @@ -386,7 +386,7 @@ use python VM set +e -"$ROOT/bin/dvm" apply invalid >/dev/null 2>"$TMP/invalid.err" +"$ROOT/bin/dvm" sync invalid >/dev/null 2>"$TMP/invalid.err" status="$?" set -e [ "$status" -ne 0 ] @@ -406,11 +406,11 @@ VM rm -f "$TMP/state/created" rm -rf "$TMP/state"/dvm-* set +e -"$ROOT/bin/dvm" apply --all >"$TMP/apply-all-fail.out" 2>"$TMP/apply-all-fail.err" +"$ROOT/bin/dvm" sync --all >"$TMP/apply-all-fail.out" 2>"$TMP/apply-all-fail.err" status="$?" set -e [ "$status" -ne 0 ] -grep -Fq 'dvm: apply failed: bad' "$TMP/apply-all-fail.err" -grep -Fq 'dvm apply --all:' "$TMP/apply-all-fail.out" +grep -Fq 'dvm: sync failed: bad' "$TMP/apply-all-fail.err" +grep -Fq 'dvm sync --all:' "$TMP/apply-all-fail.out" grep -Fq '1 failed' "$TMP/apply-all-fail.out" grep -Fq 'create dvm-second' "$TMP/state/log" From 1bb998c76597c9abb59ac2825d97192ec342ed1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ko=C5=82odziejczyk?= Date: Wed, 6 May 2026 13:43:11 +0200 Subject: [PATCH 2/2] Better default configs --- CHANGELOG.md | 9 ++++ README.md | 22 ++------- docs/config.md | 73 ++++++++++++++-------------- docs/recipes.md | 21 ++++---- share/dvm/config.sh | 27 ++++++----- share/dvm/lib/core.sh | 82 +++++++++++++++++++++++++++++++- share/dvm/recipes/agent-user.sh | 1 + share/dvm/recipes/bat.sh | 1 + share/dvm/recipes/chezmoi.sh | 1 + share/dvm/recipes/claude.sh | 1 + share/dvm/recipes/cloudflared.sh | 1 + share/dvm/recipes/codex.sh | 1 + share/dvm/recipes/fzf.sh | 1 + share/dvm/recipes/git-delta.sh | 1 + share/dvm/recipes/git.sh | 1 + share/dvm/recipes/helix.sh | 1 + share/dvm/recipes/just.sh | 1 + share/dvm/recipes/lazygit.sh | 1 + share/dvm/recipes/llama.sh | 1 + share/dvm/recipes/mistral.sh | 1 + share/dvm/recipes/node.sh | 1 + share/dvm/recipes/opencode.sh | 1 + share/dvm/recipes/python.sh | 1 + share/dvm/recipes/starship.sh | 1 + share/dvm/recipes/tmux.sh | 1 + share/dvm/recipes/yazi.sh | 1 + share/dvm/recipes/zsh.sh | 1 + share/dvm/vms/app.sh | 32 +++++++------ tests/smoke.sh | 10 ++++ 29 files changed, 208 insertions(+), 89 deletions(-) mode change 100755 => 100644 share/dvm/recipes/agent-user.sh mode change 100755 => 100644 share/dvm/recipes/bat.sh mode change 100755 => 100644 share/dvm/recipes/chezmoi.sh mode change 100755 => 100644 share/dvm/recipes/claude.sh mode change 100755 => 100644 share/dvm/recipes/cloudflared.sh mode change 100755 => 100644 share/dvm/recipes/codex.sh mode change 100755 => 100644 share/dvm/recipes/fzf.sh mode change 100755 => 100644 share/dvm/recipes/git-delta.sh mode change 100755 => 100644 share/dvm/recipes/git.sh mode change 100755 => 100644 share/dvm/recipes/helix.sh mode change 100755 => 100644 share/dvm/recipes/just.sh mode change 100755 => 100644 share/dvm/recipes/lazygit.sh mode change 100755 => 100644 share/dvm/recipes/llama.sh mode change 100755 => 100644 share/dvm/recipes/mistral.sh mode change 100755 => 100644 share/dvm/recipes/node.sh mode change 100755 => 100644 share/dvm/recipes/opencode.sh mode change 100755 => 100644 share/dvm/recipes/python.sh mode change 100755 => 100644 share/dvm/recipes/starship.sh mode change 100755 => 100644 share/dvm/recipes/tmux.sh mode change 100755 => 100644 share/dvm/recipes/yazi.sh mode change 100755 => 100644 share/dvm/recipes/zsh.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 155c3ee..1f79c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ - 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: ` 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. diff --git a/README.md b/README.md index d9daff9..fb295f9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/config.md b/docs/config.md index 8b17f6a..3b1478d 100644 --- a/docs/config.md +++ b/docs/config.md @@ -11,28 +11,25 @@ Global config lives at: ~/.config/dvm/config.sh ``` -Defaults copied by `./install.sh --init`: - -```bash -DVM_CPUS=4 -DVM_MEMORY=8GiB -DVM_DISK=80GiB -DVM_ARCH=default -DVM_USER="${USER:-developer}" -DVM_CODE_ROOT="~/code" -DVM_HOST_IP="127.0.0.1" -DVM_AI_AGENT_USER="dvm-agent" -# Codex defaults to unattended yolo mode inside the dvm-agent Bubblewrap sandbox. -# Set to 0 in a VM config when you want Codex approval prompts and its own sandbox. -# DVM_CODEX_YOLO=1 -# Claude defaults to unattended bypass mode inside the dvm-agent Bubblewrap sandbox. -# Set to 0 in a VM config when you want Claude permission prompts. -# DVM_CLAUDE_BYPASS=1 -# Optional for VMs that use the chezmoi recipe: -# DVM_CHEZMOI_ROLE="vm" -# DVM_CHEZMOI_NAME="Your Name" -# DVM_CHEZMOI_EMAIL="you@example.com" -``` +Built-in defaults apply when a variable is unset: + +| Variable | Default | +| -------------------- | -------------------------------- | +| `DVM_CPUS` | `2` | +| `DVM_MEMORY` | `2GiB` | +| `DVM_DISK` | `10GiB` | +| `DVM_ARCH` | `default` (resolves to host arch)| +| `DVM_USER` | `${USER:-developer}` | +| `DVM_CODE_ROOT` | `~/code` | +| `DVM_CODE_DIR` | `${DVM_CODE_ROOT}/` | +| `DVM_PORTS` | empty | +| `DVM_HOST_IP` | `127.0.0.1` | +| `DVM_AI_AGENT_USER` | `dvm-agent` | + +`./install.sh --init` copies a fully commented `share/dvm/config.sh` to +`~/.config/dvm/config.sh`. Uncomment lines there to override the built-in defaults +globally. Per-VM config in `~/.config/dvm/vms/.sh` overrides global config in +turn. `DVM_ARCH=default` resolves to `aarch64` on Apple Silicon and `x86_64` on Intel before rendering the Lima YAML. @@ -65,27 +62,29 @@ dvm init myapp dvm sync myapp ``` -Example: +`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: ```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_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 ``` ## Variables -- `DVM_CPUS`, `DVM_MEMORY`, `DVM_DISK`: Lima VM sizing. +- `DVM_CPUS`, `DVM_MEMORY`, `DVM_DISK`: Lima VM sizing. Default `2`, `2GiB`, `10GiB`. - `DVM_ARCH`: `default`, `aarch64`, or `x86_64`. - `DVM_USER`: primary guest user. - `DVM_CODE_ROOT`: default parent for VM code directories. diff --git a/docs/recipes.md b/docs/recipes.md index b54ea89..1fa466c 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -5,18 +5,19 @@ that boundary clear. ## Host Config -Host config lives in `~/.config/dvm/vms/.sh`: +Host config lives in `~/.config/dvm/vms/.sh`. `dvm init` writes a fully commented +template (built-in defaults: `DVM_CPUS=2`, `DVM_MEMORY=2GiB`, `DVM_DISK=10GiB`). +Uncomment what you need: ```bash -DVM_CPUS=4 -DVM_MEMORY=8GiB -DVM_DISK=80GiB -DVM_CODE_DIR="~/code/app" -DVM_PORTS="3000:3000" +# 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 -use agent-user -use codex +# use node # Node.js, npm, corepack +# use agent-user # dvm-agent user with Bubblewrap sandbox for AI tools +# use codex # Codex CLI ``` `use ` only selects `recipes/.sh`; it does not run the recipe on the host. @@ -42,6 +43,8 @@ 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: ` near the top of a recipe so `dvm init` shows it in + the auto-generated "available recipes" comment block. ## Built-In Recipes diff --git a/share/dvm/config.sh b/share/dvm/config.sh index 5bea7ea..5c74097 100644 --- a/share/dvm/config.sh +++ b/share/dvm/config.sh @@ -1,20 +1,25 @@ # shellcheck shell=bash # shellcheck disable=SC2034,SC2088 -# Global DVM defaults. Copy to ~/.config/dvm/config.sh and edit there. +# Global DVM defaults. Uncomment lines below to override built-in defaults. +# Per-VM config in ~/.config/dvm/vms/.sh overrides these in turn. + +# VM resources (per-VM DVM_CPUS / DVM_MEMORY / DVM_DISK override these): +# DVM_CPUS=2 # default 2 +# DVM_MEMORY=2GiB # default 2GiB +# DVM_DISK=10GiB # default 10GiB + +# DVM_ARCH=default # default; resolves to host arch +# DVM_USER="${USER:-developer}" # default; primary guest user +# DVM_CODE_ROOT="~/code" # default; parent for DVM_CODE_DIR +# DVM_HOST_IP="127.0.0.1" # default; bind IP for two-part DVM_PORTS +# DVM_AI_AGENT_USER="dvm-agent" # default; user for AI tools -DVM_CPUS=4 -DVM_MEMORY=8GiB -DVM_DISK=80GiB -DVM_ARCH=default -DVM_USER="${USER:-developer}" -DVM_CODE_ROOT="~/code" -DVM_HOST_IP="127.0.0.1" -DVM_AI_AGENT_USER="dvm-agent" # Codex defaults to unattended yolo mode inside the dvm-agent Bubblewrap sandbox. -# Set to 0 in a VM config when you want Codex approval prompts and its own sandbox. +# Set to 0 to enable Codex approval prompts and its own sandbox. # DVM_CODEX_YOLO=1 + # Claude defaults to unattended bypass mode inside the dvm-agent Bubblewrap sandbox. -# Set to 0 in a VM config when you want Claude permission prompts. +# Set to 0 to enable Claude permission prompts. # DVM_CLAUDE_BYPASS=1 # Optional chezmoi [data] values for VMs that use the chezmoi recipe: diff --git a/share/dvm/lib/core.sh b/share/dvm/lib/core.sh index a7c4d7c..ee3ba3f 100644 --- a/share/dvm/lib/core.sh +++ b/share/dvm/lib/core.sh @@ -112,6 +112,82 @@ open_editor() { $editor "$file" } +host_max_cpus() { + if command -v nproc >/dev/null 2>&1; then + nproc 2>/dev/null && return 0 + fi + if command -v sysctl >/dev/null 2>&1; then + sysctl -n hw.ncpu 2>/dev/null && return 0 + fi + printf '?\n' +} + +host_max_memory() { + local kb bytes + if [ -r /proc/meminfo ]; then + kb="$(awk '/^MemTotal:/ {print $2; exit}' /proc/meminfo 2>/dev/null || true)" + if [ -n "$kb" ]; then + printf '%dGiB\n' "$((kb / 1024 / 1024))" + return 0 + fi + fi + if command -v sysctl >/dev/null 2>&1; then + bytes="$(sysctl -n hw.memsize 2>/dev/null || true)" + if [ -n "$bytes" ]; then + printf '%dGiB\n' "$((bytes / 1024 / 1024 / 1024))" + return 0 + fi + fi + 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 + 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 + case "$line" in + *__DVM_HOST_MAX_MEMORY__*) line="${line//__DVM_HOST_MAX_MEMORY__/$max_memory}" ;; + esac + printf '%s\n' "$line" + done <"$src" >"$dst" +} + init_vm() { local name template src dst name="${1:-}" @@ -126,7 +202,7 @@ init_vm() { printf 'dvm: VM config already exists: %s\n' "$dst" >&2 else mkdir -p "$(dirname "$dst")" - cp "$src" "$dst" + render_vm_template "$src" "$dst" printf 'dvm: created VM config: %s\n' "$dst" fi open_editor "$dst" @@ -191,6 +267,10 @@ load_vm() { DVM_CODE_DIR="${DVM_CODE_DIR:-${DVM_CODE_ROOT%/}/$name}" DVM_PORTS="${DVM_PORTS:-}" + DVM_CPUS="${DVM_CPUS:-2}" + DVM_MEMORY="${DVM_MEMORY:-2GiB}" + DVM_DISK="${DVM_DISK:-10GiB}" + DVM_USER="${DVM_USER:-${USER:-developer}}" DVM_HOST_IP="${DVM_HOST_IP:-127.0.0.1}" DVM_LLAMA_SERVICE="${DVM_LLAMA_SERVICE:-dvm-llama.service}" DVM_CLOUDFLARED_SERVICE="${DVM_CLOUDFLARED_SERVICE:-dvm-cloudflared.service}" diff --git a/share/dvm/recipes/agent-user.sh b/share/dvm/recipes/agent-user.sh old mode 100755 new mode 100644 index 6335792..b6c41ab --- a/share/dvm/recipes/agent-user.sh +++ b/share/dvm/recipes/agent-user.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: dvm-agent user with Bubblewrap sandbox for AI tools set -euo pipefail : "${DVM_CODE_DIR:?DVM_CODE_DIR is required}" diff --git a/share/dvm/recipes/bat.sh b/share/dvm/recipes/bat.sh old mode 100755 new mode 100644 index 1d50ecb..687b5c8 --- a/share/dvm/recipes/bat.sh +++ b/share/dvm/recipes/bat.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: bat (cat with syntax highlighting) set -euo pipefail sudo dnf5 install -y bat diff --git a/share/dvm/recipes/chezmoi.sh b/share/dvm/recipes/chezmoi.sh old mode 100755 new mode 100644 index 9bc8fd8..0b0ef2f --- a/share/dvm/recipes/chezmoi.sh +++ b/share/dvm/recipes/chezmoi.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: public dotfiles via chezmoi over HTTPS set -euo pipefail : "${DVM_CHEZMOI_REPO:?DVM_CHEZMOI_REPO is required for recipe chezmoi}" diff --git a/share/dvm/recipes/claude.sh b/share/dvm/recipes/claude.sh old mode 100755 new mode 100644 index 2104630..30af81c --- a/share/dvm/recipes/claude.sh +++ b/share/dvm/recipes/claude.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: Claude Code CLI set -euo pipefail : "${DVM_AI_AGENT_USER:=dvm-agent}" diff --git a/share/dvm/recipes/cloudflared.sh b/share/dvm/recipes/cloudflared.sh old mode 100755 new mode 100644 index eb06a3d..632ca41 --- a/share/dvm/recipes/cloudflared.sh +++ b/share/dvm/recipes/cloudflared.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: Cloudflare Tunnel service (dedicated VM) set -euo pipefail cloudflared_die() { diff --git a/share/dvm/recipes/codex.sh b/share/dvm/recipes/codex.sh old mode 100755 new mode 100644 index 62d5d17..f96282a --- a/share/dvm/recipes/codex.sh +++ b/share/dvm/recipes/codex.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: Codex CLI set -euo pipefail : "${DVM_AI_AGENT_USER:=dvm-agent}" diff --git a/share/dvm/recipes/fzf.sh b/share/dvm/recipes/fzf.sh old mode 100755 new mode 100644 index 026b93f..4cd376c --- a/share/dvm/recipes/fzf.sh +++ b/share/dvm/recipes/fzf.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: fzf fuzzy finder set -euo pipefail sudo dnf5 install -y fzf diff --git a/share/dvm/recipes/git-delta.sh b/share/dvm/recipes/git-delta.sh old mode 100755 new mode 100644 index 08b424a..653d853 --- a/share/dvm/recipes/git-delta.sh +++ b/share/dvm/recipes/git-delta.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: delta diff pager set -euo pipefail sudo dnf5 install -y git-delta diff --git a/share/dvm/recipes/git.sh b/share/dvm/recipes/git.sh old mode 100755 new mode 100644 index 223bf07..2c700e3 --- a/share/dvm/recipes/git.sh +++ b/share/dvm/recipes/git.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: git set -euo pipefail sudo dnf5 install -y git diff --git a/share/dvm/recipes/helix.sh b/share/dvm/recipes/helix.sh old mode 100755 new mode 100644 index 4e20d8b..8ca4b0f --- a/share/dvm/recipes/helix.sh +++ b/share/dvm/recipes/helix.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: Helix editor set -euo pipefail sudo dnf5 install -y helix diff --git a/share/dvm/recipes/just.sh b/share/dvm/recipes/just.sh old mode 100755 new mode 100644 index 7dc49ed..047cc4e --- a/share/dvm/recipes/just.sh +++ b/share/dvm/recipes/just.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: just task runner set -euo pipefail sudo dnf5 install -y just diff --git a/share/dvm/recipes/lazygit.sh b/share/dvm/recipes/lazygit.sh old mode 100755 new mode 100644 index d65a6d7..ff21352 --- a/share/dvm/recipes/lazygit.sh +++ b/share/dvm/recipes/lazygit.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: lazygit TUI set -euo pipefail # Pinned fallback release checked 2026-05-03: diff --git a/share/dvm/recipes/llama.sh b/share/dvm/recipes/llama.sh old mode 100755 new mode 100644 index 9bae5a4..00bd4a4 --- a/share/dvm/recipes/llama.sh +++ b/share/dvm/recipes/llama.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: llama.cpp service (dedicated VM) set -euo pipefail llama_die() { diff --git a/share/dvm/recipes/mistral.sh b/share/dvm/recipes/mistral.sh old mode 100755 new mode 100644 index c69ebfc..148e067 --- a/share/dvm/recipes/mistral.sh +++ b/share/dvm/recipes/mistral.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: Mistral CLI set -euo pipefail : "${DVM_AI_AGENT_USER:=dvm-agent}" diff --git a/share/dvm/recipes/node.sh b/share/dvm/recipes/node.sh old mode 100755 new mode 100644 index 75f2665..91b1bab --- a/share/dvm/recipes/node.sh +++ b/share/dvm/recipes/node.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: Node.js, npm, corepack set -euo pipefail sudo dnf5 install -y nodejs npm diff --git a/share/dvm/recipes/opencode.sh b/share/dvm/recipes/opencode.sh old mode 100755 new mode 100644 index 4cb9adc..aea23d5 --- a/share/dvm/recipes/opencode.sh +++ b/share/dvm/recipes/opencode.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: OpenCode CLI set -euo pipefail : "${DVM_AI_AGENT_USER:=dvm-agent}" diff --git a/share/dvm/recipes/python.sh b/share/dvm/recipes/python.sh old mode 100755 new mode 100644 index 125393e..8c970de --- a/share/dvm/recipes/python.sh +++ b/share/dvm/recipes/python.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: Python and uv set -euo pipefail sudo dnf5 install -y python3 python3-pip uv diff --git a/share/dvm/recipes/starship.sh b/share/dvm/recipes/starship.sh old mode 100755 new mode 100644 index bc757b5..4ad3ad2 --- a/share/dvm/recipes/starship.sh +++ b/share/dvm/recipes/starship.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: starship prompt set -euo pipefail # Pinned fallback release checked 2026-05-03: diff --git a/share/dvm/recipes/tmux.sh b/share/dvm/recipes/tmux.sh old mode 100755 new mode 100644 index d586131..6164562 --- a/share/dvm/recipes/tmux.sh +++ b/share/dvm/recipes/tmux.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: tmux terminal multiplexer set -euo pipefail sudo dnf5 install -y tmux diff --git a/share/dvm/recipes/yazi.sh b/share/dvm/recipes/yazi.sh old mode 100755 new mode 100644 index 1643ed1..545ef76 --- a/share/dvm/recipes/yazi.sh +++ b/share/dvm/recipes/yazi.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: yazi file manager set -euo pipefail # Pinned fallback release checked 2026-05-03: diff --git a/share/dvm/recipes/zsh.sh b/share/dvm/recipes/zsh.sh old mode 100755 new mode 100644 index 2374c95..39b3e7b --- a/share/dvm/recipes/zsh.sh +++ b/share/dvm/recipes/zsh.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Description: zsh shell (sets as login shell) set -euo pipefail sudo dnf5 install -y zsh shadow-utils diff --git a/share/dvm/vms/app.sh b/share/dvm/vms/app.sh index 76923c1..2bd1915 100644 --- a/share/dvm/vms/app.sh +++ b/share/dvm/vms/app.sh @@ -1,20 +1,24 @@ # shellcheck shell=bash # shellcheck disable=SC2034,SC2088 -# Example project VM. Copy to ~/.config/dvm/vms/.sh and edit. +# Project VM. Uncomment lines below to override defaults. -DVM_CPUS=4 -DVM_MEMORY=8GiB -DVM_DISK=80GiB -DVM_CODE_DIR="~/code/$DVM_NAME" -DVM_PORTS="3000:3000 5173:5173" -DVM_CHEZMOI_REPO="https://github.com/YOUR_USER/dotfiles.git" -# Optional overrides when you use non-default `dvm ssh-key` paths: +# Resources +# DVM_CPUS=2 # default 2 (host max: __DVM_HOST_MAX_CPUS__) +# DVM_MEMORY=2GiB # default 2GiB (host max: __DVM_HOST_MAX_MEMORY__) +# DVM_DISK=10GiB # default 10GiB + +# Code directory inside the guest. ~ expands to the guest user home. +# DVM_CODE_DIR="~/code/$DVM_NAME" + +# 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" + +# 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" -use node -use python -use agent-user -use codex -use claude -use chezmoi +# Available recipes. Uncomment to enable. +__DVM_AVAILABLE_RECIPES__ diff --git a/tests/smoke.sh b/tests/smoke.sh index 1e35ea9..8ebee96 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -178,6 +178,16 @@ grep -Fq 'dvm init [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" +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"