feat(iac): scaffold orca-platform layout (M1.1)
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,3 +35,4 @@ vendor/
|
||||
|
||||
# Rust
|
||||
**/target/
|
||||
.orca-out/
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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/"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -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}"
|
||||
@@ -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}"
|
||||
@@ -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}"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
Executable
+31
@@ -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"
|
||||
Executable
+56
@@ -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"
|
||||
@@ -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
|
||||
Executable
+56
@@ -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
|
||||
Reference in New Issue
Block a user