service_books.nu — hero_books server + UI lifecycle module #80
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_skills#80
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?
Child of #75.
Objective
Add
tools/modules/services/service_books.nuimplementing the standardinstall | start | stop | statuslifecycle for the hero_books service (server + UI) so it can be driven the same way asservice_os,service_browser,service_proxy, etc.Scope
ssh://git@forge.ourworld.tf/lhumina_code/hero_books.gitbuildenv.sh):hero_books,hero_books_server,hero_books_ui,hero_books_admin,hero_docshero_books_server,hero_books_uilhumina_code/hero_zero/services/hero_books.toml$HERO_SOCKET_DIR/hero_books/rpc.sock,$HERO_SOCKET_DIR/hero_books/ui.sock(exact paths to confirm in spec)RUST_LOG=info,HERO_BOOKS_DATA=$HERO_VAR/books,HERO_EMBEDDER_URL=unix:///…/hero_embedder/rpc.sockRUST_LOG=infohero_indexer_server,hero_embedder_server— hero_proc resolves these at orchestration time; spec should flag the operator prerequisite (both must be installed + registered, or hero_books will hit retry caps for the same reason hero_os's UI did without WASM assets).--rootflag supported but optional — default is user-level hero_proc.Acceptance criteria
use services/mod.nu *(oruse services/service_books.nu *) makesservice_booksavailable.service_books install [--root] [--update]cloneslhumina_code/hero_books, builds via cargo workspace, places all five binaries in~/hero/bin/(or/root/hero/bin/with--root).service_books start [--reset] [--root] [--update]registers both runtime actions + the service withhero_proc, starts, prints RPC + UI socket paths andhttp+unix://…/ui.sock/URL in the summary.service_books status [--root]reports state.service_books stop [--root]cleanly unregisters.--rootonly needed when running under root's hero_proc; default path is user-level.running(with embedder/indexer available) or a clean degraded-mode signal when dependencies are missing.Template & references
tools/modules/services/service_os.nu(newest two-binary example, merged in PR #78) andtools/modules/services/service_browser.nu(simpler user-level variant).claude/skills/nu_service/SKILL.md(build),claude/skills/nu_service_use/SKILL.md(use).tools/modules/services/lib.nu—svc_require_proc,svc_cargo_install,svc_update,svc_bin,svc_sock_base.Out of scope
hero_embedder_server/hero_indexer_serverdependencies — they have (or will have) their ownservice_*.numodules. The spec should only surface the dependency as a pre-flight warning / operator note, not try to start them transitively.hero_books_adminandhero_docsbinaries — they are installed (so CLI users can invoke them) but are not registered as hero_proc actions.Implementation Spec for Issue #80
Objective
Add
tools/modules/services/service_books.nu— a Nushell lifecycle module that registers and supervises the hero_books service (server + UI) with hero_proc, following the two-action pattern established by the freshly-mergedservice_os.nu(PR #78).Requirements
install | start | stop | statusas exported commands, matching the conventions ofservice_os.nu/service_browser.nu.hero_booksworkspace:hero_books,hero_books_server,hero_books_ui,hero_books_admin,hero_docs(perbuildenv.shline 16). Onlyhero_books_serverandhero_books_uiare registered as hero_proc actions; the other three ship alongside because they are CLI utilities (hero_booksorchestrator,hero_books_adminadmin UI,hero_docsdocusaurus generator) that users invoke directly.hero_bookswith the two runtime actions.RUST_LOG=info,HERO_BOOKS_DATA=<hero_home>/var/books,HERO_EMBEDDER_URL=unix://<sock_base>/hero_embedder/rpc.sock— all three computed at register time fromsvc_home/svc_sock_baseso root-vs-user installs resolve correctly.RUST_LOG=infoonly.crates/hero_books_server/src/web/axum_server.rs:39-48andcrates/hero_books_ui/src/main.rs:129-137):$HERO_SOCKET_DIR/hero_books/rpc.sock$HERO_SOCKET_DIR/hero_books/ui.sock$HERO_SOCKET_DIR/hero_embedder/rpc.sockis missing — hero_books_server runs in "degraded mode" without the embedder (axum_server.rs:56-62), so start should still register + start and the operator can bring upservice_embedderseparately.--root(-r)flag on every command; user-level is the default. Passwordless sudo is required for--root(same contract as the rest of the family).--update(-u)oninstall/startto pull viasvc_update;--resetonstartto force a clean re-register.tools/modules/services/mod.nu.Files to Modify/Create
tools/modules/services/service_books.nu— new lifecycle module, ~290 lines, cloned fromservice_os.nuand retargeted to hero_books.tools/modules/services/mod.nu— append one line:export use service_books.nu(alphabetical insertion betweenservice_browser.nuandservice_codescalers.nuis fine but the existing file is not strictly sorted — append at end of list for a minimal diff, matching the style of the most recentservice_os.nuline).No edits to
lib.nu: every helper needed already exists (svc_home,svc_bin,svc_bin_dir,svc_sock_base,svc_require_sudo,svc_need_sudo,svc_require_proc,svc_proc_healthy,svc_update,svc_cargo_install).Implementation Plan
Step 1: Create the module skeleton and constants block
Files:
tools/modules/services/service_books.nuservice_os.nu:1-52and adapt the prose:hero_books,hero_books_admin,hero_docs) explicitly.crates/hero_books_server/src/web/axum_server.rs:56-62).hero_indexer_server,hero_embedder_server) are NOT started transitively by this module — it only preflight-warns for the embedder socket.service_os.nu:40-41):use ../clients/proc.nu *use ./lib.nu *const SVX_SERVICE_NAME = "hero_books"const SVX_FORGE_LOC = "lhumina_code/hero_books"const SVX_BINARIES = ["hero_books" "hero_books_server" "hero_books_ui" "hero_books_admin" "hero_docs"]— full list frombuildenv.sh:16.const SVX_ACTIONS = ["hero_books_server" "hero_books_ui"]— only the runtime daemons.Dependencies: none.
Step 2: Implement
svx_server_actionwith HERO_BOOKS_DATA + HERO_EMBEDDER_URLFiles:
service_books.nuservice_os.nu:58-97(server action).--rootflips home/sock paths correctly: Rationale for the embedder URL: the TOML encodesunix:///root/hero/var/sockets/hero_embedder/rpc.sock, but hero_books_server's compiled-in default isunix://$HERO_SOCKET_DIR/hero_embedder/rpc.sock(seecrates/hero_books_lib/src/lib.rs:50-57). Matching the compiled default viasvc_sock_baseis correct for both user-level and--rootinstalls; the TOML hardcode is a zero-service artifact.envrecord:kill_other.socket:[$"($sock_base)/hero_books/rpc.sock"](note directory = service name, file =rpc.sock, confirmed ataxum_server.rs:40-48).health_checks[0].openrpc_socket:$"($sock_base)/hero_books/rpc.sock".service_os.nu:68-78(5 attempts, 30 s start timeout, SIGTERM, 10 s stop timeout).Dependencies: Step 1.
Step 3: Implement
svx_ui_actionFiles:
service_books.nuservice_os.nu:99-138.bin = (svc_bin "hero_books_ui" $root),sock_baseas in Step 2.{RUST_LOG: "info"}only (the UI reads its ownHERO_BOOKS_SOCKETenv optional override; default fromcrates/hero_books_ui/src/main.rs:82-86points to$HERO_SOCKET_DIR/hero_books/rpc.sockwhich is the same value the server publishes, so no env plumbing is needed).kill_other.socket:[$"($sock_base)/hero_books/ui.sock"].health_checks[0].openrpc_socket:$"($sock_base)/hero_books/ui.sock"— same convention asservice_os.nu:129(hero_proc uses this field as a liveness probe target; it does not require the socket to speak OpenRPC).service_os.nu:109-119.Dependencies: Step 1.
Step 4: Implement
svx_service_configandsvx_drop_registrationFiles:
service_books.nusvx_service_config: copy fromservice_os.nu:140-152verbatim except for the human-readable description — use"Hero Books — library server and UI". Keepcontext_name: "core",class: "system",critical: false,status: "start".svx_drop_registration: copyservice_os.nu:155-161verbatim; it iterates$SVX_ACTIONS, so it automatically handles the two-action case without modification.Dependencies: Step 1.
Step 5: Implement
svx_check_embedderpreflight helperFiles:
service_books.nuservice_os.nu:166-182: same warn-don't-fail shape.$"(svc_sock_base $root)/hero_embedder/rpc.sock".svx_check_assets: (Prefertest -Sover-fsince sockets won't match-f.)service_os.nu:174-181, with remediation lines pointing toservice_embedder install && service_embedder start, and a note that hero_books does not need a restart once the embedder comes up (the server retries on next RPC).hero_indexer_server— it is another optional dependency listed inhero_books.toml:4, and its absence does not block server startup. Document this in the header comment.Dependencies: Step 1.
Step 6: Implement
installFiles:
service_books.nuservice_os.nu:191-198verbatim (signature and body). The five-binary list is already encoded inSVX_BINARIES;svc_cargo_installrunscargo build --releaseagainst the workspace rootCargo.toml, and the hero_books workspace publishes all five bins at the workspace level, so the plain cargo build produces them all intarget/release/.make installtarget uses an extrabuild_lib.sh install_binarieshelper (Makefile:144-149) but the sibling modules all go cargo-direct and that's the canonical path per lib.nu.--root(-r),--update(-u).Dependencies: Steps 1-5 (uses
SVX_FORGE_LOCandSVX_BINARIES).Step 7: Implement
startFiles:
service_books.nuservice_os.nu:215-293, with string substitutions and one added step.service_os.nu:226-235).hero_books_serverrather thanhero_os_server(service_os.nu:242-250).svx_check_assetscall withsvx_check_embedder $rootat the equivalent position (service_os.nu:253).proc action set, then the service, then start (service_os.nu:260-270).service_os.nu:272-292; print service name, action list, running state, both socket paths, and the threeproc ...commands for status/logs. Update the example URLs — hero_books_ui serves HTTP over Unix socket the same way as hero_os_ui (crates/hero_books_ui/src/main.rs:140-148), so the summary lineui url : http+unix://<ui.sock>/ served by hero_books_ui; reach the UI via hero_routerstays structurally identical.Dependencies: Steps 2-6.
Step 8: Implement
stopFiles:
service_books.nuservice_os.nu:306-321with the service name changed.svx_drop_registrationis already parameterised viaSVX_ACTIONS.Dependencies: Step 4.
Step 9: Implement
statusFiles:
service_books.nuservice_os.nu:330-335— one-line body delegating toproc service status $SVX_SERVICE_NAME --root=$rootaftersvc_require_proc.Dependencies: Step 4.
Step 10: Wire into
mod.nuFiles:
tools/modules/services/mod.nuexport use service_books.nuas a new final line (currently ends at line 8 withservice_os.nu).Dependencies: Step 1.
Step 11: Local syntax check
nu -c "source tools/modules/services/service_books.nu; print parse-ok"from the hero_skills repo root, to catch parser errors.nu -c "use tools/modules/services/mod.nu *; scope commands | where name =~ '^service_books '"to verify the re-export path resolves.Dependencies: Steps 1-10.
Acceptance Criteria
service_books installbuilds all five binaries and places them in~/hero/bin/.service_books install --rootdoes the same into/root/hero/bin/via sudo.service_books startregisters thehero_books_serverandhero_books_uiactions and ahero_booksservice with hero_proc, then starts it; idempotent on re-run.service_books start --resetforce-restarts even when already running.service_books startwarns but does NOT fail when$HERO_SOCKET_DIR/hero_embedder/rpc.sockis absent, and the service still transitions torunning.service_books stopremoves both actions and the service cleanly; no-op when hero_proc is down (prints remediation).service_books statusprints hero_proc status for thehero_booksservice.HERO_BOOKS_DATAandHERO_EMBEDDER_URLare set in the registered server action env (inspect viaproc action get hero_books_server) and resolve to user-home paths when run without--rootand to/root/...with--root.service_books --help/scope commandsshows the four subcommands viamod.nu.Notes
Binary inventory (5 binaries, 2 actions).
hero_books_server,hero_books_ui→ registered as hero_proc actions (from the[server]and[ui]blocks ofhero_zero/services/hero_books.toml).hero_books→ CLI orchestrator with its own--start/--stophero_proc flow (seeMakefile:41-47). We install it but do NOT register it, because this module IS the orchestrator for Nushell users.hero_books_admin→ optional admin Axum server (not in the TOML, but present inbuildenv.sh:16and shipped alongside). Not registered as an action because the admin UI is mounted under/admininsidehero_books_serveralready (crates/hero_books_server/src/web/axum_server.rs:88-94); the standalone admin binary is a developer convenience.hero_docs→ one-shot docusaurus generator CLI (src/bin/hero_docs.rs). Never a daemon. Shipped sohero_docs new/generateis on $PATH.Socket path confirmation.
$HERO_SOCKET_DIR/hero_books/rpc.sock— confirmed atcrates/hero_books_server/src/web/axum_server.rs:39-48(hero_books_lib::hero_service_socket_dir("hero_books")+"rpc.sock").$HERO_SOCKET_DIR/hero_books/ui.sock— confirmed atcrates/hero_books_ui/src/main.rs:129-137(buildsHERO_SOCKET_DIR/hero_books+ui.sock).HERO_BOOKS_DATA + HERO_EMBEDDER_URL placeholder handling.
__HERO_VAR__/booksand a hardcoded/root/hero/var/sockets/hero_embedder/rpc.sock— these are placeholder strings for hero_zero's templating.HERO_BOOKS_DATA = $"(svc_home $root)/var/books"— matches hero_books_lib's defaultlibraries_base_dir()(library.rs:38,54-56).HERO_EMBEDDER_URL = $"unix://(svc_sock_base $root)/hero_embedder/rpc.sock"— matches hero_books_lib'sdefault_embedder_url()(lib.rs:50-57) and routes correctly for both user and root installs. This departs from the TOML's/root/...hardcode on purpose; the TOML is consumed by a different codepath that doesn't participate here.svc_home $root/svc_sock_base $rootat the spot where the action spec is built. Document this convention inline in the module header so subsequent services can reuse it.UI assets.
include_str!/include_bytes!fromcrates/hero_books_ui/static/at compile time (handlers.rs:1291-1294). No external asset bundle is required, so no asset preflight is needed — this is unlike hero_os_ui which hard-fails without$HERO_OS_ASSETS/index.html(the reason forsvx_check_assetsinservice_os.nu:166-182).Dependencies (depends_on).
hero_books.toml:4listshero_indexer_serverandhero_embedder_server. These are consumed by hero_zero's dependency resolver; this nu module does NOT try to start them transitively (consistent withservice_os.nu, which does not start hero_router even though hero_os's UI is brokered through hero_router).hero_embedder is not available — starting in degraded mode (no vector search)and continues (axum_server.rs:55-62) — so warn-don't-fail is the correct policy.Root vs user-level specifics.
svc_home $root/svc_sock_base $root(lib.nu:26-43).HERO_SOCKET_DIRenv override is honored only for user-level (lib.nu:37-39), which matches the rest of the family.--roottriggerssvc_require_sudoat entry (error out with a clear message if passwordless sudo isn't configured). Compilation always happens in the invoking user's cargo cache; only the final binary copy goes through sudo.HERO_EMBEDDER_URLenv var built withsvc_sock_base $rootresolves to/root/hero/var/sockets/hero_embedder/rpc.sockunder--rootand$HOME/hero/var/sockets/hero_embedder/rpc.sock(or$HERO_SOCKET_DIR/...when set) otherwise, which is the correct topology.Hetzner smoke-test plan.
service_proc start --root(prereq).service_books install --root— expect five binaries in/root/hero/bin/; verify withls /root/hero/bin/hero_books* /root/hero/bin/hero_docs.service_books start --reset --root— expect the embedder warning block to fire (since embedder is not installed yet on a fresh box), then the service to register and enterrunningstate.service_books status --root— expect a running state for thehero_booksservice with both actions healthy.curl --unix-socket /root/hero/var/sockets/hero_books/ui.sock http://localhost/health— expect 200 OK (the UI publishes/health).proc logs tail hero_books_server --root— expect the degraded-mode banner.service_embedder install --root && service_embedder start --root— expect hero_books_server to start responding to vector-backed RPCs on the next invocation without a restart.service_books stop --root— expect clean unregistration.running, sockets appear at the expected paths, and the UI/healthendpoint returns 200. Full Q&A / vector-search functionality is NOT part of the smoke-test success criterion — that comes afterservice_embedder start.Implementation summary
Changes
tools/modules/services/service_books.nu— ~320 lines, modelled onservice_os.nuwith three books-specific pieces.tools/modules/services/mod.nu— addedexport use service_books.nuas the 9th entry.What the module does
service_books install [--root] [--update]— cloneslhumina_code/hero_books, runs an explicitcargo build --release --workspacepass (necessary because hero_books has a hybrid workspace+root-package layout; plaincargo buildmisses_server/_ui/_admin), then copies all 5 binaries (hero_books,hero_books_server,hero_books_ui,hero_books_admin,hero_docs) to~/hero/bin/(or/root/hero/bin/with--root).service_books start [--reset] [--root] [--update]— pre-flights the hero_embedder socket (warns, does not fail), drops any stale registration, registers both runtime actions + the service, starts it, prints both Unix sockets and thehttp+unix://…/ui.sock/URL.service_books status [--root]— returns the hero_proc record.service_books stop [--root]— cleanly unregisters; tolerant of hero_proc being down.End-to-end smoke test on Hetzner
Run from a clean state on the root-level box. Every assertion green.
Phase 1 — error paths with hero_proc DOWN
service_books status --rooterrors with "hero_proc is not running" + remediationservice_books stop --rootwarns and returns exit 0service_books start --rooterrors with the same guidance before touching binariesPhase 2 — full lifecycle with hero_proc UP
service_proc start --roothealthyservice_books install --root— workspace build, 5/5 binaries in/root/hero/bin/service_books start --reset --root— embedder preflight warning fires (expected, embedder not installed), actions + service register, summary prints rpc sock, ui sock,http+unix://URL/root/hero/var/sockets/hero_books/rpc.sockis a live unix socket/root/hero/var/sockets/hero_books/ui.sockis a live unix socketcurl --unix-socket rpc.sockreturns HTTP (socket accepts requests)curl --unix-socket ui.sock /healthreturns HTTPservice_books status --rootreturns{name: hero_books, state: running, pid, restarts: 0, run_id}service_books start --root(no--reset) early-exits with "already running"HERO_BOOKS_DATA=/root/hero/var/bookson the registered server actionHERO_EMBEDDER_URL=unix:///root/hero/var/sockets/hero_embedder/rpc.sockon the registered server actionrestartsstill 0,statestillrunningservice_books stop --rootstops and unregistersservice_books status --rootreturns expectedservice 'hero_books' not foundPhase 3 — cleanup
service_proc stop --rootran clean.Issues found and fixed during testing
Missing
servesubcommand. First run of the module ran the binaries with no arguments, which prints help and exits. hero_proc saw the immediate exit, retried 5×, marked the servicefailed(state=failed, restarts=6, pid=0). Fix: setscript: $"($bin) serve"in both server and UI action specs. Confirmed by inspectinghero_books_server --helpwhich showsCommands: serve.Hybrid workspace build. hero_books has a root
[package]plus[workspace]members. Plaincargo build --release --manifest-path Cargo.tomlonly builds the root crate (hero_books+hero_docs) plus its direct path-deps (hero_books_lib,hero_books_docusaurus), missinghero_books_server,hero_books_ui,hero_books_admin. Fix (local toservice_books.nu): add an explicitcargo build --release --workspacepass beforesvc_cargo_install. hero_os / hero_proc are pure virtual workspaces (no root package) so plain cargo build covers all members there; hero_books is the first hybrid case in the family.sccache stall. Observed cargo wedging at 0.5% CPU with 0 rustc children — sccache server IPC had a defunct child and held cargo blocked. Worked around by unsetting
RUSTC_WRAPPER/SCCACHE_*env in the test wrapper. Not in the PR scope; mentioned for the record. Likely belongs in a separate follow-up alongside theservice_proc.nu:270fix tracked in #79.Acceptance criteria
service_books installbuilds all five binaries and places them in~/hero/bin/.service_books install --rootdoes the same into/root/hero/bin/via sudo.service_books startregistershero_books_server+hero_books_ui+ thehero_booksservice with hero_proc and starts it; idempotent on re-run.service_books start --resetforce-restarts even when already running.service_books startwarns but does NOT fail when$HERO_SOCKET_DIR/hero_embedder/rpc.sockis absent; service still transitions torunning.service_books stopremoves both actions and the service cleanly; no-op when hero_proc is down.service_books statusprints the hero_proc record.HERO_BOOKS_DATAandHERO_EMBEDDER_URLare set in the registered server action env and resolve to user-home paths (user-level) or/root/...paths (with--root).use services/mod.nu *.PR opened: #81