feat(iac): scaffold orca-platform layout (M1.1)
Lands the per-VM × per-service manifest tree, per-env overlays, VM specs
for SysEleven provisioning, DNS zone placeholder, plan/apply/validate
scripts, and a Makefile.
Structure (per INFRASTRUCTURE.md §2 + IMPLEMENTATION_PLAN.md M1.1):
- manifests/{vm-edge,vm-control,vm-data,stage}/<service>.toml — 35 stubs
- overlays/{dev,stage,prod}/overlay.toml — env-selection rules
- vms/{vm-edge,vm-control,vm-data,stage}.toml — OpenStack flavor/IP/firewall
- dns/yourplatform.com.zone.template — PowerDNS zone (body lands in M0.3)
- cluster.toml.tmpl — cluster-level config rendered per env
- scripts/validate.sh — TOML parse + structural sanity
- scripts/plan.sh — merge manifests + overlay → .orca-out/<env>/
- scripts/apply.sh — push to Orca controller (no-op until M1.2)
- Makefile — validate / plan / apply / diff / clean
Each manifest header names the milestone that finalises its real values;
images today are 'placeholder' for services that need their own repo to
exist first. make validate stays green; apply gates on ORCA_API_URL.
CI workflow swapped from the broken 'orca validate' to 'make validate',
which calls a Python TOML parser plus structural checks (placement.node
matches vm dir, resources.memory present, no mis-nested keys).
Refs: M1.1
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# CI for orca-platform (IaC). `shared` always runs; `validate` activates
|
# CI for orca-platform (IaC).
|
||||||
# when at least one Orca manifest lands.
|
# `shared` always runs (commitlint + gitleaks + trivy fs).
|
||||||
|
# `validate` always runs (parses every manifest + overlay + vm spec).
|
||||||
name: ci
|
name: ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -53,18 +54,18 @@ jobs:
|
|||||||
TRIVY_VERSION=0.70.0
|
TRIVY_VERSION=0.70.0
|
||||||
curl -fsSL "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \
|
curl -fsSL "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \
|
||||||
| tar -xz -C /tmp trivy
|
| 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:
|
validate:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
if: hashFiles('**/*.orca.yaml','**/*.orca.yml','manifests/**') != ''
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: install orca
|
- name: setup python
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
curl -fsSL https://orca.meghsakha.com/install.sh | sh
|
which python3
|
||||||
orca version
|
python3 --version
|
||||||
|
|
||||||
- name: orca validate
|
- name: make validate
|
||||||
run: orca validate ./
|
run: make validate
|
||||||
|
|||||||
@@ -35,3 +35,4 @@ vendor/
|
|||||||
|
|
||||||
# Rust
|
# Rust
|
||||||
**/target/
|
**/target/
|
||||||
|
.orca-out/
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Generated section is appended on release tag via `git-cliff` (see `.gitea/workfl
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- feat(iac): scaffold orca-platform — manifests/, overlays/, vms/, dns/, scripts/, Makefile (M1.1)
|
||||||
-
|
-
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -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,110 @@
|
|||||||
# orca-platform
|
# 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):
|
> 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) ·
|
> [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) ·
|
> [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)
|
> [Implementation Plan](https://gitea.meghsakha.com/platform/docs/src/branch/main/IMPLEMENTATION_PLAN.md)
|
||||||
|
|
||||||
## What this is
|
## 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
|
**Plane:** Infra
|
||||||
**Owner:** @sharang
|
**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)
|
**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
|
||||||
|
├── vms/ # OpenStack VM specs (consumed by M1.2 provisioner)
|
||||||
|
│ ├── vm-edge.toml m2.small, public IP, identity+infra
|
||||||
|
│ ├── vm-control.toml m2.medium, private, control plane
|
||||||
|
│ ├── vm-data.toml m2.medium, private, data plane (scale driver)
|
||||||
|
│ └── stage.toml m2.small, public IP, ephemeral
|
||||||
|
├── dns/
|
||||||
|
│ └── yourplatform.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
|
## Run locally
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# prerequisites: see CONTRIBUTING.md for tooling once code lands
|
make validate # check all manifests parse + have required fields
|
||||||
make dev # starts dependencies + this service on http://localhost:3000
|
make plan ENV=stage # resolve manifests for stage → .orca-out/stage/
|
||||||
make test # unit + integration
|
make plan ENV=prod # same for prod
|
||||||
make e2e # only if this repo ships user-facing flows
|
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 (consumes `vms/*.toml`); 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
|
## Endpoints / surface
|
||||||
|
|
||||||
{{For services: list the top-level routes or commands.
|
| Target | What it does |
|
||||||
For libraries: list the public API entry points.
|
|---|---|
|
||||||
For IaC: list the make targets.}}
|
| `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
|
## Deployment
|
||||||
|
|
||||||
| Env | URL | How |
|
| Env | Apply path | Trigger |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| dev | `http://localhost:3000` | `make dev` |
|
| dev | `docker-compose` in each product repo | dev's machine |
|
||||||
| stage | `https://orca-platform.stage.yourplatform.com` | auto on merge to `main` |
|
| stage | `make apply ENV=stage` against the stage Orca controller | CI on merge to main + image build |
|
||||||
| prod | `https://orca-platform.yourplatform.com` | manual: tag `vX.Y.Z` + sign-off |
|
| 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
|
## Observability
|
||||||
|
|
||||||
- Traces, logs, metrics: [SigNoz](https://signoz.meghsakha.com) — service name `orca-platform`
|
- Traces, logs, metrics: [SigNoz](https://signoz.meghsakha.com) — service name per individual container
|
||||||
- Audit events: Tenant Registry `/audit` (Retraced-shape schema)
|
- On-call: `oncall@yourplatform.com` · runbooks at `platform/docs/runbooks/`
|
||||||
- On-call: `oncall@yourplatform.com` · runbook at `platform/docs/runbooks/orca-platform.md`
|
|
||||||
|
|
||||||
## Contributing
|
## 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
|
## 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@yourplatform.com"
|
||||||
|
|
||||||
|
[ai]
|
||||||
|
provider = "litellm"
|
||||||
|
endpoint = "https://llm.yourplatform.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 yourplatform.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 yourplatform.com.
|
||||||
|
$TTL 60
|
||||||
|
|
||||||
|
@ IN SOA ns1.yourplatform.com. oncall.yourplatform.com. (
|
||||||
|
; serial — bumped by CI on every commit
|
||||||
|
2026051800
|
||||||
|
3600 ; refresh
|
||||||
|
600 ; retry
|
||||||
|
604800 ; expire
|
||||||
|
60 ; minimum TTL
|
||||||
|
)
|
||||||
|
|
||||||
|
@ IN NS ns1.yourplatform.com.
|
||||||
|
@ IN NS ns2.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.com/portal:env-stage"
|
||||||
|
port = 3000
|
||||||
|
domain = "*.stage.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.com/portal:placeholder"
|
||||||
|
port = 3000
|
||||||
|
domain = "*.yourplatform.com"
|
||||||
|
depends_on = ["tenant-registry"]
|
||||||
|
|
||||||
|
[service.placement]
|
||||||
|
node = "vm-control"
|
||||||
|
|
||||||
|
[service.resources]
|
||||||
|
memory = "1Gi"
|
||||||
|
cpu = 1.0
|
||||||
|
|
||||||
|
[service.env]
|
||||||
|
KEYCLOAK_ISSUER = "https://auth.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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.yourplatform.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
+63
@@ -0,0 +1,63 @@
|
|||||||
|
#!/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}')
|
||||||
|
# Validate VMs parse too
|
||||||
|
for p in sorted(root.glob('vms/*.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
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# VM specs
|
||||||
|
|
||||||
|
Source of truth for the four billable VMs in `INFRASTRUCTURE.md §1`. These
|
||||||
|
files are consumed by the M1.2 provisioning step (Terraform/OpenStack against
|
||||||
|
SysEleven DUS2). Orca itself ignores this directory — it deals with services,
|
||||||
|
not infrastructure.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# stage — single-VM stage environment. App plane only, ephemeral DBs.
|
||||||
|
# Calls OUT to prod Keycloak + prod Stalwart per §2 isolation rules.
|
||||||
|
|
||||||
|
[vm]
|
||||||
|
name = "stage"
|
||||||
|
env = "stage"
|
||||||
|
flavor = "m2.small" # 2 vCPU, 8 GB RAM
|
||||||
|
public_ip = true # tester access without VPN
|
||||||
|
region = "DUS2"
|
||||||
|
private_network = "platform-stage"
|
||||||
|
private_ip_cidr = "10.1.1.0/24"
|
||||||
|
|
||||||
|
[vm.disk]
|
||||||
|
block_volume_gb = 50 # pg-stage + mongo-stage + qdrant-stage; reset per release
|
||||||
|
|
||||||
|
[vm.firewall]
|
||||||
|
ingress_public = [
|
||||||
|
{ proto = "tcp", ports = [80, 443], source = "0.0.0.0/0", purpose = "stage portal" },
|
||||||
|
]
|
||||||
|
ingress_private = [] # stage does not accept private traffic from prod
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# vm-control — Control plane (portal, tenant-registry, ERPNext, Stalwart).
|
||||||
|
# See INFRASTRUCTURE.md §1, §2, §6.
|
||||||
|
|
||||||
|
[vm]
|
||||||
|
name = "vm-control"
|
||||||
|
env = "prod"
|
||||||
|
flavor = "m2.medium" # 4 vCPU, 16 GB RAM
|
||||||
|
public_ip = false # only reachable via vm-edge orca-proxy
|
||||||
|
region = "DUS2"
|
||||||
|
private_network = "platform-prod"
|
||||||
|
private_ip_cidr = "10.0.2.0/24"
|
||||||
|
|
||||||
|
[vm.disk]
|
||||||
|
block_volume_gb = 250 # MariaDB (ERPNext) + Stalwart mail spool, medium growth
|
||||||
|
|
||||||
|
[vm.firewall]
|
||||||
|
ingress_public = [] # no public ingress
|
||||||
|
ingress_private = [
|
||||||
|
{ proto = "tcp", ports = "all", source = "10.0.0.0/16", purpose = "intra-platform" },
|
||||||
|
]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# vm-data — Data plane (CERTifAI, compliance, pg-app, MongoDB, MinIO).
|
||||||
|
# Scale driver — bump flavor at Tier B/C per §13.
|
||||||
|
|
||||||
|
[vm]
|
||||||
|
name = "vm-data"
|
||||||
|
env = "prod"
|
||||||
|
flavor = "m2.medium" # 4 vCPU, 16 GB RAM (Tier B bump: m2.large)
|
||||||
|
public_ip = false
|
||||||
|
region = "DUS2"
|
||||||
|
private_network = "platform-prod"
|
||||||
|
private_ip_cidr = "10.0.3.0/24"
|
||||||
|
|
||||||
|
[vm.disk]
|
||||||
|
block_volume_gb = 500 # MongoDB + pg-app + Qdrant + MinIO, fast growth (scales with N customers)
|
||||||
|
|
||||||
|
[vm.firewall]
|
||||||
|
ingress_public = []
|
||||||
|
ingress_private = [
|
||||||
|
{ proto = "tcp", ports = "all", source = "10.0.0.0/16", purpose = "intra-platform" },
|
||||||
|
]
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# vm-edge — Identity + Infra plane, public IP, root auth dependency.
|
||||||
|
# See INFRASTRUCTURE.md §1, §2.
|
||||||
|
|
||||||
|
[vm]
|
||||||
|
name = "vm-edge"
|
||||||
|
env = "prod"
|
||||||
|
flavor = "m2.small" # 2 vCPU, 8 GB RAM
|
||||||
|
public_ip = true
|
||||||
|
region = "DUS2"
|
||||||
|
private_network = "platform-prod"
|
||||||
|
private_ip_cidr = "10.0.1.0/24"
|
||||||
|
|
||||||
|
[vm.disk]
|
||||||
|
block_volume_gb = 50 # pg-keycloak + pg-infisical + Gitea repos (slow growth)
|
||||||
|
|
||||||
|
[vm.firewall]
|
||||||
|
# vm-edge is the only host accepting public traffic. Everything else is
|
||||||
|
# behind the private network.
|
||||||
|
ingress_public = [
|
||||||
|
{ proto = "tcp", ports = [80, 443], source = "0.0.0.0/0", purpose = "orca-proxy HTTP/HTTPS" },
|
||||||
|
{ proto = "tcp", ports = [53], source = "0.0.0.0/0", purpose = "PowerDNS (TCP)" },
|
||||||
|
{ proto = "udp", ports = [53], source = "0.0.0.0/0", purpose = "PowerDNS (UDP)" },
|
||||||
|
]
|
||||||
|
ingress_private = [
|
||||||
|
{ proto = "tcp", ports = "all", source = "10.0.0.0/16", purpose = "intra-platform" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user