• v0.4.0 d127f2ee55

    Release v0.4.0
    Some checks failed
    go-ci / lint + build + test (pull_request) Successful in 53s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 3m6s
    manifest-check / check (pull_request) Successful in 0s
    go-ci / lint + build + test (push) Successful in 52s
    release / decide + act (push) Successful in 8s
    release / release (push) Successful in 0s
    release / attach-release-asset (push) Failing after 34s
    deploy / deploy purser to /srv/docker/purser (release) Failing after 1m53s
    Stable

    carpenter released this 2026-08-21 17:12:56 +02:00 | 4 commits to main since this release

    Added

    • release: attach a compiled purser-linux-amd64 binary to each Forgejo release (#16)

      A new attach-release-asset job 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 gives PURSER_CERT_LIFETIME as 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_WINDOW is refused too: no minTLSCertDuration is 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/version had no test file; purser#7 carried a negative-control AC for the dev/unknown fallback that was never met. Six tests now cover the three arms — Tag set, VCS revision present, and neither. One arm pins the empty-revision guard: vcs.revision present 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 injectable readBuildInfo hook 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
  • v0.3.0 1d7da9e8e9

    Release v0.3.0
    All checks were successful
    go-ci / lint + build + test (push) Successful in 50s
    release / decide + act (push) Successful in 5s
    release / release (push) Successful in 0s
    deploy / deploy purser to /srv/docker/purser (release) Successful in 25s
    Stable

    Ghost released this 2026-08-06 13:48:14 +02:00 | 16 commits to main since this release

    Added

    • deploy: a failed post-deploy probe now restores the previous image AND the previous source tree (#32)

      docker compose up --wait replaces 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
  • v0.2.0 1036335189

    Release v0.2.0
    All checks were successful
    go-ci / lint + build + test (push) Successful in 48s
    release / decide + act (push) Successful in 5s
    release / release (push) Successful in 0s
    Stable

    Ghost released this 2026-08-06 11:50:19 +02:00 | 21 commits to main since this release

    Added

    • store: expired certificates' bundles are purged, so a stolen backup carries less (#3)

      A sweep runs at startup and hourly, clearing the stored .p12 ciphertext of any certificate past its not_after. The audit row stays; only the key material goes. Keyed on not_after and never on revoked_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 VERSION build-arg (#16)

      docker compose build --build-arg VERSION=v0.1.0 now 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 called abandon() directly, so removing its call from any exit in Issue() left them green while a live certificate leaked. Four Issue()-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_ROOT is now verified against the bundle it produces (#13)

      The loadCARoot tests check the loader in isolation and would all pass with Service.CARoot assigned nowhere — the original defect. A new test decodes the issued .p12 and 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 from PURSER_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_DRIFT remains the named path to change it deliberately and measure. The override and the conforming happy path are now tested at Issue(); both were untested.

    • docs: the backup-alone claim now states both legs it rests on (#3)

      The claim lived in internal/store while the entropy supporting it was stated in internal/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 deployed legacy-des the 3DES cipher binds first, at ~112 bits. Also records why the PKCS#12 iteration count is deliberately not raised.

    • docs: the 168h certificate 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.md now states the deployed value, the 8760h provisioner ceiling, and the reason not to reach for it. ocserv consults no CRL, so NotAfter is the only mechanism that ends access.

    Downloads
  • v0.1.0 828d97f305

    Release v0.1.0
    All checks were successful
    go-ci / lint + build + test (pull_request) Successful in 49s
    manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
    manifest-check / check (pull_request) Successful in 0s
    go-ci / lint + build + test (push) Successful in 48s
    release / decide + act (push) Successful in 5s
    release / release (push) Successful in 0s
    Stable

    alex released this 2026-08-05 22:38:07 +02:00 | 42 commits to main since this release

    Added

    • 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-toolkit v0.35.0 — fragment-check, manifest-check, and release workflows wired (#7)

      Pins the three reusable workflows from frankenbit/release-toolkit v0.35.0. First release-decide run on push to main will seed .release-toolkit-manifest.json and open the initial release prep PR.

    Downloads