service_office.nu — hero_office server + UI lifecycle module #97
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#97
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_office.nuimplementing the standardinstall | start | stop | statuslifecycle for the hero_office service (OnlyOffice connector — server + UI). The repo already ships an in-treescripts/nu_service.nuwith most of the shape; this issue lifts that into the centralservices/tree (souse services/mod.nu *surfacesservice_office) with three small additions that match the newer modules (svc_bins_okshort-circuit, hero_foundry soft-dep warning, richer summary block).Scope
ssh://git@forge.ourworld.tf/lhumina_code/hero_office.gitbuildenv.sh):hero_office_server,hero_office_ui(no separate CLI — the__HERO_BIN__/hero_officereference in the hero_zero TOML is a bug).hero_office_server,hero_office_ui.lhumina_code/hero_zero/services/hero_office.toml. KNOWN BUG — points[server] execat a non-existenthero_officebinary and has no[ui]block. The module ignores the TOML and usesbuildenv.sh/ the in-reponu_service.nuas ground truth. Fix of the TOML is out of scope.$HERO_SOCKET_DIR/hero_office/rpc.sock— OpenRPC JSON-RPC (health-checked)$HERO_SOCKET_DIR/hero_office/ui.sock— admin + OnlyOffice wrapper pagesRUST_LOG=info,hero_office_server=debug, plus optionalONLYOFFICE_JWT_SECRET,OO_SERVER_URL,CONNECTOR_EXTERNAL_URL,DEFAULT_CONTEXT,HERO_SOCKET_DIRRUST_LOG=info,hero_office_ui=debug, plus optionalOO_SERVER_URL,CONNECTOR_EXTERNAL_URL,DEFAULT_CONTEXT,HERO_SOCKET_DIRhero_foundry/rpc.sockat request time for file ops; absence is a soft warning (service starts + health OK, only file operations fail).[workspace]only), 4 members, 2 with[[bin]]. Plainsvc_cargo_installsuffices.--rootflag optional; user-level default.Acceptance criteria
use services/mod.nu *makesservice_officeavailable.service_office install [--root] [--update] [--reset]cloneslhumina_code/hero_office, skips rebuild when both binaries already exist (viasvc_bins_ok) unless--resetor--update.service_office start [--reset] [--root] [--update]registers both actions + the service, seeds optional OnlyOffice env when set in the invoking shell, warns (non-fatally) whenhero_foundrysocket is absent, prints both socket paths in the summary. Idempotent without--reset.service_office status [--root]reports state.service_office stop [--root]cleanly unregisters.Template & references
service_whiteboard.nufor the two-binary baseline;service_db.nufor the new--reset+svc_bins_okshort-circuit pattern oninstall;service_books.nufor thesvx_check_foundrysoft-dep warning.lhumina_code/hero_office/scripts/nu_service.nu— already captures env pass-through and kill_other/health-check shapes; our module aligns with it.tools/modules/services/lib.nu.Implementation Spec for Issue #97
Objective
Add a Nushell lifecycle module that manages the
hero_officeservice (OnlyOffice connector) throughhero_proc, exactly the wayservice_db.nu/service_whiteboard.nu/service_books.nu/service_aibroker.numanage theirs. The module supervises two binaries —hero_office_serverandhero_office_ui— both of which bind Unix domain sockets only (no TCP ports). Thehero_officerepo already ships an in-treescripts/nu_service.nuthat is functionally equivalent; this module reproduces that behaviour inside the centralhero_skillsservices index with four deviations listed in §4.Requirements
hero_office_server+hero_office_ui, both registered under a single servicehero_office.$HERO_SOCKET_DIR/hero_office/rpc.sock(server) and$HERO_SOCKET_DIR/hero_office/ui.sock(UI).cargo build --releaseat the virtual workspace root.buildenv.shlists exactlyBINARIES="hero_office_server hero_office_ui". No CLI binary ships —SVX_BINARIES == SVX_ACTIONS.scriptis the bare binary path for both (no subcommand — neithermain.rshas a clap parser).--rootflips paths correctly):ONLYOFFICE_JWT_SECRET,OO_SERVER_URL,CONNECTOR_EXTERNAL_URL,DEFAULT_CONTEXT,HERO_SOCKET_DIR.OO_SERVER_URL,CONNECTOR_EXTERNAL_URL,DEFAULT_CONTEXT,HERO_SOCKET_DIR.RUST_LOGbase:info,hero_office_server=debug(server) andinfo,hero_office_ui=debug(UI). Always set.scripts/nu_service.nu— server max_attempts=5 / UI=3, 2s delay, backoff, 60s cap, 30s start+stability; server stop_timeout_ms=10000 / UI=5000; health interval 2s server / 3s UI, timeout 5s, retries 3, start_period 3s server / 5s UI.hero_foundry:startemits a warning when$HERO_SOCKET_DIR/hero_foundry/rpc.sockis absent but does NOT fail (server starts and health-checks clean without it; only document-read/write RPCs fail until foundry comes up; server retries on next RPC so no hero_office restart is needed).hero_procas always —svc_require_procpath.installcarries the new canonical shape:--root(-r),--update(-u),--reset; short-circuit viasvc_bins_okwhen neither--resetnor--updateand all binaries already in place; otherwise delegate tosvc_cargo_install.startidempotent;stoptolerates deadhero_proc.start: service / actions / state /rpc sock/ui sock/ui url http+unix://…// commands hints. Ship-house style matchingservice_db.nu/service_aibroker.nu.Files to Modify / Create
tools/modules/services/service_office.nu— new module.tools/modules/services/mod.nu— appendexport use service_office.nu.Nothing else is touched. In particular,
hero_zero/services/hero_office.tomlis NOT modified — see §7.Implementation Plan
Step 1: Header comment
Follow
service_db.nu:1-50shape. Sections: purpose + two-binary breakdown, socket layout block,hero_prochard-dep paragraph,hero_foundrysoft-dep paragraph, env pass-through rationale (conditional inclusion, not empty-string), TOML-bug callout, standardImport as a module…usage block,--root(-r)paragraph,./lib.nucloser. Do NOT carry over the "user's context comes from main.rs" paragraph from the in-repo module — that belongs to server internals.Step 2: Imports
No
forge.nuimport (no in-source file lookup).Step 3: Constants
Step 4: NEW helper
svx_check_foundry [root: bool](Deviation #3)Structurally identical to
service_books.nu::svx_check_embedder(lines 185-202). Check$(svc_sock_base $root)/hero_foundry/rpc.sock. When missing, emit the six-line warning:⚠ hero_foundry socket not found at ($foundry_sock)hero_office_server will start in degraded mode — document read/write calls will fail.Bring up hero_foundry when ready:service_foundry install($flag)service_foundry start($flag)hero_office does not require a restart after hero_foundry comes up —the server retries on the next RPC call.Non-fatal: returns normally in both branches.
Step 5:
svx_server_action [root: bool]Follow
service_aibroker.nu::svx_server_actionshape for the conditional-env pattern. KEY DIFFERENCE FROM AIBROKER: OnlyOffice'sconfig.rstreats unset env vars as "use default"; passing empty strings breaks the defaults. So instead of aibroker's "always pass, possibly empty" pattern, useupsertinsideif is-not-emptyguards:Rest of the action record:
script: $bin, retry/stop/kill_other/health values verbatim from the in-repo module (captured in the ground-truth block).Step 6:
svx_ui_action [root: bool]Same conditional-env pattern as step 5.
RUST_LOGbase:info,hero_office_ui=debug; four optional vars (noONLYOFFICE_JWT_SECRET). Retrymax_attempts: 3;stop_timeout_ms: 5000; kill_other.socket isui.sock; health interval 3000 / start_period 5000.Step 7:
svx_service_config []Step 8:
svx_drop_registration [root: bool]Identical to
service_db.nu:171-177.Step 9:
install [--root(-r), --update(-u), --reset](Deviations #1 + #2)Copy
service_db.nu:186-198verbatim with constants swapped:Step 10:
start [--reset, --root(-r), --update(-u)](Deviation #4 placement)Follow
service_books.nu::startstructure:if $root { svc_require_sudo }svc_require_proc "service_office" $rootinstall --root=$root --update=$update --reset=$reset+ post-install binary presence verificationsvx_check_foundry $root(non-fatal)svx_drop_registration $rootproc action set (svx_server_action $root) --root=$root | ignoreproc action set (svx_ui_action $root) --root=$root | ignoreproc service set (svx_service_config) --root=$root | ignoreproc service start $SVX_SERVICE_NAME --root=$root | ignoresleep 1sec+ rich summary block (service/actions/state/rpc sock/ui sock/ui url/commands), modelled onservice_db.nu:269-293.Step 11:
stop [--root(-r)]Copy
service_db.nu::stopverbatim with name swap. Keep thesvc_proc_healthyshort-circuit.Step 12:
status [--root(-r)]Copy
service_db.nu::status.Step 13:
mod.nuAdd
export use service_office.nuin line with the otherservice_*entries.The four deviations from the in-repo
scripts/nu_service.nuinstallgrows a--resetflag (in-repo has only--root/--update).installgains thesvc_bins_okshort-circuit.startgainssvx_check_foundrypreflight warning.startgrows the ship-house summary block.Smoke Test Plan (Hetzner,
--root)installruns cargo; secondinstallwithout flags prints→ hero_office binaries already in place — skipping build;install --resetre-runs cargo.svx_check_foundryemits the six-line warning, service still reaches running;curl --unix-socket rpc.sock /health→ 200;curl --unix-socket rpc.sock /openrpc.json→ OpenRPC doc;curl --unix-socket ui.sock /→ 3xx to/admin/;curl -L --unix-socket ui.sock /admin/→ 200 HTML.startwithout--resetreturnshero_office is already running.--resetreclaim path — while running,start --reset; all health probes pass again; no socket-bind errors./health→ connection refused.Acceptance Criteria (from #97)
service_office.nuexportsinstall,start,stop,status.installaccepts--root,--update,--reset; short-circuits viasvc_bins_ok.startis idempotent, callssvx_check_foundrybetween install-verify and drop-registration, prints the rich summary.RUST_LOGbase + conditionally-included env vars.mod.nuexports the new module.--root.hero_zero/services/hero_office.tomlis NOT modified.lib.nu.→,✓,⚠,===).Notes
scripts/nu_service.nu: the in-repo module stays for anyone working insidehero_office/directly; the centraltools/modules/services/copy is whatmod.nuexports. Same decision taken for every otherservice_*.nu. The two must not drift beyond the four declared deviations without updating both in lockstep.exec = "__HERO_BIN__/hero_office"references a nonexistent binary, and there is no[ui]block.hero_zeroconsumes that TOML at a different provisioning layer; the nushell lifecycle path bypasses it and writes actions/services directly viaproc action set/proc service set. Fixing the TOML is a separate hero_zero cleanup, out of scope here — mirrorsservice_aibroker.nu's treatment of its mis-setsourcefield.config.rstreats unset env vars as "use default"; passing empty strings would break defaults (e.g.OO_SERVER_URL=""would 4xx every browser request).service_aibroker.nucan pass empty strings becausehero_aibroker_lib::Config::loadtolerates empty lists; no such tolerance here.hero_foundryis a soft dep: server opens the foundry socket lazily at first document request./healthand/openrpc.jsondon't touch it. Hard-failing would block a legitimate "bring up office first, foundry later" scenario and force a restart cascade that the server itself does not need. The warning is operator awareness, not gating.SVX_BINARIES == SVX_ACTIONS. Keep them as separate constants for shape consistency with siblings; five bytes for readability.Critical Files for Implementation
tools/modules/services/service_office.nu(new)tools/modules/services/mod.nu(one-line append)tools/modules/services/service_db.nu(install shape + summary template)tools/modules/services/service_aibroker.nu(conditional-env reference)tools/modules/services/service_books.nu(soft-dep warning shape)tools/modules/services/lib.nu(svc_bins_oketc.)Implementation summary
Changes
tools/modules/services/service_office.nu— ~400 lines.tools/modules/services/mod.nu— appendedexport use service_office.nu.End-to-end smoke test on Hetzner (
--root)Smoke ran with
hero_foundryabsent — the degraded-mode scenario, which is the more demanding branch because it exercises the non-fatal preflight AND the lazy-open server path.service_office status --rootwith hero_proc down → actionable error pointing toservice_proc start --rootservice_office stop --rootwith hero_proc down → benign warning, no errorservice_proc start --roothealthyservice_office install --root(first) produced 2 binaries in/root/hero/bin/via cargo buildinstallwith no flags →→ hero_office binaries already in place — skipping build(svc_bins_okshort-circuit)service_office start --reset --rootwith foundry absent → 6-line soft-dep warning fires, service still reaches runningrpc.sockpresentui.sockpresentcurl --unix-socket rpc.sock /health→ HTTP 200,{"status":"healthy","version":"0.1.0"}curl --unix-socket rpc.sock /openrpc.json→ HTTP 200, OpenRPC 1.3.2, 6 methodscurl --unix-socket ui.sock /→ HTTP 307 redirect to/admin/curl --unix-socket ui.sock /admin/→ HTTP 200, Hero Office Admin HTMLstatus→{name: hero_office, state: running, restarts: 0, pid: 1061844, current_run_id: 20}start(no--reset) prints "already running" with hintcurrent_run_idstable at 20,restarts: 0, staterunning(3 samples)start --reset --rootwhile running — both sockets reclaimed;rest.sock /healthreturns HTTP 200 after restartservice_office stop --root→✓ hero_office stopped and unregisteredstatusreturns expectedservice 'hero_office' not foundhero_office_server/hero_office_uiprocesses; socket directory emptyNotes
running+ both health checks pass (rpc.sock/healthHTTP 200, ui.sock/admin/HTTP 200). This is exactly the branch the in-reposcripts/nu_service.nudid not cover.svc_bins_okshort-circuit validated: secondinstallreturns in <1 s with the canonical one-line skip message;start --resetstill triggers a rebuild pass via the innerinstall --reset=$resetcall.ONLYOFFICE_JWT_SECRET/OO_SERVER_URL/CONNECTOR_EXTERNAL_URLset, so the action env contains onlyRUST_LOG— server logs show it resolving defaults cleanly, no OO-related JS errors in the /admin/ page.hero_whiteboard,hero_officebinaries do clean up their sockets on SIGTERM.hero_zero/services/hero_office.tomlreferencing a non-existenthero_officeCLI, no[ui]block) remains — out of scope per spec, same treatment as aibroker's mis-setsourcefield.Acceptance criteria (from #97)
service_office.nuexportsinstall,start,stop,status.installaccepts--root,--update,--reset; short-circuits viasvc_bins_ok.startis idempotent, callssvx_check_foundrybetween install-verify and drop-registration, prints the rich summary.RUST_LOGbase + conditionally-included env vars.mod.nuexports the new module.--root.hero_zero/services/hero_office.tomlis NOT modified.lib.nu.PR opened: #98