service_foundry.nu — hero_foundry server + UI lifecycle module #94
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#94
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_foundry.nuimplementinginstall | start | stop | statusfor the hero_foundry service (version control + repo browser).Scope — with a known ambiguity to resolve in spec
There are two separate repos both relevant to "foundry":
lhumina_code/hero_foundry—buildenv.shsaysBINARIES="hero_foundry hero_foundry_server hero_foundry_ui". Thehero_foundry.tomlin hero_zero declares BOTH[server](binaryhero_foundry_server) and[ui](binaryhero_foundry_ui start) pointing at this repo. This is the canonical "foundry" stack.lhumina_code/hero_foundry_ui— separate repo withBINARIES="hero_foundry hero_foundry_ui_server hero_foundry_ui". Has its ownhero_foundry_ui.tomlin hero_zero that defines a standalone service withexec = sh -c 'cd /root/hero/share/hero_foundry_ui && exec __HERO_BIN__/hero_foundry_ui'.Both buildenv.sh files set
PROJECT_NAME="hero_foundry"— they collide on binary namehero_foundry_ui.Decision to lock in the spec (Plan agent should probe both repos'
main.rsand pick):service_foundry.nucovering hero_foundry only (server + UI both from that repo, matchinghero_foundry.toml). Skip hero_foundry_ui.toml / repo entirely; someone else handles that service later.service_foundry_ui.nu(separate PR, separate sub-issue) covers the standalone repo.My recommendation: Option A for this PR — tightest scope, matches the
hero_foundry.tomlwhich is the more complete service definition. Thehero_foundry_uirepo and its TOML look like an alternate/legacy config; needs an owner decision before we invest.Scope (assuming Option A)
ssh://git@forge.ourworld.tf/lhumina_code/hero_foundry.githero_foundry/buildenv.sh):hero_foundry,hero_foundry_server,hero_foundry_uihero_foundry_server,hero_foundry_uihero_zero/services/hero_foundry.tomlhero_foundry_server --allow-dir __HERO_VAR__/hero_foundry/repos --webdav-storage __HERO_VAR__/hero_foundry/webdavhero_foundry_ui start[env], applies to both):RUST_LOG=infoHERO_FOUNDRY_REPOS=__HERO_VAR__/hero_foundry/reposHERO_FOUNDRY_BASE_PATH=/hero_foundry/ui$HERO_SOCKET_DIR/hero_foundry/{rpc,ui}.sock(spec should confirm via main.rs).Acceptance criteria
use services/mod.nu *makesservice_foundryavailable.service_foundry install [--root] [--update] [--reset]clones + builds all 3 binaries.service_foundry start [--reset] [--root] [--update]registers both actions + service; passes the--allow-dir/--webdav-storageflags to the server action; invokeshero_foundry_ui startfor the UI action.RUST_LOG,HERO_FOUNDRY_REPOS,HERO_FOUNDRY_BASE_PATHwith__HERO_VAR__resolved at register time viasvc_home $root.service_foundry status [--root]reports state.service_foundry stop [--root]cleanly unregisters.packages.nuservices_extra.Template
service_voice.nu(most recent merged, uses the latest--reset/--updatepatterns andsvc_bins_okshort-circuit).serve/startsubcommand pattern:service_books.nu(usesscript: $"($bin) serve").__HERO_VAR__/ env-var resolution at register time:service_books.nu'sHERO_BOOKS_DATA+HERO_EMBEDDER_URLpattern.Questions the spec must answer
hero_foundry_ui start— confirm viamain.rsthat it requiresstart(notserve).--allow-dirand--webdav-storageinscript:with resolved paths, or set them via env?crates/*/src/main.rs.Implementation Spec for Issue #94
Objective
Add
tools/modules/services/service_foundry.nu— aninstall | start | stop | statuslifecycle module for thehero_foundryservice (server + UI) that registers both actions withhero_procand mirrors the authoritative Rustbuild_service_definition()athero_foundry/crates/hero_foundry/src/main.rs:102-181.Requirements
lhumina_code/hero_foundryrepo only. Option A confirmed — the separatelhumina_code/hero_foundry_uirepo is a distinct Actix-based service with its own store/issues/wiki, out of scope for this sub-issue (future sub-issue after owner decision).hero_foundry,hero_foundry_server,hero_foundry_ui(perhero_foundry/buildenv.sh:6).hero_foundry_server,hero_foundry_ui.hero_foundryCLI is installed but NOT registered.--allow-dirand--webdav-storageflags with paths resolved at register time (see Notes — env-var fallback not viable because the server's clap parser doesn't declare env attributes for those flags).hero_foundry_ui startliterally as declared inhero_zero/services/hero_foundry.toml:12. UI binary has no clap parser sostartis a harmless no-op token, but kept for TOML parity.--root,--update,--resetflags oninstallandstart, matching theservice_voice.nucontract.Files to Modify/Create
tools/modules/services/service_foundry.nu(new, ~210 lines, modeled onservice_voice.nuwithservice_biz.nu-style inline-script flags).tools/modules/services/mod.nu(+1 line:export use service_foundry.nuappended afterservice_voice.nu).tools/modules/services/packages.nu(+1use service_foundry.nuat top, +1 entry inservices_extraafter theservice_voiceentry).Implementation Plan
Step 1: Copy baseline from
service_voice.nuFiles:
tools/modules/services/service_foundry.nuservice_voice.nu:1-203. Rename everyhero_voice→hero_foundry, everyservice_voice→service_foundry.SVX_SERVICE_NAME = "hero_foundry"SVX_FORGE_LOC = "lhumina_code/hero_foundry"SVX_BINARIES = ["hero_foundry" "hero_foundry_server" "hero_foundry_ui"]SVX_ACTIONS = ["hero_foundry_server" "hero_foundry_ui"]hero_foundry/buildenv.sh:6; action names fromhero_foundry/crates/hero_foundry/src/main.rs:105(server) andmain.rs:142(ui).Dependencies: none.
Step 2: Write
svx_server_actionwith inline flags + envFiles:
tools/modules/services/service_foundry.nuModel on
service_voice.nu:11-50+service_biz.nu:81-126inline-script pattern. Resolve paths at register time viasvc_home $root:name: "hero_foundry_server"(confirmedmain.rs:105).interpreter: "exec"— required whenscriptcontains spaces/flags.env: {RUST_LOG: "info", HERO_FOUNDRY_REPOS: $data_repos, HERO_FOUNDRY_BASE_PATH: "/hero_foundry/ui"}.HERO_FOUNDRY_BASE_PATHis consumed athero_foundry_server/src/main.rs:155(baked intoServerConfig.base_path).HERO_FOUNDRY_REPOSis cosmetic/TOML-parity (only read byhero_foundry_examples/examples/seed_data.rs:18).retry_policy:max_attempts: 5, delay_ms: 2000, backoff: true, max_delay_ms: 60000, start_timeout_ms: 30000, stability_period_ms: 30000(Rust sourcemain.rs:110-116+ nu-convention completion).stop_signal: "SIGTERM", stop_timeout_ms: 10000, timeout_ms: 0, tty: false(main.rs:107-108).kill_other.socket: [$"($sock_base)/hero_foundry/rpc.sock"](confirmedhero_foundry_server/src/main.rs:199).health_checks[0]:action: "hero_foundry_server",openrpc_socket: $"($sock_base)/hero_foundry/rpc.sock", policyinterval_ms: 2000, timeout_ms: 5000, retries: 3, start_period_ms: 3000(main.rs:133-138).Dependencies: Step 1.
Step 3: Write
svx_ui_actionwithstartsubcommandFiles:
tools/modules/services/service_foundry.nuModel on
service_voice.nu:52-91+service_books.nu:118-124subcommand shape:name: "hero_foundry_ui",interpreter: "exec",env: {RUST_LOG: "info"}.HERO_FOUNDRY*inhero_foundry/crates/hero_foundry_ui/). Base-path prefixing uses theX-Forwarded-PrefixHTTP middleware (hero_foundry_ui/src/main.rs:58-71), not env.retry_policy.max_attempts: 3(main.rs:148) + nu-convention completions.stop_timeout_ms: 5000.kill_other.socket: [$"($sock_base)/hero_foundry/ui.sock"](confirmedhero_foundry_ui/src/main.rs:128).health_checks[0]:interval_ms: 3000, timeout_ms: 5000, retries: 3, start_period_ms: 5000(main.rs:168-172).Dependencies: Step 1.
Step 4:
svx_service_configFiles:
tools/modules/services/service_foundry.nuIdentical to
service_voice.nu:93-105with:description: "Hero Foundry — version control and code repository system"(fromhero_zero/services/hero_foundry.toml:3).Dependencies: Step 1.
Step 5:
svx_drop_registrationFiles:
tools/modules/services/service_foundry.nuIdentical to
service_voice.nu:107-113.Dependencies: Step 1.
Step 6:
installFiles:
tools/modules/services/service_foundry.nuIdentical to
service_voice.nu:115-127. No workspace pre-build needed —hero_foundry/Cargo.tomlis a virtual workspace,svc_cargo_installhandles all three binaries directly (unlikehero_bookswhich has a hybrid workspace).Dependencies: Step 1.
Step 7:
startFiles:
tools/modules/services/service_foundry.nuModel on
service_voice.nu:129-180. Order:svc_require_sudo(if--root) →svc_require_proc "service_foundry"→ early-exit if already running (unless--reset/--update) →install→ binary existence check onhero_foundry_server→svx_drop_registration→ register server action → register UI action → register service → start → settle → summary.Summary block prints rpc/ui sockets + the data directory paths (
repos,webdav) so operators see where repos are expected to land:No preflight for external sockets — TOML declares no dependencies.
Dependencies: Steps 2-6.
Step 8:
stopandstatusFiles:
tools/modules/services/service_foundry.nuIdentical to
service_voice.nu:182-202.Dependencies: Step 5.
Step 9: Module header docstring
Files:
tools/modules/services/service_foundry.nu30-40 line preamble in the style of
service_biz.nu:1-60. Must explicitly document:hero_foundryvshero_foundry_ui); this module covers onlyhero_foundry. Future sub-issue for the separate Actix-based repo.hero_foundry/crates/hero_foundry/src/main.rs:102-181.--allow-dir+--webdav-storageinline because the clap parser does not declare[env: ...]fallbacks.starttoken is decorative (no clap parser in the UI binary); kept for TOML parity.HERO_FOUNDRY_REPOSis cosmetic — only read byhero_foundry_examples; kept for operator expectations.HERO_FOUNDRY_BASE_PATHis server-side (not UI) — the server uses it to render prefixed links.Dependencies: Steps 1-8.
Step 10: Wire into
mod.nuFiles:
tools/modules/services/mod.nuAppend
export use service_foundry.nuat the end (afterservice_voice.nu). Preserves newest-at-end convention.Dependencies: Step 1.
Step 11: Wire into
packages.nuFiles:
tools/modules/services/packages.nuuse service_foundry.nuin theuseblock at the top, after the existinguse service_voice.nu.services_extraafter theservice_voiceentry:Dependencies: Step 1.
Step 12: Syntax check
nu -c "source tools/modules/services/service_foundry.nu; print parse-ok".nu -c "source tools/modules/services/packages.nu; print parse-ok".nu -c "use tools/modules/services/mod.nu *; scope commands | where name =~ '^service_foundry '"— expect 4 subcommands.Dependencies: Steps 1-11.
Step 13: Smoke test on Hetzner
Run under
flockto serialize with the other agent.hero_procmust be up first.service_foundry install --root— clone, build 3 binaries, copy to/root/hero/bin/.service_foundry install --rootagain — expectsvc_bins_okshort-circuit.service_foundry start --reset --root— both actions registered, servicerunning.ls /root/hero/var/sockets/hero_foundry/—rpc.sock+ui.sockpresent.curl --unix-socket rpc.sock http://localhost/— HTTP response.curl --unix-socket ui.sock http://localhost/— HTTP response.service_foundry status --root— record shows running, 0 restarts.proc action get hero_foundry_server --root— verify env hasRUST_LOG,HERO_FOUNDRY_REPOS,HERO_FOUNDRY_BASE_PATH.proc action get hero_foundry_ui --root— verify env has onlyRUST_LOG.ls /root/hero/var/hero_foundry/—repos/andwebdav/auto-created by server.service_foundry start --root— idempotent "already running".service_foundry stop --root— clean unregister.service_foundry status --root—service 'hero_foundry' not found.Dependencies: Steps 1-12.
Acceptance Criteria
use services/mod.nu *makesservice_foundryavailable with 4 subcommands.service_foundry install [--root] [--update] [--reset]clones + builds 3 binaries viasvc_cargo_install;--resetforces rebuild, elsesvc_bins_okshort-circuits.service_foundry start [--reset] [--root] [--update]registers both actions + service; serverscript:contains--allow-dir <svc_home>/var/hero_foundry/repos --webdav-storage <svc_home>/var/hero_foundry/webdav; UIscript:contains<bin>/hero_foundry_ui start.RUST_LOG=info,HERO_FOUNDRY_REPOS=<svc_home>/var/hero_foundry/repos,HERO_FOUNDRY_BASE_PATH=/hero_foundry/ui.RUST_LOG=infoonly.kill_other.socketon server =<sock_base>/hero_foundry/rpc.sock; on UI =<sock_base>/hero_foundry/ui.sock.service_foundry status [--root]delegates toproc service status hero_foundry.service_foundry stop [--root]cleanly unregisters.lib.nunot modified.mod.nuhasexport use service_foundry.nuappended.packages.nuhasuse service_foundry.nuin imports AND an entry inservices_extrawith the--reset/--updatepassthrough pattern.Notes
Option A/B/C — A confirmed. Read both
main.rs:hero_foundry/crates/hero_foundry_ui/src/main.rs(649 lines): Axum,rust_embedstatic assets, askama templates,FoundryClientSDK proxying to server overrpc.sock. Pure read-only repo browser.hero_foundry_ui/crates/hero_foundry_ui/src/main.rs(2111 lines): Actix,actix_session, opens.forge/.heroforgefiles directly, ownFoundryStorewith issues/projects/wiki/PRs, readsREPO_PATHenv, org-based routing/{org}/{repo}/....These are distinct services with non-overlapping feature sets. Option A covers hero_foundry only;
lhumina_code/hero_foundry_uineeds its own sub-issue after an owner decision.UI subcommand verification.
hero_foundry/crates/hero_foundry_ui/src/main.rs:86-89:No clap, no argv parsing —
mainignores all arguments.startis decorative; kept for TOML parity.Flags vs env on server action. Server clap at
hero_foundry/crates/hero_foundry_server/src/main.rs:46-74exposes--allow-dir,--webdav-storage,--repo,--read-write,--max-cache,--bind,--default-repo. None declare[env: ...]fallbacks.--allow-dirand--webdav-storageMUST be CLI args — inlinescript:is the only option (same pattern asservice_biz.nu).Env var placement.
HERO_FOUNDRY_BASE_PATH→ server. Read athero_foundry_server/src/main.rs:155, used byServerConfig.base_pathto prefix links in rendered responses.HERO_FOUNDRY_REPOS→ server (cosmetic). Grep acrosshero_foundry/crates/finds only one read, inhero_foundry_examples/examples/seed_data.rs:18. Keeping it on the server action matches the TOML[env]block and operator expectations.RUST_LOG→ both.X-Forwarded-PrefixHTTP header.Socket paths verified.
$HERO_SOCKET_DIR/hero_foundry/rpc.sock(fromsocket_path("rpc")athero_foundry_server/src/main.rs:199).$HERO_SOCKET_DIR/hero_foundry/ui.sock(fromsocket_path("ui")athero_foundry_ui/src/main.rs:128).hero_foundry/crates/hero_foundry_ui_server/src/main.rs:35binds a different rpc path — this binary is an SDK shim, NOT inSVX_BINARIES, NOT registered.Workspace layout.
hero_foundry/Cargo.tomlis a virtual workspace. All three binaries reachable throughsvc_cargo_installdirectly — no hybrid pre-build likeservice_books.nuneeds.Data directories. Server auto-creates
--webdav-storagepath viastd::fs::create_dir_allathero_foundry_server/src/main.rs:121. The--allow-dirpath for repos must exist or be creatable; server errors out if not.Critical Files for Implementation
/Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/service_voice.nu/Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/service_biz.nu/Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/service_books.nu/Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_skills/tools/modules/services/packages.nu/Users/mahmoud/code/forge.ourworld.tf/lhumina_code/hero_foundry/crates/hero_foundry/src/main.rsImplementation summary
Changes
tools/modules/services/service_foundry.nu— ~280 lines, combines patterns fromservice_voice.nu(baseline),service_biz.nu(inline script flags), andservice_books.nu(env-at-register-time paths).tools/modules/services/mod.nu— appendedexport use service_foundry.nu.tools/modules/services/packages.nu— addeduse service_foundry.nuand entry inservices_extrawith--reset/--updatepassthrough.End-to-end smoke test on Hetzner
Ran under
flockon/tmp/hero_skills_smoke.lock. 23 of 24 assertions PASS. The lone FAIL is a wrong test assumption, not a module bug.service_proc start --rootupservice_foundry install --rootbuilt 3 binariesservice_foundry install --rootagain →svc_bins_okshort-circuitservice_foundry start --reset --rootregisters + startsrpc.sockis a live unix socketui.sockis a live unix socketcurl --unix-socket rpc.sockaccepts HTTPcurl --unix-socket ui.sockaccepts HTTPstatusreturns{name: hero_foundry, state: running}HERO_FOUNDRY_REPOS+HERO_FOUNDRY_BASE_PATH+RUST_LOGscript:contains--allow-dirand--webdav-storageRUST_LOG; UI script hasstarttokenstate: runningheld/root/hero/var/hero_foundry/webdavdir auto-created/root/hero/var/hero_foundry/reposdir auto-createdservice_foundry stop --rootcleanly unregistersstatusreturnsservice 'hero_foundry' not foundNote on the 2o "fail"
Server source (
hero_foundry_server/src/main.rs:121) callsstd::fs::create_dir_allonly on the--webdav-storagepath. It does NOT create the--allow-dirpath — that flag is treated as an existing access-control root. The server runs happily without that directory existing (hencestate: running), but any actual repo operations against it would need the dir to be present.That's consistent with how
--allow-diris documented in the server's clap help ("directory tree to allow access into"). The module is correct; the test just had the wrong expectation. Operators bootstrapping hero_foundry need tomkdir -p <svc_home>/var/hero_foundry/repos(or the start summary could be extended to create it — but that's an opinion, and the server doesn't complain about absence today).Acceptance criteria
use services/mod.nu *exposes 4 subcommands.service_foundry installbuilds 3 binaries (hero_foundry, hero_foundry_server, hero_foundry_ui); idempotent short-circuit viasvc_bins_ok.service_foundry startregisters both actions + service; serverscript:contains--allow-dir <path>and--webdav-storage <path>with paths resolved viasvc_home $root; UIscript:contains<bin>/hero_foundry_ui start.RUST_LOG=info,HERO_FOUNDRY_REPOS=<svc_home>/var/hero_foundry/repos,HERO_FOUNDRY_BASE_PATH=/hero_foundry/ui.RUST_LOG=infoonly.kill_other.socketon server =<sock_base>/hero_foundry/rpc.sock; on UI =<sock_base>/hero_foundry/ui.sock.statusdelegates toproc service status hero_foundry.stopcleanly unregisters.lib.nuunchanged.mod.nure-exportsservice_foundry.packages.nuhasuse service_foundry.nuandservices_extraentry with--reset/--updatepassthrough.Out of scope (flagged in header)
The sibling
lhumina_code/hero_foundry_uirepo is a distinct Actix-based service (own FoundryStore, issues/wiki/PRs, org-scoped routing, readsREPO_PATH). It needs its own sub-issue after an owner decision on whether it's still supported. This module coverslhumina_code/hero_foundryonly.PR opened: #95