The stream is mail, not shouted
Publish release / package (aarch64, , aarch64, , , ) (push) Failing after 2s
Publish release / package (x86_64, /var/local/cargo-target/iris-cargo-home, bare, /usr/bin/sccache, /var/local/rustup, /var/local/cargo-target) (push) Failing after 21s
Test / test (push) Successful in 25s

Matches portal 0.3.44. The unit runs /usr/bin/gdo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-23 21:15:40 +02:00
co-authored by Claude Fable 5.1
parent 29481e69df
commit 1c1a8f4309
7 changed files with 14 additions and 10 deletions
+5 -1
View File
@@ -1,9 +1,13 @@
# Changelog # Changelog
## 0.1.2 (2026-09-23)
- The stream is `mail`, not shouted; matches portal 0.3.44. The unit runs the packaged binary at /usr/bin/gdo.
## 0.1.1 (2026-09-23) ## 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. - 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) ## 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. - First release: a durable consumer `gdo` on the `mail` 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]] [[package]]
name = "gdo" name = "gdo"
version = "0.1.1" version = "0.1.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-nats", "async-nats",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gdo" name = "gdo"
version = "0.1.1" version = "0.1.2"
edition = "2021" edition = "2021"
description = "Sends the code that opens the iris: delivers portal's outgoing mail from JetStream to the host's SMTP" description = "Sends the code that opens the iris: delivers portal's outgoing mail from JetStream to the host's SMTP"
license = "MIT" license = "MIT"
+2 -2
View File
@@ -4,8 +4,8 @@
# project.uhhm.no (namespace bl). pkgver is the version; the tag has # project.uhhm.no (namespace bl). pkgver is the version; the tag has
# to agree with it. # to agree with it.
pkgname=gdo pkgname=gdo
pkgver=0.1.1 pkgver=0.1.2
pkgrel=2 pkgrel=1
pkgdesc="Sends the code that opens the iris: delivers portal's outgoing mail from JetStream to the host's SMTP" pkgdesc="Sends the code that opens the iris: delivers portal's outgoing mail from JetStream to the host's SMTP"
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
url="https://project.uhhm.no/uhhm/gdo" url="https://project.uhhm.no/uhhm/gdo"
+1 -1
View File
@@ -9,7 +9,7 @@ site's content says to send.
Portal decides *when* a person hears something and renders the mail: Portal decides *when* a person hears something and renders the mail:
`mail:` on a state in `aggregates.yaml` sends when a record enters `mail:` on a state in `aggregates.yaml` sends when a record enters
that state, and an invite mails its one-time link built in. It that state, and an invite mails its one-time link built in. It
publishes each rendered mail on `portal.mail.send` in the `WORMHOLE` publishes each rendered mail on `portal.mail.send` in the `mail`
JetStream stream. gdo holds a durable consumer there and hands each JetStream stream. gdo holds a durable consumer there and hands each
message to the host's SMTP. Portal never talks SMTP; gdo never reads message to the host's SMTP. Portal never talks SMTP; gdo never reads
content; a message survives either of them being down, because the content; a message survives either of them being down, because the
+1 -1
View File
@@ -1,5 +1,5 @@
# One per host, not per site: every portal instance on the host # One per host, not per site: every portal instance on the host
# publishes to the same WORMHOLE stream, and each message carries its # publishes to the same mail stream, and each message carries its
# own sender. Installed by the gdo package (pacman -S gdo). # own sender. Installed by the gdo package (pacman -S gdo).
[Unit] [Unit]
Description=gdo - delivers portal's outgoing mail to the host's SMTP Description=gdo - delivers portal's outgoing mail to the host's SMTP
+3 -3
View File
@@ -1,7 +1,7 @@
//! gdo - the transmitter that sends the code through the gate so the //! gdo - the transmitter that sends the code through the gate so the
//! iris opens. Portal (uhhm/portal) decides when a person hears //! iris opens. Portal (uhhm/portal) decides when a person hears
//! something and renders the mail; gdo holds a durable consumer on //! something and renders the mail; gdo holds a durable consumer on
//! the `WORMHOLE` stream and hands each message to the host's SMTP. //! the `mail` stream and hands each message to the host's SMTP.
//! No site knowledge, no state of its own: if it dies, the stream //! No site knowledge, no state of its own: if it dies, the stream
//! keeps the mail until it is back. //! keeps the mail until it is back.
//! //!
@@ -22,7 +22,7 @@ use std::time::Duration;
/// Portal's side of this contract is `portal::mail`. Same names, same /// Portal's side of this contract is `portal::mail`. Same names, same
/// stream config, so whichever boots first creates it. /// stream config, so whichever boots first creates it.
const STREAM: &str = "WORMHOLE"; const STREAM: &str = "mail";
const SUBJECTS: &str = "portal.mail.>"; const SUBJECTS: &str = "portal.mail.>";
const SUBJECT: &str = "portal.mail.send"; const SUBJECT: &str = "portal.mail.send";
const CONTRACT: u8 = 1; const CONTRACT: u8 = 1;
@@ -255,7 +255,7 @@ async fn probe(cfg: Config, to: &str, from: &str) -> anyhow::Result<()> {
let mut headers = async_nats::HeaderMap::new(); let mut headers = async_nats::HeaderMap::new();
headers.insert("Nats-Msg-Id", id.as_str()); headers.insert("Nats-Msg-Id", id.as_str());
js.publish_with_headers(SUBJECT, headers, serde_json::to_vec(&mail)?.into()).await?.await?; js.publish_with_headers(SUBJECT, headers, serde_json::to_vec(&mail)?.into()).await?.await?;
println!("sent {id} into {STREAM}"); println!("sent {id} into stream {STREAM}");
let deadline = tokio::time::Instant::now() + Duration::from_secs(60); let deadline = tokio::time::Instant::now() + Duration::from_secs(60);
loop { loop {
let Ok(mut consumer) = stream.get_consumer::<async_nats::jetstream::consumer::pull::Config>(&cfg.consumer).await else { let Ok(mut consumer) = stream.get_consumer::<async_nats::jetstream::consumer::pull::Config>(&cfg.consumer).await else {