3rd-party Ludus source from OsmSec. docs.ludus.cloud/docs/using-ludus/sources
ludus ludus-source

README.md

Ludus Source — OsmSec #

Packer templates for Ludus, published by OsmSec. Six images across Ubuntu, Kali and Debian, ready to deploy in any range.

Quick start #

ludus source add https://tangled.org/osmsec.xyz/ludus-source-osmsec --all
ludus templates build -n osmsec-ubuntu-26.04-x64-server-template

Adding a source registers the templates; building turns them into VM images. Build one image and everyone on the host can clone it.

Requires Ludus 2.2.0 or later.

Templates #

Name Base CPU RAM Disk Login
osmsec-ubuntu-26.04-x64-server-template Ubuntu 26.04.1 LTS 1 2 GB 20 GB localuser:password
osmsec-ubuntu-26.04-x64-desktop-template Ubuntu 26.04.1 LTS 2 4 GB 25 GB localuser:password
osmsec-ubuntu-24.04-x64-server-template Ubuntu 24.04.5 LTS 1 2 GB 20 GB localuser:password
osmsec-ubuntu-24.04-x64-desktop-template Ubuntu 24.04.5 LTS 2 4 GB 25 GB localuser:password
osmsec-kali-2026.2-x64-desktop-template Kali Linux 2026.2 (XFCE) 2 4 GB 25 GB kali:kali
osmsec-debian-13-x64-server-template Debian 13.7 (Trixie) 1 1 GB 10 GB debian:debian

The name is the vm_name in each template — use it as the template: value in a range config. The osmsec- prefix keeps these images identifiable and prevents name collisions with built-in templates.

Logging in #

Each image provisions the account Ludus expects for its distribution:

Distro Account Defined by
Ubuntu localuser:password group_vars/ubuntu.yml
Kali kali:kali group_vars/kali.yml
Debian debian:debian group_vars/debian.yml

These names aren't a preference. Ludus uses them for Ansible over SSH when provisioning a range, and no range-config field overrides them. An image with a different account will build and boot fine, then fail deployment with UNREACHABLE! ... Invalid/incorrect password.

All accounts have passwordless sudo.

Building #

# One template
ludus templates build -n osmsec-debian-13-x64-server-template

# Everything
ludus templates build -n all

Each build downloads its ISO and installs unattended, taking roughly 10–15 minutes for a server image and 20–30 for a desktop image depending on network and storage. Re-sync after changes without rebuilding:

ludus source sync

Using a template #

ludus:
  - vm_name: "{{ range_id }}-kali"
    hostname: kali
    template: osmsec-kali-2026.2-x64-desktop-template
    vlan: 10
    ip_last_octet: 10
    ram_gb: 8
    cpus: 4
    linux: true
ludus range config set -f range-config.yml
ludus range deploy

CPU and RAM in a range config override the template defaults. Disk does not — a range config has no disk field, so each range VM inherits the template's disk size, and changing it later means rebuilding the template. Size disk generously; memory and CPU are cheap to adjust per range.

How it works #

The Ubuntu images install via cloud-init's subiquity autoinstall (http/user-data). The Kali and Debian images are Debian-based and use a debian-installer preseed (http/*-preseed.cfg) fetched over HTTP.

Both mechanisms do the same three things:

  • create the Ludus account, with passwordless sudo and SSH enabled;
  • install the minimum packages needed in a range;
  • clear SSH host keys and /etc/machine-id, so every clone generates its own identity on first boot instead of sharing the image's.

No provisioners run during a build. Everything in a .pkr.hcl beyond sizing and the ISO is Proxmox and Ludus wiring.

Why resolvconf is installed #

Ludus configures a VM's DNS by writing dns-nameservers <router> into /etc/network/interfaces. That directive is only acted on by the ifupdown resolvconf hook, so without the resolvconf package it is silently ignored.

This matters most for the range router: it boots with an empty /etc/resolv.conf, and every apt-get during deployment fails to resolve deb.debian.org. All templates therefore install resolvconf, and validate_source.py fails any template that drops it.

Repository layout #

source.yml                     source metadata
templates/
  ubuntu-26.04-x64-server/     one directory per template
    *.pkr.hcl                  Packer config: sizing, ISO, Proxmox wiring
    icon.png                   catalog icon
    http/                      installer payload (user-data or preseed)
tools/                         validation scripts

Working on templates #

python3 tools/validate_source.py   # template structure, accounts, sizing
./tools/check_isos.sh              # ISO checksums against each vendor

validate_source.py applies the same rules Ludus does, plus checks for the mistakes that have actually broken builds here: missing Ludus variables, accounts that don't match group_vars, preseed installs that forget to enable SSH, and wrong boot devices. It runs on a stock Python with no third-party packages. Run both scripts before pushing.

tools/sha512crypt.py generates password hashes for the Ubuntu user-data files (Python 3.13 removed the stdlib crypt module):

python3 tools/sha512crypt.py yourpassword

ISO storage #

Each template downloads its ISO with iso_download_pve = true, which hands the download to the Proxmox node. The node names the file from the URL, so it lands in storage under its real name:

local:iso/debian-13.7.0-amd64-netinst.iso

Without that flag Packer downloads the ISO itself and uploads it from its local cache, where the file is keyed by checksum — leaving storage full of files like 835a50e8f0f00897249604c3b931dbb5d953f672.iso. The node still verifies iso_checksum during the download; a mismatch fails the task and the build.

These are lab images with public default credentials. Don't put real data on them, and don't reuse them outside a range.

To pin a build, register the source at a specific commit:

ludus source add https://tangled.org/osmsec.xyz/ludus-source-osmsec --ref <commit-sha>

License #

MIT — see LICENSE.