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
240 changes: 43 additions & 197 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@

Keep your friends close, your supply chain in a VM.

DVM, short for dev VM, is a small open source command-line wrapper around Lima for
Fedora project VMs.
DVM, short for dev VM, is a small Bash wrapper around Lima for Fedora project VMs. It
keeps project code off the host, creates per-VM SSH keys, supports per-VM GPG signing
subkeys, syncs opt-in host dotfiles as snapshots, and provides an isolated `dvm-agent`
user for hosted AI coding tools.

The repository contains the reusable core. User-specific VM behavior lives outside the
repo in `~/.config/dvm`, so local configuration can be kept in a separate dotfiles or
project configuration repository.

DVM is intentionally small. It creates similar Lima VMs, reruns user setup scripts,
manages per-VM SSH keys, helps with GPG signing subkeys, and refuses to delete a VM
when repositories have uncommitted changes.

## Install

Requirements:
## Requirements

- macOS
- Bash
- Lima
- GPG for `dvm gpg ...`

Guest support target:

- Lima `template:fedora`
- `dnf5` available in the guest

## Install

For stable installations, use a signed release tag:

```bash
Expand All @@ -33,18 +32,12 @@ git checkout --detach vX.Y.Z
./install.sh --init
```

Replace `vX.Y.Z` with the release version to install. The `main` branch is for
development and testing.

This symlinks:

```text
~/.local/bin/dvm -> <repo>/bin/dvm
```

The scripts are written in Bash (`#!/usr/bin/env bash`). This is independent of your
interactive shell; using Zsh as the default macOS shell is fine.

Update the core by moving to a newer signed release tag:

```bash
Expand All @@ -57,50 +50,30 @@ git checkout --detach vX.Y.Z

Development checkouts may track `main` and update with `git pull --ff-only`.

## Config
## Quick Start

`dvm init` creates:

```text
~/.config/dvm/config.sh
~/.config/dvm/setup.d/fedora.sh
~/.local/share/dvm/
```bash
dvm init
dvm new myapp
dvm myapp
git clone git@github.com:example/myapp.git ~/code/myapp
```

User configuration is shell code by design and is the extension point.

Common config:
Rerun setup in one VM or every VM:

```bash
DVM_PREFIX="dvm"
DVM_CPUS="4"
DVM_MEMORY="8GiB"
DVM_DISK="80GiB"

DVM_PACKAGES="git openssh-clients gpg helix ripgrep fd-find jq"
DVM_SETUP_SCRIPTS="$DVM_CONFIG/setup.d/fedora.sh"
DVM_DOTFILES_DIR="$HOME/.dotfiles"
dvm setup myapp
dvm setup-all
```

Put package-independent setup, shell config, and tool config in `setup.d/fedora.sh`.
If `DVM_DOTFILES_DIR` is set, DVM copies a snapshot of that host directory into the VM
before user setup scripts run. It does not mount the host directory live. User setup
scripts run inside the VM as the guest user with:
Run hosted AI tools through the restricted agent user:

```text
DVM_NAME
DVM_VM_NAME
DVM_CODE_DIR
DVM_DOTFILES_TARGET
```bash
dvm agent setup myapp
dvm agent install myapp codex
dvm agent myapp -- codex
```

Dotfiles sync is opt-in. By default DVM excludes `.git`, `.ssh`, `.gnupg`, `.env`, and
`secrets`, refuses dangerous source paths such as `/`, `$HOME`, `~/.ssh`, and
`~/.gnupg`, and keeps the target under the guest home directory.

The default workflow keeps source code inside the VM under `~/code`. No host project
directory is mounted.

## Commands

```text
Expand All @@ -113,165 +86,38 @@ dvm ssh <name> [command...]
dvm key <name>
dvm list
dvm rm <name> [--force]
dvm gpg create <name> <primary-key> [--expire 1y]
dvm gpg install <name> [secret-subkey.asc] [--signing-key fpr]
dvm gpg revoke <name>
dvm ai create|setup|pull|models|use|status|host ...
dvm agent setup|install|<name> ...
dvm gpg create|install|revoke ...
dvm doctor
dvm completion zsh
```

`dvm <name>` is a shortcut for `dvm enter <name>`.

## Create A VM
## Docs

```bash
dvm new myapp
```

This creates `dvm-myapp`, starts it, runs core setup, runs user setup scripts, creates
`~/.ssh/id_ed25519_myapp` inside the VM, and prints the public key.

Enter it:

```bash
dvm myapp
git clone git@github.com:example/myapp.git ~/code/myapp
```

Rerun setup in one or all VMs:

```bash
dvm setup myapp
dvm setup-all
```

This is the intended way to add packages everywhere or refresh dotfiles snapshots. The
script does not try to remove packages automatically; removals should be explicit and
manual.

## Delete Safety

```bash
dvm rm myapp
```

Before deleting, DVM searches Git repositories under `~/code` in the VM. If any repo
has unstaged changes, staged changes, or untracked files, deletion is refused.

Force deletion:

```bash
dvm rm myapp --force
```

If a GPG signing subkey was created for the VM, `rm` prints the recorded subkey
fingerprint and the revoke command. Deleting a VM does not revoke GPG keys
automatically.

## GPG

Create a signing subkey on the host and export a secret-subkey bundle:

```bash
dvm gpg create myapp <primary-key-id> --expire 1y
```

Files are written under:

```text
~/.local/share/dvm/gpg/
```

Install the subkey into the VM and configure Git commit signing:

```bash
dvm gpg install myapp
```

Revoke the VM subkey on the host:

```bash
dvm gpg revoke myapp
```

Revocation only updates the local GPG keyring and exports the updated public key. It
does not update GitHub/GitLab, remove old public keys from remote services, delete the
secret bundle from disk, or change anything inside an already-deleted VM. Upload the
updated public key wherever the old public key was trusted. Depending on the local GPG
setup, revoke/create commands may open pinentry.

## Doctor

Check local requirements and paths:

```bash
dvm doctor
```

## Completion

Zsh:

```bash
source <(dvm completion zsh)
```

Add that line to a shell startup file to enable completion automatically.
- [Docs index](docs/README.md)
- [VM lifecycle](docs/vms.md)
- [Config and dotfiles](docs/config.md)
- [GPG signing subkeys](docs/gpg.md)
- [Local llama.cpp AI VM](docs/ai-vm.md)
- [Hosted AI tools through `dvm agent`](docs/ai-tools.md)
- [Security policy and model](SECURITY.md)
- [Contributing](CONTRIBUTING.md)
- [Maintainer release process](docs/release.md)
- [GitHub security settings](docs/github-security.md)

## Security

Read [SECURITY.md](SECURITY.md) before installing DVM for stable use. The short
version:
Read [SECURITY.md](SECURITY.md) before installing DVM for stable use. Short version:

- install and update from signed release tags
- keep `main` for development and testing
- do not run remote install scripts directly
- keep VM setup scripts in user-controlled config or dotfiles
- review setup scripts before running `dvm setup` or `dvm setup-all`

Repository maintainer settings are documented in
[docs/github-security.md](docs/github-security.md).
- keep setup scripts in user-controlled config or dotfiles
- run hosted AI tools through `dvm agent`, not the normal VM user

## License

DVM is released under the [MIT License](LICENSE).

## Contributing

Contributions are welcome when they keep the project small, auditable, and focused on
VM lifecycle, SSH, GPG, and user-controlled setup. See
[CONTRIBUTING.md](CONTRIBUTING.md).

## Maintainer Release Process

Maintainer checklist:

```bash
bash scripts/check.sh
git tag -s vX.Y.Z -m "dvm vX.Y.Z"
git push origin main
git push origin vX.Y.Z
```

Create the GitHub release from the signed `v*` tag. Published releases and tags should
not be moved or replaced; publish a new fixed release instead.

## LLaMA VM

llama.cpp can be configured as a normal named VM:

```bash
dvm new ai
```

Place llama-specific package and service setup in `setup.d/fedora.sh` behind a name
check:

```bash
if [ "$DVM_NAME" = "ai" ]; then
sudo dnf5 install -y llama-cpp
# configure a systemd service here
fi
```

This keeps the core small while still making the AI VM reproducible.
12 changes: 9 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ Useful report details:
## Security Model

DVM is a small wrapper around Lima. It helps isolate project work into separate Fedora
VMs and keeps user-controlled setup outside the core repository. It is not a sandbox
that can provide stronger guarantees than Lima, QEMU, macOS virtualization, SSH, GPG,
or the packages and scripts that users choose to run.
VMs and keeps user-controlled setup outside the core repository. The core targets Lima
`template:fedora` and assumes `dnf5` in the guest. It is not a sandbox that can
provide stronger guarantees than Lima, QEMU, macOS virtualization, SSH, GPG, or the
packages and scripts that users choose to run.

Security-sensitive behavior in scope:

Expand All @@ -43,6 +44,11 @@ DVM does not mount host dotfiles into VMs by default. If dotfiles sync is enable
copies a filtered snapshot during setup so project code in the VM does not retain a
persistent read path back to the host.

Hosted AI tools should run through `dvm agent`, which uses a separate VM user and
bubblewrap to hide the normal VM user's home while exposing project code. This limits
access to per-VM SSH keys, GPG subkeys, dotfiles, and secret-manager config, but it
does not make AI-executed project code safe.

## Safe Installation

Install from a signed release tag, not from an arbitrary branch. Before running
Expand Down
8 changes: 8 additions & 0 deletions bin/dvm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ source "$DVM_CORE/lib/config.sh"
# shellcheck disable=SC1091
source "$DVM_CORE/lib/vm.sh"
# shellcheck disable=SC1091
source "$DVM_CORE/lib/ai.sh"
# shellcheck disable=SC1091
source "$DVM_CORE/lib/agent.sh"
# shellcheck disable=SC1091
source "$DVM_CORE/lib/gpg.sh"
# shellcheck disable=SC1091
source "$DVM_CORE/lib/doctor.sh"
Expand All @@ -44,6 +48,8 @@ usage:
dvm key <name>
dvm list
dvm rm <name> [--force]
dvm ai create|setup|pull|models|use|status|host ...
dvm agent setup|install|<name> ...
dvm gpg create|install|revoke ...
dvm doctor
dvm completion zsh
Expand All @@ -67,6 +73,8 @@ dvm_main() {
key) dvm_key "$@" ;;
list | ls) dvm_list_names "$@" ;;
rm | delete) dvm_rm "$@" ;;
ai) dvm_ai_cmd "$@" ;;
agent) dvm_agent_cmd "$@" ;;
gpg) dvm_gpg_cmd "$@" ;;
doctor) dvm_doctor "$@" ;;
completion) dvm_completion "$@" ;;
Expand Down
19 changes: 19 additions & 0 deletions defaults/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,23 @@ DVM_DOTFILES_DIR="${DVM_DOTFILES_DIR:-}"
DVM_DOTFILES_TARGET="${DVM_DOTFILES_TARGET:-$DVM_GUEST_HOME/.dotfiles}"
DVM_DOTFILES_EXCLUDES="${DVM_DOTFILES_EXCLUDES:-.git .ssh .gnupg .env secrets}"

# Optional llama.cpp VM helper config. `dvm ai create` uses these values.
DVM_AI_NAME="${DVM_AI_NAME:-ai}"
DVM_AI_PACKAGES="${DVM_AI_PACKAGES:-llama-cpp curl}"
DVM_AI_SERVER_CMD="${DVM_AI_SERVER_CMD:-llama-server}"
DVM_AI_SERVICE_NAME="${DVM_AI_SERVICE_NAME:-dvm-llama.service}"
DVM_AI_HOST="${DVM_AI_HOST:-0.0.0.0}"
DVM_AI_PORT="${DVM_AI_PORT:-8080}"
DVM_AI_MODELS_DIR="${DVM_AI_MODELS_DIR:-$DVM_GUEST_HOME/models}"
DVM_AI_DEFAULT_MODEL="${DVM_AI_DEFAULT_MODEL:-qwen25-coder-7b-q4}"
# Space-separated alias=url entries. Aliases become model filenames in the VM.
DVM_AI_MODELS="${DVM_AI_MODELS:-qwen25-coder-7b-q4=https://huggingface.co/bartowski/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/main/Qwen2.5-Coder-7B-Instruct-Q4_K_M.gguf?download=true}"
DVM_AI_EXTRA_ARGS="${DVM_AI_EXTRA_ARGS:-}"

# Hosted AI tools should run through `dvm agent`, not the normal VM user.
DVM_AGENT_USER="${DVM_AGENT_USER:-dvm-agent}"
DVM_AGENT_HOME="${DVM_AGENT_HOME:-/home/$DVM_AGENT_USER}"
DVM_AGENT_PACKAGES="${DVM_AGENT_PACKAGES:-bubblewrap acl shadow-utils}"
DVM_AGENT_CLAUDE_CHANNEL="${DVM_AGENT_CLAUDE_CHANNEL:-stable}"

DVM_GPG_DIR="${DVM_GPG_DIR:-$DVM_STATE/gpg}"
Loading