Repository navigation
Releases
pubkit 0.34.0
0.34.0 - 2026-11-09
Commit ad3b30b986df; release record pub:sha2-256:db6xh6bj7bdqhpr6kohn6zocireos3fmt7vzt3g3z2fxxeewjq2q.
Changed
- The nightly's pins calls the sustenaut.bump hooks, as pubkit bump does, so a new pubdeploy or pubsec release reaches each repository's deploy.providers, launchers and lock without a bump by hand; the nightly installs the latest pubdeploy for its hook
- In the merge queue, pr.yml's sonar job is skipped, like the tests, when the pull request's own run already passed on the same tree; main's push still scans it with that run's coverage. A repository whose ruleset requires SonarQube Cloud's "SonarCloud Code Analysis" check drops it from protect.checks and runs
pubkit protect, or its queue runs wait for a check that never comes - pubkit pins moves every other repository's reusable workflow that a hand-written workflow pins at a release (OWNER/REPO/.github/workflows/...@) to that repository's latest release, by its tag's commit, except a deploy.providers package's, which its own bump hook moves
Removed
- actions/deploy-ssh leaves pubkit: it is pubdeploy's, whose k8s-deploy.yml calls its own copy from pubdeploy 0.2.0; a workflow of a repository's own that uses Sustenaut/pubkit/actions/deploy-ssh moves to Sustenaut/pubdeploy/actions/deploy-ssh
- The commands a control plane runs across many repositories moved to pubfleet, with the same arguments: board, board-publish, state, variables and freshness, with the reusable board.yml and nightly.yml and .github/nightly/; pubkit keeps protect and the repos file, the variables block's shape that sync checks, bump, and the per-repository nightly (pubkit nightly, pins, crates, record)
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.34.0 wheel by its sha256
pubkit 0.33.0
0.33.0 - 2026-11-09
Commit d770bafe0ec5; release record pub:sha2-256:w6ksf635mhse2l7ys3edcw3d5mepndzmg2t5v7v4nl5txmrcwjqq.
Removed
- The deploy walk leaves pubkit for pubdeploy (Sustenaut/pubdeploy): pubkit deploy walk, check and types, pubkit k8s preflight and diagnose, k8s-deploy.yml, deploy-check.yml and k8s/components/app are gone, with no stubs; run ./pubdeploy, which pubkit sync writes in a repository with a deploy block, and call pubdeploy's workflows. deploy.providers now lists pubdeploy at pubkit's floor beside pubsec, ./pubkit runs with it, and sync and bump call the new sustenaut.sync and sustenaut.bump hook groups, pubdeploy's being the deploy block's offline check, the scheduled walk's caller, and moving deploy.providers and the uses of pubdeploy's workflows. deploy-ssh stays until pubdeploy calls its own copy
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.33.0 wheel by its sha256
pubkit 0.32.1
0.32.1 - 2026-11-09
Commit 94373cfb8a07; release record pub:sha2-256:zv6cml67vb3vvazc6svvfvk3chaipclbezkproicgf7kdvyybzeq.
Fixed
- tests/requirements-pubkit.txt lists deploy.providers' wheels (pubsec, and any other provider) beside pubkit, so pubkit sync --check, which runs in the tests' environment here and in CI, finds the need types they offer, and pubkit bump moves both pins at once
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.32.1 wheel by its sha256
pubkit 0.32.0
0.32.0 - 2026-11-09
Commit 0a436c5d805f; release record pub:sha2-256:uxmbegaitjzuvjv5k3dyzxgfas5zla7lf3velbrojiremsdvppsa.
Changed
- The merge queue skips the tests a pull request's own run already passed on the same files: job-tested, in the queue and on main, looks for a successful run of the queue's workflow (on a pull request or in the queue) whose commit has this commit's tree, following a run that skipped its tests to the run it took them from, and its coverage when not expired. pr.yml starts with tested where the queue runs it, as main.yml does; a pull request's own run, a moved main or a batch of pull requests still tests everything, and a Rust crate's queue.yml is unchanged
Removed
- The commands and need types that hold a secret's value, a write token or a private key are pubsec's (Sustenaut/pubsec): pubkit secrets run, app-secrets, github-app create, service-account-key create and signing-key backup now print the ./pubsec command that replaces each and exit 2, and go in the next minor; bitwarden.secret, bitwarden.generated, gcp.service-account-key, github.secret, github.variable, github.deploy-settings and k8s.secret are no longer built in. pubkit sync writes ./pubsec, which runs the pinned pubsec release, its wheel checked against its sha256, beside ./with-secrets, which now runs ./pubsec run, and lists pubsec in deploy.providers of every repository with a deploy block (sync --check fails without it); deploy-check.yml and terraform.yml run ./pubsec run. The walk asks for no write token itself: it calls each sustenaut.walk hook, prepare(ctx, plan, terminal), and pubsec's asks for it. The provider contract, the Context names a provider's types use, is frozen from this release, as the README's Provider contract section lists it: changing one is a breaking change
Added
- A wheel mirror for when GitHub's release downloads fail: pubkit.json's mirror.wheels names a base that serves each wheel at /f//.whl. The new managed tests/install-lock, which pubkit.mk's venv target and the reusable workflows' hash-locked installs now go through, tries a lock three times and then once more with each GitHub release wheel taken from the mirror, the hashes unchanged; pip-audit's last try reads the locks rewritten the same way, and ./pubkit fetches its own wheel and deploy.providers' wheels from the mirror when GitHub fails, each checked against its sha256. Without the setting, installs only retry and ./pubkit is unchanged
- A provider mirror for Terraform: terraform.yml's init and the service CI's backend-less init are tried three times, 15 s apart, and then, when pubkit.json's mirror.terraform names a Terraform provider network mirror, once more with every provider from it; the module's lock still checks each archive
- tests/install-lock's last try takes every package not pinned by a GitHub release URL from pubkit.json's mirror.pypi, a simple repository index (PEP 503) at /simple/, instead of PyPI, the lock's hashes unchanged; the mirror block takes pypi beside wheels and terraform
Fixed
- CI: the reusable workflows try each setup step (make venv, npm ci, a repository's own), each hash-locked install of tests/requirements.lock and pip-audit up to three times, 15 s apart, and the managed pubkit.mk's venv target its install likewise, as pip retries no 504 and GitHub's release downloads sometimes answer one; a finding still fails pip-audit, as it fails each try. pubkit deploy walk --fulfil: a generated secret that is in Bitwarden but misshapen is planned as "replace NAME (misshapen: why)", not as generating a new one, so the typed yes knows a value goes. The dependency review leaves the owner's own reusable workflows and actions, which carry no license GitHub reads, out of its license check alone: pubkit sync lists those a repository's workflows call in .github/dependency-review-licenses.txt, of which the review takes only the owner's, and without the file it leaves out pubkit's and pubrel's; advisories still apply to them
- The dependabot.yml pubkit sync writes no longer opens pull requests that move the owner's pubkit or pubrel pins: every github-actions entry ignores /pubkit/* and /pubrel/*, ahead of any ignores given in security.dependabot.options. Those pins move with pubkit bump, so such a pull request always failed pubkit sync --check
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.32.0 wheel by its sha256
pubkit 0.31.3
0.31.3 - 2026-11-08
Commit 32deb2d21f8c; release record pub:sha2-256:g2r5p77hbpcpz4binym6ppcgmb6akmpsavjc3kka3itdkdrm6gna.
Fixed
- CI: the Terraform module job retries terraform init up to three times, caches each module's providers, and no longer cancels the other modules when one fails, so a timed-out provider checksum download from GitHub no longer fails the run
- pubkit deploy walk: the admin channel keeps one SSH connection for the walk (ControlMaster, its socket in a private directory), opened before the first check, so k8s.namespace, deploy.rollout and the Secrets no longer run out of their budgets on a new connection's second over the tailnet; k8s.namespace's budget is 4 s and deploy.rollout's 8 s, which asks for its three run listings at once; github.deploy-settings asks for its four listings at once, inside its 3 s; and a k8s.secret that is ready by its keys is made again, and its readers restarted, when the walk makes a new value of a need it is made from, so Bitwarden and the cluster no longer differ after a misshapen generated secret is replaced
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.31.3 wheel by its sha256
pubkit 0.31.2
0.31.2 - 2026-11-08
Commit c59e1b582490; release record pub:sha2-256:suxorjdsqg6kwezcdomhkb7trnroozpfgi5bhdabzjhlyd67sfia.
Added
- pubkit deploy walk: the deploy check (deploy check is now walk without --fulfil), and with --fulfil it makes what is missing. It prints the plan, waits for a typed yes (--yes skips it), asks once at a hidden prompt for a Bitwarden write token when a need wants one, and fulfils each auto need that is missing and whose dependencies are ready, checking it again: namespaces over the admin channel (ssh root@HOST), Secrets from the values that fill them on kubectl's standard input with a restart of what reads them, manifests, GitHub secrets and variables from Bitwarden as app-secrets sends them, the gate, the rollout, CronJob runs, generated Bitwarden secrets and service account keys; propose needs print the change to make. The deploy channel (--kubectl naming the server's kubectl-read verb) reads only, and lists a Secret's key names with secret-keys. New need types gcp.service-account-key, auth.sign-in-client, storage.tenant, github.variable-entry and k8s.cronjob; deploy.units walks several units, each journaled; deploy.providers runs ./pubkit with the packages whose types the needs use, each wheel checked against its sha256, and pubkit bump moves them; --journal URL writes the deploy-journal/2 document to storage; the reusable deploy-check.yml walks it every 30 minutes, read-only, and pubkit sync seeds its caller. pubkit sync manages with-secrets in every repository with a with-secrets.json, and sync --check fails when the deploys lines of k8s-deploy.yml and the units disagree, or when a deploy calling k8s-deploy.yml 0.31.0 or later is not started by workflow_run, which pubkit bump rewrites
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.31.2 wheel by its sha256
pubkit 0.31.1
0.31.1 - 2026-11-07
Commit 4e83a6b83234; release record pub:sha2-256:d3q63qaivbynxvpildwkxbvucpmz4odcuefps4w2e65jovbsjucq.
Added
- terraform.yml, a reusable workflow that applies a Terraform stack from the default branch (on a push there, or by hand), as a Google account it gets through Workload Identity Federation (inputs workload-identity-provider and service-account; callers grant id-token: write and pull-requests: read): it plans to a file and applies exactly that plan, one run at a time, with the plan in the job's summary. Nothing runs for a pull request, for another ref, or until the repository variable TERRAFORM_CI is on. The job runs in the environment named by environment (terraform), which the caller limits to the default branch, so a provider can trust the token's subject repo:OWNER/REPO:environment:terraform alone (or its immutable form). A plan that deletes anything, replacements included, is applied only when the merged pull request has the label terraform-destroy. With secrets (the default) every terraform command runs through ./pubkit secrets run with the environment's secret BWS_ACCESS_TOKEN; with secrets: false terraform runs plainly
- pubkit deploy check, and pubkit.json's deploy block: what a service needs before it deploys (its namespace, the Secrets its pods read and what fills them, GitHub secrets and variables, the services it calls, its host), implied where pubkit.json and the rendered manifests already say it, and checked in order, each ready, missing, drift, blocked or unknown, with what to do about it; it reads kubectl, gh, HTTPS, dig and the environment ./with-secrets gives, changes nothing, prints no value, and --json prints the walk as a deploy-journal/1 document. --offline checks what needs no network, and pubkit sync --check runs it: every Secret the rendered overlay reads must be one a need fills, with its keys, and every Secret a need fills must be read. pubkit deploy types lists the 14 need types pubkit has and any a package offers under the entry-point group sustenaut.needs. pubkit secrets run (./with-secrets) sets PUBKIT_SECRETS_PROJECT to the project it read. pubkit now depends on PyYAML, to read the manifests kustomize renders
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.31.1 wheel by its sha256
pubkit 0.31.0
0.31.0 - 2026-11-07
Commit d9131ec530aa; release record pub:sha2-256:juscqofinzetmunbyjjpgdjb7p7hg7iezd67qwjnnup5eaxxgxaq.
Changed
- k8s-deploy.yml deploys a commit only once CI has passed on it: a ready job finds the commit (the one a workflow_run tested, when that run passed on a push to the default branch; a pull request's or the merge queue's run deploys nothing) and waits, up to wait minutes, for the latest push run on it of each workflow require names, main and, where there is one, integrations, failing if one did not pass; build and deploy check out, tag and deploy that commit, and a commit the default branch has moved on from is not deployed, except by hand. A caller grants actions: read and moves its trigger from push to a workflow_run of its CI. pubkit k8s preflight, for a server's deploy program to run before it applies, checks through kubectl that every Secret rendered manifests read is there with every key they name (env, envFrom, secret and projected volumes, imagePullSecrets; an optional reference may be missing), and that oauth2-proxy's cookie secret is 16, 24 or 32 bytes as it is or after URL-safe base64 decoding, naming standard base64 as the likely mistake; it fails in seconds, naming the Secret, the key and what reads it, and never prints a value. pubkit k8s diagnose prints the state, last exit, warning events and last logs of each pod that is not ready. Both are one file that runs without pubkit
Added
- pubkit init and sync add .env to a repository's .gitignore when it is missing, leaving the rest of the file as it is, and pubkit sync --check fails until it is there: local tools such as ./with-secrets read secrets from .env, so no repository may commit one
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.31.0 wheel by its sha256
pubkit 0.30.1
0.30.1 - 2026-11-06
Commit c2bd664bb5df; release record pub:sha2-256:7m3c2nyqy4opuqgkbyd5zextptgdgftlzvl5u7w3wujjxar4c7fa.
Added
- pubkit variables --check and --apply keep repositories' GitHub variables to the values a state file gives each one, strings or structured JSON: --check prints how they differ, a diff of the changed entries for JSON, and fails; --apply sets those that differ and runs the deploy workflow the repository's pubkit.json names. A pubkit.json declares the names it reads in a variables block, which sync checks, and state --check refuses a state value for a name its repository does not declare
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.30.1 wheel by its sha256
pubkit 0.30.0
0.30.0 - 2026-11-06
Commit 7ac743dbdf0c; release record pub:sha2-256:tfdmnylcg4w7powbxns4nesmtscnavctllkbiwdehx3xni3ox5rq.
Changed
- The managed AGENTS.md asks every branch to take in origin/main before each push: a rebase if it was never pushed, a merge if it was, and a stop on any conflict whose resolution is not clear
Added
- actions/deploy-ssh takes input, a file sent as it is on the command's standard input in place of a bundle, such as rendered manifests for a program that applies what it reads
- k8s/components/app, a kustomize Component a service takes by a pinned remote URL: it patches the Deployment app to run as user 1000, not root, under RuntimeDefault seccomp, with no API token, its container app with no privilege escalation, no capabilities and a read-only root filesystem, and probes /healthz to start and be ready and /livez to stay alive, on the port http
- k8s-deploy.yml, a service's whole deploy workflow for a k3s server: it builds the image with actions/docker-build, runs the caller's check, pushes it to ghcr.io, renders each overlay deploys lists with kustomize and sends it to the server's forced command with actions/deploy-ssh over the tailnet, one run at a time per unit, in the environment server; a unit can wait on a variable set to on, and nothing deploys without SUSTENAUT_SERVER
Install:
pubkit init --kind=rust|utility|service, or pubkit sync, then pubkit lock: the lock pins the v0.30.0 wheel by its sha256