Files
orca/examples/full-stack
Jon Chery 52e17aefbf feat(P12): --type linux SSH-join + UAT plan + signoff script (REQ-161..163)
--type linux (REQ-161):
- internal/linux/bootstrap.go: SSH bootstrap for generic Linux workers
  (orcas pubkey, system user, drift-events dir; no PVE role/sudoers)
- internal/cli/node.go: joinLinux function + --type linux dispatch
- peer-setup kept as documented fallback

UAT plan (REQ-162):
- docs/uat.md: 3-host topology (lead Ubuntu + pve01 Proxmox + worker01
  Ubuntu), 22 step-by-step commands, 35-claim matrix, Proxmox
  prerequisite + alternative 3xUbuntu path (C-48), signoff procedure

UAT signoff script (REQ-163, C-47):
- scripts/uat-signoff.sh: 35 idempotent read-only assertions, exit 0
  iff all pass. Includes 4 critical-path assertions: job deploys to
  remote, ACL deny-by-default, seal/unseal round-trip, OIDC health
- scripts/uat-smoke.sh: 13 CI-tested pure-CLI assertions for .coreci.yml

Tests: node join --type linux test, fingerprint test updated, smoke
test all 13 pass.

---ci---
project: orca
phase: 12
milestone: v0.13
status: complete
requirements:
  covered: [161, 162, 163]
---/ci---
2026-08-10 14:33:29 +00:00
..

Full-Stack Example with Ingress

This directory contains a complete multi-service stack deployed with 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
web-app.md Service process Unix socket (default) Frontend HTTP server, 3 replicas, rolling update
api.md Service process TCP 127.0.0.1:9090 (R-007 opt-in) Backend API, 2 replicas, canary update
worker.md Job process none One-shot batch worker with lifecycle hooks
log-shipper.md Service process Unix socket (metrics) Log shipper on a dedicated node
postgres.md Service process Unix socket Database with volume replication, blue-green update

Rendered artifacts

The rendered/ directory shows what Orca generates on the target nodes when you submit these jobspecs:

File Description
traefik-dynamic-web-app.yaml Traefik dynamic config for the web-app Service
traefik-dynamic-api.yaml Traefik dynamic config for the api Service (TCP bind)
systemd-web-app.service Systemd unit for the web-app alloc
systemd-api.service Systemd unit for the api alloc (with TCP bind marker)
systemd-log-shipper.service Systemd unit for the log-shipper alloc

Walkthrough

Prerequisites

  • Orca installed (orca version works)
  • 2+ Linux nodes reachable over SSH (for multi-node scheduling)
  • Traefik installed on the lead node (watches /etc/traefik/dynamic/)

Step 1: Initialize the cluster

# On the operator laptop
orca init

This creates ~/.orca/ (or /root/.orca with --system), bootstraps the CA, generates the server cert, auto-detects the OS, and registers a localhost node.

Step 2: Join remote nodes

# Join a Proxmox node (v0.9 canonical SSH-push path)
orca node join --type proxmox --host 192.168.1.100 --ssh-user root

# Join a second node
orca node join --type proxmox --host 192.168.1.101 --ssh-key ~/.ssh/orca_ed25519

Step 3: Declare node capacity

The CLI-side scheduler uses capacity declarations for bin-packing:

orca node capacity set --cpu 4000 --memory 8192 --disk 100000 --node 192.168.1.100
orca node capacity set --cpu 4000 --memory 8192 --disk 100000 --node 192.168.1.101

Step 4: Create a namespace

orca ns create prod --parent _defaults

This creates ~/.orca/prod/ with db/, jobs/, alloc/, and ns.md.

Step 5: Submit the stack

orca job run web-app.md
orca job run api.md
orca job run worker.md
orca job run log-shipper.md
orca job run postgres.md

Each orca job run parses the .md jobspec, validates it against the schema, schedules it via the CLI-side bin-packing scheduler, and generates the systemd + Traefik artifacts on the target node via SSH-push.

Step 6: Observe placements

orca job list --watch

# Output:
# ID                                   NAME          STATUS   EXIT
# abc-123...                           web-app       running  0
# def-456...                           api           running  0
# ghi-789...                           worker        complete 0
# jkl-012...                           log-shipper   running  0
# mno-345...                           postgres      running  0

Step 7: Inspect rendered artifacts

After submission, the target nodes have:

/etc/systemd/system/orca-v1-web-app.service       # systemd unit
/etc/systemd/system/orca-v1-api.service           # systemd unit (TCP bind)
/etc/traefik/dynamic/orca-web-app.yaml            # Traefik dynamic config
/etc/traefik/dynamic/orca-api.yaml                # Traefik dynamic config
/run/orca/alloc-web-app-0/port-http.sock          # Unix socket (R-007 default)

See the rendered/ directory in this example for the exact file contents.

Step 8: Verify ingress

Traefik watches /etc/traefik/dynamic/ and atomically reloads when a file changes (write-tmp + rename, gate C-10). The web-app is reachable at https://<cluster-domain>/web-app and the API at https://<cluster-domain>/api.

Health checks (/healthz on each backend) ensure Traefik only routes to healthy instances.

Step 9: Drain and rollback

To drain a service (stop traffic, keep the workload running):

# Orca writes a Traefik config with weight:0 on every backend
# (RenderDrain). Traefik stops sending traffic.

To roll back, re-submit the normal jobspec — Orca writes the non-drained Traefik config and Traefik resumes routing.

Ingress model

See docs/ingress.md for the full Traefik ingress reference. Key points:

  • kind: Service implies a Traefik route (D-175).
  • Default bind is a Unix socket at /run/orca/alloc-<id>/port-<name>.sock (R-007).
  • service.bind: 127.0.0.1 opts in to TCP (loopback only).
  • One Traefik dynamic file per Service at /etc/traefik/dynamic/orca-<name>.yaml.
  • Atomic reload via write-tmp + rename (gate C-10).
  • Drain sets weight: 0 per backend.

Validation

All jobspecs in this directory are validated by a Go test:

go test ./examples/full-stack/ -v -run TestExamplesValidate

This test parses each .md file with jobspec.ParseFile and validates it against schema.ValidatorFor(kind) — ensuring every field used in the examples exists in the current WorkloadSpec struct and passes the per-kind validators (gate C-20).

v0.11 forward

The following are not yet implemented in v0.9 and will land in v0.11:

  • DaemonSet schedule: block: the parser does not yet populate the schedule: frontmatter block (v0.9 parser gap). The log-shipper example uses kind: Service with count: 1 and a node.role constraint as a workaround.
  • Secret resolution: env: { KEY: { from: "secret:..." } } is parsed but not resolved to EnvironmentFile=/LoadCredential= until v0.11-P03.
  • Transactional update execution: the update: block's plan is computed but not executed transactionally until v0.11-P10.
  • Socket activation: real socket unit files land in v0.11-P08.