Overview

Date: 2026-05-11

Time: 15:35

Overview

This is a production deployment script for catbeez-arcade, a web application hosting browser games at catbeez.com. It uses FTL2's automation API to provision a Linode VPS, harden the server (firewall, SSH, SELinux, fail2ban), configure Caddy as a reverse proxy, and deploy the application with game assets. It demonstrates a complete real-world FTL2 workflow: cloud provisioning → bootstrap → hardening → application deployment → verification.

Usage Patterns

The script is a standalone uv run script with inline dependency declarations. Run it directly:


./deploy-catbeez.py
# or
uv run deploy-catbeez.py

The core pattern is async with automation(...) as ftl: which creates an FTL2 session with state tracking and secret bindings. All operations flow through the ftl object:


# Cloud provisioning via community module
result = await ftl.community.general.linode_v4(label='catbeez-prod', ...)
linode_ip = result["instance"]["ipv4"][0]

# Host registration and targeting
ftl.add_host(hostname='catbeez-prod', ansible_host=linode_ip, ansible_user='admin', ansible_become=True)

# Module calls on specific hosts
await ftl["catbeez-prod"].copy(content=CADDYFILE, dest='/etc/caddy/Caddyfile', mode='0644')
await ftl["catbeez-prod"].service(name='caddy', state='started', enabled=True)

Key pattern: host re-registration — the script first connects as root to bootstrap an admin user, then re-registers the same hostname with ansibleuser='admin' and ansiblebecome=True for subsequent privileged operations.

API and Configuration

State file: state-prod.json — tracks idempotent state across runs.

Secret bindings: Maps module names to environment variables for automatic secret injection:


secret_bindings={"community.general.linode_v4": {"access_token": "LINODE_TOKEN"}}

Requires LINODE_TOKEN environment variable to be set.

Constants:

Key Behaviors

Relationships