From 75a9a45c6ff54eb82e13677734a2bee9c15e2956 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 19 Oct 2026 09:23:45 +0200 Subject: [PATCH] mesh-mail: pin mesh-sdk go to release 2, declare the sender account, bus process and work-queue stream (ADR 0436 clauses 7, 8; issue 1172) --- .../mesh-mail/cmd/mesh-mail/manifest_test.go | 142 +++++++++++++++++- .../mesh-mail/cmd/mesh-mail/sdk_pin_test.go | 31 ++++ modules/mesh-mail/go.mod | 14 +- modules/mesh-mail/go.sum | 36 ++++- modules/mesh-mail/module.json | 64 +++++++- 5 files changed, 267 insertions(+), 20 deletions(-) create mode 100644 modules/mesh-mail/cmd/mesh-mail/sdk_pin_test.go diff --git a/modules/mesh-mail/cmd/mesh-mail/manifest_test.go b/modules/mesh-mail/cmd/mesh-mail/manifest_test.go index 87b01d30..8e8dd9d3 100644 --- a/modules/mesh-mail/cmd/mesh-mail/manifest_test.go +++ b/modules/mesh-mail/cmd/mesh-mail/manifest_test.go @@ -30,11 +30,16 @@ type manifest struct { OwnSecrets map[string]map[string]string `json:"own-secrets"` Tools []string `json:"tools"` Emits []string `json:"emits"` + State []string `json:"state"` Streams []struct { - Name string `json:"name"` - Events []string `json:"events"` - MaxAgeDays int `json:"max-age-days"` - Grant struct { + Name string `json:"name"` + Events []string `json:"events"` + MaxAgeDays int `json:"max-age-days"` + MaxMiB int `json:"max-mib"` + Retention string `json:"retention"` + MaxDeliveries int `json:"max-deliveries"` + Duplicates int `json:"duplicates"` + Grant struct { Config string `json:"config"` Events string `json:"events"` } `json:"grant"` @@ -95,13 +100,35 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) { t.Fatalf("tools %v, the code serves %v", m.Tools, names) } // Its events go to its own stream MAIL, kept a week, each account's part heard only by the consumers the trusted - // config value grants names for it (ADR 0359 §2, §4; ADR 0363): none on the shared EVENTS stream. + // config value grants names for it (ADR 0359 §2, §4; ADR 0363): none on the shared EVENTS stream. The module's + // top-level emits is the sender's two outcomes (novox/hq ADR 0436 §7, §11, issue 1172): the module as a whole may + // publish them, though only the process send actually does (its own bus.emits, checked in + // TestItsManifestDeclaresTheSenderProcess). var events []string for _, c := range Changes { events = append(events, EventKey("*", c)) } - if len(m.Emits) != 0 || len(m.Streams) != 1 || m.Streams[0].Name != "mail" || !reflect.DeepEqual(m.Streams[0].Events, events) || - m.Streams[0].MaxAgeDays != 7 || m.Streams[0].Grant.Config != "grants" || m.Streams[0].Grant.Events != "mail.{key}.**" { + var mailStream *struct { + Name string `json:"name"` + Events []string `json:"events"` + MaxAgeDays int `json:"max-age-days"` + MaxMiB int `json:"max-mib"` + Retention string `json:"retention"` + MaxDeliveries int `json:"max-deliveries"` + Duplicates int `json:"duplicates"` + Grant struct { + Config string `json:"config"` + Events string `json:"events"` + } `json:"grant"` + } + for i := range m.Streams { + if m.Streams[i].Name == "mail" { + mailStream = &m.Streams[i] + } + } + if !reflect.DeepEqual(m.Emits, []string{"mail.send.sent", "mail.send.failed"}) || len(m.Streams) != 2 || mailStream == nil || + !reflect.DeepEqual(mailStream.Events, events) || mailStream.MaxAgeDays != 7 || mailStream.Grant.Config != "grants" || + mailStream.Grant.Events != "mail.{key}.**" { t.Fatalf("emits %v, streams %+v; the code says %v", m.Emits, m.Streams, events) } // It calls nothing and consumes nothing: mesh-mail-notifier (issue 800 part B) is the one that invokes and @@ -187,3 +214,104 @@ func TestItsProcessesAreThePreparationTheActAndTheJudge(t *testing.T) { t.Fatalf("keep restarts on %v", keep["restart-on"]) } } + +// 1172/AC2: the manifest declares the sender's account, own-secrets owner, work-queue stream and process bus, +// and widens the state directory and accounts.json so the sender (not the operator account) can read them, +// mirroring mesh-controller's own fixtures (internal/catalogue/process_bus_test.go:14-31, +// own_secret_owner_test.go:16-29, stream_work_queue_test.go:13-14). +func TestItsManifestDeclaresTheSenderProcess(t *testing.T) { + m, byID := readManifest(t) + + account := byID["account"] + if account == nil || account["type"] != "user" || account["name"] != "mesh-mail-send" { + t.Fatalf("the sender's account resource: %v", account) + } + + fam := m.OwnSecrets[SecretFamily+"*"] + if fam["owner"] != "mesh-mail-send" { + t.Fatalf("mail-credential-*'s owner: %v; want mesh-mail-send", fam["owner"]) + } + + var sendsStream *struct { + Name string `json:"name"` + Events []string `json:"events"` + MaxAgeDays int `json:"max-age-days"` + MaxMiB int `json:"max-mib"` + Retention string `json:"retention"` + MaxDeliveries int `json:"max-deliveries"` + Duplicates int `json:"duplicates"` + Grant struct { + Config string `json:"config"` + Events string `json:"events"` + } `json:"grant"` + } + for i := range m.Streams { + if m.Streams[i].Name == "mail-sends" { + sendsStream = &m.Streams[i] + } + } + if sendsStream == nil || !reflect.DeepEqual(sendsStream.Events, []string{"mail.send.approved"}) || + sendsStream.MaxAgeDays != 1 || sendsStream.MaxMiB != 64 || sendsStream.Retention != "work-queue" || + sendsStream.MaxDeliveries != 5 || sendsStream.Duplicates != 24 { + t.Fatalf("the stream mail-sends: %+v", sendsStream) + } + + if !reflect.DeepEqual(m.Emits, []string{"mail.send.sent", "mail.send.failed"}) { + t.Fatalf("top-level emits: %v", m.Emits) + } + + send := byID["send"] + if send == nil || send["type"] != "process" || send["user"] != "mesh-mail-send" { + t.Fatalf("the process send: %v", send) + } + run, _ := send["run"].([]any) + if len(run) != 2 || run[0] != "./mesh-mail" || run[1] != "send" { + t.Fatalf("send's run: %v", send["run"]) + } + env, _ := send["env"].(map[string]any) + keepEnv, _ := byID["keep"]["env"].(map[string]any) + for _, k := range []string{"MESH_MAIL_ACCOUNTS", "MESH_MAIL_SECRETS"} { + if env[k] != keepEnv[k] { + t.Fatalf("send's %s is %v, keep's is %v", k, env[k], keepEnv[k]) + } + } + h, _ := send["health"].(map[string]any) + if h["kind"] != "unit" { + t.Fatalf("send's health: %v", send["health"]) + } + bus, _ := send["bus"].(map[string]any) + if bus == nil { + t.Fatalf("send declares no bus") + } + if !reflect.DeepEqual(toStrings(bus["emits"]), []string{"mail.send.sent", "mail.send.failed"}) || + !reflect.DeepEqual(toStrings(bus["queues"]), []string{"mail-sends"}) || + !reflect.DeepEqual(toStrings(bus["state"]), []string{"sends"}) || bus["asks"] != true || + !reflect.DeepEqual(toStrings(bus["invokes"]), []string{"seat:operator-channel.tell"}) { + t.Fatalf("send's bus: %+v", bus) + } + + // On novox the operator account's state directory is 0700, so mesh-mail-send cannot reach its own 0600 + // files inside it: the state directory becomes 0711 (passable, not listable) and accounts.json (server + // names and addresses, no password) becomes 0644, so the sender can read the outgoing server. No password + // file's mode widens (the lead's decision, issue 1172). + if byID["state"]["mode"] != "0711" { + t.Fatalf("the state directory's mode: %v; want 0711", byID["state"]["mode"]) + } + if byID["accounts"]["mode"] != "0644" { + t.Fatalf("accounts.json's mode: %v; want 0644", byID["accounts"]["mode"]) + } + if byID["grants"]["mode"] != "0600" { + t.Fatalf("grants.json's mode widened to %v; it holds no password, but was not asked to widen", byID["grants"]["mode"]) + } +} + +func toStrings(v any) []string { + list, _ := v.([]any) + out := make([]string, 0, len(list)) + for _, item := range list { + if s, ok := item.(string); ok { + out = append(out, s) + } + } + return out +} diff --git a/modules/mesh-mail/cmd/mesh-mail/sdk_pin_test.go b/modules/mesh-mail/cmd/mesh-mail/sdk_pin_test.go new file mode 100644 index 00000000..44f0a371 --- /dev/null +++ b/modules/mesh-mail/cmd/mesh-mail/sdk_pin_test.go @@ -0,0 +1,31 @@ +package main + +// The SDK pin a process's own bus connection needs (novox/hq ADR 0436 §4, issue 1172): mesh-mail's go.mod +// requires mesh-sdk go at the release-2 commit (main 34392e7) and go 1.26.0, and go/process is importable. +// The blank import below is what proves importability at compile time; go vet (run before every commit, per +// the context file) already fails the build if it is not. + +import ( + "os" + "strings" + "testing" + + _ "git.novox.be/novox/mesh-sdk/go/process" +) + +// 1172/AC1: given mesh-mail's go.mod, when built, then it requires mesh-sdk go at +// v0.1.14-0.20261019062050-34392e74916c and go 1.26.0, and go/process is importable (the import above). +func TestGoModPinsTheSDKsProcessRelease(t *testing.T) { + raw, err := os.ReadFile("../../go.mod") + if err != nil { + t.Fatal(err) + } + mod := string(raw) + const wantSDK = "git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019062050-34392e74916c" + if !strings.Contains(mod, wantSDK) { + t.Fatalf("go.mod does not require %q:\n%s", wantSDK, mod) + } + if !strings.Contains(mod, "go 1.26.0") { + t.Fatalf("go.mod does not declare go 1.26.0:\n%s", mod) + } +} diff --git a/modules/mesh-mail/go.mod b/modules/mesh-mail/go.mod index 16e2aaf6..9f3bf724 100644 --- a/modules/mesh-mail/go.mod +++ b/modules/mesh-mail/go.mod @@ -1,9 +1,9 @@ module mesh-mail -go 1.25.0 +go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019062050-34392e74916c github.com/emersion/go-imap/v2 v2.0.0-beta.8 github.com/emersion/go-message v0.18.2 github.com/jackc/pgx/v5 v5.11.0 @@ -14,6 +14,12 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.29.0 // indirect + github.com/klauspost/compress v1.20.0 // indirect + github.com/nats-io/nats.go v1.54.0 // indirect + github.com/nats-io/nkeys v0.4.16 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/sync v0.23.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect ) diff --git a/modules/mesh-mail/go.sum b/modules/mesh-mail/go.sum index da60653c..bea2f6aa 100644 --- a/modules/mesh-mail/go.sum +++ b/modules/mesh-mail/go.sum @@ -1,5 +1,7 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44 h1:r5xIo5qWOXQdCSMxvqJy8kV4U+P/QWXFK6971Ha6wU8= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013182924-5933eb757d44/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019062050-34392e74916c h1:/f6f2ugss172hDJSL3EFZqb9PdfO5DKzYVfPiX2LdAM= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261019062050-34392e74916c/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= +github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,6 +11,8 @@ github.com/emersion/go-message v0.18.2 h1:rl55SQdjd9oJcIoQNhubD2Acs1E6IzlZISRTK7 github.com/emersion/go-message v0.18.2/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA= github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk= github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= +github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -17,6 +21,20 @@ github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= +github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/minio/highwayhash v1.0.4 h1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4= +github.com/minio/highwayhash v1.0.4/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU= +github.com/nats-io/jwt/v2 v2.8.1/go.mod h1:nWnOEEiVMiKHQpnAy4eXlizVEtSfzacZ1Q43LIRavZg= +github.com/nats-io/nats-server/v2 v2.11.17 h1:GKEghcFK6A+aFx11Yf1LjgLC3txAwvyhnYzhBIQZA8I= +github.com/nats-io/nats-server/v2 v2.11.17/go.mod h1:B1sFVz4StNosQ903ak4N1G01Fl/9f8e06mXpFIE2K24= +github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= +github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k= +github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -27,6 +45,8 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -36,14 +56,16 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -52,8 +74,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/modules/mesh-mail/module.json b/modules/mesh-mail/module.json index 38a5ccea..79679468 100644 --- a/modules/mesh-mail/module.json +++ b/modules/mesh-mail/module.json @@ -48,7 +48,8 @@ "mail-credential-*": { "path": "${dir:state}/mail-credential-*.secret", "issued-by": "outside", - "prompt": "the password of the mail account {member}" + "prompt": "the password of the mail account {member}", + "owner": "mesh-mail-send" } }, "config": { @@ -83,6 +84,10 @@ "state": [ "sends" ], + "emits": [ + "mail.send.sent", + "mail.send.failed" + ], "streams": [ { "name": "mail", @@ -96,13 +101,29 @@ "config": "grants", "events": "mail.{key}.**" } + }, + { + "name": "mail-sends", + "events": [ + "mail.send.approved" + ], + "max-age-days": 1, + "max-mib": 64, + "retention": "work-queue", + "max-deliveries": 5, + "duplicates": 24 } ], "resources": [ + { + "id": "account", + "type": "user", + "name": "mesh-mail-send" + }, { "id": "state", "type": "directory", - "mode": "0700", + "mode": "0711", "place": ".", "unknown": "program" }, @@ -125,7 +146,7 @@ "trusted": true, "type": "file", "path": "${dir:state}/accounts.json", - "mode": "0600", + "mode": "0644", "merge": "json", "template": "files/accounts.json", "takes-config": [ @@ -214,6 +235,43 @@ "looks": 2, "grace": "180s" } + }, + { + "id": "send", + "type": "process", + "name": "mesh-mail-send", + "artifact": "tools", + "run": [ + "./mesh-mail", + "send" + ], + "user": "mesh-mail-send", + "env": { + "MESH_MAIL_ACCOUNTS": "${dir:state}/accounts.json", + "MESH_MAIL_SECRETS": "${dir:state}" + }, + "restart-on": [ + "accounts" + ], + "bus": { + "emits": [ + "mail.send.sent", + "mail.send.failed" + ], + "queues": [ + "mail-sends" + ], + "state": [ + "sends" + ], + "asks": true, + "invokes": [ + "seat:operator-channel.tell" + ] + }, + "health": { + "kind": "unit" + } } ], "build": {