- Go 89.6%
- Shell 6.7%
- Makefile 3.7%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| internal/provider | ||
| .gitignore | ||
| .goreleaser.yaml | ||
| go.mod | ||
| go.sum | ||
| install.sh | ||
| main.go | ||
| Makefile | ||
| README.md | ||
| spec.md | ||
terraform-provider-local-dhcp
Allocates unused IP addresses on the local network so Terraform/OpenTofu can create machines with static IPs in a single apply — no more hardcoding free IPs by hand. Built for the talos-faktory Proxmox/Talos module, but generic to any UniFi-backed network.
How it picks an IP
An address is only allocated when both checks say it's free:
- UniFi controller inventory (authoritative): active clients
(
stat/sta) plus every known client's last lease and fixed-IP reservation (rest/user) — this covers devices that are offline right now but still own an address. Read-only: the provider never writes to the controller. - Live probe (belt and braces): one ICMP echo, then a kernel ARP-table
check — catching statically-configured hosts the controller has never
seen. Hosts that drop ICMP still answer the ARP request the ping
triggers. Disable with
verify = falsewhen Terraform doesn't run on the same L2 segment as the range.
Allocation is a resource, not a data source: the pick happens once at create time and is then pinned in state, so later plans never churn node IPs as the network changes. Concurrent allocations in one apply are serialized and never collide. Destroy is a no-op — the allocation only ever lives in Terraform state.
Installation
No provider registry is involved. Releases on this Forgejo repo are
installed into the local filesystem mirror
(~/.terraform.d/plugins), which terraform and tofu consult before ever
contacting a registry:
curl -fsSL https://git.ttdsm.org/rssnyder/terraform-provider-localdhcp/raw/branch/main/install.sh | sh -s 0.1.0
(or clone and ./install.sh 0.1.0). Then reference it normally:
terraform {
required_providers {
localdhcp = {
source = "rssnyder/localdhcp"
version = "0.1.0"
}
}
}
tofu init resolves the provider from the mirror; the lock file records
the local build's hash. (The script installs under both the
registry.opentofu.org and registry.terraform.io hostnames, since tofu
and terraform expand rssnyder/localdhcp against different default
registries.) Re-run install.sh with the new version when
upgrading. Machines that run applies (laptop, CI runners) each need the
install once per version.
Usage
# unifi_url / unifi_api_key / allow_insecure fall back to the
# UNIFI_API / UNIFI_API_KEY / UNIFI_INSECURE environment variables.
provider "localdhcp" {}
resource "localdhcp_ip" "control_plane" {
count = 1
# keep picks in the static zone, outside the DHCP pool
range_start = "192.168.2.32"
range_end = "192.168.2.127"
exclude = ["192.168.2.69"]
}
output "control_plane_ips" {
value = localdhcp_ip.control_plane[*].ip
}
localdhcp_free_ips is a companion data source that lists all
controller-free addresses in a range — for inspection only (it recomputes
every plan).
Provider configuration
| Attribute | Env fallback | Default | |
|---|---|---|---|
unifi_url |
UNIFI_API |
— | Controller base URL, e.g. https://192.168.2.1 |
unifi_api_key |
UNIFI_API_KEY |
— | API key (UniFi OS: Settings → Control Plane → Integrations) |
unifi_site |
— | default |
Site name |
allow_insecure |
UNIFI_INSECURE |
false |
Skip TLS verification |
verify |
— | true |
Live ICMP/ARP probe before allocating |
Works against UniFi OS consoles (/proxy/network/...) and self-hosted
controllers (/api/...) — the client tries both paths.
Releasing
Push a tag and the release Forgejo action builds zips for
linux/darwin × amd64/arm64 with GoReleaser and attaches them (plus a
SHA256SUMS file) to a Forgejo release:
git tag v0.1.0 && git push origin v0.1.0
Why no registry?
The public registries (registry.terraform.io / registry.opentofu.org) only
ingest providers from GitHub repos and require GPG-signed releases, and
Forgejo doesn't implement the provider-registry protocol. None of that is
needed: the filesystem mirror above gives full init/lockfile support from
plain release assets. If this ever needs to be consumed by machines where
pre-installing is annoying, the next step up is a
network mirror
— static JSON hosted anywhere (e.g. the MinIO/Garage S3) pointing at the
Forgejo release zips.
Development
make build # binary in bin/
make test
make install VERSION=0.1.0 # put the local build in the filesystem mirror
Or point at the working tree with a dev override (no version pinning, no init needed):
# dev.tfrc
provider_installation {
dev_overrides {
"rssnyder/localdhcp" = "/home/riley/terraform-provider-local-dhcp/bin"
}
direct {}
}
TF_CLI_CONFIG_FILE=$PWD/dev.tfrc tofu apply