From bd026a4fd8129b8847581881b2bd5bad33c25e90 Mon Sep 17 00:00:00 2001 From: glebmark <87950237+glebmark@users.noreply.github.com> Date: Wed, 24 Dec 2025 11:19:01 +0400 Subject: [PATCH] helm charts for smartbox --- smartbox/Chart.yaml | 7 + smartbox/README.md | 583 ++++-------------- smartbox/README.md.gotmpl | 123 ++++ smartbox/aidbox/portal/aidbox.yaml | 148 ----- smartbox/aidbox/portal/init-bundle.json | 366 ----------- smartbox/aidbox/portal/kustomization.yaml | 17 - smartbox/aidbox/portal/namespace.yaml | 9 - smartbox/aidbox/sandbox/aidbox.yaml | 147 ----- smartbox/aidbox/sandbox/init-bundle.json | 577 ----------------- smartbox/aidbox/sandbox/kustomization.yaml | 17 - smartbox/aidbox/sandbox/namespace.yaml | 9 - smartbox/database/configmap.yaml | 30 - smartbox/database/kustomization.yaml | 12 - smartbox/database/namespace.yaml | 8 - smartbox/database/pvc.yaml | 14 - smartbox/database/service.yaml | 12 - smartbox/database/statefulset.yaml | 62 -- smartbox/fhir-app-portal/configmap.yaml | 58 -- smartbox/fhir-app-portal/deployment.yaml | 104 ---- smartbox/fhir-app-portal/ingress.yaml | 100 --- smartbox/fhir-app-portal/kustomization.yaml | 12 - smartbox/fhir-app-portal/namespace.yaml | 8 - smartbox/fhir-app-portal/service.yaml | 32 - smartbox/secrets/secrets-template.env | 41 -- smartbox/templates/NOTES.txt | 82 +++ smartbox/templates/_helpers.tpl | 85 +++ .../templates/aidbox-portal-configmap.yaml | 287 +++++++++ .../templates/aidbox-sandbox-configmap.yaml | 318 ++++++++++ smartbox/templates/configmap.yaml | 37 ++ smartbox/templates/database-configmap.yaml | 24 + smartbox/templates/database-pvc.yaml | 19 + smartbox/templates/database-secret.yaml | 14 + smartbox/templates/database-service.yaml | 17 + smartbox/templates/database-statefulset.yaml | 69 +++ smartbox/templates/deployment.yaml | 95 +++ smartbox/templates/hpa.yaml | 33 + smartbox/templates/ingress.yaml | 47 ++ smartbox/templates/secrets.yaml | 15 + smartbox/templates/service.yaml | 26 + smartbox/templates/serviceaccount.yaml | 14 + smartbox/values.yaml | 336 ++++++++++ 41 files changed, 1763 insertions(+), 2251 deletions(-) create mode 100644 smartbox/Chart.yaml create mode 100644 smartbox/README.md.gotmpl delete mode 100644 smartbox/aidbox/portal/aidbox.yaml delete mode 100644 smartbox/aidbox/portal/init-bundle.json delete mode 100644 smartbox/aidbox/portal/kustomization.yaml delete mode 100644 smartbox/aidbox/portal/namespace.yaml delete mode 100644 smartbox/aidbox/sandbox/aidbox.yaml delete mode 100644 smartbox/aidbox/sandbox/init-bundle.json delete mode 100644 smartbox/aidbox/sandbox/kustomization.yaml delete mode 100644 smartbox/aidbox/sandbox/namespace.yaml delete mode 100644 smartbox/database/configmap.yaml delete mode 100644 smartbox/database/kustomization.yaml delete mode 100644 smartbox/database/namespace.yaml delete mode 100644 smartbox/database/pvc.yaml delete mode 100644 smartbox/database/service.yaml delete mode 100644 smartbox/database/statefulset.yaml delete mode 100644 smartbox/fhir-app-portal/configmap.yaml delete mode 100644 smartbox/fhir-app-portal/deployment.yaml delete mode 100644 smartbox/fhir-app-portal/ingress.yaml delete mode 100644 smartbox/fhir-app-portal/kustomization.yaml delete mode 100644 smartbox/fhir-app-portal/namespace.yaml delete mode 100644 smartbox/fhir-app-portal/service.yaml delete mode 100644 smartbox/secrets/secrets-template.env create mode 100644 smartbox/templates/NOTES.txt create mode 100644 smartbox/templates/_helpers.tpl create mode 100644 smartbox/templates/aidbox-portal-configmap.yaml create mode 100644 smartbox/templates/aidbox-sandbox-configmap.yaml create mode 100644 smartbox/templates/configmap.yaml create mode 100644 smartbox/templates/database-configmap.yaml create mode 100644 smartbox/templates/database-pvc.yaml create mode 100644 smartbox/templates/database-secret.yaml create mode 100644 smartbox/templates/database-service.yaml create mode 100644 smartbox/templates/database-statefulset.yaml create mode 100644 smartbox/templates/deployment.yaml create mode 100644 smartbox/templates/hpa.yaml create mode 100644 smartbox/templates/ingress.yaml create mode 100644 smartbox/templates/secrets.yaml create mode 100644 smartbox/templates/service.yaml create mode 100644 smartbox/templates/serviceaccount.yaml create mode 100644 smartbox/values.yaml diff --git a/smartbox/Chart.yaml b/smartbox/Chart.yaml new file mode 100644 index 0000000..8856463 --- /dev/null +++ b/smartbox/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +appVersion: "1.0.0" +name: smartbox +description: FHIR App Portal solution with Aidbox backend by Health Samurai +type: application +version: 0.1.0 + diff --git a/smartbox/README.md b/smartbox/README.md index 7bb58d8..87cd5d8 100644 --- a/smartbox/README.md +++ b/smartbox/README.md @@ -1,517 +1,164 @@ -# FHIR App Portal - Kubernetes Deployment Guide +# Smartbox -This guide will help you deploy the FHIR App Portal solution to your Kubernetes cluster using Kustomize. +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0](https://img.shields.io/badge/AppVersion-1.0.0-informational?style=flat-square) -## Table of Contents +## Description -- [Overview](#overview) -- [Prerequisites](#prerequisites) -- [Architecture](#architecture) -- [Configuration](#configuration) -- [Deployment](#deployment) -- [Verification](#verification) -- [Troubleshooting](#troubleshooting) +FHIR App Portal solution with Aidbox backend by Health Samurai ---- +This Helm chart deploys the FHIR App Portal solution which includes: -## Overview - -The solution consists of four components: - -| Component | Description | Namespace | -|-----------|-------------|-----------| -| **AidboxDB** | PostgreSQL database for Aidbox | `aidbox-db` | -| **Aidbox Portal** | FHIR server for admin portal (production data) | `aidbox-portal` | -| **Aidbox Sandbox** | FHIR server for developer portal (test data) | `aidbox-sandbox` | -| **FHIR App Portal** | Web application serving both portals | `fhir-app-portal` | - ---- +- **FHIR App Portal**: Web application serving Admin and Developer portals +- **AidboxDB**: PostgreSQL database for Aidbox instances +- **Init Bundles**: ConfigMaps with initialization data for Aidbox Portal and Sandbox ## Prerequisites -### Required Infrastructure - - Kubernetes cluster v1.24+ -- NGINX Ingress Controller -- cert-manager with `letsencrypt` ClusterIssuer -- Flux CD (for HelmRelease resources) or Helm CLI +- Helm v3.x +- NGINX Ingress Controller (optional, for ingress) +- cert-manager with ClusterIssuer (optional, for TLS) - Aidbox license key (contact https://aidbox.app) -- Storage class for PersistentVolumeClaim (default or specify in `database/pvc.yaml`) - -### Required DNS Records - -Configure 4 DNS A/CNAME records pointing to your ingress: - -| Purpose | Example | -|---------|---------| -| Admin Aidbox | `aidbox.yourdomain.com` | -| Sandbox Aidbox | `aidbox-sandbox.yourdomain.com` | -| Admin Portal | `portal.yourdomain.com` | -| Developer Portal | `portal-sandbox.yourdomain.com` | - ---- - -## Architecture - -``` - ┌─────────────────────────────┐ - │ NGINX Ingress │ - └──────────┬──────────────────┘ - │ - ┌────────────────────────────┼────────────────────────────┐ - │ │ │ - ▼ ▼ ▼ -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Aidbox Portal │ │ Aidbox Sandbox │ │ FHIR App Portal │ -│ (aidbox-portal) │◄──────►│(aidbox-sandbox) │◄──────►│(fhir-app-portal)│ -└────────┬────────┘ └────────┬────────┘ └─────────────────┘ - │ │ - └──────────┬───────────────┘ - ▼ - ┌─────────────────┐ - │ AidboxDB │ - │ (aidbox-db) │ - └─────────────────┘ -``` - ---- - -## Configuration - -### Step 1: Update Domain Names -Replace `yourdomain.com` with your actual domain in the following files: +## Installation -#### Aidbox Portal (`aidbox/portal/`) +### 1. Prepare configuration -**aidbox.yaml** - Update host: -```yaml -host: aidbox.yourdomain.com -``` - -**init-bundle.json** - Update OAuth redirect URI: -```json -"redirect_uri": "https://portal.yourdomain.com/api/admin/auth/callback" -``` - -#### Aidbox Sandbox (`aidbox/sandbox/`) +Create a values file with your configuration: -**aidbox.yaml** - Update host: ```yaml -host: aidbox-sandbox.yourdomain.com -``` - -**init-bundle.json** - Update OAuth redirect URI: -```json -"redirect_uri": "https://portal-sandbox.yourdomain.com/api/developer/auth/callback" -``` - -**init-bundle.json** - Update TokenIntrospector (for cross-instance auth): -```json -"jwt": { - "iss": "https://aidbox.yourdomain.com" -}, -"jwks_uri": "https://aidbox.yourdomain.com/.well-known/jwks.json" -``` +# values-production.yaml +domain: + baseDomain: "example.com" + adminPortal: "portal.example.com" + developerPortal: "portal-sandbox.example.com" + adminAidbox: "aidbox.example.com" + sandboxAidbox: "aidbox-sandbox.example.com" -**init-bundle.json** - Update AccessPolicy issuer: -```json -"iss": { - "constant": "https://aidbox.yourdomain.com" -} -``` - -#### FHIR App Portal (`fhir-app-portal/`) - -**configmap.yaml** - Update all URLs: -```yaml -CORS_ORIGINS: "https://portal.yourdomain.com,https://portal-sandbox.yourdomain.com" -ADMIN_FRONTEND_URL: "https://portal.yourdomain.com" -DEVELOPER_FRONTEND_URL: "https://portal-sandbox.yourdomain.com" -AIDBOX_ADMIN_PUBLIC_URL: "https://aidbox.yourdomain.com" -AIDBOX_DEV_PUBLIC_URL: "https://aidbox-sandbox.yourdomain.com" -ADMIN_AIDBOX_PUBLIC_URL: "https://aidbox.yourdomain.com" -DEVELOPER_AIDBOX_PUBLIC_URL: "https://aidbox-sandbox.yourdomain.com" -``` +portal: + secrets: + SESSION_SECRET: "" + ADMIN_API_CLIENT_SECRET: "" + DEVELOPER_API_CLIENT_SECRET: "" -**ingress.yaml** - Update hosts: -```yaml -tls: -- hosts: - - portal.yourdomain.com - - portal-sandbox.yourdomain.com -rules: -- host: portal.yourdomain.com -- host: portal-sandbox.yourdomain.com +database: + enabled: true + secrets: + POSTGRES_USER: "postgres" + POSTGRES_PASSWORD: "" ``` -### Step 2: Configure Database Storage (Optional) +### 2. Install the chart -If you need a specific storage class, update `database/pvc.yaml`: +```console +helm repo add healthsamurai https://healthsamurai.github.io/helm-charts -```yaml -spec: - storageClassName: your-storage-class - resources: - requests: - storage: 50Gi # Adjust as needed +helm upgrade --install smartbox healthsamurai/smartbox \ + --namespace smartbox --create-namespace \ + --values values-production.yaml ``` ---- - -## Deployment - -### Step 1: Deploy Database - -```bash -# Create database namespace and secret -kubectl create namespace aidbox-db - -kubectl create secret generic aidboxdb-secret \ - --namespace aidbox-db \ - --from-literal=POSTGRES_USER=postgres \ - --from-literal=POSTGRES_PASSWORD= +### 3. Deploy Aidbox instances -# Deploy database -kubectl apply -k database/ -``` - -Wait for the database to be ready: +After installing this chart, deploy Aidbox instances using the generated init bundle ConfigMaps: ```bash -kubectl wait --for=condition=ready pod -l service=aidboxdb -n aidbox-db --timeout=120s -``` - -### Step 2: Create Aidbox Secrets +# Add Aidbox Helm repo +helm repo add aidbox https://aidbox.github.io/helm-charts -```bash -# Aidbox Portal secrets +# Create Aidbox Portal namespace and secrets kubectl create namespace aidbox-portal - -kubectl create secret generic postgres-secrets \ - --namespace aidbox-portal \ - --from-literal=BOX_DB_USER=postgres \ - --from-literal=BOX_DB_PASSWORD= - -kubectl create secret generic aidbox-secrets \ - --namespace aidbox-portal \ - --from-literal=AIDBOX_LICENSE= \ - --from-literal=BOX_AUTH_KEYS_SECRET= \ - --from-literal=BOX_ADMIN_PASSWORD= -``` - -```bash -# Aidbox Sandbox secrets -kubectl create namespace aidbox-sandbox - -kubectl create secret generic postgres-secrets \ - --namespace aidbox-sandbox \ +kubectl create secret generic aidbox-secrets -n aidbox-portal \ + --from-literal=AIDBOX_LICENSE= \ + --from-literal=BOX_AUTH_KEYS_SECRET= \ + --from-literal=BOX_ADMIN_PASSWORD= +kubectl create secret generic postgres-secrets -n aidbox-portal \ --from-literal=BOX_DB_USER=postgres \ - --from-literal=BOX_DB_PASSWORD= - -kubectl create secret generic aidbox-secrets \ - --namespace aidbox-sandbox \ - --from-literal=AIDBOX_LICENSE= \ - --from-literal=BOX_AUTH_KEYS_SECRET= \ - --from-literal=BOX_ADMIN_PASSWORD= -``` - -### Step 3: Create FHIR App Portal Secrets - -```bash -kubectl create namespace fhir-app-portal + --from-literal=BOX_DB_PASSWORD= -kubectl create secret generic fhir-app-portal--session-secret \ - --namespace fhir-app-portal \ - --from-literal=SESSION_SECRET=$(openssl rand -hex 32) \ - --from-literal=ADMIN_API_CLIENT_SECRET= \ - --from-literal=DEVELOPER_API_CLIENT_SECRET= -``` - -### Step 4: Deploy with Kustomize - -#### Option A: Using Flux CD (Recommended) +# Copy init bundle to Aidbox namespace +kubectl create configmap init-bundle -n aidbox-portal \ + --from-file=init-bundle.json=<(kubectl get cm smartbox-portal-init-bundle -n smartbox -o jsonpath='{.data.init-bundle\.json}') -If you have Flux CD installed, apply the manifests: - -```bash # Deploy Aidbox Portal -kubectl apply -k aidbox/portal/ - -# Deploy Aidbox Sandbox -kubectl apply -k aidbox/sandbox/ - -# Deploy FHIR App Portal -kubectl apply -k fhir-app-portal/ -``` - -Flux will reconcile the HelmRelease resources and deploy Aidbox via Helm. - -#### Option B: Without Flux (Manual Helm) - -If you don't have Flux CD, deploy Aidbox using Helm directly: - -```bash -# Add Aidbox Helm repository -helm repo add aidbox https://aidbox.github.io/helm-charts -helm repo update -``` - -**Deploy Aidbox Portal:** - -```bash -kubectl create configmap init-bundle \ - --namespace aidbox-portal \ - --from-file=init-bundle.json=aidbox/portal/init-bundle.json - -helm install aidbox aidbox/aidbox \ - --namespace aidbox-portal \ - --set host=aidbox.yourdomain.com \ +helm install aidbox aidbox/aidbox -n aidbox-portal \ + --set host=aidbox.example.com \ --set protocol=https \ - --set image.tag=2510 \ - --set config.BOX_ID=aidbox-portal \ - --set config.BOX_INSTANCE_NAME=aidbox-portal \ - --set config.BOX_DB_HOST=aidboxdb.aidbox-db.svc.cluster.local \ - --set config.BOX_DB_PORT=5432 \ + --set config.BOX_DB_HOST=smartbox-db.smartbox.svc.cluster.local \ --set config.BOX_DB_DATABASE=portal \ - --set config.BOX_INIT_BUNDLE=file:///init-bundle/init-bundle.json \ - --set ingress.enabled=true \ - --set ingress.className=nginx \ - --set 'ingress.annotations.cert-manager\.io/cluster-issuer=letsencrypt' \ - --set 'extraEnvFromSecrets[0]=aidbox-secrets' \ - --set 'extraEnvFromSecrets[1]=postgres-secrets' \ - --set 'volumes[0].name=init-bundle' \ - --set 'volumes[0].configMap.name=init-bundle' \ - --set 'volumeMounts[0].name=init-bundle' \ - --set 'volumeMounts[0].mountPath=/init-bundle' \ - --wait --timeout 10m + --set extraEnvFromSecrets[0]=aidbox-secrets \ + --set extraEnvFromSecrets[1]=postgres-secrets \ + --set volumes[0].name=init-bundle \ + --set volumes[0].configMap.name=init-bundle \ + --set volumeMounts[0].name=init-bundle \ + --set volumeMounts[0].mountPath=/init-bundle ``` -**Deploy Aidbox Sandbox:** +Repeat similar steps for Aidbox Sandbox. -```bash -kubectl create configmap init-bundle \ - --namespace aidbox-sandbox \ - --from-file=init-bundle.json=aidbox/sandbox/init-bundle.json - -helm install aidbox aidbox/aidbox \ - --namespace aidbox-sandbox \ - --set host=aidbox-sandbox.yourdomain.com \ - --set protocol=https \ - --set image.tag=2510 \ - --set config.BOX_ID=aidbox-sandbox \ - --set config.BOX_INSTANCE_NAME=aidbox-sandbox \ - --set config.BOX_DB_HOST=aidboxdb.aidbox-db.svc.cluster.local \ - --set config.BOX_DB_PORT=5432 \ - --set config.BOX_DB_DATABASE=sandbox \ - --set config.BOX_INIT_BUNDLE=file:///init-bundle/init-bundle.json \ - --set ingress.enabled=true \ - --set ingress.className=nginx \ - --set 'ingress.annotations.cert-manager\.io/cluster-issuer=letsencrypt' \ - --set 'extraEnvFromSecrets[0]=aidbox-secrets' \ - --set 'extraEnvFromSecrets[1]=postgres-secrets' \ - --set 'volumes[0].name=init-bundle' \ - --set 'volumes[0].configMap.name=init-bundle' \ - --set 'volumeMounts[0].name=init-bundle' \ - --set 'volumeMounts[0].mountPath=/init-bundle' \ - --wait --timeout 10m -``` - -**Deploy FHIR App Portal:** - -```bash -kubectl apply -k fhir-app-portal/ -``` - ---- - -## Verification - -### Check Pod Status - -```bash -# Database -kubectl get pods -n aidbox-db - -# Aidbox instances -kubectl get pods -n aidbox-portal -kubectl get pods -n aidbox-sandbox - -# Portal application -kubectl get pods -n fhir-app-portal -``` - -All pods should be in `Running` state. - -### Check Ingresses - -```bash -kubectl get ingress --all-namespaces -``` - -### Check Certificates - -```bash -kubectl get certificates --all-namespaces -``` - -Certificates should show `Ready: True`. - -### Test Endpoints - -After deployment, access your applications: - -| Application | URL | -|-------------|-----| -| Admin Portal | `https://portal.yourdomain.com` | -| Developer Portal | `https://portal-sandbox.yourdomain.com` | -| Admin Aidbox | `https://aidbox.yourdomain.com` | -| Sandbox Aidbox | `https://aidbox-sandbox.yourdomain.com` | - -**Default Aidbox Credentials:** -- Username: `admin` -- Password: Value set in `BOX_ADMIN_PASSWORD` secret - ---- - -## Troubleshooting - -### Database Issues - -```bash -# Check database pod status -kubectl describe pod -n aidbox-db -l service=aidboxdb - -# Check database logs -kubectl logs -n aidbox-db -l service=aidboxdb - -# Connect to database manually -kubectl exec -it -n aidbox-db statefulset/aidboxdb -- psql -U postgres - -# List databases -kubectl exec -it -n aidbox-db statefulset/aidboxdb -- psql -U postgres -c "\l" -``` - -### Aidbox Pods Not Starting - -```bash -# Check pod events -kubectl describe pod -n aidbox-portal -l app.kubernetes.io/name=aidbox - -# Check logs -kubectl logs -n aidbox-portal -l app.kubernetes.io/name=aidbox - -# Check database connectivity -kubectl exec -it -n aidbox-portal -- nc -zv aidboxdb.aidbox-db.svc.cluster.local 5432 -``` - -### Certificate Issues - -```bash -# Check certificate status -kubectl describe certificate -n aidbox-portal - -# Check cert-manager logs -kubectl logs -n cert-manager -l app=cert-manager -``` - -### Ingress Not Working - -```bash -# Check ingress status -kubectl describe ingress -n aidbox-portal - -# Check ingress controller logs -kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -``` - -### HelmRelease Not Reconciling (Flux) - -```bash -# Check HelmRelease status -kubectl get helmrelease -n aidbox-portal - -# Check Flux logs -kubectl logs -n flux-system -l app=helm-controller - -# Force reconciliation -flux reconcile helmrelease aidbox -n aidbox-portal -``` - ---- - -## File Structure +## Architecture ``` -customer-deployment/ -├── README.md -├── database/ -│ ├── kustomization.yaml -│ ├── namespace.yaml -│ ├── configmap.yaml # PostgreSQL configuration -│ ├── pvc.yaml # Persistent Volume Claim -│ ├── statefulset.yaml # Database StatefulSet -│ └── service.yaml # Database Service -├── aidbox/ -│ ├── portal/ -│ │ ├── kustomization.yaml -│ │ ├── namespace.yaml -│ │ ├── aidbox.yaml # HelmRepository + HelmRelease -│ │ └── init-bundle.json # OAuth clients & access policies -│ └── sandbox/ -│ ├── kustomization.yaml -│ ├── namespace.yaml -│ ├── aidbox.yaml # HelmRepository + HelmRelease -│ └── init-bundle.json # OAuth clients, policies & test data -├── fhir-app-portal/ -│ ├── kustomization.yaml -│ ├── namespace.yaml -│ ├── configmap.yaml -│ ├── deployment.yaml -│ ├── service.yaml -│ └── ingress.yaml -└── secrets/ - └── secrets-template.env # Template for secrets reference + ┌─────────────────────────────┐ + │ NGINX Ingress │ + └──────────┬──────────────────┘ + │ + ┌──────────────────────────┼──────────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Aidbox Portal │ │ Aidbox Sandbox │ │ FHIR App Portal │ +│ (aidbox-portal) │◄───│(aidbox-sandbox) │◄───│ (smartbox) │ +└────────┬────────┘ └────────┬────────┘ └─────────────────┘ + │ │ + └──────────┬───────────┘ + ▼ + ┌─────────────────┐ + │ AidboxDB │ + │ (smartbox-db) │ + └─────────────────┘ ``` ---- - -## Configuration Reference - -### Required Secrets +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| domain.baseDomain | string | `"yourdomain.com"` | Base domain for all services | +| domain.adminPortal | string | `"portal.yourdomain.com"` | Admin portal hostname | +| domain.developerPortal | string | `"portal-sandbox.yourdomain.com"` | Developer/sandbox portal hostname | +| domain.adminAidbox | string | `"aidbox.yourdomain.com"` | Admin Aidbox hostname | +| domain.sandboxAidbox | string | `"aidbox-sandbox.yourdomain.com"` | Sandbox Aidbox hostname | +| portal.replicaCount | int | `1` | Number of portal replicas | +| portal.image.repository | string | `"healthsamurai/fhir-app-portal"` | Portal image repository | +| portal.image.tag | string | `"edge"` | Portal image tag | +| portal.secrets.SESSION_SECRET | string | `""` | Session secret for cookie encryption | +| portal.secrets.ADMIN_API_CLIENT_SECRET | string | `""` | Admin API client secret | +| portal.secrets.DEVELOPER_API_CLIENT_SECRET | string | `""` | Developer API client secret | +| database.enabled | bool | `true` | Enable AidboxDB deployment | +| database.image.repository | string | `"healthsamurai/aidboxdb"` | Database image repository | +| database.image.tag | string | `"16.1"` | Database image tag | +| database.persistence.enabled | bool | `true` | Enable persistent storage | +| database.persistence.size | string | `"50Gi"` | Storage size | +| database.secrets.POSTGRES_USER | string | `""` | PostgreSQL username | +| database.secrets.POSTGRES_PASSWORD | string | `""` | PostgreSQL password | +| ingress.enabled | bool | `true` | Enable ingress | +| ingress.className | string | `"nginx"` | Ingress class name | +| aidboxPortal.enabled | bool | `true` | Enable Aidbox Portal init bundle | +| aidboxSandbox.enabled | bool | `true` | Enable Aidbox Sandbox init bundle | + +## Required Secrets | Namespace | Secret Name | Keys | |-----------|-------------|------| -| `aidbox-db` | `aidboxdb-secret` | `POSTGRES_USER`, `POSTGRES_PASSWORD` | -| `aidbox-portal` | `aidbox-secrets` | `AIDBOX_LICENSE`, `BOX_AUTH_KEYS_SECRET`, `BOX_ADMIN_PASSWORD` | -| `aidbox-portal` | `postgres-secrets` | `BOX_DB_USER`, `BOX_DB_PASSWORD` | -| `aidbox-sandbox` | `aidbox-secrets` | `AIDBOX_LICENSE`, `BOX_AUTH_KEYS_SECRET`, `BOX_ADMIN_PASSWORD` | -| `aidbox-sandbox` | `postgres-secrets` | `BOX_DB_USER`, `BOX_DB_PASSWORD` | -| `fhir-app-portal` | `fhir-app-portal--session-secret` | `SESSION_SECRET`, `ADMIN_API_CLIENT_SECRET`, `DEVELOPER_API_CLIENT_SECRET` | - -### Database Connection - -The Aidbox instances connect to the database using: - -``` -Host: aidboxdb.aidbox-db.svc.cluster.local -Port: 5432 -Databases: portal, sandbox -``` - ---- - -## Deployment Order Summary - -1. **Database** → `kubectl apply -k database/` -2. **Secrets** → Create all secrets in respective namespaces -3. **Aidbox Portal** → `kubectl apply -k aidbox/portal/` -4. **Aidbox Sandbox** → `kubectl apply -k aidbox/sandbox/` -5. **FHIR App Portal** → `kubectl apply -k fhir-app-portal/` - ---- +| smartbox | `-smartbox` | `SESSION_SECRET`, `ADMIN_API_CLIENT_SECRET`, `DEVELOPER_API_CLIENT_SECRET` | +| smartbox | `-smartbox-db` | `POSTGRES_USER`, `POSTGRES_PASSWORD` | +| aidbox-portal | `aidbox-secrets` | `AIDBOX_LICENSE`, `BOX_AUTH_KEYS_SECRET`, `BOX_ADMIN_PASSWORD` | +| aidbox-portal | `postgres-secrets` | `BOX_DB_USER`, `BOX_DB_PASSWORD` | +| aidbox-sandbox | `aidbox-secrets` | `AIDBOX_LICENSE`, `BOX_AUTH_KEYS_SECRET`, `BOX_ADMIN_PASSWORD` | +| aidbox-sandbox | `postgres-secrets` | `BOX_DB_USER`, `BOX_DB_PASSWORD` | ## Support - **Aidbox Documentation**: https://docs.aidbox.app - **Aidbox Support**: https://aidbox.app/support + diff --git a/smartbox/README.md.gotmpl b/smartbox/README.md.gotmpl new file mode 100644 index 0000000..3cd5dd7 --- /dev/null +++ b/smartbox/README.md.gotmpl @@ -0,0 +1,123 @@ +{{ template "chart.header" . }} + +{{ template "chart.versionBadge" . }}{{ template "chart.typeBadge" . }}{{ template "chart.appVersionBadge" . }} + +## Description + +{{ template "chart.description" . }} + +This Helm chart deploys the FHIR App Portal solution which includes: + +- **FHIR App Portal**: Web application serving Admin and Developer portals +- **AidboxDB**: PostgreSQL database for Aidbox instances +- **Init Bundles**: ConfigMaps with initialization data for Aidbox Portal and Sandbox + +## Prerequisites + +- Kubernetes cluster v1.24+ +- Helm v3.x +- NGINX Ingress Controller (optional, for ingress) +- cert-manager with ClusterIssuer (optional, for TLS) +- Aidbox license key (contact https://aidbox.app) + +## Installation + +### 1. Prepare configuration + +Create a values file with your configuration: + +```yaml +# values-production.yaml +domain: + baseDomain: "example.com" + adminPortal: "portal.example.com" + developerPortal: "portal-sandbox.example.com" + adminAidbox: "aidbox.example.com" + sandboxAidbox: "aidbox-sandbox.example.com" + +portal: + secrets: + SESSION_SECRET: "" + ADMIN_API_CLIENT_SECRET: "" + DEVELOPER_API_CLIENT_SECRET: "" + +database: + enabled: true + secrets: + POSTGRES_USER: "postgres" + POSTGRES_PASSWORD: "" +``` + +### 2. Install the chart + +```console +helm repo add healthsamurai https://healthsamurai.github.io/helm-charts + +helm upgrade --install smartbox healthsamurai/smartbox \ + --namespace smartbox --create-namespace \ + --values values-production.yaml +``` + +### 3. Deploy Aidbox instances + +After installing this chart, deploy Aidbox instances using the generated init bundle ConfigMaps: + +```bash +# Add Aidbox Helm repo +helm repo add aidbox https://aidbox.github.io/helm-charts + +# Create Aidbox Portal namespace and secrets +kubectl create namespace aidbox-portal +kubectl create secret generic aidbox-secrets -n aidbox-portal \ + --from-literal=AIDBOX_LICENSE= \ + --from-literal=BOX_AUTH_KEYS_SECRET= \ + --from-literal=BOX_ADMIN_PASSWORD= +kubectl create secret generic postgres-secrets -n aidbox-portal \ + --from-literal=BOX_DB_USER=postgres \ + --from-literal=BOX_DB_PASSWORD= + +# Copy init bundle to Aidbox namespace +kubectl create configmap init-bundle -n aidbox-portal \ + --from-file=init-bundle.json=<(kubectl get cm smartbox-portal-init-bundle -n smartbox -o jsonpath='{.data.init-bundle\.json}') + +# Deploy Aidbox Portal +helm install aidbox aidbox/aidbox -n aidbox-portal \ + --set host=aidbox.example.com \ + --set protocol=https \ + --set config.BOX_DB_HOST=smartbox-db.smartbox.svc.cluster.local \ + --set config.BOX_DB_DATABASE=portal \ + --set extraEnvFromSecrets[0]=aidbox-secrets \ + --set extraEnvFromSecrets[1]=postgres-secrets \ + --set volumes[0].name=init-bundle \ + --set volumes[0].configMap.name=init-bundle \ + --set volumeMounts[0].name=init-bundle \ + --set volumeMounts[0].mountPath=/init-bundle +``` + +Repeat similar steps for Aidbox Sandbox. + +## Architecture + +``` + ┌─────────────────────────────┐ + │ NGINX Ingress │ + └──────────┬──────────────────┘ + │ + ┌──────────────────────────┼──────────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Aidbox Portal │ │ Aidbox Sandbox │ │ FHIR App Portal │ +│ (aidbox-portal) │◄───│(aidbox-sandbox) │◄───│ (smartbox) │ +└────────┬────────┘ └────────┬────────┘ └─────────────────┘ + │ │ + └──────────┬───────────┘ + ▼ + ┌─────────────────┐ + │ AidboxDB │ + │ (smartbox-db) │ + └─────────────────┘ +``` + +{{ template "chart.valuesSection" . }} + diff --git a/smartbox/aidbox/portal/aidbox.yaml b/smartbox/aidbox/portal/aidbox.yaml deleted file mode 100644 index 7a39fee..0000000 --- a/smartbox/aidbox/portal/aidbox.yaml +++ /dev/null @@ -1,148 +0,0 @@ -# ============================================================================= -# Aidbox Portal - HelmRepository and HelmRelease -# ============================================================================= -# This deploys Aidbox via Flux HelmRelease. -# If not using Flux, see README for manual Helm installation. -# ============================================================================= ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: aidbox -spec: - interval: 24h - url: https://aidbox.github.io/helm-charts ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: aidbox -spec: - interval: 1h - timeout: 5m - chart: - spec: - chart: aidbox - sourceRef: - kind: HelmRepository - name: aidbox - interval: 1h - releaseName: aidbox - install: - remediation: - retries: 3 - upgrade: - remediation: - retries: 3 - values: - # ========================================================================= - # Domain Configuration - # ========================================================================= - # IMPORTANT: Replace with your actual domain - host: aidbox.yourdomain.com - protocol: https - - # ========================================================================= - # Image Configuration - # ========================================================================= - image: - tag: "2510" - - # ========================================================================= - # Aidbox Configuration - # ========================================================================= - config: - # Instance identification - BOX_ID: aidbox-portal - BOX_INSTANCE_NAME: aidbox-portal - BOX_WEB_PORT: 8080 - - # Database configuration - BOX_DB_HOST: aidboxdb.aidbox-db.svc.cluster.local - BOX_DB_PORT: 5432 - BOX_DB_DATABASE: portal - - # Init bundle (bootstrap configuration) - BOX_INIT_BUNDLE: file:///init-bundle/init-bundle.json - - # FHIR configuration - BOX_BOOTSTRAP_FHIR_PACKAGES: 'hl7.fhir.r4.core#4.0.1' - BOX_COMPATIBILITY_VALIDATION_JSON__SCHEMA_REGEX: '#{:fhir-datetime}' - BOX_FHIR_BUNDLE_EXECUTION_VALIDATION_MODE: limited - BOX_FHIR_COMPLIANT_MODE: true - BOX_FHIR_CORRECT_AIDBOX_FORMAT: true - BOX_FHIR_CREATEDAT_URL: https://aidbox.app/ex/createdAt - BOX_FHIR_SCHEMA_VALIDATION: true - BOX_FHIR_SEARCH_AUTHORIZE_INLINE_REQUESTS: false - BOX_FHIR_SEARCH_CHAIN_SUBSELECT: true - BOX_FHIR_SEARCH_COMPARISONS: true - BOX_FHIR_LEGACY_FCE_PACKAGE: us-core-extensions.legacy.aidbox#0.0.0 - BOX_FHIR_VALIDATION_SKIP_REFERENCE: true - - # Security configuration - BOX_MODULE_SDC_STRICT_ACCESS_CONTROL: true - BOX_SEARCH_INCLUDE_CONFORMANT: true - BOX_SECURITY_AUDIT_LOG_ENABLED: true - BOX_SECURITY_DEV_MODE: false - BOX_SETTINGS_MODE: read-write - BOX_SECURITY_ORGBAC_ENABLED: true - BOX_USAGE_STATS: false - - # Observability - BOX_OBSERVABILITY_STDOUT_PRETTY_LOG_LEVEL: info - BOX_METRICS_PORT: 8765 - - # ========================================================================= - # Secrets Configuration - # ========================================================================= - # aidbox-secrets: AIDBOX_LICENSE, BOX_AUTH_KEYS_SECRET, BOX_ADMIN_PASSWORD - # postgres-secrets: BOX_DB_USER, BOX_DB_PASSWORD - extraEnvFromSecrets: - - aidbox-secrets - - postgres-secrets - - # ========================================================================= - # Init Bundle Volume Mount - # ========================================================================= - volumes: - - name: init-bundle - configMap: - name: init-bundle - - volumeMounts: - - name: init-bundle - mountPath: /init-bundle - - # ========================================================================= - # Ingress Configuration - # ========================================================================= - ingress: - enabled: true - className: nginx - annotations: - cert-manager.io/cluster-issuer: letsencrypt - nginx.ingress.kubernetes.io/ssl-redirect: "true" - - # ========================================================================= - # Resource Limits - # ========================================================================= - resources: - limits: - memory: 2Gi - requests: - cpu: 500m - memory: 2Gi - - # ========================================================================= - # Probes Configuration - # ========================================================================= - startupProbe: - initialDelaySeconds: 50 - periodSeconds: 10 - failureThreshold: 20 - - # ========================================================================= - # Monitoring - # ========================================================================= - serviceMonitor: - enabled: false diff --git a/smartbox/aidbox/portal/init-bundle.json b/smartbox/aidbox/portal/init-bundle.json deleted file mode 100644 index 36d65f3..0000000 --- a/smartbox/aidbox/portal/init-bundle.json +++ /dev/null @@ -1,366 +0,0 @@ -{ - "resourceType": "Bundle", - "type": "transaction", - "entry": [ - { - "resource": { - "hl7.fhir.us.core#6.1.0": "https://packages.simplifier.net/hl7.fhir.us.core/6.1.0", - "us-core-extensions.legacy.aidbox#0.0.0": "https://storage.googleapis.com/aidbox-public/smartbox/us-core-extensions.legacy.aidbox.tar.gz" - }, - "request": { - "method": "POST", - "url": "/$upload-fhir-npm-packages" - } - }, - { - "resource": { - "resourceType": "FHIRSchema", - "url": "http://aidbox.app/StructureDefinition/Client/created-by", - "id": "client-created-by", - "base": "Extension", - "name": "client-created-by", - "kind": "complex-type", - "type": "Extension", - "version": "0.0.1", - "elements": { - "url": { - "fixed": "http://aidbox.app/StructureDefinition/Client/created-by" - }, - "value": { - "choices": [ - "valueReference" - ] - }, - "valueReference": { - "type": "Reference", - "refers": [ - "User" - ], - "choiceOf": "value" - } - }, - "derivation": "constraint" - }, - "request": { - "method": "PUT", - "url": "FHIRSchema/client-created-by" - } - }, - { - "resource": { - "resourceType": "SearchParameter", - "id": "Client.created-by", - "url": "http://aidbox.app/StructureDefinition/Client/created-by", - "name": "created-by", - "status": "active", - "code": "created-by", - "base": [ - "Client" - ], - "type": "token", - "description": "Client created-by User", - "expression": "Client.meta.extension.where(url='http://aidbox.app/StructureDefinition/Client/created-by').value.Reference.id" - }, - "request": { - "method": "PUT", - "url": "SearchParameter/Client.created-by" - } - }, - { - "resource": { - "resourceType": "FHIRSchema", - "url": "http://aidbox.app/StructureDefinition/Client/status", - "id": "client-status", - "base": "Extension", - "name": "client-status", - "kind": "complex-type", - "type": "Extension", - "version": "0.0.2", - "elements": { - "url": { - "fixed": "http://aidbox.app/StructureDefinition/Client/status" - }, - "value": { - "choices": [ - "valueCode" - ] - }, - "valueCode": { - "type": "code", - "choiceOf": "value", - "constraints": { - "enum-client-status": { - "severity": "error", - "expression": "%context.subsetOf('draft' | 'review' | 'active' | 'rejected')" - } - } - } - }, - "derivation": "constraint" - }, - "request": { - "method": "PUT", - "url": "FHIRSchema/client-status" - } - }, - { - "resource": { - "resourceType": "SearchParameter", - "id": "Client.status", - "url": "http://aidbox.app/StructureDefinition/Client/status", - "name": "status", - "status": "active", - "code": "status", - "base": [ - "Client" - ], - "type": "token", - "description": "Client status (draft, review, active, rejected)", - "expression": "Client.meta.extension.where(url='http://aidbox.app/StructureDefinition/Client/status').value.code" - }, - "request": { - "method": "PUT", - "url": "SearchParameter/Client.status" - } - }, - { - "_comment": "OAuth Client for Admin Portal - UPDATE redirect_uri with your domain", - "resource": { - "resourceType": "Client", - "id": "smartbox-admin-portal", - "first_party": true, - "grant_types": [ - "code" - ], - "scope": [ - "openid", - "profile", - "email" - ], - "auth": { - "authorization_code": { - "redirect_uri": "https://portal.yourdomain.com/api/admin/auth/callback", - "access_token_expiration": 3600, - "token_format": "jwt", - "pkce": true, - "refresh_token": true, - "refresh_token_expiration": 86400 - } - } - }, - "request": { - "method": "PUT", - "url": "Client/smartbox-admin-portal" - } - }, - { - "resource": { - "resourceType": "AccessPolicy", - "id": "admin-api", - "engine": "matcho", - "description": "Scoped access for admin API client (sessions, orgs, clients, users, tos/privacy)", - "matcho": { - "client": { - "id": "admin-api" - }, - "$one-of": [ - { - "params": { - "resource/type": "Session" - }, - "operation": { - "id": { - "$one-of": [ - "FhirCreate", - "FhirSearch", - "FhirRead", - "FhirUpdate", - "FhirPatch" - ] - } - } - }, - { - "params": { - "resource/type": "Organization" - }, - "operation": { - "id": { - "$one-of": [ - "FhirSearch", - "FhirRead" - ] - } - } - }, - { - "params": { - "resource/type": "Client" - }, - "operation": { - "id": { - "$one-of": [ - "FhirSearch", - "FhirRead" - ] - } - } - }, - { - "params": { - "resource/type": "User" - }, - "operation": { - "id": "FhirRead" - } - }, - { - "params": { - "resource/type": "DocumentReference", - "resource/id": { - "$one-of": [ - "smartbox-tos", - "smartbox-privacy" - ] - } - }, - "operation": { - "id": { - "$one-of": [ - "FhirRead", - "FhirUpdate", - "FhirCreate" - ] - } - } - } - ] - } - }, - "request": { - "method": "PUT", - "url": "AccessPolicy/admin-api" - } - }, - { - "resource": { - "resourceType": "SearchParameter", - "id": "Communication.about", - "url": "http://smartbox.hs/sp/Communication-about", - "name": "communication-about", - "status": "active", - "code": "about", - "base": [ - "Communication" - ], - "type": "reference", - "description": "Search Communication by Communication.about (any reference, incl. Aidbox system resources)", - "expression": "Communication.about" - }, - "request": { - "method": "PUT", - "url": "SearchParameter/Communication.about" - } - }, - { - "resource": { - "resourceType": "SearchParameter", - "id": "Session.client", - "url": "http://smartbox.hs/sp/Session-client", - "name": "session-client", - "status": "active", - "code": "client", - "base": [ - "Session" - ], - "type": "reference", - "description": "Search Session by client reference", - "expression": "Session.client", - "target": [ - "Client" - ] - }, - "request": { - "method": "PUT", - "url": "SearchParameter/Session.client" - } - }, - { - "resource": { - "roles": [ - { - "type": "admin" - } - ] - }, - "request": { - "method": "PATCH", - "url": "User/admin" - } - }, - { - "resource": { - "resourceType": "AccessPolicy", - "id": "allow-read-settings", - "engine": "matcho", - "description": "Allow admin-api client to read Aidbox settings", - "matcho": { - "client": { "id": "admin-api" }, - "uri": { - "$one-of": ["/api/v1/settings/introspect", "^/api/v1/settings/.*"] - } - } - }, - "request": { - "method": "PUT", - "url": "AccessPolicy/allow-read-settings" - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/smart-app-can-read-patient-api" - }, - "resource": { - "engine": "matcho", - "matcho": { - "jwt": { - "atv": 2, - "scope": "present?", - "context": { - "patient": "present?" - } - }, - "client": { - "type": "smart-app", - "active": true - } - }, - "id": "smart-app-can-read-patient-api", - "resourceType": "AccessPolicy" - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/patient-can-get-launch-uri" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "patient-can-get-launch-uri", - "type": "rpc", - "engine": "matcho-rpc", - "rpc": { - "aidbox.smart/get-launch-uri": { - "user": { - "fhirUser": { - "resourceType": "Patient" - } - } - } - } - } - } - ] -} - diff --git a/smartbox/aidbox/portal/kustomization.yaml b/smartbox/aidbox/portal/kustomization.yaml deleted file mode 100644 index 1fd3c81..0000000 --- a/smartbox/aidbox/portal/kustomization.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: aidbox-portal - -resources: - - namespace.yaml - - aidbox.yaml - -generatorOptions: - disableNameSuffixHash: true - -configMapGenerator: - - name: init-bundle - files: - - init-bundle.json - diff --git a/smartbox/aidbox/portal/namespace.yaml b/smartbox/aidbox/portal/namespace.yaml deleted file mode 100644 index df80ab0..0000000 --- a/smartbox/aidbox/portal/namespace.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: aidbox-portal - labels: - app: aidbox - environment: production - component: portal - diff --git a/smartbox/aidbox/sandbox/aidbox.yaml b/smartbox/aidbox/sandbox/aidbox.yaml deleted file mode 100644 index b1940db..0000000 --- a/smartbox/aidbox/sandbox/aidbox.yaml +++ /dev/null @@ -1,147 +0,0 @@ -# ============================================================================= -# Aidbox Sandbox - HelmRepository and HelmRelease -# ============================================================================= -# This deploys Aidbox via Flux HelmRelease. -# If not using Flux, see README for manual Helm installation. -# ============================================================================= ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: aidbox -spec: - interval: 24h - url: https://aidbox.github.io/helm-charts ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: aidbox -spec: - interval: 1h - timeout: 5m - chart: - spec: - chart: aidbox - sourceRef: - kind: HelmRepository - name: aidbox - interval: 1h - releaseName: aidbox - install: - remediation: - retries: 3 - upgrade: - remediation: - retries: 3 - values: - # ========================================================================= - # Domain Configuration - # ========================================================================= - # IMPORTANT: Replace with your actual domain - host: aidbox-sandbox.yourdomain.com - protocol: https - - # ========================================================================= - # Image Configuration - # ========================================================================= - image: - tag: "2510" - - # ========================================================================= - # Aidbox Configuration - # ========================================================================= - config: - # Instance identification - BOX_ID: aidbox-sandbox - BOX_INSTANCE_NAME: aidbox-sandbox - BOX_WEB_PORT: 8080 - - # Database configuration - BOX_DB_HOST: aidboxdb.aidbox-db.svc.cluster.local - BOX_DB_PORT: 5432 - BOX_DB_DATABASE: sandbox - - # Init bundle (bootstrap configuration) - BOX_INIT_BUNDLE: file:///init-bundle/init-bundle.json - - # FHIR configuration - BOX_BOOTSTRAP_FHIR_PACKAGES: 'hl7.fhir.r4.core#4.0.1' - BOX_COMPATIBILITY_VALIDATION_JSON__SCHEMA_REGEX: '#{:fhir-datetime}' - BOX_FHIR_BUNDLE_EXECUTION_VALIDATION_MODE: limited - BOX_FHIR_COMPLIANT_MODE: true - BOX_FHIR_CORRECT_AIDBOX_FORMAT: true - BOX_FHIR_CREATEDAT_URL: https://aidbox.app/ex/createdAt - BOX_FHIR_SCHEMA_VALIDATION: true - BOX_FHIR_SEARCH_AUTHORIZE_INLINE_REQUESTS: false - BOX_FHIR_SEARCH_CHAIN_SUBSELECT: true - BOX_FHIR_SEARCH_COMPARISONS: true - BOX_FHIR_LEGACY_FCE_PACKAGE: us-core-extensions.legacy.aidbox#0.0.0 - - # Security configuration - BOX_MODULE_SDC_STRICT_ACCESS_CONTROL: true - BOX_SEARCH_INCLUDE_CONFORMANT: true - BOX_SECURITY_AUDIT_LOG_ENABLED: true - BOX_SECURITY_DEV_MODE: false - BOX_SETTINGS_MODE: read-write - BOX_SECURITY_ORGBAC_ENABLED: true - BOX_USAGE_STATS: false - - # Observability - BOX_OBSERVABILITY_STDOUT_PRETTY_LOG_LEVEL: info - BOX_METRICS_PORT: 8765 - - # ========================================================================= - # Secrets Configuration - # ========================================================================= - # aidbox-secrets: AIDBOX_LICENSE, BOX_AUTH_KEYS_SECRET, BOX_ADMIN_PASSWORD - # postgres-secrets: BOX_DB_USER, BOX_DB_PASSWORD - extraEnvFromSecrets: - - aidbox-secrets - - postgres-secrets - - # ========================================================================= - # Init Bundle Volume Mount - # ========================================================================= - volumes: - - name: init-bundle - configMap: - name: init-bundle - - volumeMounts: - - name: init-bundle - mountPath: /init-bundle - - # ========================================================================= - # Ingress Configuration - # ========================================================================= - ingress: - enabled: true - className: nginx - annotations: - cert-manager.io/cluster-issuer: letsencrypt - nginx.ingress.kubernetes.io/ssl-redirect: "true" - - # ========================================================================= - # Resource Limits - # ========================================================================= - resources: - limits: - memory: 2Gi - requests: - cpu: 500m - memory: 2Gi - - # ========================================================================= - # Probes Configuration - # ========================================================================= - startupProbe: - initialDelaySeconds: 50 - periodSeconds: 10 - failureThreshold: 20 - - # ========================================================================= - # Monitoring - # ========================================================================= - serviceMonitor: - enabled: false diff --git a/smartbox/aidbox/sandbox/init-bundle.json b/smartbox/aidbox/sandbox/init-bundle.json deleted file mode 100644 index 20d7e25..0000000 --- a/smartbox/aidbox/sandbox/init-bundle.json +++ /dev/null @@ -1,577 +0,0 @@ -{ - "resourceType": "Bundle", - "type": "transaction", - "entry": [ - { - "_comment": "Upload required FHIR packages", - "resource": { - "hl7.fhir.us.core#6.1.0": "https://packages.simplifier.net/hl7.fhir.us.core/6.1.0", - "us-core-extensions.legacy.aidbox#0.0.0": "https://storage.googleapis.com/aidbox-public/smartbox/us-core-extensions.legacy.aidbox.tar.gz" - }, - "request": { - "method": "POST", - "url": "/$upload-fhir-npm-packages" - } - }, - { - "_comment": "Load sample test data for developers", - "resource": { - "source": "https://storage.googleapis.com/aidbox-public/smartbox/all_data.ndjson.gz" - }, - "request": { - "method": "POST", - "url": "/$load" - } - }, - { - "resource": { - "resourceType": "FHIRSchema", - "url": "http://aidbox.app/StructureDefinition/Client/created-by", - "id": "client-created-by", - "base": "Extension", - "name": "client-created-by", - "kind": "complex-type", - "type": "Extension", - "version": "0.0.1", - "elements": { - "url": { - "fixed": "http://aidbox.app/StructureDefinition/Client/created-by" - }, - "value": { - "choices": [ - "valueReference" - ] - }, - "valueReference": { - "type": "Reference", - "refers": [ - "User" - ], - "choiceOf": "value" - } - }, - "derivation": "constraint" - }, - "request": { - "method": "PUT", - "url": "FHIRSchema/client-created-by" - } - }, - { - "resource": { - "resourceType": "SearchParameter", - "id": "Client.created-by", - "url": "http://aidbox.app/StructureDefinition/Client/created-by", - "name": "created-by", - "status": "active", - "code": "created-by", - "base": [ - "Client" - ], - "type": "token", - "description": "Client created-by User", - "expression": "Client.meta.extension.where(url='http://aidbox.app/StructureDefinition/Client/created-by').value.Reference.id" - }, - "request": { - "method": "PUT", - "url": "SearchParameter/Client.created-by" - } - }, - { - "resource": { - "resourceType": "FHIRSchema", - "url": "http://aidbox.app/StructureDefinition/Client/status", - "id": "client-status", - "base": "Extension", - "name": "client-status", - "kind": "complex-type", - "type": "Extension", - "version": "0.0.2", - "elements": { - "url": { - "fixed": "http://aidbox.app/StructureDefinition/Client/status" - }, - "value": { - "choices": [ - "valueCode" - ] - }, - "valueCode": { - "type": "code", - "choiceOf": "value", - "constraints": { - "enum-client-status": { - "severity": "error", - "expression": "%context.subsetOf('draft' | 'review' | 'active' | 'rejected')" - } - } - } - }, - "derivation": "constraint" - }, - "request": { - "method": "PUT", - "url": "FHIRSchema/client-status" - } - }, - { - "resource": { - "resourceType": "SearchParameter", - "id": "Client.status", - "url": "http://aidbox.app/StructureDefinition/Client/status", - "name": "status", - "status": "active", - "code": "status", - "base": [ - "Client" - ], - "type": "token", - "description": "Client status (draft, review, active, rejected)", - "expression": "Client.meta.extension.where(url='http://aidbox.app/StructureDefinition/Client/status').value.code" - }, - "request": { - "method": "PUT", - "url": "SearchParameter/Client.status" - } - }, - { - "_comment": "OAuth Client for Developer Portal - UPDATE redirect_uri with your domain", - "resource": { - "resourceType": "Client", - "id": "smartbox-developer-portal", - "first_party": true, - "grant_types": [ - "code" - ], - "scope": [ - "openid", - "profile", - "email" - ], - "auth": { - "authorization_code": { - "redirect_uri": "https://portal-sandbox.yourdomain.com/api/developer/auth/callback", - "access_token_expiration": 3600, - "token_format": "jwt", - "pkce": true, - "refresh_token": true, - "refresh_token_expiration": 86400 - } - } - }, - "request": { - "method": "PUT", - "url": "Client/smartbox-developer-portal" - } - }, - { - "_comment": "Link admin user to test patient for sandbox testing", - "resource": { - "fhirUser": { - "reference": "Patient/test-pt-1" - } - }, - "request": { - "method": "PATCH", - "url": "User/admin" - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/dev-can-get-launch-uri" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "dev-can-get-launch-uri", - "type": "rpc", - "engine": "matcho-rpc", - "rpc": { - "aidbox.smart/get-launch-uri": { - "user": { - "fhirUser": { - "resourceType": "Patient" - } - } - } - } - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/dev-smart-app-read" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "dev-smart-app-read", - "type": "rest", - "engine": "matcho", - "matcho": { - "client": { - "type": "smart-app" - }, - "operation": { - "id": { - "$one-of": [ - "FhirSearch", - "FhirRead" - ] - } - } - } - } - }, - { - "resource": { - "resourceType": "AccessPolicy", - "id": "developer-api", - "engine": "matcho", - "description": "Scoped access for developer API client (sessions, oauth state, orgs, users)", - "matcho": { - "client": { - "id": "developer-api" - }, - "$one-of": [ - { - "params": { - "resource/type": "Session" - }, - "operation": { - "id": { - "$one-of": [ - "FhirCreate", - "FhirSearch", - "FhirRead", - "FhirUpdate", - "FhirPatch" - ] - } - } - }, - { - "params": { - "resource/type": "Organization" - }, - "operation": { - "id": { - "$one-of": [ - "FhirSearch", - "FhirRead" - ] - } - } - }, - { - "params": { - "resource/type": "User" - }, - "operation": { - "id": "FhirRead" - } - } - ] - } - }, - "request": { - "method": "PUT", - "url": "AccessPolicy/developer-api" - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/dev-client-search-own" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "dev-client-search-own", - "engine": "matcho", - "description": "Developers can search only their own Client apps", - "matcho": { - "user": { - "roles": { - "$contains": { - "type": "developer" - } - } - }, - "operation": { - "id": "FhirSearch" - }, - "params": { - "resource/type": "Client", - "created-by": ".user.id" - } - } - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/dev-client-read-write-own" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "dev-client-read-write-own", - "engine": "sql", - "sql": { - "query": "select true from client where id = {{params.resource/id}}\n and exists (\n select 1\n from jsonb_array_elements(resource->'meta'->'extension') ext\nwhere ext->>'url' = 'http://aidbox.app/StructureDefinition/Client/created-by'\n and ext #>> '{value,Reference,id}' = {{user.id}});" - } - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/developer-user-read-self" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "developer-user-read-self", - "engine": "matcho", - "description": "Developers can read only their own User resource", - "matcho": { - "user": { - "roles": { - "$contains": { - "type": "developer" - } - } - }, - "operation": { - "id": "FhirRead" - }, - "params": { - "resource/type": "User", - "resource/id": ".user.id" - } - } - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/admin-review-sandbox-clients" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "admin-review-sandbox-clients", - "engine": "matcho", - "description": "Admins can review sandbox Clients (read/search only)", - "matcho": { - "user": { - "roles": { - "$contains": { - "type": "admin" - } - } - }, - "params": { - "resource/type": "Client" - }, - "operation": { - "id": { - "$one-of": [ - "FhirSearch", - "FhirRead" - ] - } - } - } - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/admin-role-access" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "admin-role-access", - "description": "Role-based access policy for administrators - allows full system access", - "engine": "matcho", - "matcho": { - "user": { - "roles": { - "contains": { - "type": "admin" - } - } - } - } - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/patient-role-access" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "patient-role-access", - "description": "Role-based access policy for patients - allows access to own data and consent management", - "engine": "matcho", - "matcho": { - "user": { - "roles": { - "contains": { - "type": "patient" - } - } - }, - "operation": { - "id": { - "$one-of": [ - "FhirSearch", - "FhirRead", - "FhirPatch", - "FhirCreate", - "FhirUpdate" - ] - } - }, - "$or": [ - { - "params": { - "resource/type": "Patient" - }, - "operation": { - "id": { - "$one-of": [ - "FhirRead" - ] - } - } - }, - { - "params": { - "resource/type": "Consent" - }, - "operation": { - "id": { - "$one-of": [ - "FhirSearch", - "FhirRead", - "FhirPatch", - "FhirCreate", - "FhirUpdate" - ] - } - } - } - ] - } - } - }, - { - "_comment": "Email template for developer signup - UPDATE redirect URL with your domain", - "request": { - "method": "PUT", - "url": "NotificationTemplate/auth-signup-email" - }, - "resource": { - "resourceType": "NotificationTemplate", - "template": "\n

Confirm your email address

\n

Hello {{params.data.name}}{{params.email}}{{params.username}}!

\n

We just need to verify that {{params.email}} {{params.username}} is your email address.

\n

Confirm Email Address

\nDidn't request this email?\n

No worries! Your address may have been entered by mistake. If you ignore or delete this email, nothing further will happen.

\n

If you're having problems, please feel free to contact us. We'll be glad to help.

" - } - }, - { - "_comment": "Token introspector for cross-Aidbox authentication - UPDATE with your admin Aidbox domain", - "request": { - "method": "PUT", - "url": "TokenIntrospector/admin-aidbox" - }, - "resource": { - "resourceType": "TokenIntrospector", - "id": "admin-aidbox", - "type": "jwt", - "jwt": { - "iss": "https://aidbox.yourdomain.com" - }, - "jwks_uri": "https://aidbox.yourdomain.com/.well-known/jwks.json" - } - }, - { - "_comment": "Allow requests from admin Aidbox - UPDATE issuer with your admin Aidbox domain", - "request": { - "method": "PUT", - "url": "AccessPolicy/allow-admin-tokens" - }, - "resource": { - "resourceType": "AccessPolicy", - "id": "allow-admin-tokens", - "description": "Allow requests authenticated with Admin Aidbox JWT tokens", - "engine": "json-schema", - "schema": { - "required": [ - "jwt" - ], - "properties": { - "jwt": { - "required": [ - "iss" - ], - "properties": { - "iss": { - "constant": "https://aidbox.yourdomain.com" - } - } - } - } - } - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/dev-client-search-communication" - }, - "resource": { - "engine": "matcho", - "matcho": { - "user": { - "roles": { - "$contains": { - "type": "developer" - } - } - }, - "params": { - "resource/type": "Communication", - ".about.0.reference": "present?" - }, - "operation": { - "id": "FhirSearch" - } - }, - "description": "Developers can search communication for certain app", - "id": "dev-client-search-communication", - "resourceType": "AccessPolicy" - } - }, - { - "request": { - "method": "PUT", - "url": "AccessPolicy/smart-app-can-read-patient-api" - }, - "resource": { - "engine": "matcho", - "matcho": { - "jwt": { - "atv": 2, - "scope": "present?", - "context": { - "patient": "present?" - } - }, - "client": { - "type": "smart-app", - "active": true - } - }, - "id": "smart-app-can-read-patient-api", - "resourceType": "AccessPolicy" - } - } - ] -} - diff --git a/smartbox/aidbox/sandbox/kustomization.yaml b/smartbox/aidbox/sandbox/kustomization.yaml deleted file mode 100644 index dc33801..0000000 --- a/smartbox/aidbox/sandbox/kustomization.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: aidbox-sandbox - -resources: - - namespace.yaml - - aidbox.yaml - -generatorOptions: - disableNameSuffixHash: true - -configMapGenerator: - - name: init-bundle - files: - - init-bundle.json - diff --git a/smartbox/aidbox/sandbox/namespace.yaml b/smartbox/aidbox/sandbox/namespace.yaml deleted file mode 100644 index 2f5b82f..0000000 --- a/smartbox/aidbox/sandbox/namespace.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: aidbox-sandbox - labels: - app: aidbox - environment: production - component: sandbox - diff --git a/smartbox/database/configmap.yaml b/smartbox/database/configmap.yaml deleted file mode 100644 index 7c76f9b..0000000 --- a/smartbox/database/configmap.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: aidboxdb-envs -data: - POSTGRES_DB: postgres - PGDATA: /data/pg ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: aidboxdb-config -data: - postgres.conf: |- - listen_addresses = '*' - max_replication_slots = 30 - max_wal_senders = 30 - max_wal_size = '1GB' - max_worker_processes = 128 - pg_stat_statements.max = 500 - pg_stat_statements.save = false - pg_stat_statements.track = top - pg_stat_statements.track_utility = true - shared_buffers = '1GB' - shared_preload_libraries = 'pg_stat_statements' - synchronous_commit = off - track_io_timing = on - wal_level = logical - wal_log_hints = on - diff --git a/smartbox/database/kustomization.yaml b/smartbox/database/kustomization.yaml deleted file mode 100644 index 756ce2b..0000000 --- a/smartbox/database/kustomization.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: aidbox-db - -resources: - - namespace.yaml - - configmap.yaml - - pvc.yaml - - statefulset.yaml - - service.yaml - diff --git a/smartbox/database/namespace.yaml b/smartbox/database/namespace.yaml deleted file mode 100644 index a0d0c5a..0000000 --- a/smartbox/database/namespace.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: aidbox-db - labels: - app: aidboxdb - component: database - diff --git a/smartbox/database/pvc.yaml b/smartbox/database/pvc.yaml deleted file mode 100644 index da03b79..0000000 --- a/smartbox/database/pvc.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: aidboxdb-data -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - # Adjust storage size based on your needs - storage: 50Gi - # Uncomment and set if you need a specific storage class - # storageClassName: standard - diff --git a/smartbox/database/service.yaml b/smartbox/database/service.yaml deleted file mode 100644 index f7eae25..0000000 --- a/smartbox/database/service.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: aidboxdb -spec: - selector: - service: aidboxdb - ports: - - protocol: TCP - port: 5432 - targetPort: 5432 - diff --git a/smartbox/database/statefulset.yaml b/smartbox/database/statefulset.yaml deleted file mode 100644 index b082f45..0000000 --- a/smartbox/database/statefulset.yaml +++ /dev/null @@ -1,62 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: aidboxdb -spec: - replicas: 1 - selector: - matchLabels: - service: aidboxdb - serviceName: aidboxdb - template: - metadata: - labels: - service: aidboxdb - spec: - containers: - - name: main - image: healthsamurai/aidboxdb:16.1 - imagePullPolicy: Always - args: - - --config_file=/etc/configs/postgres.conf - ports: - - containerPort: 5432 - protocol: TCP - envFrom: - - configMapRef: - name: aidboxdb-envs - - secretRef: - name: aidboxdb-secret - volumeMounts: - - name: db-data - mountPath: /data - subPath: pg - - name: aidboxdb-config - mountPath: /etc/configs - - name: db-dshm - mountPath: /dev/shm - readinessProbe: - exec: - command: - - bash - - -c - - psql -c 'SELECT 1' - initialDelaySeconds: 10 - timeoutSeconds: 2 - resources: - requests: - memory: 1Gi - cpu: 500m - limits: - memory: 2Gi - volumes: - - name: db-data - persistentVolumeClaim: - claimName: aidboxdb-data - - name: aidboxdb-config - configMap: - name: aidboxdb-config - - name: db-dshm - emptyDir: - medium: Memory - diff --git a/smartbox/fhir-app-portal/configmap.yaml b/smartbox/fhir-app-portal/configmap.yaml deleted file mode 100644 index 445c34a..0000000 --- a/smartbox/fhir-app-portal/configmap.yaml +++ /dev/null @@ -1,58 +0,0 @@ -# ============================================================================= -# FHIR App Portal Configuration -# ============================================================================= -# Update all URLs with your actual domain names before deploying. -# ============================================================================= - -apiVersion: v1 -kind: ConfigMap -metadata: - name: fhir-app-portal-config -data: - # ============================================================================ - # Backend Service Configuration - # ============================================================================ - NODE_ENV: "production" - PORTAL_BACKEND_PORT: "8081" - DEPLOYMENT_MODE: "prod" - - # CORS Configuration - # IMPORTANT: Update with your actual portal domains - CORS_ORIGINS: "https://portal.yourdomain.com,https://portal-sandbox.yourdomain.com" - - # ============================================================================ - # Frontend URLs (for OAuth redirect URI generation) - # ============================================================================ - # IMPORTANT: Update with your actual portal domains - ADMIN_FRONTEND_URL: "https://portal.yourdomain.com" - DEVELOPER_FRONTEND_URL: "https://portal-sandbox.yourdomain.com" - - # ============================================================================ - # Aidbox Instance Configuration (Internal K8s URLs) - # ============================================================================ - # These are internal Kubernetes service URLs - update namespace if needed - AIDBOX_DEV_URL: "http://aidbox-api.aidbox-sandbox.svc" - AIDBOX_DEV_CLIENT_ID: "smartbox-developer-portal" - AIDBOX_ADMIN_URL: "http://aidbox-api.aidbox-portal.svc" - AIDBOX_ADMIN_CLIENT_ID: "smartbox-admin-portal" - - # API Client IDs (non-sensitive) - ADMIN_API_CLIENT_ID: "admin-api" - DEVELOPER_API_CLIENT_ID: "developer-api" - - # ============================================================================ - # Aidbox Public URLs (for OAuth browser redirects) - # ============================================================================ - # IMPORTANT: Update with your actual Aidbox domains - AIDBOX_ADMIN_PUBLIC_URL: "https://aidbox.yourdomain.com" - AIDBOX_DEV_PUBLIC_URL: "https://aidbox-sandbox.yourdomain.com" - - # ============================================================================ - # Frontend Runtime Configuration - # ============================================================================ - # These variables are used by generate-runtime-config.sh - # to inject VITE_AIDBOX_URL into the built frontend at runtime - # IMPORTANT: Update with your actual Aidbox domains - ADMIN_AIDBOX_PUBLIC_URL: "https://aidbox.yourdomain.com" - DEVELOPER_AIDBOX_PUBLIC_URL: "https://aidbox-sandbox.yourdomain.com" - diff --git a/smartbox/fhir-app-portal/deployment.yaml b/smartbox/fhir-app-portal/deployment.yaml deleted file mode 100644 index b8edf36..0000000 --- a/smartbox/fhir-app-portal/deployment.yaml +++ /dev/null @@ -1,104 +0,0 @@ -# ============================================================================= -# FHIR App Portal Deployment -# ============================================================================= -# This deployment serves both the Admin Portal and Developer Portal frontends. -# ============================================================================= - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: fhir-app-portal - labels: - app: fhir-app-portal - version: v1 -spec: - replicas: 1 - selector: - matchLabels: - app: fhir-app-portal - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 1 - maxUnavailable: 0 - template: - metadata: - labels: - app: fhir-app-portal - version: v1 - spec: - containers: - - name: fhir-app-portal - image: healthsamurai/fhir-app-portal:edge - imagePullPolicy: Always - ports: - - name: portal - containerPort: 8095 - protocol: TCP - - name: dev-portal - containerPort: 8096 - protocol: TCP - - name: backend - containerPort: 8081 - protocol: TCP - - # ====================================================================== - # Environment Variables - # ====================================================================== - # All non-sensitive config loaded from ConfigMap - # All sensitive config loaded from Secret - envFrom: - - configMapRef: - name: fhir-app-portal-config - - secretRef: - name: fhir-app-portal--session-secret - - # ====================================================================== - # Health Probes - # ====================================================================== - livenessProbe: - httpGet: - path: /health - port: 8095 - scheme: HTTP - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - successThreshold: 1 - failureThreshold: 3 - - readinessProbe: - httpGet: - path: /health - port: 8095 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 5 - timeoutSeconds: 3 - successThreshold: 1 - failureThreshold: 3 - - # ====================================================================== - # Resource Limits (adjust based on your needs) - # ====================================================================== - resources: - limits: - memory: 512Mi - requests: - cpu: 100m - memory: 256Mi - - # ====================================================================== - # Security Context Configuration - # ====================================================================== - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - add: - - NET_BIND_SERVICE # Required to bind to ports 80/81 - - restartPolicy: Always - terminationGracePeriodSeconds: 30 - diff --git a/smartbox/fhir-app-portal/ingress.yaml b/smartbox/fhir-app-portal/ingress.yaml deleted file mode 100644 index 7140e33..0000000 --- a/smartbox/fhir-app-portal/ingress.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# ============================================================================= -# FHIR App Portal Ingress -# ============================================================================= -# Routes traffic to Admin Portal and Developer Portal. -# IMPORTANT: Update all hostnames with your actual domains. -# ============================================================================= - -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: fhir-app-portal-ingress - annotations: - # ========================================================================= - # SSL/TLS Configuration - # ========================================================================= - # Use cert-manager to automatically provision SSL certificates - cert-manager.io/cluster-issuer: "letsencrypt" - - # Force HTTPS redirects - nginx.ingress.kubernetes.io/ssl-redirect: "true" - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" - - # ========================================================================= - # Host Header Configuration (CRITICAL for domain routing) - # ========================================================================= - # Preserve the original Host header so nginx inside pod can route correctly - nginx.ingress.kubernetes.io/upstream-vhost: "$host" - nginx.ingress.kubernetes.io/preserve-host: "true" - - # ========================================================================= - # CORS Configuration - # ========================================================================= - # Enable Cross-Origin Resource Sharing - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS" - nginx.ingress.kubernetes.io/cors-allow-origin: "*" - - # ========================================================================= - # Performance & Compression - # ========================================================================= - # Enable gzip compression - nginx.ingress.kubernetes.io/enable-gzip: "true" - - # ========================================================================= - # Rate Limiting - # ========================================================================= - # Limit to 100 requests per second per IP - nginx.ingress.kubernetes.io/limit-rps: "100" - - # ========================================================================= - # Timeout Configuration - # ========================================================================= - # Increase timeouts for long-running requests - nginx.ingress.kubernetes.io/proxy-connect-timeout: "300" - nginx.ingress.kubernetes.io/proxy-send-timeout: "300" - nginx.ingress.kubernetes.io/proxy-read-timeout: "300" - -spec: - ingressClassName: nginx - - # =========================================================================== - # TLS Configuration - # =========================================================================== - # cert-manager will automatically create and manage certificates - # IMPORTANT: Update hostnames with your actual domains - tls: - - hosts: - - portal.yourdomain.com - - portal-sandbox.yourdomain.com - secretName: fhir-portal-tls # Auto-created by cert-manager - - # =========================================================================== - # Routing Rules - # =========================================================================== - # IMPORTANT: Update hostnames with your actual domains - rules: - # Admin Portal - - host: portal.yourdomain.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: fhir-app-portal - port: - number: 8095 - - # Developer Portal (Sandbox) - - host: portal-sandbox.yourdomain.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: fhir-app-portal - port: - number: 8096 - diff --git a/smartbox/fhir-app-portal/kustomization.yaml b/smartbox/fhir-app-portal/kustomization.yaml deleted file mode 100644 index af3e968..0000000 --- a/smartbox/fhir-app-portal/kustomization.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: fhir-app-portal - -resources: - - namespace.yaml - - configmap.yaml - - deployment.yaml - - service.yaml - - ingress.yaml - diff --git a/smartbox/fhir-app-portal/namespace.yaml b/smartbox/fhir-app-portal/namespace.yaml deleted file mode 100644 index 7188567..0000000 --- a/smartbox/fhir-app-portal/namespace.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: fhir-app-portal - labels: - app: fhir-app-portal - environment: production - diff --git a/smartbox/fhir-app-portal/service.yaml b/smartbox/fhir-app-portal/service.yaml deleted file mode 100644 index 081f966..0000000 --- a/smartbox/fhir-app-portal/service.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# ============================================================================= -# FHIR App Portal Service -# ============================================================================= -# Exposes the Admin Portal (8095) and Developer Portal (8096) ports. -# ============================================================================= - -apiVersion: v1 -kind: Service -metadata: - name: fhir-app-portal - labels: - app: fhir-app-portal -spec: - type: ClusterIP - selector: - app: fhir-app-portal - ports: - - name: portal - protocol: TCP - port: 8095 - targetPort: 8095 - - name: developer-portal - protocol: TCP - port: 8096 - targetPort: 8096 - - # Session affinity for consistent user sessions - sessionAffinity: ClientIP - sessionAffinityConfig: - clientIP: - timeoutSeconds: 10800 - diff --git a/smartbox/secrets/secrets-template.env b/smartbox/secrets/secrets-template.env deleted file mode 100644 index bc79bd1..0000000 --- a/smartbox/secrets/secrets-template.env +++ /dev/null @@ -1,41 +0,0 @@ -# ============================================================================= -# Secrets Template for FHIR App Portal Deployment -# ============================================================================= -# This file is a reference for required secrets. -# Create secrets using kubectl commands as shown in README.md -# ============================================================================= - -# ============================================================================= -# Database (aidbox-db namespace) -# ============================================================================= -POSTGRES_USER=postgres -POSTGRES_PASSWORD= - -# ============================================================================= -# Aidbox License (both aidbox-portal and aidbox-sandbox namespaces) -# ============================================================================= -# Contact Aidbox (https://aidbox.app) to obtain a license key -AIDBOX_LICENSE= - -# ============================================================================= -# Aidbox Security (both aidbox-portal and aidbox-sandbox namespaces) -# ============================================================================= -# JWT signing secret - generate with: openssl rand -hex 32 -BOX_AUTH_KEYS_SECRET= - -# Admin user password -BOX_ADMIN_PASSWORD= - -# Database credentials (should match POSTGRES_USER/POSTGRES_PASSWORD above) -BOX_DB_USER=postgres -BOX_DB_PASSWORD= - -# ============================================================================= -# FHIR App Portal (fhir-app-portal namespace) -# ============================================================================= -# Session secret - generate with: openssl rand -hex 32 -SESSION_SECRET= - -# API Client Secrets (for OAuth between portal and Aidbox) -ADMIN_API_CLIENT_SECRET= -DEVELOPER_API_CLIENT_SECRET= diff --git a/smartbox/templates/NOTES.txt b/smartbox/templates/NOTES.txt new file mode 100644 index 0000000..5074d8e --- /dev/null +++ b/smartbox/templates/NOTES.txt @@ -0,0 +1,82 @@ +Thank you for installing {{ .Chart.Name }}! + +Your release is named: {{ .Release.Name }} + +============================================================================= +FHIR App Portal +============================================================================= +{{- if .Values.ingress.enabled }} +Access the portals at: + - Admin Portal: https://{{ .Values.domain.adminPortal }} + - Developer Portal: https://{{ .Values.domain.developerPortal }} +{{- else }} +To access the FHIR App Portal, run: + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "smartbox.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8095:8095 8096:8096 + +Then access: + - Admin Portal: http://localhost:8095 + - Developer Portal: http://localhost:8096 +{{- end }} + +{{- if .Values.database.enabled }} +============================================================================= +Database (AidboxDB) +============================================================================= +Database service: {{ include "smartbox.database.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.database.service.port }} + +To connect to the database: + kubectl exec -it -n {{ .Release.Namespace }} statefulset/{{ include "smartbox.database.fullname" . }} -- psql -U postgres +{{- end }} + +============================================================================= +Aidbox Instances +============================================================================= +{{- if .Values.aidboxPortal.enabled }} +Aidbox Portal init bundle ConfigMap: {{ include "smartbox.fullname" . }}-portal-init-bundle +{{- end }} +{{- if .Values.aidboxSandbox.enabled }} +Aidbox Sandbox init bundle ConfigMap: {{ include "smartbox.fullname" . }}-sandbox-init-bundle +{{- end }} + +To deploy Aidbox instances, you have two options: + +Option A: Using Flux CD + Apply the HelmRelease resources from the generated init bundle ConfigMaps + with your Aidbox Helm values. + +Option B: Using Helm CLI + 1. Add the Aidbox Helm repository: + helm repo add aidbox https://aidbox.github.io/helm-charts + + 2. Create init-bundle ConfigMaps in Aidbox namespaces: + kubectl create namespace aidbox-portal + kubectl create configmap init-bundle -n aidbox-portal \ + --from-file=init-bundle.json=<(kubectl get cm {{ include "smartbox.fullname" . }}-portal-init-bundle -n {{ .Release.Namespace }} -o jsonpath='{.data.init-bundle\.json}') + + 3. Deploy Aidbox with Helm: + helm install aidbox aidbox/aidbox -n aidbox-portal \ + --set host={{ .Values.domain.adminAidbox }} \ + --set protocol=https \ + --set config.BOX_DB_HOST={{ include "smartbox.database.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local \ + ... (see values.yaml for full configuration) + +============================================================================= +Required Secrets +============================================================================= +Make sure the following secrets are created: + +1. Portal secrets (created by this chart): + - {{ include "smartbox.fullname" . }} (SESSION_SECRET, ADMIN_API_CLIENT_SECRET, DEVELOPER_API_CLIENT_SECRET) + +{{- if .Values.database.enabled }} +2. Database secrets (created by this chart): + - {{ include "smartbox.database.fullname" . }} (POSTGRES_USER, POSTGRES_PASSWORD) +{{- end }} + +3. Aidbox secrets (create manually in Aidbox namespaces): + - aidbox-secrets: AIDBOX_LICENSE, BOX_AUTH_KEYS_SECRET, BOX_ADMIN_PASSWORD + - postgres-secrets: BOX_DB_USER, BOX_DB_PASSWORD + +For more information, visit: https://docs.aidbox.app + diff --git a/smartbox/templates/_helpers.tpl b/smartbox/templates/_helpers.tpl new file mode 100644 index 0000000..c7416fe --- /dev/null +++ b/smartbox/templates/_helpers.tpl @@ -0,0 +1,85 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "smartbox.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "smartbox.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "smartbox.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "smartbox.labels" -}} +helm.sh/chart: {{ include "smartbox.chart" . }} +{{ include "smartbox.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "smartbox.selectorLabels" -}} +app.kubernetes.io/name: {{ include "smartbox.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "smartbox.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "smartbox.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Database fullname +*/}} +{{- define "smartbox.database.fullname" -}} +{{- printf "%s-db" (include "smartbox.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Database selector labels +*/}} +{{- define "smartbox.database.selectorLabels" -}} +app.kubernetes.io/name: {{ include "smartbox.name" . }}-db +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Portal fullname +*/}} +{{- define "smartbox.portal.fullname" -}} +{{- printf "%s-portal" (include "smartbox.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + diff --git a/smartbox/templates/aidbox-portal-configmap.yaml b/smartbox/templates/aidbox-portal-configmap.yaml new file mode 100644 index 0000000..18e7e4f --- /dev/null +++ b/smartbox/templates/aidbox-portal-configmap.yaml @@ -0,0 +1,287 @@ +{{- if .Values.aidboxPortal.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "smartbox.fullname" . }}-portal-init-bundle + labels: + {{- include "smartbox.labels" . | nindent 4 }} + app.kubernetes.io/component: aidbox-portal +data: + init-bundle.json: | + { + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "hl7.fhir.us.core#6.1.0": "https://packages.simplifier.net/hl7.fhir.us.core/6.1.0", + "us-core-extensions.legacy.aidbox#0.0.0": "https://storage.googleapis.com/aidbox-public/smartbox/us-core-extensions.legacy.aidbox.tar.gz" + }, + "request": { + "method": "POST", + "url": "/$upload-fhir-npm-packages" + } + }, + { + "resource": { + "resourceType": "FHIRSchema", + "url": "http://aidbox.app/StructureDefinition/Client/created-by", + "id": "client-created-by", + "base": "Extension", + "name": "client-created-by", + "kind": "complex-type", + "type": "Extension", + "version": "0.0.1", + "elements": { + "url": { + "fixed": "http://aidbox.app/StructureDefinition/Client/created-by" + }, + "value": { + "choices": ["valueReference"] + }, + "valueReference": { + "type": "Reference", + "refers": ["User"], + "choiceOf": "value" + } + }, + "derivation": "constraint" + }, + "request": { + "method": "PUT", + "url": "FHIRSchema/client-created-by" + } + }, + { + "resource": { + "resourceType": "SearchParameter", + "id": "Client.created-by", + "url": "http://aidbox.app/StructureDefinition/Client/created-by", + "name": "created-by", + "status": "active", + "code": "created-by", + "base": ["Client"], + "type": "token", + "description": "Client created-by User", + "expression": "Client.meta.extension.where(url='http://aidbox.app/StructureDefinition/Client/created-by').value.Reference.id" + }, + "request": { + "method": "PUT", + "url": "SearchParameter/Client.created-by" + } + }, + { + "resource": { + "resourceType": "FHIRSchema", + "url": "http://aidbox.app/StructureDefinition/Client/status", + "id": "client-status", + "base": "Extension", + "name": "client-status", + "kind": "complex-type", + "type": "Extension", + "version": "0.0.2", + "elements": { + "url": { + "fixed": "http://aidbox.app/StructureDefinition/Client/status" + }, + "value": { + "choices": ["valueCode"] + }, + "valueCode": { + "type": "code", + "choiceOf": "value", + "constraints": { + "enum-client-status": { + "severity": "error", + "expression": "%context.subsetOf('draft' | 'review' | 'active' | 'rejected')" + } + } + } + }, + "derivation": "constraint" + }, + "request": { + "method": "PUT", + "url": "FHIRSchema/client-status" + } + }, + { + "resource": { + "resourceType": "SearchParameter", + "id": "Client.status", + "url": "http://aidbox.app/StructureDefinition/Client/status", + "name": "status", + "status": "active", + "code": "status", + "base": ["Client"], + "type": "token", + "description": "Client status (draft, review, active, rejected)", + "expression": "Client.meta.extension.where(url='http://aidbox.app/StructureDefinition/Client/status').value.code" + }, + "request": { + "method": "PUT", + "url": "SearchParameter/Client.status" + } + }, + { + "_comment": "OAuth Client for Admin Portal", + "resource": { + "resourceType": "Client", + "id": "smartbox-admin-portal", + "first_party": true, + "grant_types": ["code"], + "scope": ["openid", "profile", "email"], + "auth": { + "authorization_code": { + "redirect_uri": "https://{{ .Values.domain.adminPortal }}/api/admin/auth/callback", + "access_token_expiration": 3600, + "token_format": "jwt", + "pkce": true, + "refresh_token": true, + "refresh_token_expiration": 86400 + } + } + }, + "request": { + "method": "PUT", + "url": "Client/smartbox-admin-portal" + } + }, + { + "resource": { + "resourceType": "AccessPolicy", + "id": "admin-api", + "engine": "matcho", + "description": "Scoped access for admin API client (sessions, orgs, clients, users, tos/privacy)", + "matcho": { + "client": {"id": "admin-api"}, + "$one-of": [ + { + "params": {"resource/type": "Session"}, + "operation": {"id": {"$one-of": ["FhirCreate", "FhirSearch", "FhirRead", "FhirUpdate", "FhirPatch"]}} + }, + { + "params": {"resource/type": "Organization"}, + "operation": {"id": {"$one-of": ["FhirSearch", "FhirRead"]}} + }, + { + "params": {"resource/type": "Client"}, + "operation": {"id": {"$one-of": ["FhirSearch", "FhirRead"]}} + }, + { + "params": {"resource/type": "User"}, + "operation": {"id": "FhirRead"} + }, + { + "params": { + "resource/type": "DocumentReference", + "resource/id": {"$one-of": ["smartbox-tos", "smartbox-privacy"]} + }, + "operation": {"id": {"$one-of": ["FhirRead", "FhirUpdate", "FhirCreate"]}} + } + ] + } + }, + "request": { + "method": "PUT", + "url": "AccessPolicy/admin-api" + } + }, + { + "resource": { + "resourceType": "SearchParameter", + "id": "Communication.about", + "url": "http://smartbox.hs/sp/Communication-about", + "name": "communication-about", + "status": "active", + "code": "about", + "base": ["Communication"], + "type": "reference", + "description": "Search Communication by Communication.about (any reference, incl. Aidbox system resources)", + "expression": "Communication.about" + }, + "request": { + "method": "PUT", + "url": "SearchParameter/Communication.about" + } + }, + { + "resource": { + "resourceType": "SearchParameter", + "id": "Session.client", + "url": "http://smartbox.hs/sp/Session-client", + "name": "session-client", + "status": "active", + "code": "client", + "base": ["Session"], + "type": "reference", + "description": "Search Session by client reference", + "expression": "Session.client", + "target": ["Client"] + }, + "request": { + "method": "PUT", + "url": "SearchParameter/Session.client" + } + }, + { + "resource": { + "roles": [{"type": "admin"}] + }, + "request": { + "method": "PATCH", + "url": "User/admin" + } + }, + { + "resource": { + "resourceType": "AccessPolicy", + "id": "allow-read-settings", + "engine": "matcho", + "description": "Allow admin-api client to read Aidbox settings", + "matcho": { + "client": {"id": "admin-api"}, + "uri": {"$one-of": ["/api/v1/settings/introspect", "^/api/v1/settings/.*"]} + } + }, + "request": { + "method": "PUT", + "url": "AccessPolicy/allow-read-settings" + } + }, + { + "request": { + "method": "PUT", + "url": "AccessPolicy/smart-app-can-read-patient-api" + }, + "resource": { + "engine": "matcho", + "matcho": { + "jwt": {"atv": 2, "scope": "present?", "context": {"patient": "present?"}}, + "client": {"type": "smart-app", "active": true} + }, + "id": "smart-app-can-read-patient-api", + "resourceType": "AccessPolicy" + } + }, + { + "request": { + "method": "PUT", + "url": "AccessPolicy/patient-can-get-launch-uri" + }, + "resource": { + "resourceType": "AccessPolicy", + "id": "patient-can-get-launch-uri", + "type": "rpc", + "engine": "matcho-rpc", + "rpc": { + "aidbox.smart/get-launch-uri": { + "user": {"fhirUser": {"resourceType": "Patient"}} + } + } + } + } + ] + } +{{- end }} + diff --git a/smartbox/templates/aidbox-sandbox-configmap.yaml b/smartbox/templates/aidbox-sandbox-configmap.yaml new file mode 100644 index 0000000..9ed4eae --- /dev/null +++ b/smartbox/templates/aidbox-sandbox-configmap.yaml @@ -0,0 +1,318 @@ +{{- if .Values.aidboxSandbox.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "smartbox.fullname" . }}-sandbox-init-bundle + labels: + {{- include "smartbox.labels" . | nindent 4 }} + app.kubernetes.io/component: aidbox-sandbox +data: + init-bundle.json: | + { + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "_comment": "Upload required FHIR packages", + "resource": { + "hl7.fhir.us.core#6.1.0": "https://packages.simplifier.net/hl7.fhir.us.core/6.1.0", + "us-core-extensions.legacy.aidbox#0.0.0": "https://storage.googleapis.com/aidbox-public/smartbox/us-core-extensions.legacy.aidbox.tar.gz" + }, + "request": { + "method": "POST", + "url": "/$upload-fhir-npm-packages" + } + }, + { + "_comment": "Load sample test data for developers", + "resource": { + "source": "https://storage.googleapis.com/aidbox-public/smartbox/all_data.ndjson.gz" + }, + "request": { + "method": "POST", + "url": "/$load" + } + }, + { + "resource": { + "resourceType": "FHIRSchema", + "url": "http://aidbox.app/StructureDefinition/Client/created-by", + "id": "client-created-by", + "base": "Extension", + "name": "client-created-by", + "kind": "complex-type", + "type": "Extension", + "version": "0.0.1", + "elements": { + "url": {"fixed": "http://aidbox.app/StructureDefinition/Client/created-by"}, + "value": {"choices": ["valueReference"]}, + "valueReference": {"type": "Reference", "refers": ["User"], "choiceOf": "value"} + }, + "derivation": "constraint" + }, + "request": {"method": "PUT", "url": "FHIRSchema/client-created-by"} + }, + { + "resource": { + "resourceType": "SearchParameter", + "id": "Client.created-by", + "url": "http://aidbox.app/StructureDefinition/Client/created-by", + "name": "created-by", + "status": "active", + "code": "created-by", + "base": ["Client"], + "type": "token", + "description": "Client created-by User", + "expression": "Client.meta.extension.where(url='http://aidbox.app/StructureDefinition/Client/created-by').value.Reference.id" + }, + "request": {"method": "PUT", "url": "SearchParameter/Client.created-by"} + }, + { + "resource": { + "resourceType": "FHIRSchema", + "url": "http://aidbox.app/StructureDefinition/Client/status", + "id": "client-status", + "base": "Extension", + "name": "client-status", + "kind": "complex-type", + "type": "Extension", + "version": "0.0.2", + "elements": { + "url": {"fixed": "http://aidbox.app/StructureDefinition/Client/status"}, + "value": {"choices": ["valueCode"]}, + "valueCode": { + "type": "code", + "choiceOf": "value", + "constraints": { + "enum-client-status": { + "severity": "error", + "expression": "%context.subsetOf('draft' | 'review' | 'active' | 'rejected')" + } + } + } + }, + "derivation": "constraint" + }, + "request": {"method": "PUT", "url": "FHIRSchema/client-status"} + }, + { + "resource": { + "resourceType": "SearchParameter", + "id": "Client.status", + "url": "http://aidbox.app/StructureDefinition/Client/status", + "name": "status", + "status": "active", + "code": "status", + "base": ["Client"], + "type": "token", + "description": "Client status (draft, review, active, rejected)", + "expression": "Client.meta.extension.where(url='http://aidbox.app/StructureDefinition/Client/status').value.code" + }, + "request": {"method": "PUT", "url": "SearchParameter/Client.status"} + }, + { + "_comment": "OAuth Client for Developer Portal", + "resource": { + "resourceType": "Client", + "id": "smartbox-developer-portal", + "first_party": true, + "grant_types": ["code"], + "scope": ["openid", "profile", "email"], + "auth": { + "authorization_code": { + "redirect_uri": "https://{{ .Values.domain.developerPortal }}/api/developer/auth/callback", + "access_token_expiration": 3600, + "token_format": "jwt", + "pkce": true, + "refresh_token": true, + "refresh_token_expiration": 86400 + } + } + }, + "request": {"method": "PUT", "url": "Client/smartbox-developer-portal"} + }, + { + "_comment": "Link admin user to test patient for sandbox testing", + "resource": {"fhirUser": {"reference": "Patient/test-pt-1"}}, + "request": {"method": "PATCH", "url": "User/admin"} + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/dev-can-get-launch-uri"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "dev-can-get-launch-uri", + "type": "rpc", + "engine": "matcho-rpc", + "rpc": {"aidbox.smart/get-launch-uri": {"user": {"fhirUser": {"resourceType": "Patient"}}}} + } + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/dev-smart-app-read"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "dev-smart-app-read", + "type": "rest", + "engine": "matcho", + "matcho": { + "client": {"type": "smart-app"}, + "operation": {"id": {"$one-of": ["FhirSearch", "FhirRead"]}} + } + } + }, + { + "resource": { + "resourceType": "AccessPolicy", + "id": "developer-api", + "engine": "matcho", + "description": "Scoped access for developer API client (sessions, oauth state, orgs, users)", + "matcho": { + "client": {"id": "developer-api"}, + "$one-of": [ + {"params": {"resource/type": "Session"}, "operation": {"id": {"$one-of": ["FhirCreate", "FhirSearch", "FhirRead", "FhirUpdate", "FhirPatch"]}}}, + {"params": {"resource/type": "Organization"}, "operation": {"id": {"$one-of": ["FhirSearch", "FhirRead"]}}}, + {"params": {"resource/type": "User"}, "operation": {"id": "FhirRead"}} + ] + } + }, + "request": {"method": "PUT", "url": "AccessPolicy/developer-api"} + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/dev-client-search-own"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "dev-client-search-own", + "engine": "matcho", + "description": "Developers can search only their own Client apps", + "matcho": { + "user": {"roles": {"$contains": {"type": "developer"}}}, + "operation": {"id": "FhirSearch"}, + "params": {"resource/type": "Client", "created-by": ".user.id"} + } + } + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/dev-client-read-write-own"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "dev-client-read-write-own", + "engine": "sql", + "sql": { + "query": "select true from client where id = {{ "{{" }}params.resource/id{{ "}}" }}\n and exists (\n select 1\n from jsonb_array_elements(resource->'meta'->'extension') ext\nwhere ext->>'url' = 'http://aidbox.app/StructureDefinition/Client/created-by'\n and ext #>> '{value,Reference,id}' = {{ "{{" }}user.id{{ "}}" }});" + } + } + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/developer-user-read-self"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "developer-user-read-self", + "engine": "matcho", + "description": "Developers can read only their own User resource", + "matcho": { + "user": {"roles": {"$contains": {"type": "developer"}}}, + "operation": {"id": "FhirRead"}, + "params": {"resource/type": "User", "resource/id": ".user.id"} + } + } + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/admin-review-sandbox-clients"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "admin-review-sandbox-clients", + "engine": "matcho", + "description": "Admins can review sandbox Clients (read/search only)", + "matcho": { + "user": {"roles": {"$contains": {"type": "admin"}}}, + "params": {"resource/type": "Client"}, + "operation": {"id": {"$one-of": ["FhirSearch", "FhirRead"]}} + } + } + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/admin-role-access"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "admin-role-access", + "description": "Role-based access policy for administrators - allows full system access", + "engine": "matcho", + "matcho": {"user": {"roles": {"contains": {"type": "admin"}}}} + } + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/patient-role-access"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "patient-role-access", + "description": "Role-based access policy for patients - allows access to own data and consent management", + "engine": "matcho", + "matcho": { + "user": {"roles": {"contains": {"type": "patient"}}}, + "operation": {"id": {"$one-of": ["FhirSearch", "FhirRead", "FhirPatch", "FhirCreate", "FhirUpdate"]}}, + "$or": [ + {"params": {"resource/type": "Patient"}, "operation": {"id": {"$one-of": ["FhirRead"]}}}, + {"params": {"resource/type": "Consent"}, "operation": {"id": {"$one-of": ["FhirSearch", "FhirRead", "FhirPatch", "FhirCreate", "FhirUpdate"]}}} + ] + } + } + }, + { + "_comment": "Token introspector for cross-Aidbox authentication", + "request": {"method": "PUT", "url": "TokenIntrospector/admin-aidbox"}, + "resource": { + "resourceType": "TokenIntrospector", + "id": "admin-aidbox", + "type": "jwt", + "jwt": {"iss": "https://{{ .Values.domain.adminAidbox }}"}, + "jwks_uri": "https://{{ .Values.domain.adminAidbox }}/.well-known/jwks.json" + } + }, + { + "_comment": "Allow requests from admin Aidbox", + "request": {"method": "PUT", "url": "AccessPolicy/allow-admin-tokens"}, + "resource": { + "resourceType": "AccessPolicy", + "id": "allow-admin-tokens", + "description": "Allow requests authenticated with Admin Aidbox JWT tokens", + "engine": "json-schema", + "schema": { + "required": ["jwt"], + "properties": { + "jwt": { + "required": ["iss"], + "properties": {"iss": {"constant": "https://{{ .Values.domain.adminAidbox }}"}} + } + } + } + } + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/dev-client-search-communication"}, + "resource": { + "engine": "matcho", + "matcho": { + "user": {"roles": {"$contains": {"type": "developer"}}}, + "params": {"resource/type": "Communication", ".about.0.reference": "present?"}, + "operation": {"id": "FhirSearch"} + }, + "description": "Developers can search communication for certain app", + "id": "dev-client-search-communication", + "resourceType": "AccessPolicy" + } + }, + { + "request": {"method": "PUT", "url": "AccessPolicy/smart-app-can-read-patient-api"}, + "resource": { + "engine": "matcho", + "matcho": { + "jwt": {"atv": 2, "scope": "present?", "context": {"patient": "present?"}}, + "client": {"type": "smart-app", "active": true} + }, + "id": "smart-app-can-read-patient-api", + "resourceType": "AccessPolicy" + } + } + ] + } +{{- end }} + diff --git a/smartbox/templates/configmap.yaml b/smartbox/templates/configmap.yaml new file mode 100644 index 0000000..47b52f6 --- /dev/null +++ b/smartbox/templates/configmap.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "smartbox.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} +data: + # Backend Service Configuration + NODE_ENV: {{ .Values.portal.config.NODE_ENV | quote }} + PORTAL_BACKEND_PORT: {{ .Values.portal.config.PORTAL_BACKEND_PORT | quote }} + DEPLOYMENT_MODE: {{ .Values.portal.config.DEPLOYMENT_MODE | quote }} + + # CORS Configuration + CORS_ORIGINS: "https://{{ .Values.domain.adminPortal }},https://{{ .Values.domain.developerPortal }}" + + # Frontend URLs (for OAuth redirect URI generation) + ADMIN_FRONTEND_URL: "https://{{ .Values.domain.adminPortal }}" + DEVELOPER_FRONTEND_URL: "https://{{ .Values.domain.developerPortal }}" + + # Aidbox Instance Configuration (Internal K8s URLs) + AIDBOX_DEV_URL: {{ .Values.portal.config.AIDBOX_DEV_URL | quote }} + AIDBOX_DEV_CLIENT_ID: {{ .Values.portal.config.AIDBOX_DEV_CLIENT_ID | quote }} + AIDBOX_ADMIN_URL: {{ .Values.portal.config.AIDBOX_ADMIN_URL | quote }} + AIDBOX_ADMIN_CLIENT_ID: {{ .Values.portal.config.AIDBOX_ADMIN_CLIENT_ID | quote }} + + # API Client IDs + ADMIN_API_CLIENT_ID: {{ .Values.portal.config.ADMIN_API_CLIENT_ID | quote }} + DEVELOPER_API_CLIENT_ID: {{ .Values.portal.config.DEVELOPER_API_CLIENT_ID | quote }} + + # Aidbox Public URLs (for OAuth browser redirects) + AIDBOX_ADMIN_PUBLIC_URL: "https://{{ .Values.domain.adminAidbox }}" + AIDBOX_DEV_PUBLIC_URL: "https://{{ .Values.domain.sandboxAidbox }}" + + # Frontend Runtime Configuration + ADMIN_AIDBOX_PUBLIC_URL: "https://{{ .Values.domain.adminAidbox }}" + DEVELOPER_AIDBOX_PUBLIC_URL: "https://{{ .Values.domain.sandboxAidbox }}" + diff --git a/smartbox/templates/database-configmap.yaml b/smartbox/templates/database-configmap.yaml new file mode 100644 index 0000000..b3c8db5 --- /dev/null +++ b/smartbox/templates/database-configmap.yaml @@ -0,0 +1,24 @@ +{{- if .Values.database.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "smartbox.database.fullname" . }}-envs + labels: + {{- include "smartbox.labels" . | nindent 4 }} + app.kubernetes.io/component: database +data: + POSTGRES_DB: {{ .Values.database.config.POSTGRES_DB | quote }} + PGDATA: {{ .Values.database.config.PGDATA | quote }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "smartbox.database.fullname" . }}-config + labels: + {{- include "smartbox.labels" . | nindent 4 }} + app.kubernetes.io/component: database +data: + postgres.conf: | + {{- .Values.database.postgresConfig | nindent 4 }} +{{- end }} + diff --git a/smartbox/templates/database-pvc.yaml b/smartbox/templates/database-pvc.yaml new file mode 100644 index 0000000..c2101ff --- /dev/null +++ b/smartbox/templates/database-pvc.yaml @@ -0,0 +1,19 @@ +{{- if and .Values.database.enabled .Values.database.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "smartbox.database.fullname" . }}-data + labels: + {{- include "smartbox.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + accessModes: + {{- toYaml .Values.database.persistence.accessModes | nindent 4 }} + resources: + requests: + storage: {{ .Values.database.persistence.size }} + {{- if .Values.database.persistence.storageClassName }} + storageClassName: {{ .Values.database.persistence.storageClassName | quote }} + {{- end }} +{{- end }} + diff --git a/smartbox/templates/database-secret.yaml b/smartbox/templates/database-secret.yaml new file mode 100644 index 0000000..88bad35 --- /dev/null +++ b/smartbox/templates/database-secret.yaml @@ -0,0 +1,14 @@ +{{- if .Values.database.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "smartbox.database.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} + app.kubernetes.io/component: database +type: Opaque +data: + POSTGRES_USER: {{ required "database.secrets.POSTGRES_USER is required" .Values.database.secrets.POSTGRES_USER | b64enc }} + POSTGRES_PASSWORD: {{ required "database.secrets.POSTGRES_PASSWORD is required" .Values.database.secrets.POSTGRES_PASSWORD | b64enc }} +{{- end }} + diff --git a/smartbox/templates/database-service.yaml b/smartbox/templates/database-service.yaml new file mode 100644 index 0000000..85066fd --- /dev/null +++ b/smartbox/templates/database-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.database.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "smartbox.database.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + selector: + {{- include "smartbox.database.selectorLabels" . | nindent 4 }} + ports: + - protocol: TCP + port: {{ .Values.database.service.port }} + targetPort: {{ .Values.database.service.port }} +{{- end }} + diff --git a/smartbox/templates/database-statefulset.yaml b/smartbox/templates/database-statefulset.yaml new file mode 100644 index 0000000..3cffe3b --- /dev/null +++ b/smartbox/templates/database-statefulset.yaml @@ -0,0 +1,69 @@ +{{- if .Values.database.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "smartbox.database.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + replicas: 1 + selector: + matchLabels: + {{- include "smartbox.database.selectorLabels" . | nindent 6 }} + serviceName: {{ include "smartbox.database.fullname" . }} + template: + metadata: + labels: + {{- include "smartbox.database.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: "{{ .Values.database.image.repository }}:{{ .Values.database.image.tag }}" + imagePullPolicy: {{ .Values.database.image.pullPolicy }} + args: + - --config_file=/etc/configs/postgres.conf + ports: + - containerPort: {{ .Values.database.service.port }} + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "smartbox.database.fullname" . }}-envs + - secretRef: + name: {{ include "smartbox.database.fullname" . }} + volumeMounts: + - name: db-data + mountPath: /data + subPath: pg + - name: db-config + mountPath: /etc/configs + - name: db-dshm + mountPath: /dev/shm + readinessProbe: + exec: + command: + - bash + - -c + - psql -c 'SELECT 1' + initialDelaySeconds: 10 + timeoutSeconds: 2 + resources: + {{- toYaml .Values.database.resources | nindent 12 }} + volumes: + {{- if .Values.database.persistence.enabled }} + - name: db-data + persistentVolumeClaim: + claimName: {{ include "smartbox.database.fullname" . }}-data + {{- else }} + - name: db-data + emptyDir: {} + {{- end }} + - name: db-config + configMap: + name: {{ include "smartbox.database.fullname" . }}-config + - name: db-dshm + emptyDir: + medium: Memory +{{- end }} + diff --git a/smartbox/templates/deployment.yaml b/smartbox/templates/deployment.yaml new file mode 100644 index 0000000..2a0148f --- /dev/null +++ b/smartbox/templates/deployment.yaml @@ -0,0 +1,95 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "smartbox.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.portal.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "smartbox.selectorLabels" . | nindent 6 }} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "smartbox.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.portal.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "smartbox.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.portal.securityContext | nindent 12 }} + image: "{{ .Values.portal.image.repository }}{{ if .Values.portal.image.digest }}@{{ .Values.portal.image.digest }}{{ else }}:{{ .Values.portal.image.tag | default .Chart.AppVersion }}{{ end }}" + imagePullPolicy: {{ .Values.portal.image.pullPolicy }} + ports: + - name: portal + containerPort: {{ .Values.portal.service.portalPort }} + protocol: TCP + - name: dev-portal + containerPort: {{ .Values.portal.service.developerPort }} + protocol: TCP + - name: backend + containerPort: 8081 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "smartbox.fullname" . }} + - secretRef: + name: {{ include "smartbox.fullname" . }} + {{- range .Values.extraEnvFromConfigMaps }} + - configMapRef: + name: {{ . }} + {{- end }} + {{- range .Values.extraEnvFromSecrets }} + - secretRef: + name: {{ . }} + {{- end }} + livenessProbe: + {{- toYaml .Values.portal.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.portal.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.portal.resources | nindent 12 }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: Always + terminationGracePeriodSeconds: 30 + diff --git a/smartbox/templates/hpa.yaml b/smartbox/templates/hpa.yaml new file mode 100644 index 0000000..36d350d --- /dev/null +++ b/smartbox/templates/hpa.yaml @@ -0,0 +1,33 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "smartbox.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "smartbox.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} + diff --git a/smartbox/templates/ingress.yaml b/smartbox/templates/ingress.yaml new file mode 100644 index 0000000..38d48d5 --- /dev/null +++ b/smartbox/templates/ingress.yaml @@ -0,0 +1,47 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "smartbox.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.domain.adminPortal | quote }} + - {{ .Values.domain.developerPortal | quote }} + secretName: {{ .Values.ingress.tls.secretName }} + {{- end }} + rules: + # Admin Portal + - host: {{ .Values.domain.adminPortal | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "smartbox.fullname" . }} + port: + number: {{ .Values.portal.service.portalPort }} + # Developer Portal (Sandbox) + - host: {{ .Values.domain.developerPortal | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "smartbox.fullname" . }} + port: + number: {{ .Values.portal.service.developerPort }} +{{- end }} + diff --git a/smartbox/templates/secrets.yaml b/smartbox/templates/secrets.yaml new file mode 100644 index 0000000..09d0751 --- /dev/null +++ b/smartbox/templates/secrets.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "smartbox.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} +type: Opaque +data: + # Session secret for cookie encryption + # Generate with: openssl rand -hex 32 + SESSION_SECRET: {{ required "portal.secrets.SESSION_SECRET is required" .Values.portal.secrets.SESSION_SECRET | b64enc }} + # API Client Secrets (for OAuth between portal and Aidbox) + ADMIN_API_CLIENT_SECRET: {{ required "portal.secrets.ADMIN_API_CLIENT_SECRET is required" .Values.portal.secrets.ADMIN_API_CLIENT_SECRET | b64enc }} + DEVELOPER_API_CLIENT_SECRET: {{ required "portal.secrets.DEVELOPER_API_CLIENT_SECRET is required" .Values.portal.secrets.DEVELOPER_API_CLIENT_SECRET | b64enc }} + diff --git a/smartbox/templates/service.yaml b/smartbox/templates/service.yaml new file mode 100644 index 0000000..165da7a --- /dev/null +++ b/smartbox/templates/service.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "smartbox.fullname" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} +spec: + type: {{ .Values.portal.service.type }} + selector: + {{- include "smartbox.selectorLabels" . | nindent 4 }} + ports: + - name: portal + protocol: TCP + port: {{ .Values.portal.service.portalPort }} + targetPort: portal + - name: developer-portal + protocol: TCP + port: {{ .Values.portal.service.developerPort }} + targetPort: dev-portal + {{- if .Values.portal.service.sessionAffinity.enabled }} + sessionAffinity: ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: {{ .Values.portal.service.sessionAffinity.timeoutSeconds }} + {{- end }} + diff --git a/smartbox/templates/serviceaccount.yaml b/smartbox/templates/serviceaccount.yaml new file mode 100644 index 0000000..d322d93 --- /dev/null +++ b/smartbox/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "smartbox.serviceAccountName" . }} + labels: + {{- include "smartbox.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} + diff --git a/smartbox/values.yaml b/smartbox/values.yaml new file mode 100644 index 0000000..4f72725 --- /dev/null +++ b/smartbox/values.yaml @@ -0,0 +1,336 @@ +# ============================================================================= +# Smartbox Helm Chart Values +# ============================================================================= +# FHIR App Portal solution with Aidbox backend +# ============================================================================= + +# -- Domain configuration (REQUIRED - update with your actual domains) +domain: + # Base domain for all services + baseDomain: "yourdomain.com" + # Admin portal hostname + adminPortal: "portal.yourdomain.com" + # Developer/sandbox portal hostname + developerPortal: "portal-sandbox.yourdomain.com" + # Admin Aidbox hostname + adminAidbox: "aidbox.yourdomain.com" + # Sandbox Aidbox hostname + sandboxAidbox: "aidbox-sandbox.yourdomain.com" + +# ============================================================================= +# FHIR App Portal Configuration +# ============================================================================= +portal: + replicaCount: 1 + + image: + repository: healthsamurai/fhir-app-portal + pullPolicy: Always + tag: "edge" + digest: "" + + imagePullSecrets: [] + + # -- Portal configuration + config: + NODE_ENV: "production" + PORTAL_BACKEND_PORT: "8081" + DEPLOYMENT_MODE: "prod" + # Aidbox internal K8s service URLs + AIDBOX_DEV_URL: "http://aidbox-api.aidbox-sandbox.svc" + AIDBOX_DEV_CLIENT_ID: "smartbox-developer-portal" + AIDBOX_ADMIN_URL: "http://aidbox-api.aidbox-portal.svc" + AIDBOX_ADMIN_CLIENT_ID: "smartbox-admin-portal" + # API Client IDs (non-sensitive) + ADMIN_API_CLIENT_ID: "admin-api" + DEVELOPER_API_CLIENT_ID: "developer-api" + + service: + type: ClusterIP + # Admin portal port + portalPort: 8095 + # Developer portal port + developerPort: 8096 + # Session affinity for consistent user sessions + sessionAffinity: + enabled: true + timeoutSeconds: 10800 + + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + memory: 512Mi + + livenessProbe: + httpGet: + path: /health + port: portal + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + readinessProbe: + httpGet: + path: /health + port: portal + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + + # -- Portal secrets (REQUIRED) + secrets: + # Session secret for cookie encryption - generate with: openssl rand -hex 32 + SESSION_SECRET: "" + # API Client Secrets (for OAuth between portal and Aidbox) + ADMIN_API_CLIENT_SECRET: "" + DEVELOPER_API_CLIENT_SECRET: "" + +# ============================================================================= +# Ingress Configuration +# ============================================================================= +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/upstream-vhost: "$host" + nginx.ingress.kubernetes.io/preserve-host: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS" + nginx.ingress.kubernetes.io/cors-allow-origin: "*" + nginx.ingress.kubernetes.io/enable-gzip: "true" + nginx.ingress.kubernetes.io/limit-rps: "100" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "300" + nginx.ingress.kubernetes.io/proxy-send-timeout: "300" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + tls: + enabled: true + secretName: fhir-portal-tls + +# ============================================================================= +# Database Configuration (AidboxDB) +# ============================================================================= +database: + enabled: true + + image: + repository: healthsamurai/aidboxdb + tag: "16.1" + pullPolicy: Always + + # -- PostgreSQL configuration + config: + POSTGRES_DB: postgres + PGDATA: /data/pg + + # -- PostgreSQL server configuration + postgresConfig: | + listen_addresses = '*' + max_replication_slots = 30 + max_wal_senders = 30 + max_wal_size = '1GB' + max_worker_processes = 128 + pg_stat_statements.max = 500 + pg_stat_statements.save = false + pg_stat_statements.track = top + pg_stat_statements.track_utility = true + shared_buffers = '1GB' + shared_preload_libraries = 'pg_stat_statements' + synchronous_commit = off + track_io_timing = on + wal_level = logical + wal_log_hints = on + + persistence: + enabled: true + size: 50Gi + # storageClassName: "" + accessModes: + - ReadWriteOnce + + service: + port: 5432 + + # -- Database secrets (REQUIRED if database.enabled) + secrets: + POSTGRES_USER: "" + POSTGRES_PASSWORD: "" + + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + memory: 2Gi + +# ============================================================================= +# Aidbox Portal Configuration +# ============================================================================= +aidboxPortal: + enabled: true + + # -- Aidbox image tag + imageTag: "2510" + + # -- Aidbox configuration + config: + BOX_ID: aidbox-portal + BOX_INSTANCE_NAME: aidbox-portal + BOX_WEB_PORT: 8080 + BOX_DB_PORT: 5432 + BOX_DB_DATABASE: portal + BOX_INIT_BUNDLE: file:///init-bundle/init-bundle.json + BOX_BOOTSTRAP_FHIR_PACKAGES: 'hl7.fhir.r4.core#4.0.1' + BOX_COMPATIBILITY_VALIDATION_JSON__SCHEMA_REGEX: '#{:fhir-datetime}' + BOX_FHIR_BUNDLE_EXECUTION_VALIDATION_MODE: limited + BOX_FHIR_COMPLIANT_MODE: true + BOX_FHIR_CORRECT_AIDBOX_FORMAT: true + BOX_FHIR_CREATEDAT_URL: https://aidbox.app/ex/createdAt + BOX_FHIR_SCHEMA_VALIDATION: true + BOX_FHIR_SEARCH_AUTHORIZE_INLINE_REQUESTS: false + BOX_FHIR_SEARCH_CHAIN_SUBSELECT: true + BOX_FHIR_SEARCH_COMPARISONS: true + BOX_FHIR_LEGACY_FCE_PACKAGE: us-core-extensions.legacy.aidbox#0.0.0 + BOX_FHIR_VALIDATION_SKIP_REFERENCE: true + BOX_MODULE_SDC_STRICT_ACCESS_CONTROL: true + BOX_SEARCH_INCLUDE_CONFORMANT: true + BOX_SECURITY_AUDIT_LOG_ENABLED: true + BOX_SECURITY_DEV_MODE: false + BOX_SETTINGS_MODE: read-write + BOX_SECURITY_ORGBAC_ENABLED: true + BOX_USAGE_STATS: false + BOX_OBSERVABILITY_STDOUT_PRETTY_LOG_LEVEL: info + BOX_METRICS_PORT: 8765 + + ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt + nginx.ingress.kubernetes.io/ssl-redirect: "true" + + resources: + requests: + cpu: 500m + memory: 2Gi + limits: + memory: 2Gi + + startupProbe: + initialDelaySeconds: 50 + periodSeconds: 10 + failureThreshold: 20 + + serviceMonitor: + enabled: false + +# ============================================================================= +# Aidbox Sandbox Configuration +# ============================================================================= +aidboxSandbox: + enabled: true + + # -- Aidbox image tag + imageTag: "2510" + + # -- Aidbox configuration + config: + BOX_ID: aidbox-sandbox + BOX_INSTANCE_NAME: aidbox-sandbox + BOX_WEB_PORT: 8080 + BOX_DB_PORT: 5432 + BOX_DB_DATABASE: sandbox + BOX_INIT_BUNDLE: file:///init-bundle/init-bundle.json + BOX_BOOTSTRAP_FHIR_PACKAGES: 'hl7.fhir.r4.core#4.0.1' + BOX_COMPATIBILITY_VALIDATION_JSON__SCHEMA_REGEX: '#{:fhir-datetime}' + BOX_FHIR_BUNDLE_EXECUTION_VALIDATION_MODE: limited + BOX_FHIR_COMPLIANT_MODE: true + BOX_FHIR_CORRECT_AIDBOX_FORMAT: true + BOX_FHIR_CREATEDAT_URL: https://aidbox.app/ex/createdAt + BOX_FHIR_SCHEMA_VALIDATION: true + BOX_FHIR_SEARCH_AUTHORIZE_INLINE_REQUESTS: false + BOX_FHIR_SEARCH_CHAIN_SUBSELECT: true + BOX_FHIR_SEARCH_COMPARISONS: true + BOX_FHIR_LEGACY_FCE_PACKAGE: us-core-extensions.legacy.aidbox#0.0.0 + BOX_MODULE_SDC_STRICT_ACCESS_CONTROL: true + BOX_SEARCH_INCLUDE_CONFORMANT: true + BOX_SECURITY_AUDIT_LOG_ENABLED: true + BOX_SECURITY_DEV_MODE: false + BOX_SETTINGS_MODE: read-write + BOX_SECURITY_ORGBAC_ENABLED: true + BOX_USAGE_STATS: false + BOX_OBSERVABILITY_STDOUT_PRETTY_LOG_LEVEL: info + BOX_METRICS_PORT: 8765 + + ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt + nginx.ingress.kubernetes.io/ssl-redirect: "true" + + resources: + requests: + cpu: 500m + memory: 2Gi + limits: + memory: 2Gi + + startupProbe: + initialDelaySeconds: 50 + periodSeconds: 10 + failureThreshold: 20 + + serviceMonitor: + enabled: false + +# ============================================================================= +# Global Settings +# ============================================================================= +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: false + automount: true + annotations: {} + name: "" + +podAnnotations: {} +podLabels: {} +podSecurityContext: {} + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} +tolerations: [] +affinity: {} + +extraEnvFromConfigMaps: [] +extraEnvFromSecrets: [] + +# Additional volumes on the output Deployment definition. +volumes: [] + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +