- Python 98.9%
- Jinja 1.1%
| changelogs | ||
| meta | ||
| playbooks | ||
| plugins/lookup | ||
| roles | ||
| .gitignore | ||
| AGENT.md | ||
| galaxy.yml | ||
| README.md | ||
| requirements.yml | ||
silvarion.provision
Ansible collection for declarative, parallelized infrastructure provisioning on Proxmox.
The collection reads a structured infrastructure repository (local path or git URL), dynamically registers matching hosts, and runs a three-play pipeline:
- Provision — for each registered host, run its
silvarion.provision.*roles (e.g.proxmox) withconnection: localto create the LXC container/VM. - Prepare the controller — install (via
ansible-galaxy) any tech role or collection referenced by a created host that isn't already installed. - Configure — SSH into each created container/VM and run the rest of its
ansible_roles_list(the tech roles: mysql, redis, etc.).
Requirements
| Requirement | Version |
|---|---|
| Ansible | >= 2.14 |
community.proxmox |
>= 1.0.0 |
Installation
From a git repository
# Install directly from git
ansible-galaxy collection install \
git+https://git.silvarion.org/ansible-collections/silvarion.provision.git
# Install a specific branch or tag
ansible-galaxy collection install \
git+https://git.silvarion.org/ansible-collections/silvarion.provision.git,main
From a local clone
git clone https://git.silvarion.org/ansible-collections/silvarion.provision.git
ansible-galaxy collection install ./silvarion.provision/ --force
Via requirements.yml (recommended)
Create a requirements.yml in your project:
collections:
- name: https://git.silvarion.org/ansible-collections/silvarion.provision.git
type: git
version: main
- name: community.proxmox
version: ">=1.0.0"
Then install:
ansible-galaxy collection install -r requirements.yml
Infrastructure Repository Structure
The inventory role (via the env_parser lookup plugin) expects a structured
YAML repository that serves as the single source of truth for your
infrastructure. playbook_env_source (or inventory_env_source when using
the role directly) points to the root of an environment directory
(e.g., infra/testlab/).
<env_dir>/
<platform>/ # e.g., proxmox
metadata.yml # Layer 1: provider-wide defaults
defaults/
<tech>.yml # Layer 2: per-tech resource profiles
<node>/ # e.g., pve01
metadata.yml # Layer 3: node-specific overrides
<category>/ # e.g., base | monitoring | comms
<server_type>/ # e.g., lxc | vms
<tech>/ # e.g., mysql | redis | netbox
<purpose>/ # e.g., main | dev | public
group_vars/
all.yml # Layer 4: leaf instance variables (REQUIRED)
Legacy leaves (all.yml directly under <tech>/, with no <purpose> level)
are still recognized — purpose defaults to main for those.
Variable Precedence (lowest → highest)
| Layer | File | Purpose |
|---|---|---|
| 1 | <platform>/metadata.yml |
Provider-wide defaults (subnet, VMID range, SSH keys) |
| 2 | <platform>/defaults/<tech>.yml |
Per-tech resource profile (cores, memory, disk) |
| 3 | <platform>/<node>/metadata.yml |
Node-specific overrides |
| 4 | <platform>/<node>/<category>/<server_type>/<tech>/<purpose>/group_vars/all.yml |
Instance variables |
Higher layers override lower layers via recursive merge. The fully merged
variable dict is stored as provision_unit on each registered host.
Example all.yml (leaf)
# proxmox/pve01/base/lxc/mysql/main/group_vars/all.yml
ansible_host: 10.0.0.110
proxmox_node: pve01
ansible_roles_list:
- name: my_namespace.mysql
mysql_port: 3306
mysql_root_pswd: "{{ vault_mysql_root_pswd }}"
silvarion.provision.proxmox (the platform provisioning role) is prepended
to ansible_roles_list automatically — via inventory_default_provision_roles
— so leaf files only need to list their tech roles.
To run only a subset of a host's ansible_roles_list for a given invocation
(e.g. re-configuring just one tech role without touching the leaf all.yml),
set ansible_target_roles_list to an allowlist of role names/roles as a host
var or extra-var. It only affects the tech configuration play (roles applied
on the target hosts via SSH); leaving it unset or empty runs every role in
ansible_roles_list as before.
ansible-playbook silvarion.provision.main \
-e playbook_env_source=/opt/infra/testlab \
-e '{"ansible_target_roles_list": ["my_namespace.mysql"]}' \
--tags tech
Example metadata.yml (platform level)
# proxmox/metadata.yml
proxmox_api_host: 192.168.1.10
proxmox_api_user: root@pam
proxmox_api_token_id: ansible
proxmox_subnet_prefix: "10.0.0"
proxmox_gateway: "10.0.0.1"
proxmox_bridge: vmbr0
Usage
Running the main playbook
# Deploy everything in testlab on all nodes
ansible-playbook silvarion.provision.main \
-e playbook_env_source=/opt/infra/testlab \
-e playbook_platform=proxmox \
-e playbook_node=all \
-e playbook_category=all \
-e playbook_server_type=all \
-e playbook_tech=all
# Deploy only MySQL containers on pvetest, base category
ansible-playbook silvarion.provision.main \
-e playbook_env_source=/opt/infra/testlab \
-e playbook_platform=proxmox \
-e playbook_node=pvetest \
-e playbook_category=base \
-e playbook_server_type=lxc \
-e playbook_tech=mysql
# Delete all base containers on pvetest (requires explicit confirmation)
ansible-playbook silvarion.provision.main \
-e playbook_env_source=/opt/infra/testlab \
-e playbook_platform=proxmox \
-e playbook_node=pvetest \
-e playbook_category=base \
-e playbook_server_type=lxc \
-e playbook_tech=all \
-e playbook_action=delete \
-e playbook_confirm_delete=true
# Run only the tech configuration phase (assumes containers/VMs already exist)
ansible-playbook silvarion.provision.main \
-e playbook_env_source=/opt/infra/testlab \
--tags tech
Using the inventory role individually
# In your own playbook
- name: Build inventory from an environment directory
hosts: localhost
gather_facts: false
roles:
- role: silvarion.provision.inventory
vars:
inventory_env_source: /opt/infra/testlab
inventory_platform: proxmox
inventory_node: pvetest
inventory_category: base
inventory_server_type: lxc
inventory_tech: mysql
See roles/inventory/README.md and roles/proxmox/README.md for each role's
full variable reference — kept there, next to the code, instead of duplicated
here where it drifts out of sync.
Playbook Structure
| File | What it does |
|---|---|
playbooks/main.yml |
Entry point — imports dynamic_inventory.yml then infra_work.yml |
playbooks/dynamic_inventory.yml |
Runs the inventory role to parse the env dir and register hosts |
playbooks/infra_work.yml |
3 plays: provision (Play 1), install missing tech roles on the controller (Play 2, tag tech), configure via SSH (Play 3, tag tech) |
playbooks/tech/run_role.yml |
Shared include_tasks — promotes one ansible_roles_list entry's vars, then include_role |
Run only the tech-configuration phase (Play 2 + Play 3) with --tags tech,
which assumes the containers/VMs from Play 1 already exist.
Playbook-level Variables (playbook_*)
Set as -e on silvarion.provision.main; each maps to the underlying role
variable for that play.
| Variable | Default | Description |
|---|---|---|
playbook_env_source |
(required) | Local path or git URL to the environment directory |
playbook_domain |
"" |
Domain suffix passed to the inventory role |
playbook_platform |
proxmox |
Scope filter: platform subdirectory name, or all |
playbook_node |
all |
Scope filter: node name, or all |
playbook_category |
all |
Scope filter: category name, or all |
playbook_server_type |
all |
Scope filter: lxc, vms, or all |
playbook_tech |
all |
Scope filter: tech stack name, or all |
playbook_purpose |
all |
Scope filter: purpose name, or all |
playbook_action |
create |
create or delete |
playbook_confirm_delete |
false |
Must be true to allow playbook_action=delete |
playbook_throttle |
1 |
Max concurrent provisioning operations |
playbook_bootstrap |
true |
Run the proxmox role's LXC bootstrap steps |
playbook_secure_logging |
true |
no_log on tasks handling secrets |
playbook_debug |
false |
Verbose debug output |
ansible_roles_list and ansible_target_roles_list are set per-host (leaf
all.yml / host vars / -e), not as playbook_* vars — see
Infrastructure Repository Structure above.
Sensitive Variables
Never store these in plaintext. Use ansible-vault, environment variables, or
a secrets lookup (silvarion.provision.openbao / silvarion.secrets.openbao):
# Via ansible-vault encrypted group_vars
ansible-vault encrypt_string 'my-api-secret' --name proxmox_api_token_secret
# Or via an OpenBao lookup inline in a leaf all.yml — evaluated lazily,
# raises AnsibleError (not an empty string) if the lookup fails
proxmox_api_token_secret: "{{ lookup('silvarion.provision.openbao',
server='https://openbao.example.com:8200',
path='proxmox/ansible', key='api_token_secret',
action='read') }}"
Collection Structure
silvarion.provision/
├── galaxy.yml # Collection metadata and dependencies
├── meta/
│ └── runtime.yml # Ansible version requirement
├── plugins/
│ └── lookup/
│ ├── env_parser.py # Walks the env dir, merges the 4-layer hierarchy
│ └── openbao.py # OpenBao KV read/write/generate lookup
├── playbooks/
│ ├── main.yml # Entry point
│ ├── dynamic_inventory.yml # Inventory registration
│ ├── infra_work.yml # 3-play provision/install/configure pipeline
│ ├── tech/run_role.yml # Shared per-role include
│ └── plugin_test.yml # End-to-end test for the openbao lookup
├── requirements.yml # Dependency installation shortcut
└── roles/
├── inventory/ # Parse env dir, register hosts
└── proxmox/ # Create/delete LXC containers and VMs
License
MIT
Author
Jesus Alejandro Sanchez Davila jsanchez.consultant@gmail.com