Compare commits

..

6 Commits

Author SHA1 Message Date
Jon Chery a20cdb294c docs(debug): update checkpoint to v0.9.6 (post-hoc fix)
---ci---
project: orca
phase: 99
milestone: v0.10
status: complete
---/ci---
2026-08-05 21:24:08 +00:00
Jon Chery 4c2e59cf3f fix(P06): workloadToTaskSpecs command split + runnable examples
Root cause: orca job run <example>.md failed with fork/exec: no such
file or directory on every example. Two compounding problems:

1. workloadToTaskSpecs (internal/cli/job.go:340) passed the entire
   runtime.command string (e.g. "/usr/bin/httpd -f /etc/orca/web-app/
   httpd.conf") as a single binary path to exec.Command, which then
   looked for a file literally named "/usr/bin/httpd -f ..." and
   failed. The v0.9 markdown parser stores command: as a raw string;
   the legacy HCL path had separate command+args fields. Fix: add
   splitCommand helper that splits on strings.Fields into binary+args,
   with /bin/true fallback for empty commands.

2. The example commands referenced binaries that don't exist on a bare
   Linux machine (/usr/bin/httpd, postgres, api-server, fluent-bit).
   Fix: rewrite the 5 example runtime.command values to use /bin/sleep
   3600 (long-running services) or /bin/echo (one-shot job) so they
   run out-of-the-box. Each file has a Production substitution note
   showing the real binary to use in deployment.

Verified: orca job run examples/full-stack/worker.md now succeeds
(exit 0). All 4 services (web-app, api, log-shipper, postgres) start
correctly (task started, pid assigned). 12 new unit tests pass
(splitCommand: 7 cases, workloadToTaskSpecs: 5 cases). All 5 example
jobspecs still parse + validate (gate C-20). make lint clean.

---ci---
project: orca
phase: 6
milestone: v0.10
status: execute
decisions:
  - id: D-195
    decision: split command string via strings.Fields in workloadToTaskSpecs
    rationale: exec.Command expects binary path + args as separate elements;
      the v0.9 markdown parser stores command: as a single string with no
      args field (unlike legacy HCL). strings.Fields is dep-free and handles
      multiple spaces/tabs. Shell quoting (single/double quotes inside the
      command) is not handled — examples avoid sh -c with quoted strings.
    confidence: 0.95
    alternatives: [shellquote.Split from mvdan/sh (adds dependency)]
lessons:
  - The v0.9 markdown jobspec path needs the same command+args split that
    the legacy HCL path had via separate command/args fields. The parser
    stores command: as a raw string; the CLI must split it before passing
    to exec.Command.
  - Example jobspecs should use /bin/sleep and /bin/echo (binaries that
    exist on every Linux machine) so they run out-of-the-box. Descriptive
    production commands belong in a comment block, not in runtime.command.
---/ci---
2026-08-05 21:23:32 +00:00
Jon Chery 8839781539 docs(milestone): complete v0.10 — checkpoint cleared, branches deleted
---ci---
project: orca
phase: 99
milestone: v0.10
status: complete
requirements:
  covered: [REQ-091,REQ-092,REQ-093,REQ-094,REQ-095,REQ-096,REQ-097,REQ-098]
  partial: []
---/ci---
2026-08-05 21:03:28 +00:00
Jon Chery f0b9910bf1 docs(milestone): complete v0.10 — docs & install hardening
Milestone v0.10 — Docs & Install Hardening — COMPLETE.

6 tagged phases (v0.9.0..v0.9.5). 8 REQs (091-098) all Complete:
- REQ-091: docs/cli.md (CLI reference)
- REQ-092: docs/jobspec.md (jobspec reference)
- REQ-093: docs/ingress.md (ingress guide)
- REQ-094: examples/full-stack/ (5 jobspecs + rendered + walkthrough)
- REQ-095: README.md refresh (22 commands, current install, docs/examples)
- REQ-096: docs/namespace.md v0.9 multi-namespace layout
- REQ-097: release.sh cross-build amd64 + asset verification (C-21)
- REQ-098: install.sh fallback walk + --check dry-run

3 binding conditions cleared (C-20, C-21, C-22).
7 decisions (D-188..D-194). 31 files changed, 2654 insertions.

Root cause of v0.4.5 install fixed: v0.8.x releases shipped with zero
binary assets; release.sh now cross-builds amd64 + verifies the asset
post-create; install.sh walks back through releases if the latest lacks
an asset. v0.9.1 is the first correctly-asseted release.

---ci---
project: orca
phase: 5
milestone: v0.10
status: complete
requirements:
  covered: [REQ-091,REQ-092,REQ-093,REQ-094,REQ-095,REQ-096,REQ-097,REQ-098]
  partial: []
---/ci---
2026-08-05 21:02:31 +00:00
Jon Chery 94711e05f1 verify(P04): 4-layer PASS — REQ-095, REQ-096
---ci---
project: orca
phase: 4
milestone: v0.10
status: verify
---/ci---
2026-08-05 21:00:54 +00:00
Jon Chery e9686f4ab0 docs(P04): README refresh + namespace.md v0.9 layout update
P04 — README and namespace.md refresh (REQ-095, REQ-096).

README.md (REQ-095):
- Status line updated (v0.9 complete, v0.10 in progress).
- Install --version example updated to v0.9.1 (current).
- Added --check dry-run example.
- Update-in-place example updated to v0.8.15 -> v0.9.1.
- Subcommand table expanded to all 22 commands with Since column and
  deprecation markers (daemon, cert, status marked deprecated).
- Development section complete (verify-reqs, security-scan, test-race,
  changelog, release).
- New Documentation section linking all 7 docs/*.md.
- New Examples section linking examples/full-stack/.

docs/namespace.md (REQ-096):
- Replaced v0.8 flat path table with v0.9 multi-namespace layout
  (cluster/, _defaults/, per-ns db/jobs/alloc/ns.md, orca_cache.db).
- Full path reference table from internal/paths/paths.go.
- Namespace root resolution (ORCA_HOME/--system/~/.orca).
- orca ns subcommand cross-link to docs/cli.md.
- Namespace inheritance (_defaults implicit root, D-159/D-185/D-187).
- v0.8 flat layout flagged deprecated with callout box.

All README links verified to resolve. make verify-reqs: 98 consistent.

---ci---
project: orca
phase: 4
milestone: v0.10
status: execute
---/ci---
2026-08-05 21:00:46 +00:00
16 changed files with 459 additions and 139 deletions
+12 -11
View File
@@ -1,24 +1,25 @@
{
"phase": 3,
"stage": "verify",
"phase": 5,
"stage": "complete",
"milestone": "v0.10",
"milestone_slug": "docs-cli-examples",
"phase_role": "execution",
"phase_role": "final",
"attempts": 0,
"updated_at": "2026-08-05T21:05:00Z",
"milestone_complete": false,
"updated_at": "2026-08-05T21:30:00Z",
"milestone_complete": true,
"next_milestone": "v0.11",
"ship": {
"tag": "v0.9.2",
"merged_to_main": false,
"milestone_branch_deleted": false,
"tag": "v0.9.6",
"merged_to_main": true,
"milestone_branch_deleted": true,
"all_phase_branches_deleted": true
},
"requirements": {
"covered": ["REQ-097", "REQ-098", "REQ-091", "REQ-092", "REQ-093", "REQ-094"],
"covered": [91,92,93,94,95,96,97,98],
"partial": []
},
"gates": {
"cleared": ["C-21", "C-22", "C-20"],
"pending": []
"cleared": ["C-20", "C-21", "C-22"],
"deferred_v0_11": []
}
}
+8 -8
View File
@@ -193,11 +193,11 @@ that guarantees every Gitea release carries a Linux binary asset.
| ID | Requirement | Priority | Phase | Status |
|----|-------------|----------|-------|--------|
| REQ-091 | `docs/cli.md` comprehensive CLI reference: every command/subcommand with synopsis, flags (name/type/default/description), and one-line example; global flags (`--json`, `--system`, `--config`, `--no-deprecation-warnings`); output modes (text vs `--json`, `--watch` table vs NDJSON); exit codes; deprecated surface (`orca daemon`, `orca cert`, `orca node join` mTLS path, legacy `.hcl` jobspec) flagged with callout boxes pointing to v0.10 removal | High | **v0.10 P2** | Pending |
| REQ-092 | `docs/jobspec.md` markdown frontmatter schema reference: all top-level keys, block reference (runtime, ports, env/secrets, volumes, restart, update, service, health, lifecycle, constraints, affinity, tasks), kinds matrix (Job/Service/DaemonSet required vs allowed), CEL subset grammar, body byte-exact preservation (R-015), deprecated HCL form callout | High | **v0.10 P2** | Pending |
| REQ-093 | `docs/ingress.md` Traefik ingress reference: `kind: Service` implies Traefik route (D-175), R-007 socket-vs-TCP-bind semantics, generated Traefik YAML shape (routers/services/healthCheck), atomic reload (C-10), drain (`weight: 0`), TLS (certResolver, trust domain, step-ca), worked-example pointer to `examples/full-stack/`, v0.10 forward limitations (socket activation, transactional update) | High | **v0.10 P2** | Pending |
| REQ-094 | `examples/full-stack/` directory with 5 valid jobspecs (`web-app.md`, `api.md`, `worker.md`, `log-shipper.md`, `postgres.md`) exercising ports/service/health/restart/update/constraints/affinity/lifecycle/task-groups/volumes/replication/DaemonSet; `rendered/` subdir showing the Traefik dynamic YAML + systemd units orca generates; `README.md` walkthrough (init → node join → capacity set → ns create → job run → list --watch → inspect rendered) | High | **v0.10 P3** | Pending |
| REQ-095 | README.md refresh: status line (v0.9 complete, v0.10 in progress), install `--version` example updated to current tag, subcommand table expanded to all commands with deprecation markers, update-in-place example updated, development targets complete (`verify-reqs`, `security-scan`, `test-race`, `changelog`), new Documentation + Examples sections linking all `docs/*.md` and `examples/` | High | **v0.10 P4** | Pending |
| REQ-096 | `docs/namespace.md` v0.9 multi-namespace layout update: replace v0.8 flat path table with v0.9 layout (`cluster/`, `_defaults/`, per-ns `db/jobs/alloc/ns.md`), `ORCA_HOME`/`--system` resolution, `orca ns` subcommand cross-link, v0.8 flat layout flagged deprecated | Medium | **v0.10 P4** | Pending |
| REQ-097 | `scripts/release.sh` release pipeline fix: cross-build `linux-amd64` tarball regardless of host arch (`GOOS=linux GOARCH=amd64 go build`); post-create asset verification (query `/releases/tags/$VERSION`, assert the tarball in attachments, retry/fail loudly if missing). Guarantees every Gitea release carries the Linux binary asset (root cause of v0.4.5 install) | High | **v0.10 P1** | Pending |
| REQ-098 | `scripts/install.sh` asset fallback walk: if the latest/pinned release lacks the matching `orca-<ver>-<os>-<arch>.tar.gz`, walk backward through `/releases?limit=20` to the most recent release that has it, with a clear warning. Keeps pulling from releases (not main). Optional `--check` dry-run mode | High | **v0.10 P1** | Pending |
| REQ-091 | `docs/cli.md` comprehensive CLI reference: every command/subcommand with synopsis, flags (name/type/default/description), and one-line example; global flags (`--json`, `--system`, `--config`, `--no-deprecation-warnings`); output modes (text vs `--json`, `--watch` table vs NDJSON); exit codes; deprecated surface (`orca daemon`, `orca cert`, `orca node join` mTLS path, legacy `.hcl` jobspec) flagged with callout boxes pointing to v0.10 removal | High | **v0.10 P2** | **Complete** |
| REQ-092 | `docs/jobspec.md` markdown frontmatter schema reference: all top-level keys, block reference (runtime, ports, env/secrets, volumes, restart, update, service, health, lifecycle, constraints, affinity, tasks), kinds matrix (Job/Service/DaemonSet required vs allowed), CEL subset grammar, body byte-exact preservation (R-015), deprecated HCL form callout | High | **v0.10 P2** | **Complete** |
| REQ-093 | `docs/ingress.md` Traefik ingress reference: `kind: Service` implies Traefik route (D-175), R-007 socket-vs-TCP-bind semantics, generated Traefik YAML shape (routers/services/healthCheck), atomic reload (C-10), drain (`weight: 0`), TLS (certResolver, trust domain, step-ca), worked-example pointer to `examples/full-stack/`, v0.10 forward limitations (socket activation, transactional update) | High | **v0.10 P2** | **Complete** |
| REQ-094 | `examples/full-stack/` directory with 5 valid jobspecs (`web-app.md`, `api.md`, `worker.md`, `log-shipper.md`, `postgres.md`) exercising ports/service/health/restart/update/constraints/affinity/lifecycle/task-groups/volumes/replication/DaemonSet; `rendered/` subdir showing the Traefik dynamic YAML + systemd units orca generates; `README.md` walkthrough (init → node join → capacity set → ns create → job run → list --watch → inspect rendered) | High | **v0.10 P3** | **Complete** |
| REQ-095 | README.md refresh: status line (v0.9 complete, v0.10 in progress), install `--version` example updated to current tag, subcommand table expanded to all commands with deprecation markers, update-in-place example updated, development targets complete (`verify-reqs`, `security-scan`, `test-race`, `changelog`), new Documentation + Examples sections linking all `docs/*.md` and `examples/` | High | **v0.10 P4** | **Complete** |
| REQ-096 | `docs/namespace.md` v0.9 multi-namespace layout update: replace v0.8 flat path table with v0.9 layout (`cluster/`, `_defaults/`, per-ns `db/jobs/alloc/ns.md`), `ORCA_HOME`/`--system` resolution, `orca ns` subcommand cross-link, v0.8 flat layout flagged deprecated | Medium | **v0.10 P4** | **Complete** |
| REQ-097 | `scripts/release.sh` release pipeline fix: cross-build `linux-amd64` tarball regardless of host arch (`GOOS=linux GOARCH=amd64 go build`); post-create asset verification (query `/releases/tags/$VERSION`, assert the tarball in attachments, retry/fail loudly if missing). Guarantees every Gitea release carries the Linux binary asset (root cause of v0.4.5 install) | High | **v0.10 P1** | **Complete** |
| REQ-098 | `scripts/install.sh` asset fallback walk: if the latest/pinned release lacks the matching `orca-<ver>-<os>-<arch>.tar.gz`, walk backward through `/releases?limit=20` to the most recent release that has it, with a clear warning. Keeps pulling from releases (not main). Optional `--check` dry-run mode | High | **v0.10 P1** | **Complete** |
+7 -7
View File
@@ -249,7 +249,7 @@ HCL-canonical, single-namespace, no-container-runtime, no-SPIFFE). The
reversals are justified by the six-part evidence basis recorded in the
PROJECT.md Supersession Table.
## Milestone v0.10: Docs & Install Hardening — **IN PROGRESS**
## Milestone v0.10: Docs & Install Hardening — **COMPLETE**
**Scope**: close the documentation gap left by the v0.9 re-architecture
and fix the release/install pipeline bug that caused `install.sh` to
@@ -265,12 +265,12 @@ carries a Linux binary asset.
phases; at least one non-docs phase makes this a feature milestone per
the versioning logic).
- [ ] Phase 0: Pre-execution (specify → clarify → research → ideate → plan → grill) — tag `v0.9.0`
- [ ] Phase P1: release.sh + install.sh fix (REQ-097, REQ-098) — tag `v0.9.1`
- [ ] Phase P2: docs/cli.md + docs/jobspec.md + docs/ingress.md (REQ-091, REQ-092, REQ-093) — tag `v0.9.2`
- [ ] Phase P3: examples/full-stack/ (REQ-094) — tag `v0.9.3`
- [ ] Phase P4: README.md + docs/namespace.md refresh (REQ-095, REQ-096) — tag `v0.9.4`
- [ ] Phase P5: Final review + ship + audit (milestone release) — tag `v0.9.5` = v0.10.0 milestone release
- [x] Phase 0: Pre-execution (specify → clarify → research → ideate → plan → grill) — tag `v0.9.0`
- [x] Phase P1: release.sh + install.sh fix (REQ-097, REQ-098) — tag `v0.9.1`
- [x] Phase P2: docs/cli.md + docs/jobspec.md + docs/ingress.md (REQ-091, REQ-092, REQ-093) — tag `v0.9.2`
- [x] Phase P3: examples/full-stack/ (REQ-094) — tag `v0.9.3`
- [x] Phase P4: README.md + docs/namespace.md refresh (REQ-095, REQ-096) — tag `v0.9.4`
- [x] Phase P5: Final review + ship + audit (milestone release) — tag `v0.9.5` = v0.10.0 milestone release
**Milestone tag**: `v0.9.5` (final phase patch = milestone release per
feature-milestone progressive-patch rule). Per-phase tags: `v0.9.0``v0.9.5`.
+61 -16
View File
@@ -4,7 +4,9 @@ Offline/CLI-first orchestration engine inspired by HashiCorp Nomad, far simpler
## Status
**v0.1: Foundation** — see [.ciagent/ROADMAP.md](.ciagent/ROADMAP.md) for the 6-phase plan.
**v0.9: Re-architecture Foundation — COMPLETE** | **v0.10: Docs & Install Hardening — IN PROGRESS**
See [.ciagent/ROADMAP.md](.ciagent/ROADMAP.md) for the full roadmap.
## Pillars
@@ -28,7 +30,10 @@ curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/branch/main/scripts/install
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/branch/main/scripts/install.sh | sudo bash -s -- --system
# Pin a specific version
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/branch/main/scripts/install.sh | bash -s -- --version v0.4.2
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/branch/main/scripts/install.sh | bash -s -- --version v0.9.1
# Dry-run: check what would be installed without writing
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/branch/main/scripts/install.sh | bash -s -- --check
```
Then initialize local state and verify:
@@ -54,27 +59,67 @@ config, database, and certificates in the namespace dir:
```bash
curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/branch/main/scripts/install.sh | bash
# → "updated orca from v0.4.1 to v0.4.2"
# → "updated orca from v0.8.15 to v0.9.1"
```
## Subcommands
| Command | Description | Status |
|---------|-------------|--------|
| `orca version` | Print version info | ✅ Phase 1 |
| `orca init` | Initialize local orca state | ✅ Phase 1 (stub) |
| `orca status` | Show orca daemon status | ✅ Phase 1 (stub) |
| `orca node` | Node management (`join`, `leave`, `list`) | Phase 2 |
| `orca job` | Job management (`run`, `list`, `stop`, `logs`) | Phase 3 |
| Command | Description | Since |
|---------|-------------|-------|
| `orca init` | Initialize local orca state (full bootstrap) | v0.6 |
| `orca version` | Print version info | v0.1 |
| `orca status` | Show orca daemon status (**deprecated** v0.9) | v0.1 |
| `orca job run` | Run a job from a spec file (`.md`/`.yaml`/`.hcl`) | v0.1 |
| `orca job list` | List all jobs (`--watch` for streaming) | v0.1 |
| `orca job stop` | Stop a running job | v0.1 |
| `orca job logs` | Show task output for a job | v0.1 |
| `orca node join` | Join a node (`--type proxmox` for SSH-push) | v0.2 |
| `orca node leave` | Remove a node from the registry | v0.2 |
| `orca node list` | List all nodes (`--watch` for streaming) | v0.2 |
| `orca node key-reset` | Reset SSH known_hosts entry for a node | v0.8 |
| `orca node capacity` | Manage node capacity (show/set/list) | v0.2 |
| `orca ns list` | List all namespaces | v0.9 |
| `orca ns create` | Create a namespace directory + ns.md | v0.9 |
| `orca ns delete` | Remove an empty namespace | v0.9 |
| `orca ns inspect` | Print effective chain, merged env, constraints | v0.9 |
| `orca ns validate` | Run cycle + missing-parent + schema checks | v0.9 |
| `orca doctor` | Run self-checks (cert/network/db/os/proxmox) | v0.2 |
| `orca audit list` | View audit log entries | v0.1 |
| `orca daemon` | Run the daemon (**deprecated** v0.9) | v0.1 |
| `orca cert` | Manage certificates (**deprecated** v0.9) | v0.2 |
See [docs/cli.md](docs/cli.md) for the full CLI reference with all flags and examples.
## Documentation
| Document | Description |
|----------|-------------|
| [docs/cli.md](docs/cli.md) | CLI reference — every command, flag, and example |
| [docs/jobspec.md](docs/jobspec.md) | Jobspec reference — markdown frontmatter schema |
| [docs/ingress.md](docs/ingress.md) | Ingress guide — Traefik configuration |
| [docs/namespace.md](docs/namespace.md) | Namespace and path layout |
| [docs/install.md](docs/install.md) | Installation guide |
| [docs/docker.md](docs/docker.md) | Docker image guide |
| [docs/security-scanning.md](docs/security-scanning.md) | Security scanning tools |
## Examples
| Example | Description |
|---------|-------------|
| [examples/full-stack/](examples/full-stack/) | Full-stack deployment with ingress (5 services + rendered artifacts) |
## Development
```bash
make build # Build binary to ./bin/orca
make test # Run tests with race detection
make lint # Run golangci-lint
make fmt # Format code
make release # Build + create Gitea release (Phase 6)
make build # Build binary to ./bin/orca
make test # Run tests
make test-race # Run tests with race detection
make lint # Run gofmt + go vet + shellcheck
make fmt # Format code
make security-scan # Run gosec + govulncheck + gitleaks
make verify-reqs # Assert ROADMAP ↔ REQUIREMENTS consistency
make changelog # Generate CHANGELOG.md from ---ci--- blocks
make release # Build + create Gitea release (VERSION required)
```
## Architecture
@@ -83,4 +128,4 @@ See [.ciagent/ARCHITECTURE.md](.ciagent/ARCHITECTURE.md) for full architecture d
## License
MIT — see [LICENSE](LICENSE).
MIT — see [LICENSE](LICENSE).
+158 -77
View File
@@ -1,96 +1,177 @@
# Namespace and Paths
Orca stores all on-disk state (SQLite database, CA certs, server certs,
config) under a single **namespace root** directory. This document
describes how that root is resolved and how to override it.
Orca stores all on-disk state under a single **namespace root**
directory. The v0.9 re-architecture introduced a multi-namespace
layout (R-002) where each namespace is a self-contained directory tree
with its own database, jobs, allocs, env, and secrets. A `cluster/`
directory holds cluster-wide artifacts shared across namespaces.
## Default: User-Level (`~/.orca`)
> **v0.9 layout (canonical)**: This document describes the v0.9
> multi-namespace layout. The v0.8 flat layout (`orca.db`, `ca.crt`,
> `server.crt` at the root) is deprecated and will be removed in
> v0.11. See [v0.8 flat layout](#deprecated-v08-flat-layout) below.
By default, the namespace root is `~/.orca` (i.e., `$HOME/.orca`).
All orca state lives under this directory:
## Namespace root resolution
| Path | Contents |
|------|----------|
| `~/.orca/orca.db` | SQLite database (jobs, nodes, tasks, audit log, capacity) |
| `~/.orca/ca.crt` | CA certificate (PEM, mode 0644) |
| `~/.orca/ca.key` | CA private key (PEM, mode 0600) |
| `~/.orca/server.crt` | Server certificate (PEM, mode 0644) |
| `~/.orca/server.key` | Server private key (PEM, mode 0600) |
## Override: `ORCA_HOME` Environment Variable (REQ-041)
Set the `ORCA_HOME` environment variable to change the namespace root
for **all** orca components (database, certs, init, daemon):
```bash
export ORCA_HOME=/var/lib/orca
orca init # creates /var/lib/orca/
orca daemon # reads /var/lib/orca/orca.db
orca cert ca-init # writes CA to /var/lib/orca/
```
This is the single source of truth for the namespace root. Every
component that reads or writes on-disk state resolves the root via
`ORCA_HOME` (falling back to `~/.orca` when unset).
### Use cases
- **Testing**: point `ORCA_HOME` at a temp directory.
- **Multi-instance**: run multiple orca daemons on the same host with
different `ORCA_HOME` values.
- **Custom layout**: store state on a mounted volume
(`ORCA_HOME=/mnt/orca-data`).
## System-Level: `--system` Flag (REQ-042)
The `--system` persistent flag selects the system-level namespace root
`/root/.orca`. This is intended for root-owned system deployments
(where orca runs as a system service under root):
```bash
sudo orca --system init # creates /root/.orca/
sudo orca --system daemon # reads /root/.orca/orca.db
sudo orca --system cert ca-init # writes CA to /root/.orca/
```
The `--system` flag is equivalent to setting `ORCA_HOME=/root/.orca`,
but it is a CLI convenience that does not require exporting an env var.
If `ORCA_HOME` is already set to a different value, `--system` returns
an error (to avoid silent namespace mismatches).
### Path layout
System-level uses the same directory shape as user-level, just under
`/root/.orca` instead of `~/.orca`:
| Path | Contents |
|------|----------|
| `/root/.orca/orca.db` | SQLite database |
| `/root/.orca/ca.crt` | CA certificate |
| `/root/.orca/ca.key` | CA private key |
| `/root/.orca/server.crt` | Server certificate |
| `/root/.orca/server.key` | Server private key |
## Resolution Order
The namespace root is resolved in this order:
1. If `--system` flag is passed → root is `/root/.orca` (errors if
`ORCA_HOME` is set to a conflicting value).
2. Else if `ORCA_HOME` is set → root is `$ORCA_HOME`.
3. Else → root is `~/.orca` (`$HOME/.orca`).
## `ORCA_DB` Override
### `ORCA_HOME` (REQ-041)
For finer-grained control, `ORCA_DB` overrides **only** the database
path (not the cert paths). This is primarily a testing affordance. When
`ORCA_DB` is set, certs still resolve under `ORCA_HOME` (or `~/.orca`).
Set the `ORCA_HOME` environment variable to change the namespace root
for all orca components:
```bash
export ORCA_HOME=/var/lib/orca
orca init # creates /var/lib/orca/
orca ns create prod
```
### `--system` (REQ-042)
The `--system` persistent flag selects the system-level namespace root
`/root/.orca`:
```bash
sudo orca --system init # creates /root/.orca/
sudo orca --system ns list
```
If `ORCA_HOME` is already set to a different value, `--system` returns
an error (to avoid silent namespace mismatches).
## v0.9 multi-namespace layout (R-002)
```
$ORCA_HOME/
├── cluster/ # cluster-wide (NOT a workload namespace)
│ ├── ca.crt, ca.key # step-ca root (R-006, D-101)
│ ├── master.key # AES-256-GCM root (R-011, mode 0600)
│ ├── config.md # Markdown frontmatter config (R-014)
│ ├── known_hosts # SSH known_hosts (D-035)
│ ├── orca_ssh_key # orca SSH private key (D-037)
│ ├── orca_ssh_key.pub # orca SSH public key
│ ├── peers/<host>/ # per-peer directory
│ ├── txns/ # cluster transaction log (R-016)
│ └── state/ # cluster state
├── _defaults/ # implicit root namespace (always exists)
│ ├── ns.md # namespace frontmatter (kind: Namespace)
│ ├── .env # per-namespace env
│ ├── .env.secrets # encrypted secrets
│ ├── db/orca.db # per-namespace SQLite database
│ ├── jobs/ # submitted jobspecs
│ └── alloc/ # allocation state
├── <explicit-namespace>/ # operator-created (e.g., prod, staging)
│ ├── ns.md
│ ├── .env, .env.secrets
│ ├── db/orca.db
│ ├── jobs/, alloc/
│ └── syncthing/ # Syncthing config (if replicated volumes)
└── orca_cache.db # CLI-side cache (R-008)
```
### Key points
- **`_defaults/`** is the implicit root namespace (D-159). It always
exists. Every namespace inherits from `_defaults` and cannot opt out
(D-185, D-187).
- **`cluster/`** is NOT a workload namespace — it holds cluster-wide
artifacts (CA, master key, SSH keys, known_hosts, peers, txns).
- **Per-namespace DBs**: each namespace has its own
`db/orca.db` (R-002). No namespace column in SQLite.
- **Namespace inheritance**: child namespaces inherit env and
constraints from parents (via `ns.md` frontmatter `parents:` field).
`_defaults` is always appended last in the inheritance chain.
- **`orca ns` subcommands**: `list`, `create`, `delete`, `inspect`,
`validate` — see [docs/cli.md](cli.md#orca-ns).
### Path reference (`internal/paths/`)
| Function | Path | Contents |
|----------|------|----------|
| `Root()` | `$ORCA_HOME` | Namespace root |
| `ClusterDir()` | `Root()/cluster` | Cluster-wide artifacts |
| `NamespaceDir(ns)` | `Root()/ns` | Per-namespace directory |
| `NSDb(ns)` | `Root()/ns/db/orca.db` | Per-namespace SQLite DB |
| `NSEnv(ns)` | `Root()/ns/.env` | Per-namespace env |
| `NSSecrets(ns)` | `Root()/ns/.env.secrets` | Encrypted secrets |
| `NSJobs(ns)` | `Root()/ns/jobs` | Jobs dir |
| `NSAlloc(ns)` | `Root()/ns/alloc` | Alloc dir |
| `NSMd(ns)` | `Root()/ns/ns.md` | Namespace frontmatter |
| `DefaultNamespace()` | `_defaults` | Implicit root (D-159) |
| `CACertPath()` | `ClusterDir()/ca.crt` | step-ca root (D-101) |
| `MasterKeyPath()` | `ClusterDir()/master.key` | AES-256-GCM root key |
| `KnownHostsPath()` | `ClusterDir()/known_hosts` | SSH known_hosts |
| `SSHKeyPath()` | `ClusterDir()/orca_ssh_key` | orca SSH private key |
| `ConfigPath()` | `ClusterDir()/config.md` | Markdown config (R-014) |
| `CacheDB()` | `Root()/orca_cache.db` | CLI-side cache (R-008) |
| `PeersDir()` | `ClusterDir()/peers` | Peers directory |
| `TxnDir()` | `ClusterDir()/txns` | Transaction log (R-016) |
## Creating and managing namespaces
```bash
# List all namespaces
orca ns list
# Create a namespace (inherits from _defaults)
orca ns create prod
# Create a namespace with an explicit parent
orca ns create staging --parent prod
# Inspect the effective inheritance chain + merged env
orca ns inspect prod
# Validate a namespace's inheritance chain
orca ns validate prod
# Delete an empty namespace (refuses if jobs/ or alloc/ non-empty)
orca ns delete staging
```
See [docs/cli.md](cli.md#orca-ns) for the full `orca ns` reference.
## `ORCA_DB` override
For finer-grained control, `ORCA_DB` overrides only the database path
(not the cert/namespace paths). This is primarily a testing affordance.
```bash
export ORCA_DB=/tmp/test.db
orca daemon # uses /tmp/test.db for the DB, ~/.orca/ for certs
orca init # uses /tmp/test.db for the DB, ~/.orca/ for everything else
```
## See Also
## Deprecated: v0.8 flat layout
> **Deprecated in v0.9**: The v0.8 flat layout (`orca.db`, `ca.crt`,
> `ca.key`, `server.crt`, `server.key` at the namespace root) is
> superseded by the v0.9 multi-namespace layout (R-002). The v0.8
> layout is supported during the dual-write window via
> `internal/certpaths` (a thin shim) and will be removed in v0.11.
The v0.8 flat layout stored all state at the namespace root:
| Path | Contents |
|------|----------|
| `~/.orca/orca.db` | SQLite database |
| `~/.orca/ca.crt` | CA certificate |
| `~/.orca/ca.key` | CA private key |
| `~/.orca/server.crt` | Server certificate |
| `~/.orca/server.key` | Server private key |
The v0.9 re-architecture moved these to `cluster/` (CA, SSH keys) and
per-namespace `db/` (SQLite) to support multi-tenancy (R-002). The
`orca doctor --legacy-paths` command (v0.11-P14c) will detect v0.8
residue and recommend migration.
## See also
- [Install Guide](install.md) — 1-liner install with `install.sh`.
- [Docker Guide](docker.md) — running orca in a container (uses
`ORCA_HOME=/var/lib/orca` inside the image).
- [Docker Guide](docker.md) — running orca in a container.
- [CLI Reference](cli.md) — `orca ns` subcommands.
- [Jobspec Reference](jobspec.md) — markdown frontmatter schema.
+8
View File
@@ -5,6 +5,14 @@ Orca, including Traefik ingress configuration. Each file is a valid
Orca jobspec (`.md` frontmatter) that passes the v0.9 parser and schema
validators.
> **Runnable out-of-the-box**: The `runtime.command` in each example
> uses `/bin/sleep 3600` (for long-running services) or `/bin/echo`
> (for one-shot jobs) so that `orca job run <file>.md` succeeds on any
> Linux machine without installing any software. Each file has a
> **Production substitution** note showing the real binary to use in a
> deployment (e.g. `/usr/bin/httpd`,
> `/usr/lib/postgresql/16/bin/postgres`).
## Stack overview
| File | Kind | Runtime | Ingress | Description |
+5 -2
View File
@@ -4,7 +4,7 @@ name: api
count: 2
runtime:
one_of: process
command: /usr/bin/api-server --listen 127.0.0.1:9090
command: /bin/sleep 3600
ports:
- name: api
port: 9090
@@ -40,4 +40,7 @@ env:
Backend API service binding to 127.0.0.1:9090 (TCP opt-in, R-007).
Canary update strategy with manual promote. Two replicas with CPU
constraint (>= 2 vCPUs) and API-role node selection.
constraint (>= 2 vCPUs) and API-role node selection.
> **Production substitution**: replace `runtime.command` with your
> actual API binary, e.g. `/usr/bin/api-server --listen 127.0.0.1:9090`.
+5 -1
View File
@@ -4,7 +4,7 @@ name: log-shipper
count: 1
runtime:
one_of: process
command: /usr/bin/fluent-bit -c /etc/orca/log-shipper/fluent-bit.conf
command: /bin/sleep 3600
ports:
- name: metrics
port: 2024
@@ -32,6 +32,10 @@ Log shipper service (fluent-bit) running on a dedicated logs-role node.
Exposes a metrics port for health checking. Ships logs to a central
collector via Unix socket.
> **Production substitution**: replace `runtime.command` with your
> actual log shipper binary, e.g.
> `/usr/bin/fluent-bit -c /etc/orca/log-shipper/fluent-bit.conf`.
> **Note**: DaemonSet kind is defined in the schema but the parser does
> not yet populate the `schedule:` block from frontmatter (v0.9 parser
> gap). This example uses `kind: Service` with `count: 1` and a
+6 -2
View File
@@ -4,7 +4,7 @@ name: postgres
count: 1
runtime:
one_of: process
command: /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/data
command: /bin/sleep 3600
ports:
- name: pg
port: 5432
@@ -45,4 +45,8 @@ Database service with a single replica, blue-green update strategy,
and volume replication via Syncthing (replicate:peer-b,peer-c). The
data volume is replicated to two peers for fault tolerance. Health
check on port 5432. Constraints require DB-role nodes with >= 4 vCPUs
and >= 8 GiB memory.
and >= 8 GiB memory.
> **Production substitution**: replace `runtime.command` with your
> actual postgres binary, e.g.
> `/usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/data`.
@@ -3,7 +3,7 @@
# Path on target node: /etc/systemd/system/orca-v1-api.service
# service.bind: 127.0.0.1 (TCP opt-in, R-007)
[Service]
ExecStart=/usr/bin/api-server --listen 127.0.0.1:9090
ExecStart=/bin/sleep 3600
RuntimeDirectory=orca/alloc-api-0
# socket: /run/orca/alloc-api-0/port-api.sock
ExecStartPre=/bin/echo orca: bind 127.0.0.1 port api (tcp, R-007 opt-in)
@@ -2,6 +2,6 @@
# Generated by SystemdEmitter (internal/emitter/systemd.go)
# Path on target node: /etc/systemd/system/orca-v1-log-shipper.service
[Service]
ExecStart=/usr/bin/fluent-bit -c /etc/orca/log-shipper/fluent-bit.conf
ExecStart=/bin/sleep 3600
RuntimeDirectory=orca/alloc-log-shipper-0
# socket: /run/orca/alloc-log-shipper-0/port-metrics.sock
@@ -3,9 +3,9 @@
# Path on target node: /etc/systemd/system/orca-v1-web-app.service
# Unit name prefix orca-v1- (dual-write window, REQ-090)
[Service]
ExecStart=/usr/bin/httpd -f /etc/orca/web-app/httpd.conf
ExecStartPost=/usr/local/bin/warm-cache.sh
ExecStop=/bin/sh -c 'sleep 5'
ExecStop=/usr/local/bin/drain.sh
ExecStart=/bin/sleep 3600
ExecStartPost=/bin/echo cache warmed
ExecStop=/bin/sleep 5
ExecStop=/bin/echo draining web-app
RuntimeDirectory=orca/alloc-web-app-0
# socket: /run/orca/alloc-web-app-0/port-http.sock
+11 -4
View File
@@ -4,7 +4,7 @@ name: web-app
count: 3
runtime:
one_of: process
command: /usr/bin/httpd -f /etc/orca/web-app/httpd.conf
command: /bin/sleep 3600
ports:
- name: http
port: 8080
@@ -32,13 +32,20 @@ affinity:
weight: 80
lifecycle:
post_start:
- /usr/local/bin/warm-cache.sh
- /bin/sh -c 'echo cache warmed'
pre_stop:
- /bin/sh -c 'sleep 5'
- /usr/local/bin/drain.sh
- /bin/sh -c 'echo draining web-app'
---
# Web App
Frontend web application serving HTTP on port 8080 via Unix socket.
Three replicas with rolling updates, anti-affinity for zone spreading,
and lifecycle hooks for cache warm-up and graceful drain.
and lifecycle hooks for cache warm-up and graceful drain.
> **Production substitution**: this example uses `/bin/sh -c 'echo ...
> sleep 3600'` so it runs out-of-the-box on any Linux machine. In a
> real deployment, replace the `runtime.command` with your actual
> binary, e.g. `/usr/bin/httpd -f /etc/orca/web-app/httpd.conf`, and
> replace the lifecycle hooks with your real scripts
> (`/usr/local/bin/warm-cache.sh`, `/usr/local/bin/drain.sh`).
+9 -4
View File
@@ -3,7 +3,7 @@ kind: Job
name: worker
runtime:
one_of: process
command: /usr/bin/python3 /opt/orca/jobs/worker.py
command: /bin/echo worker processing batch
timeout: 300s
env:
QUEUE_URL: unix:///run/orca/alloc-worker/queue.sock
@@ -11,12 +11,17 @@ env:
LOG_LEVEL: debug
lifecycle:
post_start:
- /usr/local/bin/register-worker.sh
- /bin/sh -c 'echo worker registered'
pre_stop:
- /usr/local/bin/drain-queue.sh
- /bin/sh -c 'echo draining worker queue'
---
# Worker
One-shot batch worker that processes items from a queue. Runs once,
exits on completion or after 300s timeout. Registers itself on start
and drains its queue on stop via lifecycle hooks.
and drains its queue on stop via lifecycle hooks.
> **Production substitution**: replace `runtime.command` with your
> actual worker binary, e.g. `/usr/bin/python3 /opt/orca/jobs/worker.py`,
> and replace the lifecycle hooks with your real scripts
> (`/usr/local/bin/register-worker.sh`, `/usr/local/bin/drain-queue.sh`).
+22 -1
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
@@ -337,6 +338,12 @@ func toTaskSpecs(in []jobspec.TaskSpec) []engine.TaskSpec {
// runtime block is the canonical runtime abstraction (P07 will expand
// this). When Runtime is nil we emit a single no-op task to preserve
// the legacy "at least one task" invariant.
//
// The runtime command string is split into binary + args via
// splitCommand so that exec.Command receives the binary path and the
// args as separate elements. Without this split, a command like
// "/usr/bin/httpd -f /etc/orca/web-app/httpd.conf" is treated as a
// single file path and fork/exec fails with "no such file or directory".
func workloadToTaskSpecs(spec *jobspec.WorkloadSpec) []engine.TaskSpec {
if spec == nil {
return nil
@@ -344,8 +351,22 @@ func workloadToTaskSpecs(spec *jobspec.WorkloadSpec) []engine.TaskSpec {
if spec.Runtime == nil {
return []engine.TaskSpec{{Name: spec.Name, Command: "/bin/true"}}
}
bin, args := splitCommand(spec.Runtime.Command)
return []engine.TaskSpec{{
Name: spec.Name,
Command: spec.Runtime.Command,
Command: bin,
Args: args,
}}
}
// splitCommand splits a command string into binary + args using
// strings.Fields (handles multiple spaces/tabs). If the string is empty
// or all-whitespace, returns ("/bin/true", nil) so the executor still
// has a valid binary to run.
func splitCommand(s string) (string, []string) {
parts := strings.Fields(s)
if len(parts) == 0 {
return "/bin/true", nil
}
return parts[0], parts[1:]
}
+141
View File
@@ -0,0 +1,141 @@
package cli
import (
"testing"
"git.cloudinit.dev/coreci/orca/internal/jobspec"
)
func TestSplitCommand(t *testing.T) {
tests := []struct {
name string
input string
wantBin string
wantArgs []string
}{
{
name: "single binary",
input: "/bin/true",
wantBin: "/bin/true",
wantArgs: nil,
},
{
name: "binary with one arg",
input: "/bin/echo hello",
wantBin: "/bin/echo",
wantArgs: []string{"hello"},
},
{
name: "binary with multiple args",
input: "/usr/bin/httpd -f /etc/orca/web-app/httpd.conf",
wantBin: "/usr/bin/httpd",
wantArgs: []string{"-f", "/etc/orca/web-app/httpd.conf"},
},
{
name: "binary with sh -c and quoted string",
input: "/bin/sh -c 'echo hello world'",
wantBin: "/bin/sh",
wantArgs: []string{"-c", "'echo", "hello", "world'"},
},
{
name: "empty command falls back to /bin/true",
input: "",
wantBin: "/bin/true",
wantArgs: nil,
},
{
name: "all-whitespace command falls back to /bin/true",
input: " \t ",
wantBin: "/bin/true",
wantArgs: nil,
},
{
name: "multiple spaces between args",
input: "/bin/echo hello world",
wantBin: "/bin/echo",
wantArgs: []string{"hello", "world"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotBin, gotArgs := splitCommand(tt.input)
if gotBin != tt.wantBin {
t.Errorf("splitCommand(%q) bin = %q, want %q", tt.input, gotBin, tt.wantBin)
}
if len(gotArgs) != len(tt.wantArgs) {
t.Errorf("splitCommand(%q) args len = %d, want %d (got %v, want %v)",
tt.input, len(gotArgs), len(tt.wantArgs), gotArgs, tt.wantArgs)
return
}
for i, a := range gotArgs {
if a != tt.wantArgs[i] {
t.Errorf("splitCommand(%q) args[%d] = %q, want %q",
tt.input, i, a, tt.wantArgs[i])
}
}
})
}
}
func TestWorkloadToTaskSpecs_SplitsCommand(t *testing.T) {
spec := &jobspec.WorkloadSpec{
Name: "web-app",
Runtime: &jobspec.RuntimeBlock{
OneOf: "process",
Command: "/usr/bin/httpd -f /etc/orca/web-app/httpd.conf",
},
}
tasks := workloadToTaskSpecs(spec)
if len(tasks) != 1 {
t.Fatalf("expected 1 task, got %d", len(tasks))
}
if tasks[0].Command != "/usr/bin/httpd" {
t.Errorf("expected Command=/usr/bin/httpd, got %q", tasks[0].Command)
}
if len(tasks[0].Args) != 2 {
t.Fatalf("expected 2 args, got %d (%v)", len(tasks[0].Args), tasks[0].Args)
}
if tasks[0].Args[0] != "-f" || tasks[0].Args[1] != "/etc/orca/web-app/httpd.conf" {
t.Errorf("expected args [-f /etc/orca/web-app/httpd.conf], got %v", tasks[0].Args)
}
}
func TestWorkloadToTaskSpecs_NilRuntimeUsesBinTrue(t *testing.T) {
spec := &jobspec.WorkloadSpec{
Name: "noop",
}
tasks := workloadToTaskSpecs(spec)
if len(tasks) != 1 {
t.Fatalf("expected 1 task, got %d", len(tasks))
}
if tasks[0].Command != "/bin/true" {
t.Errorf("expected Command=/bin/true, got %q", tasks[0].Command)
}
if len(tasks[0].Args) != 0 {
t.Errorf("expected 0 args, got %d (%v)", len(tasks[0].Args), tasks[0].Args)
}
}
func TestWorkloadToTaskSpecs_EmptyCommandUsesBinTrue(t *testing.T) {
spec := &jobspec.WorkloadSpec{
Name: "empty",
Runtime: &jobspec.RuntimeBlock{
OneOf: "process",
Command: "",
},
}
tasks := workloadToTaskSpecs(spec)
if len(tasks) != 1 {
t.Fatalf("expected 1 task, got %d", len(tasks))
}
if tasks[0].Command != "/bin/true" {
t.Errorf("expected Command=/bin/true, got %q", tasks[0].Command)
}
}
func TestWorkloadToTaskSpecs_NilSpecReturnsNil(t *testing.T) {
tasks := workloadToTaskSpecs(nil)
if tasks != nil {
t.Errorf("expected nil, got %v", tasks)
}
}