- An event bus between the hub's processes (issue #273): on PostgreSQL, what one process publishes (a push, a check, a run's jobs, a comment, a waiting webhook delivery, a choice in the conflict resolver) reaches every other process over
LISTEN/NOTIFY, so live pages on any web process see a push made through another, and a separate worker delivers a push's webhooks at once instead of on its once-a-minute pass. Nothing to configure; on SQLite the bus stays in process. Events say "look now" and are delivered at most once; a process that loses its listening connection listens again on a new one and logs that events may have been missed. A live page that reconnects with an event id its process never gave (another web process's, or one from before a restart) is now told to reload instead of waiting for newer ids. In the conflict resolver, choices and landings cross processes; who is present stays per process. See docs/roles.md. - Run the hub as web and worker processes (issue #272):
oxygenhub serve --role web,worker(or[server] roles,OXYHUB_SERVER_ROLES; both by default, so one process works as before). A web process serves pages, the API, the wire protocol and the runner API; a worker runs the background tasks and serves only/healthand/metrics. On PostgreSQL each singleton task (Flow's scheduler, reaper and placement pass, webhooks, nightly upkeep, backups, Diffusion's index, the log's checkpointer) runs in one process at a time under an advisory lock, and another takes it over within seconds when that process stops or loses its connection./healthreports the process'srolesand the locks it isleading. Until the transparency log has a cluster-wide writer (issue #274), run one web process per data directory. See docs/roles.md. - Repositories kept in a bucket need far fewer requests (oxygen#116): the hub reads a repository's signatures and index entries from a journal and entry lists instead of a LIST per lookup, and stops asking for the old one-list keys once its nightly gc has folded them. Another writer's new entry is seen within 30 seconds. Every writer of a bucket repository should run oxy 0.5.0 or later: until it does, entries it writes after the first fold are seen only after the next nightly gc.
- Push protection (issue #268): the hub refuses a push that adds what looks like a credential (cloud keys, GitHub, GitLab and OxygenHub tokens, Slack and Stripe keys, private keys; rules v1), naming each by file, line and a fingerprint, never the secret itself, and saying how to take it out (
oxy amend) and to revoke it if real. Nothing of a refused push is stored. A maintainer can allow one fingerprint with a reason (Settings › Push protection, audited); the setting is on for every repository and can be turned off there. Only what a push adds is scanned, within[limits] scan_limit(16 MiB),scan_file_limit(1 MiB) andscan_time_ms(2 s); a push past them is accepted and its push log says how much went unscanned. Existing history is not scanned yet. - Land when checks pass, or land now over them (issue #261): anyone who may land a proposal can set it to land by itself, on the proposal, through
POST/DELETE …/proposals/{n}/auto-landor withoh proposal land N --when-ready(--offto clear it). Once every required check has passed and nothing else blocks it, it lands as the person who set it, if they may still land. A required check that fails or is cancelled turns it off with a note on the timeline; a push to the source makes it wait again. When required checks are the only thing in the way (pending, or failed with the runners down), a maintainer may land now with a reason: on the proposal, with{"over_checks": "reason"}on the API's land, oroh proposal land N --over-checks REASON. Only the checks are passed over; approvals, code owners, requested changes, drafts, dependencies and the trust policy still apply. Both are audited, and the proposal, its timeline and the pushes page say how it landed. - The repository bar has an Artifacts menu and a More menu (issue #254): the bar is now Code, Changes, Proposals, Issues, Flow, Releases, Wiki, Artifacts, More and Settings, and no longer wraps. Artifacts holds Releases, Build artifacts (every run's outputs in one list, new), Images (the runner images the repository's jobs used, new), Terraform state and Reports; More holds Bookmarks, Tags, Diffusion, Trust, Pushes and Conflicts. Projects leaves the bar (it is on the site band and every profile). The menus work without JavaScript.
- The hub keeps Terraform and OpenTofu state (issue #252, issue #255): an HTTP backend per repository at
/api/v1/repos/OWNER/NAME/terraform/state/STATE, with locking. A job uses it with an identity token for{base}/terraformor its job token, for its own repository only; people use a token of theirs and their role. State is sealed at rest and its last 20 versions are kept; backups carry it. The Terraform state page (under Artifacts) lists each state's versions; writers download the current state or any version as a.tfstatefile, and maintainers restore one. A restore writes the version again with a serial above the current one, since OpenTofu and Terraform only take a state forward, and keeps the state it replaces as a version, so nothing is lost. Refused while the state is locked, never by a job, and audited. - Insights (issue #257): OxygenFlow's charts for the site (
/admin/insights, filtered by repository, owner or runner), an owner (/insights/{owner}) and a repository (/{owner}/{name}/insights): pickup time (median and p95), jobs finished and pass rate, duration, queued and running, runners online and slots busy, a runners table and the busiest repositories, over 1 hour, 24 hours or 7 days. The page refreshes itself every 30 seconds, and?wall=1is a view without menus for a second monitor.[server] grafana_url(OXYHUB_SERVER_GRAFANA_URL) adds an Open in Grafana link.log,metricsandinsightsare now reserved names. - Prometheus metrics (issue #212):
/metricsserves Prometheus text to a bearer of[server] metrics_token(OXYHUB_SERVER_METRICS_TOKEN); without one it is 404. It reports the build, uptime, memory, the state cache, bucket requests by kind, HTTP requests by route class with latency histograms, protocol bytes by endpoint, the database pool, pushes accepted and refused, Flow jobs and runs, runners' last heartbeats, backups, mail sent and failed, and the transparency log's size. - A transparency log (issue #238): the hub logs every signed object it accepts (reference and tag attestations, seals, the policy, its acceptances, short-lived keys' certificates, kept receipts and sealed approvals) in one append-only Merkle tree, and signs a checkpoint every 5 seconds while entries arrive. The log is served as C2SP tlog-tiles under
/log/, with a/logpage. Every refs answer carries the latest checkpoint and each bookmark's entry with its inclusion proof, so oxy checks them on fetch. Hubs can witness each other's logs:[tlog] witnessesandwitness_forinhub.toml(none by default, so nothing changes until set). - Trusted time (issue #240): the hub keeps each cosigned checkpoint, lists its witnesses at
GET /log/witnessesand in every refs answer, and serves for each logged bookmark the first cosigned checkpoints that hold its entry, from which oxy takes the integrated time: when a witness first saw it, by the witness's clock. - Agents act for their owner with no key (issue #241, issue #253): an owner makes a grant with
oh agent grant NAME [--scope] [--for 30d], signed with their own oxy key; the hub keeps it and answers with the agent's token, shown once. The agent exchanges it atPOST /api/v1/keyless/certifyfor ten-minute certificates, so its fresh keys' commits count in the owner's name, and the same token works for fetch, push and the API as an agent's token of the owner (writing when the grant allows). What the agent does is marked as its own and its owner is told.oh agent listandoh agent revokemanage grants, and a revoked grant gets no more certificates. When a push moves a bookmark with a short-lived key, the hub countersigns it with its own clock. Runners' receipts signed with keys this hub certified are kept, and a release's trust report counts them as known signers. - Approval gates (oxygenflow#57, issue #235): a job whose pipeline asks for approval waits until someone with the role it names approves it on the run page, through the API, or with
oh run approve N --job NAME(oh run rejectto refuse). Everyone who may decide is notified once the job is ready. A rejected or expired job ends cancelled and the jobs after it are skipped. A gate that asks for a sealed approval (the default for a job with an environment) is released only by a signature of the approver's own key (oxy seal --flow), kept in the repository and named in the job's identity tokens. - Environments and deployments (oxygenflow#59): a job's identity tokens name its environment only once the job is approved.
/{owner}/{name}/deploymentsandGET /api/v1/repos/{o}/{n}/deploymentsshow what each environment runs, who approved it, and the history. - Artifacts between jobs, and allow-listed egress (oxygenflow#58, oxygenflow#55): a job's inputs come from the artifacts of a job it depends on in the same run, served to the runner holding it. A sandbox with
network "egress"goes only to runners offering egress. - Flow priorities (issue #244): runners take queued jobs by level first (urgent, high, normal, low), then by turn, so repositories take turns instead of one repository's burst holding everyone. The level comes from the run when expedited, else the repository (Settings › OxygenFlow), else the organisation, else the site. A maintainer can expedite a queued or running run to urgent with a reason; at most 2 expedited runs per owner go at once unless Admin › Runners › Settings says otherwise. A pipeline may lower its own jobs with
priority "low", never raise them. - Runners show Connectivity and Status apart (issue #243): Admin › Runners shows whether a runner is online (when the hub last heard from it) apart from what it is doing (disabled, draining, idle, or running N of M). A busy runner sends a heartbeat, so a quiet job no longer shows it offline. The queue filters by repository, status and runner, and shows when each job started.
- Why a job waits (issue #259): a job some runner could take now says why it still waits: which of those runners are busy (
earth-1 busy (3 of 3)) or lack the memory or disk for it now, and how many ready jobs come first. - Pages update themselves (issue #203–issue #207): the proposal page's timeline and Landing section, the notification bell, the inbox, the dashboard, the issue page, the repository overview and Diffusion refresh in place when something changes, over one
/livestream per page. Checks keep their width and refresh in place instead of reloading the whole page (issue #232). - Signing in from an invitation with GitHub, GitLab or Google (issue #214): the registration page shows the providers' buttons for a usable invitation, and the account is made only with an unused code.
git cloneon the Code panel (issue #215) when the repository's Git mirror is on, belowoxy clone.oh issue comment -F FILE(issue #260): takes the comment from a file, or from standard input with-F -, as otherohcommands with a body do, and refuses an empty comment.- PostgreSQL (issue #211, issue #242): the hub can run on PostgreSQL 16 (or 15) when built with
--features postgres, set by[database] url(OXYHUB_DATABASE_URL).oxygenhub admin copy-db --from <old>/hub.dbmoves a stopped SQLite hub's records into an empty Postgres database (--replaceempties a fresh one first). Releases includeoxygenhub-server-VERSION-linux-x86_64.tar.gz, built for PostgreSQL. See docs/postgres.md. - SLSA provenance for build artifacts (issue #239): an artifact of a job whose signed receipt the hub kept has
…/provenance, an in-toto SLSA provenance v1 statement in a DSSE envelope signed with the hub's key; release assets link it (built_by.provenance_urlin the API).cosign verify-blob-attestation --key oxyhub-signing.pem --type slsaprovenance1 --insecure-ignore-tlog --signature PROVENANCE ASSETchecks a download, with the key from/.well-known/oxyhub-signing.pem. - Assistants stream their answers (issue #248): the time limit is now for silence, not the whole answer, so a large proposal no longer times out with nothing. Drafts fill the editor as they are written. The assistant is told every changed file before the diff, and which files it could not see; a provider that refuses a prompt as too long is asked once more with less.
- Agent tokens decide approval gates only where the repository allows it (issue #258, issue #229): a gate exists so a person decides. A decision through an agent's token (an agent-marked token or an agent's grant) is refused, saying why, unless Settings › OxygenFlow lets agents decide the repository's gates (off by default). Where allowed, the decision names the agent ("approved by carol via claude-code"). A sealed approval is a person's own key and is not affected.
- A landed proposal's runs that have not started are cancelled (issue #263): they can no longer change anything and held the queue ahead of the target's own run. Each such job says "moot: proposal #N landed"; runs already going finish.
- Newest run wins (issue #267): a new push run cancels the older push runs of its bookmark still going, as before, and now a proposal's new run cancels that proposal's older runs too, so
proposal/Nruns no longer pile up three or four deep. Each cancelled job says which run superseded it. Runs started by hand, by a schedule, by a tag or as a re-run are never cancelled this way. Settings › OxygenFlow › Newest run wins lets maintainers keep superseded runs that are already running, cancelling only the queued ones. - Flow on your own runners (issue #225): a repository's maintainers can turn OxygenFlow on for it themselves once it (or its owner) has a runner of its own: Settings › OxygenFlow › "on, own runners only". Its jobs then go only to those runners, never to the site's; letting the site's runners take them stays a site administrator's choice.
- Readers never wait for a push or gc (issue #249): pages, the API and fetches stalled for up to 43 s while a repository's gc or a push held its lock. With oxy's store now safe to read alongside a writer, reads take only a shared lock that deleting or moving a repository holds alone; pushes, gc, landings, web edits and the rest still take turns as writers.
- Repositories kept in a bucket answer quickly (issue #228, issue #234): the hub shares one client per bucket and credentials, keeps ranges of large packs on disk, looks in packs first, remembers a missing key for 30 s and lists packs once a while. The nightly gc remembers the day it ran across restarts, so a deploy during
gc_hourno longer runs it again, and a store that is already one pack is left alone./healthshows bucket requests by kind. - Restarts drain (issue #233): on SIGTERM the hub stops taking connections, ends live streams (browsers reconnect) and gives requests in flight up to 30 s, then stops within a second. It can take its socket from systemd (socket activation), so requests that arrive during a restart wait instead of failing: install
oxygenhub.socketand the service changes in docs/deploy.md. - Diffusion mends itself (issue #210): every 10 minutes the pin index is compared with each followed bookmark and re-indexed where it differs, and a consumer left behind with no open bump is caught up. A failed index write says which step failed and is tried again.
- The state cache counts what states really cost (issue #246): a loaded state was weighed at a quarter of its real size, so the hub held far more memory than its 3 GiB budget. It now weighs states by what they take, so fewer stay cached; raise
OXYHUB_STATE_CACHE_MBif large repositories miss it too often. - Sessions are kept on the server (issue #223): the cookie holds a random id, and a session ends after 14 days unused or 30 days in all. Settings › Security lists your signed-in browsers with Sign out on each. Old cookies no longer work, so everyone signs in once more.
- A new hub gives administrator only to whoever brings its setup code (issue #245): on a hub with no accounts the first registration made an administrator with no invitation, whatever the registration mode. The hub now writes a one-time setup code to
<data>/setup-code(owner-only) and logs where it is, or takes[registration] setup_code(OXYHUB_REGISTRATION_SETUP_CODE); the first registration must bring it. A sign-in provider never makes the first account.oxygenhub admin user create --adminneeds no code. - Terraform state needs the write role to read (issue #256): every account has the Read role on public and internal repositories, so any account's
repo:readtoken could read their Terraform state, which often holds secrets. Reading a state now needs arepo:writetoken and the write role; the repository's jobs keep their access. - HTTPS only (issue #218): on a site whose
base_urlis https, a request that a proxy reports came over plain http is redirected (GET, HEAD) or refused (anything else), so a form or password is never taken over http. Every cookie is Secure on an https site. - Requests out only to public addresses (issue #219): webhooks and assistants refuse a host that resolves to a loopback, private, link-local (the cloud metadata address among them) or other local address, pin the connection to the checked addresses and do not follow redirects. A site administrator who wants a local service, such as a local Ollama, must allow it with
[outbound] allowinhub.toml. - Webhooks read at most 64 KiB of an answer (issue #220), and a hook with 1000 deliveries waiting takes no more until it catches up.
- Guessing is slowed (issue #221): failed sign-ins, two-factor codes, device codes and reset requests count per account (5 free) and per address (20 free), then each doubles the wait, from 2 seconds to a 15-minute lockout; the page says when to try again.
- Security headers (issue #222, issue #227, issue #231): every response is sent with
nosniff, a referrer policy and a permissions policy, and pages withX-Frame-Options: DENYand a report-only Content-Security-Policy, with no inline scripts left in the pages.Strict-Transport-Securityis sent on https sites with the max-age from[server] hsts_max_age(OXYHUB_SERVER_HSTS_MAX_AGE); none at 0, the default. - security.txt and a security contact page (issue #224, issue #230): once an administrator sets a contact under Administration › Security contact,
/.well-known/security.txtand/security-contactshow it. - Fetches no longer fail on a bookmark moved by a key that signed nothing (issue #266, issue #271): a delegate or short-lived key that moved a bookmark without signing any of its changes left every client, runners included, failing the whole fetch with "unknown key". Fetches now carry the identities of the keys that signed every bookmark's and tag's latest attestation, so any client can verify the move. The fix first made every fetch of a repository with many bookmarks in a bucket slow (14 s for one with 145); the signers are now gathered once per change to the repository.
- A renewed key approves as the account its predecessor is linked to (issue #236): a sealed approval signed by a renewed or delegated key with the same key pair was refused because the new identity was not linked. The hub now follows the key's earlier identities to a linked one, as pushes already did.
- Closing keywords close every issue they list (issue #226):
Closes #1, #2 and #3,Fixes: #4 #5andresolves #6 & #7close every number, not only the first. - Long hashes in change titles show their two ends (issue #216), the whole in a tip, and change rows wrap rather than grow wider than their card.
- A minified file is shown plain at once: a file with a line over 4,000 bytes was highlighted for seconds and is now shown as plain text, as other forges do.
Releases 1
OxygenHub and oh 0.6.0 v0.6.0 · ✔ signed by Andy McCall · andymccall ·
Trust and auditAssets
- SHA256SUMS 934 B · blake3
d4ec22043bd9· 10 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh-0.6.0-linux-arm64.tar.gz 2.5 MiB · blake3
f176a58cde88· 5 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh-0.6.0-linux-riscv64.tar.gz 2.5 MiB · blake3
eae7c80785d8· 3 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh-0.6.0-linux-x86_64.tar.gz 2.7 MiB · blake3
b3e02a1acc55· 3 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh-0.6.0-macos-arm64.tar.gz 2.3 MiB · blake3
a60d8f0e7138· 3 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh-0.6.0-macos-x86_64.tar.gz 2.5 MiB · blake3
5d5ea2382b7f· 3 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh-0.6.0-windows-x86_64.zip 6.5 MiB · blake3
71ea62914d9d· 3 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh_0.6.0_amd64.deb 2.0 MiB · blake3
9bc8aba728b1· 3 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh_0.6.0_arm64.deb 1.8 MiB · blake3
4145524fa83f· 3 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oh_0.6.0_riscv64.deb 1.9 MiB · blake3
2c094722b04e· 2 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - oxygenhub-server-0.6.0-linux-x86_64.tar.gz 23.4 MiB · blake3
288049fbca98· 3 downloads · built by run #405 dist · signed receipt by earth-1 (short-lived key) · provenance - Source (v0.6.0.tar.zst)
- Source (v0.6.0.zip)