Packaged for the [uhhm] Arch registry, like every other uhhm binary
Test / test (push) Successful in 27s

PKGBUILD from the tagged checkout (tag must equal pkgver), built per
architecture by the release workflow: x86_64 on ergo's bare runner,
aarch64 on klokka's, uploaded to project.uhhm.no/api/packages/bl/arch/
uhhm with the REGISTRY_TOKEN repo secret. The package ships /usr/bin/
gdo, gdo.service and an /etc/gdo/env template; the installer script is
gone. !lto: makepkg's -flto hid ring's C symbols from the Rust linker.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-23 20:07:54 +02:00
co-authored by Claude Fable 5.1
parent 983491295e
commit d52e3d3378
12 changed files with 165 additions and 57 deletions
+72 -24
View File
@@ -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"
+2
View File
@@ -1 +1,3 @@
target
pkg/
*.pkg.tar.zst
+4
View File
@@ -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 <address>` 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.
Generated
+1 -1
View File
@@ -411,7 +411,7 @@ dependencies = [
[[package]]
name = "gdo"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"anyhow",
"async-nats",
+1 -1
View File
@@ -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"
+21
View File
@@ -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.
+44
View File
@@ -0,0 +1,44 @@
# Maintainer: Bendik Aagaard Lynghaug <bendik.lynghaug@gmail.com>
# 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"
}
+8 -6
View File
@@ -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:<token>@127.0.0.1:4222'
pacman -Sy gdo
<edit /etc/gdo/env: NATS_URL with the host's credentials>
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 <you>`.
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 <you>`, with the env loaded.
## The message
+10
View File
@@ -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
+2 -2
View File
@@ -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
-23
View File
@@ -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
Binary file not shown.