Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions API_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added

- **Managed app deployments — operator reconcile** (part of the App Deployments epic) — the `lnvps_operator` now reconciles `app_deployment` rows for its configured cluster into Kubernetes: one locked-down namespace per deployment (restricted Pod Security + default-deny NetworkPolicy), a Deployment + ClusterIP Service per compose service, a cert-manager-TLS Ingress for the `expose: ingress` port, PVCs for `volumes:`, and ConfigMap/Secret-backed `files:` mounted read-only. Generated `secrets:` are created once and kept stable; `${…}` env/files are resolved per deployment. PVCs use the cluster's default StorageClass. Retention is data-preserving: an **expired** deployment is scaled to **0 replicas** (pods stop, persistent volumes retained) rather than torn down — only real deletion removes the namespace/volumes. As a result the customer `AppDeployment` `status` and `hostname` fields become populated once a deployment is provisioned (previously always pending/empty). Purely operational — no request/response schema change.
- **Managed app deployments — config-file mounts (`files:`)** (part of the App Deployments epic) — the app `compose` grammar now supports injecting read-only config files into a container (rendered into a ConfigMap, or a Secret when `sensitive`, and mounted via `subPath`), so file-driven apps (e.g. strfry's `strfry.conf`) are deployable. Each service's `files:` entry declares a `path` plus either inline templated `content` (with `${…}` filled from `config`/`secrets`) or `content_from` a `config` field (including a new `type: file` for customer-supplied whole files). Validated by the shared parser (and therefore the admin catalog API): absolute non-traversal paths, a single content source, ≤256 KiB inline content, `content_from` must reference a declared field, and a file may not overlap a read-write data volume. Distinct from `volumes:` (persistent read-write PVCs).
- **Managed app deployments — compose validation** (part of the App Deployments epic) — the admin app-catalog endpoints (`POST`/`PATCH /api/admin/v1/apps`) now fully parse and validate the app's `compose` document (via a new shared `lnvps_compose` parser, the same code the operator will use to render Kubernetes objects) instead of only checking it's non-empty. Invalid or unsafe compose is rejected at catalog-edit time: unknown `depends_on`, `expose: ingress` on a non-HTTP port, absolute/`..`-traversal volume mount paths, and malformed `backup` blocks all return a clear `400`.
- **Managed app deployments — customer catalog & deployment views** (part of the App Deployments epic) — new read-only customer endpoints: `GET /api/v1/apps` and `GET /api/v1/apps/{id}` browse the catalog of offered apps (each exposes its docker-compose-style `compose` so clients can render the deploy form), and `GET /api/v1/app-deployments` and `GET /api/v1/app-deployments/{id}` list/show your own deployments (name, hostname, status, desired state, billing subscription). Ordering and lifecycle control land in a later increment.
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions lnvps_operator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ path = "src/main.rs"

[dependencies]
lnvps_db = { path = "../lnvps_db", features = ["nostr-domain"] }
lnvps_compose = { path = "../lnvps_compose" }
rand = "0.8"
chrono = { version = "0.4", features = ["serde"] }

# Workspace dependencies
tokio = { workspace = true, features = ["time", "signal"] }
Expand Down
Loading
Loading