feat(iac): scaffold orca-platform layout (M1.1)
ci / shared (push) Successful in 5s
ci / validate (push) Successful in 2s

Lands manifests/, overlays/, dns/, scripts/, Makefile per M1.1. Bundles yourplatform.com→breakpilot.com rename. vms/ removed (out-of-scope for Orca).

Refs: M1.1
This commit was merged in pull request #3.
This commit is contained in:
2026-05-18 20:28:40 +00:00
parent c196f5e801
commit 8e37f65b8e
53 changed files with 1023 additions and 35 deletions
+10 -9
View File
@@ -1,5 +1,6 @@
# CI for orca-platform (IaC). `shared` always runs; `validate` activates
# when at least one Orca manifest lands.
# CI for orca-platform (IaC).
# `shared` always runs (commitlint + gitleaks + trivy fs).
# `validate` always runs (parses every manifest + overlay + vm spec).
name: ci
on:
@@ -53,18 +54,18 @@ jobs:
TRIVY_VERSION=0.70.0
curl -fsSL "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \
| tar -xz -C /tmp trivy
/tmp/trivy fs --severity HIGH,CRITICAL --exit-code 1 --no-progress --skip-dirs node_modules,target,dist .
/tmp/trivy fs --severity HIGH,CRITICAL --exit-code 1 --no-progress --skip-dirs node_modules,target,dist,.orca-out .
validate:
runs-on: docker
if: hashFiles('**/*.orca.yaml','**/*.orca.yml','manifests/**') != ''
steps:
- uses: actions/checkout@v4
- name: install orca
- name: setup python
shell: bash
run: |
curl -fsSL https://orca.meghsakha.com/install.sh | sh
orca version
which python3
python3 --version
- name: orca validate
run: orca validate ./
- name: make validate
run: make validate
+4 -4
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: docker
environment:
name: production # Gitea Environments — requires sign-off per branch protection
url: https://yourplatform.com
url: https://breakpilot.com
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
@@ -22,7 +22,7 @@ jobs:
- name: verify stage soak (>= 24h on this image)
run: |
IMG=registry.yourplatform.com/${{ github.event.repository.name }}:env-stage
IMG=registry.breakpilot.com/${{ github.event.repository.name }}:env-stage
SOAK_SECONDS=$(orca image-age --env=stage --image $IMG)
if [ "$SOAK_SECONDS" -lt 86400 ]; then
echo "Stage soak only $SOAK_SECONDS s, < 24h. Aborting."
@@ -34,12 +34,12 @@ jobs:
- name: re-tag image as semver + env-prod
uses: docker/login-action@v3
with:
registry: registry.yourplatform.com
registry: registry.breakpilot.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASS }}
- run: |
IMG=registry.yourplatform.com/${{ github.event.repository.name }}
IMG=registry.breakpilot.com/${{ github.event.repository.name }}
docker pull $IMG:env-stage
docker tag $IMG:env-stage $IMG:v${{ steps.v.outputs.version }}
docker tag $IMG:env-stage $IMG:env-prod
+1
View File
@@ -35,3 +35,4 @@ vendor/
# Rust
**/target/
.orca-out/
+1
View File
@@ -6,6 +6,7 @@ Generated section is appended on release tag via `git-cliff` (see `.gitea/workfl
## [Unreleased]
### Added
- feat(iac): scaffold orca-platform — manifests/, overlays/, dns/, scripts/, Makefile (M1.1)
-
### Changed
+1 -1
View File
@@ -86,4 +86,4 @@ When reviewing, check in this order:
## Questions
`#engineering` channel · `oncall@yourplatform.com` · or open a PR with a `[WIP]` prefix and ask in the description.
`#engineering` channel · `oncall@breakpilot.com` · or open a PR with a `[WIP]` prefix and ask in the description.
+35
View File
@@ -0,0 +1,35 @@
# orca-platform — IaC for the Breakpilot Platform.
#
# make validate parse + structural sanity check every manifest
# make plan ENV=<env> resolve manifests + overlay into .orca-out/<env>/
# make apply ENV=<env> (M1.2+) push resolved set to Orca controller
# make diff ENV=<env> alias for plan
# make clean remove .orca-out/
.PHONY: help validate plan apply diff clean
ENV ?=
ORCA_API_URL ?=
help:
@echo "orca-platform targets:"
@echo " make validate syntax + structural check (all manifests)"
@echo " make plan ENV=<env> resolve manifests for env (dev/stage/prod)"
@echo " make apply ENV=<env> push resolved set (no-op until M1.2)"
@echo " make diff ENV=<env> alias for plan"
@echo " make clean remove .orca-out/"
validate:
@./scripts/validate.sh
plan:
@./scripts/plan.sh
diff: plan
apply:
@./scripts/apply.sh
clean:
@rm -rf .orca-out
@echo "removed .orca-out/"
+68 -21
View File
@@ -1,58 +1,105 @@
# orca-platform
IaC for VMs, Orca manifests, DNS, TLS, backups.
IaC for the Breakpilot Platform: per-VM Orca service manifests, per-env overlays, DNS zones, backup/restore tooling, and the `make plan`/`make apply` wrappers.
> Part of the **Breakpilot Platform**. For the big picture see [`platform/docs`](https://gitea.meghsakha.com/platform/docs):
> [Architecture](https://gitea.meghsakha.com/platform/docs/src/branch/main/PLATFORM_ARCHITECTURE.md) ·
> [Infrastructure](https://gitea.meghsakha.com/platform/docs/src/branch/main/INFRASTRUCTURE.md) ·
> [Product Integration Spec](https://gitea.meghsakha.com/platform/docs/src/branch/main/PRODUCT_INTEGRATION_SPEC.md) ·
> [Implementation Plan](https://gitea.meghsakha.com/platform/docs/src/branch/main/IMPLEMENTATION_PLAN.md)
## What this is
IaC for VMs, Orca manifests, DNS, TLS, backups. Scaffolded under milestone M1.1. See [`platform/docs`](https://gitea.meghsakha.com/platform/docs) for the full architecture context.
The single source of truth for which container runs on which VM in which environment. Every change to prod infrastructure should flow through this repo — never through `orca deploy` from a laptop.
**Plane:** Infra
**Owner:** @sharang
**Status:** pre-alpha
**Status:** pre-alpha (M1.1 — layout only; real values land per the per-milestone schedule below)
**Linked milestone:** [M1.1](https://gitea.meghsakha.com/platform/docs/src/branch/main/IMPLEMENTATION_PLAN.md)
## Directory layout
```
.
├── manifests/ # Base service.toml per VM × service (35 stubs)
│ ├── vm-edge/ Identity + Infra plane services
│ ├── vm-control/ Control plane services
│ ├── vm-data/ Data plane services
│ └── stage/ Stage (app plane only)
├── overlays/ # Per-env sparse deltas applied on top of manifests/
│ ├── dev/overlay.toml no-op; dev runs docker-compose per-service
│ ├── stage/overlay.toml include manifests/stage/, image_tag=env-stage
│ └── prod/overlay.toml include vm-{edge,control,data}, image_tag=env-prod
├── dns/
│ └── breakpilot.com.zone.template PowerDNS zone — body lands in M0.3
├── cluster.toml.tmpl # Cluster-level config (acme_email, backup, ai); rendered per env
├── scripts/
│ ├── validate.sh # `make validate`
│ ├── plan.sh # `make plan ENV=<env>` → .orca-out/<env>/
│ ├── apply.sh # `make apply ENV=<env>` (no-op until M1.2)
│ └── restore-drill.sh.template M1.3 placeholder
└── Makefile # validate / plan / apply / diff / clean
```
## Run locally
```bash
# prerequisites: see CONTRIBUTING.md for tooling once code lands
make dev # starts dependencies + this service on http://localhost:3000
make test # unit + integration
make e2e # only if this repo ships user-facing flows
make validate # check all manifests parse + have required fields
make plan ENV=stage # resolve manifests for stage → .orca-out/stage/
make plan ENV=prod # same for prod
make apply ENV=stage # no-op until M1.2 stands up the Orca controller
```
Local secrets come from `.env.local` (gitignored). Template at `.env.example`.
`make validate` runs in CI on every PR.
## Per-milestone fill-in schedule
Each stub manifest in `manifests/` carries a header comment naming the milestone that finalises its real values. Summary:
| Milestone | What it fills in |
|---|---|
| **M0.3** | `vm-edge/powerdns-auth.toml`, DNS zone body, orca-proxy routes |
| **M1.2** | VM provisioning (Terraform/OpenStack in a separate repo); brings `make apply` online |
| **M1.3** | Backup cron services + `scripts/restore-drill.sh` |
| **M2.1** | `vm-edge/keycloak.toml` + `pg-keycloak.toml` |
| **M3.1** | `vm-edge/infisical.toml` + `pg-infisical.toml` + `redis-infisical.toml` |
| **M3.2** | `vm-control/stalwart.toml` |
| **M4.1** | `vm-control/tenant-registry.toml` + `vm-data/pg-app.toml` |
| **M5.1** | `vm-control/customer-portal.toml` + stage equivalents |
| **M6.x** | `vm-data/certifai-dashboard.toml`, `mongodb.toml`, `litellm.toml` |
| **M7.x** | compliance services on vm-data + stage |
| **M8.1** | `vm-control/erpnext.toml`, `mariadb.toml`, `redis-erpnext.toml` |
| **M9.1** | `vm-control/frappe-hd.toml` |
Until the milestone PR lands, the stub still parses and `make validate` stays green — but `apply` will refuse a stub that hasn't replaced its `placeholder` image tag (gate to be added with the first real image).
## Endpoints / surface
{{For services: list the top-level routes or commands.
For libraries: list the public API entry points.
For IaC: list the make targets.}}
| Target | What it does |
|---|---|
| `make validate` | Parse + structural check (no cluster contact) |
| `make plan ENV=<env>` | Resolve manifests + overlay → `.orca-out/<env>/` |
| `make apply ENV=<env>` | Push to Orca controller at `$ORCA_API_URL` (M1.2 brings this online) |
| `make diff ENV=<env>` | Alias for `plan` |
| `make clean` | Remove `.orca-out/` |
## Deployment
| Env | URL | How |
| Env | Apply path | Trigger |
|---|---|---|
| dev | `http://localhost:3000` | `make dev` |
| stage | `https://orca-platform.stage.yourplatform.com` | auto on merge to `main` |
| prod | `https://orca-platform.yourplatform.com` | manual: tag `vX.Y.Z` + sign-off |
| dev | `docker-compose` in each product repo | dev's machine |
| stage | `make apply ENV=stage` against the stage Orca controller | CI on merge to main + image build |
| prod | `make apply ENV=prod` against the prod Orca controller | release tag `vX.Y.Z` + sign-off |
Rollback: `orca rollout undo orca-platform --env={{env}}`.
`apply` for prod will be gated by the production-promotion gate (24h stage soak + manual sign-off) per `IMPLEMENTATION_PLAN.md §1.6`. Wiring lands in M1.2.
## Observability
- Traces, logs, metrics: [SigNoz](https://signoz.meghsakha.com) — service name `orca-platform`
- Audit events: Tenant Registry `/audit` (Retraced-shape schema)
- On-call: `oncall@yourplatform.com` · runbook at `platform/docs/runbooks/orca-platform.md`
- Traces, logs, metrics: [SigNoz](https://signoz.meghsakha.com) — service name per individual container
- On-call: `oncall@breakpilot.com` · runbooks at `platform/docs/runbooks/`
## Contributing
See [`CONTRIBUTING.md`](./CONTRIBUTING.md). TL;DR: branch from main, open a PR, 1 review + green CI, squash-merge.
See [`CONTRIBUTING.md`](./CONTRIBUTING.md). Every PR touching `manifests/` MUST keep `make validate` green; CI enforces it.
## License
+29
View File
@@ -0,0 +1,29 @@
# Cluster-level config rendered per env.
# Real values get substituted by `make plan ENV=<env>` from environment +
# overlays/<env>/overlay.toml.
#
# Schema mirrors ~/workspace/orca-infra/cluster.toml (Orca-native).
[cluster]
name = "breakpilot-${ENV}"
domain = "${DOMAIN}"
acme_email = "oncall@breakpilot.com"
[ai]
provider = "litellm"
endpoint = "https://llm.breakpilot.com"
model = "gpt-oss-120b"
api_key = "${secrets.LITELLM_API_KEY}"
[backup]
enabled = true
schedule = "0 0 3 * * *"
retention_days = 30
[[backup.targets]]
type = "s3"
endpoint = "https://s3.dus2.cloud.syseleven.net"
bucket = "platform-${ENV}-backups"
region = "dus2"
access_key = "${secrets.S3_ACCESS_KEY}"
secret_key = "${secrets.S3_SECRET_KEY}"
+20
View File
@@ -0,0 +1,20 @@
; PowerDNS authoritative zone for breakpilot.com.
; Source-of-truth: this file. Synced into PowerDNS by M0.3 deploy step.
; Real records (apex, wildcards, A, MX, SPF/DKIM/DMARC) land with M0.3.
$ORIGIN breakpilot.com.
$TTL 60
@ IN SOA ns1.breakpilot.com. oncall.breakpilot.com. (
; serial — bumped by CI on every commit
2026051800
3600 ; refresh
600 ; retry
604800 ; expire
60 ; minimum TTL
)
@ IN NS ns1.breakpilot.com.
@ IN NS ns2.breakpilot.com.
; A records, wildcards, mail/spf/dkim/dmarc land in M0.3
+28
View File
@@ -0,0 +1,28 @@
# Manifests
One `service.toml` per service, grouped by host VM, per `INFRASTRUCTURE.md §2`.
| Directory | VM | Plane(s) | Owner milestone of "real" config |
|---|---|---|---|
| `vm-edge/` | vm-edge | Identity + Infra | M2.1 (Keycloak), M3.1 (Infisical), M0.3 (PowerDNS), M2.x (Gitea), M1.2 (proxy) |
| `vm-control/` | vm-control | Control | M5.1 (portal), M4.1 (tenant-registry), M8.1 (ERPNext), M3.2 (Stalwart) |
| `vm-data/` | vm-data | Data | M6.x (CERTifAI), M7.x (compliance), M4.1 (pg-app) |
| `stage/` | stage | App plane only | promotion target of stage builds |
Each file in this directory is currently a **shape-only stub** — fields are set but image references and env wiring will be finalised by the milestone listed in the file header.
## Adding a new service
1. Pick the owning VM per `INFRASTRUCTURE.md §2`.
2. Create `<vm-name>/<service-name>.toml` following the shape of an existing stub.
3. Set `placement.node = "<vm-name>"`, `resources.memory`/`cpu` per the co-tenant budget in `INFRASTRUCTURE.md §6`.
4. Reference secrets as `${secrets.NAME}` — Infisical resolves these. No plaintext values except the Keycloak bootstrap DB URI exception (`INFRASTRUCTURE.md §8 rule 3`).
5. Run `make validate` before pushing.
## Validation
`make validate` parses every TOML and checks required fields (`name`, image OR build OR module, `placement.node`, `resources.memory`). It does NOT contact a running cluster.
`make plan ENV=<env>` merges the base manifest with the matching overlay in `overlays/<env>/` and prints the resulting service definitions. It is a no-op until matching overlays exist for the env.
`make apply ENV=<env>` is gated on a real Orca controller URL — refuses to run until `ORCA_API_URL` is set (lands in M1.2).
+14
View File
@@ -0,0 +1,14 @@
# admin-compliance stub — full config lands in M7.x.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "admin-compliance"
image = "registry.breakpilot.com/admin-compliance:env-stage"
port = 3002
[service.placement]
node = "stage"
[service.resources]
memory = "256Mi"
cpu = 0.25
+14
View File
@@ -0,0 +1,14 @@
# ai-compliance-sdk stub — full config lands in M7.x.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "ai-compliance-sdk"
image = "registry.breakpilot.com/ai-compliance-sdk:env-stage"
port = 3001
[service.placement]
node = "stage"
[service.resources]
memory = "512Mi"
cpu = 0.25
+14
View File
@@ -0,0 +1,14 @@
# backend-compliance stub — full config lands in M7.x.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "backend-compliance"
image = "registry.breakpilot.com/backend-compliance:env-stage"
port = 3000
[service.placement]
node = "stage"
[service.resources]
memory = "512Mi"
cpu = 0.25
+14
View File
@@ -0,0 +1,14 @@
# certifai-dashboard stub — full config lands in M6.x.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "certifai-dashboard"
image = "registry.breakpilot.com/certifai:env-stage"
port = 3000
[service.placement]
node = "stage"
[service.resources]
memory = "1Gi"
cpu = 0.5
+15
View File
@@ -0,0 +1,15 @@
# customer-portal stub — full config lands in M5.1.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "customer-portal"
image = "registry.breakpilot.com/portal:env-stage"
port = 3000
domain = "*.stage.breakpilot.com"
[service.placement]
node = "stage"
[service.resources]
memory = "1Gi"
cpu = 0.5
+14
View File
@@ -0,0 +1,14 @@
# litellm stub — full config lands in M6.x.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "litellm"
image = "ghcr.io/berriai/litellm:main-stable"
port = 4000
[service.placement]
node = "stage"
[service.resources]
memory = "512Mi"
cpu = 0.25
+15
View File
@@ -0,0 +1,15 @@
# mongodb-stage stub — full config lands in M6.x.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# Ephemeral.
[[service]]
name = "mongodb-stage"
image = "mongo:7"
port = 27017
[service.placement]
node = "stage"
[service.resources]
memory = "512Mi"
cpu = 0.25
+15
View File
@@ -0,0 +1,15 @@
# orca-proxy stub — full config lands in M1.2.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# Stage proxy only routes to stage app containers.
[[service]]
name = "orca-proxy"
image = "orca-managed/orca-proxy:placeholder"
port = 443
[service.placement]
node = "stage"
[service.resources]
memory = "256Mi"
cpu = 0.5
+15
View File
@@ -0,0 +1,15 @@
# pg-app-stage stub — full config lands in M4.1.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# Ephemeral; no backup, no volume; reset on each release.
[[service]]
name = "pg-app-stage"
image = "postgres:16-alpine"
port = 5432
[service.placement]
node = "stage"
[service.resources]
memory = "1Gi"
cpu = 0.5
+15
View File
@@ -0,0 +1,15 @@
# qdrant-stage stub — full config lands in M7.x.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# Ephemeral, tiny corpus.
[[service]]
name = "qdrant-stage"
image = "qdrant/qdrant:v1.10.0"
port = 6333
[service.placement]
node = "stage"
[service.resources]
memory = "512Mi"
cpu = 0.25
+19
View File
@@ -0,0 +1,19 @@
# tenant-registry stub — full config lands in M4.1.
# Host: stage. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# Calls PROD Keycloak per §2 "Calls OUT to prod"; audience is stage_client_id.
[[service]]
name = "tenant-registry"
image = "registry.breakpilot.com/tenant-registry:env-stage"
port = 8080
depends_on = ["pg-app-stage"]
[service.placement]
node = "stage"
[service.resources]
memory = "512Mi"
cpu = 0.25
[service.env]
KEYCLOAK_ISSUER = "https://auth.breakpilot.com/realms/breakpilot-prod"
+20
View File
@@ -0,0 +1,20 @@
# customer-portal stub — full config lands in M5.1.
# Host: vm-control. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "customer-portal"
image = "registry.breakpilot.com/portal:placeholder"
port = 3000
domain = "*.breakpilot.com"
depends_on = ["tenant-registry"]
[service.placement]
node = "vm-control"
[service.resources]
memory = "1Gi"
cpu = 1.0
[service.env]
KEYCLOAK_ISSUER = "https://auth.breakpilot.com/realms/breakpilot-prod"
TENANT_REGISTRY_URL = "http://tenant-registry:8080"
+25
View File
@@ -0,0 +1,25 @@
# erpnext stub — full config lands in M8.1.
# Host: vm-control. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "erpnext"
image = "frappe/erpnext:v15"
port = 8000
domain = "erp.breakpilot.com"
depends_on = ["mariadb", "redis-erpnext"]
[service.placement]
node = "vm-control"
[service.resources]
memory = "6Gi"
cpu = 2.0
[service.volume]
path = "/home/frappe/frappe-bench/sites"
[service.env]
DB_HOST = "mariadb"
REDIS_QUEUE = "redis://redis-erpnext:6379/0"
REDIS_CACHE = "redis://redis-erpnext:6379/1"
ADMIN_PASSWORD = "${secrets.ERPNEXT_ADMIN_PASSWORD}"
+15
View File
@@ -0,0 +1,15 @@
# frappe-hd stub — full config lands in M9.1.
# Host: vm-control. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "frappe-hd"
image = "frappe/helpdesk:v1"
port = 8001
depends_on = ["mariadb", "redis-erpnext"]
[service.placement]
node = "vm-control"
[service.resources]
memory = "1Gi"
cpu = 0.5
+20
View File
@@ -0,0 +1,20 @@
# mariadb stub — full config lands in M8.1.
# Host: vm-control. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "mariadb"
image = "mariadb:11"
port = 3306
[service.placement]
node = "vm-control"
[service.resources]
memory = "3Gi"
cpu = 1.0
[service.volume]
path = "/var/lib/mysql"
[service.env]
MARIADB_ROOT_PASSWORD = "${secrets.MARIADB_ROOT_PASSWORD}"
+14
View File
@@ -0,0 +1,14 @@
# redis-erpnext stub — full config lands in M8.1.
# Host: vm-control. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "redis-erpnext"
image = "redis:7-alpine"
port = 6379
[service.placement]
node = "vm-control"
[service.resources]
memory = "256Mi"
cpu = 0.25
+22
View File
@@ -0,0 +1,22 @@
# stalwart stub — full config lands in M3.2.
# Host: vm-control. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "stalwart"
image = "stalwartlabs/mail-server:latest"
port = 587
domain = "mail.breakpilot.com"
extra_ports = ["25:25", "465:465", "587:587", "993:993"]
[service.placement]
node = "vm-control"
[service.resources]
memory = "1Gi"
cpu = 0.5
[service.volume]
path = "/opt/stalwart-mail"
[service.env]
STALWART__SERVER__HOSTNAME = "mail.breakpilot.com"
+20
View File
@@ -0,0 +1,20 @@
# tenant-registry stub — full config lands in M4.1.
# Host: vm-control. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "tenant-registry"
image = "registry.breakpilot.com/tenant-registry:placeholder"
port = 8080
[service.placement]
node = "vm-control"
[service.resources]
memory = "512Mi"
cpu = 0.5
[service.env]
DATABASE_URL = "${secrets.TENANT_REGISTRY_DB_URL}"
KEYCLOAK_ISSUER = "https://auth.breakpilot.com/realms/breakpilot-prod"
KEYCLOAK_ADMIN_USER = "${secrets.KEYCLOAK_ADMIN_USER}"
KEYCLOAK_ADMIN_PASS = "${secrets.KEYCLOAK_ADMIN_PASS}"
+15
View File
@@ -0,0 +1,15 @@
# admin-compliance stub — full config lands in M7.x.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "admin-compliance"
image = "registry.breakpilot.com/admin-compliance:placeholder"
port = 3002
depends_on = ["backend-compliance", "ai-compliance-sdk"]
[service.placement]
node = "vm-data"
[service.resources]
memory = "512Mi"
cpu = 0.25
+15
View File
@@ -0,0 +1,15 @@
# ai-compliance-sdk stub — full config lands in M7.x.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "ai-compliance-sdk"
image = "registry.breakpilot.com/ai-compliance-sdk:placeholder"
port = 3001
depends_on = ["pg-app", "qdrant", "litellm"]
[service.placement]
node = "vm-data"
[service.resources]
memory = "1Gi"
cpu = 0.5
+15
View File
@@ -0,0 +1,15 @@
# backend-compliance stub — full config lands in M7.x.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "backend-compliance"
image = "registry.breakpilot.com/backend-compliance:placeholder"
port = 3000
depends_on = ["pg-app", "minio"]
[service.placement]
node = "vm-data"
[service.resources]
memory = "1Gi"
cpu = 0.5
+15
View File
@@ -0,0 +1,15 @@
# certifai-dashboard stub — full config lands in M6.x.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "certifai-dashboard"
image = "registry.breakpilot.com/certifai:placeholder"
port = 3000
depends_on = ["mongodb", "litellm"]
[service.placement]
node = "vm-data"
[service.resources]
memory = "1Gi"
cpu = 0.5
+18
View File
@@ -0,0 +1,18 @@
# litellm stub — full config lands in M6.x.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "litellm"
image = "ghcr.io/berriai/litellm:main-stable"
port = 4000
[service.placement]
node = "vm-data"
[service.resources]
memory = "1Gi"
cpu = 0.5
[service.env]
LITELLM_MASTER_KEY = "${secrets.LITELLM_MASTER_KEY}"
LITELLM_SALT_KEY = "${secrets.LITELLM_SALT_KEY}"
+23
View File
@@ -0,0 +1,23 @@
# minio stub — full config lands in M7.x.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "minio"
image = "minio/minio:latest"
port = 9000
extra_ports = ["9001:9001"]
cmd = ["server", "/data", "--console-address", ":9001"]
[service.placement]
node = "vm-data"
[service.resources]
memory = "1Gi"
cpu = 0.5
[service.volume]
path = "/data"
[service.env]
MINIO_ROOT_USER = "${secrets.MINIO_ROOT_USER}"
MINIO_ROOT_PASSWORD = "${secrets.MINIO_ROOT_PASSWORD}"
+21
View File
@@ -0,0 +1,21 @@
# mongodb stub — full config lands in M6.x.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "mongodb"
image = "mongo:7"
port = 27017
[service.placement]
node = "vm-data"
[service.resources]
memory = "2Gi"
cpu = 1.0
[service.volume]
path = "/data/db"
[service.env]
MONGO_INITDB_ROOT_USERNAME = "${secrets.MONGO_ADMIN_USER}"
MONGO_INITDB_ROOT_PASSWORD = "${secrets.MONGO_ADMIN_PASSWORD}"
+23
View File
@@ -0,0 +1,23 @@
# pg-app stub — full config lands in M4.1.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# RISK-1 (§12): single instance owns tenant_registry + compliance schemas. Split into pg-registry + pg-compliance at Tier B.
[[service]]
name = "pg-app"
image = "postgres:16-alpine"
port = 5432
[service.placement]
node = "vm-data"
[service.resources]
memory = "3Gi"
cpu = 1.0
[service.volume]
path = "/var/lib/postgresql/data"
[service.env]
POSTGRES_DB = "platform"
POSTGRES_USER = "platform"
POSTGRES_PASSWORD = "${secrets.PG_APP_PASSWORD}"
+17
View File
@@ -0,0 +1,17 @@
# qdrant stub — full config lands in M7.x.
# Host: vm-data. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "qdrant"
image = "qdrant/qdrant:v1.10.0"
port = 6333
[service.placement]
node = "vm-data"
[service.resources]
memory = "1Gi"
cpu = 0.5
[service.volume]
path = "/qdrant/storage"
+25
View File
@@ -0,0 +1,25 @@
# gitea stub — full config lands in M3.x.
# Host: vm-edge. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "gitea"
image = "gitea/gitea:1.22"
port = 3000
domain = "git.breakpilot.com"
[service.placement]
node = "vm-edge"
[service.resources]
memory = "512Mi"
cpu = 0.5
[service.volume]
path = "/data"
[service.env]
USER_UID = "1000"
USER_GID = "1000"
GITEA__database__DB_TYPE = "sqlite3"
GITEA__database__PATH = "/data/gitea/gitea.db"
GITEA__server__ROOT_URL = "https://git.breakpilot.com"
+22
View File
@@ -0,0 +1,22 @@
# infisical stub — full config lands in M3.1.
# Host: vm-edge. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "infisical"
image = "infisical/infisical:latest"
port = 8080
depends_on = ["pg-infisical", "redis-infisical"]
[service.placement]
node = "vm-edge"
[service.resources]
memory = "512Mi"
cpu = 0.5
[service.env]
DB_CONNECTION_URI = "${secrets.INFISICAL_DB_URI}"
REDIS_URL = "redis://redis-infisical:6379"
ENCRYPTION_KEY = "${secrets.INFISICAL_ENCRYPTION_KEY}"
AUTH_SECRET = "${secrets.INFISICAL_AUTH_SECRET}"
SITE_URL = "https://infisical.breakpilot.com"
+25
View File
@@ -0,0 +1,25 @@
# keycloak stub — full config lands in M2.1.
# Host: vm-edge. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# Bootstrap exception per §8 rule 3: KC_DB_URL lives in Orca env, not Infisical (Infisical runs on same VM).
[[service]]
name = "keycloak"
image = "quay.io/keycloak/keycloak:26.0"
port = 8443
domain = "auth.breakpilot.com"
depends_on = ["pg-keycloak"]
[service.placement]
node = "vm-edge"
[service.resources]
memory = "2Gi"
cpu = 1.0
[service.env]
KC_DB = "postgres"
KC_DB_URL = "${secrets.KC_DB_URL}"
KC_HOSTNAME = "auth.breakpilot.com"
KC_PROXY_HEADERS = "xforwarded"
KC_HEALTH_ENABLED = "true"
JAVA_OPTS_APPEND = "-Xms1g -Xmx1500m"
+15
View File
@@ -0,0 +1,15 @@
# orca-proxy stub — full config lands in M1.2/M0.3.
# Host: vm-edge. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# Wildcard TLS terminator; routing rules land with M0.3.
[[service]]
name = "orca-proxy"
image = "orca-managed/orca-proxy:placeholder"
port = 443
[service.placement]
node = "vm-edge"
[service.resources]
memory = "256Mi"
cpu = 0.5
+22
View File
@@ -0,0 +1,22 @@
# pg-infisical stub — full config lands in M3.1.
# Host: vm-edge. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "pg-infisical"
image = "postgres:16-alpine"
port = 5432
[service.placement]
node = "vm-edge"
[service.resources]
memory = "256Mi"
cpu = 0.25
[service.volume]
path = "/var/lib/postgresql/data"
[service.env]
POSTGRES_DB = "infisical"
POSTGRES_USER = "infisical"
POSTGRES_PASSWORD = "${secrets.PG_INFISICAL_PASSWORD}"
+22
View File
@@ -0,0 +1,22 @@
# pg-keycloak stub — full config lands in M2.1.
# Host: vm-edge. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "pg-keycloak"
image = "postgres:16-alpine"
port = 5432
[service.placement]
node = "vm-edge"
[service.resources]
memory = "512Mi"
cpu = 0.5
[service.volume]
path = "/var/lib/postgresql/data"
[service.env]
POSTGRES_DB = "keycloak"
POSTGRES_USER = "keycloak"
POSTGRES_PASSWORD = "${secrets.PG_KEYCLOAK_PASSWORD}"
+15
View File
@@ -0,0 +1,15 @@
# powerdns-auth stub — full config lands in M0.3.
# Host: vm-edge. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
[[service]]
name = "powerdns-auth"
image = "powerdns/pdns-auth:4.9"
port = 53
extra_ports = ["53:53/udp", "53:53/tcp"]
[service.placement]
node = "vm-edge"
[service.resources]
memory = "256Mi"
cpu = 0.25
+15
View File
@@ -0,0 +1,15 @@
# redis-infisical stub — full config lands in M3.1.
# Host: vm-edge. Resource budget per INFRASTRUCTURE.md §6 co-tenant notes.
# Ephemeral cache; no volume.
[[service]]
name = "redis-infisical"
image = "redis:7-alpine"
port = 6379
[service.placement]
node = "vm-edge"
[service.resources]
memory = "128Mi"
cpu = 0.1
+9
View File
@@ -0,0 +1,9 @@
# Overlays
Per-env *sparse* deltas applied on top of `manifests/`. Concept: each overlay
file may set just the fields that differ from the base manifest. The merge
script in `scripts/plan.sh` produces the final per-env service set at
`.orca-out/<env>/`.
For now the overlays are placeholder structures — concrete deltas land with
the milestones that introduce real images and replica counts (M4.1, M5.1, M6.x).
+11
View File
@@ -0,0 +1,11 @@
# Dev overlay — placeholder.
#
# Dev runs everything in docker-compose on the developer's laptop, not via
# Orca. This overlay exists so `make plan ENV=dev` is symmetric with stage/
# prod, but it does not yet point at real images.
#
# Real dev wiring lives in the per-service repos' `make dev` target.
[env]
name = "dev"
api_url = "" # no orca controller; apply is a no-op
+15
View File
@@ -0,0 +1,15 @@
# Prod overlay.
#
# Selects manifests under vm-edge / vm-control / vm-data. Stage manifests
# (manifests/stage/) are excluded from prod apply.
[env]
name = "prod"
api_url = "${ORCA_PROD_API_URL}"
[deploy]
include_dirs = ["manifests/vm-edge", "manifests/vm-control", "manifests/vm-data"]
[image]
# Default tag for prod; release.yaml retags `env-stage` → `v$VERSION` + `env-prod`.
default_tag = "env-prod"
+16
View File
@@ -0,0 +1,16 @@
# Stage overlay.
#
# Stage maps to the single 'stage' VM, app plane only. Selects only the
# services under manifests/stage/.
[env]
name = "stage"
api_url = "${ORCA_STAGE_API_URL}"
# Service filter: only deploy manifests under this directory.
[deploy]
include_dirs = ["manifests/stage"]
[image]
# Default image tag for stage builds. Per-service overrides may land later.
default_tag = "env-stage"
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# `make apply ENV=<env>` — push the resolved manifest set to an Orca controller.
#
# Refuses to run unless ORCA_API_URL is set (or read from overlays/<env>).
# In M1.1 this is a guard; the real call lands once vm-edge has an Orca
# controller (M1.2).
set -euo pipefail
ENV="${ENV:?usage: make apply ENV=<dev|stage|prod>}"
case "$ENV" in dev|stage|prod) ;; *) echo "unknown env: $ENV" >&2; exit 2;; esac
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
OUT="$ROOT/.orca-out/$ENV"
if [ ! -d "$OUT" ]; then
echo "no resolved manifests at $OUT — run \`make plan ENV=$ENV\` first" >&2
exit 1
fi
if [ -z "${ORCA_API_URL:-}" ]; then
echo "ORCA_API_URL not set." >&2
echo "M1.2 will provision the controller; until then \`make apply\` is a no-op." >&2
echo "Want to dry-run? Use \`make plan ENV=$ENV\` and inspect .orca-out/$ENV/." >&2
exit 0 # exit 0 — no-op is the expected M1.1 behaviour
fi
# Real apply once a controller exists. orca CLI deploys a directory of TOMLs.
echo "=== apply ENV=$ENV against $ORCA_API_URL ==="
orca --api "$ORCA_API_URL" deploy --file "$OUT"
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# `make plan ENV=<env>` — show what would be deployed for the given env.
#
# Merges manifests/ with overlays/<env>/overlay.toml and writes the resolved
# service set to .orca-out/<env>/. Does NOT contact a cluster.
#
# Behavior in M1.1: the merge is a passthrough (overlays are placeholders).
# A real merge that resolves per-env image tags and replica counts will land
# alongside the first env-specific delta (M1.2 or later).
set -euo pipefail
ENV="${ENV:?usage: make plan ENV=<dev|stage|prod>}"
case "$ENV" in dev|stage|prod) ;; *) echo "unknown env: $ENV" >&2; exit 2;; esac
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
OUT="$ROOT/.orca-out/$ENV"
rm -rf "$OUT"
mkdir -p "$OUT"
OVERLAY="overlays/$ENV/overlay.toml"
[ -f "$OVERLAY" ] || { echo "missing $OVERLAY" >&2; exit 1; }
# Read include_dirs from overlay; fall back to all VMs (dev default).
INCLUDE_DIRS=$(python3 - "$OVERLAY" <<'PY'
import sys, tomllib
data = tomllib.load(open(sys.argv[1], 'rb'))
dirs = data.get('deploy', {}).get('include_dirs')
if dirs is None:
dirs = ['manifests/vm-edge', 'manifests/vm-control', 'manifests/vm-data', 'manifests/stage']
print('\n'.join(dirs))
PY
)
echo "=== plan ENV=$ENV ==="
echo "overlay: $OVERLAY"
echo "include_dirs:"
echo "$INCLUDE_DIRS" | sed 's/^/ /'
echo
count=0
while IFS= read -r dir; do
[ -d "$dir" ] || continue
for tml in "$dir"/*.toml; do
[ -e "$tml" ] || continue
rel="${tml#manifests/}"
dest="$OUT/$rel"
mkdir -p "$(dirname "$dest")"
cp "$tml" "$dest"
count=$((count+1))
done
done <<< "$INCLUDE_DIRS"
echo "→ wrote $count resolved manifests to .orca-out/$ENV/"
echo "→ apply with: ORCA_API_URL=<url> make apply ENV=$ENV"
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Stub — M1.3 fills this in.
# Per INFRASTRUCTURE.md §10 Scenario F, a quarterly cold-restore drill is
# required: pull latest pg_dump from S3, restore into a scratch Postgres,
# verify row counts, post result to oncall.
#
# Concrete steps land with M1.3 (Backups, monitoring, on-call).
echo "restore drill not implemented yet — see M1.3" >&2
exit 1
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# TOML syntax + structural sanity for every manifest in this repo.
# Used by `make validate` and by .gitea/workflows/ci.yaml.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
python3 - "$ROOT" <<'PY'
import sys, tomllib, pathlib
root = pathlib.Path(sys.argv[1])
errs = []
count = 0
for p in sorted(root.glob('manifests/**/*.toml')):
count += 1
try:
data = tomllib.load(open(p, 'rb'))
except Exception as e:
errs.append(f'{p}: TOML parse: {e}')
continue
svcs = data.get('service')
if not svcs:
errs.append(f'{p}: no [[service]] block')
continue
for svc in svcs:
for required in ('name', 'image'):
if required not in svc:
errs.append(f'{p}: service missing required field "{required}"')
# forbidden nesting bugs
for sub in ('placement', 'resources', 'env', 'volume'):
if isinstance(svc.get(sub), dict):
for fb in ('depends_on', 'extra_ports', 'cmd', 'mounts'):
if fb in svc[sub]:
errs.append(f'{p}: "{fb}" nested under [service.{sub}] — must be at [[service]] level')
# placement.node must match parent vm directory
node = (svc.get('placement') or {}).get('node')
vm_dir = p.parent.name
if node and node != vm_dir:
errs.append(f'{p}: placement.node "{node}" mismatches dir "{vm_dir}"')
if not node:
errs.append(f'{p}: missing placement.node')
mem = (svc.get('resources') or {}).get('memory')
if not mem:
errs.append(f'{p}: missing resources.memory (mandatory per §8 rule 5)')
# Validate overlays parse too
for p in sorted(root.glob('overlays/*/overlay.toml')):
count += 1
try:
tomllib.load(open(p, 'rb'))
except Exception as e:
errs.append(f'{p}: TOML parse: {e}')
print(f'checked {count} files')
for e in errs:
print(' ', e)
sys.exit(1 if errs else 0)
PY