Deploys a CTF lab on Proxmox by:
- generating cloud-init ISO images for a gateway VM, a ForcAD VM, and one VM per team
- uploading those ISOs to Proxmox storage
- cloning all VMs from a prepared Proxmox template
- applying VM networking and hardware configuration
- starting the full lab
The script reads its configuration from a deploy.yaml file and expects supporting payload files under data/.
The project creates these machines:
ctf-gw: gateway VM for the labforcad: ForcAD VMteam-1throughteam-N: one VM per team
Network layout is based on isolated /31 point-to-point links:
- ForcAD:
10.10.10.10/31<-> gateway10.10.10.11/31 - Teams:
10.58.57.x/31links generated per team - WireGuard gateway IP defaults to
10.10.10.1
You need:
uvgenisoimageormkisofssshpass(only needed to automatically download the WireGuard configs from the gateway)- access to a Proxmox VE host
- a prepared Proxmox VM template with cloud-init support
- a Proxmox API token with permission to clone VMs, configure them, and upload ISOs
System packages example on Debian/Ubuntu:
sudo apt-get update
sudo apt-get install -y genisoimage sshpassInstall uv if it is not already present:
curl -LsSf https://astral.sh/uv/install.sh | shThis project is a standard uv project: dependencies are declared in pyproject.toml, so uv handles the environment automatically.
Run in place (recommended while developing):
uv sync # create .venv and install dependencies from pyproject.toml
uv run main.pyOr install it as a tool for a globally available ad-deploy command:
uv tool install .
ad-deployEither way, uv resolves the same dependencies (requests, urllib3, proxmoxer, pyyaml, jinja2).
Before running the deploy script, make sure these paths exist:
deploy.yamldata/config.yamldata/checkers/data/services/
The script zips data/checkers/ and data/services/ and injects them into the generated cloud-init payloads.
Start from the example file:
cp deploy.example.yaml deploy.yamlThen edit deploy.yaml with your real Proxmox and network values.
proxmox.host: full Proxmox URL, for examplehttps://pve.example.org:8006proxmox.port: Proxmox API port, usually8006proxmox.node: target Proxmox node nameproxmox.token_id: token id in the formuser@realm!tokennameproxmox.token_secret: token secretproxmox.verify_tls:trueto verify TLS,falseto skip
template.vmid: VMID of the prepared source templatetemplate.clone_full:truefor full clone,falsefor linked clonestorage.iso: Proxmox storage name where ISOs are uploaded
The storage must allow ISO image content.
lab.n_teams: number of team VMs to createvmids.gateway: gateway VMIDvmids.forcad: ForcAD VMIDvmids.team_base: base VMID for team VMs
Team VMIDs are generated as team_base + team_number.
network.ctf_bridge: internal bridge for the isolated lab networknetwork.external.bridge: external bridge for gatewaynet1network.external.vlan: optional VLAN tag fornet1(blank for none)network.external.ip: optional external IP for the gatewaynetwork.external.gateway: optional external gatewaynetwork.dns_servers: list of DNS serversip_plan.forcad_ip: ForcAD address with CIDRip_plan.forcad_gw_side: gateway-side address with CIDR on the ForcAD linkip_plan.wg_gw_ip: WireGuard gateway IP
resources.team.cores/resources.team.ram_mbresources.forcad.cores/resources.forcad.ram_mbresources.gateway.cores/resources.gateway.ram_mb
lab.allow_root_ssh:trueto allow root password SSH on team VMslab.pool: Proxmox pool to use or createwireguard.port,wireguard.endpoint: WireGuard settingswireguard.scp: export the generated WireGuard configs to this machine after the lab startsenabled:trueto download them automaticallyhost: gateway SSH address (defaults tonetwork.external.ip)port/user: SSH target (default22/root)output_dir: local directory for the downloaded archives (defaultwg-configs/)wait_timeout_sec,poll_interval_sec: how long to wait for the gateway to finish- requires
sshpasson this machine; downloadsadmin.zipandteamN.zipfrom the gateway
frp.enabled,frp.server,frp.port,frp.ver: FRP settings for the gateway
uv sync
cp deploy.example.yaml deploy.yamlThen edit deploy.yaml with your real Proxmox and network values.
Create the required data structure:
data/
├── config.yaml
├── checkers/
└── services/
Run the script with uv:
uv run main.pyWhen you run uv run main.py, the script will:
- load configuration from
deploy.yaml - create a Proxmox pool if needed
- build temporary cloud-init ISO files in a timestamped local directory
- zip and include
data/checkers/anddata/services/ - upload all generated ISOs to Proxmox storage
- write a credentials file like
creds_YYYYMMDD_HHMMSS.json - destroy any existing VMs using the configured VMIDs
- clone new VMs from the template
- configure CPU, memory, NICs, IP config, and attached cloud-init ISOs
- start all VMs
- download the WireGuard config archives (
admin.zip,teamN.zip) from the gateway via scp (if enabled)
Each run generates:
- a temporary local directory like
cloud-init_YYYYMMDD_HHMMSS/ - a credentials file like
creds_YYYYMMDD_HHMMSS.json - a
wg-configs/directory with the WireGuard client archives (if export is enabled)
The credentials JSON contains generated passwords and addressing information for the deployed lab.
- The script destroys existing VMs that use the configured VMIDs before recreating them.
- If ISO uploads fail, the script stops before VM recreation completes.
- The Proxmox storage configured by
storage.isomust support ISO uploads. - If neither
genisoimagenormkisofsis installed, ISO creation fails. deploy.yamlis gitignored; commitdeploy.example.yamlinstead. Keep the Proxmox token secret out of version control.
Run uv sync to install all dependencies from pyproject.toml into the project environment.
Check:
storage.isopoints to the correct Proxmox storage- the storage has
ISO imagecontent enabled - the API token has permission to allocate space on that storage
Install genisoimage:
sudo apt-get install -y genisoimageIf your Proxmox instance uses a self-signed certificate, set:
proxmox:
verify_tls: falseUse that only when appropriate for your environment.