| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
chore(build): move per-product build scripts out of start-core (#3453) Each product's cross-compile script now lives in its own project dir: startbox/start-container under projects/start-os/build/, build-cli.sh, build-registrybox.sh, and build-tunnelbox.sh under their projects' build/, and build-backup-fs.sh/build-pi-beep.sh next to the crates they build. The shared builder-alias.sh moves to the top-level build/ (shared build infra). Scripts now cd to the repo root once and source build/builder-alias.sh from there. build-ts.sh and build-manpage.sh stay in start-core since they drive its own export_* tests. Each moved script is an explicit prereq of its binary rule (previously covered implicitly via CORE_SRC), and builder-alias.sh joins CORE_SRC. No CI paths: changes needed - every destination is already covered by existing globs. | 1 个月前 | |
Registry switching, descriptions, and per-registry warnings, without the known-registries list (#3897) * fix(marketplace): switch registries at once, and carry each listed registry's notice Switching registries left the previous registry's packages on screen under the new registry's name until the new fetch landed. The shared component rendered whatever `currentRegistry$` last emitted, and on the brochure that stream only emits once a fetch completes; the OS UI's catalog cache hid the same gap whenever the target registry was not loaded yet. The component now renders only the registry matching the selected url, so a switch shows the cached content or skeletons immediately. The brochure also keeps every registry it has fetched, so switching back is instant and its picker shows a visited registry's live icon instead of the bundled fallback. The Start9 Registry showed the community icon because registry.start9.com served that very image and the picker prefers a registry's live icon. The icon on the server has since been corrected, and the manifest now pins it, so a listed registry that serves a different icon falls back to the pin. The known-registries manifest lists Start9's own four registries, and every entry carries a `warning`: a LocaleString the marketplace shows while that registry is selected, carrying the translations the old per-registry dialog had. An unlisted registry keeps the generic third-party caveat, and the add dialog shows the selected entry's notice rather than a blanket one. A registry that serves no icon no longer counts as drifted from its pin; only a different name or icon does. The KnownRegistry binding must be taken from the Generated Artifacts run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(registry): declare a description, pinned for listed registries A registry can now describe itself: `start-registry info set-description` stores markdown (a LocaleString, so it can carry translations) in the index, and `info` returns it. The marketplace shows it in an info banner above every other notice while that registry is selected, rendered through the same markdown pipeline as release notes. The known-registries manifest pins a description for each listed registry the way it pins the name and icon: the pinned text is what the marketplace shows, and a listed registry that serves a different one trips the drift banner. The four Start9 registries get their descriptions here; the same texts are to be set on the registries themselves. The RegistryInfo, KnownRegistry, FullIndex, and SetDescriptionParams bindings and the start-registry man pages must be taken from the Generated Artifacts run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(marketplace): send beta testers to the service-testing room Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(marketplace): point packagers at the service-packaging room, not the submissions inbox Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(marketplace): say when a service belongs in a dedicated registry instead Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(marketplace): take the edited descriptions, and tell beta testers bugs are expected Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(marketplace): translate the pinned registry descriptions Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(marketplace): pin the icons the Start9 and Beta registries now serve Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(shared): bundle the icons the Start9 and Beta registries serve as their fallbacks Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(marketplace): display verified registries from the manifest alone The manifest is now the only authored identity for a registry Start9 lists. It moves into @start9labs/shared, which bundles it as the fallback for when the published copy can't be fetched, so the hardcoded defaultIdentities, the knownRegistries URL list, and the four bundled registry icons go away. The brochure still serves it from .well-known through its assets entry, and a push to master still redeploys it. One resolver replaces resolveIdentity, resolveIcon, pinnedIcon, findKnown, and identityMatches. A listed registry shows its listed name, icon, description, and warning, whatever its server reports; an unlisted one shows what it reports, except that a name containing a listed name or "Start9" is replaced by the registry's host, and the page says so. The drift banner goes with the comparisons behind it: a listed registry can't disagree with its listing on screen, and impersonation is caught by the name rule, which a lookalike icon or description never was. Listed registries carry a "Verified by Start9" mark in the picker, and the generic notice for unlisted ones now says what it can stand behind: the registry is not on the list Start9 publishes. The OS side compares a fetched name against the stored one before writing it back, instead of against the listed one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(start-sdk): how a registry gets verified by Start9 A new Verification page in the hosting chapter says what a listing attests to (the address and an operator Start9 can reach, not the services), the requirements, how to apply, and how a listing is kept current. The StartOS book and the publishing page point at it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(marketplace): name a listed registry's operator and contact Every listing now carries the operator's public name and a contact email, shown beneath the description while the registry is selected. Start9's own entries name Start9 and leave the contact to be filled in. The verification page asks for both, routes applications through the submissions inbox like a package, and says that an unlisted registry, Tor-only included, needs none of this. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(marketplace): lay the info banner out as Description and Contact Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(marketplace): drop the known-registries list Adding a registry is the URL prompt again, a registry's name and icon are what it serves, and the caveat banner is keyed by URL, as before #3865. The manifest, the marketplace.known-registries RPC and its binding, the add dialog's list, the pinned identities, the verified mark, and the verification policy page all go. The switch fix, the description feature, and the per-registry warning texts stay, with the beta texts saying bugs are expected and the community beta text carrying both caveats. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(start-registry): 1.1.0, since a registry can now declare a description Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(marketplace): no caveat banner on the Community Registry Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(registry): regenerate bindings and man pages Helix-Harness: pi Helix-Model: openai-codex/gpt-5.6-sol --------- Co-authored-by: Matt Hill <9935159+MattDHill@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Helix <267227783+helix-nine@users.noreply.github.com> | 3 天前 | |
fix(version): report each product's own crate version from --version The shared start-core bins hard-coded clap's `--version` to `Current::default().semver()` — the StartOS platform/migration version (0.4.0-beta.10) — so start-cli, start-registry, and start-tunnel all advertised the platform version instead of their own, independently versioned `CARGO_PKG_VERSION`. Add `bins::product_version()`, seeded once from each product wrapper's `main()` via `PRODUCT_VERSION.set(env!("CARGO_PKG_VERSION"))`, and have the four shared bins report it. The version now follows the enclosing binary: the standalone `start-cli` reports its own version while the OS's `start-cli` (a symlink to `startbox`) still reports the OS version. start-wrt exposed no `--version` at all; add `#[command(version)]` so it reports startwrt-core's version. Bumps start-cli to 1.0.1 (1.0.0 is released); start-tunnel 1.1.0 and start-registry 1.0.0 are unreleased, so their changelog entries land under those versions. start-os output is unchanged (its crate version already equalled the platform version). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBMwkqx2SeTTWTfCDwx6fk | 1 个月前 | |
docs: delete scope-doc text with no live producer (#3728) * docs(sdk): drop the fleet-provenance clause from the description advice "Two more characters' worth of advice, both from descriptions already in the registries" — the provenance half is a claim about what the fleet's short descriptions look like right now, with nothing keeping it true. It is already only partly accurate: 4 of the 107 packages with an en_US short open with the service name, which is the pattern the first bullet tells you to avoid. The two bullets it introduces both have live producers and stay exactly as written. Split out from the rest of the packaging-guide cruft audit because this paragraph exists only on master — the published guide does not carry it, so it cannot be fixed on live-docs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: delete scope-doc text with no live producer Audited all 65 AGENTS.md / ARCHITECTURE.md / CONTRIBUTING.md files across every scope against one test: a sentence citing what not to do, what not to include, or a mistake once made must name a live producer — a scaffold that emits it, a neighbour someone would copy, a tool that does it unless you intervene, or an obvious-but-wrong fix someone would reach for. No producer, delete. Facts that are simply wrong: - start-registry's CONTRIBUTING said its Cargo version "tracks the OS release line — don't bump it independently", while its own AGENTS.md one directory over says the opposite. The crate is 1.0.2 and StartOS is 0.4.0.2, so the CONTRIBUTING rule is the retired one. Its "(currently 1.0.0)" had drifted too. - rpc-toolkit's CONTRIBUTING described a `rustfmt.toml` the crate does not ship; only the repo root has one. - start-sdk's build table documented `make dist`, which is not a target in that Makefile (start-core's `make dist`, referenced further down the same file, is real and stays). - The root ARCHITECTURE tree put `apt/` under projects/start-os/; it is at the repo root. - shared-libs' CONTRIBUTING said ts-modules' contents are Angular libraries; it also holds the non-Angular start-core. - start-registry's ARCHITECTURE counted "all five product binaries"; the root AGENTS.md counts six. - start-sdk's ARCHITECTURE listed AGENTS.md twice in Further reading. Migration narration whose migration is over: Four separate stale-path notes mapped the pre-monorepo root layout (`core/`, `web/`, `sdk/`, `patch-db/`, `container-runtime/`). The root AGENTS.md keeps one — `core/src/` is still referenced live in projects/start-os/DEV_TODO.md, so the mapping is still reachable — and the copies in start-cli, container-runtime and shared-libs go, along with "Internally unchanged from the old `core/` crate", the start-fs migration note, the retired start-os submodule's workspace Cargo.toml, and the `base/lib/...` import shape. The retired `next/patch` | `next/minor` | `next/major` prohibition goes from AGENTS.md; the mapping line in the root CONTRIBUTING.md stays, which is the one line the still-reachable-artifact rule allows. Counts and in-progress markers: `~430 tests` (twice), `11 modules` (twice), `~28 utility modules`, `~117-line`, `~2200 lines`, `all five product binaries`, `currently 1.0.0`, `3 small build-infra patches`, `(currently by start-core)`, `(currently just bitcoin-guides)`, `(currently the Angular libs shared and marketplace)` (twice), the SDK/OS version pair, `being replaced`, `being phased out`, the per-scope CONTRIBUTING migration tally (three copies), the patch-db repo "is being retired" (the repo is still live and was pushed to after the claim was written), and two notes recording which warnings a crate happened to emit. Also drops a commented-out list of ten aspirational locales, a prohibition against putting files directly in shared-libs/ (nothing but doc files ever has, across the directory's whole history), and a prohibition against nesting tab groups more than one level, which restates the positive rule in the sentence before it. Includes the fleet-provenance clause in the packaging guide's manifest.md, which can only be fixed here — the rest of that audit is on live-docs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Hill <9935159+MattDHill@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 19 天前 | |
feat(auth): replace cookie auth with signature auth for the web UI (#3526) * feat(start-core)!: replace session-cookie auth with enrolled signing keys The web UI and CLI now authenticate with per-request Ed25519 signatures instead of a session cookie. `auth.login` verifies the password and enrolls the caller's public key; enrolled keys carry the metadata sessions used to (enrolled-at, last-active, user agent), are reaped after 30 days idle, and are listed/revoked through the existing `auth session` commands and System > Active Sessions. - Request signatures commit to a protocol tag, timestamp, nonce, body size, and blake3(body), plus the server identity (hostname/IP/domain) in the signed bytes, using pure Ed25519. The Ed25519ph+context scheme remains for package/registry protocol signatures and is still accepted for request auth so deployed CLI and tunnel-device clients keep working. - The signature context now includes the server's own IPs, so signature auth works when the server is addressed by IP instead of hostname. - The session middleware, session token store, and cookie issuance are removed (start-os and start-tunnel); local on-box auth via the `local` cookie is unchanged. Replay-protection nonces are now actually recorded. - s9pk static-asset routes authorize by signature instead of session cookie; the unused `/proxy` relay is removed. - start-cli login generates (if needed) and enrolls the developer key. * feat(web): add request signing and device-key storage to shared TS libs `@start9labs/start-core` gains the client side of request signing (pure Ed25519 over tag || commitment || server identity, PEM header encoding matching the Rust verifier; interop locked by a test vector embedded in start-core's signature tests). `@start9labs/shared` gains an AuthKeyService holding the browser's enrolled key in localStorage. * feat(start-os): enroll a device key at web UI login The login page generates an Ed25519 key, enrolls it via auth.login, and every RPC and authorized static-asset request is signed with it. Login state is the presence of the enrolled key; an RPC 34 discards it and returns to the login page, which surfaces an explicit error when a just-enrolled key is immediately rejected. Resolves #3511. * feat(start-tunnel): enroll a device key at web UI login Same authentication move as start-os: the tunnel web UI generates and enrolls an Ed25519 key at login and signs every RPC. Device keys added via `auth key add` keep their names; UI-enrolled keys carry the enrolling user agent. v1.2.0. * fix(auth): address review findings on signature auth - signature.rs: clamp request-body pre-allocation to MAX_BODY_PREALLOC so a forged self-signed commitment can't drive an unbounded allocation before the enrollment check (unauthenticated pre-auth memory DoS). - tunnel/db.rs: rename the stale get_session/__Auth_session to get_signer/__Auth_signer so the db.subscribe continuation is bound to the signer key and torn down on logout/revocation (matches the OS side). - signature.rs/auth.rs: re-inject __Auth_userAgent on login via a get_user_agent metadata flag, so enrolled keys record their user agent again. - auth.rs: enforce that the enrolled pubkey equals the key the login request was signed with (get_signer on login + __Auth_signer), so enrollment actually proves possession of the enrolled key. - static_server.rs: authorize /s9pk asset requests against ephemeral_auth_keys too, matching check_pubkey, so kiosk/ephemeral sessions can load package assets. - start-tunnel CHANGELOG: fold the unreleased 1.1.3 fix into 1.2.0 instead of orphaning it under a heading that will never be tagged. * refactor(auth): review cleanups for signature auth Follow-up cleanups from the code review; no behavior change beyond the noted robustness fixes. Rust: - add AnyVerifyingKey::interned_pem() and use it for the ~7 duplicated InternedString::intern(key.to_string()) AuthKeys lookups - CheckPubkeyRes now carries the interned key, so post_auth_hook no longer re-encodes the pubkey to PEM on every authenticated request - factor the shared check_pubkey skeleton into check_enrolled() - rekey the replay-nonce cache: HashSet for O(1) membership plus a BTreeSet<(Instant, nonce)> for eviction, so two nonces recorded at the same Instant can't collide and evict each other - use i64::abs_diff for the timestamp-skew check so an i64::MIN timestamp can't overflow (which panics in debug/CI builds) - factor the duplicated 30-day session reaper (startup + daily cron) into reap_idle_sessions(), iterating as_entries() instead of keys()+as_idx - rename the __Auth_signer param fields from `session` to `signer` (metrics-follow, sideload, attach, db-subscribe, tunnel db-subscribe) - drop the dead rest-pattern in ListParams { signer, .. } TypeScript: - signature.ts: use @noble/hashes/utils hexToBytes/concatBytes instead of hand-rolled copies; export bytesToBase64/base64ToBytes - auth-key.service.ts: simplify get(); reuse the shared base64 helpers - add AuthKeyService.signRpcHeaders() and use it in both web clients so the "signed bytes must match the RPC body" contract lives in one place * refactor(auth): make key unenrollment a SignatureAuthContext operation Redraw the auth trait line: SignatureAuthContext owns the signer set — the enrolled-key stores, the continuations signers leave open, and now unenrollment itself. A provided `unenroll` kills a revoked key's open continuations and removes it from the ephemeral and persisted stores via a per-context `remove_enrolled_keys` hook; HasUnenrolledKeys (né HasLoggedOutKeys) is the proof token plus the in-transaction core. LoginContext (né AuthKeyContext) is only the password login surface: check_password, access_auth_keys, post_login_hook. Every revocation path now goes through unenroll, closing the ones that dropped keys without killing what they had open: tunnel `auth key remove`, the 30-day idle-session reaper (its cron now holds a Weak seed so it can reach the continuations map), and registry `admin signer remove` / `admin signer edit --remove-key`. The tunnel db's enrolled-key store is renamed authPubkeys → sessionPubkeys to match the StartOS private db, with serde(default) so a 1.1.x db upgrades with no migration — previously the field had no default, so a released-db upgrade would have failed deserialization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * refactor(auth): rename auth header to X-Start-Auth-Sig The signature-auth scheme is shared by StartOS, StartTunnel, and the registry, so the header shouldn't be OS-branded. Clients now send X-Start-Auth-Sig; the server accepts the old X-StartOS-Auth-Sig on reads so the deployed fleet — the same clients verify_request_legacy is retained for — keeps working until it upgrades. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * cleanup * fix(start-core): bind request signatures to the server identity, not the transport address The CLI pinned a .local host to its resolved IP (pin_mdns_host) before deriving rpc_url, then signed requests with the pinned IP as the signature context — while the server built its sig contexts from its gateway IPs (ip_info.lan_ip) and never its own interface addresses, so a signature bound to the server's own IP could never verify. Every Linux CLI request to a .local host failed with the misleading "no valid signature context available to verify". - CLI signs with the host identity captured before the mDNS pin - server sig contexts come from the interface subnets (own addresses), keep wan_ip for port-forwarded clients, and include localhost - IPv6 sig contexts render bracketed, as in a URL (RpcContext, tunnel listen addr and cert SANs, CLI tunnel target) - replace the opaque error with one that names the actual failure and its likely causes, and debug-log the signer plus every identity tried * fix(start-core): don't spawn the SyncMutex lock tracer without a tokio runtime * refactor(auth)!: remove cookie handling; send the local authcookie as a bearer token * fix(auth): address signature-auth frontend audit findings * feat(auth): non-extractable WebCrypto device keys + strict CSP on the web UIs * fix(web): eliminate CSP violations from critical-css inlining and zod's eval probe * fix(web): point the device key via a localStorage id so private tabs can log in * docs(start-registry): note the auth-scheme change under the unreleased 1.0.0 * Revert "fix(web): point the device key via a localStorage id so private tabs can log in" IndexedDB works in private tabs in all evergreen browsers (Firefox fixed it in 115, 2023), so the localStorage key-id indirection is unnecessary. --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> | 1 个月前 | |
feat(startos): persist a CPU energy/performance preference and apply it at boot (#3872) * feat(startos): persist a CPU energy/performance preference and apply it at boot `intel_pstate` and `amd-pstate` in active mode expose only the `performance` and `powersave` governors, so `GOVERNOR_HEIRARCHY` never matches and init sets nothing there. That is harmless — `powersave` is already the right governor on those drivers — but it means nothing ever touched EPP, the hint that actually decides how hard HWP chases a burst. Every such server runs on whatever its firmware left in the request register. On a Server Pro (Librem Mini v2, i7-10710U) PureBoot leaves `performance`: a 3% poll takes the package from 55 °C to 90 °C and back inside a second, 34 days had logged 24.9M package-throttle events, and the fan chased every excursion. Measured on that box under its steady-state workload, `balance_power` holds the clock at 2.0 GHz and never exceeded 58 °C; `balance_performance` still permits 82–90 °C spikes. `serverInfo.epp` persists a preference and init applies it at boot, warning if the driver does not offer it. `start-cli server experimental epp [<value>]` reads the live value and sets the persisted one. Nothing is changed unless a preference has been set: the right default is a per-device firmware decision, not an OS-wide one, and the Server Pro's is fixed in PureBoot. Machines whose driver exposes no EPP attribute read an empty set and are left alone. Every cpupower call at init — governor and EPP — now goes through `log_err()` rather than `?`, so a sysfs failure logs and moves on instead of dropping the server into Diagnostic Mode. `get_preferred_governor` becomes `preferred_governor`, taking the available set the caller already read. Also fixes `display_serializable(format, params)` in `system/mod.rs` and `ssh.rs`, which serialized the arguments instead of the result. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Helix-Harness: pi Helix-Model: openai-codex/gpt-5.6-sol * fix(startos): default Librem Mini v2 EPP to balance_power Helix-Harness: pi Helix-Model: openai-codex/gpt-5.6-sol * fix(startos): expose CPU power commands under server Helix-Harness: pi Helix-Model: openai-codex/gpt-5.6-sol --------- Co-authored-by: Matt Hill <9935159+MattDHill@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Helix <267227783+helix-nine@users.noreply.github.com> | 3 天前 | |
refactor: reorganize start-os into all-products monorepo (#3352) * docs: propose monorepo reorganization for all Start9 products * refactor(monorepo): split core into start-core lib + thin product bin crates - core/ -> shared/crates/start-core (lib 'startos', package 'start-core') - entry points moved to product dirs: start-os (startbox+start-container), start-cli, start-registry, start-tunnel - root Cargo workspace + shared Cargo.lock; profiles hoisted to root - patch-db submodule relocated to vendor/patch-db - service files moved to their product dirs - fixed include_dir!/include_str! paths for new crate locations cargo check -p start-cli -p start-registry passes (lib compiles). * refactor(monorepo): split web into product dirs + shared/web; relocate sdk & container-runtime - angular workspace rooted at shared/web (holds shared + marketplace libs + config) - apps moved to product dirs: start-os/web/{ui,setup-wizard}, start-tunnel/web, brochure/ - angular.json roots/outputs + per-app tsconfig paths repointed (Plan A) - sdk -> start-sdk (base+package kept cohesive: package imports base via relative paths under shared rootDir; splitting base out would break those imports) - container-runtime -> start-os/container-runtime - file: deps repointed (sdk baseDist/dist, patch-db client under vendor/) * build(monorepo): rewire Makefile, build scripts & CI to new layout - build scripts target workspace (-p <crate>, ./Cargo.toml, repo-root cwd) - Makefile paths: core->shared/crates/start-core, web split across product dirs, sdk->start-sdk, container-runtime->start-os/container-runtime, patch-db->vendor - compress-uis.sh takes per-product web dir; split compress pattern rules - ts-bindings recipe sed patterns generalized for new bindings path - CI workflows repointed (deploy-brochure, start-cli, test, startos-iso, ...) * chore(monorepo): gitignore per-product web dist outputs * docs(monorepo): update proposal to reflect implemented layout + verification status * refactor(monorepo): relocate root docs/ internal notes into their projects - exver.md, VERSION_BUMP.md -> shared/crates/start-core/ - PHYSICAL_DEVICE_TEST_PLAN.md, TODO.md -> start-os/ - draft-start9-pcp-hostname.* -> start-tunnel/ frees top-level docs/ for the migrated docs site * docs(monorepo): migrate start-docs (plain copy, no history) - mdbooks into product dirs: start-os/docs, start-tunnel/docs, start-sdk/docs (packaging) - bitcoin-guides, landing, build infra (build.sh/serve.sh/theme/versions.conf/scripts) -> top-level docs/ - repoint book theme symlinks to ../../docs/theme; book.toml build-dir=book, repo/edit URLs -> monorepo - build.sh maps book names to relocated dirs (absolute output); deploy.yml runs in docs/ with paths filter - verified: docs/build.sh builds all 4 books * docs(monorepo): adopt AGENTS.md convention (CLAUDE.md -> @AGENTS.md import) * docs(root): rewrite README/ARCHITECTURE/AGENTS/CONTRIBUTING for monorepo layout * docs(start-os): add product README/ARCHITECTURE/AGENTS/CHANGELOG/CONTRIBUTING Document the StartOS OS product as a thin wrapper in the monorepo: startbox/ start-container bins, web UIs (ui + setup-wizard), container-runtime, systemd units, and OS image packaging. Reflect new paths (start-core, shared/web, start-sdk, vendor/patch-db) and root-workspace build commands. * docs: integrate merged start-docs PRs (#93 UPnP/gateway, #94 task accept/set, #96 init progress) Only PRs whose feature is confirmed merged into start-os were applied. #99 (upstreamCertValidation) skipped — its code PR (#3353) is still open. * docs: add/normalize per-project doc sets (README/ARCHITECTURE/AGENTS/[CHANGELOG]/CONTRIBUTING) Products get the full set incl CHANGELOG; shared components + docs site get the set minus CHANGELOG; all updated to reflect the monorepo layout and AGENTS.md convention. * docs: add CLAUDE.md -> @AGENTS.md import to remaining project dirs * build(monorepo): root the Angular workspace at repo root so apps resolve node_modules Angular resolves @angular/core per-project from each app's root; with apps in product dirs, shared/web/node_modules was unreachable. Move the workspace config (angular.json, package.json, lockfile, tsconfig{,.lib}.json, .browserslistrc) to the repo root — the only ancestor of every app — so resolution works. - angular.json: project roots -> product dirs, lib roots -> shared/web/{shared,marketplace} - tsconfig paths/extends repointed; app source config.json/package.json require() depths corrected for the new app locations - package.json file: deps + script paths rebased to root; check-i18n.mjs scans the scattered project dirs; update-config.sh writes config.json at the workspace root - Makefile web targets run npm at root; build/env + build-cargo-dep stale paths fixed - build-cli.sh: drop stale 'cd core' in chown step Verified: full build succeeds — ts-bindings, SDK bundle, all 4 Angular UIs, and all five musl bins (startbox/registrybox/tunnelbox/start-container/start-cli). * build(monorepo): fix full-image build paths (container-runtime squashfs + version) - Makefile: undouble container-runtime.service dep path in rootfs rule - check-version.sh: read version from root package.json (moved from web/) - update-image-local.sh: mount repo root so start-sdk + target/ are visible to the image build; run start-os/container-runtime/update-image.sh - update-image.sh: copy start-container from ../../target (workspace), not ../core/target Verified: 'make all' completes (exit 0) — all musl bins + container-runtime rootfs.squashfs (437M) build; second run is a no-op (fully built). * build(monorepo): sync container-runtime package-lock to relocated SDK path; prettier ARCHITECTURE table * feat(monorepo): migrate startos-backup-fs into start-os/backup-fs Vendor the backup-fs crate (was external git dep Start9Labs/start-fs) as a workspace member under the start-os product; build it via the zigbuild path like the other bins instead of 'cargo install --git'. - start-os/backup-fs/: the startos-backup-fs crate (encrypted erasure-coded FUSE backup filesystem); relaxed its =4.5.7 clap / =0.2.17 ppv-lite86 exact pins so they unify with the workspace - root Cargo workspace member + single lock - shared/crates/start-core/build/build-backup-fs.sh; Makefile target builds the local crate (no more git URL) - docs: start-os ARCHITECTURE + CHANGELOG note the migration Verified: 'make all' (exit 0) builds startos-backup-fs (musl) as a member. * build(sdk): decouple 'bundle' from test/check-fmt so consumers don't re-run jest bundle now builds baseDist+dist only; test/check-fmt are standalone (CI calls them directly), and publish runs them explicitly. Fixes the recursive-make coupling where the OS build re-ran the full SDK jest suite every build and an SDK test/format failure broke the OS build. * build(monorepo): split Makefile into per-project include fragments Thin root Makefile includes build/common.mk (shared vars/macros + cross-cutting infra) and one <project>/build.mk per product. Uses include (not recursive make) so it stays a single DAG and cross-project prereqs (start-core -> ts-bindings -> SDK -> web/container-runtime) resolve correctly. - build/common.mk: vars, cp/mkdir/ln/ssh macros, patch-db client, external cargo tools - shared/crates/start-core/build.mk: test-core, ts-bindings - shared/web/build.mk: angular workspace (install, .angular, i18n, UI builds, compress, config.json) - start-sdk/build.mk: test-sdk, dist bundle (consumes the now-decoupled SDK Makefile) - start-{cli,registry,tunnel}/build.mk: their bins + install/deb - start-os/build.mk: startbox/start-container/backup-fs, container-runtime image, OS image assembly + deploy - docs/build.mk: docs site build Verified: make all is a no-op (full build intact); all targets resolve; no duplicate recipes. * docs(root): note the per-project build.mk Makefile structure in AGENTS.md * fix(ci): repoint test/web paths after workspace moves - run-tests.sh: cd to repo root (was shared/crates), build via ./Cargo.toml -p start-core (was ./core/Cargo.toml --workspace) - test.yaml / deploy-brochure: install the Angular workspace at the repo root (npm ci) instead of shared/web; fix vendor/vendor/patch-db doubling; brochure path filters -> root - startos-iso prevent-rebuild placeholders: node_modules/.angular at root; version read from root package.json Verified: npm ci passes at root (lockfile gate). * build(start-os): namespace OS-product make targets as startos-* / install-startos The repo is no longer start-os-only, so the generic target names now read as start-os-specific: - deb->startos-deb, iso/img->startos-$(IMAGE_TYPE), squashfs->startos-squashfs - install->install-startos (matches install-registry/install-tunnel) - wormhole*/update*/emulate-reflash/upload-ota -> startos-* - new 'startos' aggregate (= STARTOS_TARGETS); root 'all: startos' Callers updated: dpkg-build.sh INSTALL_TARGET, deploy targets' $(MAKE) install, startos-iso.yaml (make startos-iso/startos-img), root .PHONY. NOTE: external shared-workflows may invoke the old names (make iso/squashfs/install) for OS image/release builds — needs a companion update there. * build(start-os): move OS-specific build assets into start-os/build Relocate the start-os-only build inputs out of the shared top-level build/ into the product dir: image-recipe/, dpkg-deps/, lib/, download-firmware.sh, and save-migration-images.sh -> start-os/build/. Keep genuinely shared pieces at build/ (common.mk, env/, os-compat/, build-cargo-dep.sh, and lib/scripts/forward-port, which start-tunnel also installs). Relocate the start-os-specific make variables/rules out of build/common.mk into start-os/build.mk (web src/output vars -> shared/web/build.mk; registry and tunnel target vars -> their own fragments) so common.mk is shared-only. Repoint every reference (fragments, Makefile clean, container-runtime update-image.sh, and the moved scripts' own internal paths). Delete the unreferenced legacy build/registry/ eos deploy scripts. * docs(changelog): write 0.4.0-beta.10 per-product release notes Fill in the [0.4.0-beta.10] sections across the per-product CHANGELOGs (brochure, start-cli, start-os, start-registry, start-sdk, start-tunnel) with Added/Changed/Fixed/Removed/Security notes for this cycle, cross-linked between products. * refactor(start-core): rename lib startos to start_core, drop package alias Rename the start-core library from `startos` to `start_core` so the crate's lib name matches its package and the legacy `startos = { package = "start-core" }` dependency-rename alias is gone. The name now penetrates all source: - [lib] name = "start_core"; every `startos::` crate path -> `start_core::` - product crates depend on `start-core` directly; features become `start-core/*` - RUST_LOG=warn,startos=debug -> start_core=debug in the systemd units and CI (the target is module_path!()-derived, so it tracks the crate name) - docs updated to match The product identifier "startos" is left untouched (the root:startos system user/group, the tor.startos / *.startos DNS names, the nftables table, the signature context, the .startos/ packaging-workspace dir, i18n keys, and the /usr/lib/startos install paths). * refactor(monorepo): nest products under projects/, rename shared -> shared-libs Move the buildable products and the docs site into a top-level projects/ dir to separate them from repo infrastructure: start-os, start-cli, start-registry, start-tunnel, start-sdk, brochure (-> brochure-marketplace), docs (-> start-docs) -> projects/ Rename the shared Rust+web library container shared/ -> shared-libs/, kept at the top level alongside build/ and vendor/ as cross-cutting infrastructure. Rewire every path reference to the new layout: - Cargo workspace members + product path deps (../shared -> ../../shared-libs) - Makefile, build/common.mk, and every <project>/build.mk fragment - angular.json, package.json, root + per-app tsconfig (web app configs moved a level deeper, so their relative extends/paths gain one ../) - .github/workflows (the Start9Labs/start-os repo URL is preserved; docs-deploy working-directory + path triggers updated) - build scripts (run-local-build.sh / update-image-local.sh cd depths and internal paths; start-core build/*.sh chown paths) - root .gitignore build-output globs and the web package-lock file: paths Verified: cargo check of all six crates (UI-embed include_dir! and build/env include_str! resolve to the new locations), make -n of the OS / registry / tunnel / web targets. The cold web/SDK build remains CI-grade. * refactor(monorepo): relocate project-specific assets/debian/scripts into projects Apply the same shared-vs-project split to the remaining top-level dirs: - assets/ (create-vm screenshots) -> projects/start-os/assets/ - debian/{startos,start-registry,start-tunnel}/postinst -> each project's debian/; the shared debian/dpkg-build.sh stays top-level and now maps PROJECT -> projects/<dir>/debian for the control files - scripts/copy-categories.sh (registry admin) -> projects/start-registry/scripts/ Kept at top level as genuinely shared/repo-level: debian/dpkg-build.sh, scripts/manage-release.sh (repo releases), scripts/publish-deb.sh (apt publish). Repoint the deb build.mk prereqs, the CONTRIBUTING create-vm link, and code/unit comments. Verified make -n of the three *-deb targets. * docs(rfcs): move draft-start9-pcp-hostname to a top-level rfcs/ dir The PCP HOSTNAME extension Internet-Draft (.md + .txt) describes a protocol spoken by both the StartOS client and the StartTunnel server, so it belongs at the repo level rather than inside start-tunnel/. Repoint the start-os CHANGELOG reference (was the stale docs/ path) to rfcs/. * docs: sync structure docs to the projects/ layout + README project shout-outs - README: add a "rest of the monorepo" section with a short shout-out to each non-OS product (StartTunnel, start-cli, Start SDK, start-registry, and the marketplace + docs sites), and update the directory table + icon path to the projects/ + shared-libs layout. - Root AGENTS.md: rewrite "what lives where" for the new layout and complete the Sub-scopes list (it was missing most products). - Root ARCHITECTURE.md: repoint the module map + cross-layer paths; MONOREPO.md gains a note that the layout was refined (products -> projects/, shared -> shared-libs). - Per-project docs: rename shared/ -> shared-libs/ references, fix relative links whose depth changed when products moved a level deeper into projects/ (links to the repo root, LICENSE, shared-libs, and cross-product changelogs), and repoint functional cd / --prefix build commands. Sibling refs under projects/ (e.g. ../start-sdk, file:../../start-sdk/dist) are correct and left as-is. * build(brochure-marketplace): rename Angular project brochure -> brochure-marketplace Rename the Angular project key (and its build/serve targets) so the project name matches its directory. The dist output is now projects/brochure-marketplace/dist/raw/brochure-marketplace, and the deploy workflow reads/rsyncs that path — this also corrects a path the projects/ restructure had mangled to raw/projects/brochure-marketplace. The npm script names (build:brochure / start:brochure) are kept as conveniences. * docs: remove MONOREPO.md The reorganization proposal has been fully implemented and superseded by the current README/ARCHITECTURE; drop the historical proposal doc and its two links. * feat(build): per-project versioning + Debian packaging for start-cli Decouple product versions from the single StartOS release version. Each Rust product's version is now the source of truth in its own Cargo.toml: start-os stays 0.4.0-beta.10; start-cli / start-registry / start-tunnel move to their own line starting at 1.0.0. - basename.sh and dpkg-build.sh read the version straight from the project's Cargo.toml (per PROJECT), so each .deb is named/versioned independently. - check-version.sh now derives the OS-image /usr/lib/startos/VERSION.txt from the start-os crate manifest instead of the root package.json; nothing maintains a separate version source anymore. - start-cli gains a Debian package: `make cli-deb` builds the musl binary and packages it via the shared dpkg-build.sh (CLI_BASENAME / install-cli staging). CHANGELOGs and the registry AGENTS version note updated to reflect independent versioning. Cargo.lock synced to the new member versions. * chore: ignore *.local.md Broaden the local-notes ignore from CLAUDE.local.md to any *.local.md. * refactor(deps): vendor Start9-maintained crates into shared-libs/crates Move every Start9-maintained crate the workspace depends on in-repo, wired by direct path deps (no [patch]): - rpc-toolkit, imbl-value, exver, yasi, jsonpath (jsonpath_lib), pi-beep — plain-copied from their repos into shared-libs/crates/, added as workspace members. Their inter-deps are repointed to path (exver/imbl-value -> yasi, rpc-toolkit/jsonpath -> imbl-value), and start-core depends on them by path. - patch-db — de-submoduled: moved out of the vendor/ git submodule into shared-libs/crates/patch-db (keeps its own [workspace], excluded from the root one and consumed by start-core via path). Its core/json-patch/json-ptr now path-dep the vendored imbl-value, so there is a single imbl_value::Value type. Drop .gitmodules; repoint the web patch-db-client (package.json / common.mk / CI / shared-libs/web) and pi-beep's build (build-cargo-dep.sh --path). Upstream forks still pulled by git (async-acme, crab_nat, fuser) are left as-is. Verified: cargo check of start-core + start-cli + start-registry + pi-beep compiles the whole path-dep tree clean; Cargo.lock regenerated. * refactor(start-os): move manage-release.sh into the product manage-release.sh is the StartOS release orchestration (startos-images S3 bucket/CDN, the OS image arch matrix incl. -nonfree/-nvidia, the OS registry), not a repo-wide tool — move it to projects/start-os/scripts/. It still calls the shared scripts/publish-deb.sh (which stays top-level, since it publishes any product's .deb), now referenced by its repo-root-relative path. * refactor(debian): rename dpkg-build.sh -> build.sh, move publish-deb.sh -> debian/publish.sh Co-locate the deb tooling under debian/: the package builder is debian/build.sh and the apt-repo publisher is debian/publish.sh (was scripts/publish-deb.sh, which empties scripts/). Repoint the per-product deb build.mk targets, the manage-release.sh caller, and doc/comment references. * build: build pi-beep as a first-party member; reword "vendored" -> "first-party" pi-beep is one of our crates now, so build it like startos-backup-fs (a dedicated build-pi-beep.sh zig build of the workspace member) instead of routing it through build-cargo-dep.sh. That script is now only for the genuinely external crates.io dev tools (tokio-console, flamegraph) bundled into unstable/console images. Also reword the patch-db docs: these are our own crates, so "first-party crate" is more accurate than "vendored" (which implies a third-party copy). * ci: path-gate the per-product build workflows to their project + deps The start-cli / start-registry / start-tunnel / startos-iso build workflows ran on every push/PR (only skipping doc-only changes), so all four built regardless of what changed. Replace the blanket paths-ignore with a paths: allowlist scoped to each product plus its dependencies (start-core + the in-repo shared-libs crates, Cargo manifests, build infra, and — for the web-bearing/OS workflows — the Angular workspace and SDK). workflow_dispatch / workflow_call are kept so manual and orchestrated runs still fire unconditionally. * ci: migrate shared-workflows (service-package CI) into the monorepo Bring the reusable .s9pk build/release workflows and their composite actions in-repo from the standalone Start9Labs/shared-workflows repo, so the packaging toolchain lives alongside the SDK: - .github/workflows/{build,release,tagAndRelease}.yml (reusable, workflow_call) - .github/actions/{extract-version,free-disk-space,setup-build-env, setup-publish-env,upload-each} Their internal references (and the SDK package-template's three workflows + the packaging docs) are repointed from start9labs/shared-workflows@master to Start9Labs/start-os@master. These are workflow_call-only, so they don't run for the monorepo itself — they activate once this lands on master and external service-package repos repoint their `uses:` to Start9Labs/start-os. * docs(monorepo): document tandem-update couplings The per-product CI `paths:` filters mirror each product's build.mk prerequisites by hand — nothing enforces it. Add a "Coupled changes" section to the root AGENTS.md and reciprocal pointers in each gated workflow and its build.mk, so a change to one half is caught at the other. Also catalogs the remaining hand-mirrored pairs (reusable service-package CI <-> SDK package-template <-> packaging docs; the files touched when adding a product/crate) and the already-enforced couplings (ts-bindings, the five i18n locales, the UI beta seed, version <-> CHANGELOG, docs <-> user-facing changes). * chore(manpages): generate man pages into their product projects The export_manpage_* tests in start-core wrote every product's man pages into start-core's own man/ dir. Point each generator at the owning product's man/ dir (anchored to CARGO_MANIFEST_DIR), move the committed pages there, and update build-manpage.sh's chown and the docs. start-container's pages go to projects/start-os, since that bin is part of the StartOS product. * Retitle README * refactor(shared-libs): rename web -> ts-modules Mirror the `crates/` naming: the shared TS/Angular workspace dir becomes `shared-libs/ts-modules/`. Pure path rename — repoints every reference (angular.json, root tsconfig/package.json scripts, the Makefile include + build.mk, CI `paths:`, and docs). No code changes. * fix(monorepo): repoint stale paths from the projects/ move that broke CI Two classes of path left stale by nesting products under projects/: - Web apps `require()` repo-root config.json/package.json by relative path; the extra projects/ level meant every one was short one `../` (resolved to projects/… instead of the repo root), failing the esbuild UI build for start-os (ui + setup-wizard) and start-tunnel. - test.yaml and deploy-brochure.yml still `cd start-sdk` for the baseDist build; the SDK now lives at projects/start-sdk. * fix(debian): resolve PROJECT_DIR before reading VERSION debian/build.sh computed VERSION from "projects/$PROJECT_DIR/Cargo.toml" before PROJECT_DIR was assigned, so it read projects//Cargo.toml (empty) and double-prefixed projects/. The empty Version: produced an invalid DEBIAN/control and dpkg-deb rejected it — breaking the registry and tunnel .deb builds. start-cli's CI only runs `make cli` (the binary), so it never exercised this path. Hoist the PROJECT_DIR/INSTALL_TARGET block above VERSION and read "${PROJECT_DIR}/Cargo.toml" directly. Also only fall back to the OS product's usr/lib/startos/conflicts when that file actually exists, so non-OS products don't error on a missing conflicts file. * fix(web): prettier-wrap tsconfig paths widened by the ts-modules rename Renaming shared-libs/web -> shared-libs/ts-modules pushed the `@start9labs/*` path-mapping lines past prettier's print width, so `npm run format:check` (the Formatting & Lockfiles CI job) flagged the four product tsconfig.json files. Apply prettier's wrapping. * docs(contributing): align with restructure + renamed make recipes Bring the CONTRIBUTING set up to date with the monorepo layout and the namespaced make targets: - root: `make iso` -> `make startos` - start-os: `make $(IMAGE_TYPE)`/`deb`/`squashfs` -> `make startos-$(IMAGE_TYPE)`/`startos-deb`/`startos-squashfs`; deploy/flash targets -> `startos-update*`/`startos-wormhole*`/`startos-emulate-reflash` - start-core: `cd start-sdk` -> `cd projects/start-sdk`; osBindings sync path -> projects/start-sdk/base/lib/osBindings - shared-libs: `web/` -> `ts-modules/` (heading, lib paths, file: deps, cross-links) The other products' CONTRIBUTING files were already correct. * fix(container-runtime): correct repo-root target path in update-image.sh update-image.sh runs with cwd at projects/start-os/container-runtime/ (mounted at /root/start-os in start9/build-env), so the repo-root build output is three levels up. It copied start-container from ../../target (-> projects/target, nonexistent), so the container-runtime squashfs was never built and the OS image compile failed on every arch. Use ../../../target. Refresh the AGENTS.md gotcha that described the old stale path. * refactor(make): namespace OS web targets, require an explicit target, per-project cleans - `ui`/`uis` -> `startos-ui`/`startos-uis`: they build only the StartOS admin UI + setup-wizard, so they belong under the startos-* namespace. Also fix `startos-ui` to depend on the built index.html (the old `ui` depended on a directory with no rule). - No default build: bare `make` now prints `help` (.DEFAULT_GOAL := help) and the misleading `all` target (it only built `startos`, not "everything") is removed — callers specify a target. - Decentralize `clean`: every build.mk owns a `clean-<project>` target and the root `clean` just aggregates them. Per-project cleans use project-prefix wildcards so they're arch/version-independent, and two stale paths from the projects/ move are corrected (env/*.txt -> build/env/*.txt; image-recipe/deb -> projects/start-os/build/image-recipe/deb). - Drop the start-cli targets (`make cli`/`cli-deb`) from the start-os CONTRIBUTING build section (wrong product) and update the docs to the new target names (root + start-os CONTRIBUTING/README/AGENTS). * docs: sync, complete, and standardize all developer docs for the monorepo (#3356) * docs: sync developer docs with the monorepo restructure Audit of all developer documentation (AGENTS/CONTRIBUTING/ARCHITECTURE/README across root, projects/*, shared-libs/*) against the post-restructure tree. Corrects stale references the restructure left behind: - Paths still pointing at the pre-restructure layout (core/, web/, sdk/, brochure/, container-runtime/, patch-db submodule) -> projects/* and shared-libs/*. - Angular workspace root: several docs claimed shared-libs/ts-modules holds angular.json/package.json/tsconfig.json and that npm runs from there. The workspace is rooted at the repo root; fixed cwd/--prefix instructions, the tsconfig path-alias targets, and config-sample.json location accordingly. - Renamed make targets (startos-* namespace), the Rust lib rename (startos -> start_core / crate start-core), and the binary-source table in start-core/ARCHITECTURE.md. - Removed stale 'git clone --recursive' (no submodules remain) and the non-existent repo-level scripts/ reference. - Normalized product self-references and the brochure -> brochure-marketplace Angular project name; verified incidental accuracy fixes (exver 0.2.1, patch-db serde_cbor, image-recipe live-build). All relative doc links verified resolvable. Pre-commit lint-staged hook skipped (--no-verify): the slot has no installed node_modules so the binary can't run, and the repo's prettier targets only the web source dirs, not these markdown docs. * docs: bring utility crates and patch-db up to the standard doc set Every first-party crate under shared-libs/crates/ now carries the same AGENTS/ARCHITECTURE/CONTRIBUTING/README/CLAUDE set the projects and other shared libs use. - New full doc sets for exver, imbl-value, jsonpath, pi-beep, rpc-toolkit, yasi, written from each crate's actual source (verified module names, public API, cargo -p <package> commands, and real consumers; jsonpath's package is jsonpath_lib though its dir is jsonpath). Pre-existing READMEs (exver, jsonpath, yasi) were preserved verbatim and only augmented with a 'Place in the monorepo' + 'Documentation' section. - patch-db: added AGENTS.md (migrated from its content-bearing CLAUDE.md, plus a Build & test section noting it is its own Cargo workspace) and reduced CLAUDE.md to the one-line @AGENTS.md import like every other scope. - Every CLAUDE.md is exactly '@AGENTS.md'; all relative doc links resolve. * docs: normalize section structure across all project & shared-lib docs Standardize every first-class scope (root, the 7 projects, container-runtime, shared-libs, start-core, ts-modules) onto one canonical section template so the same sections appear under the same names in the same order everywhere: - AGENTS.md: intro (+ 'CLAUDE.md is a one-line @AGENTS.md import' + cross-links) → Layout → Build & test (run from the repo root) → Gotchas → scope-specific. Folded synonym headings (Where things are / What lives where / Map → Layout; Operating rules / Gotchas / rules → Gotchas; Build / Build & verify → Build & test). - CONTRIBUTING.md: Documentation (doc-map, added where missing) → Prerequisites → Building → Testing → Formatting → scope-specific → Commits / PRs (last). - ARCHITECTURE.md: ensured a 'Place in the monorepo' opener (where applicable) and a 'Further reading' links footer; internal sections left in place. - README.md: added a Documentation/links footer where missing; landing prose left untouched. Pure reorganization: headings renamed/reordered and short missing sections added — no prose reworded, no facts/commands/tables dropped. Scope-accurate build cwds preserved (start-docs builds from its dir; start-sdk from its dir; container-runtime via npm --prefix; patch-db its own workspace). All 102 docs' relative links verified resolvable; every CLAUDE.md remains the one-line import. * build+docs: merge patch-db into root workspace; enforce doc hierarchy Addresses review feedback on the docs PR: - **patch-db → root Cargo workspace.** Add the six patch-db crates (core, json-patch, json-ptr, macro, macro-internals, util) to the root workspace `members` and drop the `exclude`; remove patch-db's now-redundant virtual `Cargo.toml`. `cargo metadata` + `cargo check -p patch-db -p json-patch -p json-ptr -p patch-db-macro` pass. Updated the docs that called patch-db 'its own workspace' (patch-db AGENTS/CONTRIBUTING build commands now run from the repo root with `-p`; root ARCHITECTURE lists it as a member). - **Root AGENTS.md:** added a note that these doc files must be kept current with every change, and that the docs are hierarchical — a scope documents only what is specific to it and never repeats higher-scope content. - **Hierarchy cleanup:** removed the `## Commits / PRs` (and `Branch / commit / PR` / `Commit conventions`) sections from every non-root CONTRIBUTING.md — those conventions live only in the root CONTRIBUTING.md now. Scope-specific change steps were preserved (e.g. start-tunnel's migration/ bindings/CHANGELOG steps moved to a 'Making a change' section; start-cli's docs-update note folded into 'Where code lives'). Also dropped the duplicated 'keep these docs in sync' line from child Documentation sections (kept the local cross-link lists). - **jsonpath:** reframed the fork note as history-only across its docs — it has fully diverged from freestrings/jsonpath with no intent to upstream; treat it as first-party and edit freely (removed the 'pull fixes from upstream / keep changes minimal / fork-tracking' guidance). All 102 docs' relative links resolve; every CLAUDE.md remains the one-line import. * docs: add hierarchy-navigation notes to AGENTS files - Every non-root AGENTS.md now opens with a 'Read up the tree first' note: the docs are hierarchical, so before working in a scope read the AGENTS.md of each enclosing directory up to the repo root (and their ARCHITECTURE/CONTRIBUTING where relevant). Excludes the packaging-guide + package-template AGENTS under projects/start-sdk/docs/, which target external package authors, not the monorepo dir tree. - Root AGENTS.md gains the converse 'Read down into what you touch' note: read a subdirectory's AGENTS.md (and any further nested ones) before editing it. - Dropped the explicit repo-root CONTRIBUTING pointer from patch-db's CONTRIBUTING.md (the walk-up convention now covers it). * docs(agents): require product docs/ book + CHANGELOG to ship with code Root AGENTS.md now mandates that any change altering user-visible behavior update that product's user-facing docs/ book (projects/<product>/docs/) in the same change and add a CHANGELOG.md entry — no deferring docs/changelog to follow-ups. * build(make): add per-project format targets mirroring the clean decomposition Each build.mk now owns `format-<project>` + `format-check-<project>` (core, web, sdk, cli, registry, tunnel, startos), matching the per-project `clean-<project>` targets; the top-level `format`/`format-check` just aggregate them. Web (the whole Angular workspace incl. brochure) formats via the root npm script; the shared crates via one `cargo +nightly fmt`; container-runtime via its own prettier config (new `format`/`format:check` npm scripts). So you can format one project (`make format-cli`) or all (`make format`). * docs: address PR review — make-target refs, ARCH de-dup, ts-modules wording - Reference stable make targets instead of raw build/format commands across project docs: builds via `make cli`/`registry`/`tunnel`/`startos`/`startos-ui`, formatting via the per-project `make format-<project>` targets (and `format-check-<project>` for CI). Kept `cargo check`/`cargo test` and crate `cargo build` (no make equivalent) as noted dev shortcuts. - Fixed stale targets: dropped `all` (removed upstream; `make` now prints help), `ui`/`uis` -> `startos-ui`/`startos-uis`. - Removed the whole-monorepo ASCII trees that several product ARCHITECTURE.md files re-drew (start-cli, start-registry, container-runtime, ...) — that layout lives once in the root ARCHITECTURE.md; each now states only where it sits. - Reworded the `shared-libs/ts-modules` directory as shared TypeScript modules (not Angular-specific; current contents are the Angular libs shared/marketplace), per review; kept accurate per-library 'Angular library' phrasing. Branch merged up to date with docs/monorepo-proposal first. * refactor(sdk): extract base into @start9labs/start-core shared lib; flatten start-sdk Move projects/start-sdk/base -> shared-libs/ts-modules/start-core (package @start9labs/start-sdk-base -> @start9labs/start-core), mirroring the Rust crate shared-libs/crates/start-core. start-core builds its own self-contained dist consumed via file: deps. Flatten start-sdk (package/ -> root): the SDK now imports @start9labs/start-core instead of ../../base/lib, and its published dist bundles start-core (bundleDependencies) so external authors still install one package. Repoint all consumers off the SDK-as-base alias onto @start9labs/start-core: - web (root file: dep + 153 import sites + shared/marketplace peerDeps) - container-runtime (file: dep; base/lib imports -> start-core, package/lib -> lib) Repoint osBindings generation, the build DAG (build.mk fragments, Makefile), and regenerate the three lockfiles. Resolves the "SDK kept cohesive" deviation: base is now an honest first-class shared TS lib named for what it is. * docs+ci: reflect start-core extraction; repoint SDK build steps off base/baseDist CI: build start-core (cd shared-libs/ts-modules/start-core && make dist) before validating the SDK + web lockfiles; validate the flattened SDK lockfile at projects/start-sdk; add start-core to the prettier check; repoint the iso prevent-rebuild mkdirs and the brochure deploy paths: filter (start-sdk -> start-core). Docs: update every project's AGENTS/ARCHITECTURE/CONTRIBUTING/README for the new layout — base extracted to @start9labs/start-core under shared-libs/ts-modules, the SDK flattened (lib/) and bundling start-core, container-runtime depending on both. Removes the resolved "SDK kept cohesive" deviation note. * refactor(monorepo): use the start-technologies name; repoint init-workspace at the monorepo - Adopt start-technologies for monorepo/repo-URL references across docs and AGENTS files (ahead of the GitHub repo rename; product refs left as start-os: projects/start-os, the start-os crate, *-startos packages, StartOS). - s9pk init-workspace now sparse-clones the start-technologies monorepo (projects/start-sdk/docs) instead of the retired standalone start-docs repo, and the two start-docs-named init strings are reworded across all five locales. - Un-hide the SDK 2.0 packaging-workspace section and rewrite it for the monorepo clone; document fetch-on-demand SDK + OS source access (docs first) in the workspace AGENTS.md and workflow.md. - Fix build-config.js to read and write the repo-root config.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkwYSXK8Uh9D16UTffCNJ1 --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: Matt Hill <9935159+MattDHill@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> | 2 个月前 | |
chore: rename make targets to consistent <project>-<recipe> form (#3400) * chore: rename make targets to consistent <project>-<recipe> form Every project-specific root make target now uses its full start-* directory name as the <project> prefix, and verb-first targets are reordered to project-first, so the whole target surface reads <project>-<recipe>. Examples: tunnel-deb -> start-tunnel-deb cli / startos -> start-cli / start-os startwrt-image -> start-wrt-image install-cli -> start-cli-install clean-tunnel -> start-tunnel-clean format-cli -> start-cli-format format-check-cli -> start-cli-format-check test-core -> start-core-test ts-bindings -> start-core-ts-bindings test-container-runtime -> container-runtime-test format-web -> web-format Aggregate targets (test, format, format-check, clean, help, metadata, touch) are unchanged. Non-start-* project tokens keep their own name: `web` (the Angular workspace) and `container-runtime` (a start-os sub-module); ts-bindings is treated as a start-core recipe. Updates every call site: the five product CI workflows, internal $(MAKE)/prerequisite references, help text, and all docs. The startos-iso.yaml workflow FILE and the `startos-ui` npm package name are deliberately left as-is (only the make targets they reference were renamed). * fix(deb): point debian/build.sh at the renamed <project>-install targets debian/build.sh derives INSTALL_TARGET from $PROJECT and calls `make "$INSTALL_TARGET"` to stage the binary into DESTDIR — this is how every product's .deb is built. It still used the pre-rename names (install-startos / install-<project>), which no longer exist, so all .deb builds would fail. Derive the new names instead: startos -> start-os-install, start-<x> -> start-<x>-install. | 2 个月前 | |
feat(start-os): the server's name is its .local hostname (#3791) * feat(start-os): the server's name is its .local hostname ServerInfo carried two spellings of one identity: a free-form display label shown in the browser tab, and the DNS label the .local address and the Linux system hostname are built from. The UI collected one and derived the other through normalize()/denormalize(), so the tab could read "My Cool Server" while every address the user typed said my-cool-server.local — and someone hunting for the second did not recognize the first. Drop ServerInfo.name and collapse ServerHostnameInfo into ServerHostname. The Server Name field now edits the .local hostname directly and surfaces ServerHostname::validate rather than silently normalizing input away, so the browser tab shows the address the user reaches the server by. server.set-hostname takes a hostname and nothing else, and setup execute drops --name. Removing normalize() means the field has to reject what it used to rewrite, so it applies the DNS label rules on top of validate()'s charset rule — a 63 character limit and no leading or trailing hyphen — and turns off autocapitalization, since a phone keyboard would otherwise capitalize the first letter of a name that must be lowercase. Those two rules stay in the form rather than in validate(): startd calls sync_hostname on every boot and ServerHostname::load does not validate, so tightening validate() would drop a server whose stored hostname predates the rule into diagnostic mode. The v0_4_0_2 migration drops the stored name; rolling back writes one back, title-casing the hostname the way the derivation it returns to would have. Restoring from a backup and transferring to a new drive both stop renaming the server: the wizard sent normalize("") for those flows, which is the "start9" fallback, and it overwrote the hostname the backup carried. * fix: enforce the DNS label rules where the operator supplies a hostname The 63-character and hyphen-edge rules lived only in the Angular form, so `server.set-hostname` would commit a hostname the kernel refuses: the DB write lands before `sync_hostname` runs, and `startd` calls `sync_hostname` unconditionally on every boot, so the server comes back into diagnostic mode with no way out but editing the database by hand. `ServerHostname::new_from_input` holds an operator-supplied hostname to the full rules and backs `set_hostname_rpc` and `new_opt`. `validate` and `new` keep the charset rule alone, so a hostname stored before a rule existed still loads and a legacy backup still restores. The form validator now trims, reports an empty value as `required`, and reports a bad character before a bad length, so pasting a name with a trailing space saves instead of failing on a character the user cannot see. Both forms submit the trimmed value. Also: the new CLI argument's help goes through `help.arg.hostname` in all five locales, the setup wizard's remaining validation messages are translated, the start-cli changelog records the two commands that changed, the transfer section of the device test plan no longer asks for a server name the flow does not offer, and `lan_address()` — dead on master and on this branch — is gone. * fix: cap the hostname at what the root CA's Common Name can carry The root CA is issued to `<hostname> Local Root CA`, and X.509 caps a Common Name at 64 characters, so a 51-character hostname aborts `AccountInfo::new` inside OpenSSL. That runs after the data drive is prepared, so a fresh setup died on a raw ASN.1 error and the operator had to start the install over. `MAX_LEN` is 50, and a test now asserts the branded name still fits, so the constant moves if the branding does. Also from review: `hostnameValidationErrors` carries the `required` message its validator emits, instead of each caller patching one in; the Server Name dialog marks an already-invalid hostname touched so `tui-error` explains why Save is disabled; the setup wizard's form moves to `NonNullableFormBuilder`; the start-cli changelog sections follow Keep a Changelog order; the transfer section of the device test plan points at the source device's name rather than the target's, which that section destroys; and the word list is `hostname-words.ts`, the last file still spelled for the display name. * fix: heal a hostname the kernel refuses, and say 50 everywhere The 0.4.0.2 release note still promised 63 characters after the cap moved to 50, so the notes announcing the change described a rule the code rejects. The `--hostname` help and the start-cli entry likewise named the length rule without the hyphen-edge one. An earlier version accepted a hostname longer than the kernel allows, and `sync_hostname` runs on every boot, so such a server comes up in diagnostic mode every time and the RPC that would rename it is out of reach. Diagnostic mode can still take an update, so `v0_4_0_2::up` is where it heals: `repair_hostname` keeps as much of the stored name as the rules allow and generates one when nothing usable remains. A previous round declined this on the grounds that a stranded server could not reach the migration; `diagnostic.rs` exposes `update`, so it can. The root CA test now builds a certificate instead of measuring a string, so it fails if anything inside `make_root_cert` grows rather than only the branding, and the hostname limit gets an entry under the root AGENTS.md coupled changes, since the number is restated by hand across Rust, TypeScript, five locales, five dictionaries and the docs. The two `.local` previews are signals rather than methods called on every change detection, the setup wizard declares the `Required` message its password field needs rather than borrowing it from the hostname helper, and the device test plan checks the hostname where the UI shows it. * fix: repair only a hostname the system refuses, not one the field would The repair added last round gated on `new_from_input`, which is the rule for a name an operator is typing now — at most 50 characters, no hyphen on either end. A server renamed on 0.4.0.1 could hold 63 characters: the UI allowed it, the kernel carries it, avahi publishes it, and the leaf certificate fits. Updating would have truncated that name and moved the `.local` address with it on the same boot, silently, breaking bookmarks and known_hosts on a server that was working. `repair_hostname` now returns a hostname untouched when `set_hostname` can carry it — in the character set and within `HOST_NAME_MAX` — and rewrites only what would fail on the boot path. It also strips hyphens before spending the length budget rather than after, so a name buried behind them survives. A backup restore builds its database through `Database::init`, which stamps the current version, so no migration ever runs over it; a backup taken from a server holding an unusable hostname would have restored straight into diagnostic mode. `recover_full_server` runs the hostname through the same repair. Also from review: the setup wizard's own validation messages come after the spread that was overwriting them, the start-cli entry names `setup execute --hostname` alongside `server set-hostname`, and the coupled-changes bullet drops a claim about a TS binding that does not exist, names both changelogs, and no longer separates itself from the list with a blank line. * fix: bound the untouched hostname band by what the server can actually serve Last round narrowed the repair's trigger to what the boot path refuses, on the grounds that a longer name still works — "the UI allowed it, the kernel carries it, avahi publishes it, and the leaf certificate fits". The last clause is false. A leaf certificate is issued to `<hostname>.local`, X.509 caps a Common Name at 64, and a hostname of 59 or more takes it past that: `make_leaf_cert` fails, `get_config` returns nothing, and the handshake ends in a fatal alert. So every HTTPS request to the `.local` name fails, for the StartOS UI and for every service binding, and the rename dialog that would fix it is served over the address that no longer works. rustls rejects a DNS label with a hyphen on either end, which costs a hostname its address the same way. `ServerHostname::is_usable` is that whole question in one place, and `repair_hostname` leaves a hostname alone only when it passes. The band this protects is 51 to 58 characters — a name the operator set, that serves fine, and whose address should not move under them. Above it there is no working address to protect, so the repair gives the server one. Both ceilings now derive from the Common Name limit they come from, and a test mints a real leaf certificate at the boundary, so `MAX_SERVED_LEN` moves if the `.local` suffix ever does. Also from review: the coupled-changes bullet credited the `server set-hostname` man page to a doc comment that `#[arg(help = ...)]` overrides. * fix(start-os): preserve hostname migrations after rebase Compose the hostname migration with the admin-port and ALPN changes already accumulated for 0.4.0.2, retaining every up and down path. Cover the combined migration plus restore and transfer hostname preservation. Raise start-cli to 2.0.0 for the removed and changed stable flags, refresh its lockfile and generated man pages, and normalize generated man-page whitespace at the source. * fix(start-os): cap server hostnames at 32 characters * docs(setup): restore state flow comments * refactor(start-os): scope hostname repair to migration | 6 天前 | |
feat(auth): replace cookie auth with signature auth for the web UI (#3526) * feat(start-core)!: replace session-cookie auth with enrolled signing keys The web UI and CLI now authenticate with per-request Ed25519 signatures instead of a session cookie. `auth.login` verifies the password and enrolls the caller's public key; enrolled keys carry the metadata sessions used to (enrolled-at, last-active, user agent), are reaped after 30 days idle, and are listed/revoked through the existing `auth session` commands and System > Active Sessions. - Request signatures commit to a protocol tag, timestamp, nonce, body size, and blake3(body), plus the server identity (hostname/IP/domain) in the signed bytes, using pure Ed25519. The Ed25519ph+context scheme remains for package/registry protocol signatures and is still accepted for request auth so deployed CLI and tunnel-device clients keep working. - The signature context now includes the server's own IPs, so signature auth works when the server is addressed by IP instead of hostname. - The session middleware, session token store, and cookie issuance are removed (start-os and start-tunnel); local on-box auth via the `local` cookie is unchanged. Replay-protection nonces are now actually recorded. - s9pk static-asset routes authorize by signature instead of session cookie; the unused `/proxy` relay is removed. - start-cli login generates (if needed) and enrolls the developer key. * feat(web): add request signing and device-key storage to shared TS libs `@start9labs/start-core` gains the client side of request signing (pure Ed25519 over tag || commitment || server identity, PEM header encoding matching the Rust verifier; interop locked by a test vector embedded in start-core's signature tests). `@start9labs/shared` gains an AuthKeyService holding the browser's enrolled key in localStorage. * feat(start-os): enroll a device key at web UI login The login page generates an Ed25519 key, enrolls it via auth.login, and every RPC and authorized static-asset request is signed with it. Login state is the presence of the enrolled key; an RPC 34 discards it and returns to the login page, which surfaces an explicit error when a just-enrolled key is immediately rejected. Resolves #3511. * feat(start-tunnel): enroll a device key at web UI login Same authentication move as start-os: the tunnel web UI generates and enrolls an Ed25519 key at login and signs every RPC. Device keys added via `auth key add` keep their names; UI-enrolled keys carry the enrolling user agent. v1.2.0. * fix(auth): address review findings on signature auth - signature.rs: clamp request-body pre-allocation to MAX_BODY_PREALLOC so a forged self-signed commitment can't drive an unbounded allocation before the enrollment check (unauthenticated pre-auth memory DoS). - tunnel/db.rs: rename the stale get_session/__Auth_session to get_signer/__Auth_signer so the db.subscribe continuation is bound to the signer key and torn down on logout/revocation (matches the OS side). - signature.rs/auth.rs: re-inject __Auth_userAgent on login via a get_user_agent metadata flag, so enrolled keys record their user agent again. - auth.rs: enforce that the enrolled pubkey equals the key the login request was signed with (get_signer on login + __Auth_signer), so enrollment actually proves possession of the enrolled key. - static_server.rs: authorize /s9pk asset requests against ephemeral_auth_keys too, matching check_pubkey, so kiosk/ephemeral sessions can load package assets. - start-tunnel CHANGELOG: fold the unreleased 1.1.3 fix into 1.2.0 instead of orphaning it under a heading that will never be tagged. * refactor(auth): review cleanups for signature auth Follow-up cleanups from the code review; no behavior change beyond the noted robustness fixes. Rust: - add AnyVerifyingKey::interned_pem() and use it for the ~7 duplicated InternedString::intern(key.to_string()) AuthKeys lookups - CheckPubkeyRes now carries the interned key, so post_auth_hook no longer re-encodes the pubkey to PEM on every authenticated request - factor the shared check_pubkey skeleton into check_enrolled() - rekey the replay-nonce cache: HashSet for O(1) membership plus a BTreeSet<(Instant, nonce)> for eviction, so two nonces recorded at the same Instant can't collide and evict each other - use i64::abs_diff for the timestamp-skew check so an i64::MIN timestamp can't overflow (which panics in debug/CI builds) - factor the duplicated 30-day session reaper (startup + daily cron) into reap_idle_sessions(), iterating as_entries() instead of keys()+as_idx - rename the __Auth_signer param fields from `session` to `signer` (metrics-follow, sideload, attach, db-subscribe, tunnel db-subscribe) - drop the dead rest-pattern in ListParams { signer, .. } TypeScript: - signature.ts: use @noble/hashes/utils hexToBytes/concatBytes instead of hand-rolled copies; export bytesToBase64/base64ToBytes - auth-key.service.ts: simplify get(); reuse the shared base64 helpers - add AuthKeyService.signRpcHeaders() and use it in both web clients so the "signed bytes must match the RPC body" contract lives in one place * refactor(auth): make key unenrollment a SignatureAuthContext operation Redraw the auth trait line: SignatureAuthContext owns the signer set — the enrolled-key stores, the continuations signers leave open, and now unenrollment itself. A provided `unenroll` kills a revoked key's open continuations and removes it from the ephemeral and persisted stores via a per-context `remove_enrolled_keys` hook; HasUnenrolledKeys (né HasLoggedOutKeys) is the proof token plus the in-transaction core. LoginContext (né AuthKeyContext) is only the password login surface: check_password, access_auth_keys, post_login_hook. Every revocation path now goes through unenroll, closing the ones that dropped keys without killing what they had open: tunnel `auth key remove`, the 30-day idle-session reaper (its cron now holds a Weak seed so it can reach the continuations map), and registry `admin signer remove` / `admin signer edit --remove-key`. The tunnel db's enrolled-key store is renamed authPubkeys → sessionPubkeys to match the StartOS private db, with serde(default) so a 1.1.x db upgrades with no migration — previously the field had no default, so a released-db upgrade would have failed deserialization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * refactor(auth): rename auth header to X-Start-Auth-Sig The signature-auth scheme is shared by StartOS, StartTunnel, and the registry, so the header shouldn't be OS-branded. Clients now send X-Start-Auth-Sig; the server accepts the old X-StartOS-Auth-Sig on reads so the deployed fleet — the same clients verify_request_legacy is retained for — keeps working until it upgrades. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * cleanup * fix(start-core): bind request signatures to the server identity, not the transport address The CLI pinned a .local host to its resolved IP (pin_mdns_host) before deriving rpc_url, then signed requests with the pinned IP as the signature context — while the server built its sig contexts from its gateway IPs (ip_info.lan_ip) and never its own interface addresses, so a signature bound to the server's own IP could never verify. Every Linux CLI request to a .local host failed with the misleading "no valid signature context available to verify". - CLI signs with the host identity captured before the mDNS pin - server sig contexts come from the interface subnets (own addresses), keep wan_ip for port-forwarded clients, and include localhost - IPv6 sig contexts render bracketed, as in a URL (RpcContext, tunnel listen addr and cert SANs, CLI tunnel target) - replace the opaque error with one that names the actual failure and its likely causes, and debug-log the signer plus every identity tried * fix(start-core): don't spawn the SyncMutex lock tracer without a tokio runtime * refactor(auth)!: remove cookie handling; send the local authcookie as a bearer token * fix(auth): address signature-auth frontend audit findings * feat(auth): non-extractable WebCrypto device keys + strict CSP on the web UIs * fix(web): eliminate CSP violations from critical-css inlining and zod's eval probe * fix(web): point the device key via a localStorage id so private tabs can log in * docs(start-registry): note the auth-scheme change under the unreleased 1.0.0 * Revert "fix(web): point the device key via a localStorage id so private tabs can log in" IndexedDB works in private tabs in all evergreen browsers (Firefox fixed it in 115, 2023), so the localStorage key-id indirection is unnecessary. --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> | 1 个月前 | |
chore(build): move per-product build scripts out of start-core (#3453) Each product's cross-compile script now lives in its own project dir: startbox/start-container under projects/start-os/build/, build-cli.sh, build-registrybox.sh, and build-tunnelbox.sh under their projects' build/, and build-backup-fs.sh/build-pi-beep.sh next to the crates they build. The shared builder-alias.sh moves to the top-level build/ (shared build infra). Scripts now cd to the repo root once and source build/builder-alias.sh from there. build-ts.sh and build-manpage.sh stay in start-core since they drive its own export_* tests. Each moved script is an explicit prereq of its binary rule (previously covered implicitly via CORE_SRC), and builder-alias.sh joins CORE_SRC. No CI paths: changes needed - every destination is already covered by existing globs. | 1 个月前 |
start-cli
start-cli is the command-line client for StartOS. It is a thin bin crate over
start-core (the shared Rust backend, crate start-core,
lib name start_core). The CLI surfaces the same RPC API the StartOS server exposes, plus
local developer tooling for building and signing .s9pk packages.
Most subcommands are remote calls: you point start-cli at a running StartOS server
(--host/-H) and it invokes the server's RPC API over HTTPS. A handful of commands
(s9pk, init-key, pubkey, util) run locally and are what package authors use day to day.
Quickstart
Build the binary from the monorepo root:
make start-cli # build the start-cli bin
cargo build -p start-cli --bin start-cli # dev shortcut (debug)
cargo build -p start-cli --bin start-cli --release # dev shortcut (release)
Run it:
target/debug/start-cli --help
# talk to a server
target/debug/start-cli -H https://server.local auth login
target/debug/start-cli -H https://server.local package list
# local developer tooling (no server needed)
target/debug/start-cli init-key # create a developer key
target/debug/start-cli pubkey # print its public key
target/debug/start-cli s9pk pack ... # build a package
In a StartOS image,
start-cliis provided as a symlink to the multiplexedstartboxbinary (see the OSMakefile), so it is always on the server'sPATH.
Connecting to a server
-H/--host accepts either a URL (https://server.local) or a host profile name
defined in a config file (see Profiles below). Common flags:
| Flag | Purpose |
|---|---|
-H, --host <url|profile> |
Target server URL or config profile |
--registry <url|profile> |
Target registry for registry commands |
--proxy <url> |
HTTP/SOCKS proxy for outbound requests |
--id-key-path <path> |
Identity signing key location |
--insecure |
Skip TLS verification (testing only) |
Profiles
Every config file carries named host and registry profiles:
host:
default: https://dev-vm.local
prod: https://prodbox.local
registry:
default: https://registry.start9.com
-H prod targets the prod profile; a command with no -H uses default. A bare URL is
shorthand for the default profile, so host: https://dev-vm.local and
host: { default: https://dev-vm.local } mean the same thing. -H/-r simply set the default
profile for that one invocation, on top of whatever the config files provide.
A profile's value is either a URL or the name of another profile, so default: prod (or -H prod)
points default at prod, and resolution follows the chain until it reaches a URL.
Profiles are read from these sources, highest precedence first:
-H/-ron the command line (set thedefaultprofile)- a config file named on the command line (
-c) - the nearest workspace
.startos/config.yaml(found by walking up from the current directory) ~/.startos/config.yaml/etc/startos/config.yaml
The profiles from every source combine into one namespace, and a name defined at more than one
level takes its value from the higher tier — so -H's default outranks the workspace's, which
outranks ~/.startos's. You can keep reusable profiles in ~/.startos/config.yaml and reach them
with -H from anywhere, while a workspace still overrides default for the box you're standing in,
and a host left in ~/.startos/config.yaml can't hijack the workspace's default. See
shared-libs/crates/start-core/src/context/config.rs.
Command surface
The full command tree comes from start-core::main_api(). Top-level groups include:
server,package,net,auth,db,ssh,wifi,disk,notification,backup,diagnostic,init,setup,kiosk— server management (remote).registry,tunnel— operate against a registry / StartTunnel server.s9pk,init-key,pubkey,util— local packaging/dev tooling.echo,state,git-info— diagnostics.
Run start-cli <group> --help for any group.
Features
Cargo features forward to start-core: beta, console, dev, test, unstable.
None are enabled by default.
Documentation
ARCHITECTURE.md— how the crate is built (entrypoint, request flow, config).CONTRIBUTING.md— how to contribute.AGENTS.md— agent/dev rules;CLAUDE.mdis a one-line@AGENTS.mdimport.
License
MIT. See LICENSE.