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


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

  1. Download the latest release from the Releases page

  2. Extract the archive:

    PowerShell (Windows):

    Expand-Archive anvil-v0.3.1-windows-x64.zip -DestinationPath C:\Tools\anvil
    

    Bash / Zsh (macOS / Linux):

    tar xzf anvil-v0.3.1-linux-x64.tar.gz -C ~/.local/bin
    
  3. 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:

OptionDescription
--dry-runPreview actions without making changes
--forceSkip confirmation prompts
-p, --packages-onlyOnly install packages, skip files
--files-onlyOnly process files, skip packages
--skip-packagesSkip package installation
--skip-filesSkip file operations
--no-backupDon't backup existing files before overwriting
--upgradeUpgrade existing packages to specified versions
--retry-failedRetry only failed packages from previous run
--parallelRun installations in parallel where safe
-j, --jobs <N>Number of parallel installations (default: 4)
--timeout <SECONDS>Global timeout for operations (default: 3600)
--force-filesForce 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 - Success
  • 1 - General error
  • 2 - Workload not found
  • 3 - Package installation failed
  • 4 - File operation failed
  • 5 - 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:

OptionDescription
-o, --output <FORMAT>Output format: table, json, yaml, html
-f, --file <PATH>Write output to file
--fail-fastStop on first failure
--packages-onlyOnly check packages
--files-onlyOnly check files
--assertions-onlyOnly evaluate declarative assertions
-s, --strictTreat warnings as errors
--fixAttempt to install missing packages
--updateUpdate packages with available updates
--no-cacheSkip cache and query winget directly
--show-diffShow 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:

OptionDescription
-a, --allInclude built-in and custom workloads
-l, --longShow detailed information
--path <DIR>Search for workloads in additional path
--all-pathsShow 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:

OptionDescription
--show-inheritanceShow inheritance hierarchy
-r, --resolvedShow 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:

OptionDescription
--strictEnable strict validation mode
--schemaOutput JSON schema for workload definitions
--check-scriptsValidate script syntax using PowerShell parser
--scripts-onlyOnly 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:

OptionDescription
-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 only
  • standard - 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:

OptionDescription
-o, --output <FORMAT>Output format: table, json, yaml, html
-l, --longShow detailed status including timestamps
--clearClear 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:

OptionShortDescription
--verbose-vIncrease verbosity (use multiple times: -v, -vv, -vvv)
--quiet-qSuppress non-essential output
--config <PATH>-cUse custom configuration file
--no-colorDisable colored output
--help-hShow help information
--version-VShow 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:

  1. Explicit path — passed via --path flag
  2. User-configured — paths from ~/.anvil/config.yaml
  3. 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):

  1. Path specified with --path option
  2. User-configured paths (from ~/.anvil/config.yaml)
  3. 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

VariableDescriptionDefault
ANVIL_CONFIGConfiguration file path~/.anvil/config.yaml
ANVIL_WORKLOADSAdditional workload search paths(none)
ANVIL_LOGLog level: error, warn, info, debug, tracewarn
NO_COLORDisable colored output (any value)(unset)
ANVIL_BACKUP_DIRBackup 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

TypeFieldsDescription
command_existscommandCheck if a command is available on PATH
file_existspathCheck if a file exists (~ expanded)
dir_existspathCheck if a directory exists (~ expanded)
env_varname, value (optional)Check if env var is set, optionally matching a value
path_containssubstringCheck if PATH contains a substring
registry_valuehive, key, name, expected (optional)Query a Windows registry value
shellcommand, description (optional)Run a shell command; passes if exit code is 0
all_ofconditionsAll child conditions must pass (logical AND)
any_ofconditionsAt 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:

  1. Anvil version: anvil --version
  2. Windows version: winver
  3. Command that failed
  4. Verbose output: anvil -vvv <command>
  5. 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

FieldRequiredDescription
idYesWinget package identifier
versionNoSpecific version to install
sourceNoPackage source: winget or msstore
overrideNoAdditional 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:

PackageID
Visual Studio CodeMicrosoft.VisualStudioCode
GitGit.Git
Windows TerminalMicrosoft.WindowsTerminal
Node.jsOpenJS.NodeJS
PythonPython.Python.3.12
RustRustlang.Rustup
PowerShellMicrosoft.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"
FieldRequiredDefaultDescription
nameYes-Homebrew formula or cask name
caskNofalseWhether this is a cask (GUI app) vs formula (CLI tool)
tapNo-Tap source (e.g., "homebrew/cask-fonts")

APT Packages (Debian/Ubuntu)

packages:
  apt:
    - name: git
    - name: build-essential
      version: "12.9"
FieldRequiredDefaultDescription
nameYes-APT package name
versionNo-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

FieldRequiredDefaultDescription
sourceYes-Path relative to workload directory
destinationYes-Target path on system
backupNotrueBackup existing files before overwriting
permissionsNo-File permissions (Unix-style, informational on Windows)
templateNofalseProcess file as Handlebars template

Path Variables

Use these variables in destination paths:

VariableDescriptionExample
~User home directoryC:\Users\username
${HOME}User home directoryC:\Users\username
${USERPROFILE}User profile directoryC:\Users\username
${APPDATA}Application dataC:\Users\username\AppData\Roaming
${LOCALAPPDATA}Local app dataC:\Users\username\AppData\Local
${WORKLOAD_DIR}Workload directoryPath 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:

VariableDescription
{{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_install and scripts.post_install were removed in v1.0. Use the commands block 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

FieldRequiredDefaultDescription
runYes-Shell command string to execute
descriptionNo-Human-readable description
timeoutNo300Timeout in seconds
elevatedNofalseRequire administrator privileges
continue_on_errorNofalseContinue to next command if this one fails
whenNo-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

PhaseWhen it runs
pre_installBefore package installation
post_installAfter 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
FieldRequiredDefaultDescription
nameYes-Variable name
valueYes-Variable value
scopeNouserScope: 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

ScopeDescriptionRequires Admin
userCurrent user onlyNo
machineAll users on systemYes

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:

SectionMerge Behavior
packagesMerged; child packages added to parent packages
filesMerged; child files override parent files with same destination
commandsMerged; child commands run after parent commands
environmentMerged; 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

VariableDescriptionExample Value
~User home directoryC:\Users\username
${HOME}User home directoryC:\Users\username
${USERNAME}Current usernameusername
${COMPUTERNAME}Machine nameWORKSTATION-01
${WORKLOAD_DIR}Workload directoryC:\Workloads\my-workload
${USERPROFILE}User profile pathC:\Users\username
${APPDATA}Roaming AppDataC:\Users\username\AppData\Roaming
${LOCALAPPDATA}Local AppDataC:\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

  1. Use Descriptive Names

    name: rust-developer           # Good
    name: workload1                # Bad
    
  2. Include Version Information

    version: "1.2.0"               # Good: semantic versioning
    version: "latest"              # Bad: not meaningful
    
  3. Write Helpful Descriptions

    description: "Complete Rust development environment with debugging tools and VS Code extensions"
    
  4. 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

  1. 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
    
  2. Use Machine Scope for Shared Tools

    packages:
      winget:
        - id: Microsoft.VisualStudioCode
          override:
            - "--scope"
            - "machine"
    

File Management

  1. Always Enable Backups for Important Files

    files:
      - source: .gitconfig
        destination: "~/.gitconfig"
        backup: true
    
  2. Use Templates for Dynamic Content

    files:
      - source: config.toml.hbs
        destination: "~/.config/app/config.toml"
        template: true
    

Script Safety

  1. Make Scripts Idempotent

    # Check before acting
    if (-not (Test-Path $target)) {
        # Create/install
    }
    
  2. Handle Errors Gracefully

    try {
        # Risky operation
    }
    catch {
        Write-Error "Operation failed: $_"
        exit 1
    }
    
  3. Include Assertions

    assertions:
      - name: "Installation verified"
        check:
          type: command_exists
          command: my-tool
    

Testing

  1. Validate Before Committing

    anvil validate my-workload --strict
    
  2. Test with Dry Run

    anvil install my-workload --dry-run
    
  3. 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

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.

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

  1. Clone your workloads repository:

    git clone https://github.com/your-org/workloads ~/my-workloads
    
  2. Configure Anvil to search this path:

    anvil config set workloads.paths '["~/my-workloads"]'
    

    Or edit ~/.anvil/config.yaml directly:

    workloads:
      paths:
        - "~/my-workloads"
    
  3. 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> --strict to 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_check was removed in v1.0. Use declarative assertions instead.

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 [email protected]: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:

  1. Explicit --path argument (highest priority)
  2. User-configured paths from config.yaml
  3. 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:

  1. 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"
    
  2. Verify platform compatibility

    • Anvil currently requires Windows 10 (1809+) or Windows 11
    • Cross-platform support is on the roadmap
  3. 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):

  1. Install Windows Package Manager

    # Open Microsoft Store to App Installer
    Start-Process "ms-windows-store://pdp/?ProductId=9NBLGGH4NNS1"
    
  2. Update App Installer

    • Open Microsoft Store → search "App Installer" → click "Update"
  3. Test availability

    winget --version
    
  4. 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:

  1. Verify package ID

    winget search <package-name>
    winget search --exact "Package Name"
    
  2. Check package source in workload

    packages:
      winget:
        - id: SomeApp.App
          source: msstore    # For Microsoft Store apps
    
  3. Update package sources

    winget source update
    

Installation hangs

Symptoms:

  • Installation appears frozen
  • No progress for extended time

Solutions:

  1. Check for interactive prompts

    • Some installers require user interaction
    • Run anvil without --quiet to see prompts
  2. Use silent install overrides

    packages:
      winget:
        - id: SomeApp.App
          override:
            - "--silent"
            - "--accept-license"
    
  3. 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:

  1. Run with elevation (Windows)

    Start-Process powershell -Verb RunAs
    anvil install my-workload
    
  2. 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:

  1. Use user-writable paths

    files:
      - source: config.json
        destination: "~/.config/app/config.json"  # Expands to user home
    
  2. Check target directory permissions

    Windows:

    Get-Acl "C:\path\to\directory" | Format-List
    

    Linux/macOS:

    ls -la /path/to/directory
    
  3. Run as administrator for system-level paths


File not found

Symptoms:

  • Error: "Source file not found"

Solutions:

  1. Verify source path — paths are relative to the workload directory

    files:
      - source: files/config.json      # Relative to workload dir
        destination: "~/.config/app/config.json"
    
  2. Check workload directory structure

    my-workload/
    ├── workload.yaml
    └── files/
        └── config.json
    

Backup failed

Symptoms:

  • Error: "Failed to create backup"

Solutions:

  1. Check disk space
  2. 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:

  1. Verify script path in workload.yaml — paths are relative to scripts/

    scripts:
      post_install:
        - path: setup.ps1    # Relative to scripts/ directory
    
  2. Test script manually

    & "C:\workloads\my-workload\scripts\setup.ps1"
    

Execution policy error (Windows)

Symptoms:

  • Error: "Running scripts is disabled on this system"

Solutions:

  1. Set execution policy

    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
    
  2. Check group policy — corporate environments may have stricter policies


Script timeout

Symptoms:

  • Error: "Script execution timed out"

Solutions:

  1. Increase timeout in workload

    scripts:
      post_install:
        - path: scripts/long-running.ps1
          timeout: 1800    # 30 minutes (default: 300)
    
  2. Check for infinite loops or slow network operations in the script


Elevated script fails (Windows)

Symptoms:

  • Error: "Elevation required"

Solutions:

  1. Run anvil as administrator
  2. 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:

  1. Run with verbose output

    anvil health my-workload --verbose
    
  2. Review health check scripts — they may have outdated expectations

  3. Update version expectations

    winget list --id Package.Name
    

Partial results

Symptoms:

  • Some checks don't run
  • Health report incomplete

Solutions:

  1. Check verbose output for errors in earlier checks

    anvil health my-workload -vvv
    
  2. 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:

  1. Check config location

    anvil config path
    
  2. View current configuration

    anvil config list
    
  3. Reset to defaults

    anvil config reset
    

Workloads not found

Symptoms:

  • Error: "Workload 'X' not found"
  • Empty list from anvil list

Solutions:

  1. Check search paths

    anvil config list
    
  2. Verify workload structure — needs a workload.yaml file

    workload-name/
    └── workload.yaml
    
  3. 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:

  1. Inspect the extends chain in each workload

    # workload-a/workload.yaml
    extends:
      - workload-b     # workload-b also extends workload-a → cycle!
    
  2. Break the cycle by removing one of the extends references

  3. Run anvil validate to see the detected chain


Maximum inheritance depth exceeded

Symptoms:

  • Error: "Maximum inheritance depth (10) exceeded for workload 'X'"

Solutions:

  1. Flatten the hierarchy — the maximum allowed depth is 10
  2. Merge intermediate workloads to reduce nesting
  3. Check for unintended long chains
    anvil show my-workload    # Review resolved inheritance
    

Parent workload not found

Symptoms:

  • Error: "Parent workload 'X' not found"

Solutions:

  1. Verify the parent name matches an available workload

    anvil list    # See all available workloads
    
  2. Check search paths — ensure the parent workload directory is discoverable

    anvil config list    # Review configured workload search paths
    
  3. Use --path if the parent is in a custom location

    anvil 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:

  1. Run anvil validate --strict to surface these warnings

  2. 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
    
  3. 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:

  1. Run the health check with verbose output to see which assertion failed

    anvil health my-workload -vvv
    
  2. Review the condition — assertions check system state (command existence, file presence, environment variables, registry values)

  3. Common assertion types and fixes:

    • Command not found — install the missing tool or add it to PATH
    • File missing — re-run anvil install to 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:

  1. Command not found — ensure the executable is installed and on PATH

    Get-Command <command-name>
    
  2. Timeout — increase the timeout in your workload definition

    scripts:
      post_install:
        - path: slow-script.ps1
          timeout: 1800    # 30 minutes
    
  3. Elevation required — run anvil as administrator, or set elevated: true


10. Error Reference

Common Error Codes

ErrorDescriptionSolution
E001Workload not foundCheck name and search paths
E002Invalid workload schemaRun anvil validate for details
E003Circular dependencyReview extends chain in workloads
E004Package installation failedCheck package manager logs, verify package ID
E005File operation failedCheck permissions, verify paths
E006Script execution failedReview script output, check syntax
E007Health check failedReview check results, update scripts
E008Configuration errorValidate config file syntax
E009Backup operation failedCheck disk space and permissions
E010Restore operation failedVerify 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:

  1. Anvil version: anvil --version
  2. Operating system and version
  3. Command that failed: exact command you ran
  4. Error message: complete error output
  5. Verbose output: run with -vvv flag
  6. 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

VariablePurpose
ANVIL_CONFIGCustom config file path
ANVIL_WORKLOADSAdditional workload search paths
ANVIL_LOGLog level (error, warn, info, debug, trace)
ANVIL_BACKUP_DIRCustom backup directory
NO_COLORDisable colored output

CLI Reference

Complete reference for every Anvil command, flag, and option.

Global Options

These options are available on all commands:

FlagDescription
-v, --verbose...Increase output verbosity (repeat for more: -v, -vv, -vvv)
-q, --quietSuppress non-essential output
-c, --config <PATH>Use a custom configuration file
--no-colorDisable colored output
-h, --helpPrint help
-V, --versionPrint 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:

ArgumentRequiredDescription
<WORKLOAD>YesName of the workload to install (or path to workload.yaml)

Options:

FlagDefaultDescription
-d, --dry-runShow what would be done without making changes
-f, --forceSkip confirmation prompts
-p, --packages-onlyOnly install packages, skip files
--skip-packagesSkip package installation
--skip-filesSkip file operations
--no-backupDon't backup existing files before overwriting
--upgradeUpgrade existing packages to specified versions
--retry-failedRetry only failed packages from previous run
--parallelRun installations in parallel where safe
-j, --jobs <N>4Number of parallel package installations
--timeout <SECONDS>3600Global timeout for operations in seconds
--files-onlyOnly process files, skip packages
--force-filesForce 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:

ArgumentRequiredDescription
<WORKLOAD>YesName of the workload to check against

Options:

FlagDefaultDescription
-o, --output <OUTPUT>tableOutput format (table, json, yaml, html)
-f, --file <PATH>Write report to file instead of stdout
--fail-fastStop on first failure
--packages-onlyOnly check packages
--files-onlyOnly check files
--assertions-onlyOnly evaluate declarative assertions
-s, --strictTreat warnings as errors
--fixAttempt to install missing packages
--updateUpdate packages with available updates
--no-cacheSkip cache and query winget directly
--show-diffShow 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:

FlagDefaultDescription
-a, --allInclude built-in and custom workloads
-l, --longShow detailed information
--path <PATH>Search for workloads in additional path
--all-pathsShow 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:

ArgumentRequiredDescription
<WORKLOAD>YesName of the workload to display

Options:

FlagDefaultDescription
-r, --resolvedShow resolved configuration (with inheritance applied)
--show-inheritanceShow inheritance tree visualization
-o, --output <OUTPUT>yamlOutput 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:

ArgumentRequiredDescription
[PATH]NoPath to workload.yaml file or workload directory

Options:

FlagDefaultDescription
--strictEnable strict validation mode
--schemaOutput JSON schema for workload definitions
--check-scriptsValidate script syntax using PowerShell parser
--scripts-onlyOnly 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:

ArgumentRequiredDescription
<NAME>YesName for the new workload

Options:

FlagDefaultDescription
-t, --template <TEMPLATE>standardBase template (minimal, standard, full)
-e, --extends <PARENT>Parent workload to extend
-o, --output <PATH>Output directory

Template values:

TemplateDescription
minimalMinimal workload with just metadata
standardStandard workload with common sections
fullFull 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:

ArgumentRequiredDescription
[WORKLOAD]NoName of the workload (shows all if omitted)

Options:

FlagDefaultDescription
-o, --output <OUTPUT>tableOutput format (table, json, yaml, html)
-l, --longShow detailed status including timestamps
--clearClear 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:

ArgumentRequiredDescription
<SHELL>YesTarget 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:

FlagDefaultDescription
-n, --name <NAME>Name for the backup
-w, --workload <WORKLOAD>Only backup files related to workload
--include-packagesInclude winget package list export
--compressCreate 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:

FlagDefaultDescription
-w, --workload <WORKLOAD>Filter by workload name
-o, --output <OUTPUT>tableOutput format (table, json, yaml, html)
-l, --longShow 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:

ArgumentRequiredDescription
<ID>YesBackup ID

Examples:

anvil backup show abc123

anvil backup restore

Synopsis: anvil backup restore [OPTIONS] [ID]

Description: Restore files from a backup.

Arguments:

ArgumentRequiredDescription
[ID]NoBackup ID to restore (or use --workload)

Options:

FlagDefaultDescription
-w, --workload <WORKLOAD>Restore all backups for a workload
-d, --dry-runShow what would be done without making changes
-f, --forceSkip 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:

FlagDefaultDescription
--older-than <DAYS>30Remove backups older than N days
-d, --dry-runShow what would be done without making changes
-f, --forceSkip 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:

FlagDefaultDescription
-w, --workload <WORKLOAD>Only verify backups for a specific workload
--fixFix 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:

ArgumentRequiredDescription
<KEY>YesConfiguration 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:

ArgumentRequiredDescription
<KEY>YesConfiguration key (e.g., defaults.shell)
<VALUE>YesValue 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:

FlagDefaultDescription
-o, --output <OUTPUT>tableOutput 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:

FlagDefaultDescription
-f, --forceSkip 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

VariableDescriptionUsed by
NO_COLORDisables colored output when set (any value). Standard convention (no-color.org).All commands
ANVIL_WORKLOADName of the current workload being processed.Scripts, templates
ANVIL_WORKLOAD_PATHAbsolute path to the workload directory.Scripts
ANVIL_DRY_RUNSet to "true" or "false" during script execution.Scripts
ANVIL_VERBOSEVerbosity level (03) during script execution.Scripts
ANVIL_PHASECurrent execution phase: pre_install, post_install, or validation.Scripts
ANVIL_VERSIONAnvil version string. Available in scripts and Handlebars templates.Scripts, templates
RUST_LOGControls Rust log output (e.g., RUST_LOG=debug). Standard env_logger / tracing variable.Debugging
RUST_BACKTRACEEnables Rust backtraces on panic (1 = short, full = complete).Debugging

Exit Codes

CodeMeaning
0Success — command completed, all checks passed
1General failure — operation failed or health checks did not pass
2Usage 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.

KeyTypeDefaultDescription
defaults.shellstring"powershell"Default shell for script execution (powershell, pwsh).
defaults.script_timeoutinteger300Default script timeout in seconds.
defaults.output_formatstring"table"Output format for commands. Valid values: table, json, yaml, html.
defaults.colorstring"auto"Color output mode. Valid values: auto, always, never.

backup

Controls automatic backup behavior during installs.

KeyTypeDefaultDescription
backup.auto_backupbooleantrueCreate a backup before install operations.
backup.retention_daysinteger30Delete backups older than this many days.
backup.max_backupsinteger10Maximum number of backups to keep per workload.
backup.compressbooleanfalseCompress backups by default.

install

Settings that govern package installation behavior.

KeyTypeDefaultDescription
install.parallel_packagesbooleanfalseInstall packages in parallel.
install.skip_installedbooleantrueSkip packages that are already installed.
install.confirmbooleantruePrompt for confirmation before installing.

workloads

Workload discovery configuration.

KeyTypeDefaultDescription
workloads.pathslist of strings[]Additional directories to search for workloads.

logging

Logging configuration.

KeyTypeDefaultDescription
logging.levelstring"info"Log verbosity level. Valid values: error, warn, info, debug, trace.
logging.filestring or nullnullPath 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

FieldTypeRequiredDefaultDescription
namestringyesUnique workload identifier (kebab-case).
versionstringyesSemantic version (e.g., "1.0.0").
descriptionstringyesHuman-readable description of the workload.
extendslist of stringsnonullParent workload names to inherit from.
packagesobjectnonullPackage definitions, grouped by manager.
fileslist of FileEntrynonullConfiguration files to deploy.
scriptsobjectnonullScripts to execute at various phases.
commandsobjectnonullInline commands to execute at various phases.
environmentobjectnonullEnvironment variables and PATH additions.
healthobjectnonullHealth check configuration flags.
assertionslist of AssertionnonullDeclarative assertions for health validation.

packages

The packages object groups package definitions by package manager. Each manager key is optional.

FieldTypeRequiredDescription
packages.wingetlist of WingetPackagenoWindows packages managed by winget.
packages.brewlist of BrewPackagenomacOS/Linux packages managed by Homebrew.
packages.aptlist of AptPackagenoDebian/Ubuntu packages managed by APT.

Winget Package

FieldTypeRequiredDefaultDescription
idstringyesWinget package identifier (e.g., Git.Git).
versionstringnolatestSpecific version to install.
sourcestringnonullPackage source (e.g., msstore for Microsoft Store apps).
overridelist of stringsnonullOverride arguments passed to the installer.
override_argslist of stringsnonullAdditional 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

FieldTypeRequiredDefaultDescription
namestringyesPackage name (e.g., git, node).
caskbooleannofalseWhether this is a cask (GUI app) vs formula (CLI tool).
tapstringnonullTap 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

FieldTypeRequiredDefaultDescription
namestringyesPackage name (e.g., git, build-essential).
versionstringnonullSpecific 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.

FieldTypeRequiredDefaultDescription
sourcestringyesPath relative to the workload's files/ directory.
destinationstringyesTarget path. ~ expands to the user's home directory.
backupbooleannotrueBack up the existing file before overwriting.
permissionsstringnonullFile permissions (reserved for future use).
templatebooleannofalseProcess 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.

FieldTypeDescription
commands.pre_installlist of CommandEntryCommands to run before package installation.
commands.post_installlist of CommandEntryCommands to run after package installation.

CommandEntry

FieldTypeRequiredDefaultDescription
runstringyesShell command string to execute.
descriptionstringnonullHuman-readable description.
timeoutintegerno300Timeout in seconds.
elevatedbooleannofalseWhether the command requires administrator privileges.
whenConditionnonullCondition that must be true for this command to run.
continue_on_errorbooleannofalseWhether 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.

FieldTypeDescription
environment.variableslist of EnvVariableEnvironment variables to set.
environment.path_additionslist of stringsDirectory paths to add to the system PATH. ~ expands to the user's home.

EnvVariable

FieldTypeRequiredDefaultDescription
namestringyesVariable name.
valuestringyesVariable value.
scopestringno"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.

FieldTypeRequiredDefaultDescription
package_checkbooleannotrueVerify that declared packages are installed.
file_checkbooleannotrueVerify that declared files are deployed.
assertion_checkbooleannotrueEvaluate 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.

FieldTypeRequiredDescription
namestringyesDisplay name for the assertion.
checkConditionyesThe 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.

FieldTypeRequiredDescription
typestringyes"command_exists"
commandstringyesCommand name to look for.
file_exists

Check whether a file exists at the given path (~ is expanded).

FieldTypeRequiredDescription
typestringyes"file_exists"
pathstringyesFile path to check.
dir_exists

Check whether a directory exists at the given path (~ is expanded).

FieldTypeRequiredDescription
typestringyes"dir_exists"
pathstringyesDirectory path to check.
env_var

Check whether an environment variable is set, optionally matching an expected value.

FieldTypeRequiredDescription
typestringyes"env_var"
namestringyesEnvironment variable name.
valuestringnoExpected value. If omitted, only checks that the variable is set.
path_contains

Check whether the system PATH contains a given substring.

FieldTypeRequiredDescription
typestringyes"path_contains"
substringstringyesSubstring to search for in PATH entries.
registry_value

Query a Windows registry value under HKCU or HKLM.

FieldTypeRequiredDescription
typestringyes"registry_value"
hivestringyesRegistry hive: "HKCU" or "HKLM".
keystringyesFull key path (e.g., "SOFTWARE\\Microsoft\\Windows\\CurrentVersion").
namestringyesValue name inside the key.
expectedstringnoExpected data. If omitted, only asserts the value exists.
shell

Run a shell command; the condition passes when the exit code is 0.

FieldTypeRequiredDescription
typestringyes"shell"
commandstringyesShell command to execute.
descriptionstringnoHuman-readable description of the check.
all_of

Logical AND — all child conditions must pass.

FieldTypeRequiredDescription
typestringyes"all_of"
conditionslist of ConditionyesChild conditions to evaluate.
any_of

Logical OR — at least one child condition must pass.

FieldTypeRequiredDescription
typestringyes"any_of"
conditionslist of ConditionyesChild 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

ModeDescription
InstallApply a workload configuration by installing packages, executing scripts, and copying files
Health CheckValidate 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

CriterionWeightDescription
Windows Integration25%Native Windows APIs, winget interoperability, registry access
Maintainability20%Code clarity, testing support, community ecosystem
Extensibility15%Plugin architecture, workload composition
Dependencies15%Target machine requirements, deployment complexity
Performance10%Startup time, execution speed
Error Handling15%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

CriterionPowerShellPythonC# ScriptC# AppRust
Windows Integration★★★★★★★★☆☆★★★★☆★★★★★★★★★☆
Maintainability★★★☆☆★★★★☆★★★☆☆★★★★★★★★★☆
Extensibility★★★☆☆★★★★☆★★★☆☆★★★★★★★★★★
Dependencies★★★★★★★☆☆☆★★☆☆☆★★★★☆★★★★★
Performance★★★☆☆★★★☆☆★★★☆☆★★★★☆★★★★★
Error Handling★★★☆☆★★★★☆★★★★☆★★★★★★★★★★
Weighted Score7268658285

3. Technology Recommendation

3.1 Primary Recommendation: Rust CLI Application

Rationale:

  1. Zero Dependencies: The single static binary eliminates runtime requirements, crucial for bootstrapping fresh Windows installations where development tools aren't yet installed.

  2. Robust Error Handling: Rust's Result<T, E> pattern enforces comprehensive error handling at compile time, preventing runtime surprises during critical system configuration.

  3. Performance: Fast startup (~5ms) and execution makes the tool feel responsive, encouraging frequent health checks.

  4. Configuration Parsing: Excellent libraries for YAML (serde_yaml), TOML (toml), and JSON (serde_json) with strong type safety.

  5. CLI Excellence: The clap crate provides industry-leading CLI parsing with automatic help generation, shell completions, and argument validation.

  6. 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)

ADRDecisionRationale
ADR-001Use Rust for core CLIZero runtime deps, robust error handling
ADR-002Use YAML for workload definitionsHuman-readable, supports comments
ADR-003Use PowerShell for scriptsNative Windows integration
ADR-004Use SHA-256 for file hashingIndustry standard, good performance
ADR-005Support workload inheritanceDRY 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:

MechanismFieldIntroducedStatus
Declarative assertionsassertions:v0.5Recommended
Health check scriptsscripts.health_checkv0.1Deprecated 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_check is used alongside assertions
  • v0.6: Warning emitted for any use of scripts.health_check (even without assertions)
  • v1.0: scripts.health_check removed; 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:

MechanismFieldIntroducedStatus
Inline commandscommands.pre_install / commands.post_installv0.6Recommended
Script filesscripts.pre_install / scripts.post_installv0.1Deprecated 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_install is used alongside commands.pre_install/commands.post_install
  • v0.8: Warning emitted for any use of scripts.pre_install/scripts.post_install
  • v1.0: scripts.pre_install and scripts.post_install removed; use commands exclusively

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: true overrides 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: true require admin privileges
  • On Windows: validated via whoami /priv or 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:

VariableExpansion
~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:

FunctionDescription
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:

FunctionDescription
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:

FunctionDescription
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:

FieldMerge Strategy
nameChild overwrites
versionChild overwrites
descriptionChild overwrites
packages.wingetAppend (child packages added after parent)
filesAppend (child files added, same destinations overwritten)
scripts.pre_installParent first, then child
scripts.post_installParent first, then child
scripts.health_checkCombine all
environment.variablesChild overwrites same-named variables
environment.path_additionsAppend

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 includes assertions: Option<Vec<Assertion>>. Health command evaluates assertions alongside legacy scripts.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:

TypeParametersPurpose
command_existscommand, min_version?Verify a CLI tool is installed
file_existspathVerify a file exists
dir_existspathVerify a directory exists
env_varname, value?Verify an environment variable is set
path_containsentryVerify PATH includes an entry
json_propertypath, property, valueVerify a JSON file property
registry_valuekey, name, valueVerify a Windows registry value
shellcommand, expected_exit_code?Escape hatch — run a shell command
all_ofchecks: [...]All sub-checks must pass (AND)
any_ofchecks: [...]At least one sub-check must pass (OR)

Implementation:

  • New module: src/conditions/ — shared predicate engine reused by assertions and future commands.when
  • New module: src/assertions/Assertion enum (tagged serde), evaluate() function
  • Update src/config/workload.rs — add assertions field to Workload
  • Update src/operations/health.rs — evaluate assertions alongside legacy scripts.health_check
  • Backward compatible — existing scripts.health_check continues to work

8.3 v0.5 — Package Manager Abstraction (Partial)

Status: Multi-manager schema implemented (Packages struct with winget, brew, apt fields; BrewPackage and AptPackage types defined). The PackageManager trait 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 includes commands: Option<CommandBlock> with pre_install and post_install phases. CommandEntry supports run, description, timeout, elevated, when (condition), and continue_on_error. Execution engine in commands::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: true to continue
  • Non-zero exit codes are errors unless expected_exit_code is 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. ConfigManager loads user-configured search paths from GlobalConfig.workloads.paths and 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:

  1. Explicit path argument (highest priority)
  2. User-configured search paths (~/.anvil/config.yaml)
  3. 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 (anvil is taken on crates.io — candidates: anvil-dev, devsmith, forgekit)
  • Cross-compilation CI for Windows, Linux, macOS ✅ Implemented (matrix build in ci.yml)
  • Shell completions ✅ Implemented (bash, zsh, fish, powershell, elvish via cli/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

CodeNameDescription
0SUCCESSOperation completed successfully
1HEALTH_CHECK_FAILEDOne or more health checks failed
2CONFIG_ERRORConfiguration file error
3WORKLOAD_NOT_FOUNDSpecified workload does not exist
4WINGET_ERRORWinget operation failed
5FILE_ERRORFile operation failed
6SCRIPT_ERRORScript execution failed
7PERMISSION_ERRORInsufficient permissions
8NETWORK_ERRORNetwork connectivity issue
9TIMEOUTOperation timed out
10CIRCULAR_DEPENDENCYCircular workload inheritance

9.4 Glossary

TermDefinition
WorkloadA named configuration bundle defining packages, files, and scripts
Health CheckValidation of system state against a workload definition
ProviderComponent that interfaces with external systems (winget, filesystem, scripts)
InheritanceMechanism for workloads to extend and build upon other workloads
Variable ExpansionReplacement of placeholders like ~ or ${HOME} with actual paths

Document History

VersionDateAuthorChanges
2.0.02026-04-16Anvil TeamRename to Anvil, updated roadmap (v0.4–v1.0)
1.0.02025-01-15Anvil TeamInitial 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/
LayerRole
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).

ProviderResponsibility
WingetProviderPackage install/upgrade/query via winget.exe
FilesystemProviderFile copy with backup, hash verification, glob expansion
ScriptProviderPowerShell script execution with timeout and output capture
TemplateProcessorHandlebars template rendering for config files
BackupManagerSystem 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)
TypeFilePurpose
InstallationStatestate/<workload>.jsonPackage install records and status
FileStateManagerstate/files.jsonFile hashes for drift detection
PackageCachecache/packages.jsonCached winget query results
GlobalConfigconfig.yamlUser settings and search paths

Config System

Workload Discovery

ConfigManager searches for workloads in this order:

  1. Direct path (if an absolute or relative path is given)
  2. <exe_dir>/workloads/<name>/workload.yaml
  3. %LOCALAPPDATA%/anvil/workloads/<name>/workload.yaml
  4. ./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:

  1. Build dependency graph from all extends references
  2. Detect cycles (error) and enforce max depth of 10
  3. Topological sort determines merge order (parents first)
  4. 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:

VariableExpands 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:

  • thiserror for domain-specific error enums in each module:
    • WingetError, FilesystemError, ScriptError, BackupError, TemplateError
    • InheritanceError (includes suggestion() for user-friendly hints)
    • FileStateError
    • ProviderError (wraps all provider errors)
    • CommandError, ConditionError
  • anyhow for 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 workload
  • create_inherited_workload() — parent + child workloads
  • create_invalid_workload() — malformed YAML
  • create_circular_workloads() — cycle detection fixtures
  • create_full_workload() — workload with all features
  • create_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:

FormatModuleDescription
tablecli/formats/table.rsHuman-readable terminal tables (default)
jsoncli/formats/json.rsMachine-readable JSON
yamlcli/formats/yaml.rsYAML output
htmlcli/formats/html.rsStandalone HTML reports

CI Pipeline

.github/workflows/ci.yml runs on every push/PR across Windows, Linux, and macOS (matrix build):

  1. cargo fmt --all -- --check
  2. cargo clippy --all-targets --all-features -- -D warnings
  3. cargo test --bin anvil (unit tests)
  4. cargo test --test cli_tests (integration tests)
  5. 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, plus all_of/any_of composition
  • --assertions-only flag for anvil health to run only assertion checks
  • assertion_check toggle in workload health configuration
  • Assertion examples in anvil init --template full scaffold
  • Multi-manager workload schema: packages.brew (Homebrew) and packages.apt (APT) fields alongside existing packages.winget
  • Platform-aware validation warns when workload references an unavailable package manager
  • Inline commands: block for workload command execution with pre_install and post_install phases
  • Conditional command execution via when: field using the predicate engine
  • continue_on_error option 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-paths to 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-dev for crates.io publishing (binary name stays anvil; install via cargo install anvil-dev)

Deprecated

  • scripts.health_check when used alongside assertionsremoved in this release (see Removed)
  • scripts.pre_install and scripts.post_install when used alongside commands (migrate to inline commands; removal planned for v1.0)

Removed

  • scripts.health_check field — use declarative assertions instead
  • --scripts-only and --script flags from anvil health command
  • script_check field from health configuration

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 Validate gate 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 (config command)
  • Backup management with create, list, show, restore, clean, and verify subcommands
  • Status command to show installation state
  • HTML output format for health reports
  • --strict flag 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:

  1. Check existing issues to avoid duplicates
  2. Gather relevant information:
    • Anvil version (anvil --version)
    • Windows version
    • Steps to reproduce
    • Expected vs actual behavior
    • Verbose output (anvil -vvv <command>)

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 rustfmt for formatting (default settings)
  • Address all clippy warnings
  • Use thiserror for error types
  • Use anyhow for 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::TempDir for file operations
  • Don't rely on external system state where avoidable

Submitting Changes

Pull Request Process

  1. Update documentation if needed
  2. Add tests for new features
  3. Ensure CI passes (format, lint, test, build)
  4. Write a clear PR description explaining:
    • What the change does
    • Why it's needed
    • How to test it
  5. Request review from maintainers
  6. Address feedback promptly
  7. Squash commits if requested

PR Title Format

  • feat: Add new feature
  • fix: Fix specific bug
  • docs: Update documentation
  • test: Add tests
  • refactor: Restructure code
  • chore: Update dependencies

Creating Workloads

Contributions of new bundled workloads are welcome! See the Workload Authoring guide for details.

Workload Guidelines

  1. Include meaningful health checks - Verify the workload achieves its purpose
  2. Document the workload purpose - Clear description and comments
  3. Test on clean Windows installation - Ensure it works from scratch
  4. Use inheritance for common bases (extend essentials if appropriate)
  5. Keep packages minimal - Only include what's necessary
  6. 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:

  1. Builds binaries for 5 platforms (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64)
  2. Creates a GitHub Release with changelog notes and SHA256 checksums
  3. Publishes to crates.io

Required secrets

SecretWhere to createUsed by
CARGO_REGISTRY_TOKENcrates.io/settings/tokenscargo publish

The GITHUB_TOKEN is provided automatically by GitHub Actions.

Setting up crates.io publishing (one-time)

  1. Log in at crates.io with your GitHub account
  2. Go to Account Settings → API Tokens
  3. Click New Token
  4. Name: anvil-release (or any descriptive name)
  5. Scopes: select publish-update (allows publishing new versions of existing crates)
  6. Click Create
  7. Copy the token (it's shown only once)
  8. In the GitHub repo, go to Settings → Secrets and variables → Actions
  9. Click New repository secret
  10. Name: CARGO_REGISTRY_TOKEN, Value: paste the token
  11. 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?

Thank you for contributing to Anvil!