towlion-infra is a Bash CLI (towlion-infra) that wraps OpenTofu to provision a single server on DigitalOcean or AWS. The infrastructure is defined in Terraform/OpenTofu HCL.
towlion-infra— Bash entrypoint. Handles env loading, SSH key generation, and delegates totofucommands.main.tf— Root module that conditionally enables eithermodules/awsormodules/digitaloceanbased on thecloud_providervariable.modules/digitalocean/— Droplet, block volume, firewall, SSH key, optional DNS domain + records.modules/aws/— EC2 instance, EBS volume, security group, key pair, optional Route 53 zone + records.cloud-init.sh— User-data script that detects and mounts the data volume at/data.bootstrap-server.sh— Post-provision bootstrap script referenced by module outputs. Runs on the server via scp+ssh.
- Shell style: Bash with
set -euo pipefail. Usedie()for fatal errors. - Provider parity: Both provider modules should provision equivalent resources and expose the same outputs (
server_ip,ssh_command,bootstrap_command,nameservers). - Credentials: Stored in
.env.local(git-ignored). The CLI sources this file viaload_env(). Use standard provider env vars (DIGITALOCEAN_TOKEN,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY). - SSH keys: Auto-generated Ed25519 keys stored in
keys/towlion(git-ignored). - OpenTofu: Use
tofu(notterraform). Required version >= 1.6.0.
.env.local # Credentials (git-ignored)
.provider # Current provider name (git-ignored)
.tfplan # Saved tofu plan (git-ignored)
towlion-infra # CLI script
cloud-init.sh # Cloud-init user-data
bootstrap-server.sh # Post-provision bootstrap script
main.tf # Root module
variables.tf # Input variables
outputs.tf # Root outputs
providers.tf # Provider blocks
modules/
aws/main.tf # AWS resources
aws/outputs.tf
aws/variables.tf
digitalocean/main.tf # DO resources
digitalocean/outputs.tf
digitalocean/variables.tf
keys/ # SSH keys (git-ignored)
bash -n towlion-infra— Syntax-check the CLI script.tofu validate— Validate Terraform configuration (requirestofu initfirst).shellcheck towlion-infra— Lint the Bash script (if shellcheck is installed).
.env.localandkeys/contain secrets — never commit them.- Both provider modules must expose identical output names. If you add an output to one, add it to the other.
- The
cloud-init.shscript runs as root on first boot. It must handle both AWS and DO device naming conventions. - The CLI passes variables to tofu via
-varflags, not tfvars files. plansaves a binary plan file (.tfplan).applyconsumes and deletes it if present;destroydeletes any stale plan file.- State files are per-provider (
terraform.aws.tfstate,terraform.digitalocean.tfstate). The CLI passes-stateautomatically. If migrating from an older singleterraform.tfstate, rename it to the provider-specific name. - DNS resources are count-gated on
var.domain != "". Whendomainis empty (default), no DNS resources are created. - DNS zones create root (
@) and wildcard (*) A records. DigitalOcean uses fixed nameservers (ns1-3.digitalocean.com); AWS Route 53 assigns unique nameservers per hosted zone (only known afterapply). - If a DigitalOcean domain already exists in the account, the user must
tofu importit before applying.