OpenTofu configuration and bootstrap scripts of the edge router of the Phorge infrastructure, a MikroTik RB3011UiAS running RouterOS 7.19. The router owns the VLANs, addressing, DHCP, DNS, firewall and NAT, and runs the HAProxy container that publishes the services to the Internet.
Documentation:
- Architecture and network plan: topology, VLANs and addresses, wiring, hosts and clusters, public services, traffic flows, change checklists.
- Reset and bootstrap runbook: rebuild the router from scratch and bring it back under OpenTofu.
Sibling repositories: FrontPlane (Kubernetes clusters), Ansible (storage, HPC and compute nodes).
versions.tf OpenTofu and provider version constraints
providers.tf Provider settings
variables.tf Input variables, with validation
locals.tf Values derived from the networks variable
interfaces.tf VLANs, veth, bridges, interface lists, VXLAN
addressing.tf IP addresses and DHCP pools
dhcp.tf DHCP networks and servers
dns.tf DNS records
firewall.tf Filter rules, NAT rules, address lists
routing.tf BGP connections
users.tf RouterOS user groups and users (passwords from .env, never from the tfvars)
containers.tf Container runtime, mounts, uploaded files, containers
encryption.tf State and plan encryption; the passphrase comes from TF_ENCRYPTION
terraform.tfvars Values: networks, DNS records, firewall, NAT, containers
.terraform.lock.hcl Pinned provider version and hashes (committed)
templates/ HAProxy configuration template and the script that reloads it
certs/ Local trust anchor: the router CA (ignored by git, see the runbook)
defaults/ RouterOS scripts: factory defaults, base configuration, Phorge.dpk
docs/ Architecture and runbooks
One entry in networks (name, VLAN ID, CIDR, optionally a DHCP pool, node ranges and the host number of the public ingress) creates the VLAN, the router address (the last usable address of the subnet), the DHCP pool, network and server, the membership of the phorge interface list and the <name>-nodes address list. The ingress addresses also feed the HAProxy configuration and the rules that let the container reach them, so they exist in one place.
- OpenTofu 1.12 or later
- RouterOS 7.19 or later with the
containerpackage and the container feature enabled indevice-mode - Access to the router's REST API (HTTPS, management LAN)
-
Create your environment file and restrict it:
cp .env.example .env chmod 600 .env
Then set
TF_VAR_hosturl,TF_VAR_usernameandTF_VAR_password, and replace the passphrase inTF_ENCRYPTION(at least 16 random characters). Keep a copy of that passphrase outside this machine: without it the state cannot be read.The passwords of the extra RouterOS users declared in
usersgo inTF_VAR_user_passwords, a JSON object keyed by user name (at least 24 random characters each). They never go interraform.tfvars. A service that reads one of them keeps its own copy: themktxppassword is also stored SOPS-encrypted in the Frontplane repository.The provider verifies the router certificate against
certs/router-ca.pem, a local file that is not committed. Fetch it as described in the runbook. The first run after a router reset can useTF_VAR_insecure_tls=trueinstead. -
Initialize:
source .env tofu init -
Plan, read the plan, then apply:
tofu plan tofu apply
Run every command from the repository root so that terraform.tfvars is loaded. Without it the plan stops on missing required variables.
The HAProxy configuration is templates/haproxy.cfg.tftpl. Edit it and run tofu apply: the container reloads it by itself within about 15 seconds, without dropping connections (see the runbook, including how to check that HAProxy accepted it). Do not edit the file on the router, the next apply would overwrite it.
The uploaded HAProxy configuration is adopted automatically (import block in containers.tf) because RouterOS file IDs shift when other files change.
The VLANs, IP addresses, bridges and bridge ports have prevent_destroy: a plan that would delete them fails. To delete one on purpose, remove its lifecycle block first.
- Every object created by OpenTofu carries a comment starting with
tofu;;;. - Firewall rules have a stable
idand are placed on the router in list order byrouteros_move_items. A rule withbeforesits right above the factory rule of that name in the same chain, the others go to the end of the chain. See the runbook. - The state is local (
terraform.tfstate, ignored by git) and encrypted with OpenTofu's native state encryption. Every command needssource .env, otherwise it stops withReference to undeclared key provider. Keep a copy of the state after every apply. - All three Phorge repositories are public. Never commit
.env, the state (not even encrypted), a reset-editedbase_configuration.rscor any other plaintext secret. - Commit messages follow Conventional Commits (
fix(fw): ...,chore(haproxy): ...).
Follow the runbook. In short: replace the placeholders in defaults/base_configuration.rsc, reset the router with the factory defaults, upload defaults/Phorge.dpk, import the base configuration, then run OpenTofu.