Add cloudhost-platform Helm chart and registry ingress manifests.
Deploy backend, frontend, PostgreSQL, and Redis on Kubernetes with optional Ingress/TLS, SQL migration hooks, and public registry exposure at repo.3fase.ir. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -177,6 +177,20 @@ ACTIVE ──(expires)──► SUSPENDED ──(grace)──► PENDING_DELETIO
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Helm Chart: cloudhost-platform
|
||||||
|
|
||||||
|
Chart at `backend/helm/cloudhost-platform/` deploys the **control plane** (NestJS API, Next.js UI, PostgreSQL, Redis) into a dedicated namespace (default `cloudhost`).
|
||||||
|
|
||||||
|
| Value | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `ingress.enabled` | Create Ingress (default `true`) |
|
||||||
|
| `ingress.tls.enabled` | cert-manager TLS via `clusterIssuer` |
|
||||||
|
| `ingress.frontend.host` / `ingress.api.host` | Public hostnames |
|
||||||
|
| `postgres.password` / `secrets.jwtSecret` | Credentials (auto-generated if empty on first install) |
|
||||||
|
| `migrations.enabled` | Post-install SQL migration Job |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🚀 Helm Chart: cloudhost-app
|
## 🚀 Helm Chart: cloudhost-app
|
||||||
|
|
||||||
Single chart at `backend/helm/cloudhost-app/` handles all runtimes:
|
Single chart at `backend/helm/cloudhost-app/` handles all runtimes:
|
||||||
@@ -208,6 +222,7 @@ host/
|
|||||||
├── backend/
|
├── backend/
|
||||||
│ ├── Dockerfile
|
│ ├── Dockerfile
|
||||||
│ ├── package.json
|
│ ├── package.json
|
||||||
|
│ ├── helm/cloudhost-platform/ # Helm chart for control plane
|
||||||
│ ├── helm/cloudhost-app/ # Helm chart for user apps
|
│ ├── helm/cloudhost-app/ # Helm chart for user apps
|
||||||
│ ├── src/
|
│ ├── src/
|
||||||
│ │ ├── main.ts / app.module.ts
|
│ │ ├── main.ts / app.module.ts
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
#### Helm Chart (`backend/helm/cloudhost-platform/`)
|
||||||
|
- **Platform chart** — deploy control plane (backend, frontend, PostgreSQL, Redis) on Kubernetes
|
||||||
|
- Ingress with `ingress.enabled` / `ingress.tls.enabled` and cert-manager TLS
|
||||||
|
- Post-install migration Job for SQL files in `migrations/`
|
||||||
|
- `values-production.example.yaml` for production overrides
|
||||||
|
|
||||||
#### Helm Chart (`backend/helm/cloudhost-app/`)
|
#### Helm Chart (`backend/helm/cloudhost-app/`)
|
||||||
- **New Helm chart** replacing legacy Handlebars templates for all deployments
|
- **New Helm chart** replacing legacy Handlebars templates for all deployments
|
||||||
- `deployment.yaml` — app deployment with `imagePullSecrets`, `imagePullPolicy: Always`, health probes
|
- `deployment.yaml` — app deployment with `imagePullSecrets`, `imagePullPolicy: Always`, health probes
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ host/
|
|||||||
│ ├── Dockerfile
|
│ ├── Dockerfile
|
||||||
│ ├── package.json
|
│ ├── package.json
|
||||||
│ ├── helm/
|
│ ├── helm/
|
||||||
│ │ └── cloudhost-app/ # Helm chart (all runtimes)
|
│ │ ├── cloudhost-platform/ # Helm chart (control plane)
|
||||||
|
│ │ └── cloudhost-app/ # Helm chart (user apps)
|
||||||
│ │ ├── Chart.yaml
|
│ │ ├── Chart.yaml
|
||||||
│ │ ├── values.yaml
|
│ │ ├── values.yaml
|
||||||
│ │ └── templates/ # K8s manifest templates
|
│ │ └── templates/ # K8s manifest templates
|
||||||
@@ -147,7 +148,29 @@ docker compose up --build
|
|||||||
|
|
||||||
Backend at port 4000, Frontend at port 3000.
|
Backend at port 4000, Frontend at port 3000.
|
||||||
|
|
||||||
### 4. Run Locally (development)
|
### 4. Deploy Platform on Kubernetes (Helm)
|
||||||
|
|
||||||
|
Prerequisites: NGINX Ingress, cert-manager (if TLS enabled), StorageClass for PVCs.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build images (set API URL to match ingress.api.host when TLS is on)
|
||||||
|
export REG=your-registry.example.com
|
||||||
|
docker build -t $REG/cloudhost-backend:latest ./backend
|
||||||
|
docker build -t $REG/cloudhost-frontend:latest \
|
||||||
|
--build-arg NEXT_PUBLIC_API_URL=https://api.platform.example.com ./frontend
|
||||||
|
docker push $REG/cloudhost-backend:latest $REG/cloudhost-frontend:latest
|
||||||
|
|
||||||
|
# Install (copy and edit values-production.example.yaml first)
|
||||||
|
helm upgrade --install cloudhost ./backend/helm/cloudhost-platform \
|
||||||
|
-n cloudhost --create-namespace \
|
||||||
|
-f backend/helm/cloudhost-platform/values-production.example.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Key values: `ingress.enabled`, `ingress.tls.enabled`, `ingress.frontend.host`, `ingress.api.host`, `postgres.password`, `secrets.jwtSecret`.
|
||||||
|
|
||||||
|
See chart defaults in `backend/helm/cloudhost-platform/values.yaml` and post-install notes via `helm get notes cloudhost -n cloudhost`.
|
||||||
|
|
||||||
|
### 5. Run Locally (development)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Terminal 1 — Backend
|
# Terminal 1 — Backend
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Patterns to ignore when packaging chart
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
values-production.yaml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: cloudhost-platform
|
||||||
|
description: CloudHost control plane — backend, frontend, PostgreSQL, Redis
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "1.0.0"
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# cloudhost-platform
|
||||||
|
|
||||||
|
Helm chart for the CloudHost control plane: backend API, frontend UI, PostgreSQL, and Redis.
|
||||||
|
|
||||||
|
## Quick install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade --install cloudhost . \
|
||||||
|
-n cloudhost --create-namespace \
|
||||||
|
-f values-production.example.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ingress / TLS
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `ingress.enabled` | Create Ingress resources |
|
||||||
|
| `ingress.tls.enabled` | Enable cert-manager TLS |
|
||||||
|
| `ingress.tls.clusterIssuer` | ClusterIssuer name (e.g. `letsencrypt-prod`) |
|
||||||
|
| `ingress.frontend.host` | UI hostname |
|
||||||
|
| `ingress.api.host` | API hostname |
|
||||||
|
|
||||||
|
Requires NGINX Ingress Controller and cert-manager when TLS is enabled.
|
||||||
|
|
||||||
|
## Frontend image
|
||||||
|
|
||||||
|
Build with the public API URL baked in:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t $REG/cloudhost-frontend:tag \
|
||||||
|
--build-arg NEXT_PUBLIC_API_URL=https://api.platform.example.com \
|
||||||
|
../../frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migrations
|
||||||
|
|
||||||
|
SQL files in `migrations/` run via a post-install/post-upgrade Job when `migrations.enabled` is true. They assume incremental schema changes on top of an existing database; for a completely empty database you may need to bootstrap schema first (e.g. one-time `NODE_ENV=development` or manual setup).
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
-- Temporary external access grants (Redis, RabbitMQ, database)
|
||||||
|
CREATE TYPE service_access_target AS ENUM (
|
||||||
|
'database',
|
||||||
|
'redis',
|
||||||
|
'rabbitmq_amqp',
|
||||||
|
'rabbitmq_management'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TYPE service_access_grant_status AS ENUM (
|
||||||
|
'active',
|
||||||
|
'expired',
|
||||||
|
'revoked'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS service_access_grants (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
"applicationId" UUID NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
|
||||||
|
"userId" UUID NOT NULL,
|
||||||
|
"clusterId" UUID NOT NULL,
|
||||||
|
namespace VARCHAR(255) NOT NULL,
|
||||||
|
target service_access_target NOT NULL,
|
||||||
|
"nodePort" INTEGER NOT NULL,
|
||||||
|
"targetPort" INTEGER NOT NULL,
|
||||||
|
host VARCHAR(255) NOT NULL,
|
||||||
|
"k8sServiceName" VARCHAR(255) NOT NULL,
|
||||||
|
status service_access_grant_status NOT NULL DEFAULT 'active',
|
||||||
|
"expiresAt" TIMESTAMPTZ NOT NULL,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_service_access_grants_app_target_status
|
||||||
|
ON service_access_grants ("applicationId", target, status);
|
||||||
|
|
||||||
|
INSERT INTO platform_settings (id, key, value, description, "createdAt", "updatedAt")
|
||||||
|
SELECT gen_random_uuid(), 'access_max_duration_minutes', '240',
|
||||||
|
'Maximum duration (minutes) for temporary external service access',
|
||||||
|
NOW(), NOW()
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM platform_settings WHERE key = 'access_max_duration_minutes'
|
||||||
|
);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- Add DOCKED lifecycle status and dock metadata (run if TypeORM sync is disabled)
|
||||||
|
ALTER TYPE applications_lifecyclestatus_enum ADD VALUE IF NOT EXISTS 'docked';
|
||||||
|
|
||||||
|
ALTER TABLE applications
|
||||||
|
ADD COLUMN IF NOT EXISTS "dockedAt" TIMESTAMPTZ,
|
||||||
|
ADD COLUMN IF NOT EXISTS "dockSnapshotId" VARCHAR;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS resource_credits (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
"userId" UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
"sourceAppName" VARCHAR,
|
||||||
|
runtime VARCHAR NOT NULL,
|
||||||
|
"databaseType" VARCHAR NOT NULL,
|
||||||
|
"cpuLimit" VARCHAR NOT NULL,
|
||||||
|
"memoryLimit" VARCHAR NOT NULL,
|
||||||
|
replicas INT NOT NULL DEFAULT 1,
|
||||||
|
"dbStorageSize" VARCHAR,
|
||||||
|
"appStorageSize" VARCHAR,
|
||||||
|
"enableRedis" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"enableRabbitmq" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"enableElasticsearch" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"billingCycle" VARCHAR,
|
||||||
|
"expiresAt" TIMESTAMPTZ NOT NULL,
|
||||||
|
"consumedAt" TIMESTAMPTZ,
|
||||||
|
"appliedApplicationId" VARCHAR,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_resource_credits_user_active
|
||||||
|
ON resource_credits ("userId", "expiresAt")
|
||||||
|
WHERE "consumedAt" IS NULL;
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
-- Usage-based pricing catalog (replaces per-cycle service_plans + scattered platform_settings)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS pricing_rates (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
runtime VARCHAR NOT NULL,
|
||||||
|
resource_type VARCHAR NOT NULL,
|
||||||
|
hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE (runtime, resource_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS addon_rates (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
resource_type VARCHAR NOT NULL UNIQUE,
|
||||||
|
hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Seed addon rows
|
||||||
|
INSERT INTO addon_rates (resource_type, hourly_price, monthly_price, yearly_price)
|
||||||
|
VALUES
|
||||||
|
('redis_addon', 0, 0, 0),
|
||||||
|
('rabbitmq_addon', 0, 0, 0),
|
||||||
|
('elasticsearch_addon', 0, 0, 0),
|
||||||
|
('custom_domain_addon', 0, 0, 0)
|
||||||
|
ON CONFLICT (resource_type) DO NOTHING;
|
||||||
|
|
||||||
|
-- Migrate runtime rates from service_plans + pricing_rules
|
||||||
|
INSERT INTO pricing_rates (runtime, resource_type, hourly_price, monthly_price, yearly_price, is_active)
|
||||||
|
SELECT
|
||||||
|
sp.runtime,
|
||||||
|
pr.resource_type,
|
||||||
|
COALESCE(MAX(CASE WHEN sp."billingCycle" = 'hourly' THEN pr.unit_price END), 0),
|
||||||
|
COALESCE(MAX(CASE WHEN sp."billingCycle" = 'monthly' THEN pr.unit_price END), 0),
|
||||||
|
COALESCE(MAX(CASE WHEN sp."billingCycle" = 'yearly' THEN pr.unit_price END), 0),
|
||||||
|
BOOL_OR(sp."isActive")
|
||||||
|
FROM pricing_rules pr
|
||||||
|
JOIN service_plans sp ON sp.id = pr."planId"
|
||||||
|
WHERE pr.resource_type IN (
|
||||||
|
'base_fee', 'cpu_per_core', 'memory_per_gb', 'storage_per_gb', 'database_addon'
|
||||||
|
)
|
||||||
|
GROUP BY sp.runtime, pr.resource_type
|
||||||
|
ON CONFLICT (runtime, resource_type) DO UPDATE SET
|
||||||
|
hourly_price = EXCLUDED.hourly_price,
|
||||||
|
monthly_price = EXCLUDED.monthly_price,
|
||||||
|
yearly_price = EXCLUDED.yearly_price,
|
||||||
|
is_active = EXCLUDED.is_active,
|
||||||
|
"updatedAt" = NOW();
|
||||||
|
|
||||||
|
-- Optional services from platform_settings JSON
|
||||||
|
UPDATE addon_rates ar SET
|
||||||
|
hourly_price = COALESCE((s.parsed->'redis'->>'hourly')::decimal, 0),
|
||||||
|
monthly_price = COALESCE((s.parsed->'redis'->>'monthly')::decimal, 0),
|
||||||
|
yearly_price = COALESCE((s.parsed->'redis'->>'yearly')::decimal, 0)
|
||||||
|
FROM (
|
||||||
|
SELECT value::jsonb AS parsed
|
||||||
|
FROM platform_settings
|
||||||
|
WHERE key = 'optional_services_pricing_toman'
|
||||||
|
LIMIT 1
|
||||||
|
) s
|
||||||
|
WHERE ar.resource_type = 'redis_addon' AND s.parsed IS NOT NULL;
|
||||||
|
|
||||||
|
UPDATE addon_rates ar SET
|
||||||
|
hourly_price = COALESCE((s.parsed->'rabbitmq'->>'hourly')::decimal, 0),
|
||||||
|
monthly_price = COALESCE((s.parsed->'rabbitmq'->>'monthly')::decimal, 0),
|
||||||
|
yearly_price = COALESCE((s.parsed->'rabbitmq'->>'yearly')::decimal, 0)
|
||||||
|
FROM (
|
||||||
|
SELECT value::jsonb AS parsed
|
||||||
|
FROM platform_settings
|
||||||
|
WHERE key = 'optional_services_pricing_toman'
|
||||||
|
LIMIT 1
|
||||||
|
) s
|
||||||
|
WHERE ar.resource_type = 'rabbitmq_addon' AND s.parsed IS NOT NULL;
|
||||||
|
|
||||||
|
UPDATE addon_rates ar SET
|
||||||
|
hourly_price = COALESCE((s.parsed->'elasticsearch'->>'hourly')::decimal, 0),
|
||||||
|
monthly_price = COALESCE((s.parsed->'elasticsearch'->>'monthly')::decimal, 0),
|
||||||
|
yearly_price = COALESCE((s.parsed->'elasticsearch'->>'yearly')::decimal, 0)
|
||||||
|
FROM (
|
||||||
|
SELECT value::jsonb AS parsed
|
||||||
|
FROM platform_settings
|
||||||
|
WHERE key = 'optional_services_pricing_toman'
|
||||||
|
LIMIT 1
|
||||||
|
) s
|
||||||
|
WHERE ar.resource_type = 'elasticsearch_addon' AND s.parsed IS NOT NULL;
|
||||||
|
|
||||||
|
UPDATE addon_rates ar SET
|
||||||
|
monthly_price = COALESCE(s.price, 0)
|
||||||
|
FROM (
|
||||||
|
SELECT value::decimal AS price
|
||||||
|
FROM platform_settings
|
||||||
|
WHERE key = 'custom_domain_monthly_price_toman'
|
||||||
|
LIMIT 1
|
||||||
|
) s
|
||||||
|
WHERE ar.resource_type = 'custom_domain_addon' AND s.price IS NOT NULL;
|
||||||
|
|
||||||
|
-- Default runtime rows for nodejs, laravel, wordpress
|
||||||
|
INSERT INTO pricing_rates (runtime, resource_type, hourly_price, monthly_price, yearly_price)
|
||||||
|
SELECT r.runtime, t.resource_type, 0, 0, 0
|
||||||
|
FROM (VALUES ('nodejs'), ('laravel'), ('wordpress')) AS r(runtime)
|
||||||
|
CROSS JOIN (
|
||||||
|
VALUES
|
||||||
|
('base_fee'),
|
||||||
|
('cpu_per_core'),
|
||||||
|
('memory_per_gb'),
|
||||||
|
('storage_per_gb'),
|
||||||
|
('database_addon')
|
||||||
|
) AS t(resource_type)
|
||||||
|
ON CONFLICT (runtime, resource_type) DO NOTHING;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- Ensure pricing_rates rows exist for every AppRuntime enum value
|
||||||
|
|
||||||
|
INSERT INTO pricing_rates (runtime, resource_type, hourly_price, monthly_price, yearly_price)
|
||||||
|
SELECT r.runtime, t.resource_type, 0, 0, 0
|
||||||
|
FROM (
|
||||||
|
VALUES
|
||||||
|
('nodejs'),
|
||||||
|
('laravel'),
|
||||||
|
('wordpress'),
|
||||||
|
('go'),
|
||||||
|
('php'),
|
||||||
|
('python'),
|
||||||
|
('django'),
|
||||||
|
('dotnet')
|
||||||
|
) AS r(runtime)
|
||||||
|
CROSS JOIN (
|
||||||
|
VALUES
|
||||||
|
('base_fee'),
|
||||||
|
('cpu_per_core'),
|
||||||
|
('memory_per_gb'),
|
||||||
|
('storage_per_gb'),
|
||||||
|
('database_addon')
|
||||||
|
) AS t(resource_type)
|
||||||
|
ON CONFLICT (runtime, resource_type) DO NOTHING;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
-- Per-addon consumption footprint and optional resource unit prices (0 = use app runtime rates)
|
||||||
|
|
||||||
|
ALTER TABLE addon_rates
|
||||||
|
ADD COLUMN IF NOT EXISTS cpu_limit VARCHAR,
|
||||||
|
ADD COLUMN IF NOT EXISTS memory_limit VARCHAR,
|
||||||
|
ADD COLUMN IF NOT EXISTS storage_gi DECIMAL(10, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS log_shipper_cpu_limit VARCHAR,
|
||||||
|
ADD COLUMN IF NOT EXISTS log_shipper_memory_limit VARCHAR,
|
||||||
|
ADD COLUMN IF NOT EXISTS cpu_hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS cpu_monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS cpu_yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS memory_hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS memory_monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS memory_yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS storage_hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS storage_monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS storage_yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
UPDATE addon_rates SET cpu_limit = '200m', memory_limit = '256Mi', storage_gi = 1
|
||||||
|
WHERE resource_type = 'redis_addon' AND cpu_limit IS NULL;
|
||||||
|
|
||||||
|
UPDATE addon_rates SET cpu_limit = '500m', memory_limit = '512Mi', storage_gi = 2
|
||||||
|
WHERE resource_type = 'rabbitmq_addon' AND cpu_limit IS NULL;
|
||||||
|
|
||||||
|
UPDATE addon_rates SET cpu_limit = '50m', memory_limit = '64Mi', storage_gi = 0,
|
||||||
|
log_shipper_cpu_limit = '50m', log_shipper_memory_limit = '64Mi'
|
||||||
|
WHERE resource_type = 'elasticsearch_addon' AND cpu_limit IS NULL;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
-- Optional services: resource profile + pricing matrix (same model as application runtimes)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS optional_service_profiles (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
service VARCHAR NOT NULL UNIQUE,
|
||||||
|
cpu_limit VARCHAR NOT NULL DEFAULT '200m',
|
||||||
|
memory_limit VARCHAR NOT NULL DEFAULT '256Mi',
|
||||||
|
storage_gi DECIMAL(10, 2) NOT NULL DEFAULT 0,
|
||||||
|
log_shipper_cpu_limit VARCHAR,
|
||||||
|
log_shipper_memory_limit VARCHAR,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS optional_service_rates (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
service VARCHAR NOT NULL,
|
||||||
|
resource_type VARCHAR NOT NULL,
|
||||||
|
hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE (service, resource_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO optional_service_profiles (service, cpu_limit, memory_limit, storage_gi, log_shipper_cpu_limit, log_shipper_memory_limit)
|
||||||
|
VALUES
|
||||||
|
('redis', '200m', '256Mi', 1, NULL, NULL),
|
||||||
|
('rabbitmq', '500m', '512Mi', 2, NULL, NULL),
|
||||||
|
('elasticsearch', '50m', '64Mi', 0, '50m', '64Mi')
|
||||||
|
ON CONFLICT (service) DO NOTHING;
|
||||||
|
|
||||||
|
UPDATE optional_service_profiles SET
|
||||||
|
log_shipper_cpu_limit = '50m',
|
||||||
|
log_shipper_memory_limit = '64Mi'
|
||||||
|
WHERE service = 'elasticsearch' AND log_shipper_cpu_limit IS NULL;
|
||||||
|
|
||||||
|
INSERT INTO optional_service_rates (service, resource_type, hourly_price, monthly_price, yearly_price)
|
||||||
|
SELECT s.service, t.resource_type, 0, 0, 0
|
||||||
|
FROM (VALUES ('redis'), ('rabbitmq'), ('elasticsearch')) AS s(service)
|
||||||
|
CROSS JOIN (
|
||||||
|
VALUES ('base_fee'), ('cpu_per_core'), ('memory_per_gb'), ('storage_per_gb')
|
||||||
|
) AS t(resource_type)
|
||||||
|
ON CONFLICT (service, resource_type) DO NOTHING;
|
||||||
|
|
||||||
|
-- Migrate legacy per-resource addon prices into optional_service_rates (if columns exist)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_name = 'addon_rates' AND column_name = 'cpu_hourly_price'
|
||||||
|
) THEN
|
||||||
|
UPDATE optional_service_rates osr SET
|
||||||
|
hourly_price = COALESCE(ar.cpu_hourly_price, 0),
|
||||||
|
monthly_price = COALESCE(ar.cpu_monthly_price, 0),
|
||||||
|
yearly_price = COALESCE(ar.cpu_yearly_price, 0)
|
||||||
|
FROM addon_rates ar
|
||||||
|
WHERE ar.resource_type = 'redis_addon' AND osr.service = 'redis' AND osr.resource_type = 'cpu_per_core'
|
||||||
|
AND (ar.cpu_hourly_price > 0 OR ar.cpu_monthly_price > 0);
|
||||||
|
|
||||||
|
UPDATE optional_service_rates osr SET
|
||||||
|
hourly_price = COALESCE(ar.memory_hourly_price, 0),
|
||||||
|
monthly_price = COALESCE(ar.memory_monthly_price, 0),
|
||||||
|
yearly_price = COALESCE(ar.memory_yearly_price, 0)
|
||||||
|
FROM addon_rates ar
|
||||||
|
WHERE ar.resource_type = 'redis_addon' AND osr.service = 'redis' AND osr.resource_type = 'memory_per_gb'
|
||||||
|
AND (ar.memory_hourly_price > 0 OR ar.memory_monthly_price > 0);
|
||||||
|
|
||||||
|
UPDATE optional_service_rates osr SET
|
||||||
|
hourly_price = COALESCE(ar.storage_hourly_price, 0),
|
||||||
|
monthly_price = COALESCE(ar.storage_monthly_price, 0),
|
||||||
|
yearly_price = COALESCE(ar.storage_yearly_price, 0)
|
||||||
|
FROM addon_rates ar
|
||||||
|
WHERE ar.resource_type = 'redis_addon' AND osr.service = 'redis' AND osr.resource_type = 'storage_per_gb'
|
||||||
|
AND (ar.storage_hourly_price > 0 OR ar.storage_monthly_price > 0);
|
||||||
|
|
||||||
|
UPDATE optional_service_profiles osp SET
|
||||||
|
cpu_limit = COALESCE(ar.cpu_limit, osp.cpu_limit),
|
||||||
|
memory_limit = COALESCE(ar.memory_limit, osp.memory_limit),
|
||||||
|
storage_gi = COALESCE(ar.storage_gi, osp.storage_gi)
|
||||||
|
FROM addon_rates ar WHERE ar.resource_type = 'redis_addon' AND osp.service = 'redis';
|
||||||
|
|
||||||
|
UPDATE optional_service_profiles osp SET
|
||||||
|
cpu_limit = COALESCE(ar.cpu_limit, osp.cpu_limit),
|
||||||
|
memory_limit = COALESCE(ar.memory_limit, osp.memory_limit),
|
||||||
|
storage_gi = COALESCE(ar.storage_gi, osp.storage_gi)
|
||||||
|
FROM addon_rates ar WHERE ar.resource_type = 'rabbitmq_addon' AND osp.service = 'rabbitmq';
|
||||||
|
|
||||||
|
UPDATE optional_service_profiles osp SET
|
||||||
|
cpu_limit = COALESCE(ar.cpu_limit, osp.cpu_limit),
|
||||||
|
memory_limit = COALESCE(ar.memory_limit, osp.memory_limit),
|
||||||
|
log_shipper_cpu_limit = COALESCE(ar.log_shipper_cpu_limit, osp.log_shipper_cpu_limit),
|
||||||
|
log_shipper_memory_limit = COALESCE(ar.log_shipper_memory_limit, osp.log_shipper_memory_limit)
|
||||||
|
FROM addon_rates ar WHERE ar.resource_type = 'elasticsearch_addon' AND osp.service = 'elasticsearch';
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
-- Per-deploy optional service resource limits (user-chosen, like application CPU/RAM/storage)
|
||||||
|
|
||||||
|
ALTER TABLE applications
|
||||||
|
ADD COLUMN IF NOT EXISTS optional_service_resources JSONB;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- Deploy-wizard default requests (TypeORM camelCase column names)
|
||||||
|
|
||||||
|
ALTER TABLE optional_service_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS "cpuRequest" VARCHAR,
|
||||||
|
ADD COLUMN IF NOT EXISTS "memoryRequest" VARCHAR;
|
||||||
|
|
||||||
|
UPDATE optional_service_profiles SET "cpuRequest" = '50m', "memoryRequest" = '64Mi'
|
||||||
|
WHERE service = 'redis' AND "cpuRequest" IS NULL;
|
||||||
|
|
||||||
|
UPDATE optional_service_profiles SET "cpuRequest" = '100m', "memoryRequest" = '256Mi'
|
||||||
|
WHERE service = 'rabbitmq' AND "cpuRequest" IS NULL;
|
||||||
|
|
||||||
|
UPDATE optional_service_profiles SET "cpuRequest" = '50m', "memoryRequest" = '64Mi'
|
||||||
|
WHERE service = 'elasticsearch' AND "cpuRequest" IS NULL;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS invoices (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
"invoiceNumber" VARCHAR NOT NULL UNIQUE,
|
||||||
|
"userId" UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
"applicationId" UUID REFERENCES applications(id) ON DELETE SET NULL,
|
||||||
|
reason VARCHAR NOT NULL DEFAULT 'manual',
|
||||||
|
status VARCHAR NOT NULL DEFAULT 'issued',
|
||||||
|
"paymentMethod" VARCHAR,
|
||||||
|
subtotal DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||||
|
total DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||||
|
"paidAmount" DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||||
|
"dueAmount" DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||||
|
"dueDate" TIMESTAMPTZ,
|
||||||
|
"paidAt" TIMESTAMPTZ,
|
||||||
|
"gatewayTrackingCode" VARCHAR,
|
||||||
|
"gatewayReference" VARCHAR,
|
||||||
|
"adminNote" VARCHAR,
|
||||||
|
"statusReason" VARCHAR,
|
||||||
|
metadata JSONB,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS invoice_lines (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
"invoiceId" UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
|
||||||
|
label VARCHAR NOT NULL,
|
||||||
|
description VARCHAR,
|
||||||
|
quantity INT NOT NULL DEFAULT 1,
|
||||||
|
"unitAmount" DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||||
|
amount DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||||
|
metadata JSONB,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE wallet_transactions
|
||||||
|
ADD COLUMN IF NOT EXISTS "invoiceId" UUID REFERENCES invoices(id) ON DELETE SET NULL;
|
||||||
|
|
||||||
|
ALTER TABLE wallet_transactions
|
||||||
|
ADD COLUMN IF NOT EXISTS "gatewayTrackingCode" VARCHAR;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invoices_user_status_created
|
||||||
|
ON invoices ("userId", status, "createdAt" DESC);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invoices_application
|
||||||
|
ON invoices ("applicationId");
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invoice_lines_invoice
|
||||||
|
ON invoice_lines ("invoiceId");
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_wallet_transactions_invoice
|
||||||
|
ON wallet_transactions ("invoiceId");
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_type
|
||||||
|
WHERE typname = 'deployments_status_enum'
|
||||||
|
) THEN
|
||||||
|
ALTER TYPE deployments_status_enum ADD VALUE IF NOT EXISTS 'cancelled';
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
-- Cluster pool allocation, health snapshots, and audit logs.
|
||||||
|
|
||||||
|
ALTER TABLE clusters
|
||||||
|
ADD COLUMN IF NOT EXISTS weight INTEGER NOT NULL DEFAULT 1,
|
||||||
|
ADD COLUMN IF NOT EXISTS tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
ADD COLUMN IF NOT EXISTS "healthStatus" VARCHAR NOT NULL DEFAULT 'unknown',
|
||||||
|
ADD COLUMN IF NOT EXISTS "lastHealthCheckedAt" TIMESTAMPTZ,
|
||||||
|
ADD COLUMN IF NOT EXISTS "healthMessage" VARCHAR,
|
||||||
|
ADD COLUMN IF NOT EXISTS "availableResources" JSONB;
|
||||||
|
|
||||||
|
ALTER TABLE cluster_pools
|
||||||
|
ADD COLUMN IF NOT EXISTS "isDefault" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 100;
|
||||||
|
|
||||||
|
ALTER TABLE cluster_pools
|
||||||
|
ALTER COLUMN strategy SET DEFAULT 'weighted-resource';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_clusters_status_health
|
||||||
|
ON clusters(status, "healthStatus");
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_clusters_tags
|
||||||
|
ON clusters USING GIN(tags);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cluster_pools_default_priority
|
||||||
|
ON cluster_pools("isDefault", priority)
|
||||||
|
WHERE "isActive" = TRUE;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS cluster_health (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
"clusterId" UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
||||||
|
status VARCHAR NOT NULL DEFAULT 'unknown',
|
||||||
|
"readyNodes" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"nodeCount" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"cpuAllocatable" VARCHAR,
|
||||||
|
"memoryAllocatable" VARCHAR,
|
||||||
|
"podCount" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"appCount" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
message VARCHAR,
|
||||||
|
resources JSONB,
|
||||||
|
"checkedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cluster_health_cluster_checked
|
||||||
|
ON cluster_health("clusterId", "checkedAt" DESC);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS cluster_allocation_logs (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
"applicationId" UUID REFERENCES applications(id) ON DELETE SET NULL,
|
||||||
|
"userId" UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
"poolId" UUID REFERENCES cluster_pools(id) ON DELETE SET NULL,
|
||||||
|
"selectedClusterId" UUID REFERENCES clusters(id) ON DELETE SET NULL,
|
||||||
|
strategy VARCHAR NOT NULL DEFAULT 'weighted-resource',
|
||||||
|
"estimatedRequest" JSONB,
|
||||||
|
"candidateScores" JSONB,
|
||||||
|
"rejectionReasons" JSONB,
|
||||||
|
status VARCHAR NOT NULL DEFAULT 'success',
|
||||||
|
message VARCHAR,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cluster_allocation_logs_user_created
|
||||||
|
ON cluster_allocation_logs("userId", "createdAt" DESC);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cluster_allocation_logs_app
|
||||||
|
ON cluster_allocation_logs("applicationId");
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cluster_allocation_logs_cluster_created
|
||||||
|
ON cluster_allocation_logs("selectedClusterId", "createdAt" DESC);
|
||||||
|
|
||||||
|
ALTER TABLE applications
|
||||||
|
ADD COLUMN IF NOT EXISTS "clusterId" UUID,
|
||||||
|
ADD COLUMN IF NOT EXISTS "poolId" UUID;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_name = 'applications' AND column_name = 'clusterId' AND data_type <> 'uuid'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE applications
|
||||||
|
ALTER COLUMN "clusterId" TYPE UUID USING NULLIF("clusterId", '')::uuid;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_name = 'applications' AND column_name = 'poolId' AND data_type <> 'uuid'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE applications
|
||||||
|
ALTER COLUMN "poolId" TYPE UUID USING NULLIF("poolId", '')::uuid;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint WHERE conname = 'fk_applications_cluster'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE applications
|
||||||
|
ADD CONSTRAINT fk_applications_cluster FOREIGN KEY ("clusterId")
|
||||||
|
REFERENCES clusters(id) ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint WHERE conname = 'fk_applications_pool'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE applications
|
||||||
|
ADD CONSTRAINT fk_applications_pool FOREIGN KEY ("poolId")
|
||||||
|
REFERENCES cluster_pools(id) ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_applications_cluster_id ON applications("clusterId");
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_applications_pool_id ON applications("poolId");
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS application_migration_jobs (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
"applicationId" UUID NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
|
||||||
|
"requestedBy" UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
"sourceClusterId" UUID NOT NULL REFERENCES clusters(id) ON DELETE RESTRICT,
|
||||||
|
"targetClusterId" UUID NOT NULL REFERENCES clusters(id) ON DELETE RESTRICT,
|
||||||
|
status VARCHAR NOT NULL DEFAULT 'queued',
|
||||||
|
attempts INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"maxAttempts" INTEGER NOT NULL DEFAULT 3,
|
||||||
|
"currentStep" VARCHAR,
|
||||||
|
"errorMessage" VARCHAR,
|
||||||
|
metadata JSONB,
|
||||||
|
"startedAt" TIMESTAMPTZ,
|
||||||
|
"completedAt" TIMESTAMPTZ,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_application_migration_jobs_app_created
|
||||||
|
ON application_migration_jobs("applicationId", "createdAt" DESC);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_application_migration_jobs_status
|
||||||
|
ON application_migration_jobs(status);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS application_migration_events (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
"migrationId" UUID NOT NULL REFERENCES application_migration_jobs(id) ON DELETE CASCADE,
|
||||||
|
step VARCHAR NOT NULL,
|
||||||
|
level VARCHAR NOT NULL DEFAULT 'info',
|
||||||
|
message VARCHAR NOT NULL,
|
||||||
|
metadata JSONB,
|
||||||
|
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_application_migration_events_migration_created
|
||||||
|
ON application_migration_events("migrationId", "createdAt" ASC);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- Standalone managed services (database / redis / rabbitmq) vs full applications
|
||||||
|
ALTER TABLE applications
|
||||||
|
ADD COLUMN IF NOT EXISTS product_type VARCHAR(32) NOT NULL DEFAULT 'application';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_applications_user_product_type
|
||||||
|
ON applications (user_id, product_type);
|
||||||
|
|
||||||
|
ALTER TABLE resource_credits
|
||||||
|
ADD COLUMN IF NOT EXISTS product_type VARCHAR(32) NOT NULL DEFAULT 'application';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE service_access_grants
|
||||||
|
ADD COLUMN IF NOT EXISTS persistent BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Snapshot creation progress (0–100) for UI feedback during DB dumps
|
||||||
|
ALTER TABLE snapshots ADD COLUMN IF NOT EXISTS progress INT NOT NULL DEFAULT 0;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
CloudHost platform has been deployed.
|
||||||
|
|
||||||
|
Release: {{ .Release.Name }}
|
||||||
|
Namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
|
||||||
|
Public URLs (configure DNS to point at your Ingress controller):
|
||||||
|
Frontend: {{ include "cloudhost-platform.frontendPublicUrl" . }}
|
||||||
|
API: {{ include "cloudhost-platform.apiPublicUrl" . }}
|
||||||
|
|
||||||
|
Build the frontend image with:
|
||||||
|
docker build -t <registry>/cloudhost-frontend:<tag> \
|
||||||
|
--build-arg NEXT_PUBLIC_API_URL={{ include "cloudhost-platform.apiPublicUrl" . }} \
|
||||||
|
./frontend
|
||||||
|
|
||||||
|
{{- if .Values.ingress.tls.enabled }}
|
||||||
|
TLS: enabled (cert-manager issuer: {{ .Values.ingress.tls.clusterIssuer }})
|
||||||
|
Ensure cert-manager and ClusterIssuer "{{ .Values.ingress.tls.clusterIssuer }}" exist.
|
||||||
|
{{- else }}
|
||||||
|
TLS: disabled (HTTP only)
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- else }}
|
||||||
|
|
||||||
|
Ingress is disabled. Port-forward to access services:
|
||||||
|
kubectl port-forward -n {{ include "cloudhost-platform.namespace" . }} svc/{{ include "cloudhost-platform.frontend.fullname" . }} 3000:3000
|
||||||
|
kubectl port-forward -n {{ include "cloudhost-platform.namespace" . }} svc/{{ include "cloudhost-platform.backend.fullname" . }} 4000:4000
|
||||||
|
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
Backend CORS (FRONTEND_URL): {{ include "cloudhost-platform.frontendPublicUrl" . }}
|
||||||
|
|
||||||
|
After install, register your Kubernetes cluster in the admin panel (Clusters) so deployments can run.
|
||||||
|
|
||||||
|
To retrieve auto-generated secrets:
|
||||||
|
kubectl get secret -n {{ include "cloudhost-platform.namespace" . }} {{ include "cloudhost-platform.secretName" . }} -o jsonpath='{.data.postgres-password}' | base64 -d; echo
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
{{- define "cloudhost-platform.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.namespace" -}}
|
||||||
|
{{- .Values.namespace | default "cloudhost" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "cloudhost-platform.chart" . }}
|
||||||
|
{{ include "cloudhost-platform.selectorLabels" . }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "cloudhost-platform.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.postgres.fullname" -}}
|
||||||
|
{{- printf "%s-postgres" (include "cloudhost-platform.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.redis.fullname" -}}
|
||||||
|
{{- printf "%s-redis" (include "cloudhost-platform.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.backend.fullname" -}}
|
||||||
|
{{- printf "%s-backend" (include "cloudhost-platform.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.frontend.fullname" -}}
|
||||||
|
{{- printf "%s-frontend" (include "cloudhost-platform.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.secretName" -}}
|
||||||
|
{{- printf "%s-secrets" (include "cloudhost-platform.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.tlsSecretName" -}}
|
||||||
|
{{- if .Values.ingress.tls.secretName }}
|
||||||
|
{{- .Values.ingress.tls.secretName }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-platform-tls" (include "cloudhost-platform.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.urlScheme" -}}
|
||||||
|
{{- if and .Values.ingress.enabled .Values.ingress.tls.enabled }}https{{- else }}http{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.frontendPublicUrl" -}}
|
||||||
|
{{- $scheme := include "cloudhost-platform.urlScheme" . -}}
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- if .Values.ingress.singleHost.enabled }}
|
||||||
|
{{- printf "%s://%s" $scheme .Values.ingress.singleHost.host }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s://%s" $scheme .Values.ingress.frontend.host }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "http://%s-frontend.%s.svc.cluster.local:3000" (include "cloudhost-platform.fullname" .) (include "cloudhost-platform.namespace" .) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.apiPublicUrl" -}}
|
||||||
|
{{- $scheme := include "cloudhost-platform.urlScheme" . -}}
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- if .Values.ingress.singleHost.enabled }}
|
||||||
|
{{- printf "%s://%s%s" $scheme .Values.ingress.singleHost.host .Values.ingress.singleHost.apiPath }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s://%s" $scheme .Values.ingress.api.host }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "http://%s-backend.%s.svc.cluster.local:4000" (include "cloudhost-platform.fullname" .) (include "cloudhost-platform.namespace" .) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.backendImage" -}}
|
||||||
|
{{- printf "%s:%s" .Values.images.backend.repository .Values.images.backend.tag }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "cloudhost-platform.frontendImage" -}}
|
||||||
|
{{- printf "%s:%s" .Values.images.frontend.repository .Values.images.frontend.tag }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
{{- if .Values.backend.enabled }}
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.backend.replicas }}
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
spec:
|
||||||
|
initContainers:
|
||||||
|
{{- if .Values.postgres.enabled }}
|
||||||
|
- name: wait-postgres
|
||||||
|
image: {{ .Values.images.busybox | quote }}
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
until nc -z {{ include "cloudhost-platform.postgres.fullname" . }} 5432; do
|
||||||
|
echo "waiting for postgres..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.redis.enabled }}
|
||||||
|
- name: wait-redis
|
||||||
|
image: {{ .Values.images.busybox | quote }}
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
until nc -z {{ include "cloudhost-platform.redis.fullname" . }} 6379; do
|
||||||
|
echo "waiting for redis..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
{{- end }}
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
image: {{ include "cloudhost-platform.backendImage" . | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.images.backend.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- containerPort: 4000
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "5432"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: {{ .Values.postgres.username | quote }}
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "cloudhost-platform.secretName" . }}
|
||||||
|
key: postgres-password
|
||||||
|
- name: DB_DATABASE
|
||||||
|
value: {{ .Values.postgres.database | quote }}
|
||||||
|
- name: REDIS_HOST
|
||||||
|
value: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
- name: REDIS_PORT
|
||||||
|
value: "6379"
|
||||||
|
- name: JWT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "cloudhost-platform.secretName" . }}
|
||||||
|
key: jwt-secret
|
||||||
|
- name: JWT_REFRESH_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "cloudhost-platform.secretName" . }}
|
||||||
|
key: jwt-refresh-secret
|
||||||
|
- name: FRONTEND_URL
|
||||||
|
value: {{ include "cloudhost-platform.frontendPublicUrl" . | quote }}
|
||||||
|
{{- range $key, $val := .Values.backend.env }}
|
||||||
|
- name: {{ $key }}
|
||||||
|
value: {{ $val | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- range $key, $val := .Values.backend.extraEnv }}
|
||||||
|
- name: {{ $key }}
|
||||||
|
value: {{ $val | quote }}
|
||||||
|
{{- end }}
|
||||||
|
volumeMounts:
|
||||||
|
- name: uploads
|
||||||
|
mountPath: /app/uploads
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/docs
|
||||||
|
port: 4000
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 15
|
||||||
|
timeoutSeconds: 5
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/docs
|
||||||
|
port: 4000
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
{{- with .Values.backend.resources }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
volumes:
|
||||||
|
- name: uploads
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ include "cloudhost-platform.backend.fullname" . }}-uploads
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{{- if .Values.backend.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.backend.fullname" . }}-uploads
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
{{- if .Values.global.storageClass }}
|
||||||
|
storageClassName: {{ .Values.global.storageClass | quote }}
|
||||||
|
{{- end }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.backend.uploads.size | quote }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{{- if .Values.backend.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 4000
|
||||||
|
targetPort: 4000
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{{- if .Values.frontend.enabled }}
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.frontend.replicas }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: {{ include "cloudhost-platform.frontendImage" . | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.images.frontend.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
env:
|
||||||
|
- name: PORT
|
||||||
|
value: "3000"
|
||||||
|
- name: HOSTNAME
|
||||||
|
value: "0.0.0.0"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 15
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
{{- with .Values.frontend.resources }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{{- if .Values.frontend.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
{{- if .Values.ingress.tls.enabled }}
|
||||||
|
cert-manager.io/cluster-issuer: {{ .Values.ingress.tls.clusterIssuer | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if and .Values.ingress.singleHost.enabled .Values.ingress.singleHost.apiPath }}
|
||||||
|
nginx.ingress.kubernetes.io/use-regex: "true"
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
|
{{- if .Values.ingress.tls.enabled }}
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
{{- if .Values.ingress.singleHost.enabled }}
|
||||||
|
- {{ .Values.ingress.singleHost.host | quote }}
|
||||||
|
{{- else }}
|
||||||
|
- {{ .Values.ingress.frontend.host | quote }}
|
||||||
|
- {{ .Values.ingress.api.host | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ include "cloudhost-platform.tlsSecretName" . }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- if .Values.ingress.singleHost.enabled }}
|
||||||
|
- host: {{ .Values.ingress.singleHost.host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: {{ .Values.ingress.singleHost.apiPath }}
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
port:
|
||||||
|
number: 4000
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
port:
|
||||||
|
number: 3000
|
||||||
|
{{- else }}
|
||||||
|
- host: {{ .Values.ingress.frontend.host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ include "cloudhost-platform.frontend.fullname" . }}
|
||||||
|
port:
|
||||||
|
number: 3000
|
||||||
|
- host: {{ .Values.ingress.api.host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ include "cloudhost-platform.backend.fullname" . }}
|
||||||
|
port:
|
||||||
|
number: 4000
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{{- if and .Values.migrations.enabled .Values.postgres.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.fullname" . }}-migrations
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
{{- (.Files.Glob "migrations/*.sql").AsConfig | nindent 2 }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{{- if and .Values.migrations.enabled .Values.postgres.enabled }}
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.fullname" . }}-migrations
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
helm.sh/hook: post-install,post-upgrade
|
||||||
|
helm.sh/hook-weight: "5"
|
||||||
|
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
|
||||||
|
spec:
|
||||||
|
backoffLimit: 3
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
initContainers:
|
||||||
|
- name: wait-postgres
|
||||||
|
image: {{ .Values.images.busybox | quote }}
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
until nc -z {{ include "cloudhost-platform.postgres.fullname" . }} 5432; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
containers:
|
||||||
|
- name: migrate
|
||||||
|
image: {{ .Values.migrations.image | quote }}
|
||||||
|
env:
|
||||||
|
- name: PGHOST
|
||||||
|
value: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
- name: PGPORT
|
||||||
|
value: "5432"
|
||||||
|
- name: PGDATABASE
|
||||||
|
value: {{ .Values.postgres.database | quote }}
|
||||||
|
- name: PGUSER
|
||||||
|
value: {{ .Values.postgres.username | quote }}
|
||||||
|
- name: PGPASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "cloudhost-platform.secretName" . }}
|
||||||
|
key: postgres-password
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
for f in $(ls /migrations/*.sql | sort); do
|
||||||
|
echo ">>> Applying $f"
|
||||||
|
psql -v ON_ERROR_STOP=1 -f "$f"
|
||||||
|
done
|
||||||
|
echo ">>> All migrations applied"
|
||||||
|
volumeMounts:
|
||||||
|
- name: migrations
|
||||||
|
mountPath: /migrations
|
||||||
|
volumes:
|
||||||
|
- name: migrations
|
||||||
|
configMap:
|
||||||
|
name: {{ include "cloudhost-platform.fullname" . }}-migrations
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{{- if .Values.createNamespace }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{{- if .Values.postgres.enabled }}
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: {{ .Values.images.postgres | quote }}
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: {{ .Values.postgres.database | quote }}
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: {{ .Values.postgres.username | quote }}
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "cloudhost-platform.secretName" . }}
|
||||||
|
key: postgres-password
|
||||||
|
- name: PGDATA
|
||||||
|
value: /var/lib/postgresql/data/pgdata
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- pg_isready
|
||||||
|
- -U
|
||||||
|
- {{ .Values.postgres.username }}
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- pg_isready
|
||||||
|
- -U
|
||||||
|
- {{ .Values.postgres.username }}
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
{{- with .Values.postgres.resources }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ include "cloudhost-platform.postgres.fullname" . }}-data
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{{- if .Values.postgres.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.postgres.fullname" . }}-data
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
{{- if .Values.global.storageClass }}
|
||||||
|
storageClassName: {{ .Values.global.storageClass | quote }}
|
||||||
|
{{- end }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.postgres.storage | quote }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{{- if .Values.postgres.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: {{ include "cloudhost-platform.postgres.fullname" . }}
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{{- if .Values.redis.enabled }}
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: redis
|
||||||
|
image: {{ .Values.images.redis | quote }}
|
||||||
|
ports:
|
||||||
|
- containerPort: 6379
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- redis-cli
|
||||||
|
- ping
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- redis-cli
|
||||||
|
- ping
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
{{- with .Values.redis.resources }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ include "cloudhost-platform.redis.fullname" . }}-data
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{{- if .Values.redis.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.redis.fullname" . }}-data
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
{{- if .Values.global.storageClass }}
|
||||||
|
storageClassName: {{ .Values.global.storageClass | quote }}
|
||||||
|
{{- end }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.redis.storage | quote }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{{- if .Values.redis.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: {{ include "cloudhost-platform.redis.fullname" . }}
|
||||||
|
ports:
|
||||||
|
- name: redis
|
||||||
|
port: 6379
|
||||||
|
targetPort: 6379
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{{- $existing := lookup "v1" "Secret" (include "cloudhost-platform.namespace" .) (include "cloudhost-platform.secretName" .) }}
|
||||||
|
{{- $pgPass := .Values.postgres.password }}
|
||||||
|
{{- if not $pgPass }}
|
||||||
|
{{- if $existing }}{{- $pgPass = index $existing.data "postgres-password" | b64dec }}{{- else }}{{- $pgPass = randAlphaNum 24 }}{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $jwt := .Values.secrets.jwtSecret }}
|
||||||
|
{{- if not $jwt }}
|
||||||
|
{{- if $existing }}{{- $jwt = index $existing.data "jwt-secret" | b64dec }}{{- else }}{{- $jwt = randAlphaNum 32 }}{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $jwtRefresh := .Values.secrets.jwtRefreshSecret }}
|
||||||
|
{{- if not $jwtRefresh }}
|
||||||
|
{{- if $existing }}{{- $jwtRefresh = index $existing.data "jwt-refresh-secret" | b64dec }}{{- else }}{{- $jwtRefresh = randAlphaNum 32 }}{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cloudhost-platform.secretName" . }}
|
||||||
|
namespace: {{ include "cloudhost-platform.namespace" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "cloudhost-platform.labels" . | nindent 4 }}
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
postgres-password: {{ $pgPass | quote }}
|
||||||
|
jwt-secret: {{ $jwt | quote }}
|
||||||
|
jwt-refresh-secret: {{ $jwtRefresh | quote }}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated after local docker build — use with:
|
||||||
|
# helm upgrade --install cloudhost . -n cloudhost -f values.yaml -f values-images.yaml
|
||||||
|
#
|
||||||
|
# Built with:
|
||||||
|
# docker build -t cloudhost-backend:1.0.0 ./backend
|
||||||
|
# docker build -t cloudhost-frontend:1.0.0 \
|
||||||
|
# --build-arg NEXT_PUBLIC_API_URL=https://api.cloudhost.local ./frontend
|
||||||
|
|
||||||
|
images:
|
||||||
|
backend:
|
||||||
|
repository: cloudhost-backend
|
||||||
|
tag: "1.0.0"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
frontend:
|
||||||
|
repository: cloudhost-frontend
|
||||||
|
tag: "1.0.0"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Example production overrides for cloudhost-platform
|
||||||
|
# cp values-production.example.yaml values-production.yaml && edit secrets/hosts
|
||||||
|
|
||||||
|
namespace: cloudhost
|
||||||
|
createNamespace: true
|
||||||
|
|
||||||
|
global:
|
||||||
|
storageClass: local-path # k3s example
|
||||||
|
|
||||||
|
images:
|
||||||
|
backend:
|
||||||
|
repository: registry.example.com/cloudhost-backend
|
||||||
|
tag: "1.0.0"
|
||||||
|
pullPolicy: Always
|
||||||
|
frontend:
|
||||||
|
repository: registry.example.com/cloudhost-frontend
|
||||||
|
tag: "1.0.0"
|
||||||
|
pullPolicy: Always
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
password: "CHANGE_ME_STRONG_POSTGRES_PASSWORD"
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
jwtSecret: "CHANGE_ME_LONG_JWT_SECRET"
|
||||||
|
jwtRefreshSecret: "CHANGE_ME_LONG_REFRESH_SECRET"
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
frontend:
|
||||||
|
host: platform.example.com
|
||||||
|
api:
|
||||||
|
host: api.platform.example.com
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
clusterIssuer: letsencrypt-prod
|
||||||
|
|
||||||
|
backend:
|
||||||
|
env:
|
||||||
|
PLATFORM_DOMAIN: apps.example.com
|
||||||
|
REGISTRY_PULL_URL: "10.0.0.50:30500"
|
||||||
|
|
||||||
|
migrations:
|
||||||
|
enabled: true
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
# CloudHost Platform — Helm values
|
||||||
|
# Deploy: helm upgrade --install cloudhost ./backend/helm/cloudhost-platform -n cloudhost --create-namespace
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
namespace: cloudhost
|
||||||
|
createNamespace: true
|
||||||
|
|
||||||
|
global:
|
||||||
|
storageClass: ""
|
||||||
|
|
||||||
|
images:
|
||||||
|
postgres: postgres:16-alpine
|
||||||
|
redis: redis:7-alpine
|
||||||
|
busybox: busybox:1.36
|
||||||
|
backend:
|
||||||
|
repository: cloudhost-backend
|
||||||
|
tag: "1.0.0"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
frontend:
|
||||||
|
repository: cloudhost-frontend
|
||||||
|
tag: "1.0.0"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
enabled: true
|
||||||
|
database: cloudhost
|
||||||
|
username: cloudhost
|
||||||
|
# Leave empty to auto-generate on first install (stored in Secret)
|
||||||
|
password: ""
|
||||||
|
storage: 10Gi
|
||||||
|
resources: {}
|
||||||
|
|
||||||
|
redis:
|
||||||
|
enabled: true
|
||||||
|
storage: 1Gi
|
||||||
|
resources: {}
|
||||||
|
|
||||||
|
backend:
|
||||||
|
enabled: true
|
||||||
|
replicas: 1
|
||||||
|
uploads:
|
||||||
|
size: 20Gi
|
||||||
|
resources: {}
|
||||||
|
extraEnv: {}
|
||||||
|
env:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: "4000"
|
||||||
|
JWT_EXPIRES_IN: 15m
|
||||||
|
JWT_REFRESH_EXPIRES_IN: 7d
|
||||||
|
PLATFORM_DOMAIN: apps.cloudhost.local
|
||||||
|
REGISTRY_URL: registry.cloudhost-builds.svc.cluster.local:5000
|
||||||
|
REGISTRY_PULL_URL: localhost:30500
|
||||||
|
BUILD_NAMESPACE: cloudhost-builds
|
||||||
|
BUILD_SERVICE_ACCOUNT: kaniko-builder
|
||||||
|
UPLOAD_DIR: /app/uploads
|
||||||
|
PLATFORM_CREATE_STORAGE_CLASS: "true"
|
||||||
|
PLATFORM_STORAGE_CLASS: cloudhost-expandable
|
||||||
|
PLATFORM_STORAGE_PROVISIONER: rancher.io/local-path
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
enabled: true
|
||||||
|
replicas: 1
|
||||||
|
resources: {}
|
||||||
|
|
||||||
|
# JWT secrets — set in production (values-production.example.yaml)
|
||||||
|
secrets:
|
||||||
|
jwtSecret: ""
|
||||||
|
jwtRefreshSecret: ""
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
frontend:
|
||||||
|
host: platform.cloudhost.local
|
||||||
|
api:
|
||||||
|
host: api.cloudhost.local
|
||||||
|
singleHost:
|
||||||
|
enabled: false
|
||||||
|
host: platform.cloudhost.local
|
||||||
|
apiPath: /api
|
||||||
|
annotations: {}
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
clusterIssuer: letsencrypt-prod
|
||||||
|
secretName: ""
|
||||||
|
|
||||||
|
migrations:
|
||||||
|
enabled: true
|
||||||
|
image: postgres:16-alpine
|
||||||
@@ -12,6 +12,8 @@ WORKDIR /app
|
|||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
ARG NEXT_PUBLIC_API_URL=http://localhost:4000
|
||||||
|
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
email: admin@3fase.ir
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-prod-account-key
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: traefik
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: registry-public
|
||||||
|
namespace: cloudhost-builds
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
traefik.ingress.kubernetes.io/service.serverstransport: cloudhost-builds-registry-transport@kubernetescrd
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- repo.3fase.ir
|
||||||
|
secretName: repo-3fase-ir-tls
|
||||||
|
rules:
|
||||||
|
- host: repo.3fase.ir
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: registry
|
||||||
|
port:
|
||||||
|
number: 5000
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Patch registry Deployment for reverse-proxy (Traefik + HTTPS)
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: registry
|
||||||
|
namespace: cloudhost-builds
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: registry
|
||||||
|
env:
|
||||||
|
- name: REGISTRY_STORAGE_DELETE_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: REGISTRY_HTTP_RELATIVEURLS
|
||||||
|
value: "true"
|
||||||
|
- name: REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin
|
||||||
|
value: '["*"]'
|
||||||
|
- name: REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods
|
||||||
|
value: '["HEAD","GET","OPTIONS","DELETE"]'
|
||||||
|
- name: REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers
|
||||||
|
value: '["Authorization","Accept","Cache-Control"]'
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: ServersTransport
|
||||||
|
metadata:
|
||||||
|
name: registry-transport
|
||||||
|
namespace: cloudhost-builds
|
||||||
|
spec:
|
||||||
|
forwardingTimeouts:
|
||||||
|
dialTimeout: 30s
|
||||||
|
responseHeaderTimeout: 600s
|
||||||
|
idleConnTimeout: 600s
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: registry-buffering
|
||||||
|
namespace: cloudhost-builds
|
||||||
|
spec:
|
||||||
|
buffering:
|
||||||
|
maxRequestBodyBytes: 0
|
||||||
|
memRequestBodyBytes: 20971520
|
||||||
|
maxResponseBodyBytes: 0
|
||||||
|
memResponseBodyBytes: 20971520
|
||||||
|
retryExpression: "IsNetworkError() && Attempts() < 2"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Merge with existing traefik helm values (helm upgrade traefik -f traefik-timeout-values.yaml)
|
||||||
|
ports:
|
||||||
|
web:
|
||||||
|
transport:
|
||||||
|
respondingTimeouts:
|
||||||
|
readTimeout: "0s"
|
||||||
|
writeTimeout: "0s"
|
||||||
|
idleTimeout: "1800s"
|
||||||
|
websecure:
|
||||||
|
transport:
|
||||||
|
respondingTimeouts:
|
||||||
|
readTimeout: "0s"
|
||||||
|
writeTimeout: "0s"
|
||||||
|
idleTimeout: "1800s"
|
||||||
Reference in New Issue
Block a user