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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/helm-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Helm Release

# Publishes the vesting-backend Helm chart to GitHub Pages.
# Triggers on tags matching helm-v* (e.g. helm-v0.1.0).
on:
push:
tags:
- 'helm-v*'
# Allow manual trigger for initial publish
workflow_dispatch:

jobs:
release:
name: Package & Publish Helm Chart
runs-on: ubuntu-latest
permissions:
contents: write # push to gh-pages branch
pages: write
id-token: write

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Helm
uses: azure/setup-helm@v4
with:
version: '3.14.0'

- name: Package chart
run: |
mkdir -p docs/helm-charts
helm package helm/vesting-backend --destination docs/helm-charts/

- name: Update Helm repo index
run: |
helm repo index docs/helm-charts/ \
--url https://faveteamz.github.io/workload-governor/helm-charts \
--merge docs/helm-charts/index.yaml

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Commit and push updated index
run: |
git add docs/helm-charts/
git commit -m "chore: publish Helm chart ${{ github.ref_name }}" || echo "Nothing to commit"
git push origin HEAD:main

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/helm-charts
destination_dir: helm-charts
keep_files: true
3 changes: 3 additions & 0 deletions docs/helm-charts/index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
apiVersion: v1
entries: {}
generated: "2026-07-01T00:00:00Z"
20 changes: 20 additions & 0 deletions helm/vesting-backend/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v2
name: vesting-backend
description: |
Helm chart for the WorkloadGovernor vesting-backend service.
Deploys the Node.js API server that proxies Soroban contract calls,
alongside an HPA, Ingress, ConfigMap, and ExternalSecret integration.
type: application
version: 0.1.0
appVersion: "1.0.0"
keywords:
- workload-governor
- stellar
- soroban
- vesting
home: https://github.com/FaveTeamz/workload-governor
sources:
- https://github.com/FaveTeamz/workload-governor
maintainers:
- name: FaveTeamz
url: https://github.com/FaveTeamz
204 changes: 204 additions & 0 deletions helm/vesting-backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# vesting-backend Helm Chart

Deploys the **WorkloadGovernor vesting-backend** service on any Kubernetes cluster.
The chart includes a `Deployment`, `Service`, `Ingress`, `HPA`, `ConfigMap`, and
an `ExternalSecret` (via the [External Secrets Operator](https://external-secrets.io/)).

## Prerequisites

| Requirement | Version |
|---|---|
| Kubernetes | ≥ 1.25 |
| Helm | ≥ 3.10 |
| [External Secrets Operator](https://external-secrets.io/) | ≥ 0.9 (optional, disable with `externalSecrets.enabled=false`) |
| metrics-server | ≥ 0.6 (required for memory-based HPA) |

---

## Install

```bash
# Add the Helm repo (GitHub Pages)
helm repo add workload-governor https://faveteamz.github.io/workload-governor
helm repo update

# Dry-run first
helm install vesting-backend workload-governor/vesting-backend \
--dry-run --debug \
--set config.CONTRACT_ID=<YOUR_CONTRACT_ID>

# Install
helm install vesting-backend workload-governor/vesting-backend \
--namespace vesting \
--create-namespace \
--set config.CONTRACT_ID=<YOUR_CONTRACT_ID>
```

### Local (kind) install

```bash
# Create a local cluster
kind create cluster --name wg-dev

# Install from local chart directory
helm install vesting-backend ./helm/vesting-backend \
--namespace vesting \
--create-namespace \
--set externalSecrets.enabled=false \
--set config.CONTRACT_ID=<CONTRACT_ID> \
--set ingress.enabled=false

# Verify
kubectl get all -n vesting
helm test vesting-backend -n vesting
```

---

## Upgrade

```bash
helm upgrade vesting-backend workload-governor/vesting-backend \
--namespace vesting \
--reuse-values \
--set image.tag=<NEW_TAG>
```

---

## Uninstall

```bash
helm uninstall vesting-backend --namespace vesting
```

---

## Configuration

All values can be overridden with `--set key=value` or a custom `-f values.yaml` file.

### Core

| Key | Default | Description |
|---|---|---|
| `replicaCount` | `2` | Number of pod replicas (ignored when HPA enabled) |
| `image.repository` | `ghcr.io/faveteamz/workload-governor` | Container image |
| `image.tag` | `""` (uses `appVersion`) | Image tag |
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
| `nameOverride` | `""` | Partial chart name override |
| `fullnameOverride` | `""` | Full release name override |

### Service

| Key | Default | Description |
|---|---|---|
| `service.type` | `ClusterIP` | Service type |
| `service.port` | `3000` | Service port |
| `service.targetPort` | `3000` | Container port |

### Ingress

| Key | Default | Description |
|---|---|---|
| `ingress.enabled` | `true` | Create Ingress resource |
| `ingress.className` | `nginx` | Ingress class |
| `ingress.annotations` | See values.yaml | Custom annotations |
| `ingress.hosts` | `vesting-backend.example.com` | Hostname rules |
| `ingress.tls` | See values.yaml | TLS secret mappings |

### HPA

| Key | Default | Description |
|---|---|---|
| `autoscaling.enabled` | `true` | Enable HPA |
| `autoscaling.minReplicas` | `2` | Minimum replicas |
| `autoscaling.maxReplicas` | `10` | Maximum replicas |
| `autoscaling.targetCPUUtilizationPercentage` | `70` | Target CPU % |
| `autoscaling.targetMemoryUtilizationPercentage` | `80` | Target memory % |

### Application Config (ConfigMap)

| Key | Default | Description |
|---|---|---|
| `config.STELLAR_NETWORK` | `testnet` | Stellar network |
| `config.SOROBAN_RPC_URL` | `https://soroban-testnet.stellar.org` | RPC endpoint |
| `config.HORIZON_URL` | `https://horizon-testnet.stellar.org` | Horizon endpoint |
| `config.CONTRACT_ID` | `""` | Deployed contract ID — **must be set** |
| `config.NODE_ENV` | `production` | Node environment |
| `config.PORT` | `3000` | Listening port |
| `config.LOG_LEVEL` | `info` | Log verbosity |

### External Secrets

| Key | Default | Description |
|---|---|---|
| `externalSecrets.enabled` | `true` | Enable ExternalSecret resource |
| `externalSecrets.secretStoreName` | `aws-secrets-manager` | SecretStore name |
| `externalSecrets.secretStoreKind` | `ClusterSecretStore` | Store kind |
| `externalSecrets.refreshInterval` | `1h` | Refresh interval |
| `externalSecrets.secrets` | See values.yaml | Secret key mappings |

Secrets synced by default:

| Env Var | AWS Secrets Manager Path |
|---|---|
| `ADMIN_TOKEN` | `workload-governor/admin-token` |
| `STELLAR_SECRET_KEY` | `workload-governor/stellar-secret-key` |
| `DATABASE_URL` | `workload-governor/database-url` |
| `REDIS_URL` | `workload-governor/redis-url` |

To disable and use a plain Kubernetes Secret instead:

```bash
helm install vesting-backend ./helm/vesting-backend \
--set externalSecrets.enabled=false
```

Then create the secret manually:

```bash
kubectl create secret generic vesting-backend-secrets \
--from-literal=ADMIN_TOKEN=<token> \
--from-literal=STELLAR_SECRET_KEY=<key> \
--from-literal=DATABASE_URL=<url> \
--from-literal=REDIS_URL=<url> \
--namespace vesting
```

### Resources

| Key | Default |
|---|---|
| `resources.requests.cpu` | `100m` |
| `resources.requests.memory` | `128Mi` |
| `resources.limits.cpu` | `500m` |
| `resources.limits.memory` | `512Mi` |

---

## Publishing to GitHub Pages

The chart is published as a Helm repository on GitHub Pages.

```bash
# From repo root — package and index
helm package helm/vesting-backend --destination docs/helm-charts/
helm repo index docs/helm-charts/ --url https://faveteamz.github.io/workload-governor/helm-charts

# Push and the gh-pages workflow will deploy automatically
git add docs/helm-charts/
git commit -m "chore: publish vesting-backend chart"
git push
```

The GitHub Actions workflow at `.github/workflows/helm-release.yml` automates this on every tag push matching `helm-v*`.

---

## Runbooks

- [Contract upgrade](../../docs/runbooks/contract-upgrade.md)
- [Admin key rotation](../../docs/runbooks/admin-key-rotation.md)
- [Cap emergency increase](../../docs/runbooks/cap-emergency-increase.md)
- [Incident response](../../docs/runbooks/incident-response.md)
32 changes: 32 additions & 0 deletions helm/vesting-backend/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
🚀 vesting-backend deployed successfully!

Release: {{ .Release.Name }}
Namespace: {{ .Release.Namespace }}
Version: {{ include "vesting-backend.imageTag" . }}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ACCESS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
https://{{ .host }}/health
{{- end }}
{{- else }}
kubectl port-forward svc/{{ include "vesting-backend.fullname" . }} 3000:{{ .Values.service.port }} -n {{ .Release.Namespace }}
Then visit: http://localhost:3000/health
{{- end }}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
NEXT STEPS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Ensure a ClusterSecretStore named "{{ .Values.externalSecrets.secretStoreName }}" exists
(or disable ExternalSecrets with --set externalSecrets.enabled=false).

2. Set your CONTRACT_ID:
helm upgrade {{ .Release.Name }} . --set config.CONTRACT_ID=<YOUR_CONTRACT_ID>

3. Confirm pods are running:
kubectl get pods -l app.kubernetes.io/name={{ include "vesting-backend.name" . }} -n {{ .Release.Namespace }}

4. Check logs:
kubectl logs -l app.kubernetes.io/name={{ include "vesting-backend.name" . }} -n {{ .Release.Namespace }} --tail=50
68 changes: 68 additions & 0 deletions helm/vesting-backend/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "vesting-backend.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.
*/}}
{{- define "vesting-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 label (chart name + version, safe for label values).
*/}}
{{- define "vesting-backend.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels applied to every resource.
*/}}
{{- define "vesting-backend.labels" -}}
helm.sh/chart: {{ include "vesting-backend.chart" . }}
{{ include "vesting-backend.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels used by Deployments and Services.
*/}}
{{- define "vesting-backend.selectorLabels" -}}
app.kubernetes.io/name: {{ include "vesting-backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Resolved service account name.
*/}}
{{- define "vesting-backend.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "vesting-backend.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Resolved image tag (falls back to chart appVersion).
*/}}
{{- define "vesting-backend.imageTag" -}}
{{- .Values.image.tag | default .Chart.AppVersion }}
{{- end }}
Loading