-
Release v0.4.0
StableSome checks failedgo-ci / lint + build + test (pull_request) Successful in 53smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 3m6smanifest-check / check (pull_request) Successful in 0sgo-ci / lint + build + test (push) Successful in 52srelease / decide + act (push) Successful in 8srelease / release (push) Successful in 0srelease / attach-release-asset (push) Failing after 34sdeploy / deploy purser to /srv/docker/purser (release) Failing after 1m53sreleased this
2026-08-21 17:12:56 +02:00 | 4 commits to main since this releaseAdded
-
release: attach a compiled
purser-linux-amd64binary to each Forgejo release (#16)A new
attach-release-assetjob runs after the cut and uploads the binary to the draft release before the operator publishes it. The binary is built with the same-ldflags "-X …/version.Tag=${TAG}"injection used by the Docker image, so the login page and the Docker-deployed binary report the same tag. The job is a no-op on non-cut runs (update / noop mode) and on dry runs.
The dashboard can now choose a certificate's expiry at issue time, up to
PURSER_MAX_CERT_LIFETIME. Leaving the box empty givesPURSER_CERT_LIFETIMEas before. The control stays hidden until the ceiling is raised above the default, so nothing changes for a deployment that does not opt in.A request above the ceiling is refused before anything is signed, with the limit named. A request shorter than
PURSER_DOWNLOAD_WINDOWis refused too: nominTLSCertDurationis set on the provisioner, so the CA would sign a certificate that can expire before it finishes being installed.-
docs: document dashboard password rotation with
hashpw(#44)Operators now have a runbook for replacing the mounted bcrypt hash without putting the password in argv or adding a change-password form to Purser.
Changed
None.
Fixed
-
version: add tests for the Tag → VCS revision → dev fallback chain (#29)
internal/versionhad no test file;purser#7carried a negative-control AC for the dev/unknown fallback that was never met. Six tests now cover the three arms —Tagset, VCS revision present, and neither. One arm pins the empty-revision guard:vcs.revisionpresent with an empty value must fall through to"dev". A mutation-verified negative control asserts the chain never returns an empty or invented string. Each arm uses an injectablereadBuildInfohook so tests exercise the full chain regardless of whether VCS info is available in the CI container. -
release: address Lookout review 4976 on attach-release-asset job
Removed
None.
Deprecated
None.
Upgrade
None.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
Release v0.3.0
Stablereleased this
2026-08-06 13:48:14 +02:00 | 16 commits to main since this releaseAdded
-
deploy: a failed post-deploy probe now restores the previous image AND the previous source tree (#32)
docker compose up --waitreplaces the container and waits second, so by the time the probe can refuse, the predecessor is already destroyed. The deploy now tags the running image before building and, on any failure after that point, restores both halves and verifies each. Restoring only the container would leave the tree at the new tag while the old image runs — the divergence the probe's first arm would then report as success.
Changed
None.
Fixed
- release: cut to a draft so the publish click can fire the deploy
Removed
None.
Deprecated
None.
Upgrade
None.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
Release v0.2.0
Stablereleased this
2026-08-06 11:50:19 +02:00 | 21 commits to main since this releaseAdded
-
store: expired certificates' bundles are purged, so a stolen backup carries less (#3)
A sweep runs at startup and hourly, clearing the stored
.p12ciphertext of any certificate past itsnot_after. The audit row stays; only the key material goes. Keyed onnot_afterand never onrevoked_at— a revoked-but-unexpired record keeps its bundle, because the 403 refusal path reads it. A download resolving to a purged record now returns 410 rather than a zero-byte file. -
deploy: pass VERSION to the build and assert the deployed binary reports it
-
deploy: probe the #5 regression surface, and make the probe parser field-safe
-
deploy: deploy purser from CI on release cuts
Changed
None.
Fixed
-
version: inject release tag via Dockerfile
VERSIONbuild-arg (#16)docker compose build --build-arg VERSION=v0.1.0now stamps the binary; without the arg the build falls back to the VCS commit hash or"dev". The ldflags path is-X git.frankenbit.de/frankenbit/purser/internal/version.Tag. -
changelog: split three sentences over the 30-word density ceiling
-
deploy: arm 5 must survive a version-less page, and the disclosure contradicted it
-
docs: 120 bits bounds the password search, not the bundle's strength
-
docs: legacy-des does not use PBKDF2 — and the error was in three places
-
deploy: arm 4 asserts the exact 404 contract, not merely "not a redirect"
-
deploy: wait for health before probing — the inspect raced the healthcheck
Removed
None.
Deprecated
None.
Upgrade
None.
Internal
-
purser: every post-sign failure path is now proven to revoke, not just inspected (#12)
The existing
TestAbandon_*tests calledabandon()directly, so removing its call from any exit inIssue()left them green while a live certificate leaked. FourIssue()-level tests now cover the drift, password, packaging and store exits, each mutation-verified to redden only for its own branch. No behaviour change. -
purser:
PURSER_EMBED_CA_ROOTis now verified against the bundle it produces (#13)The
loadCARoottests check the loader in isolation and would all pass withService.CARootassigned nowhere — the original defect. A new test decodes the issued.p12and asserts the root is present with the flag set and absent without it. No behaviour change. -
purser: the profile gate's key baseline is pinned to the measured RSA-3072, not derived from config (#2)
expectedProfile()used to take the key expectation fromPURSER_KEY_TYPE, so setting ECDSA made ECDSA the expectation and the gate fell silent at the one moment it exists to speak. A setting proves intent, not that the device accepts it.PURSER_ALLOW_PROFILE_DRIFTremains the named path to change it deliberately and measure. The override and the conforming happy path are now tested atIssue(); both were untested. -
docs: the backup-alone claim now states both legs it rests on (#3)
The claim lived in
internal/storewhile the entropy supporting it was stated ininternal/purser, so a reader had to cross packages to learn what it depended on. It rests on a conjunction. The password contributes 120 bits against a password search; the encryption is the other, equally load-bearing leg. Under the deployedlegacy-desthe 3DES cipher binds first, at ~112 bits. Also records why the PKCS#12 iteration count is deliberately not raised. -
docs: the
168hcertificate lifetime now carries its reasoning (#4)The value was chosen and proven. The configuration table still described it only as "how long an issued certificate is valid". That is the unexplained-number state this tracker exists to prevent, with a new number in it.
docs/operations.mdnow states the deployed value, the8760hprovisioner ceiling, and the reason not to reach for it. ocserv consults no CRL, soNotAfteris the only mechanism that ends access.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
Release v0.1.0
StableAll checks were successfulgo-ci / lint + build + test (pull_request) Successful in 49smanifest-check / manifest-vs-tag consistency (pull_request) Successful in 4smanifest-check / check (pull_request) Successful in 0sgo-ci / lint + build + test (push) Successful in 48srelease / decide + act (push) Successful in 5srelease / release (push) Successful in 0sreleased this
2026-08-05 22:38:07 +02:00 | 42 commits to main since this releaseAdded
-
version: running version shown at startup, on the login page, and in the dashboard footer (#7)
Read from
internal/version.Tag(set via-ldflags "-X .../version.Tag=vX.Y.Z"at build time) with a fallback to the short VCS commit hash or"dev"for scratch builds. Binary release wiring (auto-injection at release cut) is a follow-up. Visible pre-authentication on the login page so the running version is readable during an incident without logging in. -
bundle: SHA-1 MAC substitution + the encoding wiring it depends on
Changed
None.
Fixed
- changelog: drop @ prefix on release-toolkit pin to unblock v0.1.0 cut
- version: correct false release-workflow injection claims (#7)
- release: remove false build_command/asset_paths claim; correct fragment (#7)
- web: delete the stale comment asserting /d/{token} is session-protected
- web: serve /d/{token} on the token alone — Secure Client was parsing the login page
- purser: delete Service.P12Encoding — read Cfg directly, no wire to cut
- purser: embed a VALIDATED bundle root, not the CA's TLS trust file
- purser: route both leaking post-sign exits through abandon(), assign CARoot
- profile: annotate the deferred criticality instruments so go-ci passes
- binary-size-check: make arm 19 discriminate the jq selector it claims to pin
Removed
None.
Deprecated
None.
Upgrade
None.
Internal
-
release: adopt
release-toolkitv0.35.0 — fragment-check, manifest-check, and release workflows wired (#7)Pins the three reusable workflows from
frankenbit/release-toolkitv0.35.0. Firstrelease-deciderun on push to main will seed.release-toolkit-manifest.jsonand open the initial release prep PR.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-