name: Publish release # Portal does not deploy itself. Cutting a version is deliberate: # `cargo release ` bumps Cargo.toml, commits, tags v, # and pushes; this workflow reacts to the tag and publishes the # artifact as a Gitea release. Each content repo (uhhm/questions, # redoal/questions) pins PORTAL_RELEASE to one of these tags in its # own deploy workflow - bumping the pin there is what rolls a version # out to a site. Plain main pushes only run test.yml. on: push: tags: ["v*"] jobs: publish: runs-on: bare env: # The bare runner's own systemd service intentionally has a minimal # PATH/HOME (no rustup default toolchain in reach) - point it at the # shared toolchain install directly rather than assuming an ambient # dev shell environment. CARGO_HOME: /var/local/cargo RUSTUP_HOME: /var/local/rustup PATH: /var/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/bin # Shared with interactive dev builds (see ~/.config/fish/config.fish) # so a crate compiled once, by either a manual build or CI, is # cached for the other - real cache hits, not just a warm toolchain. # SCCACHE_SERVER_PORT deliberately differs from the interactive # dev shell's (4227): sccache's server is discovered by a fixed # TCP port shared by every local user, so if both contexts used # the same port, whichever one's server happened to be running # would silently "win" and serve build requests it doesn't have # filesystem permission for. Separate ports keep each context's # own server answering its own requests; the cache directory # (not the server process) is what's actually shared. SCCACHE_DIR: /var/local/sccache SCCACHE_SERVER_PORT: "4228" CARGO_TARGET_DIR: /var/local/cargo-target # cargo-leptos's own downloaded tools (wasm-bindgen, wasm-opt) cache # under $XDG_CACHE_HOME - left at its default that resolves inside # this unit's systemd StateDirectory, files it creates there end up # owned by the kernel's overflow "nobody" uid instead of the # runner's own dynamic uid (a StateDirectory quirk, not something # in our control), so a later run can't execute what an earlier run # downloaded. Redirecting it to our own known-good shared dir avoids # that entirely. XDG_CACHE_HOME: /var/local/leptos-cache steps: - uses: actions/checkout@v4 # SITE_NAME is read via option_env! (src/app.rs) - compile-time, # not a runtime env var. It is only the last-resort fallback name: # each instance's site.yaml (content-driven branding) overrides it, # so one artifact serves every site. - name: Build run: SITE_NAME=${{ vars.PORTAL_SITE_NAME }} cargo leptos build --release # The lint (uhhm/iris) is versioned as portal: this step re-pins # iris to the tag being released, builds and tests it against it, # and pushes the matching iris tag, which iris's own CI publishes. # A portal change to something the lint reads fails the release # right here. Needs IRIS_PUSH_USER (a variable) and IRIS_PUSH_TOKEN # (a secret) that may push to uhhm/iris; the workflow's own token # is scoped to this repo. A re-run of a tag whose iris tag already # exists just builds that. - name: Release iris to match env: # The shared cargo home is read-only for the runner, and a git # dependency has to be cloned into one (same as uhhm/iris's CI). CARGO_HOME: /var/local/cargo-target/iris-cargo-home RUSTC_WRAPPER: /usr/bin/sccache IRIS_PUSH_USER: ${{ vars.IRIS_PUSH_USER }} IRIS_PUSH_TOKEN: ${{ secrets.IRIS_PUSH_TOKEN }} run: | set -euo pipefail tag="${{ github.ref_name }}" ver="${tag#v}" host="${{ github.server_url }}" rm -rf iris-src git clone -q "$host/uhhm/iris.git" iris-src cd iris-src if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then echo "iris $tag already exists, building it" git checkout -q "$tag" else sed -i -E "s|^(portal = \{ git = \"[^\"]+\", tag = \")[^\"]+(\")|\1$tag\2|" Cargo.toml sed -i -E "0,/^version = \"[^\"]+\"/s//version = \"$ver\"/" Cargo.toml cargo update -q -p portal awk -v h="## $ver ($(date -u +%F)) - portal $tag" -v l="- Matches portal $tag (released by portal's publish job)." \ 'done==0 && /^## /{print h; print ""; print l; print ""; done=1} {print}' CHANGELOG.md > CHANGELOG.new mv CHANGELOG.new CHANGELOG.md fi cargo build --release cargo test if ! git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then git config user.name "portal release" git config user.email "portal@uhhm.no" git commit -qam "Match portal $tag" git tag "$tag" git push -q "https://${IRIS_PUSH_USER}:${IRIS_PUSH_TOKEN}@${host#https://}/uhhm/iris.git" main "$tag" echo "pushed iris $tag" fi - name: Package run: | set -euo pipefail tag="${{ github.ref_name }}" stage=$(mktemp -d) cp "$CARGO_TARGET_DIR/release/portal" "$stage/portal" cp "$CARGO_TARGET_DIR/release/iris" "$stage/iris" # hash-files = true: leptos resolves hash.txt next to the running # binary, so it ships beside portal in the release dir. cp "$CARGO_TARGET_DIR/release/hash.txt" "$stage/hash.txt" # site-root ("target/site" in Cargo.toml) is project-relative, # not affected by CARGO_TARGET_DIR - only the plain `cargo build` # outputs (release/, front/) move with that override. cp -r target/site "$stage/site" tar -C "$stage" -czf "portal-$tag.tar.gz" portal iris hash.txt site rm -rf "$stage" # The run's own ephemeral token has write access to this repo - # no long-lived PAT to manage. The tag already exists (cargo # release pushed it), so the release attaches to it; a re-run # finds the existing release instead of failing. - name: Publish release run: | set -euo pipefail tag="${{ github.ref_name }}" api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" auth="Authorization: token ${{ secrets.GITHUB_TOKEN }}" # The release notes are this version's section of CHANGELOG.md. notes=$(awk -v v="${tag#v}" '/^## /{p = ($2 == v)} p' CHANGELOG.md | tail -n +2) body=$(jq -n --arg tag "$tag" --arg notes "$notes" '{tag_name: $tag, name: $tag, body: $notes}') id=$(curl -sf -X POST -H "$auth" -H 'Content-Type: application/json' \ -d "$body" "$api/releases" | jq .id) \ || id=$(curl -sf -H "$auth" "$api/releases/tags/$tag" | jq .id) # Replace the asset if a re-run already uploaded one. for aid in $(curl -sf -H "$auth" "$api/releases/$id/assets" | jq '.[].id'); do curl -sf -X DELETE -H "$auth" "$api/releases/$id/assets/$aid" done curl -sf -X POST -H "$auth" \ -F "attachment=@portal-$tag.tar.gz" \ "$api/releases/$id/assets?name=portal-$tag.tar.gz" > /dev/null echo "published $tag"