Integrate zinit SDK: ZinitLifecycle for all binaries, logging via zinit, health checks #77
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?
Context
Parent issue: lhumina_code/hero_os#24
Hero Books already has basic zinit integration for service registration (
zinit_integration.rs), but it uses a custom approach rather than the standardZinitLifecyclepattern. Logging goes through a custom in-memoryOperationLoggerrather than zinit's log system.Important: In-process long-running operations (PDF generation, AI Q&A extraction, embedding upload, book export, background init pipeline, library rescan/reembed) stay as in-process async tasks. They work with in-memory state (loaded collections, Chrome CDP sessions, cached embedders, server config) and cannot be externalized to zinit subprocess jobs. However, they should log through zinit for centralized visibility.
1. Adopt
ZinitLifecyclefor hero_books_serverFiles:
crates/hero_books_server/src/main.rscrates/hero_books_server/src/zinit_integration.rs(to be removed)Currently uses a custom
--startflag and hand-written zinit registration code. Should adopt the standardZinitLifecyclepattern (non-OpenRPC binary) withrun/start/stop/status/logs/servesubcommands.Add
hero_rpc_serverdependency forZinitLifecycle. Removezinit_integration.rs.2. Add
ZinitLifecycleto hero_books_uiFile:
crates/hero_books_ui/src/main.rsCurrently standalone binary with no lifecycle management. Add same
ZinitLifecyclesubcommand pattern.3. Add
ZinitLifecycleto hero_books_viewerFile:
crates/hero_books_viewer/src/main.rsSame — add
ZinitLifecyclesubcommands.4. Replace custom OperationLogger with zinit logs
Files:
crates/hero_books_server/src/admin/logger.rs— Custom circular-bufferOperationLogger(390 lines)crates/hero_books_server/src/logging.rs—hb_log!macro (dual-channel: console + in-memory)Replace with zinit
logs.insert()using structured source names. Thehb_log!macro should forward to zinit instead of the in-memory buffer.Log source naming convention
hero_books.startuphero_books.discover.{namespace}hero_books.rescan.{namespace}hero_books.reembed.{namespace}hero_books.scan.{collection}hero_books.qa.{collection}hero_books.export.{book}hero_books.pdf.{book}hero_books.index.{namespace}hero_books.push_ai.{repo}5. Health checks in zinit service registration
File:
crates/hero_books_server/src/zinit_integration.rs(currently no health check)Configure HTTP health checks for all three services:
/healthon Unix socket/healthon Unix socket/healthon Unix socket6. Remove shell scripts, use binary subcommands
Files to remove:
scripts/run-services.shscripts/stop.shscripts/status.shReplace with Makefile targets that call binary subcommands:
7. Update zinit_sdk dependency
File:
crates/hero_books_server/Cargo.tomlUpdate to
development_kristofbranch. Addhero_rpc_serverdependency forZinitLifecycle.Summary
zinit_integration.rs+--startflagZinitLifecyclesubcommandsZinitLifecyclesubcommandsZinitLifecyclesubcommandsOperationLogger(1000-entry buffer) +hb_log!macrologs.insert()with source naming/healthrun-services.sh,stop.sh,status.shAcceptance Criteria
run/start/stop/status/logs/servesubcommands viaZinitLifecyclezinit_integration.rsremoved (replaced byZinitLifecycle)hb_log!macro forwards to zinit with structured source namesOperationLoggerremoved or reduced to thin wrapper around zinit logszinit_sdkdependency updated todevelopment_kristofAdditional: Adopt
ZinitLifecyclepattern from hero_rpcRelated issues
OServer::run_cli()/ZinitLifecyclepatternWhat this means for hero_books
Hero Books is not a standard OpenRPC-generated server (it predates the
OServer::run_cli()pattern), but it should adopt the same CLI subcommand model usingZinitLifecycledirectly — the "Non-OpenRPC Binaries" pattern from hero_rpc.Current state
hero_books_server currently uses a
--startflag to optionally register with zinit via custom code inzinit_integration.rs. This is the old pattern.Target state
All three hero_books binaries should use
ZinitLifecyclewith standard subcommands:Same for
hero_books_uiandhero_books_viewer.Implementation approach
hero_rpc_serverdependency to all three binary crates (forZinitLifecycle)claparg parsing in eachmain.rswith the standard subcommand enumrun/start/stop/status/logstoZinitLifecyclemethodsservesubcommand handlerzinit_integration.rs(replaced byZinitLifecycle)scripts/run-services.shandscripts/stop.sh(each binary manages itself)Makefiletargets:Why this matters
This aligns hero_books with the ecosystem-wide standard (hero_rpc #7). Every Hero service binary — whether OpenRPC-generated or hand-written — should follow the same
run/start/stop/status/logs/servepattern. No more customzinit_integration.rsper repo, no more shell scripts for service orchestration.Implementation Plan: Integrate zinit SDK for all long-running jobs, logging, and process lifecycle
Context
Hero Books has basic zinit integration (
zinit_integration.rs) using the oldServiceConfigBuilderAPI from thedevelopmentbranch. All long-running operations use rawstd::thread::spawn()with a custom in-memoryOperationLogger. The hero_rpc ecosystem has standardized on aZinitLifecyclepattern withrun/start/stop/serve/status/logssubcommands using the newServiceBuilder/ActionBuilder/RetryPolicyBuilderAPIs from thedevelopment_kristofbranch.This plan migrates hero_books to the standard lifecycle pattern, replaces the custom logger with zinit-captured stdout logging, converts background threads to async tasks, and adds health checks.
Phase 1: Update zinit_sdk dependency + Adopt ZinitLifecycle for server
1.1 Update zinit_sdk branch in Cargo.toml
File:
crates/hero_books_server/Cargo.toml:72branch = "development"tobranch = "development_kristof"ServiceBuilder,ActionBuilder,RetryPolicyBuilder,LogsGetInput,ServiceStartInputwithcontextfield1.2 Rewrite
zinit_integration.rs→lifecycle.rsFile:
crates/hero_books_server/src/zinit_integration.rs→ rename tolifecycle.rsstart_as_managed_service()with aZinitLifecyclestruct modeled onhero_rpc/crates/server/src/server/lifecycle.rsstart(),stop(),status(),logs(lines),run()ServiceBuilder::new()+ActionBuilder::new()+RetryPolicyBuilder::new()(new API)exec_command()returns"{binary} serve [--libraries-dir X] [--embedder-url Y] [flags]".health_http()pointing to the Unix socket health endpoint (or.health_tcp()if Unix socket URLs not supported)1.3 Restructure server CLI with subcommands
File:
crates/hero_books_server/src/main.rsClistruct with subcommand enum:Run— start via zinit, stream logs, stop on Ctrl-C (dev mode)Start— register with zinit and start in backgroundStop— stop the zinit-managed serviceServe { all current args }— actually run the server (called by zinit)Status— query zinit service statusLogs { -n lines }— fetch zinit logsServevariant holds all existing CLI args (--libraries-dir,--embedder-url, etc.)Run/Start/Stop/Status/Logsdispatch through the newZinitLifecycleServeruns the current server logic (moved from the else branch)Servebehavior (or print help)1.4 Update lib.rs module declarations
File:
crates/hero_books_server/src/lib.rspub mod zinit_integration;→pub mod lifecycle;Phase 2: Adopt ZinitLifecycle for UI and Viewer
2.1 hero_books_ui
Files:
crates/hero_books_ui/Cargo.toml,crates/hero_books_ui/src/main.rszinit_sdk = { ..., branch = "development_kristof" }dependencyServevariant gets--socketarg (current behavior)ZinitLifecycleinstance for"hero_books_ui"service.requires("hero_books_server")dependency/api/healthJSON endpoint returning{"status":"ok","service":"hero_books_ui","version":"..."}2.2 hero_books_viewer
Files:
crates/hero_books_viewer/Cargo.toml,crates/hero_books_viewer/src/main.rs/healthhandler — reuse it.requires("hero_books_server")dependencyPhase 3: Replace OperationLogger with structured stdout logging
3.1 Remove OperationLogger from AdminRpcState
File:
crates/hero_books_server/src/admin/rpc.rslogger: Arc<OperationLogger>withzinit_socket: StringinAdminRpcStatehandle_library_rescan(): replacelogger.log_operation_start/complete/errorwithlog::info!("[rescan:{ns}] ...")handle_library_reembed(): same — uselog::info!("[reembed:{ns}] ...")handle_search(): replace logger calls withlog::info!("[search] ...")handle_export(): replace logger calls withlog::info!("[export] ...")handle_logs_get(): rewrite to query zinit viaZinitRPCAPIClient::logs_get(), parse log lines into compatible JSON formathandle_logs_clear(): become a no-op or return success (zinit logs are append-only)handle_stats(): removelog_countor derive from zinit3.2 Update admin server
File:
crates/hero_books_server/src/admin/server.rsbuild_admin_router(): replacelogger: Arc<OperationLogger>param withzinit_socket: Stringzinit_socketthroughAdminRpcState3.3 Remove global logger plumbing
File:
crates/hero_books_server/src/web/server.rsset_operation_logger(),get_logger(), and theoperation_logger()OnceLockget_logger()calls withlog::info!()/log::warn!()3.4 Remove unused parameters
File:
crates/hero_books_server/src/web/axum_server.rs_loggerand_start_timeparameters fromstart_rpc_only_server()signature (already unused)3.5 Clean up main.rs
File:
crates/hero_books_server/src/main.rsOperationLogger::new()andset_operation_logger()calls3.6 Delete or simplify logging macro
File:
crates/hero_books_server/src/logging.rshb_log!macro (it is not used in actual code, only in specs)lib.rsto remove#[macro_use] pub mod logging;3.7 Delete OperationLogger
File:
crates/hero_books_server/src/admin/logger.rsLogLevel,OperationType,OperationStatus,LogEntrytypes (needed for admin API response format)OperationLoggerstruct and all its methodstypes.rs(or keep aslogger.rswith just types)3.8 Update admin mod.rs exports
File:
crates/hero_books_server/src/admin/mod.rsOperationLoggerfrompub uselineLogEntry, LogLevel, OperationStatus, OperationTypePhase 4: Convert background threads to async tasks
4.1 Convert startup pipeline thread
File:
crates/hero_books_server/src/web/axum_server.rs:102std::thread::spawn(move || { ... })withtokio::spawn(async move { ... })tokio::task::spawn_blocking()where neededCancellationTokensupport for graceful shutdownlog::info!()(already the case in this code)4.2 Convert admin rescan thread
File:
crates/hero_books_server/src/admin/rpc.rs:584std::thread::spawn(move || { ... })withtokio::spawn(async move { tokio::task::spawn_blocking(move || { ... }).await })log::info!("[rescan:{}] ...", ns)4.3 Convert admin reembed thread
File:
crates/hero_books_server/src/admin/rpc.rs:643tokio::spawn+spawn_blockinglog::info!("[reembed:{}] ...", ns)4.4 Convert import job thread
File:
crates/hero_books_server/src/web/server.rs(import job at ~line 4516)std::thread::spawnwithtokio::spawnImportJobstruct'slogfieldPhase 5: Health checks and shell script updates
5.1 Add health check to zinit service registration
File:
crates/hero_books_server/src/lifecycle.rs(new in Phase 1).health_http("http+unix:///path/to/hero_books_server.sock/health")first.health_tcp()for socket connectivity check5.2 Simplify shell scripts
Files:
scripts/run-services.sh,scripts/stop.sh,scripts/status.shrun-services.sh: Replace$ZINIT add-servicecalls withhero_books_server start,hero_books_ui start,hero_books_viewer startstop.sh: Replace$ZINIT stop/removecalls withhero_books_server stop, etc.status.sh: Replace manual socket checks withhero_books_server status, etc.Files Summary
crates/hero_books_server/Cargo.tomlcrates/hero_books_server/src/zinit_integration.rs→lifecycle.rscrates/hero_books_server/src/main.rscrates/hero_books_server/src/lib.rscrates/hero_books_ui/Cargo.tomlcrates/hero_books_ui/src/main.rscrates/hero_books_viewer/Cargo.tomlcrates/hero_books_viewer/src/main.rscrates/hero_books_server/src/admin/rpc.rscrates/hero_books_server/src/admin/server.rscrates/hero_books_server/src/admin/mod.rscrates/hero_books_server/src/web/server.rscrates/hero_books_server/src/web/axum_server.rscrates/hero_books_server/src/logging.rscrates/hero_books_server/src/admin/logger.rs(keep types only)scripts/run-services.shscripts/stop.shscripts/status.shKey Reference Files
hero_rpc/crates/server/src/server/lifecycle.rs— ZinitLifecycle pattern to replicatehero_rpc/crates/server/src/server/server.rs— OServer::run_cli() subcommand dispatch patternVerification
cargo build -p hero_books_server -p hero_books_ui -p hero_books_viewer— all 3 crates compilehero_books_server --help— shows subcommands (run/start/stop/serve/status/logs)hero_books_server serve --libraries-dir ~/hero/var/books/— server starts and serves RPChero_books_server start— registers with zinit and startshero_books_server status— shows zinit service statushero_books_server logs -n 50— shows recent logs from zinithero_books_server stop— stops the servicehero_books_uiandhero_books_viewerstd::thread::spawnremaining for long-running operationsgrep -r "OperationLogger" crates/— only type references remain (no buffer/storage)Correction: scope of zinit jobs vs in-process operations
After further discussion, the recommendation to convert in-process operations to zinit jobs was incorrect for most items listed above. Zinit jobs are subprocess-based — they spawn external commands. Most hero_books operations work with in-memory state and cannot be externalized to subprocesses.
What should NOT become zinit jobs (stays in-process)
What SHOULD use zinit
ZinitLifecyclepattern for server, UI, viewerlogs.insert()with structured source nameshero_books.{op}.{target}development_kristofRevised summary
The core improvements are:
ZinitLifecyclefor all 3 binaries (server, UI, viewer) — replacing customzinit_integration.rsand shell scriptsOperationLoggerandhb_log!macro with zinitlogs.insert()using structured source namesdevelopment_kristofbranchIn-process long-running operations stay as they are (async tasks / background threads) but should log through zinit for centralized visibility.
Integrate zinit SDK for all long-running jobs, logging, and process lifecycleto Integrate zinit SDK: ZinitLifecycle for all binaries, logging via zinit, health checksImplementation audit — code is correct
Audited all uncommitted changes. The implementation is well-structured:
hero_books_server,hero_books_ui,hero_books_viewer) usingServiceConfigBuilder— ✅ correct, uses service/action APIs onlyzinit_integration.rsdeleted — replaced bylifecycle.rs— ✅ correctlogging.rs(hb_log! macro) deleted — ✅ per plancargo check --workspacepasses)Remaining: code is uncommitted (staged + untracked files). Needs to be committed and pushed.