hero_foundry_server HTTP files API returns 503 under default hero_foundry --start registration #17

Closed
opened 2026-04-26 13:48:38 +00:00 by nabil_salah · 3 comments
Member

When hero_foundry_server is started via the hero_foundry CLI (hero_foundry --start, which registers the service with hero_proc), the HTTP files API at /api/files/* returns 503 Service Unavailable with "Storage not configured". This breaks any client that talks to that endpoint (e.g. the Office integration).

Reproduction
make stop && make run on development (pre-fix)
curl -i http:///api/files/ → 503 Service Unavailable / "Storage not configured"

Verification (post-fix)
make stop && make run
Confirm ~/hero/foundry-storage/ is created
curl -i http:///api/files/... no longer 503s
Office (or whichever client surfaced the bug) can read/write through /api/files/*

FIX

when starting the _server
check there is a default fossil repo, put in ~/hero/var/fossil/core.fossil

if not create one which will be an empty repo
put a readme in to stay this is the starting point

so basically we get a default repo

When hero_foundry_server is started via the hero_foundry CLI (hero_foundry --start, which registers the service with hero_proc), the HTTP files API at /api/files/* returns 503 Service Unavailable with "Storage not configured". This breaks any client that talks to that endpoint (e.g. the Office integration). Reproduction make stop && make run on development (pre-fix) curl -i http://<server>/api/files/<anything> → 503 Service Unavailable / "Storage not configured" Verification (post-fix) make stop && make run Confirm ~/hero/foundry-storage/ is created curl -i http://<server>/api/files/... no longer 503s Office (or whichever client surfaced the bug) can read/write through /api/files/* ## FIX when starting the _server check there is a default fossil repo, put in ~/hero/var/fossil/core.fossil if not create one which will be an empty repo put a readme in to stay this is the starting point so basically we get a default repo
Owner

Implementation Spec — Issue #17: Default fossil repo on server start

Objective

Ensure that whenever hero_foundry_server starts (in particular under the default hero_foundry --start registration path), a default fossil repository file exists at ~/hero/var/fossil/core.fossil. If it already exists, leave it alone. If it does not exist, create:

  1. The parent directory ~/hero/var/fossil/ (and any missing ancestors).
  2. A new, empty fossil repository file core.fossil at that path, using hero_foundry_core::Repository::init (which produces a SQLite database with the fossil schema).
  3. A README.md file inside the fossil repo as the initial commit on trunk, stating that this is the seeded default repository.

This complements PR #18 (which already added the WebDAV storage directory). Issue #17 explicitly asks for an additional default fossil repo so that any client expecting a default repository can find one.

Requirements

  • Idempotent: subsequent --start invocations must not modify or overwrite an existing core.fossil. They must not error if the parent directory or the file already exist.
  • Non-fatal: if seeding fails for any reason (permission denied, unexpected file at the path, unreadable HOME, etc.) the CLI must log a warning to stderr but not abort hero_foundry --start — the rest of the registration with hero_proc must still proceed.
  • Path discipline: honor $HOME exactly the way the existing CLI does (std::env::var("HOME").unwrap_or_default()), to keep behaviour aligned with default_dir and storage_dir.
  • No new external dependencies: do not invoke an external fossil binary. The repo is created in-process via hero_foundry_core::Repository::init, which is already what the rest of this codebase uses.
  • README placement: commit a README.md into the new repo as its first check-in, on the trunk branch.

Files to modify / create

Modify: crates/hero_foundry/src/main.rs

Add a new helper function ensure_default_fossil_repo() and call it from self_start() before restart_service. The helper uses hero_foundry_core::Repository to do the work.

Modify: crates/hero_foundry/Cargo.toml

Add a path dependency on hero_foundry_core.

Do NOT modify

  • crates/hero_foundry_server/src/main.rs — server binary should remain agnostic about the default repo location. The CLI is the orchestrator.
  • crates/hero_foundry_core/*Repository::init already does everything required.

Step-by-step implementation plan

Step 1 — Add hero_foundry_core dependency to the CLI crate

File: crates/hero_foundry/Cargo.toml

Under [dependencies], add:

hero_foundry_core = { path = "../hero_foundry_core" }

Step 2 — Add the helper to main.rs

File: crates/hero_foundry/src/main.rs

Add a top-level function ensure_default_fossil_repo() that:

  • Resolves ~/hero/var/fossil/core.fossil from $HOME.
  • Returns early if the file already exists (idempotent).
  • Creates the parent directory recursively if missing.
  • Initializes the repo via hero_foundry_core::Repository::init.
  • Commits a README.md as the initial check-in on trunk using the commit builder with .initial() (author hero_foundry).
  • Logs all errors to stderr but never propagates or panics.

Step 3 — Call helper from self_start()

File: crates/hero_foundry/src/main.rs

Inside fn self_start(), invoke ensure_default_fossil_repo() before restart_service runs.

Step 4 — Verify locally

  • cargo build -p hero_foundry — must succeed.
  • cargo build -p hero_foundry_server — must still succeed.
  • Manual smoke: make stop && rm -rf ~/hero/var/fossil && make run, then check ls ~/hero/var/fossil/core.fossil exists.
  • Re-run make stop && make runcore.fossil mtime/contents must be unchanged.

Acceptance criteria

  • crates/hero_foundry/Cargo.toml declares hero_foundry_core as a path dependency.
  • crates/hero_foundry/src/main.rs defines ensure_default_fossil_repo() with idempotent behaviour and non-fatal error handling.
  • self_start() calls ensure_default_fossil_repo() exactly once, before restart_service.
  • Running hero_foundry --start on a clean machine creates ~/hero/var/fossil/core.fossil.
  • Running hero_foundry --start twice is a no-op for the file.
  • If $HOME is empty/unset, the CLI logs a warning and continues without aborting.
  • Workspace build (cargo build) succeeds.
  • No new external runtime dependencies (e.g., the fossil C binary) are introduced.

Notes / decisions

  1. Why not call the external fossil binary? It would add an undeclared runtime dependency that isn't currently required anywhere else in this Rust workspace. Every other place creates repos via Repository::init. The pure-Rust path is already exercised by hero_foundry_demo and the unit tests in hero_foundry_core.

  2. README placement — committed file vs. sibling. A .fossil file is a SQLite database, not a directory, so a README cannot live "inside" it on disk. The chosen approach commits the README as the first check-in inside the repo (visible via /api/files/.../README.md), matching exactly how hero_foundry_demo seeds hero-app.forge. The alternative of dropping a sibling README.md was rejected because it would not be reachable via /api/files/*, which is the exact endpoint the bug report exercises.

  3. Why seed in the CLI rather than in the server. The CLI is already the orchestration boundary that owns "what files/dirs must exist before the server runs" (see PR #18, where default_dir and storage_dir are also created). Keeping all such side-effects in the CLI keeps the server binary side-effect-free for tests and ad-hoc invocations.

  4. Idempotency under concurrent starts. The helper does an exists() check followed by create_dir_all and Repository::init. hero_proc itself serializes service registration; Repository::init would refuse to re-init a populated database. Acceptable for this CLI-driven one-shot.

  5. .fossil vs .forge extension. The rest of this codebase generally uses .forge, but the issue explicitly names core.fossil. We honor the issue. Both extensions are just naming conventions — the on-disk format is identical.

# Implementation Spec — Issue #17: Default fossil repo on server start ## Objective Ensure that whenever `hero_foundry_server` starts (in particular under the default `hero_foundry --start` registration path), a default fossil repository file exists at `~/hero/var/fossil/core.fossil`. If it already exists, leave it alone. If it does not exist, create: 1. The parent directory `~/hero/var/fossil/` (and any missing ancestors). 2. A new, empty fossil repository file `core.fossil` at that path, using `hero_foundry_core::Repository::init` (which produces a SQLite database with the fossil schema). 3. A `README.md` file *inside* the fossil repo as the initial commit on `trunk`, stating that this is the seeded default repository. This complements PR #18 (which already added the WebDAV storage directory). Issue #17 explicitly asks for an additional default *fossil repo* so that any client expecting a default repository can find one. ## Requirements - **Idempotent**: subsequent `--start` invocations must not modify or overwrite an existing `core.fossil`. They must not error if the parent directory or the file already exist. - **Non-fatal**: if seeding fails for any reason (permission denied, unexpected file at the path, unreadable HOME, etc.) the CLI must log a warning to stderr but **not** abort `hero_foundry --start` — the rest of the registration with `hero_proc` must still proceed. - **Path discipline**: honor `$HOME` exactly the way the existing CLI does (`std::env::var("HOME").unwrap_or_default()`), to keep behaviour aligned with `default_dir` and `storage_dir`. - **No new external dependencies**: do not invoke an external `fossil` binary. The repo is created in-process via `hero_foundry_core::Repository::init`, which is already what the rest of this codebase uses. - **README placement**: commit a `README.md` *into* the new repo as its first check-in, on the `trunk` branch. ## Files to modify / create ### Modify: `crates/hero_foundry/src/main.rs` Add a new helper function `ensure_default_fossil_repo()` and call it from `self_start()` before `restart_service`. The helper uses `hero_foundry_core::Repository` to do the work. ### Modify: `crates/hero_foundry/Cargo.toml` Add a path dependency on `hero_foundry_core`. ### Do NOT modify - `crates/hero_foundry_server/src/main.rs` — server binary should remain agnostic about the default repo location. The CLI is the orchestrator. - `crates/hero_foundry_core/*` — `Repository::init` already does everything required. ## Step-by-step implementation plan ### Step 1 — Add `hero_foundry_core` dependency to the CLI crate **File**: `crates/hero_foundry/Cargo.toml` Under `[dependencies]`, add: ```toml hero_foundry_core = { path = "../hero_foundry_core" } ``` ### Step 2 — Add the helper to `main.rs` **File**: `crates/hero_foundry/src/main.rs` Add a top-level function `ensure_default_fossil_repo()` that: - Resolves `~/hero/var/fossil/core.fossil` from `$HOME`. - Returns early if the file already exists (idempotent). - Creates the parent directory recursively if missing. - Initializes the repo via `hero_foundry_core::Repository::init`. - Commits a `README.md` as the initial check-in on `trunk` using the commit builder with `.initial()` (author `hero_foundry`). - Logs all errors to stderr but never propagates or panics. ### Step 3 — Call helper from `self_start()` **File**: `crates/hero_foundry/src/main.rs` Inside `fn self_start()`, invoke `ensure_default_fossil_repo()` before `restart_service` runs. ### Step 4 — Verify locally - `cargo build -p hero_foundry` — must succeed. - `cargo build -p hero_foundry_server` — must still succeed. - Manual smoke: `make stop && rm -rf ~/hero/var/fossil && make run`, then check `ls ~/hero/var/fossil/core.fossil` exists. - Re-run `make stop && make run` — `core.fossil` mtime/contents must be unchanged. ## Acceptance criteria - [ ] `crates/hero_foundry/Cargo.toml` declares `hero_foundry_core` as a path dependency. - [ ] `crates/hero_foundry/src/main.rs` defines `ensure_default_fossil_repo()` with idempotent behaviour and non-fatal error handling. - [ ] `self_start()` calls `ensure_default_fossil_repo()` exactly once, before `restart_service`. - [ ] Running `hero_foundry --start` on a clean machine creates `~/hero/var/fossil/core.fossil`. - [ ] Running `hero_foundry --start` twice is a no-op for the file. - [ ] If `$HOME` is empty/unset, the CLI logs a warning and continues without aborting. - [ ] Workspace build (`cargo build`) succeeds. - [ ] No new external runtime dependencies (e.g., the `fossil` C binary) are introduced. ## Notes / decisions 1. **Why not call the external `fossil` binary?** It would add an undeclared runtime dependency that isn't currently required anywhere else in this Rust workspace. Every other place creates repos via `Repository::init`. The pure-Rust path is already exercised by `hero_foundry_demo` and the unit tests in `hero_foundry_core`. 2. **README placement — committed file vs. sibling.** A `.fossil` file is a SQLite database, not a directory, so a README cannot live "inside" it on disk. The chosen approach commits the README as the first check-in inside the repo (visible via `/api/files/.../README.md`), matching exactly how `hero_foundry_demo` seeds `hero-app.forge`. The alternative of dropping a sibling `README.md` was rejected because it would not be reachable via `/api/files/*`, which is the exact endpoint the bug report exercises. 3. **Why seed in the CLI rather than in the server.** The CLI is already the orchestration boundary that owns "what files/dirs must exist before the server runs" (see PR #18, where `default_dir` and `storage_dir` are also created). Keeping all such side-effects in the CLI keeps the server binary side-effect-free for tests and ad-hoc invocations. 4. **Idempotency under concurrent starts.** The helper does an `exists()` check followed by `create_dir_all` and `Repository::init`. `hero_proc` itself serializes service registration; `Repository::init` would refuse to re-init a populated database. Acceptable for this CLI-driven one-shot. 5. **`.fossil` vs `.forge` extension.** The rest of this codebase generally uses `.forge`, but the issue explicitly names `core.fossil`. We honor the issue. Both extensions are just naming conventions — the on-disk format is identical.
Owner

Test Results

  • Workspace: hero_foundry (cargo test --workspace --no-fail-fast)
  • Total: 179
  • Passed: 179
  • Failed: 0

All tests passed.

## Test Results - Workspace: hero_foundry (cargo test --workspace --no-fail-fast) - Total: 179 - Passed: 179 - Failed: 0 All tests passed.
Owner

Implementation Summary

Changes

  • crates/hero_foundry/Cargo.toml — added hero_foundry_core path dependency.
  • crates/hero_foundry/src/main.rs — added top-level helper ensure_default_fossil_repo() and a single call to it from self_start() before restart_service.

Behaviour

On hero_foundry --start, the CLI now ensures that ~/hero/var/fossil/core.fossil exists before registering the server with hero_proc:

  • If $HOME is unset, a warning is logged to stderr and the seeding step is skipped (the rest of --start proceeds normally).
  • If core.fossil already exists, the function is a no-op (idempotent across repeat starts).
  • If it does not exist, the parent directory ~/hero/var/fossil/ is created (recursively), the repo is initialised in-process via hero_foundry_core::Repository::init, and a README.md is committed as the initial check-in on trunk with author hero_foundry.
  • All error paths log to stderr and return without aborting --start, so a permission error or a stray file at the seed path will never block service registration.

Implementation notes

  • No new external runtime dependencies (no fossil C binary). The repo is created via the same hero_foundry_core::Repository::init code path used by hero_foundry_demo.
  • The README is committed inside the repo (reachable at /api/files/.../README.md), not dropped as a sibling file. This makes the seed visible through the same /api/files/* endpoint that the original bug report exercised.
  • The seed is intentionally placed in the CLI rather than the server binary, matching how PR #18 handled the default_dir and WebDAV storage_dir setup. The server binary remains side-effect-free for direct/test use.

Test results

cargo test --workspace --no-fail-fast — 179 passed, 0 failed, 2 ignored. Both cargo build -p hero_foundry and cargo build -p hero_foundry_server succeed.

  1. make stop && rm -rf ~/hero/var/fossil && make run
  2. ls ~/hero/var/fossil/core.fossil — file exists.
  3. curl -i http://<server>/api/files/... — no longer returns 503.
  4. make stop && make run again — core.fossil mtime/contents unchanged on the second run.
## Implementation Summary ### Changes - `crates/hero_foundry/Cargo.toml` — added `hero_foundry_core` path dependency. - `crates/hero_foundry/src/main.rs` — added top-level helper `ensure_default_fossil_repo()` and a single call to it from `self_start()` before `restart_service`. ### Behaviour On `hero_foundry --start`, the CLI now ensures that `~/hero/var/fossil/core.fossil` exists before registering the server with hero_proc: - If `$HOME` is unset, a warning is logged to stderr and the seeding step is skipped (the rest of `--start` proceeds normally). - If `core.fossil` already exists, the function is a no-op (idempotent across repeat starts). - If it does not exist, the parent directory `~/hero/var/fossil/` is created (recursively), the repo is initialised in-process via `hero_foundry_core::Repository::init`, and a `README.md` is committed as the initial check-in on `trunk` with author `hero_foundry`. - All error paths log to stderr and return without aborting `--start`, so a permission error or a stray file at the seed path will never block service registration. ### Implementation notes - No new external runtime dependencies (no `fossil` C binary). The repo is created via the same `hero_foundry_core::Repository::init` code path used by `hero_foundry_demo`. - The README is committed inside the repo (reachable at `/api/files/.../README.md`), not dropped as a sibling file. This makes the seed visible through the same `/api/files/*` endpoint that the original bug report exercised. - The seed is intentionally placed in the CLI rather than the server binary, matching how PR #18 handled the `default_dir` and WebDAV `storage_dir` setup. The server binary remains side-effect-free for direct/test use. ### Test results `cargo test --workspace --no-fail-fast` — 179 passed, 0 failed, 2 ignored. Both `cargo build -p hero_foundry` and `cargo build -p hero_foundry_server` succeed. ### Manual verification (recommended on the deploying machine) 1. `make stop && rm -rf ~/hero/var/fossil && make run` 2. `ls ~/hero/var/fossil/core.fossil` — file exists. 3. `curl -i http://<server>/api/files/...` — no longer returns 503. 4. `make stop && make run` again — `core.fossil` mtime/contents unchanged on the second run.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_foundry#17
No description provided.