Repository navigation
Releases: conn3xorg/conn3x
Release list
v0.6.4
v0.6.4 makes refresh stop waiting on a host that moved, stops edits to one file from overwriting each other, and lets a step with an empty for_each plan cleanly. Two contributors cleaned up command help and the file docs.
Fixes
- Moving sshd to another port no longer needs
-refresh=false. Refresh reads each step with the connection recorded at the last apply, so after you moved sshd and changedportto match, the plan waited three minutes on the old port and then failed. Now refresh waits at most 10 seconds for a host. If nothing answers, the host's steps keep what the last apply recorded, you get aHost not reachable during refreshwarning, and the plan goes on with the address in your configuration. A wrong host key or a rejected login still fails the refresh.conn3x drifttreats an unreachable host as an error, so a host that's down never reports "No drift". (#131, fixes #130) - Command help says
conn3xthroughout. A few help texts inherited from OpenTofu still said"tofu plan"or"tofu apply". Now none do, and a test checks every command's help. (#137, fixes #134) lineandblockedits to one file no longer lose each other's changes. Several edits to the same file on a host, such as alinestep withfor_each, ran at once, and each could write over the others. The apply reported every line added while the file kept one, and the next plan showed the rest as changes. Edits to one file on a host now take turns. (#141, fixes #139)- A step with an empty
for_eachhas no instances, as OpenTofu's ownfor_eachdoes. It used to fail the plan withInvalid for_each argument ... cannot be determined until apply, so an optional list a caller left empty broke the run. (#142)
Docs
- The file module page says what happens when the login user can't read a file, and how to fix it. (#138, fixes #135)
Thanks
- @dannyward630, for the last
tofumentions in command help, and the test that keeps them out (#137). - @coderbhawesh, for documenting the unreadable-file error on the file and template pages (#138).
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.6.3...v0.6.4
v0.6.3
v0.6.3 fixes two races that could fail an apply on one host, makes a moved host's plan tell the truth, and adds two checks from contributors.
Fixes
- Packages and services on one host take turns. Steps on one host run in parallel, and two of them could trip over each other. Installing related packages together, such as
zipandunzip, could fail on dnf withTransaction test error: package unzip is already installed, because one install pulled in what the other had just put on the host. Several new services could each runsystemctl daemon-reloadat once, and one read back mid-reload failed withdid not reach the wanted state. Package installs on a host now go one at a time, and so do service changes. Other hosts, and other kinds of step, still run in parallel. (#125, #126) - A new port that reaches another machine. Changing only a host's port planned an update, even when the new port reached a different machine behind the same address, so
on_removenever ran on the old one. Conn3x now compares the host keys recorded for the two addresses, inhost_keyor known_hosts. If they share none, the plan replaces the host's steps. If they share one, it's the same machine, such as sshd moving to another port, and nothing is replaced. (#124, fixes #87) - A circuit declared in two files is now one
Duplicate circuiterror that names both files, instead of a run of duplicate-resource errors. (#120) - A readable HMAC key file. If your group or other users can read
~/.config/conn3x/hmac.key, plans and refreshes ofcontent_wofiles warn and print thechmod 600command that fixes it. A key inCONN3X_HMAC_KEYisn't checked. (#95)
Thanks
- @coderbhawesh, for the duplicate-circuit check across files (#120).
- @Dodothereal, for the key permission warning (#95).
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.6.2...v0.6.3
v0.6.2
v0.6.2 fixes applies that fail on busy hosts, and two confusing reads and errors.
Fixes
- Many steps on one host at once. Conn3x runs up to 10 resources in parallel, and each command opens an SSH session on the host's one connection. OpenSSH allows 10 sessions per connection by default, so with 10 or more in flight an apply could fail with
ssh: rejected: connect failed ("open failed"). Commands now queue for a session, at most 8 at a time per connection, and Conn3x lowers that on its own for a server set to allow fewer. SetCONN3X_SSH_MAX_SESSIONSto change the limit. (#122) - Unreadable files: a file the login user can't read is an error that names the user, instead of reading as empty and planning a change that apply then fails to make. (#118)
- Steps: a step with a module and another block, such as
lifecycle, now names the block it doesn't take, pointing at it, instead of saying it needs exactly one module. (#116)
Thanks
- @coderbhawesh, for the clearer error on unexpected blocks in a step (#116).
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.6.1...v0.6.2
v0.6.1
v0.6.1 is a release of fixes: bastions named in your SSH config, handlers that run only when something changed, a CLI that speaks Conn3x everywhere, and the first change from an outside contributor.
There's no v0.6.0 release: its build showed a change that would have rejected host objects carrying fields of their own, which the docs recommend, so it was fixed first.
Fixes
SSH and circuits
ssh_config_file: aProxyJumpthat names anotherHostentry is looked up in the same file, with its ownHostName,User,PortandIdentityFile. A jump host whose name doesn't resolve fails at once instead of after the 3-minute timeout. (#112)- Handlers no longer run when the only change to a step is a setting that doesn't touch the host, such as
on_remove, the sshtimeoutorbecome_password_file. (#112) eachin thesshblock means the host in a step withfor_each, as it does in every other step. (#110)- Host objects that set an ssh setting only the
sshblock takes, such asbecomeortimeout, are an error that names the setting, instead of being ignored. Fields of your own, such asrole, are still allowed. (#110, #115)
CLI
conn3x driftworks with a relative-chdir, and rejects-jsonwith a pointer toconn3x plan -refresh-only -jsoninstead of ignoring it. (#106)conn3x fmtformats.circuit.hclfiles. (#106)conn3x version -jsonprints JSON, with aconn3x_versionfield. The version line in every-jsonstream names the Conn3x release, and the OpenTofu version no longer ends in-dev. (#106, #107)conn3x init -jsonno longer prints the encryption warning as plain text. (#106)- Help and error text say
conn3xinstead oftofueverywhere, andconvert ansiblewithout a playbook says one is required. (#106)
Converter
conn3x convert ansibleleaves afiletask withstate: linkthat setsowner,groupormodeas a TODO that says why, instead of writing alinkstep that fails validation. (#103)
Docs
- Every module page has examples and notes: how it reads, plans and applies, what removal does, and which Ansible modules convert to it. Paired arguments are marked "One of". (#98, #100, #101, #102)
- Tutorials, from trying Conn3x locally to migrating a playbook.
- A CLI reference with a page for every command, and the circuit language split into one page per topic.
Thanks
- @Dodothereal, for marking both sides of an argument pair as "One of" in the module reference (#98). The first outside contribution to Conn3x.
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.5.0...v0.6.1
v0.5.0
v0.5.0 adds a module for the system locale and makes every file Conn3x writes safe against a power cut.
New
-
The
localemodule. Set the systemLANG:step "lang" { locale { name = "en_GB.UTF-8" } }
Conn3x uses
update-localeon Debian and Ubuntu, andlocalectlor/etc/locale.confon the Red Hat family, and reads the setting back from the same place. A locale that isn't installed is generated wherelocale-genexists. On the Red Hat family, the error names theglibc-langpackpackage to add first. Tested on Rocky Linux 10, Fedora 45, Ubuntu 24.04 and Debian 13.
Fixes
- Writes survive a power cut.
file,templateanddownloadsteps write to a temporary file and rename it into place. They now flush the new content to disk before the rename and the directory after it, so a crash right after an apply leaves the old file or the new one, never an empty one.
Docs
- Module pages now carry examples and notes: how each module reads, plans and applies, what removal does, and which Ansible modules convert to it.
fileandpackageare first, and every example is checked against Conn3x in CI. - Host facts are now documented.
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.4.0...v0.5.0
v0.4.0
Ansible users loop over things constantly. v0.4.0 lets a step do the same.
New
-
Repeat a step with
for_each. A step can now run once per item, on every host:step "tools" { for_each = ["git", "jq", "tree"] package { name = item.value } }
item.keyanditem.valueare the current item, andeach.keyis still the host.for_eachtakes a list or set of strings, or a map. Drop an item and the next plan removes just that item from every host. Repeating a step has the details. -
The Ansible converter translates simple loops. A task with
loop: [a, b]over plain values, using{{ item }}, now becomes a step withfor_each. Loops over variables, items with fields,loop_controlandwhenstill come out asTODO(convert)with the reason. -
conn3x initsuggests state encryption. When neither the configuration norTF_ENCRYPTIONencrypts state, init ends with a warning and a link to OpenTofu's state encryption. Conn3x state holds what it reads from your servers, so it's worth turning on.
Fixes
- Handlers run when a hand edit is put back. If someone edited a file by hand, apply restored it but didn't run the handler, so a service could keep running the edited config. Every host step now has a
revisionthat changes whenever Conn3x changes something on the host, and handlers watch it.
Upgrading
The first plan after upgrading shows each handler as updated in place while it records the new triggers. Nothing runs on your hosts, and the plan after that is empty.
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.3.0...v0.4.0
v0.3.0
A database password has no business sitting in a state file. v0.3.0 keeps it out.
New
-
Secrets in files stay out of state. Give a file
content_woinstead ofcontent, or setwrite_only = trueon a template, and the content reaches the host but not the plan or the state:variable "db_password" { type = string ephemeral = true } step "db_password" { file { path = "/etc/app/db-password" content_wo = var.db_password } }
State records
content_hmac, an HMAC-SHA256 of the file, so a hand edit on the host still shows up in the next plan, and apply puts your value back. Write-only files default to mode0600.The HMAC key lives outside state too, in
CONN3X_HMAC_KEYor~/.config/conn3x/hmac.key, which Conn3x creates the first time it writes a secret. Share it with everything that runs against the same state, CI included. Secrets in files has the details, including how to pass the value in so it stays out of saved plans.
Fixes
- Hosts behind one address. Two hosts reached through the same address on different ports got the same resource IDs. IDs now include the port when it isn't 22. Resources already in state keep their IDs, so upgrading plans no changes.
- Module reference examples. Where two arguments are alternatives, such as firewalld's
portandservice, the example now sets one, so it validates.
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.2.2...v0.3.0
v0.2.2
Your servers don't all sit on the open internet, and they don't all log in the same way. v0.2.2 stops pretending they do.
New
-
Jump hosts. If you reach the database through a bastion, say so in the
sshblock:ssh { user = "deploy" jump_hosts = ["deploy@bastion.example.com"] }
Add more entries and Conn3x hops through each in turn. Every jump host's key is checked against
known_hosts, the same as the target's. -
Your SSH config. Already have
Host web1in~/.ssh/config? Point Conn3x at the file withssh_config_file = "~/.ssh/config"and use the alias as the address. It picks upHostName,User,Port,IdentityFile,ProxyJumpandUserKnownHostsFile. Anything you set in thesshblock still wins, and Conn3x reads nothing until you set the option. -
A login per host. A host in a circuit can now be an object instead of a bare address:
hosts = { web = "10.0.0.4" db = { address = "10.0.0.5", port = 2222, user = "admin" } }
Its
user,port,private_key_file,host_keyandjump_hostsoverride the circuit'ssshblock for that host alone. One circuit now covers a mixed fleet, and your inventory can be an ordinary variable or local. -
Homebrew.
brew install conn3xorg/tap/conn3xworks on macOS.
Changed
user is no longer required in the ssh block when your SSH config supplies it. If neither one sets it, the error says exactly that. Plain-address hosts and v0.1 configurations work unchanged.
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.1.1...v0.2.2
There are no v0.2.0 or v0.2.1 releases. Both builds failed at the Homebrew step, and their tags stay because tags here are never moved or deleted.
v0.1.1
A fix for Fedora, found by adding Fedora 45 to the test hosts.
Fixes
- Packages installed under another name. On Fedora, asking for
wgetinstallswget2-wget, which provides it. v0.1.0 then checked for a package calledwget, didn't find one, and failed the apply. Conn3x now also looks for what provides the name, so the step succeeds and the next plan is empty.
Testing
Fedora 45 now runs the full end-to-end suite on every change, alongside Rocky Linux 10, Ubuntu 24.04 and Debian 13.
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtFull changes: v0.1.0...v0.1.1
v0.1.0
Conn3x creates your servers and configures them in one plan. This is the first release.
If you run Terraform or OpenTofu to make machines and Ansible to set them up, you know the seam between them: an inventory script that turns state into hosts, a second run that might fail halfway, and two places to look when something drifts. Conn3x closes it. You write circuits, which are playbooks in HCL, beside your resources. One conn3x plan then shows the droplet about to be created next to the exact packages, files and services headed for it, wired to an IP address that doesn't exist yet.
What's in v0.1
- One plan, one state, both layers. Every Terraform and OpenTofu provider works unchanged, because Conn3x is built on OpenTofu v1.13.2.
- About 30 modules for the everyday work: files, templates, line and block edits, directories and links, packages and repositories, services and timers, cron, users, groups and SSH keys, sysctl, mounts, hostname and time zone, firewalld and ufw, SELinux booleans and file contexts, checksummed downloads, archives, git, guarded commands, reboots and wait_for, plus a
conn3x_hostdata source for facts. - Diffs you can trust. Every plan reads the hosts first and shows what would change, line by line for files.
conn3x driftreports what changed by hand since the last apply, in the cloud and on the hosts, and exits 2 when it finds something.conn3x convert ansibleturns playbooks and inventories into circuits. Anything it can't prove means the same thing stays in the output, commented, with the reason.- Nothing on your servers. No agent, no Python. Conn3x connects over SSH, checks host keys every time, and runs plain shell commands as root or through sudo.
It runs on Linux and macOS, and configures Rocky, RHEL, AlmaLinux, Fedora, Debian and Ubuntu. The modules are tested end to end on Rocky 10, Ubuntu 24.04 and Debian 13. The ones that need a real kernel, such as SELinux, firewalls, sysctl, mounts and reboots, run against real DigitalOcean droplets instead, every night.
Start here: Install · Get started in 15 minutes · Migrate from Ansible
It's early, and we want to hear where it falls short. Questions and ideas go in Discussions, and bugs in Issues.
Verify a download
cosign verify-blob \
--certificate-identity 'https://github.com/conn3xorg/conn3x/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle checksums.txt.sigstore.json checksums.txt
sha256sum --check --ignore-missing checksums.txtThe archives also carry GitHub build provenance: gh attestation verify <archive> --repo conn3xorg/conn3x.
Full history: https://github.com/conn3xorg/conn3x/commits/v0.1.0