tftp-os
TFTP-based firmware provisioning library with optional extras for power control, hypervisors, cloud, and observability.
Overview
tftp-os is a firmware provisioning library. Its core mission is simple: given a device's MAC address (or hostname, subnet, serial number, or group), resolve which firmware file to serve.
Beyond the core, tftp-os ships modules for common provisioning workflows: BMC power control, hypervisor backends (libvirt, bhyve, Hyper-V, VMM), cloud-init config generation, cloud image management, cluster provisioning, audit logging, Prometheus metrics, webhooks, console access, and RBAC.
Note: All modules ship with every install. The optional extras in
pyproject.tomlare documentation-only markers that group modules by feature area. They carry no additional pip dependencies today but provide a hook for adding external requirements in the future. See docs/SCOPE.md for details.
pip install tftpos # all modules included
pip install tftpos[tls] # + cryptography (TLS cert generation)
pip install tftpos[postgres] # + psycopg2 (PostgreSQL backend)
pip install tftpos[mysql] # + pymysql (MySQL/MariaDB backend)
pip install tftpos[all] # all external deps (tls, postgres, mysql)
tftp-os is a reusable library -- it contains no CLI, no REST API routes, and no UI. It is designed as a foundation that anyone can build on. See flossware-tftpos for a reference application with desktop, mobile, and web frontends.
tftp-os works on its own for scenarios where you need to serve firmware to devices -- router firmware (OpenWRT, DD-WRT, FreshTomato), embedded systems, IoT devices, or any TFTP-based provisioning workflow.
pxe-os builds on tftp-os as a decorator layer, adding iPXE script generation, autoinstall templates (kickstart, preseed, autoinstall, autoyast), OS installer plugins, DHCP configuration, and a web UI. If you need full PXE boot provisioning for operating systems, use pxe-os. If you need a firmware serving foundation, use tftp-os directly.
Project Status
- 1072 tests, all passing
- Python 3.10 -- 3.13
- Development Status: Alpha (Development Status :: 3 - Alpha)
tftp-os is alpha software. It has been extracted from PxeOS and has not been deployed in a production environment. The test suite validates logic correctness; a lab proof (
test_lab_tftp.py) demonstrates the full MAC → stage → TFTP transfer → checksum verify loop using tftpy. See Known Limitations for details.
Quick Start
# Install
pip install -e .
# Create configuration directory
sudo mkdir -p /etc/tftpos/profiles
# Create a config file
cat > /etc/tftpos/tftpos.toml << 'EOF'
[server]
host = "0.0.0.0"
port = 8443
[paths]
tftp_root = "/srv/tftp"
distro_root = "/srv/tftpos/distros"
data_dir = "/etc/tftpos"
[database]
backend = "sqlite"
url = "sqlite:///tftpos.db"
EOF
# Create host rules
cat > /etc/tftpos/hosts.toml << 'EOF'
[[host]]
mac = "aa:bb:cc:dd:ee:ff"
profile = "my-router"
os_family = "openwrt"
os_version = "23.05"
[[host]]
mac_prefix = "52:54:00"
profile = "lab-device"
os_family = "openwrt"
os_version = "23.05"
EOF
# Create a profile
cat > /etc/tftpos/profiles/my-router.toml << 'EOF'
[profile]
name = "my-router"
os_family = "openwrt"
os_version = "23.05"
arch = "x86_64"
EOF
Programmatic usage
from tftpos.config import load_config, load_hosts
from tftpos.engine import FirmwareEngine
from tftpos.matcher import HostMatcher
from tftpos.plugins.static import StaticFirmwarePlugin
from tftpos.registry import PluginRegistry
# Load configuration
config = load_config(Path("/etc/tftpos/tftpos.toml"))
rules = load_hosts(Path("/etc/tftpos/hosts.toml"))
# Build the engine with the built-in StaticFirmwarePlugin
registry = PluginRegistry()
registry.register(
StaticFirmwarePlugin,
distro_root="/srv/tftpos/distros",
os_family="openwrt",
supported_versions=["23.05"],
)
matcher = HostMatcher(rules)
engine = FirmwareEngine(registry, matcher, config)
# Resolve a MAC to its firmware file
firmware_path = engine.serve(mac="aa:bb:cc:dd:ee:ff")
# Stage the firmware under tftp_root for an external TFTP server
staged_path = engine.stage(mac="aa:bb:cc:dd:ee:ff")
Integration with TFTP servers
tftp-os resolves firmware paths and stages files — it does not speak the TFTP protocol. Pair it with any TFTP server (dnsmasq, tftpd-hpa, atftpd):
- tftp-os resolves MAC → firmware file path via
engine.serve() engine.stage()copies/links the firmware intotftp_root- Your TFTP server serves files from
tftp_rootto the device - tftp-os tracks provisioning state
See docs/DEPLOYMENT.md for TFTP server setup.
Architecture
+-----------+
| TOML |
| Config |
+-----------+
|
+------------------+------------------+
| | |
+-----------+ +-------------+ +---------------+
| hosts.toml| | tftpos.toml | | profiles/*.toml|
+-----------+ +-------------+ +---------------+
| | |
v v v
+----------+ +--------------+ +----------------+
|HostMatcher| |TftpOSConfig | |ProvisionProfile|
+----------+ +--------------+ +----------------+
| | |
+------------------+------------------+
|
+------v------+
|FirmwareEngine|
+------+------+
|
+------v------+
|PluginRegistry|
+------+------+
|
+------v------+
|FirmwarePlugin|
+------+------+
|
firmware path
Data flow:
HostMatcherreceives a MAC address (and optional hostname, subnet, serial, groups, arch) and finds the best-matchingHostRuleFirmwareEngineloads theProvisionProfilefor that rule from a TOML file underdata_dir/profiles/- The engine looks up the
FirmwarePluginfor the rule'sos_familyin thePluginRegistry - The plugin validates the profile and returns the firmware file path
Configuration
tftp-os uses TOML configuration files. There are three types:
Server config (tftpos.toml)
[server]
host = "0.0.0.0"
port = 8443
[paths]
tftp_root = "/srv/tftp"
distro_root = "/srv/tftpos/distros"
data_dir = "/etc/tftpos"
[tls]
cert = "/etc/tftpos/tls/cert.pem"
key = "/etc/tftpos/tls/key.pem"
auto_generate = true
[auth]
enabled = false
[database]
backend = "sqlite" # sqlite, postgresql, mariadb
url = "sqlite:///tftpos.db"
[rate_limit]
enabled = false
tftp_requests_per_minute = 300.0
tftp_burst = 50
api_requests_per_minute = 60.0
api_burst = 20
auth_requests_per_minute = 10.0
auth_burst = 5
[logging]
level = "INFO"
json_format = false
# log_file = "/var/log/tftpos/tftpos.log"
syslog_enabled = false
journald_enabled = false
[audit]
enabled = true
# log_file = "/var/log/tftpos/audit.log"
buffer_size = 1000
[discovery]
enabled = false
service_name = "tftpos"
# [[webhooks]]
# url = "https://example.com/webhook"
# events = ["provision.started", "provision.complete"]
# secret = "your-hmac-secret"
Host rules (hosts.toml)
# Exact MAC match (highest priority)
[[host]]
mac = "aa:bb:cc:dd:ee:ff"
profile = "my-router"
os_family = "openwrt"
os_version = "23.05"
# MAC prefix match (e.g., all QEMU VMs)
[[host]]
mac_prefix = "52:54:00"
profile = "lab-device"
os_family = "openwrt"
os_version = "23.05"
# Hostname pattern match
[[host]]
hostname_pattern = "router-*.lab.example.com"
profile = "lab-router"
os_family = "ddwrt"
os_version = "2024"
# Subnet match
[[host]]
subnet = "10.0.0.0/24"
profile = "office-device"
os_family = "openwrt"
os_version = "23.05"
# Catch-all (lowest priority)
[[host]]
profile = "default"
os_family = "openwrt"
os_version = "23.05"
# With BMC power control
[[host]]
mac = "11:22:33:44:55:66"
profile = "managed-device"
os_family = "openwrt"
os_version = "23.05"
bmc_host = "192.168.1.100"
bmc_user = "admin"
bmc_password = "{{secret:bmc_password}}"
bmc_driver = "ipmi"
Profiles (profiles/*.toml)
[profile]
name = "my-router"
os_family = "openwrt"
os_version = "23.05"
arch = "x86_64"
firmware = "bios" # bios or uefi
vendor = "openwrt"
install_url = "https://downloads.openwrt.org/releases/23.05.5/targets/x86/64/"
packages = ["luci", "wireguard-tools"]
post_scripts = ["#!/bin/sh\necho 'Provisioning complete'"]
[profile.network]
hostname = "router-01.lab.example.com"
gateway = "10.0.0.1"
nameservers = ["10.0.0.1", "8.8.8.8"]
[profile.disk]
size = "1G"
[profile.extra]
custom_key = "custom_value"
Host Matching
HostMatcher uses tiered priority matching. When multiple rules could match a request, the most specific match wins:
| Tier | Match type | Example |
|---|---|---|
| 0 | Exact MAC | mac = "aa:bb:cc:dd:ee:ff" |
| 1 | MAC prefix | mac_prefix = "52:54:00" |
| 2 | Hostname pattern | hostname_pattern = "router-*.lab" |
| 3 | Subnet | subnet = "10.0.0.0/24" |
| 4 | Serial number | serial = "SN123456" |
| 5 | Group | group = "lab-devices" |
| 6 | Architecture | arch = "aarch64" |
| 7 | Catch-all | No match criteria set |
Within the same tier, rules are sorted by priority (lower number = higher priority, default 100).
Hostname patterns use fnmatch glob syntax. Subnet matching uses standard CIDR notation.
Plugin Architecture
tftp-os uses a plugin system for firmware handling. Each plugin implements the FirmwarePlugin abstract base class:
from tftpos.plugins.base import FirmwarePlugin
from tftpos.models import ProvisionProfile
class MyFirmwarePlugin(FirmwarePlugin):
@property
def os_family(self) -> str:
return "mybrand"
@property
def supported_versions(self) -> list[str]:
return ["1.0", "2.0"]
def firmware_path(self, profile: ProvisionProfile) -> str:
return f"/srv/firmware/mybrand/{profile.os_version}/firmware.bin"
def validate_profile(self, profile: ProvisionProfile) -> list[str]:
errors = super().validate_profile(profile)
if not profile.install_url:
errors.append("install_url is required")
return errors
Registration
Via entry points (for installable packages):
# In your package's pyproject.toml
[project.entry-points."tftpos.plugins"]
mybrand = "my_plugins.mybrand:MyFirmwarePlugin"
Manual registration (instance or class with kwargs):
registry = PluginRegistry()
# Zero-arg plugins
registry.register(MyFirmwarePlugin)
# Plugins requiring constructor args (e.g. StaticFirmwarePlugin)
registry.register(
StaticFirmwarePlugin,
distro_root="/srv/tftpos/distros",
os_family="openwrt",
supported_versions=["23.05"],
)
Via load_builtins() (for local module paths):
registry = PluginRegistry()
registry.load_builtins(["my_plugins.mybrand", "my_plugins.ddwrt"])
See docs/PLUGIN_GUIDE.md for a complete tutorial.
Provisioning State Machine
tftp-os tracks each device through a provisioning lifecycle:
REGISTERED ──> BOOTING ──> INSTALLING ──> POST_INSTALL ──> COMPLETE
│ │ │ │
└─────────────┴────────────┴──────────────┴──────> FAILED
| State | Description |
|---|---|
REGISTERED |
Host registered, waiting for boot request |
BOOTING |
Boot request served |
INSTALLING |
Installation config served |
POST_INSTALL |
Post-install scripts running |
COMPLETE |
Provisioning finished successfully |
FAILED |
Provisioning failed |
from tftpos.state import ProvisionTracker, ProvisionState
tracker = ProvisionTracker()
tracker.register("aa:bb:cc:dd:ee:ff", "my-router", "openwrt", "23.05")
tracker.transition("aa:bb:cc:dd:ee:ff", ProvisionState.BOOTING)
# Boot-once: disable netboot after completion
tracker.disable_netboot("aa:bb:cc:dd:ee:ff")
assert not tracker.is_netboot_enabled("aa:bb:cc:dd:ee:ff")
# Callbacks
tracker.on_state_change(ProvisionState.COMPLETE, lambda record: print(f"Done: {record.mac}"))
Storage Backends
tftp-os supports multiple storage backends for provisioning state:
| Backend | Config url |
Use case |
|---|---|---|
| SQLite | sqlite:///tftpos.db |
Single-node (default) |
| PostgreSQL | postgresql://user:pass@host/db |
Multi-node / production |
| MySQL/MariaDB | mysql+pymysql://user:pass@host/db |
Existing MySQL infrastructure |
| JSON | N/A (programmatic) | Development / testing |
| In-memory | N/A (programmatic) | Testing |
# tftpos.toml
[database]
backend = "postgresql"
url = "postgresql://tftpos:secret@db.example.com/tftpos"
Install the appropriate driver:
pip install tftpos[postgres] # PostgreSQL
pip install tftpos[mysql] # MySQL/MariaDB
Cloud-Init
Generate cloud-init configuration for bare-metal or VM provisioning:
from tftpos.cloud_init import generate, create_config_drive
from tftpos.models import ProvisionProfile
profile = ProvisionProfile(
name="server-01",
os_family="ubuntu",
os_version="24.04",
network={"hostname": "server-01.example.com"},
packages=["docker.io", "nginx"],
post_scripts=["#!/bin/bash\nsystemctl enable nginx"],
)
# Generate config files
config = generate(profile)
print(config.user_data) # cloud-config YAML
print(config.meta_data) # instance metadata JSON
print(config.network_config) # network-config v2
# Create a config-drive ISO
iso_path = create_config_drive(profile, Path("/tmp/cloud-init.iso"))
Cloud Images
Import and manage cloud images (qcow2, raw, vmdk, vhd, vhdx):
from tftpos.cloud_image import import_cloud_image, list_images, convert_image
# Import from URL
image = import_cloud_image(
source="https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img",
os_family="ubuntu",
vendor="ubuntu",
version="24.04",
)
# Convert format
convert_image(Path("image.qcow2"), "raw", Path("image.raw"))
# List all images
for img in list_images():
print(f"{img.name}: {img.format} ({img.size_bytes} bytes)")
Cluster Provisioning
Define and provision groups of machines in coordinated order:
# cluster.toml
[cluster]
name = "lab-cluster"
provisioning_order = ["control-plane", "workers"]
[[cluster.hosts]]
hostname = "cp-01"
mac = "aa:bb:cc:00:00:01"
role = "control-plane"
profile = "k8s-control"
[[cluster.hosts]]
hostname = "worker-01"
mac = "aa:bb:cc:00:00:02"
role = "workers"
profile = "k8s-worker"
[[cluster.hosts]]
hostname = "worker-02"
mac = "aa:bb:cc:00:00:03"
role = "workers"
profile = "k8s-worker"
from tftpos.cluster import ClusterManager, ClusterStore, parse_cluster_toml
store = ClusterStore(Path("/etc/tftpos"))
manager = ClusterManager(store)
# Parse and create
with open("cluster.toml") as f:
definition = parse_cluster_toml(f.read())
manager.create_cluster(definition)
# Provision in order (control-plane first, then workers)
manager.start_provisioning("lab-cluster")
BMC Power Control
Control server power via IPMI or Redfish:
from tftpos.power import PowerManager, IPMIDriver, RedfishDriver
pm = PowerManager()
# Register IPMI-controlled device
pm.register("aa:bb:cc:dd:ee:ff", IPMIDriver(
host="192.168.1.100",
username="admin",
password="secret",
))
# Register Redfish-controlled device
pm.register("11:22:33:44:55:66", RedfishDriver(
host="192.168.1.101",
username="admin",
password="secret",
))
# Control power
pm.power_on("aa:bb:cc:dd:ee:ff")
pm.set_boot_device("aa:bb:cc:dd:ee:ff", device="pxe")
status = pm.power_status("aa:bb:cc:dd:ee:ff") # "on", "off", "unknown"
BMC credentials in host rules support secret references ({{secret:KEY}}), resolved at runtime by SecretsManager.
Console Access
WebSocket proxy for VNC, SPICE, and serial consoles:
from tftpos.console import ConsoleConfig, ConsoleType, ConsoleProxy
config = ConsoleConfig(
console_type=ConsoleType.VNC,
host="192.168.1.100",
port=5900,
)
proxy = ConsoleProxy(config)
await proxy.connect()
await proxy.send_to_backend(b"data")
response = await proxy.receive_from_backend()
await proxy.close()
Host rules can specify console access directly:
[[host]]
mac = "aa:bb:cc:dd:ee:ff"
profile = "my-server"
os_family = "openwrt"
os_version = "23.05"
console_type = "vnc"
console_endpoint = "192.168.1.100:5900"
Hypervisor Backends
tftp-os can create and manage VMs on four hypervisor platforms:
| Backend | Platform | CLI tools |
|---|---|---|
LibvirtBackend |
Linux (KVM/QEMU) | virsh, virt-install |
BhyveBackend |
FreeBSD | bhyvectl, bhyve |
VmmBackend |
OpenBSD | vmctl |
HyperVBackend |
Windows | PowerShell Hyper-V |
from tftpos.client import detect_hypervisor
# Auto-detect available hypervisor
backend = detect_hypervisor()
if backend:
print(f"Found: {backend.hypervisor_name}")
# Create and start a VM
backend.create_vm(
name="test-vm",
mac="52:54:00:00:00:01",
memory_mb=2048,
vcpus=2,
disk_gb=20,
)
backend.start_vm("test-vm")
Observability
Metrics
Prometheus-compatible metrics in text exposition format:
from tftpos.metrics import render_metrics
print(render_metrics())
# tftpos_provisions_total 0
# tftpos_active_provisions 0
# tftpos_boot_requests_total 0
# ...
Audit logging
Structured audit trail for all provisioning events:
from tftpos.audit import init_audit, get_audit_logger
init_audit()
logger = get_audit_logger()
logger.log_boot_request(mac="aa:bb:cc:dd:ee:ff", profile="my-router")
logger.log_state_transition(
mac="aa:bb:cc:dd:ee:ff",
old_state="registered",
new_state="booting",
)
# Query recent events
events = logger.query(mac="aa:bb:cc:dd:ee:ff", limit=10)
Audit supports file logging, stdout, and syslog output.
Webhooks
Webhook delivery for provisioning lifecycle events:
from tftpos.webhooks import WebhookManager, WebhookConfig
manager = WebhookManager(webhooks=[
WebhookConfig(
url="https://example.com/hook",
events=["provision.started", "provision.complete"],
secret="hmac-secret",
),
])
# Fire an event (async delivery)
manager.fire("provision.started", {"mac": "aa:bb:cc:dd:ee:ff"})
Supported events: provision.started, provision.complete, provision.failed, netboot.disabled.
Webhook payloads are signed with HMAC-SHA256 when a secret is configured. Verify with:
from tftpos.webhooks import verify_signature
valid = verify_signature(payload_bytes, secret, signature_header)
Security
RBAC
Three roles with hierarchical access:
| Role | Permissions |
|---|---|
VIEWER |
Read-only access |
OPERATOR |
Read + write (manage hosts, trigger provisions) |
ADMIN |
Full access (manage API keys, configuration) |
from tftpos.auth import ApiKeyStore, Role
store = ApiKeyStore(Path("/etc/tftpos"))
raw_key, api_key = store.create_key("my-key", Role.OPERATOR)
print(f"API key: {raw_key}") # tftpos_...
# Validate a key
validated = store.validate(raw_key) # Returns ApiKey or None
Enable in config:
[auth]
enabled = true
Rate limiting
Token-bucket rate limiting with three endpoint groups:
[rate_limit]
enabled = true
tftp_requests_per_minute = 300.0
tftp_burst = 50
api_requests_per_minute = 60.0
api_burst = 20
auth_requests_per_minute = 10.0
auth_burst = 5
TLS
Auto-generated self-signed certificates by default. Configure custom certs:
[tls]
cert = "/etc/tftpos/tls/cert.pem"
key = "/etc/tftpos/tls/key.pem"
auto_generate = false
See docs/SECURITY.md for details.
Secrets Management
Two providers for resolving {{secret:KEY}} references in profiles and host rules:
File-based (default): Stores secrets in <data_dir>/secrets.json.
Environment variables: Reads TFTPOS_SECRET_<KEY> environment variables.
from tftpos.secrets import SecretsManager, FileSecretsProvider
provider = FileSecretsProvider(Path("/etc/tftpos"))
provider.set("bmc_password", "my-secret")
manager = SecretsManager(provider)
resolved_profile = manager.resolve_profile(profile) # {{secret:bmc_password}} -> my-secret
Repository Mirrors
Manage local mirrors of package repositories:
from tftpos.repo_mirror import RepoManager, RepoMirror
manager = RepoManager(Path("/etc/tftpos"))
manager.add_mirror(RepoMirror(
name="openwrt-23.05",
source_url="rsync://downloads.openwrt.org/releases/23.05.5/",
local_path="/srv/tftpos/mirrors/openwrt-23.05",
sync_interval=86400,
))
result = manager.sync_mirror("openwrt-23.05")
print(f"Sync {'succeeded' if result.success else 'failed'}")
Named Objects
CRUD store for named distros and hosts, backed by JSON files:
from tftpos.named_objects import NamedObjectStore, NamedDistro, NamedHost
store = NamedObjectStore(Path("/etc/tftpos"))
# Register a distro
store.add_distro(NamedDistro(
name="openwrt-23.05",
os_family="openwrt",
vendor="openwrt",
version="23.05",
kernel_path="/srv/tftpos/distros/openwrt/23.05/vmlinuz",
))
# Register a host
store.add_host(NamedHost(
name="router-01",
mac="aa:bb:cc:dd:ee:ff",
profile="my-router",
hostname="router-01.lab.example.com",
))
# Look up
distro = store.get_distro("openwrt-23.05")
host = store.find_host_by_mac("aa:bb:cc:dd:ee:ff")
ISO Detection
Detect OS family and version from a mounted ISO or image:
from tftpos.iso_detect import detect_iso, is_live_iso
alias = detect_iso(Path("/mnt/iso"))
# DistroAlias(os_family="fedora", vendor="fedora", version="40")
live = is_live_iso(Path("/mnt/iso"))
# True if squashfs/live filesystem found
Supports detection via .treeinfo, .discinfo, README.diskdefines, Debian dists/, OpenBSD MANIFEST/bsd.rd, FreeBSD base.txz, and volume labels.
Structured Logging
Configurable logging with JSON output, syslog, journald, and correlation IDs:
from tftpos.logging_config import configure_logging, LoggingConfig
from tftpos.logging_config import set_correlation_id, get_correlation_id
configure_logging(LoggingConfig(
level="INFO",
json_format=True, # JSON lines output
syslog_enabled=False,
journald_enabled=False,
))
# Correlation IDs for request tracing
cid = set_correlation_id() # generates UUID
# All log records now include cid=<uuid>
Enable in config:
[logging]
level = "INFO"
json_format = true
syslog_enabled = false
journald_enabled = false
Distro Mnemonics
Shorthand aliases for common distro identifiers:
| Alias | OS Family | Vendor | Version |
|---|---|---|---|
fedora40 |
fedora | fedora | 40 |
rhel9 |
fedora | rhel | 9 |
ubuntu2404 |
ubuntu | ubuntu | 24.04 |
noble |
ubuntu | ubuntu | 24.04 |
deb12 |
debian | debian | 12 |
bookworm |
debian | debian | 12 |
fbsd14 |
freebsd | freebsd | 14 |
obsd76 |
openbsd | openbsd | 7.6 |
arch |
arch | arch | latest |
win11 |
windows | windows | 11 |
tumbleweed |
suse | opensuse | tumbleweed |
from tftpos.mnemonics import resolve_mnemonic
alias = resolve_mnemonic("rhel9")
# DistroAlias(os_family="fedora", vendor="rhel", version="9")
40+ built-in aliases are included. Register custom aliases:
from tftpos.mnemonics import get_registry, DistroAlias
registry = get_registry()
registry.register("myrouter", DistroAlias("openwrt", "openwrt", "23.05"))
Relationship to PxeOS
tftp-os is the foundation layer. PxeOS is the decorator layer.
| Concern | tftp-os | PxeOS |
|---|---|---|
| Host matching | MAC, hostname, subnet, serial, group, arch | Same (delegates to tftp-os) |
| Profile loading | TOML-based profiles | Same (delegates to tftp-os) |
| Firmware serving | Generic firmware path | iPXE scripts, kernel + initrd + boot args |
| Autoinstall | Cloud-init only | Kickstart, preseed, autoinstall, autoyast |
| OS plugins | FirmwarePlugin (firmware path) |
OSPlugin extends FirmwarePlugin (boot assets, autoinstall, ISO extraction) |
| Plugin registry | Entry point: tftpos.plugins |
Entry point: pxeos.plugins |
| DHCP | Not managed | Generates dnsmasq/ISC DHCP config |
| Web UI | None | Dashboard with live status |
Dependency direction: PxeOS depends on tftp-os. tftp-os never imports from PxeOS.
# PxeOS engine wraps tftp-os engine (has-a, not is-a)
from tftpos.engine import FirmwareEngine
from pxeos.engine import ProvisioningEngine
engine = ProvisioningEngine(registry, matcher, config)
# engine._firmware is a FirmwareEngine instance
# engine.render_ipxe_script() -- PXE-specific
# engine._firmware.serve() -- tftp-os firmware resolution
Known Limitations
- No production deployment yet -- use the
tftpos.stagingmodule (stage(),unstage(),list_staged()) orengine.stage()to place resolved firmware undertftp_rootfor an external TFTP daemon; lab-proven via tftpy (see docs/HARDWARE_LAB.md); system tftpd + real device test pending - No REST API implementation -- this is intentional; tftp-os is a pure library with no API routes
- Plugin ecosystem -- tftp-os ships
StaticFirmwarePluginfor simple{distro_root}/{os_family}/{os_version}/firmware.binlayouts; for more complex needs, write your own or use PxeOS's OS plugins (see docs/PLUGIN_GUIDE.md) - IPMI/Redfish -- Power control shells out to
ipmitooland makes HTTP requests to Redfish endpoints; not tested against real BMCs - Hypervisor backends -- Shell out to platform-specific CLI tools; tested with mocks only
- Cloud image import -- Requires
qemu-imgandwget/curlon the host
System Requirements
| Requirement | Value |
|---|---|
| Python | >= 3.10 |
| OS | Linux, FreeBSD, OpenBSD, NetBSD |
Dependencies
| Package | Version | Purpose |
|---|---|---|
| jinja2 | >= 3.1 | Template rendering |
| sqlalchemy | >= 2.0 | Database ORM |
| tomli | >= 2.0 | TOML parsing (Python < 3.11) |
Optional dependency groups
# External dependencies (these pull in additional pip packages)
pip install tftpos[tls] # cryptography (TLS cert generation)
pip install tftpos[postgres] # psycopg2 (PostgreSQL)
pip install tftpos[mysql] # pymysql (MySQL/MariaDB)
pip install tftpos[test-tftp] # tftpy (lab integration tests)
# Convenience
pip install tftpos[all] # all external deps (tls, postgres, mysql)
pip install tftpos[dev] # pytest, ruff, mypy, bandit, coverage
The
power,hypervisor,cloud,cluster, andobservabilityextras are documentation-only markers — they carry no additional pip dependencies and all modules ship with every install. They exist to document which feature areas exist and to provide a hook for future external requirements. See docs/SCOPE.md for the full module classification.
Development
git clone https://github.com/FlossWare/tftp-os.git
cd tftp-os
pip install -e ".[dev]"
pytest
# Lint and type-check
ruff check tftpos/
mypy tftpos/
Try it locally
import tempfile
from pathlib import Path
from tftpos.config import load_config, load_hosts
from tftpos.engine import FirmwareEngine
from tftpos.matcher import HostMatcher
from tftpos.registry import PluginRegistry
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
# Minimal config
(tmp_path / "tftpos.toml").write_text("""
[server]
host = "127.0.0.1"
port = 8443
[paths]
tftp_root = "/tmp/tftp"
data_dir = "{tmp}"
[database]
backend = "sqlite"
url = "sqlite://"
""".format(tmp=tmp))
# Host rule
(tmp_path / "hosts.toml").write_text("""
[[host]]
mac = "aa:bb:cc:dd:ee:ff"
profile = "demo"
os_family = "openwrt"
os_version = "23.05"
""")
config = load_config(tmp_path / "tftpos.toml")
rules = load_hosts(tmp_path / "hosts.toml")
engine = FirmwareEngine(PluginRegistry(), HostMatcher(rules), config)
result = engine.serve(mac="aa:bb:cc:dd:ee:ff")
print(result)
See docs/TESTING.md for test structure and coverage targets.
Deployment
See docs/DEPLOYMENT.md for installation, systemd, firewall, TLS, and DHCP/TFTP server setup.
Security
See docs/SECURITY.md for RBAC, API keys, rate limiting, input validation, and vulnerability reporting.
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tftpos-0.9.tar.gz.
File metadata
- Download URL: tftpos-0.9.tar.gz
- Upload date:
- Size: 164.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
530e04ce9b8e503eb153af41f47fbd2ab3810bfb32159062ceb6aae5ef0707f1
|
|
| MD5 |
e0801d29689cfb560c243915e17aa1ab
|
|
| BLAKE2b-256 |
85e468fe3849c646f36fbe061339f626b1b4122f99c628648cbc07d808162ddc
|
File details
Details for the file tftpos-0.9-py3-none-any.whl.
File metadata
- Download URL: tftpos-0.9-py3-none-any.whl
- Upload date:
- Size: 104.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9568cae36fbee90ede970e424337cc38276be22a59225e59f56fb87c980bcebc
|
|
| MD5 |
82d7c652c9f2f92d1edb7337a48116da
|
|
| BLAKE2b-256 |
64e3495d99f293fe10162548589c9af79367fe55f092a1b88ab4ea2472196624
|