diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml index 5b3ec49..1b8e964 100644 --- a/.gitea/workflows/publish.yml +++ b/.gitea/workflows/publish.yml @@ -1,46 +1,94 @@ name: Publish release -# A `v*` tag builds the binary and attaches it to the Gitea release as -# `gdo`, with the tag's CHANGELOG.md section as the notes. Hosts -# install it with deploy/install.sh. +# A `v*` tag builds the package on each architecture's own runner +# (x86_64 on ergo's bare runner, aarch64 on klokka's) and uploads it +# to the [uhhm] Arch registry, like uhhm/corp and cnats. Hosts then +# `pacman -S gdo`. The tag has to agree with PKGBUILD's pkgver, and +# CHANGELOG.md has to have the section; a release with the tag's notes +# is created from the x86_64 job for the record, with no asset. on: push: tags: ["v*"] jobs: - publish: - runs-on: bare + package: + strategy: + matrix: + include: + - arch: x86_64 + runs-on: bare + # The bare runner's shared toolchain and caches, with the + # writable cargo home uhhm/iris uses. + cargo_home: /var/local/cargo-target/iris-cargo-home + rustc_wrapper: /usr/bin/sccache + rustup_home: /var/local/rustup + target_dir: /var/local/cargo-target + - arch: aarch64 + runs-on: aarch64 + # klokka's host runner: the system cargo, defaults everywhere. + cargo_home: "" + rustc_wrapper: "" + rustup_home: "" + target_dir: "" + runs-on: ${{ matrix.runs-on }} env: - CARGO_HOME: /var/local/cargo-target/iris-cargo-home - RUSTC_WRAPPER: /usr/bin/sccache - RUSTUP_HOME: /var/local/rustup + CARGO_HOME: ${{ matrix.cargo_home }} + RUSTC_WRAPPER: ${{ matrix.rustc_wrapper }} + RUSTUP_HOME: ${{ matrix.rustup_home }} + CARGO_TARGET_DIR: ${{ matrix.target_dir }} PATH: /var/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/bin SCCACHE_DIR: /var/local/sccache SCCACHE_SERVER_PORT: "4228" - CARGO_TARGET_DIR: /var/local/cargo-target steps: - - uses: actions/checkout@v4 + # Plain git: the aarch64 host runner has no Node.js for + # actions/checkout (same as uhhm/corp). + - name: Checkout + run: | + git init -q . + git fetch -q --depth 1 "https://ci:${{ gitea.token }}@${GITEA_SERVER#https://}/${{ gitea.repository }}.git" "${{ gitea.sha }}" + git checkout -q FETCH_HEAD + env: + GITEA_SERVER: ${{ gitea.server_url }} - - name: Build - run: cargo build --release && cargo test --release + - name: Check tag matches pkgver + run: | + want="${GITHUB_REF_NAME#v}" + have=$(sed -n 's/^pkgver=//p' PKGBUILD) + [ "$want" = "$have" ] || { echo "tag $GITHUB_REF_NAME != pkgver $have" >&2; exit 1; } + notes=$(awk -v v="$want" '/^## /{p = ($2 == v)} p' CHANGELOG.md | tail -n +2) + [ -n "$(printf "%s" "$notes" | tr -d "[:space:]")" ] || { echo "CHANGELOG.md has no section for $GITHUB_REF_NAME" >&2; exit 1; } - - name: Publish release + # -d: cargo is the shared toolchain, not a pacman package the + # runner could verify. Builds and tests from the checkout. Empty + # env values above are unset here so cargo's defaults apply. + - name: Build package + run: | + for v in CARGO_HOME RUSTC_WRAPPER RUSTUP_HOME CARGO_TARGET_DIR; do + eval "[ -n \"\$$v\" ]" || unset "$v" + done + makepkg -f -d --noconfirm + + # REGISTRY_TOKEN is a write:package token for bl, a secret on this + # repo (org repos do not inherit bl's). The registry namespace is + # always bl: pacman.conf's [uhhm] points at /api/packages/bl/arch/uhhm. + - name: Publish to the Arch package registry + run: | + for pkg in $(makepkg --packagelist); do + echo "==> $pkg" + curl --fail-with-body --user "bl:${{ secrets.REGISTRY_TOKEN }}" \ + --upload-file "$pkg" \ + "${{ gitea.server_url }}/api/packages/bl/arch/uhhm" + done + + - name: Release notes + if: matrix.arch == 'x86_64' run: | set -euo pipefail tag="${{ github.ref_name }}" api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" auth="Authorization: token ${{ secrets.GITHUB_TOKEN }}" notes=$(awk -v v="${tag#v}" '/^## /{p = ($2 == v)} p' CHANGELOG.md | tail -n +2) - [ -n "$(printf "%s" "$notes" | tr -d "[:space:]")" ] || { echo "CHANGELOG.md has no section for $tag"; exit 1; } 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) - 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=@/var/local/cargo-target/release/gdo" \ - "$api/releases/$id/assets?name=gdo" > /dev/null - echo "published $tag" + curl -sf -X POST -H "$auth" -H 'Content-Type: application/json' -d "$body" "$api/releases" > /dev/null \ + || echo "release $tag exists" diff --git a/.gitignore b/.gitignore index eb5a316..894f074 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ target +pkg/ +*.pkg.tar.* diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a640a7..530b35e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.1 (2026-09-23) + +- Packaged: `pacman -S gdo` from the [uhhm] registry on project.uhhm.no (x86_64 and aarch64, built by the release workflow from the tagged checkout); the unit and `/etc/gdo/env` ship in the package, and the installer script is gone. + ## 0.1.0 (2026-09-23) - First release: a durable consumer `gdo` on the `WORMHOLE` stream (`portal.mail.>`), each message handed to the host's SMTP as text with an HTML alternative, acked on acceptance and retried after a minute otherwise; `gdo probe
` sends one message through and reports when it was delivered; an optional Typst theme (`GDO_TYPST_THEME`) renders a PDF of every mail and attaches it. diff --git a/Cargo.lock b/Cargo.lock index b3b5e90..bb4827f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -411,7 +411,7 @@ dependencies = [ [[package]] name = "gdo" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-nats", diff --git a/Cargo.toml b/Cargo.toml index 81ef77e..8ae9b27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gdo" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = "Sends the code that opens the iris: delivers portal's outgoing mail from JetStream to the host's SMTP" license = "MIT" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..add8a78 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Bendik Aagaard Lynghaug + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..94efb51 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,44 @@ +# Maintainer: Bendik Aagaard Lynghaug +# Built from the tagged checkout by .gitea/workflows/release.yml, once +# per architecture, and uploaded to the [uhhm] registry on +# project.uhhm.no (namespace bl). pkgver is the version; the tag has +# to agree with it. +pkgname=gdo +pkgver=0.1.1 +pkgrel=1 +pkgdesc="Sends the code that opens the iris: delivers portal's outgoing mail from JetStream to the host's SMTP" +arch=('x86_64' 'aarch64') +url="https://project.uhhm.no/uhhm/gdo" +license=('MIT') +depends=('glibc' 'gcc-libs') +makedepends=('cargo') +optdepends=('postfix: the SMTP it hands mail to' + 'nats-server: the JetStream it reads from' + 'typst: PDF themes (GDO_TYPST_THEME)') +backup=('etc/gdo/env') +# !lto: makepkg's -flto would turn ring's C objects into GCC bitcode the +# Rust linker cannot see (undefined ring_core_* symbols). +options=('!debug' '!lto') + +# Sources are the checkout itself: makepkg resolves local sources by +# basename next to the PKGBUILD, so the tagged tree CI builds from is +# the source of truth, as in uhhm/corp. + +build() { + cd "$startdir" + cargo build --release --locked +} + +check() { + cd "$startdir" + cargo test --release --locked +} + +package() { + cd "$startdir" + install -Dm755 "${CARGO_TARGET_DIR:-target}/release/gdo" "$pkgdir/usr/bin/gdo" + install -Dm644 deploy/gdo.service "$pkgdir/usr/lib/systemd/system/gdo.service" + install -Dm600 deploy/env "$pkgdir/etc/gdo/env" + install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} diff --git a/README.md b/README.md index 0f28480..9e2771a 100644 --- a/README.md +++ b/README.md @@ -31,16 +31,18 @@ gdo --version | `GDO_CONSUMER` | `gdo` | the durable consumer's name | | `GDO_TYPST_THEME` | unset | a `.typ` file; set, every mail carries a PDF rendered from it | -Install on a host from a release, as root: +Install on a host from the [uhhm] Arch registry on project.uhhm.no +(the repo and its key: see uhhm/corp's README), as root: ``` -curl -sfLO https://project.uhhm.no/uhhm/gdo/raw/branch/main/deploy/install.sh -sudo sh install.sh v0.1.0 'nats://infra:@127.0.0.1:4222' +pacman -Sy gdo + +systemctl enable --now gdo ``` -That puts the binary in `/usr/local/bin`, the env in `/etc/gdo.env` -(mode 0600, the one file with a secret), and the unit -`deploy/gdo.service` enabled and running. Then `gdo probe `. +The package puts the binary in `/usr/bin`, the env template in +`/etc/gdo/env` (mode 0600, the one file with a secret), and the unit +`gdo.service`. Then `gdo probe `, with the env loaded. ## The message diff --git a/deploy/env b/deploy/env new file mode 100644 index 0000000..f0e604a --- /dev/null +++ b/deploy/env @@ -0,0 +1,10 @@ +# gdo - one per host. Read by gdo.service; mode 0600, the one file +# with a secret in it. See /usr/share/doc/gdo/README.md. +NATS_URL=nats://127.0.0.1:4222 +SMTP_HOST=127.0.0.1 +SMTP_PORT=25 +# What gdo says in EHLO; a strict server refuses a bare hostname. +# Unset, the host's full name is used. +#SMTP_HELO=host.example.no +# A .typ file: set, every mail carries a PDF rendered from it. +#GDO_TYPST_THEME=/etc/gdo/theme.typ diff --git a/deploy/gdo.service b/deploy/gdo.service index ed23499..bfbb7c5 100644 --- a/deploy/gdo.service +++ b/deploy/gdo.service @@ -1,6 +1,6 @@ # One per host, not per site: every portal instance on the host # publishes to the same WORMHOLE stream, and each message carries its -# own sender. Install with deploy/install.sh. +# own sender. Installed by the gdo package (pacman -S gdo). [Unit] Description=gdo - delivers portal's outgoing mail to the host's SMTP After=network-online.target nats-server.service postfix.service @@ -8,7 +8,7 @@ Wants=network-online.target [Service] ExecStart=/usr/local/bin/gdo -EnvironmentFile=-/etc/gdo.env +EnvironmentFile=-/etc/gdo/env DynamicUser=yes Restart=always RestartSec=5 diff --git a/deploy/install.sh b/deploy/install.sh deleted file mode 100755 index d674395..0000000 --- a/deploy/install.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -# Install or upgrade gdo on this host from a Gitea release. Run as root: -# sudo sh install.sh v0.1.0 [nats://user:pass@127.0.0.1:4222] -# The NATS URL is only needed the first time; it lands in /etc/gdo.env, -# which is the one file with a secret in it (mode 0600). -set -eu -tag=${1:?usage: install.sh vX.Y.Z [NATS_URL]} -base=https://project.uhhm.no/uhhm/gdo -tmp=$(mktemp) -curl -sfL "$base/releases/download/$tag/gdo" -o "$tmp" -install -m 0755 "$tmp" /usr/local/bin/gdo -rm -f "$tmp" -if [ ! -f /etc/gdo.env ]; then - nats=${2:-nats://127.0.0.1:4222} - umask 077 - printf 'NATS_URL=%s\nSMTP_HOST=127.0.0.1\nSMTP_PORT=25\nSMTP_HELO=%s\n# GDO_TYPST_THEME=/etc/gdo/theme.typ\n' "$nats" "$(hostname -f 2>/dev/null || cat /etc/hostname)" > /etc/gdo.env -fi -curl -sfL "$base/raw/tag/$tag/deploy/gdo.service" -o /etc/systemd/system/gdo.service -systemctl daemon-reload -systemctl enable --now gdo -systemctl restart gdo -sleep 2 -systemctl is-active gdo && /usr/local/bin/gdo --version