krz/orgo

Lightning fast org-mode static site generator. fast go org-mode static-site-generator

Commit 6a024f243e

6a024f243e9341c69cf714dd4e697bcabb092975

parent: 71e9f793b9

Unsigned

cmc <hello@cleberg.net> · 2026-08-11 23:43 UTC

Publish to crates.io from CI, with no stored token

Trusted publishing: the job mints a GitHub OIDC token, exchanges it with
crates.io for a token that lives for the length of the job, and crates.io revokes
it at the end. Nothing is stored in this repository, so there is nothing to leak
and nothing to rotate.

Two guards around it, because publishing is the one step here that cannot be
undone — a version can be yanked but never replaced:

- `needs: build`, so nothing publishes unless all four target binaries compiled.
- `environment: crates-io`. Adding a required reviewer to that environment in the
  repository settings turns a tag push into "waiting for a human". Until someone
  does, the environment exists but gates nothing.

`--locked` publishes the dependency versions the tests actually ran against.

Also runs `cargo package` in CI on Linux. Packaging breaks in ways an ordinary
build does not — an `exclude` that drops a file the tests need, a `readme` path
that moved — and finding that at a tag is finding it too late.

Layout: unified · split

.github/workflows/ci.yml +7
@@ -70,6 +70,13 @@ jobs:
7070 - name: Clippy
7171 run: cargo clippy --all-targets --locked -- -D warnings
7272
73 # `cargo package` builds the crate exactly as crates.io will receive it, which is
74 # how an `exclude` that drops a file the tests need, or a `readme` pointing at a
75 # file that was renamed, gets caught here rather than during a release.
76 - name: Package
77 if: runner.os == 'Linux'
78 run: cargo package --locked
79
7380 # The documentation site is built by the tool it documents, so a docs page that no
7481 # longer builds is a product defect. `--strict` fails on broken internal links,
7582 # which is the failure mode a docs site actually has.
.github/workflows/release.yml +40
@@ -111,3 +111,43 @@ jobs:
111111 files: |
112112 *.tar.gz
113113 *.tar.gz.sha256
114
115 publish:
116 name: publish to crates.io
117 needs: build
118 runs-on: ubuntu-latest
119 # Named so it can be gated. Adding a required reviewer to this environment in the
120 # repository settings turns a tag push into "waiting for a human", which is the right
121 # shape for the one step in this workflow that cannot be undone: a published version
122 # can be yanked but never replaced.
123 environment: crates-io
124 permissions:
125 # The OIDC token this mints *is* the credential — there is no API token stored in
126 # this repository, and nothing to leak from a compromised job beyond a token that
127 # crates.io revokes when the job ends.
128 id-token: write
129 contents: read
130 steps:
131 - name: Checkout
132 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
133 with:
134 ref: ${{ github.event.inputs.tag || github.ref }}
135
136 - name: Install Rust
137 uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
138 with:
139 toolchain: stable
140
141 # Exchanges GitHub's OIDC token for a short-lived crates.io token, and revokes it
142 # when the job finishes. Configured on crates.io against this repository, this
143 # workflow's filename, and the environment above.
144 - name: Authenticate with crates.io
145 id: auth
146 uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
147
148 # `--locked` publishes exactly the dependency versions the tests ran against, rather
149 # than whatever resolves at publish time.
150 - name: Publish
151 run: cargo publish --locked
152 env:
153 CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}