# 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

```bash
# 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

```bash
# 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:

```bash
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

```bash
orca ns create prod --parent _defaults
```

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

### Step 5: Submit the stack

```bash
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

```bash
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):

```bash
# 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](../../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:

```bash
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.