Skip to main content

ubitofu mascot — a tofu block wearing a UniFi access point, holding an OpenTofu gear

ubitofu — import your UniFi / Ubiquiti UDM config into OpenTofu (Terraform)

ubitofu enumerates a live UniFi Network controller (UDM, UDM-Pro, Cloud Key, or self-hosted) and generates clean, directly-appliable OpenTofu/Terraform HCL for the ubiquiti-community/unifi provider — bringing existing networks, VLANs, WLANs, firewall rules, port profiles, port forwards, WireGuard VPN, clients and devices under infrastructure-as-code. Plan-only and re-runnable: run once to import an existing controller, and again to reconcile drift.

It never runs tofu apply and never writes to the controller. Every run reads from the controller and writes HCL to your working directory, so running it against production networks is safe.

Installation

pip install ubitofu

Requires Python 3.11 or later. Runtime dependencies (python-hcl2, httpx, deepdiff) install automatically.

Both entry points are equivalent:

ubitofu --help
python -m ubitofu --help

Importing an existing UniFi controller into Terraform/OpenTofu

Four subcommands take you from a live controller to appliable code:

$ ubitofu enumerate --config config.toml   # import blocks + coverage gaps (requires tofu-init'd workdir)
$ ubitofu generate  --config config.toml   # imports.tf + generated.tf + unifi-variables.tf
$ ubitofu reconcile --config config.toml   # merge drift into committed HCL in place
$ ubitofu reconcile --check --config config.toml   # gate: classify only, write nothing, same exit codes
$ ubitofu verify    --config config.toml   # plan must be clean (or secrets-only)
  • enumerate walks the controller and prints import blocks plus a report of anything it cannot bring under management. Requires a tofu-init'd workdir — it reads the provider schema for the coverage audit.
  • generate writes imports.tf, generated.tf, and unifi-variables.tf — a self-contained, appliable configuration for the ubiquiti-community/unifi provider.
  • reconcile edits your committed, hand-tuned .tf in place, preserving comments and layout: it updates drifted scalars and names each changed nested attribute precisely in the report; appends newly-adopted objects with their import blocks (and, for objects that carry a secret, the matching variable declaration plus a warning to set it); flags resources that would be destroyed on apply; and distinguishes objects deleted on the controller from those configured but not yet applied. Re-runs against an unchanged controller are a no-op and never produce a duplicate resource name. The printed report is the product; nothing is applied.
  • verify runs a plan and passes only when it is clean (or the only diffs are in schema-sensitive attributes whose values live in variables).

Exit codes

Every subcommand uses the same flat, rsync-style scheme — distinct small codes you can case on, no report-grepping:

code meaning
0 success — in sync / clean plan / nothing to report
10 drift captured — committed *.tf edited or reconciled_new.tf appended (reconcile)
11 attention required — complex/diverged/orphaned/secret findings (reconcile), real drift (verify)
12 drift captured AND attention required
13 forbidden device create — remove the block or adopt via UI (reconcile)
1 error — controller unreachable, tofu failure, secrets
2 usage error

Under set -e/pipefail, capture the code instead of aborting:

rc=0; ubitofu reconcile --config config.toml | tee report.txt || rc=$?
case "$rc" in
  0)  ;;                                          # nothing to do
  10) open_pr ;;                                  # drift captured
  11) notify "manual attention needed" ;;
  12) open_pr; notify "manual attention needed" ;;
  13) die "device create planned — remove the block or adopt in the UI" ;;
  *)  die "reconcile failed ($rc)" ;;
esac

Configuration is TOML:

controller_url = "https://192.168.1.1"
site           = "default"
api_key_source = "op"                                # or "env"
api_key_ref    = "op://YourVault/unifi.api-key/credential"
op_vault       = "YourVault"
workdir        = "./work"

Coverage audit — nothing is silently ignored

Every run audits the live controller against the provider's schema (tofu providers schema -json): setting sections and their fields, probed API collections, and provider resources missing from ubitofu's own manifest. Findings land in two places:

  • the console report (Coverage gaps: section), and
  • COVERAGE.md in the workdir — byte-stable, committed alongside your HCL.

COVERAGE.md is the acceptance ledger. A new gap arrives as a git diff and rides whatever drift-PR automation you run; merging that diff is the acknowledgment. A gap disappears only when a provider release actually models the config. There are no ignore lists.

Secrets

Secret attributes (WLAN passphrases, dynamic-DNS passwords, …) are never emitted as plaintext. Each one known to the SECRETS table renders as a var.<name> reference, and generate writes a unifi-variables.tf declaring every referenced variable (type = string, sensitive = true) so the generated config is self-contained.

You supply variable values from your secret manager — e.g. TF_VAR_<name> environment variables or a git-ignored *.auto.tfvars. The tool prints a suggested secret-manager reference for each variable (rendered with your configured op_vault); these references are reporter output only — the tool never writes them to files.

ubitofu omits sensitive attributes without a SECRETS rule from the HCL and adds them to lifecycle { ignore_changes }. As a safety net, any emitted string value that still looks secret-shaped (a secret-bearing attribute name, or a 44-char base64 WireGuard-key shape) is suppressed the same way, with a loud warning naming the resource and attribute — add a SECRETS rule to manage it properly.

How it compares

Unlike general-purpose importers such as terraformer, ubitofu is purpose-built for the ubiquiti-community/unifi provider: it knows which attributes are settable, which are computed, and which are secrets, so the HCL it emits applies cleanly instead of fighting the provider schema. Compared with hand-rolled scripts (e.g. terrifi-style one-offs), it is re-runnable and drift-aware — re-run verify any time to confirm code and controller still agree.

Claude Code workflow skill

The repo ships a Claude Code skill at .claude/skills/unifi-tofu-reconcile-workflow/. Its rule: never hand-author UniFi HCL — draft with ubitofu and refine.

If you run Claude Code inside a clone of this repo, the skill is available automatically.

pip install ubitofu does not install the skill — PyPI packages do not carry Claude Code skills. Its best home is your own infrastructure repo: copy the unifi-tofu-reconcile-workflow directory into that repo's .claude/skills/ and commit it, so everyone who clones the repo gets it. To use it across every project instead, copy it into ~/.claude/skills/.

License

Licensed under GPL-3.0-or-later — see LICENSE.

Also relevant if you searched: unifi terraform import, udm as code, opentofu ubiquiti, ubiquiti-community/unifi provider import.

Metadata

Release files for ubitofu 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ubitofu 0.6.0
File Size Uploaded
ubitofu-0.6.0.tar.gz 108.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ubitofu 0.6.0
File Interpreter ABI Platform
ubitofu-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 168.8 kB

Release files / ubitofu-0.6.0.tar.gz

Download URL ubitofu-0.6.0.tar.gz
Size 108.2 kB
Tags Source
SHA-256 checksum
How to use checksums
988a60b8eff7e20e2f3afce028b76404e61b314ae4d015fb8d4d7c9d09702262
BLAKE2b-256 checksum
How to use checksums
2867c210a9bb0365191b9e622606124b469985cd35155d39dfed6b179d57a639
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

Release files / ubitofu-0.6.0-py3-none-any.whl

Download URL ubitofu-0.6.0-py3-none-any.whl
Size 60.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ba7187374b66df9e5ede96763969e3e266537df6ba025cc279aac997cdbcd257
BLAKE2b-256 checksum
How to use checksums
9bbc5be57e214e2bc7761b179768fc6be55b43ba3c4a3a613389db53b59a8358
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

Release history Release notifications | RSS feed

0.10.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.1

2 release files

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.3.3

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page