From 5988691a2f0aef3e340ccd787762d307d992e7b2 Mon Sep 17 00:00:00 2001 From: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Date: Tue, 21 Oct 2025 23:18:50 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20deployment=20charts?= =?UTF-8?q?=20for=20StorageHub=20MSP,=20BSP=20&=20Indexer=20nodes=20(Local?= =?UTF-8?q?=20&=20Stagenet=20envs)=20(#160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR adds comprehensive Kubernetes deployment infrastructure for StorageHub components, enabling deployment of the full StorageHub network stack (MSP, BSP, Indexer, and Fisherman nodes) alongside DataHaven nodes in both local and stagenet environments. ### What's Added **1. New Helm Chart: StorageHub MSP Backend API** (`deploy/charts/backend/`) - REST API service for StorageHub operations - Connects to PostgreSQL database for indexed blockchain data - Connects to RPC nodes for real-time blockchain queries - Configurable via TOML configuration file - Supports environment-specific overrides - Includes comprehensive documentation **2. StorageHub Node Deployment Charts** (`deploy/charts/node/storagehub/`) - **MSP Node** (`sh-mspnode`): Main Service Provider nodes with charging capabilities - **BSP Node** (`sh-bspnode`): Backup Service Provider nodes for redundancy - **Indexer Node** (`sh-idxnode`): Full indexing node with PostgreSQL integration - **Fisherman Node** (`sh-fisherman`): Network monitoring and verification node **3. Environment Configurations** - **Local environment** (`deploy/environments/local/`): Development setup with hostpath storage - **Stagenet environment** (`deploy/environments/stagenet/`): Production-like setup with AWS EBS - PostgreSQL database configurations for Indexer and Fisherman nodes - Proper service discovery and network configuration **4. Enhanced CLI Tooling** (`test/cli/`) - New `deploy storagehub` command for deploying StorageHub components - Updated `launch storagehub` command for local testing - Interactive deployment with environment selection - Automatic database provisioning via Bitnami PostgreSQL charts **5. Node Configuration Improvements** - Fork-aware transaction pool for DH boot & validator nodes - Unsafe RPC methods exposed on MSP nodes (for provider operations) - JWT secret support for MSP Backend authentication - ECDSA key scheme for StorageHub BCSV keys (DataHaven compatibility) ### Architecture ``` StorageHub Stack: ├── MSP Nodes (2 replicas) → Storage providers with charging ├── BSP Nodes (2 replicas) → Backup storage providers ├── Indexer Node → Database indexing + PostgreSQL ├── Fisherman Node → Monitoring + PostgreSQL (shared with Indexer) └── MSP Backend API → REST API for StorageHub operations ``` ### Testing **Local Testing**: ```bash cd test bun cli launch storagehub # Interactive launcher # or bun cli deploy storagehub # Deploy via Helm ``` **Stagenet Deployment**: ```bash cd deploy helm install sh-mspnode ./charts/node \ -f ./charts/node/storagehub/sh-mspnode.yaml \ -f ./environments/stagenet/sh-mspnode.yaml \ -n datahaven-stagenet ``` ### Breaking Changes None - This is purely additive infrastructure. ### Migration Notes For existing deployments: 1. DataHaven nodes now use `--pool-type fork-aware` flag 2. Bootnode and validator node configs updated accordingly 3. No action required for existing DataHaven-only deployments --- deploy/charts/backend/Chart.yaml | 9 + deploy/charts/backend/README.md | 270 ++++++++++++++++++ .../backend/storagehub/sh-mspbackend.yaml | 64 +++++ deploy/charts/backend/templates/_helpers.tpl | 60 ++++ .../charts/backend/templates/configmap.yaml | 48 ++++ .../charts/backend/templates/deployment.yaml | 80 ++++++ deploy/charts/backend/templates/ingress.yaml | 61 ++++ deploy/charts/backend/templates/secret.yaml | 13 + deploy/charts/backend/templates/service.yaml | 22 ++ .../backend/templates/serviceaccount.yaml | 12 + deploy/charts/backend/values.yaml | 122 ++++++++ deploy/charts/node/datahaven/dh-bootnode.yaml | 1 + .../charts/node/datahaven/dh-validator.yaml | 1 + deploy/charts/node/storagehub/sh-bspnode.yaml | 47 +++ .../charts/node/storagehub/sh-fisherman.yaml | 44 +++ deploy/charts/node/storagehub/sh-idxnode.yaml | 38 +++ deploy/charts/node/storagehub/sh-mspnode.yaml | 49 ++++ deploy/environments/local/dh-bootnode.yaml | 2 +- deploy/environments/local/sh-bspnode.yaml | 44 +++ deploy/environments/local/sh-fisherman.yaml | 44 +++ deploy/environments/local/sh-idxnode-db.yaml | 21 ++ deploy/environments/local/sh-idxnode.yaml | 33 +++ deploy/environments/local/sh-mspbackend.yaml | 73 +++++ deploy/environments/local/sh-mspnode.yaml | 44 +++ deploy/environments/stagenet/dh-bootnode.yaml | 2 +- .../environments/stagenet/dh-validator.yaml | 2 +- deploy/environments/stagenet/sh-bspnode.yaml | 51 ++++ .../environments/stagenet/sh-fisherman.yaml | 51 ++++ .../environments/stagenet/sh-idxnode-db.yaml | 22 ++ deploy/environments/stagenet/sh-idxnode.yaml | 28 ++ .../environments/stagenet/sh-mspbackend.yaml | 71 +++++ deploy/environments/stagenet/sh-mspnode.yaml | 51 ++++ operator/Dockerfile.local | 25 ++ test/cli/handlers/deploy/index.ts | 4 + test/cli/handlers/deploy/storagehub.ts | 216 ++++++++++++++ test/cli/handlers/launch/index.ts | 13 +- test/cli/handlers/launch/storagehub.ts | 36 +++ test/cli/index.ts | 9 +- 38 files changed, 1775 insertions(+), 8 deletions(-) create mode 100644 deploy/charts/backend/Chart.yaml create mode 100644 deploy/charts/backend/README.md create mode 100644 deploy/charts/backend/storagehub/sh-mspbackend.yaml create mode 100644 deploy/charts/backend/templates/_helpers.tpl create mode 100644 deploy/charts/backend/templates/configmap.yaml create mode 100644 deploy/charts/backend/templates/deployment.yaml create mode 100644 deploy/charts/backend/templates/ingress.yaml create mode 100644 deploy/charts/backend/templates/secret.yaml create mode 100644 deploy/charts/backend/templates/service.yaml create mode 100644 deploy/charts/backend/templates/serviceaccount.yaml create mode 100644 deploy/charts/backend/values.yaml create mode 100644 deploy/charts/node/storagehub/sh-bspnode.yaml create mode 100644 deploy/charts/node/storagehub/sh-fisherman.yaml create mode 100644 deploy/charts/node/storagehub/sh-idxnode.yaml create mode 100644 deploy/charts/node/storagehub/sh-mspnode.yaml create mode 100644 deploy/environments/local/sh-bspnode.yaml create mode 100644 deploy/environments/local/sh-fisherman.yaml create mode 100644 deploy/environments/local/sh-idxnode-db.yaml create mode 100644 deploy/environments/local/sh-idxnode.yaml create mode 100644 deploy/environments/local/sh-mspbackend.yaml create mode 100644 deploy/environments/local/sh-mspnode.yaml create mode 100644 deploy/environments/stagenet/sh-bspnode.yaml create mode 100644 deploy/environments/stagenet/sh-fisherman.yaml create mode 100644 deploy/environments/stagenet/sh-idxnode-db.yaml create mode 100644 deploy/environments/stagenet/sh-idxnode.yaml create mode 100644 deploy/environments/stagenet/sh-mspbackend.yaml create mode 100644 deploy/environments/stagenet/sh-mspnode.yaml create mode 100644 operator/Dockerfile.local create mode 100644 test/cli/handlers/deploy/storagehub.ts create mode 100644 test/cli/handlers/launch/storagehub.ts diff --git a/deploy/charts/backend/Chart.yaml b/deploy/charts/backend/Chart.yaml new file mode 100644 index 00000000..b2fd66b4 --- /dev/null +++ b/deploy/charts/backend/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: sh-mspbackend +description: A Helm chart for StorageHub MSP Backend API +type: application +version: 0.1.0 +appVersion: "1.0.0" +maintainers: +- name: StorageHub Team + email: team@storagehub.io \ No newline at end of file diff --git a/deploy/charts/backend/README.md b/deploy/charts/backend/README.md new file mode 100644 index 00000000..efedff2d --- /dev/null +++ b/deploy/charts/backend/README.md @@ -0,0 +1,270 @@ +# StorageHub MSP Backend Helm Chart + +This Helm chart deploys the StorageHub MSP Backend API service that provides REST API access to the StorageHub network data. + +## Overview + +The StorageHub MSP Backend API: +- Connects to a StorageHub Indexer's database for indexed blockchain data +- Connects to a StorageHub MSP node for real-time blockchain queries +- Provides REST API endpoints for StorageHub operations + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ +- Running StorageHub Indexer database (PostgreSQL) +- Running StorageHub MSP node + +## Installation + +### Using base configuration + +```bash +helm install sh-mspbackend ./charts/backend \ + -f ./charts/backend/storagehub/sh-mspbackend.yaml +``` + +### For Local environment + +```bash +helm install sh-mspbackend ./charts/backend \ + -f ./charts/backend/storagehub/sh-mspbackend.yaml \ + -f ./environments/local/sh-mspbackend.yaml \ + -n kt-datahaven-local +``` + +### For Stagenet environment + +```bash +helm install sh-mspbackend ./charts/backend \ + -f ./charts/backend/storagehub/sh-mspbackend.yaml \ + -f ./environments/stagenet/sh-mspbackend.yaml \ + -n datahaven-stagenet +``` + +## Configuration + +### Key Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `image.repository` | Container image repository | `moonsonglabs/storage-hub-msp-backend` | +| `image.tag` | Container image tag | `latest` | +| `replicaCount` | Number of replicas | `1` | +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.port` | Service port | `8080` | +| `service.targetPort` | Service target port | `80` | +| `backend.port` | Backend application port | `8080` | +| `backend.database.url` | PostgreSQL connection URL | `postgresql://storagehub:storagehub@sh-indexer-db:5432/storagehub` | +| `backend.rpc.endpoint` | WebSocket RPC endpoint | `ws://sh-idxnode:9944` | +| `backend.api.defaultPageSize` | Default page size for API results | `20` | +| `backend.api.maxPageSize` | Maximum page size for API results | `100` | +| `ingress.enabled` | Enable ingress | `false` | + +### Configuration File + +The backend uses a TOML configuration file passed via the `--config` CLI argument. This file is automatically generated from the Helm values and mounted as a ConfigMap at `/configs/config.toml`. + +#### Basic Configuration: +```yaml +backend: + port: 8080 + + database: + url: postgresql://indexer:indexer@sh-idxnode-db-postgresql:5432/datahaven + + rpc: + endpoint: ws://sh-mspnode-0:9955 + + api: + defaultPageSize: 20 + maxPageSize: 100 + + auth: + jwtSecret: "your-secret-here" + + args: + - "--config" + - "/configs/config.toml" + +configMap: + enabled: true +``` + +#### Alternative: Building Database URL from Components +The chart can also construct the database URL from separate components: +```yaml +backend: + database: + host: sh-idxnode-db-postgresql + port: 5432 + name: datahaven + user: indexer + password: production_password +``` + +**Note:** For production deployments, consider using Kubernetes Secrets or external secret management solutions for sensitive values like database passwords and JWT secrets. + +### Environment Variables + +Additional environment variables can be configured: + +```yaml +backend: + env: + NODE_ENV: production + LOG_LEVEL: info +``` + +### Additional ConfigMap Data + +You can add extra files to the ConfigMap: + +```yaml +configMap: + enabled: true + data: + custom-config.yaml: | + # Your custom configuration here + key: value +``` + +### CLI Arguments + +Additional CLI arguments can be specified to pass to the backend application: + +```yaml +backend: + args: + - "--config" + - "/configs/config.toml" + - "--log-level" + - "debug" +``` + +### Using Environment Variables from ConfigMaps or Secrets + +You can inject environment variables from existing ConfigMaps or Secrets: + +```yaml +backend: + envFrom: + - configMapRef: + name: my-config + - secretRef: + name: my-secret +``` + +## Accessing the Service + +### Local Environment + +When deployed with `NodePort` service type: +```bash +# Access via NodePort (configured as 30300 in local environment) +curl http://localhost:30300/ + +# Or via ingress if enabled +curl http://sh-mspbackend.datahaven.local/ +``` + +### Stagenet Environment + +```bash +# Access via ingress +curl https://sh-mspbackend.datahaven-kt.xyz/ +``` + +## Generated Configuration + +The chart automatically generates a `config.toml` file with the following structure: + +```toml +host = "0.0.0.0" +port = 8080 + +[api] +default_page_size = 20 +max_page_size = 100 + +[storage_hub] +rpc_url = "ws://sh-mspnode-0:9955" +msp_callback_url = "http://sh-mspbackend:8080" +timeout_secs = 30 +max_concurrent_requests = 100 +verify_tls = true +mock_mode = false + +[auth] +jwt_secret = "your-secret-here" + +[database] +url = "postgresql://indexer:indexer@sh-idxnode-db-postgresql:5432/datahaven" +mock_mode = false +``` + +## Troubleshooting + +### Check pod status +```bash +kubectl get pods -l app.kubernetes.io/name=sh-mspbackend -n +``` + +### View logs +```bash +kubectl logs -l app.kubernetes.io/name=sh-mspbackend -n +``` + +### Verify database connection +For local environment: +```bash +kubectl exec -it deployment/sh-mspbackend -n kt-datahaven-local -- nc -zv sh-idxnode-db-postgresql 5432 +``` + +For stagenet environment: +```bash +kubectl exec -it deployment/sh-mspbackend -n datahaven-stagenet -- nc -zv sh-idxnode-db-postgresql 5432 +``` + +### Verify RPC connection +For local environment: +```bash +kubectl exec -it deployment/sh-mspbackend -n kt-datahaven-local -- nc -zv sh-mspnode-0 9955 +``` + +For stagenet environment: +```bash +kubectl exec -it deployment/sh-mspbackend -n datahaven-stagenet -- nc -zv sh-mspnode-0 9955 +``` + +### View generated configuration +```bash +kubectl get configmap sh-mspbackend-config -n -o yaml +``` + +## Uninstallation + +```bash +# For local environment +helm uninstall sh-mspbackend -n kt-datahaven-local + +# For stagenet environment +helm uninstall sh-mspbackend -n datahaven-stagenet +``` + +## Environment-Specific Examples + +### Local Environment Values +See `environments/local/sh-mspbackend.yaml` for the complete local configuration, which includes: +- NodePort service on port 30300 +- Debug logging +- Traefik ingress at `sh-mspbackend.datahaven.local` +- Minimal resource requests for development + +### Stagenet Environment Values +See `environments/stagenet/sh-mspbackend.yaml` for the complete stagenet configuration, which includes: +- ClusterIP service with AWS NLB annotations +- Production logging levels +- ALB ingress with SSL at `sh-mspbackend.datahaven-kt.xyz` +- Production-level resource requests and limits \ No newline at end of file diff --git a/deploy/charts/backend/storagehub/sh-mspbackend.yaml b/deploy/charts/backend/storagehub/sh-mspbackend.yaml new file mode 100644 index 00000000..8f8de5e0 --- /dev/null +++ b/deploy/charts/backend/storagehub/sh-mspbackend.yaml @@ -0,0 +1,64 @@ +# StorageHub MSP Backend API base configuration +# This file contains the base configuration for the StorageHub MSP Backend API + +# Chart metadata +fullnameOverride: sh-mspbackend + +# Container image +image: + repository: moonsonglabs/storage-hub-msp-backend + tag: latest + pullPolicy: Always + +# Service configuration +service: + type: ClusterIP + port: 8080 + targetPort: 80 + +# Backend API configuration +backend: + port: 8080 + + # Database connection to StorageHub Indexer PostgreSQL + database: + url: postgresql://indexer:indexer@sh-idxnode-db-postgresql:5432/datahaven + + # RPC connection to StorageHub Indexer node + rpc: + endpoint: ws://sh-mspnode-0:9955 + + # Authentication (set in environment-specific values) + # auth: + # jwtSecret: "set-in-environment-values" + + # CLI arguments for the backend application + args: + - "--config" + - "/configs/config.toml" + +# Resource limits +resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + +# Service account +serviceAccount: + create: true + name: sh-mspbackend + +# Security context +securityContext: + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + +# Ingress configuration (disabled by default, enabled per environment) +ingress: + enabled: false diff --git a/deploy/charts/backend/templates/_helpers.tpl b/deploy/charts/backend/templates/_helpers.tpl new file mode 100644 index 00000000..d9930293 --- /dev/null +++ b/deploy/charts/backend/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "backend.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "backend.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 "backend.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "backend.labels" -}} +helm.sh/chart: {{ include "backend.chart" . }} +{{ include "backend.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "backend.selectorLabels" -}} +app.kubernetes.io/name: {{ include "backend.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "backend.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "backend.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/deploy/charts/backend/templates/configmap.yaml b/deploy/charts/backend/templates/configmap.yaml new file mode 100644 index 00000000..22afaa07 --- /dev/null +++ b/deploy/charts/backend/templates/configmap.yaml @@ -0,0 +1,48 @@ +{{- if .Values.configMap.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "backend.fullname" . }}-config + labels: + {{- include "backend.labels" . | nindent 4 }} +data: + config.toml: | + # StorageHub Backend Configuration + host = "0.0.0.0" + port = {{ .Values.backend.port }} + + [api] + default_page_size = {{ .Values.backend.api.defaultPageSize | default 20 }} + max_page_size = {{ .Values.backend.api.maxPageSize | default 100 }} + + [storage_hub] + {{- if .Values.backend.rpc.endpoint }} + rpc_url = {{ .Values.backend.rpc.endpoint | quote }} + {{- end }} + + msp_callback_url = "http://{{ include "backend.fullname" . }}:8080" + + timeout_secs = 30 + max_concurrent_requests = 100 + verify_tls = true + mock_mode = false + + [auth] + {{- if .Values.backend.auth.jwtSecret }} + jwt_secret = {{ .Values.backend.auth.jwtSecret | quote }} + {{- end }} + + [database] + {{- if .Values.backend.database.url }} + url = {{ .Values.backend.database.url | quote }} + {{- else if .Values.backend.database.host }} + url = "postgresql://{{ .Values.backend.database.user | default "indexer" }}:{{ .Values.backend.database.password | default "password" }}@{{ .Values.backend.database.host }}:{{ .Values.backend.database.port | default 5432 }}/{{ .Values.backend.database.name | default "storagehub" }}" + {{- else }} + url = "postgresql://indexer:password@sh-indexer-db:5432/storagehub" + {{- end }} + + mock_mode = false + {{- range $key, $value := .Values.configMap.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/deploy/charts/backend/templates/deployment.yaml b/deploy/charts/backend/templates/deployment.yaml new file mode 100644 index 00000000..c998e2eb --- /dev/null +++ b/deploy/charts/backend/templates/deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "backend.fullname" . }} + labels: + {{- include "backend.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "backend.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "backend.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "backend.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.backend.port }} + protocol: TCP + env: + - name: PORT + value: {{ .Values.backend.port | quote }} + {{- range $key, $value := .Values.backend.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- with .Values.backend.envFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.backend.args }} + args: + {{- toYaml .Values.backend.args | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.configMap.enabled }} + volumeMounts: + - name: config + mountPath: "/configs/config.toml" + subPath: "config.toml" + readOnly: true + {{- end }} + {{- if .Values.configMap.enabled }} + volumes: + - name: config + configMap: + name: {{ include "backend.fullname" . }}-config + {{- 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 }} \ No newline at end of file diff --git a/deploy/charts/backend/templates/ingress.yaml b/deploy/charts/backend/templates/ingress.yaml new file mode 100644 index 00000000..ca8849a5 --- /dev/null +++ b/deploy/charts/backend/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "backend.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "backend.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/deploy/charts/backend/templates/secret.yaml b/deploy/charts/backend/templates/secret.yaml new file mode 100644 index 00000000..812e46ef --- /dev/null +++ b/deploy/charts/backend/templates/secret.yaml @@ -0,0 +1,13 @@ +{{- if .Values.secrets.enabled -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "backend.fullname" . }}-custom + labels: + {{- include "backend.labels" . | nindent 4 }} +type: Opaque +data: + {{- range $key, $value := .Values.secrets.data }} + {{ $key }}: {{ $value | b64enc | quote }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/deploy/charts/backend/templates/service.yaml b/deploy/charts/backend/templates/service.yaml new file mode 100644 index 00000000..8d65dcbb --- /dev/null +++ b/deploy/charts/backend/templates/service.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "backend.fullname" . }} + labels: + {{- include "backend.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + selector: + {{- include "backend.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/deploy/charts/backend/templates/serviceaccount.yaml b/deploy/charts/backend/templates/serviceaccount.yaml new file mode 100644 index 00000000..56355387 --- /dev/null +++ b/deploy/charts/backend/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "backend.serviceAccountName" . }} + labels: + {{- include "backend.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/deploy/charts/backend/values.yaml b/deploy/charts/backend/values.yaml new file mode 100644 index 00000000..61ff0885 --- /dev/null +++ b/deploy/charts/backend/values.yaml @@ -0,0 +1,122 @@ +# Default values for backend. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: storagehub/backend + pullPolicy: IfNotPresent + tag: "latest" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 3000 + targetPort: 3000 + annotations: {} + +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: api.storagehub.local + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 250m + memory: 256Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# Backend API Configuration +backend: + # Port the backend listens on + port: 3000 + + # Database configuration + database: + # Full database connection URL + url: "postgresql://storagehub:storagehub@sh-indexer-db:5432/storagehub" + # Use existing secret for database URL + existingSecret: "" + # Key in the secret containing the DATABASE_URL + existingSecretUrlKey: "database-url" + + # RPC Node configuration + rpc: + endpoint: "ws://sh-idxnode:9944" + # Alternative HTTP endpoint if needed + httpEndpoint: "http://sh-idxnode:9933" + + # API configuration + api: + defaultPageSize: 20 + maxPageSize: 100 + + # Authentication configuration + auth: + jwtSecret: "" + + # Environment variables + env: + NODE_ENV: "production" + LOG_LEVEL: "info" + + # Additional environment variables from ConfigMap or Secret + envFrom: [] + + # CLI arguments to pass to the backend application + args: [] + # Example: + # args: + # - "--config" + # - "/app/config.toml" + # - "--log-level" + # - "debug" + +# ConfigMap for configuration files +configMap: + enabled: true + # Additional configuration to merge into config.json + extraConfig: {} + # Additional files to add to ConfigMap + data: {} + +# Secrets for sensitive data +secrets: + enabled: false + data: {} \ No newline at end of file diff --git a/deploy/charts/node/datahaven/dh-bootnode.yaml b/deploy/charts/node/datahaven/dh-bootnode.yaml index 4d537199..3da23587 100644 --- a/deploy/charts/node/datahaven/dh-bootnode.yaml +++ b/deploy/charts/node/datahaven/dh-bootnode.yaml @@ -26,6 +26,7 @@ node: - "--allow-private-ipv4" - "--discover-local" - "--network-backend libp2p" + - "--pool-type fork-aware" ingress: enabled: false diff --git a/deploy/charts/node/datahaven/dh-validator.yaml b/deploy/charts/node/datahaven/dh-validator.yaml index 9822a469..dc9a2356 100644 --- a/deploy/charts/node/datahaven/dh-validator.yaml +++ b/deploy/charts/node/datahaven/dh-validator.yaml @@ -43,6 +43,7 @@ node: persistGeneratedNodeKey: true flags: - "--network-backend libp2p" + - "--pool-type fork-aware" # Note: Bootnode discovery will happen automatically via the chainspec downloaded from customChainspecUrl enableOffchainIndexing: true diff --git a/deploy/charts/node/storagehub/sh-bspnode.yaml b/deploy/charts/node/storagehub/sh-bspnode.yaml new file mode 100644 index 00000000..56697db8 --- /dev/null +++ b/deploy/charts/node/storagehub/sh-bspnode.yaml @@ -0,0 +1,47 @@ +name: sh-bspnode +description: Datahaven BSP node +fullnameOverride: sh-bspnode + +image: + repository: datahavenxyz/datahaven + tag: main + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +node: + command: datahaven-node + customChainspecUrl: http://dh-bootnode:8080/chainspec.json + forceDownloadChainspec: true + role: full + replicas: 2 + chainData: + pruning: 1000 + storageClass: "gp2" + chainKeystore: + storageClass: "gp2" + keys: + # This is Alice seed. To generate new seed run: docker run --rm datahavenxyz/datahaven:latest key generate + - seed: "bottom drive obey lake curtain smoke basket hold race lonely fit walk" + type: bcsv + scheme: ecdsa + # ${HOSTNAME##*-} will be evaluated as the pod index, pod-0: //Eve, pod-1: //Ferdie + extraDerivation: '$([ "${HOSTNAME##*-}" = "0" ] && echo "//Eve" || echo "//Ferdie")' + persistGeneratedNodeKey: true + flags: + - "--allow-private-ipv4" + - "--discover-local" + - "--network-backend libp2p" + - "--provider" + - "--provider-type bsp" + - "--max-storage-capacity 10737418240" # 10 GiB + - "--jump-capacity=1073741824" # 1 GiB + +ingress: + enabled: false + perReplica: true + wildcardDomain: datahaven.local + # If enabled, this would generate: + # - sh-bspnode-0.datahaven.local + # - sh-bspnode-1.datahaven.local diff --git a/deploy/charts/node/storagehub/sh-fisherman.yaml b/deploy/charts/node/storagehub/sh-fisherman.yaml new file mode 100644 index 00000000..612584fd --- /dev/null +++ b/deploy/charts/node/storagehub/sh-fisherman.yaml @@ -0,0 +1,44 @@ +name: sh-fisherman +description: Datahaven Fisherman node +fullnameOverride: sh-fisherman + +image: + repository: datahavenxyz/datahaven + tag: main + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +node: + command: datahaven-node + customChainspecUrl: http://dh-bootnode:8080/chainspec.json + forceDownloadChainspec: true + role: full + replicas: 1 + chainData: + pruning: 1000 + storageClass: "gp2" + chainKeystore: + storageClass: "gp2" + keys: + # This is Alice seed. To generate new seed run: docker run --rm datahavenxyz/datahaven:latest key generate + - seed: "bottom drive obey lake curtain smoke basket hold race lonely fit walk" + type: bcsv + scheme: ecdsa + # ${HOSTNAME##*-} will be evaluated as the pod index, pod-0: //Gustavo, pod-1: //Hermano + extraDerivation: '$([ "${HOSTNAME##*-}" = "0" ] && echo "//Gustavo" || echo "//Hermano")' + persistGeneratedNodeKey: true + flags: + - "--allow-private-ipv4" + - "--discover-local" + - "--network-backend libp2p" + - "--fisherman" + - "--fisherman-database-url postgresql://indexer:indexer@sh-idxnode-db-postgresql:5432/datahaven" + +ingress: + enabled: false + perReplica: false + wildcardDomain: datahaven.local + # If enabled, this would generate: + # - sh-fisherman-0.datahaven.local diff --git a/deploy/charts/node/storagehub/sh-idxnode.yaml b/deploy/charts/node/storagehub/sh-idxnode.yaml new file mode 100644 index 00000000..8a606ab5 --- /dev/null +++ b/deploy/charts/node/storagehub/sh-idxnode.yaml @@ -0,0 +1,38 @@ +name: sh-idxnode +description: Datahaven Indexer node +fullnameOverride: sh-idxnode + +image: + repository: datahavenxyz/datahaven + tag: main + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +node: + command: datahaven-node + customChainspecUrl: http://dh-bootnode:8080/chainspec.json + forceDownloadChainspec: true + role: full + replicas: 1 + chainData: + pruning: 1000 + storageClass: "gp2" + chainKeystore: + storageClass: "gp2" + persistGeneratedNodeKey: true + flags: + - "--allow-private-ipv4" + - "--discover-local" + - "--network-backend libp2p" + - "--indexer" + - "--indexer-mode full" + - "--indexer-database-url postgresql://indexer:indexer@sh-idxnode-db-postgresql:5432/datahaven" + +ingress: + enabled: false + perReplica: false + wildcardDomain: datahaven.local + # If enabled, this would generate: + # - sh-idxnode-0.datahaven.local diff --git a/deploy/charts/node/storagehub/sh-mspnode.yaml b/deploy/charts/node/storagehub/sh-mspnode.yaml new file mode 100644 index 00000000..34f6de6a --- /dev/null +++ b/deploy/charts/node/storagehub/sh-mspnode.yaml @@ -0,0 +1,49 @@ +name: sh-mspnode +description: Datahaven MSP node +fullnameOverride: sh-mspnode + +image: + repository: datahavenxyz/datahaven + tag: main + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +node: + command: datahaven-node + customChainspecUrl: http://dh-bootnode:8080/chainspec.json + forceDownloadChainspec: true + role: full + replicas: 2 + chainData: + pruning: 1000 + storageClass: "gp2" + chainKeystore: + storageClass: "gp2" + keys: + # This is Alice seed. To generate new seed run: docker run --rm datahavenxyz/datahaven:latest key generate + - seed: "bottom drive obey lake curtain smoke basket hold race lonely fit walk" + type: bcsv + scheme: ecdsa + # ${HOSTNAME##*-} will be evaluated as the pod index, pod-0: //Charlie, pod-1: //Dave + extraDerivation: '$([ "${HOSTNAME##*-}" = "0" ] && echo "//Charlie" || echo "//Dave")' + persistGeneratedNodeKey: true + allowUnsafeRpcMethods: true + flags: + - "--allow-private-ipv4" + - "--discover-local" + - "--network-backend libp2p" + - "--provider" + - "--provider-type msp" + - "--msp-charging-period 100" # in blocks, 100 blocks = 10 minutes + - "--max-storage-capacity 10737418240" # 10 GiB + - "--jump-capacity=1073741824" # 1 GiB + +ingress: + enabled: false + perReplica: true + wildcardDomain: datahaven.local + # If enabled, this would generate: + # - sh-mspnode-0.datahaven.local + # - sh-mspnode-1.datahaven.local diff --git a/deploy/environments/local/dh-bootnode.yaml b/deploy/environments/local/dh-bootnode.yaml index de46dd2e..245db825 100644 --- a/deploy/environments/local/dh-bootnode.yaml +++ b/deploy/environments/local/dh-bootnode.yaml @@ -15,7 +15,7 @@ imagePullSecrets: ingress: enabled: false ingressClassName: traefik - host: dh-bootnode-0.datahaven-kt.local + host: dh-bootnode-0.datahaven.local node: chain: local diff --git a/deploy/environments/local/sh-bspnode.yaml b/deploy/environments/local/sh-bspnode.yaml new file mode 100644 index 00000000..db3339f3 --- /dev/null +++ b/deploy/environments/local/sh-bspnode.yaml @@ -0,0 +1,44 @@ +# BSP-specific settings for Local environment + +global: + environment: local + namespace: kt-datahaven-local + +image: + tag: local + pullPolicy: IfNotPresent + +imagePullSecrets: + - name: datahaven-dockerhub + +# Ingress disabled for local (can be enabled for testing) +ingress: + enabled: false + ingressClassName: traefik + perReplica: true + wildcardDomain: datahaven.local + # If enabled, this would generate: + # - sh-bspnode-0.datahaven.local + # - sh-bspnode-1.datahaven.local + +node: + chain: local + customChainspecUrl: + forceDownloadChainspec: false + chainData: + storageClass: "hostpath" + persistence: + size: 10Gi + chainKeystore: + storageClass: "hostpath" + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "200m" + perNodeServices: + apiService: + enabled: true + type: NodePort diff --git a/deploy/environments/local/sh-fisherman.yaml b/deploy/environments/local/sh-fisherman.yaml new file mode 100644 index 00000000..1f544a8d --- /dev/null +++ b/deploy/environments/local/sh-fisherman.yaml @@ -0,0 +1,44 @@ +# Fisherman-specific settings for Local environment + +global: + environment: local + namespace: kt-datahaven-local + +image: + tag: local + pullPolicy: IfNotPresent + +imagePullSecrets: + - name: datahaven-dockerhub + +# Ingress disabled for local (can be enabled for testing) +ingress: + enabled: false + ingressClassName: traefik + perReplica: true + wildcardDomain: datahaven.local + # If enabled, this would generate: + # - sh-fisherman-0.datahaven.local + # - sh-fisherman-1.datahaven.local + +node: + chain: local + customChainspecUrl: + forceDownloadChainspec: false + chainData: + storageClass: "hostpath" + persistence: + size: 10Gi + chainKeystore: + storageClass: "hostpath" + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "200m" + perNodeServices: + apiService: + enabled: true + type: NodePort diff --git a/deploy/environments/local/sh-idxnode-db.yaml b/deploy/environments/local/sh-idxnode-db.yaml new file mode 100644 index 00000000..c66998cd --- /dev/null +++ b/deploy/environments/local/sh-idxnode-db.yaml @@ -0,0 +1,21 @@ +# Install PostgreSQL into local k8S cluster +# helm repo add bitnami https://charts.bitnami.com/bitnami +# helm repo update +# helm install sh-idxnode-db bitnami/postgresql -f ./deploy/environments/local/sh-idxnode-db.yaml -n datahaven-local + +auth: + username: indexer + password: indexer + database: datahaven + postgresPassword: postgres +primary: + persistence: + enabled: true + size: 10Gi # requires a default StorageClass + resources: + requests: { cpu: "100m", memory: "256Mi" } + limits: { cpu: "500m", memory: "512Mi" } +volumePermissions: + enabled: true # helps with FS perms on some storage classes +metrics: + enabled: true # optional: Prometheus exporter diff --git a/deploy/environments/local/sh-idxnode.yaml b/deploy/environments/local/sh-idxnode.yaml new file mode 100644 index 00000000..a3aeda52 --- /dev/null +++ b/deploy/environments/local/sh-idxnode.yaml @@ -0,0 +1,33 @@ +# Indexer-specific settings for Local environment + +global: + environment: local + namespace: kt-datahaven-local + +image: + tag: local + pullPolicy: IfNotPresent + +imagePullSecrets: + - name: datahaven-dockerhub + +ingress: + enabled: false + +node: + chain: local + customChainspecUrl: + forceDownloadChainspec: false + chainData: + storageClass: "hostpath" + persistence: + size: 10Gi + chainKeystore: + storageClass: "hostpath" + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "200m" diff --git a/deploy/environments/local/sh-mspbackend.yaml b/deploy/environments/local/sh-mspbackend.yaml new file mode 100644 index 00000000..ae5006dc --- /dev/null +++ b/deploy/environments/local/sh-mspbackend.yaml @@ -0,0 +1,73 @@ +# StorageHub MSP Backend API configuration for Local environment + +global: + environment: local + namespace: kt-datahaven-local + +# Use local image +image: + repository: moonsonglabs/storage-hub-msp-backend + tag: latest + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +# Single replica for local development +replicaCount: 1 + +# Service configuration for local access +service: + type: NodePort + port: 8080 + nodePort: 30300 # Fixed NodePort for local access + +# Backend configuration for local environment +backend: + database: + url: postgresql://indexer:indexer@sh-idxnode-db-postgresql:5432/datahaven + + rpc: + url: ws://sh-idxnode:9944 + + auth: + jwtSecret: "local-development-secret" + + env: + LOG_LEVEL: debug + + # CLI arguments for local development + args: + - "--config" + - "/app/config/config.toml" + +# Minimal resources for local development +resources: + requests: + memory: "128Mi" + cpu: "50m" + limits: + memory: "256Mi" + cpu: "200m" + +# Enable ingress for local development with Traefik +ingress: + enabled: true + className: traefik + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web + hosts: + - host: sh-mspbackend.datahaven.local + paths: + - path: / + pathType: Prefix + +# ConfigMap for local environment +configMap: + enabled: true + extraConfig: + environment: "local" + features: + debug: true + swagger: true + metrics: true \ No newline at end of file diff --git a/deploy/environments/local/sh-mspnode.yaml b/deploy/environments/local/sh-mspnode.yaml new file mode 100644 index 00000000..b4bdb88c --- /dev/null +++ b/deploy/environments/local/sh-mspnode.yaml @@ -0,0 +1,44 @@ +# MSP-specific settings for Local environment + +global: + environment: local + namespace: kt-datahaven-local + +image: + tag: local + pullPolicy: IfNotPresent + +imagePullSecrets: + - name: datahaven-dockerhub + +# Ingress disabled for local (can be enabled for testing) +ingress: + enabled: false + ingressClassName: traefik + perReplica: true + wildcardDomain: datahaven.local + # If enabled, this would generate: + # - sh-mspnode-0.datahaven.local + # - sh-mspnode-1.datahaven.local + +node: + chain: local + customChainspecUrl: + forceDownloadChainspec: false + chainData: + storageClass: "hostpath" + persistence: + size: 10Gi + chainKeystore: + storageClass: "hostpath" + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "200m" + perNodeServices: + apiService: + enabled: true + type: NodePort diff --git a/deploy/environments/stagenet/dh-bootnode.yaml b/deploy/environments/stagenet/dh-bootnode.yaml index d03787dd..b3f41d40 100644 --- a/deploy/environments/stagenet/dh-bootnode.yaml +++ b/deploy/environments/stagenet/dh-bootnode.yaml @@ -5,7 +5,7 @@ global: namespace: kt-datahaven-stagenet image: - tag: main + tag: latest pullPolicy: Always imagePullSecrets: diff --git a/deploy/environments/stagenet/dh-validator.yaml b/deploy/environments/stagenet/dh-validator.yaml index 5deca490..66af0a42 100644 --- a/deploy/environments/stagenet/dh-validator.yaml +++ b/deploy/environments/stagenet/dh-validator.yaml @@ -5,7 +5,7 @@ global: namespace: kt-datahaven-stagenet image: - tag: main + tag: latest pullPolicy: Always imagePullSecrets: diff --git a/deploy/environments/stagenet/sh-bspnode.yaml b/deploy/environments/stagenet/sh-bspnode.yaml new file mode 100644 index 00000000..fa0ef078 --- /dev/null +++ b/deploy/environments/stagenet/sh-bspnode.yaml @@ -0,0 +1,51 @@ +# BSP-specific settings for stagenet + +global: + environment: stagenet + namespace: kt-datahaven-stagenet + +image: + tag: latest + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +ingress: + enabled: true + perReplica: true + wildcardDomain: datahaven-kt.xyz + # This will generate: + # - sh-bspnode-0.datahaven-kt.xyz + # - sh-bspnode-1.datahaven-kt.xyz + # (based on replica count) + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' + alb.ingress.kubernetes.io/load-balancer-name: sh-bspnode + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-2-2017-01 + alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120 + alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30 + alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:601766312530:certificate/61c2fc57-4ce4-4a80-90b8-2810c1221f09 + external-dns.alpha.kubernetes.io/hostname: sh-bspnode.datahaven-kt.xyz + meta.helm.sh/release-name: sh-bspnode + meta.helm.sh/release-namespace: kt-datahaven-stagenet + +node: + chain: stagenet-local + chainData: + persistence: + size: 20Gi + resources: + requests: + memory: "512Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "500m" + perNodeServices: + apiService: + enabled: true + type: NodePort diff --git a/deploy/environments/stagenet/sh-fisherman.yaml b/deploy/environments/stagenet/sh-fisherman.yaml new file mode 100644 index 00000000..2d82b49e --- /dev/null +++ b/deploy/environments/stagenet/sh-fisherman.yaml @@ -0,0 +1,51 @@ +# Fisherman-specific settings for stagenet + +global: + environment: stagenet + namespace: kt-datahaven-stagenet + +image: + tag: latest + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +ingress: + enabled: true + perReplica: true + wildcardDomain: datahaven-kt.xyz + # This will generate: + # - sh-fisherman-0.datahaven-kt.xyz + # - sh-fisherman-1.datahaven-kt.xyz + # (based on replica count) + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' + alb.ingress.kubernetes.io/load-balancer-name: sh-fisherman + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-2-2017-01 + alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120 + alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30 + alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:601766312530:certificate/61c2fc57-4ce4-4a80-90b8-2810c1221f09 + external-dns.alpha.kubernetes.io/hostname: sh-fisherman.datahaven-kt.xyz + meta.helm.sh/release-name: sh-fisherman + meta.helm.sh/release-namespace: kt-datahaven-stagenet + +node: + chain: stagenet-local + chainData: + persistence: + size: 20Gi + resources: + requests: + memory: "512Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "500m" + perNodeServices: + apiService: + enabled: true + type: NodePort diff --git a/deploy/environments/stagenet/sh-idxnode-db.yaml b/deploy/environments/stagenet/sh-idxnode-db.yaml new file mode 100644 index 00000000..1845defa --- /dev/null +++ b/deploy/environments/stagenet/sh-idxnode-db.yaml @@ -0,0 +1,22 @@ +# Install PostgreSQL into local k8S cluster +# helm repo add bitnami https://charts.bitnami.com/bitnami +# helm repo update +# helm install sh-idxnode-db bitnami/postgresql -f ./deploy/environments/local/sh-idxnode-db.yaml -n kt-datahaven-stagenet + +auth: + username: indexer + password: indexer + database: datahaven + postgresPassword: postgres +primary: + persistence: + enabled: true + size: 10Gi # requires a default StorageClass + storageClass: "gp2" + resources: + requests: { cpu: "100m", memory: "256Mi" } + limits: { cpu: "500m", memory: "512Mi" } +volumePermissions: + enabled: true # helps with FS perms on some storage classes +metrics: + enabled: true # optional: Prometheus exporter diff --git a/deploy/environments/stagenet/sh-idxnode.yaml b/deploy/environments/stagenet/sh-idxnode.yaml new file mode 100644 index 00000000..1e713ea2 --- /dev/null +++ b/deploy/environments/stagenet/sh-idxnode.yaml @@ -0,0 +1,28 @@ +# Indexer-specific settings for stagenet + +global: + environment: stagenet + namespace: kt-datahaven-stagenet + +image: + tag: latest + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +ingress: + enabled: false + +node: + chain: stagenet-local + chainData: + persistence: + size: 20Gi + resources: + requests: + memory: "512Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "500m" diff --git a/deploy/environments/stagenet/sh-mspbackend.yaml b/deploy/environments/stagenet/sh-mspbackend.yaml new file mode 100644 index 00000000..cf93b1d7 --- /dev/null +++ b/deploy/environments/stagenet/sh-mspbackend.yaml @@ -0,0 +1,71 @@ +# StorageHub MSP Backend API configuration for Stagenet environment + +global: + environment: stagenet + namespace: datahaven-stagenet + +# Stagenet image configuration +image: + repository: moonsonglabs/storage-hub-msp-backend + tag: latest + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +replicaCount: 1 + +# Service configuration +service: + type: ClusterIP + port: 8080 + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: "nlb" + +# Backend configuration for stagenet +backend: + database: + url: postgresql://indexer:indexer@sh-idxnode-db-postgresql:5432/datahaven + + rpc: + endpoint: ws://sh-mspnode-0:9955 + + auth: + jwtSecret: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + + # CLI arguments for production + args: + - "--config" + - "/configs/config.toml" + +# Production-ready resources +resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + +# Enable ingress with SSL +ingress: + enabled: true + host: sh-mspbackend.datahaven-kt.xyz + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' + alb.ingress.kubernetes.io/load-balancer-name: sh-mspbackend + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-2-2017-01 + alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120 + alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30 + alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:601766312530:certificate/61c2fc57-4ce4-4a80-90b8-2810c1221f09 + external-dns.alpha.kubernetes.io/hostname: sh-mspbackend.datahaven-kt.xyz + meta.helm.sh/release-name: sh-mspbackend + meta.helm.sh/release-namespace: kt-datahaven-stagenet + hosts: + - host: sh-mspbackend.datahaven-kt.xyz + paths: + - path: / + pathType: Prefix diff --git a/deploy/environments/stagenet/sh-mspnode.yaml b/deploy/environments/stagenet/sh-mspnode.yaml new file mode 100644 index 00000000..6bf28aee --- /dev/null +++ b/deploy/environments/stagenet/sh-mspnode.yaml @@ -0,0 +1,51 @@ +# MSP-specific settings for stagenet + +global: + environment: stagenet + namespace: kt-datahaven-stagenet + +image: + tag: latest + pullPolicy: Always + +imagePullSecrets: + - name: datahaven-dockerhub + +ingress: + enabled: true + perReplica: true + wildcardDomain: datahaven-kt.xyz + # This will generate: + # - sh-mspnode-0.datahaven-kt.xyz + # - sh-mspnode-1.datahaven-kt.xyz + # (based on replica count) + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' + alb.ingress.kubernetes.io/load-balancer-name: sh-mspnode + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-2-2017-01 + alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120 + alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30 + alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:601766312530:certificate/61c2fc57-4ce4-4a80-90b8-2810c1221f09 + external-dns.alpha.kubernetes.io/hostname: sh-mspnode.datahaven-kt.xyz + meta.helm.sh/release-name: sh-mspnode + meta.helm.sh/release-namespace: kt-datahaven-stagenet + +node: + chain: stagenet-local + chainData: + persistence: + size: 20Gi + resources: + requests: + memory: "512Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "500m" + perNodeServices: + apiService: + enabled: true + type: NodePort diff --git a/operator/Dockerfile.local b/operator/Dockerfile.local new file mode 100644 index 00000000..f1f226b9 --- /dev/null +++ b/operator/Dockerfile.local @@ -0,0 +1,25 @@ +# Dockerfile.local - Fast local image build using pre-built binary +# Usage: docker build -f Dockerfile.local -t datahaven:local . +# Prerequisite: cargo build --release (or --release --features fast-runtime) + +FROM docker.io/parity/base-bin:latest + +# Copy the pre-built binary from local target directory +COPY ./target/release/datahaven-node /usr/local/bin/datahaven-node + +# Make sure binary is executable +RUN chmod +x /usr/local/bin/datahaven-node + +USER root +RUN useradd -m -u 1001 -U -s /bin/sh -d /datahaven datahaven && \ + mkdir -p /data /datahaven/.local/share && \ + chown -R datahaven:datahaven /data && \ + ln -s /data /datahaven/.local/share/datahaven && \ + /usr/local/bin/datahaven-node --version + +USER datahaven + +EXPOSE 30333 9933 9944 9615 +VOLUME ["/data"] + +ENTRYPOINT ["/usr/local/bin/datahaven-node"] diff --git a/test/cli/handlers/deploy/index.ts b/test/cli/handlers/deploy/index.ts index c6fea896..ca88ce32 100644 --- a/test/cli/handlers/deploy/index.ts +++ b/test/cli/handlers/deploy/index.ts @@ -9,6 +9,7 @@ import { deployDataHavenSolochain } from "./datahaven"; import { deployKurtosis } from "./kurtosis"; import { setParametersFromCollection } from "./parameters"; import { deployRelayers } from "./relayer"; +import { deployStorageHubComponents } from "./storagehub"; import { performValidatorOperations } from "./validator"; // Non-optional properties determined by having default values @@ -39,6 +40,7 @@ export interface DeployOptions { skipValidatorOperations: boolean; skipSetParameters: boolean; skipRelayers: boolean; + skipStorageHub: boolean; } const deployFunction = async (options: DeployOptions, launchedNetwork: LaunchedNetwork) => { @@ -82,6 +84,8 @@ const deployFunction = async (options: DeployOptions, launchedNetwork: LaunchedN await deployRelayers(options, launchedNetwork); + await deployStorageHubComponents(options, launchedNetwork); + // Cleaning up the port forwarding for the validator. await validatorPortForwardCleanup(); diff --git a/test/cli/handlers/deploy/storagehub.ts b/test/cli/handlers/deploy/storagehub.ts new file mode 100644 index 00000000..9422268d --- /dev/null +++ b/test/cli/handlers/deploy/storagehub.ts @@ -0,0 +1,216 @@ +import path from "node:path"; +import { $ } from "bun"; +import invariant from "tiny-invariant"; +import { logger, printDivider, printHeader } from "utils"; +import { waitFor } from "utils/waits"; +import { isNetworkReady } from "../../../launcher/datahaven"; +import type { LaunchedNetwork } from "../../../launcher/types/launchedNetwork"; +import { forwardPort } from "../common/kubernetes"; +import type { DeployOptions } from "."; + +/** + * Deploys StorageHub components (MSP, BSP, Indexer, Fisherman nodes and databases) in a Kubernetes namespace. + * + * @param options - Configuration options for launching the network. + * @param launchedNetwork - An instance of LaunchedNetwork to track the network's state. + * @returns A promise that resolves when all StorageHub components are deployed. + */ +export const deployStorageHubComponents = async ( + options: DeployOptions, + launchedNetwork: LaunchedNetwork +): Promise => { + if (options.skipStorageHub) { + logger.info("🏳️ Skipping StorageHub components deployment"); + printDivider(); + return; + } + + printHeader("Deploying StorageHub Components"); + + invariant(options.datahavenImageTag, "❌ DataHaven image tag not defined"); + + if (!options.dockerUsername) { + await checkTagExists(options.datahavenImageTag); + } + + // Deploy StorageHub Indexer database first (Indexer PostgreSQL database) + await deployStorageHubDatabase(options, launchedNetwork); + + // Deploy StorageHub nodes (MSP, BSP, Indexer, Fisherman) + await deployStorageHubNodes(options, launchedNetwork); + + // Deploy StorageHub MSP Backend API + await deployStorageHubBackend(options, launchedNetwork); + + await registerStorageHubNodes(launchedNetwork); + + printDivider(); +}; + +/** + * Deploys StorageHub PostgreSQL databases for Indexer and Fisherman nodes. + */ +const deployStorageHubDatabase = async ( + options: DeployOptions, + launchedNetwork: LaunchedNetwork +): Promise => { + logger.info("🗄️ Deploying StorageHub PostgreSQL database..."); + + const deployDatabase = async (name: string, component: string) => { + const timeout = "3m"; + const args = [ + "upgrade", + "--install", + name, + "oci://registry-1.docker.io/bitnamicharts/postgresql", + "-f", + `environments/${options.environment}/${component}-db.yaml`, + "-n", + launchedNetwork.kubeNamespace, + "--wait", + "--timeout", + timeout + ]; + + logger.info(`📦 Deploying ${name} database...`); + logger.debug(await $`helm ${args}`.cwd(path.join(process.cwd(), "../deploy")).text()); + logger.success(`${name} database deployed successfully`); + }; + + // Deploy Indexer database + await deployDatabase("sh-indexer-db", "sh-idxnode"); +}; + +/** + * Deploys StorageHub nodes (MSP, BSP, Indexer, Fisherman). + */ +const deployStorageHubNodes = async ( + options: DeployOptions, + launchedNetwork: LaunchedNetwork +): Promise => { + logger.info("🚀 Deploying StorageHub nodes..."); + + const deployNode = async (name: string, component: string) => { + const timeout = "5m"; + const args = [ + "upgrade", + "--install", + name, + "charts/node", + "-f", + `charts/node/storagehub/${component}.yaml`, + "-f", + `environments/${options.environment}/${component}.yaml`, + "-n", + launchedNetwork.kubeNamespace, + "--wait", + "--timeout", + timeout + ]; + + logger.info(`🏗️ Deploying ${name}...`); + logger.debug(await $`helm ${args}`.cwd(path.join(process.cwd(), "../deploy")).text()); + logger.success(`${name} deployed successfully`); + }; + + // Deploy StorageHub nodes in dependency order + await deployNode("sh-mspnode", "sh-mspnode"); + await deployNode("sh-bspnode", "sh-bspnode"); + await deployNode("sh-idxnode", "sh-idxnode"); + await deployNode("sh-fisherman", "sh-fisherman"); +}; + +/** + * Deploys StorageHub MSP Backend API. + */ +const deployStorageHubBackend = async ( + options: DeployOptions, + launchedNetwork: LaunchedNetwork +): Promise => { + logger.info("🚀 Deploying StorageHub MSP Backend API..."); + + const timeout = "3m"; + const args = [ + "upgrade", + "--install", + "sh-mspbackend", + "charts/backend", + "-f", + "charts/backend/storagehub/sh-mspbackend.yaml", + "-f", + `environments/${options.environment}/sh-mspbackend.yaml`, + "-n", + launchedNetwork.kubeNamespace, + "--wait", + "--timeout", + timeout + ]; + + logger.debug(await $`helm ${args}`.cwd(path.join(process.cwd(), "../deploy")).text()); + logger.success("StorageHub MSP Backend API deployed successfully"); +}; + +/** + * Waits for StorageHub Indexer node to be ready and registers nodes in LaunchedNetwork. + */ +const registerStorageHubNodes = async (launchedNetwork: LaunchedNetwork): Promise => { + // Forward port from indexer node to localhost for health checks + const indexerPort = 9944; + const { cleanup: indexerPortForwardCleanup } = await forwardPort( + "sh-idxnode-0", + indexerPort, + indexerPort + 100, // Use different local port to avoid conflicts + launchedNetwork + ); + + // Wait for the StorageHub Indexer to start + logger.info("⌛️ Waiting for StorageHub Indexer to start..."); + const timeoutMs = 5000; // 5 second timeout + const delayMs = 5000; // 5 second delay between iterations + await waitFor({ + lambda: async () => { + logger.info(`📡 Checking if StorageHub Indexer is ready (timeout: ${timeoutMs / 1000}s)...`); + const isReady = await isNetworkReady(indexerPort + 100, timeoutMs); + if (!isReady) { + logger.info( + `⌛️ StorageHub Indexer not ready, waiting ${delayMs / 1000}s to check again...` + ); + } + return isReady; + }, + iterations: 12, // 12 iterations of 5 + 5 = 2 minutes + delay: delayMs, + errorMessage: "StorageHub Indexer not ready" + }); + + logger.success("StorageHub Indexer is ready"); + + // Clean up the port forwarding + await indexerPortForwardCleanup(); + + // Register StorageHub nodes in LaunchedNetwork + launchedNetwork.addContainer("sh-mspnode-0", { ws: 9944 }); + launchedNetwork.addContainer("sh-bspnode-0", { ws: 9944 }); + launchedNetwork.addContainer("sh-idxnode-0", { ws: 9944 }); + launchedNetwork.addContainer("sh-fisherman-0", { ws: 9944 }); + + logger.info("📝 StorageHub nodes successfully registered in launchedNetwork."); +}; + +/** + * Checks if an image exists in Docker Hub. + * + * @param tag - The tag of the image to check. + * @returns A promise that resolves when the image is found. + */ +const checkTagExists = async (tag: string) => { + const cleanTag = tag.trim(); + logger.debug(`Checking if image ${cleanTag} is available on Docker Hub`); + const result = await $`docker manifest inspect ${cleanTag}`.nothrow().quiet(); + invariant( + result.exitCode === 0, + `❌ Image ${tag} not found.\n Does this image exist?\n Are you logged and have access to the repository?` + ); + + logger.success(`Image ${cleanTag} found on Docker Hub`); +}; diff --git a/test/cli/handlers/launch/index.ts b/test/cli/handlers/launch/index.ts index 307fcabf..4d0e79a0 100644 --- a/test/cli/handlers/launch/index.ts +++ b/test/cli/handlers/launch/index.ts @@ -10,6 +10,7 @@ import { launchDataHavenSolochain } from "./datahaven"; import { launchKurtosis } from "./kurtosis"; import { setParametersFromCollection } from "./parameters"; import { launchRelayers } from "./relayer"; +import { launchStorageHubComponents } from "./storagehub"; import { performSummaryOperations } from "./summary"; import { performValidatorOperations } from "./validator"; @@ -44,6 +45,7 @@ export interface LaunchOptions { setParameters?: boolean; relayer?: boolean; relayerImageTag: string; + storagehub?: boolean; cleanNetwork?: boolean; } @@ -94,6 +96,8 @@ const launchFunction = async (options: LaunchOptions, launchedNetwork: LaunchedN await launchRelayers(options, launchedNetwork); + await launchStorageHubComponents(options, launchedNetwork); + await performSummaryOperations(options, launchedNetwork); const fullEnd = performance.now(); const fullMinutes = ((fullEnd - timeStart) / (1000 * 60)).toFixed(1); @@ -119,7 +123,8 @@ export const launchPreActionHook = ( buildDatahaven, launchKurtosis, relayer, - setParameters + setParameters, + storagehub } = thisCmd.opts(); // Check for conflicts with --all flag @@ -132,10 +137,11 @@ export const launchPreActionHook = ( fundValidators === false || setupValidators === false || setParameters === false || - relayer === false) + relayer === false || + storagehub === false) ) { thisCmd.error( - "--all cannot be used with --no-datahaven, --no-build-datahaven, --no-launch-kurtosis, --no-deploy-contracts, --no-fund-validators, --no-setup-validators, --no-update-validator-set, --no-set-parameters, or --no-relayer" + "--all cannot be used with --no-datahaven, --no-build-datahaven, --no-launch-kurtosis, --no-deploy-contracts, --no-fund-validators, --no-setup-validators, --no-update-validator-set, --no-set-parameters, --no-relayer, or --no-storagehub" ); } @@ -149,6 +155,7 @@ export const launchPreActionHook = ( thisCmd.setOptionValue("setupValidators", true); thisCmd.setOptionValue("setParameters", true); thisCmd.setOptionValue("relayer", true); + thisCmd.setOptionValue("storagehub", true); thisCmd.setOptionValue("cleanNetwork", true); } diff --git a/test/cli/handlers/launch/storagehub.ts b/test/cli/handlers/launch/storagehub.ts new file mode 100644 index 00000000..13d8ff46 --- /dev/null +++ b/test/cli/handlers/launch/storagehub.ts @@ -0,0 +1,36 @@ +import { logger, printHeader } from "utils"; +import type { LaunchedNetwork } from "../../../launcher/types/launchedNetwork"; +import { deployStorageHubComponents } from "../deploy/storagehub"; +import type { LaunchOptions } from "."; + +/** + * Launches StorageHub components by delegating to the deploy function. + * + * @param options - Launch options. + * @param launchedNetwork - The launched network instance. + * @returns A promise that resolves when StorageHub components are launched. + */ +export const launchStorageHubComponents = async ( + options: LaunchOptions, + launchedNetwork: LaunchedNetwork +): Promise => { + // Convert launch options to deploy options format + const deployOptions = { + environment: "local" as const, // Launch is typically used for local development + skipStorageHub: !options.storagehub, + datahavenImageTag: options.datahavenImageTag, + dockerUsername: undefined, + dockerPassword: undefined, + dockerEmail: undefined + }; + + printHeader("Launching StorageHub Components"); + logger.info( + "🚀 Launching StorageHub components (MSP, BSP, Indexer, Fisherman nodes and databases)..." + ); + + // Reuse the deploy StorageHub function + await deployStorageHubComponents(deployOptions as any, launchedNetwork); + + logger.success("StorageHub components launched successfully"); +}; diff --git a/test/cli/index.ts b/test/cli/index.ts index 68f708c8..c3f44174 100644 --- a/test/cli/index.ts +++ b/test/cli/index.ts @@ -49,7 +49,7 @@ program `🫎 DataHaven: Network Deployer CLI for deploying a full DataHaven network stack to a Kubernetes cluster It will deploy: - DataHaven solochain validators (all envs), - - Storage providers (all envs) (TODO), + - StorageHub components: MSP, BSP, Indexer, Fisherman nodes and databases (local & stagenet envs), - Kurtosis Ethereum private network (stagenet env), - Snowbridge Relayers (all envs) ` @@ -98,6 +98,11 @@ program .option("--skip-validator-operations", "Skip performing validator operations", false) .option("--skip-set-parameters", "Skip setting DataHaven runtime parameters", false) .option("--skip-relayers", "Skip deploying Snowbridge Relayers", false) + .option( + "--skip-storage-hub", + "Skip deploying StorageHub components (MSP, BSP, Indexer, Fisherman, databases)", + false + ) .hook("preAction", deployPreActionHook) .action(deploy); @@ -109,7 +114,7 @@ program `🫎 DataHaven: Network Launcher CLI for launching a full DataHaven network. Complete with: - Solo-chain validators, - - Storage providers (TODO), + - StorageHub components: MSP, BSP, Indexer, Fisherman nodes and databases, - Ethereum Private network, - Snowbridge Relayers `