Releases: swenske/Janus
Release list
Janus v2026.10.06 (Alpha)
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 📚 The documentation has its own site: janus.sw-servers.net/docs - the docs of this release, searchable, for three audiences: a user guide with a ten-minute quick start, how Janus works inside, and how to contribute or integrate it into a private cloud, with end-to-end guides for libvirt/KVM, Proxmox VE, bare metal and VMware.
- 🖥️ Terraform configures a node in the apply that creates it: with a fleet, creating a node and applying its HAProxy configuration in one
tofu applycould fail with "unknown certificate authority". A machine is now ready once its node answers the Controller. - 🖥️ A node's page refreshes by itself again in a new browser - every 5 s, as intended.
📚 Documentation
- janus.sw-servers.net/docs follows the newest release;
/docs/next/shows main's. Search with suggestions and completion, light and dark themes, diagrams, screenshots of the Controller. - User guide: a quick start - a node in a virtual machine, configured through its API, serving HTTP - updating nodes, troubleshooting, and the janusctl reference, generated from janusctl itself.
- Private cloud: images, first boot, automation, networking, observability, lifecycle and security - and end-to-end guides per platform. The libvirt/KVM guide's Terraform example is applied as written on every image build; the VMware guide isn't verified on a real ESXi host yet.
- How it works inside: boot and A/B updates, trust and certificates, the Controller, how an image is built. Contributing: the development environment, tests, conventions, writing an extension, releasing.
- Links that last:
https://janus.sw-servers.net/docs/<file>.mdopens the file's page wherever it lives -/docs/vrrp.mdis the VRRP page - and the GitHub links already published keep working.
🖥️ Controller
- Fix - a node's page stood still in a new browser: with no refresh interval remembered, the page started with auto-refresh off, so its charts and lists didn't move until one was picked. It's every 5 s, as it was meant to be.
- Fix - a machine is ready once its node answers: a node created on a hypervisor takes the fleet's trust a moment after it registers, and until then it refuses the Controller's certificate. The machine was "ready" from its admission, so Terraform or a script that configured the node straight away was refused. A node still silent after two minutes leaves its machine ready all the same, with a warning on its card.
- Readable colours: text in the accent and status colours - links, active tabs, badges - meets WCAG AA contrast in both themes; the dark theme's buttons are a deeper orange.
- Documentation links (firewall, VRRP, BGP, Consul, Let's Encrypt, the exporters, updating the Controller) open the docs site.
🌐 janus.sw-servers.net
- The landing page shows the Controller, and the image builder's "How to install on…" opens each platform's guide.
- Both pages pass an accessibility check (contrast, links, the navigation on a phone) and show a card when they're shared.
⚠️ Upgrade notes
- Update the Controller (from its page, or
docker compose pull && docker compose up -d): both fixes are in it. A machine it creates now shows "registering" a few seconds longer, until its node answers. - Nodes and janusctl: unchanged in this release - updating nodes is optional.
📖 Docs: Quick start · Controller · Terraform · 🐳 Docker Hub
Full Changelog: v2026.10.05...v2026.10.06
Janus v2026.10.05-5 (Alpha)
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 🐛 The extensions panel opens again: on a node's page, Update › Change extensions… and Apps › Add or remove apps… showed an empty page in v2026.10.05-4. They list the image factory's extensions again, ready to prepare an update with another set.
🐛 Fixes
- Node page › Change extensions / Add or remove apps: v2026.10.05-4 moved the node page's version and update state to one shared source, and the extensions panel lost a function it still used - opening it took the whole page down. Fixed; nothing else on the node pages was affected.
- The Controller's frontend is now checked for this kind of mistake (a name used but never imported) before it's built, in CI.
⚠️ Upgrade notes
- Only the Controller changes: update it (from its page, or
docker compose pull && docker compose up -d). Nodes on v2026.10.05-4 need nothing. - Coming from an older release, see v2026.10.05-4's notes - trusted browsers, the page's own HTTPS certificate (
-tls-certno longer serves the registration port), janusctl completion and pickers.
📖 Docs: Controller · Image factory · 🐳 Docker Hub
Full Changelog: v2026.10.05-4...v2026.10.05-5
Janus v2026.10.05-4 (Alpha)
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 🔐 Trust this browser: give your second factor once, tick the box, and that browser only asks for your password for the next 12 hours - signing out and in again, or an idle session ending, no longer asks for the code all day.
- 🔒 The Controller's page with your own certificate: upload a certificate (a public one, or your organization's CA's) on the page, or give it as files read again when they're renewed - nodes keep registering exactly as before.
- ⌨️ janusctl completion and pickers: Tab completion for bash, zsh and fish - commands, flags, your nodes, and what's on the node itself (services, maps, certificates, paths) -, and in a terminal, whatever you leave out is picked from a list filtered as you type.
- 🖥️ The node page follows a reboot at once: after an update, the version, the update badge and the Update dot change the moment the node is back - no reload.
- 🧮 CPUs on the node's overview: the model, logical CPUs, cores, sockets, frequency and architecture - arm64 included.
🔐 Sign-in
- When giving your second factor (code, recovery code or passkey), "Trust this browser for 12 hours": that browser's next sign-ins take the password alone until then.
- How long: Accounts › Sessions › Trust a browser for at most (hours) - 12 by default, up to 720 (30 days), 0 to always ask. A shorter value applies to the browsers already trusted.
- Your account (the dialog under your name) lists your trusted browsers - "this browser" marked, where and when each was trusted and last used - and forgets any or all of them.
- Every trusted browser is forgotten on a password change, a Reset 2FA, a disabled account, or when the account's last second factor is removed.
- The browser keeps a secret in a cookie sent to the sign-in only (HttpOnly, Secure, SameSite Strict); the Controller keeps its SHA-256. The Audit shows such a sign-in
via trusted browser. - Adding an SSH key from a trusted browser's sign-in asks for the second factor once more: a key outlives the browser's trust, so a stolen cookie can't leave one behind.
🔒 The Controller's HTTPS certificate
- Nodes › HTTPS certificate (admins, at the bottom): what the page is served with - self-signed, your certificate, or from
-tls-cert-, its names, issuer and expiry. Paste or load the certificate, its chain and its private key, Check it (a warning when it doesn't name the address you opened the page by, or expires within 30 days), then Serve it: new connections get it at once, no restart. Back to self-signed removes it. - Refused with a reason: an expired or not-yet-valid certificate, one not for servers, a key that isn't the certificate's, an encrypted key, a chain out of order, Ed25519 (browsers refuse it), RSA under 2048 bits.
- The key is sealed with the Controller's master key (
<data-dir>/ui-tls.json), so your backups carry it.PUT /api/controller/tlswith an admin API token does the same from a script. - As files:
-tls-cert/-tls-key, or the newJANUS_CONTROLLER_TLS_CERT/JANUS_CONTROLLER_TLS_KEY. They win over an uploaded certificate and are read again within 30 seconds of changing - a certbot renewal needs no restart. - With a certificate browsers trust, passkeys work on the page.
- Nodes are untouched: they keep registering against the Controller's own self-signed identity (the registration port, the Provision panel's
controller-ca.crt), whatever the page uses.
⌨️ janusctl
- Shell completion for bash, zsh and fish, installed by the package (
janusctl completion bash|zsh|fishwithout it): commands and their flags, your contexts and nodes, fixed values (roles,start|stop|restart...), and from the node itself - services, maps and their keys, HAProxy's certificates and files, Let's Encrypt names, firewall tables and sets, interfaces, paths (system cat /etc/hap⇥ →/etc/haproxy/). It asks the node within 2 seconds at most and never prompts for anything. zsh and fish show what each candidate is; with fzf-tab, zsh's menu becomes a fuzzy finder. - Pickers, in a terminal:
janusctlalone opens a palette of every command,janusctl systemone of its commands; a context with several nodes and no-npicks the nodes (several at once, Tab to mark --n '?'too); an argument janusctl can list is picked -system logsthe service,haproxy map-getthe map,system cata file, browsing the node's directories. The command line that would have done it is shown after. Never forlifecycle;JANUS_NO_PICKER=1keeps the usage errors. - Colours and symbols in a terminal: tables with a bold header and coloured states, a red ✖ for errors, ▸ ✅ 🔁 ⏳ and a progress bar for install, upgrade and apply steps. Never in a pipe, a script, or with
NO_COLOR- the output scripts read hasn't changed. janusctl help [COMMAND]: every command by group, or one command's flags. A group alone or an unknown command now answers at once, without reaching a node.janusctl loginwithout a pin takes a Controller whose certificate this machine trusts (a public one, or your CA installed) - and keeps trusting its renewals. A context that pinned the Controller's certificate is told when it changes, with the new fingerprint and how to sign in again.system infoshows the CPU topology (cpu topology: 4 cores, 1 socket) and no longer prints(0 MHz).
🖥️ Controller and node pages
- The node's header follows a reboot: the version, the update or security badge and the Update dot are reloaded as soon as the node is back - after an update or a reboot from the page, a reboot from janusctl, or Refresh now. A poll failing while the node reboots is retried within 5 seconds.
- Overview › Node: the CPU model (each one counted on a hybrid chip: "4 × ARM Cortex-A76 + 4 × ARM Cortex-A55"), logical CPUs, cores, sockets, the top frequency, and the architecture; the CPU tile counts the CPUs next to the load.
- The janusctl command in your account › SSH keys drops the fingerprint pin when the page has its own certificate.
🧱 Node
- CPUInfo on arm64: the core is named from its implementer and part numbers ("ARM Cortex-A72", as
lscpudoes) instead of nothing, its frequency from cpufreq when there's one. Sockets and cores come from sysfs on both architectures. - The console banner's logo: straight-edged triangles and a slightly thinner bar, closer to the Janus symbol.
🐛 Fixes
- The update check compares versions as dates: a node that just installed a release newer than what the Controller had cached was offered the older one as an "update" for up to 10 minutes, and a build after a release was offered that release again.
⚠️ Upgrade notes
-tls-cert/-tls-keynow only serve the page (the main port), no longer the registration port. If you provisioned nodes with that certificate as theircontroller-ca.crtand they haven't registered yet, give them the Controller's own (controller-ca.crtfrom the Provision panel) - or the fleet root, which they check first.- Changing the page's certificate: janusctl contexts that pinned the self-signed one must sign in again -
janusctl loginalone when the machine trusts the new certificate's CA, else-controller-cawith that CA. Terraform:ca_certbecomes that CA, or nothing for a public certificate. - Trusting a browser is on by default (12 h) but opt-in: nothing changes until someone ticks the box. Set Trust a browser for at most to 0 to turn it off.
- janusctl's help text is new, and a group alone or an unknown command now fails before connecting (exit code 2, as before).
📖 Docs: Controller: HTTPS certificate · Controller: sessions and second factors · janusctl: shell completion · Using janusctl · Terraform · 🐳 Docker Hub
Full Changelog: v2026.10.05-3...v2026.10.05-4
Janus v2026.10.05-3 (Alpha)
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 🏷️ Permissions by node: label your nodes (
team=web,env=prod…) and give an account rights on the nodes a label picks, over some subjects only - "operator onteam=web, HAProxy only". An account no longer needs a role over every node. - 🔑 Narrowed API tokens: a token can be limited to the nodes some labels pick and to some subjects, besides its role.
- 🧱 Terraform
janus_haproxy_config: terraform a node'shaproxy.cfg- with a token that may do that and nothing else. - 🎟️ Enrollment tokens: a batch of nodes - a rack, bare metal - admitted at once without the approval step, already labelled.
- ⌨️ janusctl for these accounts too: their certificate carries what they may do on each node, and each node checks it itself.
🏷️ Labels, rights and subjects
- Labels on each node's card (admins):
key=value, as many as 32. Grants and tokens pick nodes by them. - What a call is about: every node operation belongs to one subject - observe (reading the state: info, stats, HAProxy's status…), haproxy (configuration, maps, ACLs, certificates, HAProxy's files, Let's Encrypt), services (services, reboots, logs), network (network, firewall, VRRP, BGP, Consul), system (updates, access, the node's files, packet capture, exporters' settings, reset) - and, on the Controller, machines (power, console, size, network and version of the VMs it created).
- Accounts (admins): an account has a role over everything - reader, operator, admin - or none, and grants: a role on the nodes whose labels match, over some subjects (all when none is given). On a node it may do the most its role and its grants allow.
- API tokens: a role at most, and optionally the nodes some labels pick and some subjects. A narrowed token reaches only those nodes, and none of the Controller's own routes.
- What an account sees: only the nodes - and machines - it reaches; on a node's page, only what it may do there. The Controller refuses a call outside the account's subjects before it leaves; the node checks the role and the subjects again itself, and logs them:
web-dev (os:operator; haproxy) via janus-controller.
🧱 Terraform
janus_haproxy_config: a node'shaproxy.cfg, by the node's name or ID. HAProxy checks it first - a configuration it refuses fails the apply with HAProxy's message and changes nothing -, then takes it over without dropping connections. A change made elsewhere shows in the next plan; destroying the resource leaves the node's configuration as it is. Importable by the node's name or ID.janus_node.labels: the node's labels on the Controller. Left unset, Terraform doesn't touch them.- The use case: an account with no role over everything, an operator on
team=webover HAProxy only, and its token narrowed the same way - it terraforms HAProxy on those nodes, and may restart, resize or see nothing else.
🎟️ Enrollment tokens
- Nodes page, under Provision new nodes (admins): Enrollment tokens - a name, how many nodes (up to 1000), how long (up to a year), labels. Shown once; the Controller keeps only its SHA-256.
- Give it to the nodes as their registration token:
-registration-tokenonjanusctl lifecycle installorjanusctl image seed-controller, orregistration_tokenin NoCloud user-data - the Provision panel puts it in its commands. Each node that presents it is admitted at once, with the token's labels. - Past its uses or its date, or once revoked, a node waits for approval like any other. The list shows each token's uses and the nodes it admitted.
⌨️ janusctl
janusctl loginworks for accounts without a role over everything, and for narrowed tokens: the certificate carries what the account may do on each node it reaches -janusctl loginsays it:scoped: os:operator (haproxy) on 3 nodes-, and each node checks its own entry.janusctl nodeslists only those nodes.- The nodes are named by their CA's key: a node that replaces its CA stays named. A node labelled after you signed in needs a new
janusctl login. - An account whose rights are the same on every node keeps a plain certificate with its role, as before.
- The Controller's page approving janusctl offers "what your account may do, node by node", or a lower role.
💾 Backups
- Checked against Backblaze B2 (the Controller's README shows how: a key that may only write, Object Lock); B2's answer to a wrong secret is now explained.
⚠️ Upgrade notes
- Update your nodes before relying on subjects: a node from before this release ignores them - until it's updated, only the Controller holds an account to its subjects on that node (it still refuses the rest before sending it), and a scoped janusctl certificate opens nothing there (it carries no role an older node knows).
- Before giving a team its narrowed rights, label the nodes: a grant reaches only the nodes that carry all its labels (one without labels, every node).
- Something reading the nodes' logs: a narrowed call reads
api: <Method>: web-dev (os:operator; haproxy) via janus-controller, with janusctlweb-dev (os:operator, fleet, scoped; haproxy). janusctl nodesand the Controller's lists now show each account only what it reaches. Existing accounts and tokens keep their role over everything: nothing changes for them.-registration-tokenonlifecycle installneeds the node doing the install (its ISO) of this release; NoCloud'sregistration_tokenworks with earlier nodes too.
📖 Docs: Provisioning a node · Terraform · Using janusctl · Controller · Architecture: mTLS / PKI · 🐳 Docker Hub
Full Changelog: v2026.10.05-2...v2026.10.05-3
Janus v2026.10.05-2 (Alpha)
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- ⌨️
janusctl login: sign in to your Controller once - with an SSH key of your account, in the browser, or with a code from a machine without one - and janusctl reaches every node directly with a 12-hour certificate of the fleet:janusctl -n edge-1,edge-2 haproxy show-info,janusctl -all version. - 💾 Backups: the Controller backs itself and its nodes' configurations up to an S3 bucket, encrypted to a backup kit only you hold - and a new Controller restores one from its first page.
- 🛰️ Nodes without a Controller:
janusctl fleetkeeps a fleet itself - nodes provisioned with it trust it from their first boot, adopted on the fingerprint their console shows, each machine (laptop, CI) with its own issuing CA, limited to its role. - 🧭 Node pages show what your role may do, and nothing else; a node's Access page shows who it lets in and replaces its own CA.
⌨️ janusctl and the Controller
janusctl login -controller HOST -controller-fingerprint SHA256 -user NAME -ssh-key ~/.ssh/id_ed25519.pub: an SSH key of your account signs a challenge (from ssh-agent - Ed25519 - or its file - Ed25519, ECDSA, RSA); the Controller gives a certificate of its fleet for that key, for 12 hours, renewed by itself while the key is there. The signature names the Controller's certificate janusctl saw: one relayed by another server is refused.- SSH keys for janusctl in your account's dialog - adding one needs a sign-in with a second factor; a key can carry a lower role than the account, and expire. An admin sees each account's keys on Accounts and revokes them all at once (a lost laptop).
- Without a key:
janusctl login -controller HOSTopens the Controller's page - approve the key janusctl shows -, or-deviceprints a code to enter on the page from any machine. - In CI:
JANUS_TOKEN=janus_... janusctl login -controller HOST- a certificate for an hour with the token's role. - Contexts (
janusctl context list|use|delete,janusctl nodes),-n NODE[,NODE...]and-all(each node's lines prefixed with its name). A node's own certificate (-endpoint -ca -cert -key) still comes first. - The nodes log a call made with a fleet certificate as such:
alice (os:admin, fleet)- the first boot'sadminis no longer the same line.
💾 Backups
- Backups tab (admins): make the backup kit - the key backups are encrypted to, in a file protected by a passphrase, like the fleet's recovery kit; the Controller keeps only its public half: it writes backups, it can't read them. Admins' SSH or age keys can decrypt them too.
- Where and when: any S3 bucket (AWS, MinIO, Garage, Ceph, Backblaze B2, Cloudflare R2…), once a day by default, the last 30 kept; Back up now, Download a backup.
- What's in it: everything the Controller keeps (accounts, second factors, nodes, the fleet, hypervisors, machines, the audit, its master key) and each node's configuration (HAProxy's configuration, maps and files, network, firewall, VRRP, BGP, Consul, Let's Encrypt, the exporters) - never a node's private key.
- Each backup is signed by the Controller: a changed byte, or a backup another Controller made, is refused at restore.
- Restoring: on a new Controller, its first page - Restore a backup instead (the bucket or a file, the kit and its passphrase) -, or
dashboardd restoreon the host. It starts again as the Controller backed up: the nodes keep trusting it. - A failed backup, or none for twice the interval, shows Backup failing / Backup late at the top of the Controller for admins.
🛰️ A fleet without a Controller
janusctl fleet init KIT: the fleet's root goes into a recovery kit (its passphrase shown once), this machine gets an issuing CA - janusctl signs itself 12-hour certificates with it, without any server.- Provision new nodes with it:
janusctl image seed-fleet,janusctl lifecycle install -fleet-root -fleet-bundle, orfleet_root_cert+fleet_bundlein NoCloud user-data (janusctl fleet exportwrites them). The node trusts the fleet from its first boot, and its console shows its CA's fingerprint (ca sha256 ...) at every boot. janusctl fleet adopt NAME -endpoint IP -ca-fingerprint SHA256: nothing to copy from the node - janusctl checks its CA against the fingerprint before sending anything. A node running already: adopt it once with its first boot's admin credential.- More machines:
fleet issuer requeston the new one,issuer sign -kitwhere the kit is,fleet sync,issuer accept.-rolelimits the machine: a CI requested asos:operatorgets an issuing CA that signs operators and readers only - the nodes refuse an admin's or a Controller's certificate made with its key. fleet issuer revoke+fleet synctakes a machine out;fleet statusshows each node's bundle.- Lost every machine, or the Controller:
fleet recover -kit KIT, thenfleet adopt ... -kit KITper node. A Controller's recovery kit works too: its nodes come under janusctl when no backup brings it back.
🧭 Node pages
- A page offers only what your role may call on the node - the same table the node enforces: a reader sees no Apply, Reload or power button, no logs, files or capture; an operator runs HAProxy, services and reboots, not the network, firewall, updates or the node's CA.
- Access shows who the node lets in - its fleet's root, bundle and issuing CAs, its own CA - and replaces its own CA (admins, once the node trusts the fleet): the new admin credential is for a key made in your browser, never sent, or printed on the node's console. Every certificate the old CA issued stops working; the Controller follows the new CA by itself.
- Client certificates issued from the page, or with
janusctl pki generate-client-config -role os:operator, can now carry the operator role.
⚠️ Upgrade notes
- Update your nodes before giving a machine a limited issuing CA: nodes from before this release ignore the limit and let such a CA sign any role.
- Backups stay off until you set them up: make the backup kit, then the bucket. Give the Controller S3 credentials that may only write, and turn the bucket's versioning or Object Lock on - whoever took the Controller could then stop the backups, not erase the past ones.
- Something reading the nodes' logs: a call with a fleet certificate now reads
api: <Method>: alice (os:admin, fleet); through the Controller it's unchanged (... via janus-controller). janusctl loginneeds the Controller's fleet set up (Securing the fleet). janusctl comes with this release as usual: the.debfrom the release orapt.sw-servers.net.
📖 Docs: Using janusctl · Backups · A fleet without a Controller · Provisioning a node · Architecture: mTLS / PKI · 🐳 Docker Hub
Full Changelog: v2026.10.05...v2026.10.05-2
Janus v2026.10.05 (Alpha)
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 👥 Accounts and roles: the Controller's one admin password becomes accounts - reader, operator, admin -, each signing in by name. A role applies on the Controller and on the nodes it reaches for the account: each node checks it, and logs who acted.
- 🔐 A second factor: an authenticator app, a passkey or a security key, with recovery codes - required for admins by default.
- 🌐 Node pages on the Controller's address:
/nodes/<id>/, behind your account - no more port per node, no more certificate to import in the browser. - 📜 An audit: every change made on the Controller, and every sign-in, with who made it.
👥 Accounts
- Accounts tab (admins): make an account with a role - the Controller makes its password, shown once, to hand over; its owner chooses their own at the first sign-in. Change a role, Reset password, Disable, Delete. The last enabled admin can't be demoted, disabled or deleted.
- Roles: a reader sees everything and changes nothing; an operator also runs nodes - HAProxy, services, reboots - and powers machines and opens their consoles; an admin does the rest (accounts, the fleet, hypervisors, machines, approvals, updates). The pages leave out what the role would be refused, and the Controller refuses it anyway.
- Sessions end after 30 minutes nobody touched the page, and 12 hours after the sign-in at most - both settable. A page left open, refreshing itself, doesn't keep a session alive.
- API tokens belong to an account, with its role or a lower one: demoted with it, stopped with it. Each account manages its own; an admin sees everyone's.
- Audit tab (admins): every change - from the pages or with a token -, every sign-in, failed ones with the name tried, also in the container's log.
- Locked out of every admin account:
docker exec janus-controller /dashboardd reset-user adminon the host prints a new password - the running Controller takes it at once.
🔐 Second factors
- An authenticator app (TOTP: Bitwarden, Aegis, 1Password, Google Authenticator…) - its QR code drawn by the Controller, its secret sealed with the master key, each code good once.
- Passkeys and security keys (WebAuthn), for the name the Controller is opened by - with a certificate the browser trusts: browsers refuse passkeys on a certificate clicked through, where the app works.
- Ten recovery codes come with the first factor - copy or download them -, each good once.
- Who needs one: admins (default), everyone or nobody - asked at the first run, changed on Accounts. Such an account without any sets one up at its next sign-in, before anything else. The account button lists your factors, adds and removes them (with your password), and makes new recovery codes. An admin resets an account's factors - a lost phone.
🌐 Node pages
- A node's page is
https://<controller>/nodes/<id>/- Open on its card. It acts for your account, which it shows in its sidebar; a reader gets a banner, and a session that ended says so. - The Controller reaches a node that trusts its fleet for your account: the node applies your role itself (
os:reader,os:operator,os:admin) and refuses the rest with its own message. A node that doesn't trust the fleet yet - reached with its service credential - opens for admins only until it's updated. - No per-node ports (
9500-9599) any more. The Access page issues certificates forjanusctland for adding a node to another Controller.
⚠️ Upgrade notes
- Your password stays: it's the account
admin's - sign in with the nameadmin. Your API tokens stay too, as admin's: Terraform keeps working. - At your next sign-in, set up a second factor - the default for admins. An authenticator app works with the Controller's self-signed certificate; a passkey needs a certificate your browser trusts (
-tls-cert, or trust the Controller's). - Bookmarks to a node's port (
https://<controller>:95xx/) and the.pfxcertificates imported in the browser for them are no longer used: open nodes from the Controller's page, and remove the certificates from the browser. - A node that doesn't trust the fleet yet opens for admins only: update it - it joins the fleet by itself.
- Scripts that set the Controller up through the API:
POST /api/auth/setuptakesname(defaultadmin) andmfa_required(admins,everyoneornobody); signing in takesname(defaultadmin).
📖 Docs: Accounts and roles · Securing the fleet · Architecture: mTLS / PKI · Terraform · 🐳 Docker Hub
Full Changelog: v2026.10.04...v2026.10.05
Janus v2026.10.04-2 (Alpha)
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 🛡️ A fleet for your nodes: the Controller no longer keeps a credential per node. Set up its fleet once - three steps on its page - and every node trusts it: the Controller reaches them with certificates of its own, valid a day, and deletes the credentials it kept. The fleet's root key leaves the Controller: it's in a recovery kit you keep.
- 🔑 A node joins with no key at all: a new node announces itself with its CA's certificate only - you compare its fingerprint with its console, approve, and it takes the fleet's trust by itself.
- 👷 An operator role, and who did what:
os:operatorruns HAProxy and the services, not how the node is set up; and every change a node makes says who asked for it.
🖥️ Controller
- Secure your fleet (main page): create the fleet; store its recovery kit and passphrase - the kit is text, a password manager's secure note holds it, and
age -dopens it; give both back. Only then does the root key leave the Controller and do the nodes change anything. - Each node's card says how the Controller reaches it: fleet, or needs an update for a node too old to trust one - it keeps working as before until it's updated. A Fleet card shows the root's fingerprint and the CAs' expiry.
- The Waiting for approval card shows each node's CA fingerprint - the node's console shows the same when it announces itself: compare before approving.
- Provision new nodes gives the fleet's root too:
-controller-fleet-root fleet-root.crtforjanusctl lifecycle installandimage seed-controller,controller_fleet_root_certin NoCloud user-data. Nodes provisioned with it check the Controller through the fleet - a certificate it renews itself - instead of a pinned one that expires. - The fleet's issuing key is sealed with a master key:
JANUS_CONTROLLER_MASTER_KEY_FILE, outside the data directory (see the upgrade notes).
🧩 Nodes
- Fleet trust (
AccessService): a node pins its fleet's root once, then only takes a bundle that root signed, newer than its own. Its own CA - the first-boot admin certificate - always lets in, and only it can make the node forget its fleet. - Roles:
os:admin,os:operator- HAProxy's configuration, reload, maps, ACLs, certificates, files, ACME, servers' state, services and their logs, reboots - andos:reader. The Controller's certificate acts only for the user it names, and every change is logged on the node:api: HAProxyService/ApplyConfig: alice (os:operator) via janus-controller. - Self-registration without a key: to a Controller with a fleet, a node sends its CA's certificate, a token if it has one, and a secret it polls with until approved - its enrollment survives a reboot. To a Controller without a fleet, or older, it announces itself with a service credential as before.
- Replace a node's own CA (
LocalCARotate): every certificate it issued stops working - the first-boot admin one included -, the fleet's keep working. The new CA comes cross-signed by the old one: whoever pinned the old CA still verifies the node, and the Controller follows by itself.
🧰 janusctl
janusctl access trust | trust-set | trust-reset | rotate-ca DIR-rotate-camakes the new admin certificate's key locally, never sent (-consoleleaves it to the node, printed like at first boot).-controller-fleet-root FILEforlifecycle installandimage seed-controller.-as-user/-as-roles: with a Controller certificate, the user the calls are made for.
🐛 Fixes
- A machine's console no longer loses what it printed first for the reader that opened it.
⚠️ Upgrade notes
-
Controller first, with its master key outside the data volume. In
compose.yaml, on thejanus-controllerservice:environment: JANUS_CONTROLLER_MASTER_KEY_FILE: /secrets/master.key volumes: - janus-controller-secrets:/secrets
and
janus-controller-secrets:undervolumes:- then update it from its page. Without it, the key is made in the data directory: it works, but a copy of the data then holds the fleet's issuing key, and the Controller says so. -
Then set up the fleet from its page, and store the kit and its passphrase together.
-
Nodes: update them - each joins the fleet by itself once it runs this release. Until then, they keep working with the credential the Controller has.
-
A reader certificate opening a node's page in the browser is still refused (since v2026.10.04): use an admin one.
📖 Docs: Securing the fleet · Provisioning a node · API routes · Architecture: mTLS / PKI · 🐳 Docker Hub
Full Changelog: v2026.10.04...v2026.10.04-2
Janus v2026.10.04 (Alpha) - 🔒 security update
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 🔒 A security update for the Controller and every node, fixing two vulnerabilities in Janus's own code: a node's reader certificate could act as an admin through the Controller (🟠 high), and an admin certificate could carry a node's private keys away through its file API (🟡 medium). Update the Controller, then your nodes.
- 🪪 Client certificates named after who they're for, valid as long as you choose: an hour to a year, from a node's Access page or
janusctl. - 📦 Releases now say what they fix in Janus itself too: each fix gets its own GitHub security advisory.
🔒 Security
For the Controller:
-
🟠 high JANUS-2026-002 - a node's reader certificate opened its page with admin rights. The Controller acts on a node with its own admin credential, whatever certificate opened the node's page, and didn't check that certificate's role: an
os:readercertificate - meant for status and metrics - could reboot, reset or reconfigure the node, change its HAProxy configuration, read its files or capture its traffic. Now only anos:admincertificate opens a node's page; any other gets a 403 that says why.It concerns you if reader certificates were issued - from a node's Access page, or
janusctl pki generate-client-config -role os:reader- to people who can reach the Controller.
For every node:
- 🟡 medium JANUS-2026-001 - an admin certificate could read the node's private keys and credentials. The file API - the Controller's file browser,
janusctl system catandsystem cp- served every file, the node CA's private key included: whoever held an admin certificate, even for a moment, could sign themselves new ones and keep access after theirs was gone, and take credentials that belong to other systems (a DNS provider's API token, a site's TLS key). Now it never serves the node's PKI keys, the Controller registration token, the ACME account key and DNS provider credentials, nor any file holding a private key, however the path is written.
Besides updating: if a certificate of a node went to someone who shouldn't keep access, reset the node's state - a new certificate authority, every certificate issued before refused - add it to the Controller again, and replace the DNS provider credentials and TLS keys it held.
Both fixes have their advisory on the Security tab.
🖥️ Controller
- Access page: a certificate now carries the name you give it - who it's for, shown on the certificate itself - and the validity you choose: 1 hour, 1 day, 7, 30 or 90 days, or 1 year. A node older than this release ignores both: the Controller then refuses the certificate and says to update the node, rather than hand out one valid for a year.
- Choosing a reader
.pfxsays it won't open the node's page: use it withjanusctl. - When it adds a node, the Controller's own credential is named
janus-controller. - 🐛 A machine's console no longer loses what it printed first for the reader that opened it.
🧰 janusctl
janusctl pki generate-client-config -name alice-laptop -ttl 12h DIR: named, valid as long as asked - one year at most. Against an older node, nothing is written.
📦 Releases say what they fix - in Janus itself too
- A vulnerability of Janus's own code gets a record committed with its fix (
security/fixes): what was wrong, who is affected, what to do, rated with its CVSS vector. - The release that fixes it counts it like an upstream fix - in its name, its 🔒 section, its
security.jsonand the Controller's 🔒 badge - and publishes its own security advisory, whatever its severity.
⚠️ Upgrade notes
- Controller first: update it from its page - the high fix is its own. A Controller on v2026.10.03-5 or later shows itself a 🔒 badge as soon as this release is out.
- Nodes: update them - from the node's Update page or
janusctl lifecycle upgrade. A node with extensions gets this release from the image factory once it has built it. - What changes:
- a reader certificate no longer opens a node's page in a browser - use an admin one (the browser remembers its choice per site: close it to be asked again);
- the file browser and
janusctl system cat/cpno longer read/etc/janus/pki, the registration token, ACME's secrets or files holding a private key - a folder's.tarleaves them out.
📖 Docs: Security policy · Janus's own vulnerabilities · Architecture: mTLS / PKI · 🐳 Docker Hub
What's Changed
- deps: Bump oxlint from 1.85.0 to 1.86.0 in /dashboard/frontend in the npm group across 1 directory by @dependabot[bot] in #6
Full Changelog: v2026.10.03...v2026.10.04
Janus v2026.10.03-6 (Alpha) - 🔒 security update
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 🔒 node_exporter rebuilt with Janus's own Go: upstream's 1.12.1 binary was built with Go 1.26.5 and carried eight vulnerabilities of Go's standard library - one critical - in what it serves on :9100. The prometheus-node-exporter extension now builds it from source with Go 1.26.8: none left. Update the nodes that have this extension.
- 🛡️ The Controller's 🔒 badge knows your extensions: a fix to an extension only flags the nodes that have it.
🔒 Security
For nodes with the prometheus-node-exporter extension - nothing changes for the others:
-
node_exporter 1.12.1, built with Go 1.26.8 (was upstream's binary, built with Go 1.26.5). Fixed, in what it serves:
- 🔴 critical CVE-2026-39821: Punycode labels in
golang.org/x/net/idna, as Go's standard library carries it; - 🟠 high:
net/http(CVE-2026-56853, ReadHeaderTimeout on the unencrypted HTTP/2 check),crypto/tls(CVE-2026-56862, post-handshake messages),encoding/asn1(CVE-2026-33818) andencoding/xml(CVE-2026-56859) recursion depth, DNS SVCB/HTTPS record parsing (CVE-2026-46600); - 🟡 medium:
html/template(CVE-2026-56858),net/url(CVE-2026-56860).
The same version, the same collectors and flags: only the Go it's built with changes. Its source comes through Go's checksum database, checked against a pinned sha256.
- 🔴 critical CVE-2026-39821: Punycode labels in
🖥️ Controller
- The 🔒 badge counts only what a node has: a release's fixes to an extension's software - node_exporter, keepalived, nftables, bird, Consul, qemu-ga - only flag the nodes with that extension. The others see only the fixes to what every node runs.
📦 Releases say what they fix
- A release now also compares the Go binaries its extensions carry with the previous release's: a rebuild with a newer Go, at the same version, shows up in its 🔒 section, its
security.jsonand its advisory - this release is the first. security.jsonsays which extension a fix is for.
⚠️ Upgrade notes
- Controller first: update it from its page. The previous one doesn't know a fix can be an extension's, and would flag every node on v2026.10.03-5 with a critical badge.
- Nodes with the prometheus-node-exporter extension: update them - they get this release from the image factory once it has built it (the node's Update page says when).
- Other nodes: nothing changes for them.
📖 Docs: Following upstreams · Security policy
What's Changed
- deps: Bump oxlint from 1.85.0 to 1.86.0 in /dashboard/frontend in the npm group across 1 directory by @dependabot[bot] in #6
Full Changelog: v2026.10.03-5...v2026.10.03-6
Janus v2026.10.03-5 (Alpha) - 🔒 security update
Warning
Alpha software. Janus is under active early development: expect breaking changes, incomplete features and rough edges. Not yet recommended for production use.
✨ Highlights
- 🔒 A security update for nodes: HAProxy 3.4.6 - three fixes HAProxy rates MAJOR apply to Janus -, Linux 6.18.55 and zlib 1.3.2. Update your nodes.
- 🛡️ The Controller shows which nodes miss security fixes: a 🔒 badge, red or orange by how severe what they miss is - and the same for the Controller itself.
- 📦 Every release now says what it fixes: in its name, in a 🔒 section like this one, in a machine-readable
security.json, and in a GitHub security advisory when a fix is rated high or critical.
🔒 Security
For nodes - every node, whatever its extensions:
-
HAProxy 3.4.0 → 3.4.6. Of the fixes HAProxy rates MAJOR, three apply to Janus:
- 🟠 htx: the length limits of a header or trailer weren't checked again when it was updated;
- 🟠 htx: an empty HTTP message carrying an error could swap buffers it mustn't;
- 🟠 ssl/ocsp: OCSP stapling read the OCSP response without its lock.
Three more MAJOR fixes are in QUIC/HTTP/3, which Janus's HAProxy is built without, and 212 other bug fixes come along.
-
Linux 6.18.53 → 6.18.55: 🟠 CVE-2026-52988, in nf_tables, which Janus's kernel builds. The four other CVEs these releases fix are in code it doesn't build.
-
zlib 1.3.1 → 1.3.2 (HAProxy's compression): the fixes of zlib's security audit, among them ⚪ CVE-2026-27171; two other CVEs it fixes don't concern HAProxy's use of zlib. On amd64, zlib was Alpine's build: it's now built from source on both architectures.
How Janus is built:
- Every download is checked: the kernel, HAProxy and zlib sources weren't checked at all. Each download of the build is now checked against a sha256 pinned once its upstream's signature was verified.
- Pinned: the images the build starts from, by digest; the UKI's boot stub - the first code the firmware runs - from Debian's package in that pinned image, its version recorded, instead of whatever the build machine had; CI actions, by commit.
🖥️ Controller
-
🔒 security update: a node running a release older than one that fixes vulnerabilities gets a shield instead of the usual update badge - red when the worst of what it misses is rated critical or high, orange below:
- on its card, with a count in the page's header;
- in the node page's top bar, on Update in its menu, and in the Update view, with a link to the release that fixes it;
- the Controller's own update card, for the Controller.
It reads what each release fixes from its
security.json: nodes still on an older release show it as soon as the Controller runs this one.
📦 Releases say what they fix
- A release that fixes vulnerabilities is named "- 🔒 security update" and has a 🔒 section in its notes.
- Every release carries:
security.json: what it fixes since the previous release, each fix rated, for nodes or the Controller;sbom.cdx.json: what it's made of - every upstream component (with its download's checksum), Go module, npm package and base image (CycloneDX).
- When a fix is rated high or critical, a GitHub security advisory is published - this release gets one.
- To be told: watch the repository with Watch → Custom → Releases. GitHub's "Security alerts" option only reaches the repository's maintainers.
- SECURITY.md: supported versions (the latest release), how to report a vulnerability privately, and how soon a fix ships - 72 hours for critical, 7 days for high.
⚠️ Upgrade notes
- Nodes: update them - from the Controller (the node's Update page) or with
janusctl lifecycle upgrade. A node with extensions gets this release from the image factory once it has built it. - Controller: update it from its page - it's what shows the 🔒 badges.
- Nothing to configure.
📖 Docs: Security policy · Following upstreams · 🐳 Docker Hub
Full Changelog: v2026.10.03-4...v2026.10.03-5