service_mail.nu — hero_mail lifecycle module #158
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_skills#158
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Add
service_mail.nuper the tracker in #75. Module exposesinstall | start [--reset] | stop | statusfor thehero_mailstack.Scope
tools/modules/services/service_mail.nunu_serviceandnu_service_useskills.service_browser.nu(user-level Rust multi-binary; no manager).hero_mail.Service-specific notes
~/hero/bin/(nobuildenv.sh::BINARIESdeclared yet — list inferred fromCargo.tomlworkspace members):hero_mail_server(JSON-RPC 2.0 server, talks to Stalwart)hero_mail_clihero_mail_ui(admin dashboard)hero_mail_server+hero_mail_uidirectly with hero_proc as separate actions.--rootforservice_mail.~/.hero/var/sockets/hero_mail_*.sock(note the dotted leading directory). Canonical Hero is~/hero/var/sockets/hero_mail/; the nu module should use the canonical path and the implementer should reconcile any discrepancy with the binaries' actual behavior.service_mail installvalidates a working Stalwart endpoint and errors out clearly if absent. A separateinstall_stalwarthelper ininstallers.nu(or system-package install) handles the install — same pattern asinstall_bunforservice_books. This keepsservice_mail.nuscoped to hero_mail itself.service_mail installalso installs Stalwart (via apt / upstream binary). Simpler for the user; harder to reuse the install logic if other services adopt Stalwart later. Approach 1 is the recommended default unless there's a strong reason to bundle.Repo blockers (resolve as part of this issue or in a sibling PR)
The hero_mail repo is incomplete relative to the canonical Hero layout —
service_mail.nuwill need at least the first two of these to do a clean build:buildenv.sh(orscripts/buildenv.sh) — needed to declarePROJECT_NAME/BINARIES/VERSIONfor the standardbuild_lib.shflow.scripts/build_lib.shsymlink/shim..forgejo/workflows/build-linux.yaml(release CI). Sibling concern; not strictly required forservice_mail.nu.MIT(the rest of the workspace shipsApache-2.0). Worth a maintainer call.The implementer should either add the missing files to hero_mail in the same PR or open a companion issue against
hero_mailand gateservice_mail.nuon it.Acceptance criteria
use services/mod.nu *makesservice_mailavailable.service_mail installclones the repo, runscargo build --release, and copieshero_mail_server,hero_mail_cli,hero_mail_uito~/hero/bin/. Stalwart prerequisite is validated (or installed, depending on the chosen approach above).service_mail start [--reset]registers the server + UI as separate hero_proc actions and becomes healthy.service_mail statusreports state for both.service_mail stopcleanly unregisters both.startoutput prints sockets / UI URL / a short test plan, per thenu_service_useskill, and explicitly notes the Stalwart dependency status.References
nu_service,nu_service_useinstallers.nu::install_bun(#137)Major scope correction after research on the actual repo. hero_mail is not a mail server — it's a control plane wrapping Stalwart. hero_mail itself binds only Unix sockets, so this is a user-level service (no
--root); the privileged SMTP/IMAP ports are bound by Stalwart, separately. No manager binary —service_mail.numust registerhero_mail_server+hero_mail_uidirectly. Two options for handling the Stalwart dependency are documented (prerequisite check vs bundled install); recommended default is prerequisite-only, mirroring theinstall_bunprecedent from #137. Also flagged: the hero_mail repo is missing canonical layout files (buildenv.sh,scripts/build_lib.sh, MIT license inconsistency) — implementer needs to address those as part of this work or in a companion PR.Implementation Spec for Issue #158
Objective
Add
tools/modules/services/service_mail.nutohero_skills: a Nushell lifecycle module exposinginstall | start [--reset] | stop | statusfor thehero_mailHero service, plus a one-line registration inservices/mod.nu. The module follows the user-level multi-binary pattern set byservice_browser.nuand registershero_mail_server+hero_mail_uias two separatehero_procactions (no manager binary) — exactly the two-action shape used byservice_livekit.nu. Stalwart is treated as an external prerequisite: a soft preflight warning is printed atstarttime, mirroring the "preflight check, do not auto-install" precedent set byinstall_bunin #137.Context & Decisions
hero_mail_serverdoes not yet integrate with Stalwart at all (mail.system.healthreturns hardcoded"stalwart_running": false; README states "Stalwart integration is the next step") — there is no env var or config file to wire today. A bundled install would be premature. (3) Mirrors howservice_bookstreatshero_embedder(soft warn, do not hard-fail, do not auto-install). The check is soft (warn, do not fail) because hero_mail's binaries currently start regardless of Stalwart state — failing here would block the rest of the lifecycle for no operational benefit. Aninstall_stalwarthelper is not added toinstallers.nuin this PR; we leave a TODO comment in the preflight function pointing at a future helper.service_mail.nuonly. Justification: (1) The hero_mail repo lives in a separate working tree; editing it requires a separate clone + PR which is out of scope for ahero_skillsissue. (2)service_browser.nu(the template) builds viasvc_cargo_install→ barecargo build --bin <name>against the repo'sCargo.toml, which is what hero_mail already supports (cargo build --workspaceper the Makefile). The build path does not requirebuildenv.shorscripts/build_lib.sh. (3) The MIT vs Apache-2.0 license mismatch and missing.forgejo/workflows/build-linux.yamlare CI / release concerns, not service-lifecycle concerns. The companion issue should be filed againstlhumina_code/hero_mailto track: relicensing to Apache-2.0; addingbuildenv.sh/scripts/build_lib.shshim; and adding.forgejo/workflows/build-linux.yaml.127.0.0.1:25via pure-bash</dev/tcp/...(noncdependency), fall back to checkingstalwart-cli/stalwart-mail/stalwarton$PATH. This matches what is observable about a running Stalwart instance regardless of how it was installed (Debian package, Docker, raw binary, etc.). The user can also exportHERO_MAIL_STALWART_ENDPOINT(host:port) to override the probe target — this hook is added now so when hero_mail grows real Stalwart integration it can be plumbed throughenv:on the action without further service_mail changes.~/hero/var/sockets/hero_mail/rpc.sockand~/hero/var/sockets/hero_mail/ui.sock. This matchesservice_browser,service_books,service_livekit. The hero_mail binaries' default socket paths today are flat (~/hero/var/sockets/hero_mail_server.sock,~/hero/var/sockets/hero_mail_ui.sock) — confirmed by readingcrates/hero_mail_server/src/main.rs::default_socket_pathandcrates/hero_mail_ui/src/main.rs::default_ui_socket_path. The README's~/.hero/var/sockets/...is documentation drift; the binaries actually use~/hero/var/sockets/. Reconciliation: the action'sscriptfield passes explicit--bind unix:<canonical>and--server unix:<canonical>flags so the binaries bind underhero_mail/exactly as the rest of Hero. No code change needed in hero_mail today; the README typo can be fixed in the companion issue.hero_mail_cliis installed but NOT registered as an action. Same asservice_bookstreatshero_books,hero_books_admin,hero_docs. The CLI is installed under~/hero/bin/so users can call it directly; it is not a long-running daemon.--rootflag exposed for tree-uniformity. Issue says no--rootis needed because Stalwart owns the privileged ports. We still expose it (matchingservice_browser/service_books/service_livekit's public surface) so operators who runprocunder root for system-wide observability still have a path. Documented explicitly that hero_mail itself does not require root.Requirements
use services/mod.nu *makesservice_mailavailable (acceptance #1).service_mail installclones+builds+copies the three binaries to~/hero/bin/(acceptance #2a).service_mail start [--reset]runs the Stalwart prereq check and warns (does not fail) when Stalwart is not detected.service_mail start [--reset]registershero_mail_server+hero_mail_uias two separate hero_proc actions, registers ahero_mailservice composed of those two actions, and starts it (acceptance #2b).service_mail statusprints state for the composite service (acceptance #2c).service_mail stopcleanly unregisters the service and both actions (acceptance #2d).startoutput includes: service name, action names, state, rpc sock path, ui sock path, UI URL, Stalwart preflight result, and a short test-plan command block (proc service status,proc logs tail hero_mail_server,proc logs tail hero_mail_ui,hero_mail_cli system health) pernu_service_use(acceptance #3).Files to Modify/Create
tools/modules/services/service_mail.nu— NEW (the lifecycle module)tools/modules/services/mod.nu— add one line:export use service_mail.nutools/modules/installers/installers.nuin this PR (noinstall_stalwartis added; deferred until hero_mail wires actual Stalwart integration). A TODO comment inservice_mail.nureferences the future helper.Implementation Plan
Step 1: Author
tools/modules/services/service_mail.nuFiles:
tools/modules/services/service_mail.nuHeader / docstring: copy
service_browser.nuheader, swap names. Document explicitly:--rootis exposed for tree-uniformity but not normally needed."startruns a soft preflight (TCP probe + binary-on-PATH check) and warns when Stalwart is missing, but does not hard-fail — hero_mail's own RPC handlers tolerate Stalwart being absent."hero_mail_server(RPC) +hero_mail_ui(admin dashboard).hero_mail_cliis installed but not registered.Imports:
Constants:
Note:
hero_mail_cliis inSVX_BINARIESsosvc_cargo_installbuilds + copies it; it is NOT inSVX_ACTIONSso it is never registered with hero_proc.svx_server_action [root: bool]: copy fromservice_browser.nu, but thescriptfield includes--bind unix:<canonical_sock>so the binary uses canonical Hero layout (<sock_base>/hero_mail/rpc.sock) rather than its default flat layout. Health-check viaopenrpc_socket: $rpc_sock.svx_ui_action [root: bool]: same pattern, plus the--server unix://<rpc_sock>flag so the UI proxies to the canonical-path server. Theunix://(with scheme + double slash) matcheshero_mail_ui'snormalize_server_urlexpectations.svx_check_stalwart [root: bool](new helper, soft warn only):${HERO_MAIL_STALWART_ENDPOINT:-127.0.0.1:25}viabash -c 'timeout 1 bash -c "</dev/tcp/HOST/PORT"'(pure bash, noncdep).which stalwart-cli/stalwart-mail/stalwart.{ok: bool, endpoint: string, source: "tcp_probe" | "binary_only" | "missing"}.installcommand: thin wrapper, identical structure toservice_browser.Cargo.tomlis a pure workspace (no root package), socargo build --bin <name> --manifest-path Cargo.toml(whichsvc_cargo_installuses) will resolve each--binagainst workspace members. This is howservice_livekitalready works forlk-backend— no--workspaceshim needed.startcommand: structurally cloneservice_browser.nu::start, with two additions:let stalwart_status = (svx_check_stalwart $root)after the binary check (beforesvc_drop_registration), and stash the result for the end-of-startsummary.cliline pointing athero_mail_cli(with note "not registered as an action"), and the test-plan block.stopandstatus: verbatim fromservice_browser.nu, just rename strings.Dependencies: none.
Step 2: Register the new module in
services/mod.nuFiles:
tools/modules/services/mod.nuexport use service_mail.nuin the existing alphabetical-ish list. Place it next toexport use service_matrixchat.nufor cohesion (both are messaging services). Exact insertion point: after the existingexport use service_matrixchat.nuline.Dependencies: Step 1.
Step 3: File the companion issue against
lhumina_code/hero_mailFiles: none (Forgejo issue, not a code change).
~/.hero/...typo (the binaries actually default to~/hero/...percrates/hero_mail_server/src/main.rs::default_socket_pathandcrates/hero_mail_ui/src/main.rs::default_ui_socket_path).Dependencies: independent of Steps 1–2 (can be filed in parallel).
Step 4: Smoke test on a target host (verification, not code)
use services/mod.nu *service_mail install→ expect three binaries under~/hero/bin/(hero_mail_server,hero_mail_ui,hero_mail_cli)service_mail start→ expect Stalwart warning printed, both actions registered,state: runningservice_mail status→ both actions reported viaproc service status hero_mailservice_mail stop→ service + actions removed; sockets cleaned bykill_otherDependencies: Steps 1–2.
Acceptance Criteria
use services/mod.nu *makesservice_mailavailableservice_mail installbuilds + copies all three binaries (hero_mail_server,hero_mail_ui,hero_mail_cli) to~/hero/bin/service_mail start [--reset]registershero_mail_server+hero_mail_uias two separate hero_proc actions and the compositehero_mailservice becomes healthyservice_mail statusreports state for the composite service (which surfaces both actions)service_mail stopcleanly unregisters service + both actionsstartoutput prints sockets / UI URL / Stalwart preflight result / short test plan, pernu_service_useNotes
hero_mailservice is the canonical Hero pattern (matchesservice_livekit,service_browser,service_books,service_proxy). hero_proc owns the supervision; there is nothing for a manager binary to do.--bindand--serverflags in the actionscriptfield. hero_mail's binaries today default to flat socket paths instead of the canonical per-service subdir. Rather than wait for hero_mail to fix its defaults, we pin canonical paths via CLI args at registration time. This is the same trickservice_booksuses (script: $"($bin) serve"). When hero_mail's defaults are fixed in its companion PR, the flags here can be dropped without behavioral change.script:and notargs:. hero_proc action records usescriptas a single shell-style string withinterpreter: "exec". There is noargsfield in the canonical action shape.hero_mail_cliplacement inSVX_BINARIES. Includeshero_mail_clisosvc_cargo_installbuilds and installs it; users invokinghero_mail_cli system healthafterservice_mail installget a working CLI. It is intentionally NOT inSVX_ACTIONSbecause it is a one-shot CLI, not a daemon. Same pattern asservice_bookstreatshero_books,hero_books_admin,hero_docs.bash </dev/tcp/...). Pure bash builtin, no extra package required.ncis NOT a base tool on TF Grid Ubuntu flists.service_*modules consume the hero_mail repo as a build input; the relicensing affects redistribution of compiled binaries, which is independent of this lifecycle module's own (Apache-2.0) licence.~/.hero/var/sockets/hero_mail_*.sock(dotted dir, flat layout). Code uses~/hero/var/sockets/hero_mail_*.sock(no dot, flat layout). Module pins canonical Hero~/hero/var/sockets/hero_mail/{rpc,ui}.sockvia--bind. The README typo is tracked in the companion issue.Critical Files for Implementation
tools/modules/services/service_browser.nu(template — header, install/start/stop/status structure, two-action shape)tools/modules/services/service_livekit.nu(two-action precedent with both server + ui registered separately, no manager)tools/modules/services/service_books.nu(precedent forscript: $"($bin) <args>"with CLI flags inside the actionscriptfield; precedent for soft-warn external dep)tools/modules/services/lib.nu(shared helpers: svc_install, svc_drop_registration, svc_service_config, svc_start_preflight, svc_require_proc, svc_stop_service, svc_service_status, svc_server_timing/health, svc_ui_timing/health, svc_bin, svc_sock_base)tools/modules/services/mod.nu(one-line export to add)tools/modules/installers/installers.nu(install_bun precedent for the soft-prereq pattern; no edits in this PR but referenced for the future install_stalwart helper)Validation results
Project type: Hero skills repo (Nushell + Markdown). No Rust/Node/Python test suite — validation is parse-/load-checks via
nu -c.Files touched:
tools/modules/services/service_mail.nu(NEW, ~300 lines)tools/modules/services/mod.nu(+1 line —export use service_mail.nu)Checks:
nu -c "source tools/modules/services/service_mail.nu; print 'parsed ok'"nu -c "use tools/modules/services/mod.nu *; print 'mod.nu ok'"service_mail install --helpservice_mail start --helpservice_mail stop --helpservice_mail status --helpSummary: all parse/load checks passed (6/6).
Other test infrastructure inspected:
.forgejo/workflows/build.yaml— runsscripts/test.sh, which validates SKILL.md frontmatter underclaude/skills/(perSKILLS_DIRinbuildenv.sh). It does not exercise files undertools/modules/services/.Makefile, noCONTRIBUTING.md, no Nushell-specific test harness.nu -cis the appropriate (and only) automated validation for this change.Output excerpts (for reference):
Git status:
Exactly the two expected files — no collateral changes.
Implementation summary
service_mail.nulifecycle module is implemented on branchdevelopment_service_mailand ready for review.Changes
tools/modules/services/service_mail.nu(~307 lines) — Nushell lifecycle module exposinginstall | start [--reset] | stop | statusforhero_mail. Two-action shape (no manager): registershero_mail_server(RPC) andhero_mail_ui(admin dashboard) as separate hero_proc actions composed into ahero_mailservice.hero_mail_cliis built and shipped to~/hero/bin/but not registered as an action. Actionscriptfields pin the canonical socket layout~/hero/var/sockets/hero_mail/{rpc,ui}.sockvia--bind unix:/--server unix://flags so binaries land in the per-service subdir regardless of their flat-default behavior.tools/modules/services/mod.nu(+1 line) —export use service_mail.nuplaced next toservice_matrixchat.nu.Stalwart preflight
Soft prerequisite check (warning, not hard failure):
${HERO_MAIL_STALWART_ENDPOINT:-127.0.0.1:25}via pure-bash</dev/tcp/...(noncdependency).which stalwart-cli/stalwart-mail/stalwart.startsummary block.startsucceeds in stub mode when Stalwart is absent —mail.system.healthalready reportsstalwart_running:falsedefensively.svx_check_stalwartreferencesinstallers.nu::install_bun(#137) as the precedent for a future hard-prereq + auto-install path once hero_mail wires real Stalwart integration.startsummary blockPer
nu_service_use, the end-of-start banner prints: service name, both action names, state, rpc sock path, ui sock path, UI URL, Stalwart preflight status, thehero_mail_clipath (with note "not registered as an action"), and a four-line test plan (proc service status,proc logs tailfor both daemons,hero_mail_cli system health).Validation results
All 6/6 parse / load / help-render checks pass — see #158 (comment) for the table. No automated test infrastructure exists for nu modules in this repo (
.forgejo/workflows/build.yamlonly validatesSKILL.mdfrontmatter underclaude/skills/).Companion issue
Filed against
lhumina_code/hero_mailto track the canonical-layout work that doesn't block this PR but does affect cross-tree consistency:Tracked items:
buildenv.sh,scripts/build_lib.shshim,.forgejo/workflows/build-linux.yaml, MIT → Apache-2.0 relicense, README socket-path typo.Caveats
--bindon the UI action assumeshero_mail_uiaccepts a--bind unix:<path>flag (the spec called for--serveronly; both flags are passed for consistency with the server action and to ensure the UI listens on the canonical path rather than a flat default). If the binary rejects--bind, a one-line follow-up fix can drop it.--rootis exposed on the public surface for tree-uniformity even though the issue says hero_mail itself doesn't need root — Stalwart owns the privileged ports, hero_mail binds Unix sockets only. Documented inline.