Anvil
Declarative Workstation Configuration Management
Anvil is a configuration management tool that lets you define your development environment in YAML. Describe the packages, configuration files, scripts, and environment variables your workstation needs, and Anvil takes care of the rest — installing software, deploying dotfiles, running setup scripts, and validating that everything is healthy.
Key Features
- Package management — install software via winget with version pinning (Homebrew and APT planned)
- File synchronization — deploy configuration files with automatic backup and integrity checks
- Script execution — run PowerShell setup and validation scripts with timeout and elevation support
- Workload inheritance — compose configurations with DRY principles using
extends - Health checks — validate that the live system matches your workload definition
- Multiple output formats — table, JSON, YAML, and HTML reports
- Backup and restore — snapshot and roll back system state
- Shell completions — tab completion for PowerShell, Bash, Zsh, and Fish
Quick Install
# From crates.io (requires Rust 1.75+)
cargo install anvil-dev
Or download a pre-built binary from the Releases page.
Quick Start
# List available workloads
anvil list
# Preview what an install would do
anvil install rust-developer --dry-run
# Install a workload
anvil install rust-developer
# Verify system health
anvil health rust-developer
Learn More
- Read the User Guide for complete usage instructions.
- See Workload Authoring to create your own workloads.
- Check the Troubleshooting guide if you run into issues.
- Review the Specification for the technical design and roadmap.
- Explore the Architecture docs to understand the codebase.
- Want to help? Read the Contributing guide.
If Anvil saves you time, consider sponsoring the project.
User Guide
A comprehensive guide to using Anvil for workstation configuration management.
1. Introduction
What is Anvil?
Anvil is a declarative configuration management tool for developer workstations. It allows you to define your development environment in YAML files and automatically:
- Install software packages via package managers (currently winget; Homebrew and APT planned)
- Copy and manage configuration files
- Execute setup and validation scripts
- Verify system health against your defined configuration
Key Concepts
- Workload: A configuration bundle containing package definitions, files to deploy, and scripts to run
- Package: A software application to be installed via winget
- File: A configuration file to be copied to your system
- Script: A PowerShell or CMD script to execute during installation or health checks
- Inheritance: The ability to compose workloads by extending other workloads
System Requirements
Current platform: Windows
- Windows 10 (version 1809 or later) or Windows 11
- Windows Package Manager (winget) version 1.4 or later
- PowerShell 5.1 or later (included with Windows)
- Administrator access (for some operations)
Cross-platform support (macOS, Linux) is on the roadmap.
2. Installation
Install from crates.io
# Prerequisites: Rust 1.75+
cargo install anvil-dev
Download Pre-built Binary
-
Download the latest release from the Releases page
-
Extract the archive:
PowerShell (Windows):
Expand-Archive anvil-v0.3.1-windows-x64.zip -DestinationPath C:\Tools\anvilBash / Zsh (macOS / Linux):
tar xzf anvil-v0.3.1-linux-x64.tar.gz -C ~/.local/bin -
Add to your PATH:
PowerShell:
# Add to current session $env:PATH += ";C:\Tools\anvil" # Add permanently (User scope) [Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\Tools\anvil", "User")Bash / Zsh:
# Add to current session export PATH="$HOME/.local/bin:$PATH" # Add permanently echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc
Build from Source
# Prerequisites: Rust 1.75+ and Visual Studio Build Tools
# Clone the repository
git clone https://github.com/kafkade/anvil.git
cd anvil
# Build release binary
cargo build --release
# The binary is at target/release/anvil.exe
Verify Installation
anvil --version
# Output: anvil 0.3.1
anvil --help
# Shows available commands and options
Shell Completions Setup
Generate and install shell completions for better command-line experience:
PowerShell
# Generate completions
anvil completions powershell > $HOME\Documents\WindowsPowerShell\anvil.ps1
# Add to your PowerShell profile
Add-Content $PROFILE '. $HOME\Documents\WindowsPowerShell\anvil.ps1'
Bash (WSL/Git Bash)
# Generate completions
anvil completions bash > ~/.local/share/bash-completion/completions/anvil
# Or add to .bashrc
anvil completions bash >> ~/.bashrc
Zsh
# Generate completions
anvil completions zsh > ~/.zfunc/_anvil
# Add to .zshrc (before compinit)
fpath+=~/.zfunc
3. Quick Start
List Available Workloads
See what workloads are available:
anvil list
Output:
Available Workloads:
essentials Core development tools and productivity utilities
rust-developer Rust development environment (extends essentials)
python-developer Python development environment (extends essentials)
View Workload Details
Inspect what a workload will do:
anvil show rust-developer
Dry Run Installation
Preview what would happen without making changes:
anvil install rust-developer --dry-run
Install a Workload
Apply a workload configuration:
anvil install rust-developer
Check System Health
Verify your system matches the workload definition:
anvil health rust-developer
4. Command Reference
install
Apply a workload configuration to your system.
Synopsis:
anvil install <WORKLOAD> [OPTIONS]
Arguments:
<WORKLOAD>- Name of the workload to install
Options:
| Option | Description |
|---|---|
--dry-run | Preview actions without making changes |
--force | Skip confirmation prompts |
-p, --packages-only | Only install packages, skip files |
--files-only | Only process files, skip packages |
--skip-packages | Skip package installation |
--skip-files | Skip file operations |
--no-backup | Don't backup existing files before overwriting |
--upgrade | Upgrade existing packages to specified versions |
--retry-failed | Retry only failed packages from previous run |
--parallel | Run installations in parallel where safe |
-j, --jobs <N> | Number of parallel installations (default: 4) |
--timeout <SECONDS> | Global timeout for operations (default: 3600) |
--force-files | Force overwrite files without checking hash |
Examples:
# Standard installation
anvil install rust-developer
# Preview only
anvil install rust-developer --dry-run
# Skip packages (only copy files and run scripts)
anvil install rust-developer --skip-packages
# Only deploy files
anvil install rust-developer --files-only
# Upgrade existing packages
anvil install rust-developer --upgrade
# Retry previously failed packages
anvil install rust-developer --retry-failed
# Parallel installation with 8 workers
anvil install rust-developer --parallel -j 8
Exit Codes:
0- Success1- General error2- Workload not found3- Package installation failed4- File operation failed5- Script execution failed
health
Validate system state against a workload definition.
Synopsis:
anvil health <WORKLOAD> [OPTIONS]
Arguments:
<WORKLOAD>- Name of the workload to check
Options:
| Option | Description |
|---|---|
-o, --output <FORMAT> | Output format: table, json, yaml, html |
-f, --file <PATH> | Write output to file |
--fail-fast | Stop on first failure |
--packages-only | Only check packages |
--files-only | Only check files |
--assertions-only | Only evaluate declarative assertions |
-s, --strict | Treat warnings as errors |
--fix | Attempt to install missing packages |
--update | Update packages with available updates |
--no-cache | Skip cache and query winget directly |
--show-diff | Show file differences for modified files |
Examples:
# Basic health check
anvil health rust-developer
# Detailed output
anvil -v health rust-developer
# Generate JSON report
anvil health rust-developer --output json --file health-report.json
# Generate HTML report
anvil health rust-developer --output html --file report.html
# Only check packages
anvil health rust-developer --packages-only
# Only run assertions
anvil health rust-developer --assertions-only
# Auto-fix missing packages
anvil health rust-developer --fix
# Show file diffs for modified config files
anvil health rust-developer --show-diff
Understanding Health Reports:
Health checks verify:
- Packages: Are required packages installed? Correct versions?
- Files: Do configuration files exist with expected content?
- Assertions: Do declarative condition checks pass? (see Assertion-Based Health Checks)
Status indicators:
- ✓ (Green) - Check passed
- ✗ (Red) - Check failed
- ! (Yellow) - Warning or partial match
list
List available workloads.
Synopsis:
anvil list [OPTIONS]
Options:
| Option | Description |
|---|---|
-a, --all | Include built-in and custom workloads |
-l, --long | Show detailed information |
--path <DIR> | Search for workloads in additional path |
--all-paths | Show all discovered paths including shadowed duplicates |
-o, --output <FORMAT> | Output format: table, json, yaml, html |
Examples:
# Simple list
anvil list
# Detailed list with versions and descriptions
anvil list --long
# JSON output for scripting
anvil list --output json
# List from custom directory
anvil list --path C:\MyWorkloads
# Show all paths including shadowed duplicates
anvil list --all-paths
show
Display detailed information about a workload.
Synopsis:
anvil show <WORKLOAD> [OPTIONS]
Arguments:
<WORKLOAD>- Name of the workload to display
Options:
| Option | Description |
|---|---|
--show-inheritance | Show inheritance hierarchy |
-r, --resolved | Show fully resolved workload (after inheritance) |
-o, --output <FORMAT> | Output format: yaml (default), json |
Examples:
# Show workload details
anvil show rust-developer
# Show inheritance tree
anvil show rust-developer --show-inheritance
# Export as JSON
anvil show rust-developer --output json
# Show resolved (merged) workload
anvil show rust-developer --resolved
validate
Validate workload syntax and structure.
Synopsis:
anvil validate <PATH> [OPTIONS]
Arguments:
<PATH>- Path to workload.yaml file or workload directory
Options:
| Option | Description |
|---|---|
--strict | Enable strict validation mode |
--schema | Output JSON schema for workload definitions |
--check-scripts | Validate script syntax using PowerShell parser |
--scripts-only | Only validate scripts (skip other validation) |
Examples:
# Basic validation
anvil validate my-workload
# Strict mode (treats warnings as errors)
anvil validate my-workload --strict
Validate all bundled workloads:
PowerShell:
anvil list --output json | ConvertFrom-Json | ForEach-Object { anvil validate $_.name }
Bash / Zsh:
anvil list --output json | jq -r '.[].name' | xargs -I {} anvil validate {} --strict
Common Validation Errors:
- Missing required fields (
name,version) - Invalid workload name format
- Circular inheritance dependencies
- Invalid package IDs
- Non-existent script paths
- Invalid file paths
init
Create a new workload from a template.
Synopsis:
anvil init <NAME> [OPTIONS]
Arguments:
<NAME>- Name for the new workload
Options:
| Option | Description |
|---|---|
-t, --template <NAME> | Template to use: minimal, standard (default), full |
-e, --extends <PARENT> | Parent workload to extend |
-o, --output <PATH> | Output directory |
Examples:
# Create a standard workload
anvil init my-workload
# Create minimal workload
anvil init my-workload --template minimal
# Create workload that extends essentials
anvil init my-rust-env --extends essentials
# Create in custom directory
anvil init my-workload --output C:\Workloads
Available Templates:
minimal- Basic structure with required fields onlystandard- Common sections with sensible defaults (default)full- Complete example with all features
status
Show current installation status.
Synopsis:
anvil status [WORKLOAD] [OPTIONS]
Arguments:
[WORKLOAD]- Optional workload to check status for
Options:
| Option | Description |
|---|---|
-o, --output <FORMAT> | Output format: table, json, yaml, html |
-l, --long | Show detailed status including timestamps |
--clear | Clear stored state for the specified workload |
Examples:
# Overall status
anvil status
# Status for specific workload
anvil status rust-developer
backup
Manage system state backups.
Synopsis:
anvil backup <SUBCOMMAND>
Subcommands:
backup create
Create a new backup of current system state.
# Create backup before changes
anvil backup create
# Create named backup
anvil backup create --name "before-update"
# Create backup for specific workload
anvil backup create --workload rust-developer
backup list
List available backups.
anvil backup list
anvil backup list --output json
backup show
Show details of a specific backup.
anvil backup show <BACKUP_ID>
backup restore
Restore from a backup.
# Restore from backup
anvil backup restore <BACKUP_ID>
# Preview restore
anvil backup restore <BACKUP_ID> --dry-run
# Restore all backups for a workload
anvil backup restore --workload rust-developer
backup clean
Remove old backups.
# Remove backups older than 30 days (default)
anvil backup clean
# Remove backups older than 7 days
anvil backup clean --older-than 7
# Preview what would be removed
anvil backup clean --dry-run
backup verify
Verify backup integrity.
# Verify all backups
anvil backup verify
# Verify backups for a specific workload
anvil backup verify --workload rust-developer
# Fix issues by removing corrupted entries
anvil backup verify --fix
config
Manage Anvil configuration.
Synopsis:
anvil config <SUBCOMMAND>
Subcommands:
config get
Get a specific configuration value.
anvil config get defaults.shell
anvil config get workloads.paths
config set
Set a configuration value.
anvil config set defaults.shell powershell
anvil config set defaults.output_format json
anvil config set backup.auto_backup true
config list
Display all configuration values.
anvil config list
anvil config list --output json
config reset
Reset configuration to defaults.
anvil config reset
anvil config reset --force # Skip confirmation prompt
config edit
Open configuration file in default editor.
anvil config edit
config path
Show the configuration file path.
anvil config path
completions
Generate shell completion scripts.
Synopsis:
anvil completions <SHELL>
Arguments:
<SHELL>- Target shell: powershell, bash, zsh, fish, elvish
Examples:
PowerShell:
anvil completions powershell | Out-String | Invoke-Expression
# Or save to a file and source it from $PROFILE
anvil completions powershell > $HOME\Documents\WindowsPowerShell\anvil.ps1
Bash:
anvil completions bash > ~/.local/share/bash-completion/completions/anvil
Global Options
These options work with all commands:
| Option | Short | Description |
|---|---|---|
--verbose | -v | Increase verbosity (use multiple times: -v, -vv, -vvv) |
--quiet | -q | Suppress non-essential output |
--config <PATH> | -c | Use custom configuration file |
--no-color | Disable colored output | |
--help | -h | Show help information |
--version | -V | Show version information |
Examples:
# Verbose output
anvil -v install rust-developer
# Very verbose (debug level)
anvil -vvv health rust-developer
# Quiet mode for scripting
anvil -q install rust-developer
# No colors (for log files)
anvil --no-color list > workloads.txt
5. Configuration
Configuration File Location
Anvil stores its configuration at:
~/.anvil/config.yaml
On Windows this is typically %USERPROFILE%\.anvil\config.yaml.
You can also specify a custom config file with the -c/--config global flag:
anvil -c C:\config\anvil.yaml list
Configuration Options
# Global Anvil Configuration (~/.anvil/config.yaml)
defaults:
shell: powershell # Default script shell
script_timeout: 300 # Default script timeout in seconds
output_format: table # Default output format (table, json, yaml)
color: auto # Color mode (auto, always, never)
backup:
auto_backup: true # Enable automatic backups before changes
retention_days: 30 # Days to keep backups
install:
parallel: false # Run installations in parallel by default
max_parallel: 4 # Max concurrent package installations
workloads:
paths: # Additional workload search paths
- "~/my-workloads"
- "~/work/team-workloads"
logging:
level: info # Log level: error, warn, info, debug, trace
View Current Configuration
anvil config list
Modify Configuration
# Set a value
anvil config set defaults.output_format json
# Get a value
anvil config get defaults.shell
# Reset to default
anvil config reset
6. Configuring Workload Search Paths
Anvil searches for workloads in multiple directories. You can add custom paths to include your own workloads alongside the built-in ones.
Adding a Search Path
anvil config set workloads.paths '["~/my-workloads", "/shared/team-workloads"]'
Or edit ~/.anvil/config.yaml directly:
workloads:
paths:
- "~/my-workloads"
- "/shared/team-workloads"
Search Order
Anvil resolves workloads in this priority order:
- Explicit path — passed via
--pathflag - User-configured — paths from
~/.anvil/config.yaml - Default locations — bundled workloads, local data directory, current directory
When the same workload name exists in multiple paths, the first match wins. Use anvil list --all-paths to see all discovered paths including shadowed duplicates.
Complete Config Example
# Anvil global configuration
# Location: ~/.anvil/config.yaml
workloads:
paths:
- "~/my-workloads" # Personal workloads
- "~/work/team-workloads" # Team-shared workloads
logging:
level: info
7. Working with Workloads
Discovering Workloads
Anvil searches for workloads in these locations (in priority order):
- Path specified with
--pathoption - User-configured paths (from
~/.anvil/config.yaml) - Default locations (bundled workloads, local data directory, current directory)
# List all available workloads
anvil list
# List with details
anvil list --long
# List from specific directory
anvil list --path C:\MyWorkloads
Understanding Workload Inheritance
Workloads can extend other workloads to inherit their configuration:
name: my-rust-env
version: "1.0.0"
extends:
- essentials # Inherits packages, files, scripts
- rust-developer # Adds Rust-specific config
View the inheritance tree:
anvil show my-rust-env --show-inheritance
Output:
my-rust-env
├── essentials
└── rust-developer
└── essentials
Using Custom Workload Directories
# One-time use
anvil list --path C:\MyWorkloads
anvil install my-workload --path C:\MyWorkloads
# Configure permanently
anvil config set workloads.paths '["C:\\MyWorkloads"]'
Validating Before Install
Always validate workloads before installation:
# Validate syntax
anvil validate my-workload
# Strict validation
anvil validate my-workload --strict
# Preview installation
anvil install my-workload --dry-run
8. Output Formats
Anvil supports multiple output formats for different use cases.
Table (Default)
Human-readable format for terminal display:
anvil list
┌──────────────────┬─────────┬────────────────────────────────────────────────────┐
│ Name │ Version │ Description │
├──────────────────┼─────────┼────────────────────────────────────────────────────┤
│ essentials │ 2.0.0 │ Core development tools and productivity utilities │
│ rust-developer │ 1.0.0 │ Rust development environment │
└──────────────────┴─────────┴────────────────────────────────────────────────────┘
JSON
Machine-readable format for scripting and automation:
anvil list --output json
[
{
"name": "essentials",
"version": "2.0.0",
"description": "Core development tools and productivity utilities"
},
{
"name": "rust-developer",
"version": "1.0.0",
"description": "Rust development environment"
}
]
YAML
Configuration-friendly format:
anvil show rust-developer --output yaml
name: rust-developer
version: "1.0.0"
description: Rust development environment
extends:
- essentials
packages:
winget:
- id: Rustlang.Rustup
HTML
Rich reports for documentation:
anvil health rust-developer --output html --file report.html
Generates a styled HTML document with:
- Summary statistics
- Detailed check results
- Pass/fail indicators
- Timestamp and system info
9. Environment Variables
| Variable | Description | Default |
|---|---|---|
ANVIL_CONFIG | Configuration file path | ~/.anvil/config.yaml |
ANVIL_WORKLOADS | Additional workload search paths | (none) |
ANVIL_LOG | Log level: error, warn, info, debug, trace | warn |
NO_COLOR | Disable colored output (any value) | (unset) |
ANVIL_BACKUP_DIR | Backup storage directory | ~/.anvil/backups |
Examples:
PowerShell:
# Use custom config file
$env:ANVIL_CONFIG = "C:\config\anvil.yaml"
anvil list
# Add workload search paths
$env:ANVIL_WORKLOADS = "C:\Workloads;D:\MoreWorkloads"
anvil list
# Enable debug logging
$env:ANVIL_LOG = "debug"
anvil install rust-developer
# Disable colors
$env:NO_COLOR = "1"
anvil list
Bash / Zsh:
# Use custom config file
export ANVIL_CONFIG="$HOME/.config/anvil.yaml"
anvil list
# Add workload search paths
export ANVIL_WORKLOADS="$HOME/workloads:$HOME/more-workloads"
anvil list
# Enable debug logging
export ANVIL_LOG=debug
anvil install rust-developer
# Disable colors
export NO_COLOR=1
anvil list
10. Best Practices
Always Dry-Run First
Before applying any workload, preview the changes:
anvil install my-workload --dry-run
This shows what will happen without making changes.
Use Health Checks Regularly
Verify your system state periodically:
# Quick check
anvil health rust-developer
# Detailed report
anvil -v health rust-developer --output html --file health.html
Keep Backups
Enable automatic backups in configuration:
anvil config set backup.auto_backup true
Or create manual backups before major changes:
anvil backup create --name "before-upgrade"
Version Your Workloads
Store your workloads in version control:
my-workloads/
├── .git/
├── team-base/
│ └── workload.yaml
├── frontend-dev/
│ └── workload.yaml
└── backend-dev/
└── workload.yaml
Use Inheritance Wisely
Create a base workload with common tools:
# team-base/workload.yaml
name: team-base
version: "1.0.0"
packages:
winget:
- id: Git.Git
- id: Microsoft.VisualStudioCode
Then extend it for specific roles:
# frontend-dev/workload.yaml
name: frontend-dev
extends:
- team-base
packages:
winget:
- id: OpenJS.NodeJS
Validate Before Committing
Add validation to your CI/CD pipeline:
PowerShell:
# Validate all workloads
Get-ChildItem -Directory | ForEach-Object {
anvil validate $_.Name --strict
}
Bash / Zsh:
# Validate all workloads
for dir in */; do
anvil validate "${dir%/}" --strict
done
Use Verbose Output for Debugging
When things go wrong:
# Increase verbosity
anvil -vvv install my-workload
PowerShell:
# Enable trace logging
$env:ANVIL_LOG = "trace"
anvil install my-workload
Bash / Zsh:
# Enable trace logging
export ANVIL_LOG=trace
anvil install my-workload
Script Error Handling
In your workload scripts, handle errors gracefully:
# post-install.ps1
try {
$rustVersion = rustc --version
if ($LASTEXITCODE -ne 0) {
Write-Error "Rust not installed"
exit 1
}
Write-Host "Rust installed: $rustVersion"
exit 0
}
catch {
Write-Error "Script failed: $_"
exit 1
}
11. Assertion-Based Health Checks
In addition to package and file checks, workloads can define declarative assertions using a built-in condition engine. Assertions let you express health checks directly in YAML without writing PowerShell scripts.
Defining Assertions
Add an assertions section to your workload.yaml:
name: my-workload
version: "1.0.0"
assertions:
- name: "Git is installed"
check:
type: command_exists
command: git
- name: "Config directory exists"
check:
type: dir_exists
path: "~/.config/my-app"
- name: "GOPATH is set"
check:
type: env_var
name: GOPATH
- name: "Cargo in PATH"
check:
type: path_contains
substring: ".cargo\\bin"
Available Condition Types
| Type | Fields | Description |
|---|---|---|
command_exists | command | Check if a command is available on PATH |
file_exists | path | Check if a file exists (~ expanded) |
dir_exists | path | Check if a directory exists (~ expanded) |
env_var | name, value (optional) | Check if env var is set, optionally matching a value |
path_contains | substring | Check if PATH contains a substring |
registry_value | hive, key, name, expected (optional) | Query a Windows registry value |
shell | command, description (optional) | Run a shell command; passes if exit code is 0 |
all_of | conditions | All child conditions must pass (logical AND) |
any_of | conditions | At least one child must pass (logical OR) |
Composing Conditions
Use all_of and any_of to build complex checks:
assertions:
- name: "Rust toolchain ready"
check:
type: all_of
conditions:
- type: command_exists
command: rustc
- type: command_exists
command: cargo
- type: path_contains
substring: ".cargo\\bin"
Running Assertions
Assertions are evaluated as part of anvil health:
# Run all health checks including assertions
anvil health my-workload
# Run only assertions
anvil health my-workload --assertions-only
Controlling Assertions in Health Config
You can disable assertion evaluation per-workload:
health:
assertion_check: false # Skip assertions during health checks
Getting Help
Built-in Help
# General help
anvil --help
# Command-specific help
anvil install --help
anvil health --help
Resources
Reporting Issues
When reporting issues, include:
- Anvil version:
anvil --version - Windows version:
winver - Command that failed
- Verbose output:
anvil -vvv <command> - Relevant workload files (sanitized)
This guide is for Anvil v0.3.1. For other versions, check the corresponding documentation.
Workload Authoring
A comprehensive guide to creating custom workloads for Anvil.
1. Workload Structure
A workload is a directory containing configuration files, scripts, and assets that define a system configuration.
Directory Structure
workload-name/
├── workload.yaml # Required: workload definition
├── files/ # Optional: files to deploy
│ ├── .config/
│ │ └── app.conf
│ └── settings.json
└── scripts/ # Optional: installation/health scripts
├── pre-install.ps1
├── post-install.ps1
└── health-check.ps1
Required Files
- workload.yaml: The main workload definition file (required)
Optional Directories
- files/: Contains configuration files to be copied to the target system
- scripts/: Contains PowerShell or CMD scripts for installation and validation
Naming Conventions
- Workload directory names should be lowercase with hyphens:
my-workload-name - Use descriptive names that indicate the workload's purpose
- Avoid spaces and special characters
2. Schema Reference
The workload.yaml file defines all aspects of your workload configuration.
Complete Schema
# Required fields
name: string # Workload identifier
version: string # Semantic version (e.g., "1.0.0")
# Optional fields
description: string # Human-readable description
extends: string[] # Parent workloads to inherit from
packages: object # Package definitions
files: array # File deployment definitions
commands: object # Inline command definitions
environment: object # Environment variable configuration
assertions: array # Declarative health assertions
health: object # Health check configuration
Required Fields
name
Unique identifier for the workload. Must be alphanumeric with hyphens and underscores only.
name: my-workload # Valid
name: my_workload_v2 # Valid
name: "my workload" # Invalid (spaces)
name: my.workload # Invalid (dots)
version
Semantic version string. Recommended to follow SemVer.
version: "1.0.0"
version: "2.1.0-beta"
version: "0.1.0"
Optional Fields
description
Human-readable description displayed in listings.
description: "Development environment for Rust projects with debugging tools"
extends
List of parent workloads to inherit from.
extends:
- essentials
- rust-developer
packages
Package installation definitions (see Package Definitions).
files
File deployment definitions (see File Definitions).
commands
Inline command definitions (see Commands (Inline)).
environment
Environment variable configuration (see Environment Configuration).
3. Package Definitions
Define software packages to install. Anvil supports multiple package managers: winget (Windows), brew (macOS/Linux), and apt (Debian/Ubuntu).
Basic Structure
packages:
winget:
- id: Publisher.PackageName
- id: Another.Package
Full Package Options
packages:
winget:
- id: Git.Git
version: "2.43.0" # Optional: pin to specific version
source: winget # Optional: winget, msstore
override: # Optional: additional winget arguments
- "--scope"
- "machine"
Package Fields
| Field | Required | Description |
|---|---|---|
id | Yes | Winget package identifier |
version | No | Specific version to install |
source | No | Package source: winget or msstore |
override | No | Additional arguments passed to winget |
Finding Package IDs
Use winget to search for packages:
# Search for packages
winget search vscode
# Get exact ID
winget search --exact "Visual Studio Code"
# Show package details
winget show Microsoft.VisualStudioCode
Common package IDs:
| Package | ID |
|---|---|
| Visual Studio Code | Microsoft.VisualStudioCode |
| Git | Git.Git |
| Windows Terminal | Microsoft.WindowsTerminal |
| Node.js | OpenJS.NodeJS |
| Python | Python.Python.3.12 |
| Rust | Rustlang.Rustup |
| PowerShell | Microsoft.PowerShell |
Version Pinning
Pin specific versions when compatibility matters:
packages:
winget:
# Pin exact version
- id: Python.Python.3.12
version: "3.12.0"
# Use latest (default)
- id: Git.Git
Check available versions:
winget show Python.Python.3.12 --versions
Override Arguments
Pass custom arguments to winget:
packages:
winget:
- id: Microsoft.VisualStudioCode
override:
- "--scope"
- "machine" # Install for all users
- "--override"
- "/SILENT"
Multi-Manager Packages
Define packages for multiple package managers in a single workload. Anvil selects the appropriate manager for the current platform.
packages:
winget:
- id: Git.Git
- id: Microsoft.VisualStudioCode
brew:
- name: git
- name: visual-studio-code
cask: true
apt:
- name: git
- name: build-essential
Homebrew Packages (macOS/Linux)
packages:
brew:
- name: git # CLI formula
- name: visual-studio-code # GUI cask
cask: true
- name: font-cascadia-code # Cask from a tap
cask: true
tap: "homebrew/cask-fonts"
| Field | Required | Default | Description |
|---|---|---|---|
name | Yes | - | Homebrew formula or cask name |
cask | No | false | Whether this is a cask (GUI app) vs formula (CLI tool) |
tap | No | - | Tap source (e.g., "homebrew/cask-fonts") |
APT Packages (Debian/Ubuntu)
packages:
apt:
- name: git
- name: build-essential
version: "12.9"
| Field | Required | Default | Description |
|---|---|---|---|
name | Yes | - | APT package name |
version | No | - | Specific version constraint |
Note: Homebrew and APT support is schema-complete but not yet fully implemented. Winget is the primary supported manager today.
4. File Definitions
Define configuration files to copy to the target system.
Basic Structure
files:
- source: config.json
destination: "~/.config/app/config.json"
Full File Options
files:
- source: relative/path/in/workload/file.conf
destination: "~/target/path/file.conf"
backup: true # Backup existing file
permissions: "0644" # Optional: file permissions
template: false # Process as Handlebars template
File Fields
| Field | Required | Default | Description |
|---|---|---|---|
source | Yes | - | Path relative to workload directory |
destination | Yes | - | Target path on system |
backup | No | true | Backup existing files before overwriting |
permissions | No | - | File permissions (Unix-style, informational on Windows) |
template | No | false | Process file as Handlebars template |
Path Variables
Use these variables in destination paths:
| Variable | Description | Example |
|---|---|---|
~ | User home directory | C:\Users\username |
${HOME} | User home directory | C:\Users\username |
${USERPROFILE} | User profile directory | C:\Users\username |
${APPDATA} | Application data | C:\Users\username\AppData\Roaming |
${LOCALAPPDATA} | Local app data | C:\Users\username\AppData\Local |
${WORKLOAD_DIR} | Workload directory | Path to current workload |
Examples:
files:
# Home directory
- source: .gitconfig
destination: "~/.gitconfig"
# AppData
- source: settings.json
destination: "${APPDATA}/MyApp/settings.json"
# Local AppData
- source: cache.db
destination: "${LOCALAPPDATA}/MyApp/cache.db"
Templating
Enable Handlebars templating for dynamic content:
files:
- source: config.toml.hbs
destination: "~/.config/app/config.toml"
template: true
Template file (config.toml.hbs):
# Configuration for {{username}}
# Generated on {{date}}
[user]
name = "{{username}}"
home = "{{home}}"
[paths]
workload = "{{workload_dir}}"
Available template variables:
| Variable | Description |
|---|---|
{{username}} | Current username |
{{home}} | Home directory path |
{{computername}} | Computer name |
{{workload_dir}} | Workload directory |
{{workload_name}} | Workload name |
{{date}} | Current date |
{{env.VAR_NAME}} | Environment variable |
Directory Copying
Copy entire directories:
files:
- source: config/
destination: "~/.config/myapp/"
5. Commands (Inline)
Commands let you run arbitrary shell commands directly from workload.yaml. They support conditional execution via the same predicate engine used by Assertions.
Note:
scripts.pre_installandscripts.post_installwere removed in v1.0. Use thecommandsblock instead.
Basic Structure
commands:
pre_install:
- run: "echo Preparing environment"
post_install:
- run: "cargo install ripgrep"
description: "Install ripgrep via cargo"
Full Command Options
commands:
post_install:
- run: "cargo install cargo-watch"
description: "Install cargo-watch"
timeout: 600 # Timeout in seconds (default: 300)
elevated: false # Require admin privileges (default: false)
continue_on_error: false # Continue if this command fails (default: false)
when: # Condition — skip if not met
type: command_exists
command: cargo
Command Fields
| Field | Required | Default | Description |
|---|---|---|---|
run | Yes | - | Shell command string to execute |
description | No | - | Human-readable description |
timeout | No | 300 | Timeout in seconds |
elevated | No | false | Require administrator privileges |
continue_on_error | No | false | Continue to next command if this one fails |
when | No | - | Condition predicate; command is skipped when not met |
Conditional Execution
The when field accepts any condition type from the Assertions predicate engine:
commands:
post_install:
# Only runs if cargo is on PATH
- run: "cargo install sccache"
description: "Install sccache"
when:
type: command_exists
command: cargo
# Only runs if the config file doesn't already exist
- run: "echo '{}' > ~/.config/app/config.json"
description: "Create default config"
when:
type: file_exists
path: "~/.config/app/config.json"
Command Phases
| Phase | When it runs |
|---|---|
pre_install | Before package installation |
post_install | After package installation |
Error Handling
By default, if a command fails (non-zero exit code), execution stops and subsequent commands are skipped. Set continue_on_error: true to keep going:
commands:
post_install:
- run: "cargo install cargo-watch"
continue_on_error: true # Failure won't stop the next command
- run: "cargo install cargo-edit"
6. Environment Configuration
Configure environment variables and PATH additions.
Basic Structure
environment:
variables:
- name: MY_VARIABLE
value: "my-value"
scope: user
path_additions:
- "C:\\Tools\\bin"
- "~\\.local\\bin"
Environment Variables
environment:
variables:
- name: RUST_BACKTRACE
value: "1"
scope: user # user or machine
- name: EDITOR
value: "code"
scope: user
| Field | Required | Default | Description |
|---|---|---|---|
name | Yes | - | Variable name |
value | Yes | - | Variable value |
scope | No | user | Scope: user or machine |
PATH Additions
Add directories to the system PATH:
environment:
path_additions:
- "C:\\Tools\\bin"
- "~\\.cargo\\bin"
- "${LOCALAPPDATA}\\Programs\\bin"
PATH additions are appended to the existing PATH for the specified scope.
Scope
| Scope | Description | Requires Admin |
|---|---|---|
user | Current user only | No |
machine | All users on system | Yes |
7. Assertions
Assertions are declarative health checks defined directly in workload.yaml. They let you validate system state — such as installed commands, existing files, environment variables, and PATH entries — without writing PowerShell scripts.
Use assertions when your checks are simple conditions (command exists, file exists, env var set). Use health check scripts for complex validation that requires multi-step logic or custom output.
Assertion Structure
Each assertion has a name and a check that specifies a condition:
assertions:
- name: Git is installed
check:
type: command_exists
command: git
Condition Types
command_exists
Checks whether a command is available on PATH.
- name: cargo is available
check:
type: command_exists
command: cargo
file_exists
Checks whether a file exists at the given path. Supports ~ expansion.
- name: Git config exists
check:
type: file_exists
path: "~/.gitconfig"
dir_exists
Checks whether a directory exists at the given path. Supports ~ expansion.
- name: Cargo directory exists
check:
type: dir_exists
path: "~/.cargo"
env_var
Checks whether an environment variable is set, optionally matching a specific value.
# Check existence only
- name: RUST_BACKTRACE is set
check:
type: env_var
name: RUST_BACKTRACE
# Check existence and value
- name: RUST_BACKTRACE is 1
check:
type: env_var
name: RUST_BACKTRACE
value: "1"
path_contains
Checks whether the system PATH contains a given substring.
- name: Cargo bin on PATH
check:
type: path_contains
substring: ".cargo/bin"
registry_value
Queries a Windows registry value under HKCU or HKLM. If expected is omitted, the check only asserts the value exists.
- name: Developer mode enabled
check:
type: registry_value
hive: HKLM
key: "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock"
name: AllowDevelopmentWithoutDevLicense
expected: "1"
shell
Runs an arbitrary shell command; the condition passes when the exit code is 0.
- name: Rust compiler responds
check:
type: shell
command: "rustc --version"
description: "Rust compiler version check"
Composition with all_of and any_of
Combine conditions with logical operators for complex checks.
all_of — all conditions must pass (AND)
- name: Full Rust toolchain
check:
type: all_of
conditions:
- type: command_exists
command: rustc
- type: command_exists
command: cargo
- type: dir_exists
path: "~/.cargo"
any_of — at least one must pass (OR)
- name: Python is available
check:
type: any_of
conditions:
- type: command_exists
command: python
- type: command_exists
command: python3
Enabling Assertion Checks
Assertions are evaluated during anvil health when assertion_check is enabled in the health config (it defaults to true):
health:
package_check: true
file_check: true
assertion_check: true # Evaluate declarative assertions
Complete Example
name: rust-developer
version: "1.0.0"
description: "Rust development environment"
packages:
winget:
- id: Rustlang.Rustup
assertions:
- name: cargo command exists
check:
type: command_exists
command: cargo
- name: rustc command exists
check:
type: command_exists
command: rustc
- name: Cargo directory exists
check:
type: dir_exists
path: "~/.cargo"
- name: Cargo bin on PATH
check:
type: path_contains
substring: ".cargo/bin"
- name: RUST_BACKTRACE is set
check:
type: env_var
name: RUST_BACKTRACE
value: "1"
health:
package_check: true
assertion_check: true
8. Inheritance
Workloads can extend other workloads to inherit their configuration.
Basic Inheritance
name: my-rust-dev
version: "1.0.0"
extends:
- essentials # Inherit base development tools
Multiple Inheritance
name: full-stack-dev
version: "1.0.0"
extends:
- essentials
- rust-developer
- python-developer
Merge Behavior
When a workload extends parents, configuration is merged:
| Section | Merge Behavior |
|---|---|
packages | Merged; child packages added to parent packages |
files | Merged; child files override parent files with same destination |
commands | Merged; child commands run after parent commands |
environment | Merged; child variables override parent variables |
Override Example
Parent (base/workload.yaml):
name: base
version: "1.0.0"
packages:
winget:
- id: Git.Git
- id: Microsoft.VisualStudioCode
files:
- source: .gitconfig
destination: "~/.gitconfig"
Child (extended/workload.yaml):
name: extended
version: "1.0.0"
extends:
- base
packages:
winget:
# Adds to parent packages
- id: Rustlang.Rustup
files:
# Overrides parent's .gitconfig
- source: .gitconfig
destination: "~/.gitconfig"
Inheritance Chains
Anvil resolves inheritance chains automatically:
my-workload
└── rust-developer
└── essentials
Circular dependencies are detected and rejected:
# workload-a extends workload-b
# workload-b extends workload-a
# ERROR: Circular dependency detected
View Inheritance
# Show inheritance tree
anvil show my-workload --inheritance-tree
# Show fully resolved workload
anvil show my-workload --resolved
9. Variable Expansion
Use variables in paths and values for dynamic configuration.
Supported Variables
| Variable | Description | Example Value |
|---|---|---|
~ | User home directory | C:\Users\username |
${HOME} | User home directory | C:\Users\username |
${USERNAME} | Current username | username |
${COMPUTERNAME} | Machine name | WORKSTATION-01 |
${WORKLOAD_DIR} | Workload directory | C:\Workloads\my-workload |
${USERPROFILE} | User profile path | C:\Users\username |
${APPDATA} | Roaming AppData | C:\Users\username\AppData\Roaming |
${LOCALAPPDATA} | Local AppData | C:\Users\username\AppData\Local |
${env:VAR_NAME} | Any environment variable | (varies) |
Usage Examples
files:
# Home directory shorthand
- source: .bashrc
destination: "~/.bashrc"
# Explicit home variable
- source: config.json
destination: "${HOME}/.config/myapp/config.json"
# AppData paths
- source: settings.json
destination: "${APPDATA}/MyApp/settings.json"
# Environment variable
- source: custom.conf
destination: "${env:MY_CUSTOM_PATH}/config.conf"
environment:
variables:
- name: MY_APP_HOME
value: "${HOME}/.myapp"
path_additions:
- "${HOME}/.local/bin"
- "${LOCALAPPDATA}/Programs/bin"
10. Best Practices
Workload Design
-
Use Descriptive Names
name: rust-developer # Good name: workload1 # Bad -
Include Version Information
version: "1.2.0" # Good: semantic versioning version: "latest" # Bad: not meaningful -
Write Helpful Descriptions
description: "Complete Rust development environment with debugging tools and VS Code extensions" -
Use Inheritance for Common Bases
# Create a base workload for team-wide tools # Then extend it for role-specific setups extends: - team-base
Package Management
-
Pin Versions When Necessary
packages: winget: # Pin when compatibility matters - id: Python.Python.3.12 version: "3.12.0" # Use latest for frequently updated tools - id: Git.Git -
Use Machine Scope for Shared Tools
packages: winget: - id: Microsoft.VisualStudioCode override: - "--scope" - "machine"
File Management
-
Always Enable Backups for Important Files
files: - source: .gitconfig destination: "~/.gitconfig" backup: true -
Use Templates for Dynamic Content
files: - source: config.toml.hbs destination: "~/.config/app/config.toml" template: true
Script Safety
-
Make Scripts Idempotent
# Check before acting if (-not (Test-Path $target)) { # Create/install } -
Handle Errors Gracefully
try { # Risky operation } catch { Write-Error "Operation failed: $_" exit 1 } -
Include Assertions
assertions: - name: "Installation verified" check: type: command_exists command: my-tool
Testing
-
Validate Before Committing
anvil validate my-workload --strict -
Test with Dry Run
anvil install my-workload --dry-run -
Test on Clean System
- Use a VM or container
- Document dependencies
11. Example Workloads
Minimal Workload
The simplest valid workload:
# minimal/workload.yaml
name: minimal
version: "1.0.0"
description: "A minimal workload example"
Package-Only Workload
Install software without files or scripts:
# dev-essentials/workload.yaml
name: dev-essentials
version: "1.0.0"
description: "Essential development tools"
packages:
winget:
- id: Git.Git
- id: Microsoft.VisualStudioCode
- id: Microsoft.WindowsTerminal
- id: JanDeDobbeleer.OhMyPosh
Full-Featured Workload
Complete example with all features:
# full-example/workload.yaml
name: full-example
version: "1.0.0"
description: "Complete workload demonstrating all features"
extends:
- essentials
packages:
winget:
- id: Rustlang.Rustup
- id: LLVM.LLVM
version: "17.0.6"
- id: Microsoft.VisualStudio.2022.BuildTools
override:
- "--add"
- "Microsoft.VisualStudio.Workload.VCTools"
files:
- source: files/.cargo/config.toml
destination: "~/.cargo/config.toml"
backup: true
- source: files/vscode/settings.json.hbs
destination: "${APPDATA}/Code/User/settings.json"
template: true
commands:
pre_install:
- run: "echo Checking prerequisites..."
description: "Check prerequisites"
post_install:
- run: "rustup default stable && rustup update stable"
description: "Configure Rust toolchain"
timeout: 600
- run: "rustup component add rustfmt clippy"
description: "Add Rust components"
when:
type: command_exists
command: rustup
assertions:
- name: cargo is available
check:
type: command_exists
command: cargo
- name: Cargo bin on PATH
check:
type: path_contains
substring: ".cargo/bin"
environment:
variables:
- name: RUST_BACKTRACE
value: "1"
scope: user
path_additions:
- "~/.cargo/bin"
Inherited Workload
Workload that builds on others:
# rust-advanced/workload.yaml
name: rust-advanced
version: "1.0.0"
description: "Advanced Rust development with WASM and embedded support"
extends:
- rust-developer
packages:
winget:
- id: Docker.DockerDesktop
- id: WasmEdge.WasmEdge
commands:
post_install:
- run: "rustup target add wasm32-unknown-unknown"
description: "Add WASM target"
- run: "rustup target add thumbv7em-none-eabihf"
description: "Add embedded target"
- run: "cargo install cargo-embed probe-run"
description: "Install embedded cargo tools"
continue_on_error: true
environment:
path_additions:
- "~/.wasmedge/bin"
12. Private Workload Repositories
You can maintain your own workloads in a separate Git repository and configure Anvil to discover them.
Recommended Directory Layout
my-workloads/
├── my-dev-env/
│ ├── workload.yaml
│ ├── files/
│ │ └── .gitconfig
│ └── scripts/
│ └── setup.ps1
├── team-tools/
│ ├── workload.yaml
│ └── scripts/
│ └── post-install.ps1
└── README.md
Each subdirectory containing a workload.yaml is treated as a separate workload, following the same directory structure as bundled workloads.
Setup
-
Clone your workloads repository:
git clone https://github.com/your-org/workloads ~/my-workloads -
Configure Anvil to search this path:
anvil config set workloads.paths '["~/my-workloads"]'Or edit
~/.anvil/config.yamldirectly:workloads: paths: - "~/my-workloads" -
Verify discovery:
anvil list
Tips
- Inheritance: Private workloads can
extends:built-in workloads (e.g.,extends: [essentials]) - Version control: Keep workloads in Git for team sharing and history
- Multiple repos: Add multiple paths for team-shared and personal workloads
- Precedence: If your workload has the same name as a built-in one, yours takes priority
- Validation: Run
anvil validate <name> --strictto check your workloads before committing
Complete Config Example
# Anvil global configuration
# Location: ~/.anvil/config.yaml
workloads:
paths:
- "~/my-workloads" # Personal workloads
- "~/work/team-workloads" # Team-shared workloads
logging:
level: info
Resources
This guide is for Anvil v0.3.1. For other versions, check the corresponding documentation.
Cookbook
Practical recipes for common Anvil tasks. Each recipe is self-contained with copy-pasteable examples. See the User Guide and Workload Authoring for full reference.
Creating Your First Workload
Start with anvil init to scaffold a new workload:
# Create a minimal workload
anvil init my-setup
# Create a full-featured template with all sections
anvil init my-setup --template full
# Create a workload that extends another
anvil init my-setup --extends essentials
This creates a directory with a workload.yaml and optional files/ and scripts/ subdirectories. Edit the YAML to define your environment:
name: my-setup
version: "1.0.0"
description: "My development environment"
packages:
winget:
- id: Git.Git
- id: Microsoft.VisualStudioCode
Preview what would happen, then apply:
anvil install my-setup --dry-run # preview
anvil install my-setup # apply
anvil health my-setup # verify
Adding Packages
Basic winget packages
packages:
winget:
- id: Git.Git
- id: BurntSushi.ripgrep.MSVC
- id: sharkdp.fd
Pinning a version
packages:
winget:
- id: Python.Python.3.12
version: "3.12.4"
Installing from the Microsoft Store
packages:
winget:
- id: 9NBLGGH4NNS1 # Windows Terminal
source: msstore
Custom install arguments
packages:
winget:
- id: Microsoft.VisualStudio.2022.BuildTools
override:
- --override
- "--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools"
Multi-platform packages
Define packages for multiple platforms — Anvil picks the right manager for the current OS:
packages:
winget:
- id: Git.Git
brew:
- name: git
apt:
- name: git
Deploying Configuration Files
Basic file deployment
Place source files in the files/ subdirectory of your workload, then reference them in workload.yaml:
files:
- source: config.toml
destination: "~/.cargo/config.toml"
backup: true
The ~ expands to the user's home directory. With backup: true, Anvil saves the existing file before overwriting.
Directory structure mirroring
Organize source files to mirror the target directory structure:
my-workload/
├── workload.yaml
└── files/
└── .config/
├── starship.toml
└── alacritty/
└── alacritty.toml
files:
- source: .config/starship.toml
destination: "~/.config/starship.toml"
backup: true
- source: .config/alacritty/alacritty.toml
destination: "~/.config/alacritty/alacritty.toml"
backup: true
Deploying files without touching packages
anvil install my-setup --files-only --dry-run
Using Assertions for Declarative Health Checks
Assertions are the recommended way to validate your environment — no scripting needed.
Note:
scripts.health_checkwas removed in v1.0. Use declarativeassertionsinstead.
Check that commands exist
assertions:
- name: Git is installed
check:
type: command_exists
command: git
- name: Cargo is installed
check:
type: command_exists
command: cargo
Check files and directories
assertions:
- name: SSH key exists
check:
type: file_exists
path: "~/.ssh/id_ed25519"
- name: Cargo directory exists
check:
type: dir_exists
path: "~/.cargo"
Check environment variables
assertions:
- name: RUST_BACKTRACE is set
check:
type: env_var
name: RUST_BACKTRACE
value: "1"
- name: Cargo bin is on PATH
check:
type: path_contains
substring: ".cargo/bin"
Check Windows registry values
assertions:
- name: Developer mode enabled
check:
type: registry_value
key: "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock"
value_name: "AllowDevelopmentWithoutDevLicense"
expected: "1"
Run a shell command as a check
assertions:
- name: Rust stable is default toolchain
check:
type: shell
command: "rustup default"
contains: "stable"
Compose checks with all_of / any_of
assertions:
- name: Rust toolchain is fully configured
check:
type: all_of
conditions:
- type: command_exists
command: rustc
- type: command_exists
command: cargo
- type: dir_exists
path: "~/.cargo"
- type: path_contains
substring: ".cargo/bin"
Run assertion checks:
anvil health my-setup --assertions-only
Enable assertions in the health config:
health:
assertion_check: true
Running Post-Install Commands
Use the commands block for inline shell commands that run during installation — no separate script files needed.
Basic commands
commands:
post_install:
- run: "rustup component add clippy rustfmt"
description: "Install Rust components"
- run: "cargo install cargo-watch cargo-nextest"
description: "Install cargo tools"
Pre-install setup
commands:
pre_install:
- run: "mkdir -p ~/.config"
description: "Ensure config directory exists"
Conditional execution
Only run a command when a condition is met:
commands:
post_install:
- run: "rustup component add rust-analyzer"
description: "Install rust-analyzer"
when:
command_exists: rustup
- run: "npm install -g typescript"
description: "Install TypeScript globally"
when:
command_exists: npm
Continue on error
Allow a command to fail without stopping the install:
commands:
post_install:
- run: "cargo install sccache"
description: "Install sccache (optional)"
continue_on_error: true
Using Workload Inheritance
Inheritance lets you build on top of existing workloads. Child workloads inherit packages, files, scripts, and environment from their parents.
Extending a base workload
name: rust-developer
version: "1.0.0"
description: "Rust dev environment"
extends:
- essentials # inherits Git, VS Code, Terminal, etc.
packages:
winget:
- id: Rustlang.Rustup # added on top of essentials packages
Building a team workload chain
essentials → shared dev tools
└── backend → adds Docker, Postgres, Redis
└── rust-be → adds Rust toolchain
└── go-be → adds Go toolchain
# backend/workload.yaml
name: backend
version: "1.0.0"
description: "Backend development base"
extends:
- essentials
packages:
winget:
- id: Docker.DockerDesktop
- id: PostgreSQL.PostgreSQL
# rust-be/workload.yaml
name: rust-be
version: "1.0.0"
description: "Rust backend developer"
extends:
- backend
packages:
winget:
- id: Rustlang.Rustup
How inheritance merges
- Packages: child packages are appended after parent packages
- Files: child files are appended; same destination = child overrides parent
- Commands: concatenated (parent commands run first)
- Environment: child variables override same-named parent variables
- Assertions: child assertions are appended after parent assertions
Setting Up Environment Variables
User-scoped variables
environment:
variables:
- name: EDITOR
value: "code --wait"
scope: user
- name: RUST_BACKTRACE
value: "1"
scope: user
PATH additions
environment:
path_additions:
- "~/.cargo/bin"
- "~/go/bin"
- "~/.local/bin"
Managing Private Workload Repositories
Keep team-specific workloads in a private Git repo and configure Anvil to find them.
Set up the search path
# Clone your team's workloads
git clone git@github.com:myteam/workloads.git ~/team-workloads
# Tell Anvil where to find them
anvil config set workloads.paths '["~/team-workloads"]'
Or edit ~/.anvil/config.yaml directly:
workloads:
paths:
- "~/team-workloads"
- "~/personal-workloads"
Search precedence
When multiple paths contain a workload with the same name, the first match wins:
- Explicit
--pathargument (highest priority) - User-configured paths from
config.yaml - Default search paths (bundled workloads)
See all discovered paths and any shadowed duplicates:
anvil list --all-paths --long
Private repo structure
team-workloads/
├── base-dev/
│ └── workload.yaml
├── frontend/
│ └── workload.yaml
├── backend/
│ ├── workload.yaml
│ ├── files/
│ │ └── .pgpass
│ └── scripts/
│ └── post-install.ps1
└── data-science/
└── workload.yaml
Team members clone the repo and configure the path once. Updates are pulled with git pull.
Generating Reports
JSON output for scripting
anvil health my-setup --output json | jq '.summary'
HTML health report
anvil health my-setup --output html --file report.html
YAML export
anvil show my-setup --output yaml
List workloads as JSON
anvil list --output json | jq '.[].name'
Troubleshooting
A guide to diagnosing and resolving common Anvil issues.
1. Installation Issues
Anvil won't run
Symptoms:
- "anvil" is not recognized as a command
- Application crashes immediately
Solutions:
-
Check PATH configuration
Windows:
where.exe anvil # If not found, add to PATH $env:PATH += ";C:\path\to\anvil"Linux/macOS:
which anvil # If not found, add to PATH export PATH="$PATH:/path/to/anvil" -
Verify platform compatibility
- Anvil currently requires Windows 10 (1809+) or Windows 11
- Cross-platform support is on the roadmap
-
Verify the download isn't corrupted
- Re-download from the releases page
- Verify the file hash if provided
Package manager not found
Symptoms:
- Error: "winget is not recognized"
- Error: "Package manager not available"
- Package installation fails before starting
Solutions (Windows — winget):
-
Install Windows Package Manager
# Open Microsoft Store to App Installer Start-Process "ms-windows-store://pdp/?ProductId=9NBLGGH4NNS1" -
Update App Installer
- Open Microsoft Store → search "App Installer" → click "Update"
-
Test availability
winget --version -
Restart your terminal after installing winget
2. Package Installation Issues
Package not found
Symptoms:
- Error: "No package found matching input criteria"
- Error: "Package 'X' not found in any source"
Solutions:
-
Verify package ID
winget search <package-name> winget search --exact "Package Name" -
Check package source in workload
packages: winget: - id: SomeApp.App source: msstore # For Microsoft Store apps -
Update package sources
winget source update
Installation hangs
Symptoms:
- Installation appears frozen
- No progress for extended time
Solutions:
-
Check for interactive prompts
- Some installers require user interaction
- Run anvil without
--quietto see prompts
-
Use silent install overrides
packages: winget: - id: SomeApp.App override: - "--silent" - "--accept-license" -
Check for pending system updates
- Pending OS updates can block installations
- Complete any pending updates and restart
Access denied during installation
Symptoms:
- Error: "Access is denied"
- Error: "Administrator privileges required"
Solutions:
-
Run with elevation (Windows)
Start-Process powershell -Verb RunAs anvil install my-workload -
Use user-scope installation
packages: winget: - id: Package.Name override: - "--scope" - "user"
3. File Operation Issues
Permission denied
Symptoms:
- Error: "Permission denied" when copying files
- File operations fail
Solutions:
-
Use user-writable paths
files: - source: config.json destination: "~/.config/app/config.json" # Expands to user home -
Check target directory permissions
Windows:
Get-Acl "C:\path\to\directory" | Format-ListLinux/macOS:
ls -la /path/to/directory -
Run as administrator for system-level paths
File not found
Symptoms:
- Error: "Source file not found"
Solutions:
-
Verify source path — paths are relative to the workload directory
files: - source: files/config.json # Relative to workload dir destination: "~/.config/app/config.json" -
Check workload directory structure
my-workload/ ├── workload.yaml └── files/ └── config.json
Backup failed
Symptoms:
- Error: "Failed to create backup"
Solutions:
- Check disk space
- Verify backup directory exists and is writable
$env:ANVIL_BACKUP_DIR = "D:\Backups\anvil"
4. Script Execution Issues
Script won't execute
Symptoms:
- Error: "Script execution failed"
- No output from script
Solutions:
-
Verify script path in workload.yaml — paths are relative to
scripts/scripts: post_install: - path: setup.ps1 # Relative to scripts/ directory -
Test script manually
& "C:\workloads\my-workload\scripts\setup.ps1"
Execution policy error (Windows)
Symptoms:
- Error: "Running scripts is disabled on this system"
Solutions:
-
Set execution policy
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -
Check group policy — corporate environments may have stricter policies
Script timeout
Symptoms:
- Error: "Script execution timed out"
Solutions:
-
Increase timeout in workload
scripts: post_install: - path: scripts/long-running.ps1 timeout: 1800 # 30 minutes (default: 300) -
Check for infinite loops or slow network operations in the script
Elevated script fails (Windows)
Symptoms:
- Error: "Elevation required"
Solutions:
- Run anvil as administrator
- Set the elevated flag in workload.yaml
scripts: post_install: - path: scripts/admin-setup.ps1 elevated: true
5. Health Check Issues
False failures
Symptoms:
- Health check fails but software is working
- Intermittent failures
Solutions:
-
Run with verbose output
anvil health my-workload --verbose -
Review health check scripts — they may have outdated expectations
-
Update version expectations
winget list --id Package.Name
Partial results
Symptoms:
- Some checks don't run
- Health report incomplete
Solutions:
-
Check verbose output for errors in earlier checks
anvil health my-workload -vvv -
Verify all referenced scripts exist in the workload directory
6. Configuration Issues
Config file not loading
Symptoms:
- Settings not applied
- Default values used instead of custom
Solutions:
-
Check config location
anvil config path -
View current configuration
anvil config list -
Reset to defaults
anvil config reset
Workloads not found
Symptoms:
- Error: "Workload 'X' not found"
- Empty list from
anvil list
Solutions:
-
Check search paths
anvil config list -
Verify workload structure — needs a
workload.yamlfileworkload-name/ └── workload.yaml -
Use explicit path
anvil install my-workload --path /path/to/workloads
7. Workload Inheritance Issues
Circular dependency detected
Symptoms:
- Error: "Circular dependency detected: A → B → A"
Solutions:
-
Inspect the
extendschain in each workload# workload-a/workload.yaml extends: - workload-b # workload-b also extends workload-a → cycle! -
Break the cycle by removing one of the
extendsreferences -
Run
anvil validateto see the detected chain
Maximum inheritance depth exceeded
Symptoms:
- Error: "Maximum inheritance depth (10) exceeded for workload 'X'"
Solutions:
- Flatten the hierarchy — the maximum allowed depth is 10
- Merge intermediate workloads to reduce nesting
- Check for unintended long chains
anvil show my-workload # Review resolved inheritance
Parent workload not found
Symptoms:
- Error: "Parent workload 'X' not found"
Solutions:
-
Verify the parent name matches an available workload
anvil list # See all available workloads -
Check search paths — ensure the parent workload directory is discoverable
anvil config list # Review configured workload search paths -
Use
--pathif the parent is in a custom locationanvil install my-workload --path /path/to/workloads
8. Platform-Specific Warnings
Workload references unavailable package manager
Symptoms:
- Warning: "Homebrew packages defined but Homebrew is not available on Windows"
- Warning: "APT packages defined but APT is not available on Windows"
- Warning: "Winget packages defined but winget is only available on Windows"
Solutions:
-
Run
anvil validate --strictto surface these warnings -
Use platform-appropriate package managers in your workload
packages: winget: # Windows only - id: Package.Name brew: # macOS only (planned) - name: package apt: # Linux only (planned) - name: package -
Split into platform-specific workloads if targeting multiple OSes
9. Assertion and Command Failures
Assertion failure
Symptoms:
- Health check assertion fails with
[FAIL] - Error: condition evaluation returned false
Solutions:
-
Run the health check with verbose output to see which assertion failed
anvil health my-workload -vvv -
Review the condition — assertions check system state (command existence, file presence, environment variables, registry values)
-
Common assertion types and fixes:
- Command not found — install the missing tool or add it to PATH
- File missing — re-run
anvil installto deploy files - Environment variable not set — check your shell profile or restart the terminal
Command execution failure
Symptoms:
- Error: "Command not found: X"
- Error: "Command timed out after N seconds"
- Error: "Command requires elevated privileges"
Solutions:
-
Command not found — ensure the executable is installed and on PATH
Get-Command <command-name> -
Timeout — increase the timeout in your workload definition
scripts: post_install: - path: slow-script.ps1 timeout: 1800 # 30 minutes -
Elevation required — run anvil as administrator, or set
elevated: true
10. Error Reference
Common Error Codes
| Error | Description | Solution |
|---|---|---|
E001 | Workload not found | Check name and search paths |
E002 | Invalid workload schema | Run anvil validate for details |
E003 | Circular dependency | Review extends chain in workloads |
E004 | Package installation failed | Check package manager logs, verify package ID |
E005 | File operation failed | Check permissions, verify paths |
E006 | Script execution failed | Review script output, check syntax |
E007 | Health check failed | Review check results, update scripts |
E008 | Configuration error | Validate config file syntax |
E009 | Backup operation failed | Check disk space and permissions |
E010 | Restore operation failed | Verify backup exists and is valid |
11. Getting Help
Verbose Output
Get more detailed information about what Anvil is doing:
anvil -v <command> # Some detail
anvil -vv <command> # More detail
anvil -vvv <command> # Maximum detail (debug level)
Enable Logging
# Set log level via environment variable
ANVIL_LOG=debug anvil install my-workload
# Available levels: error, warn, info, debug, trace
System Information
Gather helpful information when reporting issues:
# Anvil version
anvil --version
# Operating system
# Windows: winver
# Linux/macOS: uname -a
# Package manager version
winget --version # Windows
Reporting Issues
When reporting issues, include:
- Anvil version:
anvil --version - Operating system and version
- Command that failed: exact command you ran
- Error message: complete error output
- Verbose output: run with
-vvvflag - Workload file: contents of
workload.yaml(sanitized if needed)
Resources
Quick Reference
Common Commands for Troubleshooting
# Check Anvil installation
anvil --version
# Validate workload
anvil validate my-workload --strict
# Preview installation (dry run)
anvil install my-workload --dry-run
# Verbose health check
anvil health my-workload -vvv
# View current configuration
anvil config list
# Reset configuration
anvil config reset
Environment Variables
| Variable | Purpose |
|---|---|
ANVIL_CONFIG | Custom config file path |
ANVIL_WORKLOADS | Additional workload search paths |
ANVIL_LOG | Log level (error, warn, info, debug, trace) |
ANVIL_BACKUP_DIR | Custom backup directory |
NO_COLOR | Disable colored output |
CLI Reference
Complete reference for every Anvil command, flag, and option.
Global Options
These options are available on all commands:
| Flag | Description |
|---|---|
-v, --verbose... | Increase output verbosity (repeat for more: -v, -vv, -vvv) |
-q, --quiet | Suppress non-essential output |
-c, --config <PATH> | Use a custom configuration file |
--no-color | Disable colored output |
-h, --help | Print help |
-V, --version | Print version |
Commands
anvil install
Synopsis: anvil install [OPTIONS] <WORKLOAD>
Description: Apply a workload configuration to the system. Installs packages, deploys files, and runs scripts as defined in the workload.
Arguments:
| Argument | Required | Description |
|---|---|---|
<WORKLOAD> | Yes | Name of the workload to install (or path to workload.yaml) |
Options:
| Flag | Default | Description |
|---|---|---|
-d, --dry-run | — | Show what would be done without making changes |
-f, --force | — | Skip confirmation prompts |
-p, --packages-only | — | Only install packages, skip files |
--skip-packages | — | Skip package installation |
--skip-files | — | Skip file operations |
--no-backup | — | Don't backup existing files before overwriting |
--upgrade | — | Upgrade existing packages to specified versions |
--retry-failed | — | Retry only failed packages from previous run |
--parallel | — | Run installations in parallel where safe |
-j, --jobs <N> | 4 | Number of parallel package installations |
--timeout <SECONDS> | 3600 | Global timeout for operations in seconds |
--files-only | — | Only process files, skip packages |
--force-files | — | Force overwrite files without checking hash |
Examples:
# Install a workload
anvil install essentials
# Dry run to preview changes
anvil install dev-tools --dry-run
# Install only packages, skip everything else
anvil install essentials --packages-only
# Force install with parallel jobs
anvil install dev-tools --force --parallel -j 8
# Retry previously failed packages
anvil install essentials --retry-failed
# Install from a specific path
anvil install ./my-workload/workload.yaml
# Deploy only files, no packages or scripts
anvil install essentials --files-only --force-files
Exit Codes: 0 = success, 1 = failure
anvil health
Synopsis: anvil health [OPTIONS] <WORKLOAD>
Description: Check system health against a workload definition. Verifies packages are installed, files are deployed, and health check scripts pass.
Arguments:
| Argument | Required | Description |
|---|---|---|
<WORKLOAD> | Yes | Name of the workload to check against |
Options:
| Flag | Default | Description |
|---|---|---|
-o, --output <OUTPUT> | table | Output format (table, json, yaml, html) |
-f, --file <PATH> | — | Write report to file instead of stdout |
--fail-fast | — | Stop on first failure |
--packages-only | — | Only check packages |
--files-only | — | Only check files |
--assertions-only | — | Only evaluate declarative assertions |
-s, --strict | — | Treat warnings as errors |
--fix | — | Attempt to install missing packages |
--update | — | Update packages with available updates |
--no-cache | — | Skip cache and query winget directly |
--show-diff | — | Show file differences for modified files |
Examples:
# Check health of a workload
anvil health essentials
# JSON report saved to file
anvil health dev-tools -o json -f report.json
# Check only packages, stop on first failure
anvil health essentials --packages-only --fail-fast
# Auto-fix missing packages
anvil health essentials --fix
# Strict mode — warnings become errors
anvil health essentials --strict
Exit Codes: 0 = all checks passed, 1 = one or more checks failed
anvil list
Synopsis: anvil list [OPTIONS]
Description: List available workloads discovered from all configured search paths.
Options:
| Flag | Default | Description |
|---|---|---|
-a, --all | — | Include built-in and custom workloads |
-l, --long | — | Show detailed information |
--path <PATH> | — | Search for workloads in additional path |
--all-paths | — | Show all discovered paths including shadowed duplicates |
-o, --output <OUTPUT> | — | Output format (table, json, yaml, html) |
Examples:
# List all workloads
anvil list
# Detailed listing with extra info
anvil list --long
# Include all sources
anvil list --all
# Search an additional directory
anvil list --path ~/my-workloads
# Machine-readable output
anvil list -o json
Exit Codes: 0 = success, 1 = failure
anvil show
Synopsis: anvil show [OPTIONS] <WORKLOAD>
Description: Display detailed workload information including packages, files, scripts, and configuration.
Arguments:
| Argument | Required | Description |
|---|---|---|
<WORKLOAD> | Yes | Name of the workload to display |
Options:
| Flag | Default | Description |
|---|---|---|
-r, --resolved | — | Show resolved configuration (with inheritance applied) |
--show-inheritance | — | Show inheritance tree visualization |
-o, --output <OUTPUT> | yaml | Output format (yaml, json) |
Examples:
# Show workload definition
anvil show essentials
# Show with inheritance resolved
anvil show dev-tools --resolved
# View inheritance tree
anvil show dev-tools --show-inheritance
# Output as JSON
anvil show essentials -o json
Exit Codes: 0 = success, 1 = failure
anvil validate
Synopsis: anvil validate [OPTIONS] [PATH]
Description: Validate workload definition syntax. Checks YAML structure, schema conformance, and optionally script syntax.
Arguments:
| Argument | Required | Description |
|---|---|---|
[PATH] | No | Path to workload.yaml file or workload directory |
Options:
| Flag | Default | Description |
|---|---|---|
--strict | — | Enable strict validation mode |
--schema | — | Output JSON schema for workload definitions |
--check-scripts | — | Validate script syntax using PowerShell parser |
--scripts-only | — | Only validate scripts (skip other validation) |
Examples:
# Validate a workload directory
anvil validate workloads/essentials
# Strict mode validation
anvil validate workloads/dev-tools --strict
# Validate with script syntax checking
anvil validate workloads/essentials --check-scripts
# Dump the JSON schema
anvil validate --schema
# Validate only scripts
anvil validate workloads/essentials --scripts-only
Exit Codes: 0 = valid, 1 = validation errors found
anvil init
Synopsis: anvil init [OPTIONS] <NAME>
Description: Initialize a new workload template with scaffolded directory structure and workload.yaml.
Arguments:
| Argument | Required | Description |
|---|---|---|
<NAME> | Yes | Name for the new workload |
Options:
| Flag | Default | Description |
|---|---|---|
-t, --template <TEMPLATE> | standard | Base template (minimal, standard, full) |
-e, --extends <PARENT> | — | Parent workload to extend |
-o, --output <PATH> | — | Output directory |
Template values:
| Template | Description |
|---|---|
minimal | Minimal workload with just metadata |
standard | Standard workload with common sections |
full | Full workload with all sections and examples |
Examples:
# Create a standard workload
anvil init my-workload
# Create a minimal workload
anvil init my-workload --template minimal
# Create a workload extending essentials
anvil init my-workload --extends essentials
# Full template in a custom directory
anvil init my-workload --template full --output ~/workloads
Exit Codes: 0 = success, 1 = failure
anvil status
Synopsis: anvil status [OPTIONS] [WORKLOAD]
Description: Show installation status and state for one or all workloads.
Arguments:
| Argument | Required | Description |
|---|---|---|
[WORKLOAD] | No | Name of the workload (shows all if omitted) |
Options:
| Flag | Default | Description |
|---|---|---|
-o, --output <OUTPUT> | table | Output format (table, json, yaml, html) |
-l, --long | — | Show detailed status including timestamps |
--clear | — | Clear stored state for the specified workload |
Examples:
# Show status for all workloads
anvil status
# Status for a specific workload
anvil status essentials
# Detailed status with timestamps
anvil status essentials --long
# Clear stored state
anvil status essentials --clear
# Machine-readable output
anvil status -o json
Exit Codes: 0 = success, 1 = failure
anvil completions
Synopsis: anvil completions [OPTIONS] <SHELL>
Description: Generate shell completion scripts for the specified shell.
Arguments:
| Argument | Required | Description |
|---|---|---|
<SHELL> | Yes | Target shell (bash, zsh, fish, powershell, elvish) |
Examples:
PowerShell:
anvil completions powershell | Out-String | Invoke-Expression
# Or persist to $PROFILE
anvil completions powershell >> $PROFILE
Bash:
anvil completions bash > ~/.local/share/bash-completion/completions/anvil
Zsh:
anvil completions zsh > ~/.zfunc/_anvil
Fish:
anvil completions fish > ~/.config/fish/completions/anvil.fish
Exit Codes: 0 = success, 1 = failure
anvil backup
Synopsis: anvil backup <COMMAND>
Description: Manage file backups created during workload installation. Anvil backs up existing files before overwriting them; this command lets you list, restore, verify, and clean those backups.
anvil backup create
Synopsis: anvil backup create [OPTIONS]
Description: Create a new backup.
Options:
| Flag | Default | Description |
|---|---|---|
-n, --name <NAME> | — | Name for the backup |
-w, --workload <WORKLOAD> | — | Only backup files related to workload |
--include-packages | — | Include winget package list export |
--compress | — | Create compressed archive |
Examples:
# Create a named backup
anvil backup create --name "before-upgrade"
# Backup files for a specific workload
anvil backup create --workload essentials
# Compressed backup with package list
anvil backup create --compress --include-packages
anvil backup list
Synopsis: anvil backup list [OPTIONS]
Description: List all backups.
Options:
| Flag | Default | Description |
|---|---|---|
-w, --workload <WORKLOAD> | — | Filter by workload name |
-o, --output <OUTPUT> | table | Output format (table, json, yaml, html) |
-l, --long | — | Show detailed information |
Examples:
# List all backups
anvil backup list
# Filter by workload
anvil backup list --workload essentials
# Detailed listing
anvil backup list --long
anvil backup show
Synopsis: anvil backup show [OPTIONS] <ID>
Description: Show details for a specific backup.
Arguments:
| Argument | Required | Description |
|---|---|---|
<ID> | Yes | Backup ID |
Examples:
anvil backup show abc123
anvil backup restore
Synopsis: anvil backup restore [OPTIONS] [ID]
Description: Restore files from a backup.
Arguments:
| Argument | Required | Description |
|---|---|---|
[ID] | No | Backup ID to restore (or use --workload) |
Options:
| Flag | Default | Description |
|---|---|---|
-w, --workload <WORKLOAD> | — | Restore all backups for a workload |
-d, --dry-run | — | Show what would be done without making changes |
-f, --force | — | Skip confirmation prompts |
Examples:
# Restore a specific backup
anvil backup restore abc123
# Dry run restore
anvil backup restore abc123 --dry-run
# Restore all backups for a workload
anvil backup restore --workload essentials --force
anvil backup clean
Synopsis: anvil backup clean [OPTIONS]
Description: Remove old backups.
Options:
| Flag | Default | Description |
|---|---|---|
--older-than <DAYS> | 30 | Remove backups older than N days |
-d, --dry-run | — | Show what would be done without making changes |
-f, --force | — | Skip confirmation prompts |
Examples:
# Clean backups older than 30 days (default)
anvil backup clean
# Clean backups older than 7 days
anvil backup clean --older-than 7
# Preview what would be removed
anvil backup clean --dry-run
anvil backup verify
Synopsis: anvil backup verify [OPTIONS]
Description: Verify backup integrity by checking that backup files exist and are uncorrupted.
Options:
| Flag | Default | Description |
|---|---|---|
-w, --workload <WORKLOAD> | — | Only verify backups for a specific workload |
--fix | — | Fix issues by removing corrupted/missing entries |
Examples:
# Verify all backups
anvil backup verify
# Verify and auto-fix issues
anvil backup verify --fix
# Verify backups for one workload
anvil backup verify --workload essentials
anvil config
Synopsis: anvil config <COMMAND>
Description: Manage global Anvil configuration. Settings are persisted in a YAML file at ~/.anvil/config.yaml.
anvil config get
Synopsis: anvil config get [OPTIONS] <KEY>
Description: Get a configuration value.
Arguments:
| Argument | Required | Description |
|---|---|---|
<KEY> | Yes | Configuration key (e.g., defaults.shell) |
Examples:
anvil config get defaults.shell
anvil config get workload_paths
anvil config set
Synopsis: anvil config set [OPTIONS] <KEY> <VALUE>
Description: Set a configuration value.
Arguments:
| Argument | Required | Description |
|---|---|---|
<KEY> | Yes | Configuration key (e.g., defaults.shell) |
<VALUE> | Yes | Value to set |
Examples:
anvil config set defaults.shell powershell
anvil config set defaults.timeout 600
anvil config list
Synopsis: anvil config list [OPTIONS]
Description: List all configuration values.
Options:
| Flag | Default | Description |
|---|---|---|
-o, --output <OUTPUT> | table | Output format (table, json, yaml, html) |
Examples:
# Show all config
anvil config list
# JSON output
anvil config list -o json
anvil config reset
Synopsis: anvil config reset [OPTIONS]
Description: Reset configuration to defaults.
Options:
| Flag | Default | Description |
|---|---|---|
-f, --force | — | Skip confirmation prompt |
Examples:
# Reset with confirmation
anvil config reset
# Force reset without prompt
anvil config reset --force
anvil config edit
Synopsis: anvil config edit [OPTIONS]
Description: Open configuration file in the default editor.
Examples:
anvil config edit
anvil config path
Synopsis: anvil config path [OPTIONS]
Description: Show configuration file path.
Examples:
anvil config path
Environment Variables
| Variable | Description | Used by |
|---|---|---|
NO_COLOR | Disables colored output when set (any value). Standard convention (no-color.org). | All commands |
ANVIL_WORKLOAD | Name of the current workload being processed. | Scripts, templates |
ANVIL_WORKLOAD_PATH | Absolute path to the workload directory. | Scripts |
ANVIL_DRY_RUN | Set to "true" or "false" during script execution. | Scripts |
ANVIL_VERBOSE | Verbosity level (0–3) during script execution. | Scripts |
ANVIL_PHASE | Current execution phase: pre_install, post_install, or validation. | Scripts |
ANVIL_VERSION | Anvil version string. Available in scripts and Handlebars templates. | Scripts, templates |
RUST_LOG | Controls Rust log output (e.g., RUST_LOG=debug). Standard env_logger / tracing variable. | Debugging |
RUST_BACKTRACE | Enables Rust backtraces on panic (1 = short, full = complete). | Debugging |
Exit Codes
| Code | Meaning |
|---|---|
0 | Success — command completed, all checks passed |
1 | General failure — operation failed or health checks did not pass |
2 | Usage error — invalid arguments or missing required parameters |
Schema Reference
This document is the authoritative reference for both the global configuration file (~/.anvil/config.yaml) and the workload definition file (workload.yaml). All field names, types, and defaults are derived from the Rust source structs.
Configuration File (~/.anvil/config.yaml)
The global configuration file stores user preferences and default settings. If the file does not exist, Anvil uses the built-in defaults shown below.
defaults
Default settings applied to CLI commands.
| Key | Type | Default | Description |
|---|---|---|---|
defaults.shell | string | "powershell" | Default shell for script execution (powershell, pwsh). |
defaults.script_timeout | integer | 300 | Default script timeout in seconds. |
defaults.output_format | string | "table" | Output format for commands. Valid values: table, json, yaml, html. |
defaults.color | string | "auto" | Color output mode. Valid values: auto, always, never. |
backup
Controls automatic backup behavior during installs.
| Key | Type | Default | Description |
|---|---|---|---|
backup.auto_backup | boolean | true | Create a backup before install operations. |
backup.retention_days | integer | 30 | Delete backups older than this many days. |
backup.max_backups | integer | 10 | Maximum number of backups to keep per workload. |
backup.compress | boolean | false | Compress backups by default. |
install
Settings that govern package installation behavior.
| Key | Type | Default | Description |
|---|---|---|---|
install.parallel_packages | boolean | false | Install packages in parallel. |
install.skip_installed | boolean | true | Skip packages that are already installed. |
install.confirm | boolean | true | Prompt for confirmation before installing. |
workloads
Workload discovery configuration.
| Key | Type | Default | Description |
|---|---|---|---|
workloads.paths | list of strings | [] | Additional directories to search for workloads. |
logging
Logging configuration.
| Key | Type | Default | Description |
|---|---|---|---|
logging.level | string | "info" | Log verbosity level. Valid values: error, warn, info, debug, trace. |
logging.file | string or null | null | Path to a log file. null disables file logging. |
Full Example
defaults:
shell: powershell
script_timeout: 300
output_format: table
color: auto
backup:
auto_backup: true
retention_days: 30
max_backups: 10
compress: false
install:
parallel_packages: false
skip_installed: true
confirm: true
workloads:
paths:
- ~/my-workloads
- /shared/team-workloads
logging:
level: info
file: ~/.anvil/anvil.log
Workload Schema (workload.yaml)
A workload is defined by a workload.yaml file inside a workload directory. The directory may also contain files/ and scripts/ subdirectories.
Top-Level Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Unique workload identifier (kebab-case). |
version | string | yes | — | Semantic version (e.g., "1.0.0"). |
description | string | yes | — | Human-readable description of the workload. |
extends | list of strings | no | null | Parent workload names to inherit from. |
packages | object | no | null | Package definitions, grouped by manager. |
files | list of FileEntry | no | null | Configuration files to deploy. |
scripts | object | no | null | Scripts to execute at various phases. |
commands | object | no | null | Inline commands to execute at various phases. |
environment | object | no | null | Environment variables and PATH additions. |
health | object | no | null | Health check configuration flags. |
assertions | list of Assertion | no | null | Declarative assertions for health validation. |
packages
The packages object groups package definitions by package manager. Each manager key is optional.
| Field | Type | Required | Description |
|---|---|---|---|
packages.winget | list of WingetPackage | no | Windows packages managed by winget. |
packages.brew | list of BrewPackage | no | macOS/Linux packages managed by Homebrew. |
packages.apt | list of AptPackage | no | Debian/Ubuntu packages managed by APT. |
Winget Package
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | yes | — | Winget package identifier (e.g., Git.Git). |
version | string | no | latest | Specific version to install. |
source | string | no | null | Package source (e.g., msstore for Microsoft Store apps). |
override | list of strings | no | null | Override arguments passed to the installer. |
override_args | list of strings | no | null | Additional winget arguments. |
packages:
winget:
- id: Git.Git
- id: Microsoft.VisualStudioCode
version: "1.95.0"
- id: Notepad++.Notepad++
override:
- --override
- "/VERYSILENT /NORESTART"
- id: 9NBLGGH4NNS1
source: msstore
Brew Package
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Package name (e.g., git, node). |
cask | boolean | no | false | Whether this is a cask (GUI app) vs formula (CLI tool). |
tap | string | no | null | Tap source (e.g., homebrew/cask-fonts). |
packages:
brew:
- name: git
- name: visual-studio-code
cask: true
- name: font-cascadia-code
cask: true
tap: homebrew/cask-fonts
APT Package
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Package name (e.g., git, build-essential). |
version | string | no | null | Specific version constraint. |
packages:
apt:
- name: git
- name: build-essential
version: "12.9"
files
Each entry in the files list describes a configuration file to deploy.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
source | string | yes | — | Path relative to the workload's files/ directory. |
destination | string | yes | — | Target path. ~ expands to the user's home directory. |
backup | boolean | no | true | Back up the existing file before overwriting. |
permissions | string | no | null | File permissions (reserved for future use). |
template | boolean | no | false | Process the file as a Handlebars template before deploying. |
files:
- source: user/.config/starship.toml
destination: "~/.config/starship.toml"
- source: user/.gitconfig
destination: "~/.gitconfig"
backup: true
template: true
commands
The commands object defines inline shell commands to execute at install phases.
| Field | Type | Description |
|---|---|---|
commands.pre_install | list of CommandEntry | Commands to run before package installation. |
commands.post_install | list of CommandEntry | Commands to run after package installation. |
CommandEntry
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
run | string | yes | — | Shell command string to execute. |
description | string | no | null | Human-readable description. |
timeout | integer | no | 300 | Timeout in seconds. |
elevated | boolean | no | false | Whether the command requires administrator privileges. |
when | Condition | no | null | Condition that must be true for this command to run. |
continue_on_error | boolean | no | false | Whether to continue if this command fails. |
commands:
post_install:
- run: "rustup default stable"
description: "Set Rust stable as default toolchain"
timeout: 120
- run: "cargo install cargo-watch"
description: "Install cargo-watch"
when:
type: command_exists
command: cargo
continue_on_error: true
environment
Environment variable and PATH configuration.
| Field | Type | Description |
|---|---|---|
environment.variables | list of EnvVariable | Environment variables to set. |
environment.path_additions | list of strings | Directory paths to add to the system PATH. ~ expands to the user's home. |
EnvVariable
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Variable name. |
value | string | yes | — | Variable value. |
scope | string | no | "user" | Scope: user or machine. |
environment:
variables:
- name: EDITOR
value: code
scope: user
- name: DOTNET_CLI_TELEMETRY_OPTOUT
value: "1"
path_additions:
- "~/.cargo/bin"
- "~/.local/bin"
health
Flags that control which categories of health checks are evaluated during anvil health.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
package_check | boolean | no | true | Verify that declared packages are installed. |
file_check | boolean | no | true | Verify that declared files are deployed. |
assertion_check | boolean | no | true | Evaluate declarative assertions. |
health:
package_check: true
file_check: true
assertion_check: true
assertions
Declarative health assertions using the condition engine. Each assertion pairs a display name with a condition to evaluate.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Display name for the assertion. |
check | Condition | yes | The condition to evaluate. |
Condition Types
Conditions are serialized as tagged objects with a type discriminator field. The following condition types are available:
command_exists
Check whether a command is available on PATH.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "command_exists" |
command | string | yes | Command name to look for. |
file_exists
Check whether a file exists at the given path (~ is expanded).
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "file_exists" |
path | string | yes | File path to check. |
dir_exists
Check whether a directory exists at the given path (~ is expanded).
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "dir_exists" |
path | string | yes | Directory path to check. |
env_var
Check whether an environment variable is set, optionally matching an expected value.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "env_var" |
name | string | yes | Environment variable name. |
value | string | no | Expected value. If omitted, only checks that the variable is set. |
path_contains
Check whether the system PATH contains a given substring.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "path_contains" |
substring | string | yes | Substring to search for in PATH entries. |
registry_value
Query a Windows registry value under HKCU or HKLM.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "registry_value" |
hive | string | yes | Registry hive: "HKCU" or "HKLM". |
key | string | yes | Full key path (e.g., "SOFTWARE\\Microsoft\\Windows\\CurrentVersion"). |
name | string | yes | Value name inside the key. |
expected | string | no | Expected data. If omitted, only asserts the value exists. |
shell
Run a shell command; the condition passes when the exit code is 0.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "shell" |
command | string | yes | Shell command to execute. |
description | string | no | Human-readable description of the check. |
all_of
Logical AND — all child conditions must pass.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "all_of" |
conditions | list of Condition | yes | Child conditions to evaluate. |
any_of
Logical OR — at least one child condition must pass.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | "any_of" |
conditions | list of Condition | yes | Child conditions to evaluate. |
Assertions Example
assertions:
- name: "Git is installed"
check:
type: command_exists
command: git
- name: "Config directory exists"
check:
type: dir_exists
path: "~/.config/app"
- name: "EDITOR is set to VS Code"
check:
type: env_var
name: EDITOR
value: code
- name: "Cargo bin is in PATH"
check:
type: path_contains
substring: ".cargo/bin"
- name: "Dark mode enabled"
check:
type: registry_value
hive: HKCU
key: "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
name: AppsUseLightTheme
expected: "0x0"
- name: "Rust toolchain ready"
check:
type: all_of
conditions:
- type: command_exists
command: rustc
- type: command_exists
command: cargo
- type: path_contains
substring: ".cargo/bin"
Full Example
A complete workload.yaml demonstrating all sections:
name: developer-tools
version: "1.0.0"
description: "Full developer workstation setup"
extends:
- essentials
packages:
winget:
- id: Git.Git
- id: Microsoft.VisualStudioCode
- id: Rustlang.Rustup
- id: Starship.Starship
files:
- source: user/.config/starship.toml
destination: "~/.config/starship.toml"
backup: true
- source: user/.gitconfig
destination: "~/.gitconfig"
template: true
scripts:
pre_install:
- path: pre-install.ps1
description: "System preparation"
timeout: 60
post_install:
- path: configure-terminal.ps1
description: "Configure Windows Terminal"
timeout: 300
elevated: true
commands:
post_install:
- run: "rustup default stable"
description: "Set default Rust toolchain"
timeout: 120
- run: "cargo install cargo-watch"
description: "Install cargo-watch"
when:
type: command_exists
command: cargo
continue_on_error: true
environment:
variables:
- name: EDITOR
value: code
- name: DOTNET_CLI_TELEMETRY_OPTOUT
value: "1"
scope: user
path_additions:
- "~/.cargo/bin"
health:
package_check: true
file_check: true
assertion_check: true
assertions:
- name: "Git is installed"
check:
type: command_exists
command: git
- name: "VS Code is installed"
check:
type: command_exists
command: code
- name: "Starship config deployed"
check:
type: file_exists
path: "~/.config/starship.toml"
Specification
Version: 2.0.0
Status: Active
Last Updated: 2026-04-16
1. Executive Summary
1.1 Purpose
Anvil is a declarative workstation configuration management tool designed to automate the setup and validation of development environments. It provides a declarative approach to defining workstation configurations ("workloads") and offers both installation and health-check capabilities. Anvil currently targets Windows with winget as the primary package manager, with cross-platform support (Homebrew, APT) on the roadmap.
1.2 Core Modes of Operation
| Mode | Description |
|---|---|
| Install | Apply a workload configuration by installing packages, executing scripts, and copying files |
| Health Check | Validate the current system state against a workload definition |
1.3 Key Features
- Declarative Configuration: Define workloads in human-readable YAML files
- Package Management: Leverage winget for software installation
- File Synchronization: Copy configuration files to designated locations with integrity verification
- Script Execution: Run pre/post installation scripts and diagnostic validations
- Workload Composition: Inherit and extend base workloads for DRY configurations
- Detailed Reporting: Comprehensive logs and health reports
2. Technology Analysis
2.1 Evaluation Criteria
| Criterion | Weight | Description |
|---|---|---|
| Windows Integration | 25% | Native Windows APIs, winget interoperability, registry access |
| Maintainability | 20% | Code clarity, testing support, community ecosystem |
| Extensibility | 15% | Plugin architecture, workload composition |
| Dependencies | 15% | Target machine requirements, deployment complexity |
| Performance | 10% | Startup time, execution speed |
| Error Handling | 15% | Exception management, detailed reporting |
2.2 Technology Comparison
2.2.1 PowerShell Scripts
Strengths:
- Native Windows integration; first-class citizen on all modern Windows
- Direct winget command execution with native output parsing
- No additional runtime dependencies
- Excellent registry, file system, and Windows service access
- Built-in remoting capabilities
Weaknesses:
- Complex error handling patterns
- Limited testing frameworks compared to general-purpose languages
- Script organization becomes unwieldy at scale
- Type system limitations for complex data structures
- Inconsistent cross-version behavior (5.1 vs 7+)
Dependency Requirements: None (built into Windows)
Winget Integration: ★★★★★ Excellent - native command execution
Score: 72/100
2.2.2 Python Scripts
Strengths:
- Rich ecosystem with excellent YAML/JSON/TOML parsing libraries
- Strong testing frameworks (pytest, unittest)
- Good readability and maintainability
- Cross-platform potential
- Excellent error handling with exceptions
Weaknesses:
- Requires Python runtime installation on target machines
- Virtual environment complexity for dependencies
- Subprocess management for winget feels indirect
- Windows-specific operations require additional libraries
- Version compatibility issues (3.x versions)
Dependency Requirements: Python 3.8+ runtime, pip packages
Winget Integration: ★★★☆☆ Good - via subprocess
Score: 68/100
2.2.3 C# Script Files (dotnet-script)
Strengths:
- Strong typing with excellent IDE support
- Access to full .NET ecosystem
- Good Windows integration via .NET APIs
- Reasonable startup time for scripts
- NuGet package support
Weaknesses:
- Requires .NET SDK installation
- Less common tooling; smaller community
- Debugging experience inferior to full applications
- Script organization patterns less established
- Version pinning complexity
Dependency Requirements: .NET 6+ SDK, dotnet-script global tool
Winget Integration: ★★★☆☆ Good - via Process class
Score: 65/100
2.2.4 C# Console Application
Strengths:
- Excellent type safety and refactoring support
- Comprehensive .NET ecosystem (DI, logging, configuration)
- Strong testing capabilities (xUnit, NUnit, MSTest)
- Native Windows API access via P/Invoke
- Can produce single-file executables
- Mature error handling with structured exceptions
- Excellent async/await support for parallel operations
Weaknesses:
- Requires .NET runtime (can be self-contained but increases size)
- Longer development cycle than scripting
- Compilation step required
- Larger deployment artifact if self-contained (~60MB+)
Dependency Requirements: .NET 8 runtime (or self-contained)
Winget Integration: ★★★★☆ Very Good - Process class with structured parsing
Score: 82/100
2.2.5 Rust CLI Application
Strengths:
- Zero runtime dependencies; single static binary
- Excellent performance and minimal resource usage
- Strong type system with compile-time guarantees
- Superior error handling with Result<T, E> pattern
- Memory safety without garbage collection
- Excellent CLI frameworks (clap, structopt)
- Fast startup time
Weaknesses:
- Steeper learning curve
- Longer compilation times
- Smaller Windows-specific ecosystem
- Windows API interop more verbose than .NET
- Less familiar to typical Windows administrators
Dependency Requirements: None (static binary)
Winget Integration: ★★★★☆ Very Good - std::process::Command with structured parsing
Score: 85/100
2.3 Comparison Matrix
| Criterion | PowerShell | Python | C# Script | C# App | Rust |
|---|---|---|---|---|---|
| Windows Integration | ★★★★★ | ★★★☆☆ | ★★★★☆ | ★★★★★ | ★★★★☆ |
| Maintainability | ★★★☆☆ | ★★★★☆ | ★★★☆☆ | ★★★★★ | ★★★★☆ |
| Extensibility | ★★★☆☆ | ★★★★☆ | ★★★☆☆ | ★★★★★ | ★★★★★ |
| Dependencies | ★★★★★ | ★★☆☆☆ | ★★☆☆☆ | ★★★★☆ | ★★★★★ |
| Performance | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ | ★★★★☆ | ★★★★★ |
| Error Handling | ★★★☆☆ | ★★★★☆ | ★★★★☆ | ★★★★★ | ★★★★★ |
| Weighted Score | 72 | 68 | 65 | 82 | 85 |
3. Technology Recommendation
3.1 Primary Recommendation: Rust CLI Application
Rationale:
-
Zero Dependencies: The single static binary eliminates runtime requirements, crucial for bootstrapping fresh Windows installations where development tools aren't yet installed.
-
Robust Error Handling: Rust's
Result<T, E>pattern enforces comprehensive error handling at compile time, preventing runtime surprises during critical system configuration. -
Performance: Fast startup (~5ms) and execution makes the tool feel responsive, encouraging frequent health checks.
-
Configuration Parsing: Excellent libraries for YAML (
serde_yaml), TOML (toml), and JSON (serde_json) with strong type safety. -
CLI Excellence: The
clapcrate provides industry-leading CLI parsing with automatic help generation, shell completions, and argument validation. -
Future-Proof: Rust's stability guarantees and backwards compatibility ensure long-term maintainability.
3.2 Configuration Format: YAML
Rationale:
- Human-readable and writable without tooling
- Supports comments for documentation
- Native multi-line string support for inline scripts
- Widely understood by developers and DevOps professionals
- Excellent library support across all evaluated languages
3.3 Secondary Components: PowerShell
For pre/post installation scripts and health check validators, PowerShell remains the optimal choice:
- Native Windows command execution
- No additional dependencies
- Familiar to Windows administrators
- Direct access to Windows-specific features
3.4 Architecture Decision Records (ADR)
| ADR | Decision | Rationale |
|---|---|---|
| ADR-001 | Use Rust for core CLI | Zero runtime deps, robust error handling |
| ADR-002 | Use YAML for workload definitions | Human-readable, supports comments |
| ADR-003 | Use PowerShell for scripts | Native Windows integration |
| ADR-004 | Use SHA-256 for file hashing | Industry standard, good performance |
| ADR-005 | Support workload inheritance | DRY principle, modular configurations |
4. Project Structure
4.1 Repository Layout
anvil/
├── .github/
│ └── workflows/
│ ├── ci.yml # Continuous integration
│ └── release.yml # Release automation
├── docs/
│ ├── SPECIFICATION.md # This document
│ ├── USER_GUIDE.md # End-user documentation
│ ├── WORKLOAD_AUTHORING.md # Guide for creating workloads
│ └── TROUBLESHOOTING.md # Common issues and solutions
├── src/
│ ├── main.rs # Application entry point
│ ├── cli/
│ │ ├── mod.rs # CLI module root
│ │ ├── commands.rs # Command definitions
│ │ └── output.rs # Output formatting (table, JSON)
│ ├── config/
│ │ ├── mod.rs # Configuration module root
│ │ ├── workload.rs # Workload struct definitions
│ │ ├── schema.rs # Schema validation
│ │ └── inheritance.rs # Workload composition logic
│ ├── operations/
│ │ ├── mod.rs # Operations module root
│ │ ├── install.rs # Installation operations
│ │ ├── health.rs # Health check operations
│ │ └── backup.rs # Backup/restore operations
│ ├── providers/
│ │ ├── mod.rs # Providers module root
│ │ ├── winget.rs # Winget package manager
│ │ ├── filesystem.rs # File operations
│ │ └── script.rs # Script execution
│ ├── hashing/
│ │ ├── mod.rs # Hashing module root
│ │ └── sha256.rs # SHA-256 implementation
│ └── reporting/
│ ├── mod.rs # Reporting module root
│ ├── console.rs # Console output
│ ├── json.rs # JSON report generation
│ └── html.rs # HTML report generation
├── workloads/
│ ├── essentials/
│ │ ├── workload.yaml # Core dev tools (VS Code, Git, Terminal) and utilities
│ │ ├── files/
│ │ │ └── user/ # User configuration files
│ │ └── scripts/
│ │ ├── health-check.ps1 # Essentials health validation
│ │ └── configure-sudo.ps1 # Windows sudo configuration
│ ├── rust-developer/
│ │ ├── workload.yaml # Rust toolchain workload
│ │ ├── files/
│ │ │ └── config.toml # Cargo config
│ │ └── scripts/
│ │ ├── post-install.ps1 # Rustup component installation
│ │ └── health-check.ps1 # Rust environment validation
│ └── python-developer/
│ ├── workload.yaml # Python development workload
│ ├── files/
│ │ └── pip.ini # Pip configuration
│ └── scripts/
│ ├── post-install.ps1 # Virtual environment setup
│ └── health-check.ps1 # Python environment validation
├── tests/
│ ├── integration/
│ │ ├── install_tests.rs # Installation integration tests
│ │ └── health_tests.rs # Health check integration tests
│ └── fixtures/
│ └── sample-workload/ # Test workload
├── Cargo.toml # Rust project manifest
├── Cargo.lock # Dependency lock file
├── README.md # Project overview
├── LICENSE # License file
└── .gitignore # Git ignore rules
4.2 Workload Directory Structure
Each workload follows a consistent structure:
<workload-name>/
├── workload.yaml # Workload definition (required)
├── files/ # Files to copy to target system (optional)
│ ├── .config/
│ │ └── app/
│ │ └── settings.json
│ └── .profile
└── scripts/ # Scripts for installation/validation (optional)
├── pre-install.ps1 # Runs before package installation
├── post-install.ps1 # Runs after package installation
└── health-check.ps1 # Validation script for health mode
5. Workload Definition Schema
5.1 Schema Overview (YAML)
# workload.yaml - Full Schema Reference
# Metadata (required)
name: string # Unique workload identifier
version: string # Semantic version (e.g., "1.0.0")
description: string # Human-readable description
# Inheritance (optional)
extends: # List of parent workloads
- string # Parent workload name
# Package Management (optional)
packages:
winget: # Winget package definitions
- id: string # Winget package ID (required)
version: string # Specific version (optional, default: latest)
source: string # Package source (optional, default: winget)
override: string[] # Additional winget arguments (optional)
# File Management (optional)
files:
- source: string # Relative path from workload's files/ directory
destination: string # Absolute path or path with variables
backup: boolean # Backup existing file (optional, default: true)
permissions: string # File permissions (optional, future use)
template: boolean # Process as template (optional, default: false)
# Script Execution (optional)
scripts:
pre_install: # Scripts to run before installation
- path: string # Relative path from workload's scripts/ directory
shell: string # Execution shell (optional, default: "powershell")
elevated: boolean # Require admin privileges (optional, default: false)
timeout: integer # Timeout in seconds (optional, default: 300)
post_install: # Scripts to run after installation
- path: string
shell: string
elevated: boolean
timeout: integer
health_check: # Scripts for health validation
- path: string
shell: string
name: string # Display name for the check
description: string # What this check validates
# Environment Configuration (optional)
environment:
variables: # Environment variables to set
- name: string # Variable name
value: string # Variable value
scope: string # "user" or "machine" (default: "user")
path_additions: # Additions to PATH
- string # Path to add
# Health Check Configuration (optional)
health:
package_check: boolean # Verify packages installed (default: true)
file_check: boolean # Verify files match (default: true)
script_check: boolean # Run health check scripts (default: true)
assertion_check: boolean # Evaluate declarative assertions (default: true)
5.1.1 Assertions and Legacy Health Check Coexistence
Anvil supports two mechanisms for health validation:
| Mechanism | Field | Introduced | Status |
|---|---|---|---|
| Declarative assertions | assertions: | v0.5 | Recommended |
| Health check scripts | scripts.health_check | v0.1 | Deprecated when used with assertions |
Execution order: When both exist, assertions run first, then legacy scripts. Results are merged into a single health report.
Deprecation timeline:
- v0.5: Warning emitted when
scripts.health_checkis used alongsideassertions - v0.6: Warning emitted for any use of
scripts.health_check(even without assertions) - v1.0:
scripts.health_checkremoved; use assertions exclusively
Migration: Convert health check scripts to declarative assertions:
# Before (legacy script)
scripts:
health_check:
- path: check-git.ps1
name: "Git installed"
# After (declarative assertion)
assertions:
- name: Git installed
check:
type: command_exists
command: git
5.1.2 Commands and Legacy Scripts Coexistence
Anvil supports two mechanisms for running actions during installation:
| Mechanism | Field | Introduced | Status |
|---|---|---|---|
| Inline commands | commands.pre_install / commands.post_install | v0.6 | Recommended |
| Script files | scripts.pre_install / scripts.post_install | v0.1 | Deprecated when used with commands |
Execution order: When both exist for the same phase, commands run first, then legacy scripts.
Deprecation timeline:
- v0.6: Warning emitted when
scripts.pre_install/scripts.post_installis used alongsidecommands.pre_install/commands.post_install - v0.8: Warning emitted for any use of
scripts.pre_install/scripts.post_install - v1.0:
scripts.pre_installandscripts.post_installremoved; usecommandsexclusively
Migration: Convert script entries to inline commands:
# Before (legacy script)
scripts:
post_install:
- path: setup.ps1
description: "Install Rust components"
# After (inline command)
commands:
post_install:
- run: rustup default stable
description: "Set stable as default toolchain"
- run: cargo install cargo-watch
description: "Install cargo-watch"
when:
type: command_exists
command: cargo
Note: scripts.health_check follows a separate deprecation path (see §5.1.1).
5.1.3 Command Execution Semantics
Commands defined in commands.pre_install and commands.post_install are executed inline during the install flow.
Execution rules:
- Commands run sequentially in the order defined
- Default behavior: stop on first failure (non-zero exit code)
continue_on_error: trueoverrides this per-command- Commands with
when:conditions are evaluated before execution; if the condition fails, the command is skipped
Output handling:
- stdout and stderr are captured and included in reports
- In verbose mode (
-v), output is streamed in real-time - In quiet mode (
-q), output is suppressed unless the command fails
Timeout behavior:
- Default timeout: 300 seconds
- On timeout: process is terminated (SIGTERM on Unix, TerminateProcess on Windows)
- Timed-out commands are reported as failures
Elevated commands:
- Commands with
elevated: truerequire admin privileges - On Windows: validated via
whoami /privor equivalent before attempting - If elevation is unavailable, the command fails with a clear error message
Exit codes:
- Exit code 0 = success
- Any non-zero exit code = failure (unless
continue_on_error: true)
5.2 Example Workloads
5.2.1 Essentials Workload
# workloads/essentials/workload.yaml
name: essentials
version: "2.0.0"
description: "Core development tools and productivity utilities for everyday Windows use"
packages:
winget:
- id: Git.Git
override:
- --override
- '/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS="icons,ext\reg\shellhere,assoc,assoc_sh"'
- id: Microsoft.VisualStudioCode
override:
- --override
- '/VERYSILENT /NORESTART /MERGETASKS=!runcode,addcontextmenufiles,addcontextmenufolders,addtopath'
- id: Microsoft.WindowsTerminal
- id: JanDeDobbeleer.OhMyPosh
- id: GitHub.cli
files:
- source: user/.gitconfig
destination: "~/.gitconfig"
backup: true
- source: user/.config/pwsh
destination: "~/.config/pwsh"
backup: true
scripts:
post_install:
- path: post-install.ps1
shell: powershell
- path: configure-sudo.ps1
shell: powershell
elevated: true
health_check:
- path: health-check.ps1
name: "essentials Health Check"
description: "Verifies essentials is properly configured"
- path: verify-sudo.ps1
name: "Windows Sudo"
description: "Verifies Windows sudo is enabled and set to inline mode"
5.2.2 Rust Developer Workload
# workloads/rust-developer/workload.yaml
name: rust-developer
version: "1.0.0"
description: "Complete Rust development environment"
extends:
- essentials
packages:
winget:
- id: Rustlang.Rustup
- id: Microsoft.VisualStudio.2022.BuildTools
override:
- --override
- '--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended'
- id: LLVM.LLVM
files:
- source: config.toml
destination: "~/.cargo/config.toml"
backup: true
- source: rustfmt.toml
destination: "~/rustfmt.toml"
backup: false
scripts:
post_install:
- path: post-install.ps1
shell: powershell
description: "Install Rust components and common tools"
timeout: 600
health_check:
- path: health-check.ps1
name: "Rust Toolchain"
description: "Verifies rustc, cargo, and components are properly installed"
environment:
path_additions:
- "~/.cargo/bin"
5.2.3 Python Developer Workload
# workloads/python-developer/workload.yaml
name: python-developer
version: "1.0.0"
description: "Python development environment with common tools"
extends:
- essentials
packages:
winget:
- id: Python.Python.3.12
- id: astral-sh.uv
files:
- source: pip.ini
destination: "~/AppData/Roaming/pip/pip.ini"
backup: true
scripts:
post_install:
- path: post-install.ps1
shell: powershell
description: "Configure Python environment and install global tools"
health_check:
- path: health-check.ps1
name: "Python Environment"
description: "Verifies Python, pip, and uv are properly configured"
environment:
variables:
- name: PYTHONDONTWRITEBYTECODE
value: "1"
scope: user
5.3 Variable Expansion
Anvil supports variable expansion in destination paths and template files:
| Variable | Expansion |
|---|---|
~ | User's home directory (%USERPROFILE%) |
${HOME} | User's home directory |
${APPDATA} | Application data directory |
${LOCALAPPDATA} | Local application data |
${PROGRAMFILES} | Program Files directory |
${PROGRAMFILES_X86} | Program Files (x86) directory |
${ANVIL_WORKLOAD} | Current workload name |
${ANVIL_VERSION} | Anvil version |
6. CLI Interface Design
6.1 Command Overview
anvil <COMMAND> [OPTIONS]
Commands:
install Apply a workload configuration to the system
health Check system health against a workload definition
list List available workloads
show Display detailed workload information
validate Validate workload definition syntax
init Initialize a new workload template
backup Backup current system state
restore Restore from a backup
completions Generate shell completions
help Print help information
Options:
-v, --verbose Increase output verbosity (can be repeated: -vvv)
-q, --quiet Suppress non-essential output
-c, --config <PATH> Use custom configuration file
--no-color Disable colored output
-h, --help Print help
-V, --version Print version
6.2 Command Details
6.2.1 Install Command
anvil install [OPTIONS] <WORKLOAD>
Apply a workload configuration to the system
Arguments:
<WORKLOAD> Name of the workload to install (or path to workload.yaml)
Options:
-d, --dry-run Show what would be done without making changes
-f, --force Skip confirmation prompts
-p, --packages-only Only install packages, skip files and scripts
--skip-packages Skip package installation
--skip-files Skip file operations
--skip-scripts Skip script execution
--skip-pre-scripts Skip pre-installation scripts
--skip-post-scripts Skip post-installation scripts
--no-backup Don't backup existing files
-j, --jobs <N> Number of parallel package installations (default: 4)
--timeout <SECONDS> Global timeout for operations (default: 3600)
Examples:
anvil install rust-developer
anvil install ./custom-workload/workload.yaml
anvil install python-developer --dry-run
anvil install essentials --packages-only
6.2.2 Health Command
anvil health [OPTIONS] <WORKLOAD>
Check system health against a workload definition
Arguments:
<WORKLOAD> Name of the workload to check against
Options:
-o, --output <FORMAT> Output format: table, json, yaml, html (default: table)
-f, --file <PATH> Write report to file instead of stdout
--fail-fast Stop on first failure
--packages-only Only check packages
--files-only Only check files
--scripts-only Only run health check scripts
-s, --strict Treat warnings as errors
Exit Codes:
0 All checks passed
1 One or more checks failed
2 Configuration or runtime error
Examples:
anvil health rust-developer
anvil health python-developer --output json --file report.json
anvil health essentials --fail-fast
6.2.3 List Command
anvil list [OPTIONS]
List available workloads
Options:
-a, --all Include built-in and custom workloads
-l, --long Show detailed information
--path <PATH> Search for workloads in additional path
-o, --output <FORMAT> Output format: table, json, yaml (default: table)
Examples:
anvil list
anvil list --long
anvil list --output json
6.2.4 Show Command
anvil show <WORKLOAD>
Display detailed workload information
Arguments:
<WORKLOAD> Name of the workload to display
Options:
-r, --resolved Show resolved configuration (with inheritance applied)
-o, --output <FORMAT> Output format: yaml, json (default: yaml)
Examples:
anvil show rust-developer
anvil show python-developer --resolved
6.2.5 Validate Command
anvil validate [OPTIONS] <PATH>
Validate workload definition syntax
Arguments:
<PATH> Path to workload.yaml file or workload directory
Options:
--strict Enable strict validation mode
--schema Output JSON schema for workload definitions
Examples:
anvil validate workloads/rust-developer/
anvil validate ./custom-workload/workload.yaml --strict
6.2.6 Init Command
anvil init [OPTIONS] <NAME>
Initialize a new workload template
Arguments:
<NAME> Name for the new workload
Options:
-t, --template <NAME> Base template: minimal, standard, full (default: standard)
-e, --extends <PARENT> Parent workload to extend
-o, --output <PATH> Output directory (default: ./workloads/<NAME>)
Examples:
anvil init my-workload
anvil init frontend-dev --extends essentials
anvil init minimal-setup --template minimal
6.3 Output Formats
6.3.1 Table Format (Default)
$ anvil health rust-developer
╭─────────────────────────────────────────────────────────────────────╮
│ Anvil Health Check Report │
│ Workload: rust-developer │
├─────────────────────────────────────────────────────────────────────┤
│ Component │ Status │ Details │
├────────────────────┼─────────┼──────────────────────────────────────┤
│ Packages │
├────────────────────┼─────────┼──────────────────────────────────────┤
│ Git.Git │ ✓ OK │ 2.43.0 installed │
│ Microsoft.VSCode │ ✓ OK │ 1.85.0 installed │
│ Rustlang.Rustup │ ✓ OK │ 1.26.0 installed │
│ LLVM.LLVM │ ✗ FAIL │ Not installed │
├────────────────────┼─────────┼──────────────────────────────────────┤
│ Files │
├────────────────────┼─────────┼──────────────────────────────────────┤
│ ~/.cargo/config │ ✓ OK │ Hash matches │
│ ~/.gitconfig │ ⚠ WARN │ Modified locally │
├────────────────────┼─────────┼──────────────────────────────────────┤
│ Health Scripts │
├────────────────────┼─────────┼──────────────────────────────────────┤
│ Rust Toolchain │ ✓ OK │ All components verified │
│ Git Configuration │ ✓ OK │ User configured │
╰─────────────────────────────────────────────────────────────────────╯
Summary: 6 passed, 1 failed, 1 warning
6.3.2 JSON Format
{
"workload": "rust-developer",
"timestamp": "2025-01-15T10:30:00Z",
"overall_status": "FAILED",
"summary": {
"total": 8,
"passed": 6,
"failed": 1,
"warnings": 1
},
"checks": {
"packages": [
{
"id": "Git.Git",
"status": "OK",
"expected_version": null,
"installed_version": "2.43.0"
},
{
"id": "LLVM.LLVM",
"status": "FAILED",
"expected_version": null,
"installed_version": null,
"error": "Package not installed"
}
],
"files": [
{
"source": ".cargo/config.toml",
"destination": "C:\\Users\\dev\\.cargo\\config.toml",
"status": "OK",
"expected_hash": "sha256:abc123...",
"actual_hash": "sha256:abc123..."
}
],
"scripts": [
{
"name": "Rust Toolchain",
"status": "OK",
"output": "rustc 1.75.0, cargo 1.75.0"
}
]
}
}
7. Core Components
7.1 Component Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │
│ │ install │ │ health │ │ list │ │ show │ │ validate │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └──────┬──────┘ │
└───────┼──────────┼────────────┼──────────┼─────────────┼───────────┘
│ │ │ │ │
┌───────┴──────────┴────────────┴──────────┴─────────────┴───────────┐
│ Operations Layer │
│ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐ │
│ │ InstallOperation │ │ HealthOperation │ │ BackupOperation│ │
│ └────────┬─────────┘ └────────┬─────────┘ └───────┬────────┘ │
└───────────┼─────────────────────┼────────────────────┼─────────────┘
│ │ │
┌───────────┴─────────────────────┴────────────────────┴─────────────┐
│ Providers Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ WingetProvider │ │ FilesystemProv. │ │ ScriptProvider │ │
│ │ │ │ │ │ │ │
│ │ - install() │ │ - copy() │ │ - execute() │ │
│ │ - uninstall() │ │ - compare() │ │ - validate() │ │
│ │ - list() │ │ - backup() │ │ │ │
│ │ - search() │ │ - restore() │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
│ │ │
┌───────────┴─────────────────────┴────────────────────┴─────────────┐
│ Config Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ WorkloadParser │ │ SchemaValidator │ │ InheritanceRes. │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
7.2 Winget Provider
The Winget Provider interfaces with Windows Package Manager:
Key Functions:
| Function | Description |
|---|---|
install(package) | Install a package with optional version/overrides |
uninstall(package) | Remove an installed package |
is_installed(package) | Check if a package is installed |
get_version(package) | Get installed version of a package |
list_installed() | List all installed packages |
search(query) | Search for packages |
Error Handling:
#![allow(unused)] fn main() { pub enum WingetError { PackageNotFound(String), InstallationFailed { package: String, exit_code: i32, stderr: String }, VersionMismatch { package: String, expected: String, actual: String }, NetworkError(String), AccessDenied(String), Timeout { package: String, timeout_seconds: u64 }, } }
7.3 Filesystem Provider
Handles file operations with integrity verification:
Key Functions:
| Function | Description |
|---|---|
copy_file(src, dest) | Copy file with optional backup |
compute_hash(path) | Calculate SHA-256 hash |
compare_files(a, b) | Compare files by hash |
backup_file(path) | Create timestamped backup |
restore_file(backup, dest) | Restore from backup |
expand_path(path) | Expand variables in path |
Hash Format:
sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
7.4 Script Provider
Executes PowerShell scripts with proper error handling:
Key Functions:
| Function | Description |
|---|---|
execute(script, config) | Run a script with configuration |
validate_syntax(script) | Check script for syntax errors |
run_elevated(script) | Execute with admin privileges |
Script Execution Model:
#![allow(unused)] fn main() { pub struct ScriptConfig { pub path: PathBuf, pub shell: Shell, // PowerShell, Cmd, Bash (WSL) pub elevated: bool, pub timeout: Duration, pub working_dir: Option<PathBuf>, pub environment: HashMap<String, String>, } pub struct ScriptResult { pub exit_code: i32, pub stdout: String, pub stderr: String, pub duration: Duration, pub success: bool, } }
7.5 Inheritance Resolution
Workloads can extend other workloads with the following merge rules:
| Field | Merge Strategy |
|---|---|
name | Child overwrites |
version | Child overwrites |
description | Child overwrites |
packages.winget | Append (child packages added after parent) |
files | Append (child files added, same destinations overwritten) |
scripts.pre_install | Parent first, then child |
scripts.post_install | Parent first, then child |
scripts.health_check | Combine all |
environment.variables | Child overwrites same-named variables |
environment.path_additions | Append |
Circular Dependency Detection:
The inheritance resolver maintains a visited set to detect and reject circular dependencies:
A extends B extends C extends A → ERROR: Circular dependency detected
8. Roadmap
Phases 1–6 of the original implementation are complete. This section describes the forward-looking roadmap for Anvil, organized by milestone.
8.1 Overview
v0.4 — Declarative Assertions ✅ Implemented
│
├── Reusable condition/predicate engine
├── assertions: YAML schema
├── Integration with health command
└── Backward compatibility with scripts.health_check
v0.5 — Package Manager Abstraction ✅ Schema implemented
│
├── PackageManager trait ⬚ Pending
├── WingetProvider adapter ⬚ Pending
└── Schema design for multi-manager ✅ Implemented (winget/brew/apt)
v0.6 — Commands Block ✅ Implemented
│
├── commands: YAML schema
├── Conditional execution (when:)
├── Failure semantics
└── Backward compatibility with scripts.post_install
v0.7 — Workload Discovery & Separation ✅ Implemented
│
├── Wire search_paths through ConfigManager
├── Search precedence and conflict resolution
└── Private workload repository pattern
v1.0 — Polish & Distribution
│
├── crates.io publishing ⬚ Pending
├── Cross-platform CI ✅ Implemented (Windows/Linux/macOS matrix)
├── Shell completions ✅ Implemented (bash/zsh/fish/powershell/elvish)
├── Remove scripts.health_check (use assertions exclusively)
└── Documentation review
8.2 v0.4 — Declarative Assertions ✅
Status: Implemented. Modules:
src/conditions/,src/assertions/. Workload struct includesassertions: Option<Vec<Assertion>>. Health command evaluates assertions alongside legacyscripts.health_check.
Goal: Replace ~70% of PowerShell health-check scripts with declarative YAML assertions, evaluated natively in Rust.
Before (current):
scripts:
health_check:
- path: health-check/check-rust.ps1
name: "Rust Toolchain"
Plus a 70-line PowerShell script.
After (proposed):
assertions:
- name: "Rust compiler"
check:
type: command_exists
command: rustc
min_version: "1.70"
- name: "Cargo config"
check:
type: file_exists
path: "~/.cargo/config.toml"
- name: "RUST_BACKTRACE set"
check:
type: env_var
name: RUST_BACKTRACE
value: "1"
Assertion types:
| Type | Parameters | Purpose |
|---|---|---|
command_exists | command, min_version? | Verify a CLI tool is installed |
file_exists | path | Verify a file exists |
dir_exists | path | Verify a directory exists |
env_var | name, value? | Verify an environment variable is set |
path_contains | entry | Verify PATH includes an entry |
json_property | path, property, value | Verify a JSON file property |
registry_value | key, name, value | Verify a Windows registry value |
shell | command, expected_exit_code? | Escape hatch — run a shell command |
all_of | checks: [...] | All sub-checks must pass (AND) |
any_of | checks: [...] | At least one sub-check must pass (OR) |
Implementation:
- New module:
src/conditions/— shared predicate engine reused by assertions and futurecommands.when - New module:
src/assertions/—Assertionenum (tagged serde),evaluate()function - Update
src/config/workload.rs— addassertionsfield toWorkload - Update
src/operations/health.rs— evaluate assertions alongside legacyscripts.health_check - Backward compatible — existing
scripts.health_checkcontinues to work
8.3 v0.5 — Package Manager Abstraction (Partial)
Status: Multi-manager schema implemented (
Packagesstruct withwinget,brew,aptfields;BrewPackageandAptPackagetypes defined). ThePackageManagertrait and non-winget provider implementations are still pending.
Goal: Introduce a PackageManager trait so the install flow is decoupled from winget, enabling future cross-platform support.
Proposed trait:
#![allow(unused)] fn main() { pub trait PackageManager: Send + Sync { fn name(&self) -> &str; fn is_available(&self) -> bool; fn install(&self, package: &PackageSpec) -> Result<InstallResult>; fn is_installed(&self, package_id: &str) -> Result<Option<String>>; fn list_installed(&self) -> Result<Vec<InstalledPackage>>; } }
Schema direction:
packages:
winget:
- id: Microsoft.VisualStudioCode
brew:
- id: visual-studio-code
apt:
- id: code
Anvil selects the available manager for the current platform. The v0.5 milestone implements the trait and refactors WingetProvider — other manager implementations are deferred.
8.4 v0.6 — Commands Block ✅
Status: Implemented. Module:
src/commands/. Workload struct includescommands: Option<CommandBlock>withpre_installandpost_installphases.CommandEntrysupportsrun,description,timeout,elevated,when(condition), andcontinue_on_error. Execution engine incommands::execute_commands().
Goal: Replace post-install PowerShell scripts with inline commands that support conditional execution.
Proposed schema:
commands:
post_install:
- run: rustup default stable
description: "Set stable as default toolchain"
- run: cargo install cargo-watch
timeout: 900
- run: code --install-extension rust-lang.rust-analyzer
when:
command_exists: code
Failure semantics:
- Default: stop on first failure
continue_on_error: trueto continue- Non-zero exit codes are errors unless
expected_exit_codeis set - Commands produce structured output for reporting
The when: condition reuses the predicate engine from v0.4.
8.5 v0.7 — Workload Discovery & Separation ✅
Status: Implemented.
ConfigManagerloads user-configured search paths fromGlobalConfig.workloads.pathsand merges them with default paths.add_search_path()deduplicates entries. Search precedence matches the design below.
Goal: Support multiple workload directories so private workloads live outside the main repo.
Search precedence:
- Explicit path argument (highest priority)
- User-configured search paths (
~/.anvil/config.yaml) - Default paths (exe-relative, LOCALAPPDATA, cwd)
Duplicate workload names produce a warning with the resolved path shown.
8.6 v1.0 — Polish & Distribution
Goal: Stable release with cross-platform CI and crates.io publishing.
- Decide crate name (
anvilis taken on crates.io — candidates:anvil-dev,devsmith,forgekit) Cross-compilation CI for Windows, Linux, macOS✅ Implemented (matrix build inci.yml)Shell completions✅ Implemented (bash, zsh, fish, powershell, elvish viacli/completions.rs)- Comprehensive documentation review
- First stable release
9. Appendices
9.1 Sample Health Check Script
# health-check.ps1 - Rust Developer Workload
$ErrorActionPreference = "Stop"
$exitCode = 0
function Test-Command {
param([string]$Command, [string]$Name)
try {
$null = Get-Command $Command -ErrorAction Stop
Write-Host "✓ $Name is available" -ForegroundColor Green
return $true
}
catch {
Write-Host "✗ $Name is not available" -ForegroundColor Red
return $false
}
}
function Test-RustComponent {
param([string]$Component)
$installed = rustup component list --installed 2>&1
if ($installed -match $Component) {
Write-Host "✓ Rust component '$Component' is installed" -ForegroundColor Green
return $true
}
else {
Write-Host "✗ Rust component '$Component' is missing" -ForegroundColor Red
return $false
}
}
# Check core tools
if (-not (Test-Command "rustc" "Rust Compiler")) { $exitCode = 1 }
if (-not (Test-Command "cargo" "Cargo")) { $exitCode = 1 }
if (-not (Test-Command "rustup" "Rustup")) { $exitCode = 1 }
# Check Rust version
$rustVersion = rustc --version
Write-Host "Rust version: $rustVersion"
# Check essential components
if (-not (Test-RustComponent "rust-src")) { $exitCode = 1 }
if (-not (Test-RustComponent "rust-analyzer")) { $exitCode = 1 }
if (-not (Test-RustComponent "clippy")) { $exitCode = 1 }
if (-not (Test-RustComponent "rustfmt")) { $exitCode = 1 }
# Check cargo tools
if (-not (Test-Command "cargo-watch" "cargo-watch")) {
Write-Host "⚠ cargo-watch not installed (optional)" -ForegroundColor Yellow
}
exit $exitCode
9.2 Sample Post-Install Script
# post-install.ps1 - Rust Developer Workload
$ErrorActionPreference = "Stop"
Write-Host "Configuring Rust development environment..." -ForegroundColor Cyan
# Update Rust toolchain
Write-Host "Updating Rust toolchain..."
rustup update
# Install stable toolchain
Write-Host "Installing stable toolchain..."
rustup default stable
# Install essential components
Write-Host "Installing Rust components..."
rustup component add rust-src
rustup component add rust-analyzer
rustup component add clippy
rustup component add rustfmt
# Install common cargo tools
Write-Host "Installing cargo tools..."
cargo install cargo-watch
cargo install cargo-edit
cargo install cargo-expand
# Configure VS Code (if installed)
$vscodePath = Get-Command code -ErrorAction SilentlyContinue
if ($vscodePath) {
Write-Host "Installing VS Code extensions for Rust..."
code --install-extension rust-lang.rust-analyzer
code --install-extension vadimcn.vscode-lldb
code --install-extension serayuzgur.crates
}
Write-Host "Rust development environment configured successfully!" -ForegroundColor Green
9.3 Error Codes Reference
| Code | Name | Description |
|---|---|---|
| 0 | SUCCESS | Operation completed successfully |
| 1 | HEALTH_CHECK_FAILED | One or more health checks failed |
| 2 | CONFIG_ERROR | Configuration file error |
| 3 | WORKLOAD_NOT_FOUND | Specified workload does not exist |
| 4 | WINGET_ERROR | Winget operation failed |
| 5 | FILE_ERROR | File operation failed |
| 6 | SCRIPT_ERROR | Script execution failed |
| 7 | PERMISSION_ERROR | Insufficient permissions |
| 8 | NETWORK_ERROR | Network connectivity issue |
| 9 | TIMEOUT | Operation timed out |
| 10 | CIRCULAR_DEPENDENCY | Circular workload inheritance |
9.4 Glossary
| Term | Definition |
|---|---|
| Workload | A named configuration bundle defining packages, files, and scripts |
| Health Check | Validation of system state against a workload definition |
| Provider | Component that interfaces with external systems (winget, filesystem, scripts) |
| Inheritance | Mechanism for workloads to extend and build upon other workloads |
| Variable Expansion | Replacement of placeholders like ~ or ${HOME} with actual paths |
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 2.0.0 | 2026-04-16 | Anvil Team | Rename to Anvil, updated roadmap (v0.4–v1.0) |
| 1.0.0 | 2025-01-15 | Anvil Team | Initial specification |
This specification serves as the authoritative reference for implementing Anvil. Subsequent implementation prompts should reference this document for architecture decisions, schemas, and interfaces.
Architecture
This document describes Anvil's internal architecture for contributors. For user-facing documentation, see the User Guide. For the project vision and roadmap, see the Specification.
Overview
Anvil is a single-binary Rust CLI. The binary parses commands, loads YAML workload definitions, delegates work to providers, and tracks state on disk.
main.rs → cli/ → operations/ → providers/
↓ ↓
config/ state/
↓
conditions/ → assertions/
↓
commands/
| Layer | Role |
|---|---|
| cli/ | Command parsing (clap), output formatting (table/JSON/YAML/HTML), progress bars |
| config/ | Workload YAML loading, schema validation, inheritance resolution, global config |
| operations/ | One module per CLI command — each exposes execute(args, cli) → Result<()> |
| providers/ | External system integrations: winget, filesystem, scripts, templates, backups |
| state/ | Tracks installation records, file hashes, and package cache at ~/.anvil/ |
| conditions/ | Composable predicate engine: command existence, file/dir checks, env vars, registry, shell commands |
| assertions/ | Evaluates named assertions (backed by conditions) for health reporting |
| commands/ | Inline command execution with conditional execution, timeouts, and structured results |
Data Flow: anvil install <workload>
1. CLI parsing cli/mod.rs → InstallArgs
2. Workload discovery config/mod.rs → finds workload.yaml on search paths
3. YAML loading config/mod.rs → Workload struct (serde)
4. Inheritance config/inheritance.rs → resolved Workload (parents merged)
5. Variable expansion config/mod.rs → ~ and ${VAR} expanded
6. Plan & confirm operations/install.rs → dry-run plan, user confirmation
7. Package install providers/winget.rs → winget install per package
8. File copy providers/filesystem.rs → copy with backup and hash
9. Script execution providers/script.rs → PowerShell pre/post scripts
10. State persistence state/ → JSON files at ~/.anvil/state/
Key Types
Workload Schema (config/workload.rs)
#![allow(unused)] fn main() { pub struct Workload { pub name: String, pub version: String, pub description: String, pub extends: Option<Vec<String>>, pub packages: Option<Packages>, pub files: Option<Vec<FileEntry>>, pub scripts: Option<Scripts>, pub commands: Option<CommandBlock>, pub environment: Option<Environment>, pub health: Option<HealthConfig>, pub assertions: Option<Vec<Assertion>>, } }
The Workload struct is the central data type — deserialized from YAML via serde. All fields except name, version, and description are optional. The commands and assertions fields were added in v0.5–v0.6 alongside the conditions/, assertions/, and commands/ modules.
CLI (cli/mod.rs, cli/commands.rs)
10 top-level commands: install, health, list, show, validate, init, status, completions, backup, config.
The Cli struct (clap-derived) holds global flags (--verbose, --quiet, --no-color, --config). Each command has its own args struct.
Providers (providers/)
Providers wrap external systems. Each provider is a struct with methods — there is no shared trait yet (see roadmap).
| Provider | Responsibility |
|---|---|
WingetProvider | Package install/upgrade/query via winget.exe |
FilesystemProvider | File copy with backup, hash verification, glob expansion |
ScriptProvider | PowerShell script execution with timeout and output capture |
TemplateProcessor | Handlebars template rendering for config files |
BackupManager | System state backup and restore |
State (state/)
All state is persisted as JSON under ~/.anvil/:
~/.anvil/
├── state/
│ ├── <workload>.json # InstallationState per workload
│ └── files.json # FileStateIndex (all tracked files)
├── cache/
│ └── packages.json # PackageCache (winget query results)
└── config.yaml # GlobalConfig (user preferences)
| Type | File | Purpose |
|---|---|---|
InstallationState | state/<workload>.json | Package install records and status |
FileStateManager | state/files.json | File hashes for drift detection |
PackageCache | cache/packages.json | Cached winget query results |
GlobalConfig | config.yaml | User settings and search paths |
Config System
Workload Discovery
ConfigManager searches for workloads in this order:
- Direct path (if an absolute or relative path is given)
<exe_dir>/workloads/<name>/workload.yaml%LOCALAPPDATA%/anvil/workloads/<name>/workload.yaml./workloads/<name>/workload.yaml
Within each search path, it tries: <name>/workload.yaml, <name>/workload.yml, <name>.yaml.
Inheritance
Workloads can extend other workloads via extends: [parent]. Resolution in config/inheritance.rs:
- Build dependency graph from all
extendsreferences - Detect cycles (error) and enforce max depth of 10
- Topological sort determines merge order (parents first)
- Merge strategy:
- Packages: append, child overrides same ID
- Files: append, child overrides same destination
- Scripts: concatenate (parent first, child after)
- Environment variables: child overrides same name
- Path additions: append unique entries
Variable Expansion
Workload values support variable expansion:
| Variable | Expands to |
|---|---|
~ | $env:USERPROFILE |
${HOME} | $env:USERPROFILE |
${ANVIL_WORKLOAD} | Current workload name |
${ANVIL_VERSION} | Anvil version |
${ANVIL_WORKLOAD_PATH} | Path to workload directory |
${ENV_NAME} | Any environment variable |
Error Handling
Two-tier approach:
thiserrorfor domain-specific error enums in each module:WingetError,FilesystemError,ScriptError,BackupError,TemplateErrorInheritanceError(includessuggestion()for user-friendly hints)FileStateErrorProviderError(wraps all provider errors)CommandError,ConditionError
anyhowfor error propagation in operations and CLI code
Pattern: domain errors are created with thiserror and converted to anyhow::Error at operation boundaries using .with_context(|| ...).
Testing
Unit Tests
Inline #[cfg(test)] mod tests in the same file as the code under test. 270 unit tests covering providers, config parsing, inheritance, state management, conditions, commands, and formatting.
Integration Tests
tests/cli_tests.rs uses assert_cmd + predicates for end-to-end CLI testing. 85 integration tests covering all commands with fixture workloads.
tests/common/mod.rs provides test fixture helpers:
create_test_workload()— minimal valid workloadcreate_inherited_workload()— parent + child workloadscreate_invalid_workload()— malformed YAMLcreate_circular_workloads()— cycle detection fixturescreate_full_workload()— workload with all featurescreate_template_workload()— workload with template files
Running Tests
cargo test # All tests (355 total)
cargo test --bin anvil # Unit tests only (270)
cargo test --test cli_tests # Integration tests only (85)
cargo test test_name # Single test by name
Output Formats
All commands that produce output support --format:
| Format | Module | Description |
|---|---|---|
table | cli/formats/table.rs | Human-readable terminal tables (default) |
json | cli/formats/json.rs | Machine-readable JSON |
yaml | cli/formats/yaml.rs | YAML output |
html | cli/formats/html.rs | Standalone HTML reports |
CI Pipeline
.github/workflows/ci.yml runs on every push/PR across Windows, Linux, and macOS (matrix build):
cargo fmt --all -- --checkcargo clippy --all-targets --all-features -- -D warningscargo test --bin anvil(unit tests)cargo test --test cli_tests(integration tests)cargo build --release(Windows only)
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Added
- Declarative assertions for workload health validation (
assertions:field in workload YAML) - Condition engine with 9 predicate types:
command_exists,file_exists,dir_exists,env_var,path_contains,registry_value,shell, plusall_of/any_ofcomposition --assertions-onlyflag foranvil healthto run only assertion checksassertion_checktoggle in workload health configuration- Assertion examples in
anvil init --template fullscaffold - Multi-manager workload schema:
packages.brew(Homebrew) andpackages.apt(APT) fields alongside existingpackages.winget - Platform-aware validation warns when workload references an unavailable package manager
- Inline
commands:block for workload command execution withpre_installandpost_installphases - Conditional command execution via
when:field using the predicate engine continue_on_erroroption for commands that should not block the install flow- Configurable workload search paths via
~/.anvil/config.yaml(user paths prepended to defaults) - Search precedence with conflict resolution: explicit path > user-configured > defaults; first match wins
anvil list --all-pathsto show all discovered paths including shadowed duplicates- Cross-platform release builds for Linux (x86_64, aarch64), macOS (x86_64, aarch64), and Windows (x86_64)
Changed
- Crate renamed to
anvil-devfor crates.io publishing (binary name staysanvil; install viacargo install anvil-dev)
Deprecated
— removed in this release (see Removed)scripts.health_checkwhen used alongsideassertionsscripts.pre_installandscripts.post_installwhen used alongsidecommands(migrate to inline commands; removal planned for v1.0)
Removed
scripts.health_checkfield — use declarativeassertionsinstead--scripts-onlyand--scriptflags fromanvil healthcommandscript_checkfield fromhealthconfiguration
0.5.0- 2026-04-17
This is the first release from the new repository home at kafkade/anvil. It marks a fresh start with modernized CI/CD, updated documentation, and a clear cross-platform direction. Prior changelog entries (v0.1.0–v0.3.1) are preserved below for historical context.
Added
- Architecture reference document for contributors (
docs/ARCHITECTURE.md) - Automated releases from CHANGELOG.md with SHA256 checksums
- Consolidated CI pipeline with formatting, linting, and testing in a single gate
Changed
- Project rebranded to "Declarative Workstation Configuration Management" to reflect cross-platform direction
- Streamlined CI from 3 separate jobs to a single
Validategate plus release build - Release workflow aligned with org-wide pattern (changelog-driven notes, semver pre-release detection)
- Resolved all clippy warnings and formatting issues across the codebase
0.3.1 - 2026-01-10
Added
- Comprehensive user documentation (USER_GUIDE.md)
- Workload authoring guide (WORKLOAD_AUTHORING.md)
- Troubleshooting guide (TROUBLESHOOTING.md)
- Integration test suite for CLI commands
- GitHub Actions CI/CD workflows
- Shell completion generation for PowerShell, Bash, Zsh, and Fish
- CONTRIBUTING.md with contribution guidelines
Changed
- Improved error messages throughout CLI
- Enhanced validation reporting with detailed messages
- Updated README with badges and clearer instructions
Fixed
- Various documentation typos and inconsistencies
0.3.0 - 2026-01-08
Added
- Shell completions command for multiple shells
- Global configuration management (
configcommand) - Backup management with create, list, show, restore, clean, and verify subcommands
- Status command to show installation state
- HTML output format for health reports
--strictflag for validation command- Environment variable configuration in workloads
- PATH additions support in workloads
Changed
- Improved inheritance resolution algorithm
- Enhanced output formatting for all commands
- Better progress indicators during installation
0.2.0 - 2026-01-05
Added
- Script execution support with PowerShell and CMD
- Pre-install and post-install script hooks
- Health check script execution
- Elevated privilege handling for scripts
- Configurable script timeouts
- Template processing with Handlebars
- File integrity verification with SHA-256 checksums
- Automatic backup creation before file overwrites
- Variable expansion in paths (
~,${HOME}, etc.)
Changed
- Improved file operation error handling
- Enhanced dry-run output with detailed plan
Fixed
- Path expansion on Windows with backslashes
- File permission handling on Windows
0.1.0 - 2026-01-01
Added
- Initial release of Anvil
- Core CLI commands: install, health, list, show, validate, init
- Package management via winget integration
- Install packages with version pinning
- Custom winget arguments support
- Package health verification
- File operations
- Copy files to target locations
- Path variable expansion
- Workload system
- YAML-based workload definitions
- Workload inheritance and composition
- Circular dependency detection
- Multiple output formats
- Table (default, human-readable)
- JSON (machine-readable)
- YAML
- Bundled workloads
- essentials: Core development tools (VS Code, Git, Windows Terminal, Oh My Posh) and productivity utilities
- rust-developer: Rust toolchain with cargo tools (extends essentials)
- python-developer: Python with uv package manager (extends essentials)
Documentation
- Initial specification document
- Phase development prompts
Contributing
Thank you for your interest in contributing to Anvil! This document provides guidelines and instructions for contributing to the project.
Code of Conduct
This project follows the Contributor Covenant Code of Conduct. Please be respectful and constructive in all interactions.
How to Contribute
Reporting Bugs
Before reporting a bug:
- Check existing issues to avoid duplicates
- Gather relevant information:
- Anvil version (
anvil --version) - Windows version
- Steps to reproduce
- Expected vs actual behavior
- Verbose output (
anvil -vvv <command>)
- Anvil version (
Create a bug report with this template:
## Environment
- Anvil version:
- Windows version:
- PowerShell version:
## Description
Brief description of the bug.
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
What you expected to happen.
## Actual Behavior
What actually happened.
## Verbose Output
(paste -vvv output here)
## Workload (if applicable)
```yaml
(paste workload.yaml here)
### Suggesting Features
1. Check existing issues and discussions for similar suggestions
2. Open a feature request issue with:
- Clear description of the feature
- Use case and motivation
- Proposed implementation (if you have ideas)
### Submitting Code
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Run tests (`cargo test`)
5. Run lints (`cargo clippy`)
6. Format code (`cargo fmt`)
7. Commit with a descriptive message
8. Push to your fork
9. Open a Pull Request
---
## Development Setup
### Prerequisites
- **Rust**: 1.75 or later ([rustup.rs](https://rustup.rs/))
- **Windows**: Visual Studio Build Tools (for linking)
- **Windows Package Manager (winget)**: For package operations (integration tests)
- **Linux/macOS**: Standard build toolchain (gcc/clang)
### Building
```sh
# Clone your fork
git clone https://github.com/YOUR_USERNAME/anvil.git
cd anvil
# Build debug version
cargo build
# Build release version
cargo build --release
# Run tests
cargo test
# Run clippy lints
cargo clippy --all-targets --all-features -- -D warnings
# Format code
cargo fmt
# Run with debug output
cargo run -- -vvv list
Running Tests
# Run all tests
cargo test
# Run unit tests only
cargo test --bin anvil
# Run integration tests only
cargo test --test cli_tests
# Run a specific test
cargo test test_name
# Run tests with output
cargo test -- --nocapture
Project Structure
anvil/
├── src/
│ ├── main.rs # Entry point
│ ├── cli/ # Command line interface
│ │ ├── mod.rs # CLI definitions (clap)
│ │ ├── commands.rs # Command argument structs
│ │ ├── completions.rs # Shell completions
│ │ ├── output.rs # Output handling
│ │ ├── progress.rs # Progress indicators
│ │ └── formats/ # Output formatters
│ │ ├── mod.rs
│ │ ├── table.rs # Table formatter
│ │ ├── json.rs # JSON formatter
│ │ ├── yaml.rs # YAML formatter
│ │ └── html.rs # HTML report generator
│ ├── config/ # Configuration handling
│ │ ├── mod.rs
│ │ ├── schema.rs # Workload schema validation
│ │ ├── workload.rs # Workload parsing
│ │ ├── inheritance.rs # Inheritance resolution
│ │ └── global.rs # Global configuration
│ ├── assertions/ # Assertion evaluation engine
│ │ └── mod.rs
│ ├── commands/ # Inline command execution
│ │ └── mod.rs
│ ├── conditions/ # Condition/predicate engine
│ │ └── mod.rs
│ ├── operations/ # Command implementations
│ │ ├── mod.rs
│ │ ├── install.rs # Install command
│ │ ├── health.rs # Health check command
│ │ ├── list.rs # List command
│ │ ├── show.rs # Show command
│ │ ├── validate.rs # Validate command
│ │ ├── init.rs # Init command
│ │ ├── status.rs # Status command
│ │ ├── backup.rs # Backup command
│ │ └── config.rs # Config command
│ ├── providers/ # External integrations
│ │ ├── mod.rs
│ │ ├── winget.rs # Winget package manager
│ │ ├── filesystem.rs # File operations
│ │ ├── script.rs # Script execution
│ │ ├── template.rs # Template processing
│ │ └── backup.rs # Backup provider
│ └── state/ # State management
│ ├── mod.rs
│ ├── installation.rs # Installation state
│ ├── cache.rs # Cache management
│ └── files.rs # File state tracking
├── examples/ # Example workloads
│ ├── minimal/
│ ├── rust-developer/
│ └── python-developer/
├── tests/ # Integration tests
│ ├── cli_tests.rs
│ └── common/
│ └── mod.rs
└── docs/ # Documentation
└── src/
├── SUMMARY.md
├── introduction.md
├── user-guide.md
├── workload-authoring.md
├── troubleshooting.md
├── specification.md
├── architecture.md
├── changelog.md
└── contributing.md
Key Modules
- cli: Handles command-line parsing with clap and output formatting
- config: Parses workload YAML files and handles inheritance
- assertions: Evaluates named assertions for health reporting
- commands: Executes inline commands with timeout and elevation support
- conditions: Composable predicate engine for system state checks
- operations: Implements each CLI command's business logic
- providers: Interfaces with external systems (winget, filesystem, PowerShell)
- state: Tracks installation state and manages caching
Coding Standards
Rust Style
- Follow standard Rust conventions and idioms
- Use
rustfmtfor formatting (default settings) - Address all
clippywarnings - Use
thiserrorfor error types - Use
anyhowfor error propagation in application code
Code Organization
- Keep functions focused and small
- Use descriptive names for functions and variables
- Add doc comments for public APIs
- Use modules to organize related functionality
Error Handling
#![allow(unused)] fn main() { // Define custom errors with thiserror #[derive(Debug, thiserror::Error)] pub enum WorkloadError { #[error("Workload '{name}' not found")] NotFound { name: String }, #[error("Invalid workload schema: {message}")] InvalidSchema { message: String }, } // Use anyhow for propagation pub fn load_workload(name: &str) -> anyhow::Result<Workload> { // ... } }
Documentation
#![allow(unused)] fn main() { /// Brief description of the function. /// /// More detailed description if needed. /// /// # Arguments /// /// * `name` - The workload name to load /// /// # Returns /// /// The loaded workload or an error /// /// # Errors /// /// Returns an error if the workload is not found /// /// # Examples /// /// ``` /// let workload = load_workload("my-workload")?; /// ``` pub fn load_workload(name: &str) -> Result<Workload> { // ... } }
Commit Messages
- Use present tense ("Add feature" not "Added feature")
- Use imperative mood ("Fix bug" not "Fixes bug")
- Keep the first line under 72 characters
- Reference issues when applicable
Examples:
Add shell completions for Fish
Implement Fish shell completion generation using clap_complete.
Closes #42
Fix file backup path on Windows
Handle UNC paths correctly when creating backups.
Fixes #55
Testing
Unit Tests
Add unit tests in the same file as the code being tested:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; #[test] fn test_function_name() { // Arrange let input = "test"; // Act let result = function_under_test(input); // Assert assert_eq!(result, expected); } } }
Integration Tests
Add integration tests in tests/cli_tests.rs:
#![allow(unused)] fn main() { #[test] fn new_command_works() { anvil() .args(["new-command", "arg"]) .assert() .success() .stdout(predicate::str::contains("expected output")); } }
Test Guidelines
- Each test should be independent
- Use descriptive test names
- Test both success and failure cases
- Use
tempfile::TempDirfor file operations - Don't rely on external system state where avoidable
Submitting Changes
Pull Request Process
- Update documentation if needed
- Add tests for new features
- Ensure CI passes (format, lint, test, build)
- Write a clear PR description explaining:
- What the change does
- Why it's needed
- How to test it
- Request review from maintainers
- Address feedback promptly
- Squash commits if requested
PR Title Format
feat: Add new featurefix: Fix specific bugdocs: Update documentationtest: Add testsrefactor: Restructure codechore: Update dependencies
Creating Workloads
Contributions of new bundled workloads are welcome! See the Workload Authoring guide for details.
Workload Guidelines
- Include meaningful health checks - Verify the workload achieves its purpose
- Document the workload purpose - Clear description and comments
- Test on clean Windows installation - Ensure it works from scratch
- Use inheritance for common bases (extend
essentialsif appropriate) - Keep packages minimal - Only include what's necessary
- Validate before submitting -
anvil validate your-workload --strict
Workload Structure
workload-name/
├── workload.yaml # Required: workload definition
├── files/ # Optional: configuration files
└── scripts/ # Optional: setup/health scripts
Example
name: my-workload
version: "1.0.0"
description: "Brief description of what this workload provides"
extends:
- essentials # If applicable
packages:
winget:
- id: Package.ID
commands:
post_install:
- run: "echo Setting up..."
description: "Install and configure"
assertions:
- name: "Tool is available"
check:
type: command_exists
command: my-tool
Releasing
Releases are automated via the scripts/release.ps1 script and GitHub Actions.
Cutting a release
# Preview what will happen
./scripts/release.ps1 minor -DryRun
# Bump version, stamp changelog, commit, tag, and push
./scripts/release.ps1 minor -Push
# Patch release (0.6.0 → 0.6.1)
./scripts/release.ps1 patch -Push
# Major release (0.6.0 → 1.0.0)
./scripts/release.ps1 major -Push
The script validates (clean tree, main branch, tests pass, clippy clean) before making any changes. On push, the release workflow automatically:
- Builds binaries for 5 platforms (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64)
- Creates a GitHub Release with changelog notes and SHA256 checksums
- Publishes to crates.io
Required secrets
| Secret | Where to create | Used by |
|---|---|---|
CARGO_REGISTRY_TOKEN | crates.io/settings/tokens | cargo publish |
The GITHUB_TOKEN is provided automatically by GitHub Actions.
Setting up crates.io publishing (one-time)
- Log in at crates.io with your GitHub account
- Go to Account Settings → API Tokens
- Click New Token
- Name:
anvil-release(or any descriptive name) - Scopes: select publish-update (allows publishing new versions of existing crates)
- Click Create
- Copy the token (it's shown only once)
- In the GitHub repo, go to Settings → Secrets and variables → Actions
- Click New repository secret
- Name:
CARGO_REGISTRY_TOKEN, Value: paste the token - Click Add secret
For the first publish, you must also run cargo publish locally once to claim the crate name on crates.io.
License
By contributing to Anvil, you agree that your contributions will be licensed under the MIT or Apache-2.0 license, at the user's choice.
Questions?
- Open a Discussion
- Check the Documentation
- Review existing Issues
Thank you for contributing to Anvil!