Date: 2026-05-11
Time: 15:35
This is a complete FTL2 deployment script that provisions a Linode cloud instance, hardens it, and deploys the catbeez-arcade web application — a collection of browser-based games served behind Caddy as a reverse proxy. It demonstrates FTL2 used as a full infrastructure-as-code tool: cloud provisioning, DNS configuration, security hardening, user management, and application deployment in a single async script.
The script is a standalone uv run script with inline dependency metadata — no separate install step needed:
# Set required secrets as environment variables
export LINODE_TOKEN="..."
export CLOUDFLARE_API_TOKEN="..."
# Run the deployment
uv run deploy.py
The core pattern is async with automation(...) as ftl: which creates a managed FTL2 session with state tracking and secret bindings. All operations flow through ftl — cloud module calls (ftl.community.general.linodev4), host-targeted operations (ftl["arcade"].shell(...)), and host management (ftl.addhost(...)).
Key invocation patterns:
ftl: await ftl.community.general.linode_v4(...)await ftl["arcade"].copy(...), await ftl["arcade"].service(...)becomeuser: await ftl["arcade"].shell(cmd=..., becomeuser='catbeez')ftl.addhost(hostname='arcade', ..., ansiblebecome=True)Secret bindings map module names to environment variables — FTL2 injects secrets automatically:
secret_bindings={
"community.general.linode_v4": {"access_token": "LINODE_TOKEN"},
"community.general.cloudflare_dns": {"api_token": "CLOUDFLARE_API_TOKEN"},
}
State file (state.json) enables idempotency — FTL2 tracks what has been provisioned.
Environment variables required:
LINODE_TOKEN — Linode API access tokenCLOUDFLAREAPITOKEN — Cloudflare API token for DNS managementLocal file dependencies:
jail.local — fail2ban configuration (relative path, must exist alongside script)GAMES_SRC pathWHEEL path1. Two-phase host registration: Connects first as root to bootstrap an admin user, then re-registers the host as admin with ansible_become=True for sudo-based privilege escalation. This is a common FTL2 pattern for fresh instances.
2. Security-first ordering: All hardening (firewall, SSH lockdown, SELinux, crypto policies, fail2ban, service minimization) runs before any application deployment — so if hardening fails, no app content is exposed.
3. Firewall strategy: Uses firewalld's drop zone (silently drops all uninvited traffic) with explicit allowances for HTTP, HTTPS, and SSH from a specific IP range (136.56.0.0/16).
4. SELinux compatibility: Sets httpdcannetwork_connect boolean so Caddy can reverse-proxy to localhost:8000 under SELinux enforcing mode.
5. Idempotent-ish shell commands: Uses guards like which uv || (install uv) and grep -qF ... || echo ... to make shell commands safe to re-run, though heavy use of shell over dedicated modules reduces idempotency guarantees.
6. Uses a private Linode image (private/37121878) — Fedora 43 with Caddy pre-installed, avoiding runtime package installation.
ftl2.automation context manager, host targeting (ftl["hostname"]), addhost, waitforcommunity.general.linodev4 (cloud provisioning), community.general.cloudflaredns (DNS)user, shell, copy, file, servicecatbeez-games (game assets), catbeez-arcade (Python web app served on port 8000)play.catbeez18.com to localhost:8000