From 1a2a8148a2b0c7915387c45770c4b845f7fc547c Mon Sep 17 00:00:00 2001 From: mikkeldamsgaard Date: Thu, 26 Feb 2026 21:32:07 +0100 Subject: [PATCH] chore: add OCI publishing, ArtifactHub metadata, badges, and security policy Add distribution and discoverability infrastructure: - Release workflow: on tag push, packages chart and pushes to oci://ghcr.io/kitstream/helms, creates GitHub Release with artifact - ArtifactHub: annotations in Chart.yaml + repo metadata file - README overhaul: badges, OCI install instructions, value proposition - SECURITY.md: vulnerability reporting and security practices summary - Chart.yaml: icon, expanded keywords, source links Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yaml | 62 +++++++ CHANGELOG.md | 12 ++ README.md | 73 ++++---- SECURITY.md | 42 +++++ artifacthub-repo.yml | 4 + charts/netbird/Chart.yaml | 23 +++ charts/netbird/README.md | 323 +++++++++++++++++---------------- 7 files changed, 353 insertions(+), 186 deletions(-) create mode 100644 .github/workflows/release.yaml create mode 100644 SECURITY.md create mode 100644 artifacthub-repo.yml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..b2c07ec --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,62 @@ +name: Release + +on: + push: + tags: + - '*-[0-9]*.[0-9]*.[0-9]*' + +permissions: + contents: write + packages: write + +jobs: + release: + name: Package & Publish + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v4.0.2 + + - name: Parse tag + id: parse + run: | + TAG="${GITHUB_REF_NAME}" + VERSION=$(echo "$TAG" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+$') + CHART="${TAG%-"${VERSION}"}" + echo "chart=${CHART}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Validate chart exists + run: | + if [ ! -f "charts/${{ steps.parse.outputs.chart }}/Chart.yaml" ]; then + echo "::error::Chart directory charts/${{ steps.parse.outputs.chart }} not found" + exit 1 + fi + + - name: Validate chart version matches tag + run: | + CHART_VERSION=$(grep '^version:' "charts/${{ steps.parse.outputs.chart }}/Chart.yaml" | awk '{print $2}') + if [ "$CHART_VERSION" != "${{ steps.parse.outputs.version }}" ]; then + echo "::error::Tag version (${{ steps.parse.outputs.version }}) does not match Chart.yaml version ($CHART_VERSION)" + exit 1 + fi + + - name: Login to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Package chart + run: helm package "charts/${{ steps.parse.outputs.chart }}" + + - name: Push to OCI registry + run: helm push "${{ steps.parse.outputs.chart }}-${{ steps.parse.outputs.version }}.tgz" oci://ghcr.io/kitstream/helms + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: "${{ steps.parse.outputs.chart }}-${{ steps.parse.outputs.version }}.tgz" + generate_release_notes: true diff --git a/CHANGELOG.md b/CHANGELOG.md index f89fe36..b2b884a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ### Added +- **OCI registry publishing**: Charts are published to `oci://ghcr.io/kitstream/helms` + on tagged releases via GitHub Actions. Install with + `helm install netbird oci://ghcr.io/kitstream/helms/netbird --version `. +- **GitHub Releases**: Tagged releases automatically create GitHub Releases with + packaged chart artifacts and auto-generated release notes. +- **ArtifactHub metadata**: Chart.yaml includes ArtifactHub annotations for + discoverability on [artifacthub.io](https://artifacthub.io). +- **SECURITY.md**: Security policy with vulnerability reporting instructions and + a summary of the project's security practices. +- **README badges**: CI status, release status, license, and ArtifactHub badges + on both the repository and chart READMEs. + - **PAT seeding**: Optional Personal Access Token seeding via `pat.*` values. When `pat.enabled: true`, a service user account and PAT are seeded into the database using Initium's `seed` command. The seed waits for the server diff --git a/README.md b/README.md index e4bcf5b..0499396 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,64 @@ # Helms -A curated collection of Helm charts maintained by [KitStream](https://github.com/KitStream). This repository serves as a home for both green-field Helm charts and upgraded or improved charts sourced from the community. +[![CI](https://github.com/KitStream/helms/actions/workflows/ci.yaml/badge.svg)](https://github.com/KitStream/helms/actions/workflows/ci.yaml) +[![Release](https://github.com/KitStream/helms/actions/workflows/release.yaml/badge.svg)](https://github.com/KitStream/helms/actions/workflows/release.yaml) +[![License](https://img.shields.io/github/license/KitStream/helms)](LICENSE) +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/kitstream)](https://artifacthub.io/packages/search?repo=kitstream) -## Repository Structure +Production-ready Helm charts for self-hosted infrastructure, maintained by [KitStream](https://github.com/KitStream). -``` -helms/ -├── charts/ # Individual Helm charts, each in its own subdirectory -├── CONTRIBUTING.md # Contribution guidelines -├── LICENSE # Apache License 2.0 -└── README.md # This file -``` - -Each chart lives in its own directory under `charts/` and follows the standard [Helm chart structure](https://helm.sh/docs/topics/charts/#the-chart-file-structure): +## Charts -``` -charts// -├── Chart.yaml # Chart metadata -├── values.yaml # Default configuration values -├── templates/ # Kubernetes manifest templates -├── charts/ # Sub-chart dependencies -└── README.md # Chart-specific documentation -``` +| Chart | Description | Version | +|----------------------------|-----------------------------------------------------------------------------------------------|---------| +| [netbird](charts/netbird/) | Deploy [NetBird](https://netbird.io) VPN (management, signal, dashboard, relay) on Kubernetes | `0.1.0` | -## Prerequisites +## Quick Start -- [Helm](https://helm.sh/docs/intro/install/) v3.x -- [kubectl](https://kubernetes.io/docs/tasks/tools/) configured for your target cluster +### Install from OCI Registry -## Usage +```bash +helm install netbird oci://ghcr.io/kitstream/helms/netbird \ + --version 0.1.0 \ + -n netbird --create-namespace \ + -f my-values.yaml +``` -To install a chart from this repository: +### Install from Source ```bash -# Clone the repository git clone https://github.com/KitStream/helms.git +helm install netbird helms/charts/netbird \ + -n netbird --create-namespace \ + -f my-values.yaml +``` -# Install a chart -helm install helms/charts/ +See each chart's README for detailed configuration. -# Install with custom values -helm install helms/charts/ -f my-values.yaml -``` +## What Makes These Charts Different + +- **No shell in init containers** — Uses [Initium](https://github.com/KitStream/initium) (FROM scratch) instead of Alpine + shell scripts. No package manager, no shell escaping issues, smaller attack surface. +- **Hardened by default** — Non-root, read-only root filesystem, all capabilities dropped, no privilege escalation. +- **Structured configuration** — No raw DSN strings. Provide `database.host`, `database.user`, etc. and the chart builds it for you. +- **Automatic database readiness** — Init containers wait for your database and create it if it doesn't exist. No manual setup, no race conditions. +- **Comprehensive testing** — Unit tests (helm-unittest) + E2E tests across SQLite, PostgreSQL, and MySQL backends on every PR. + +## Prerequisites + +- [Helm](https://helm.sh/docs/intro/install/) v3.8+ (OCI support) +- [kubectl](https://kubernetes.io/docs/tasks/tools/) configured for your target cluster +- Kubernetes 1.24+ (1.28+ for SQLite PAT seeding) ## Contributing We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) before submitting pull requests. +## Security + +See [SECURITY.md](SECURITY.md) for our security policy and reporting instructions. + ## License -This project is licensed under the Apache License 2.0 — see the [LICENSE](LICENSE) file for details. +Apache License 2.0 — see [LICENSE](LICENSE). Copyright 2026 KitStream - diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9ce840c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,42 @@ +# Security Policy + +## Supported Versions + +| Chart | Version | Supported | +|---------|---------|-----------| +| netbird | 0.1.x | Yes | + +## Reporting a Vulnerability + +If you discover a security vulnerability in this project, please report it responsibly. + +**Do not open a public GitHub issue for security vulnerabilities.** + +Instead, please email security reports to the maintainers via [GitHub's private vulnerability reporting](https://github.com/KitStream/helms/security/advisories/new). + +Include: + +- A description of the vulnerability +- Steps to reproduce +- Affected chart(s) and version(s) +- Any potential impact + +We will acknowledge receipt within 48 hours and aim to provide a fix or mitigation within 7 days for critical issues. + +## Security Practices + +This project follows security best practices for Helm charts: + +- **Non-root containers**: All containers run as non-root by default +- **Read-only root filesystem**: Init containers use read-only root filesystems +- **No privilege escalation**: `allowPrivilegeEscalation: false` on all containers +- **Minimal capabilities**: All Linux capabilities are dropped unless explicitly required +- **Secret management**: Sensitive values are injected via Kubernetes Secrets, never hardcoded +- **No shell in init containers**: Uses [Initium](https://github.com/KitStream/initium) (FROM scratch image) instead of shell-based init containers +- **Pinned image versions**: All image tags are pinned to specific versions + +## Upstream Vulnerabilities + +For vulnerabilities in the upstream applications (e.g., NetBird), please report them to the respective upstream projects: + +- NetBird: https://github.com/netbirdio/netbird/security diff --git a/artifacthub-repo.yml b/artifacthub-repo.yml new file mode 100644 index 0000000..bfaf8d7 --- /dev/null +++ b/artifacthub-repo.yml @@ -0,0 +1,4 @@ +repositoryID: null # Assigned by ArtifactHub after registration +owners: + - name: KitStream + email: kitstream@users.noreply.github.com diff --git a/charts/netbird/Chart.yaml b/charts/netbird/Chart.yaml index 9dffc5d..b09013f 100644 --- a/charts/netbird/Chart.yaml +++ b/charts/netbird/Chart.yaml @@ -9,10 +9,33 @@ keywords: - vpn - wireguard - mesh + - networking + - self-hosted + - kubernetes home: https://netbird.io sources: - https://github.com/netbirdio/netbird + - https://github.com/KitStream/helms maintainers: - name: KitStream url: https://github.com/KitStream +icon: https://raw.githubusercontent.com/netbirdio/netbird/main/docs/media/logo-full.png +annotations: + artifacthub.io/license: Apache-2.0 + artifacthub.io/links: | + - name: NetBird + url: https://netbird.io + - name: Source + url: https://github.com/KitStream/helms + - name: Initium + url: https://github.com/KitStream/initium + artifacthub.io/changes: | + - kind: added + description: PAT seeding with automatic SHA256 hash computation + - kind: added + description: Structured database configuration (database.*) + - kind: added + description: Initium init containers for database wait and seed + - kind: changed + description: "Breaking: removed raw DSN in favor of structured database config" diff --git a/charts/netbird/README.md b/charts/netbird/README.md index 46c71e1..2ad302f 100644 --- a/charts/netbird/README.md +++ b/charts/netbird/README.md @@ -1,15 +1,19 @@ # NetBird Helm Chart +[![CI](https://github.com/KitStream/helms/actions/workflows/ci.yaml/badge.svg)](https://github.com/KitStream/helms/actions/workflows/ci.yaml) +[![Chart Version](https://img.shields.io/badge/chart-0.1.0-blue)](https://github.com/KitStream/helms/releases) +[![App Version](https://img.shields.io/badge/netbird-0.65.3-green)](https://github.com/netbirdio/netbird) + A Helm chart for deploying [NetBird](https://netbird.io) VPN management, signal, dashboard, and relay services on Kubernetes. ## Overview This chart deploys the NetBird self-hosted stack as two components: -| Component | Description | -|-----------|-------------| -| **Server** | Combined binary running Management API, Signal, Relay, and STUN services on a single HTTP port | -| **Dashboard** | Web UI for managing peers, groups, routes, and access policies | +| Component | Description | +|---------------|------------------------------------------------------------------------------------------------| +| **Server** | Combined binary running Management API, Signal, Relay, and STUN services on a single HTTP port | +| **Dashboard** | Web UI for managing peers, groups, routes, and access policies | The server uses a single `config.yaml` that is rendered from a ConfigMap template with sensitive values injected at pod startup from Kubernetes Secrets via [Initium](https://github.com/KitStream/initium)'s `render` subcommand (envsubst mode). @@ -27,6 +31,17 @@ For external databases (PostgreSQL, MySQL), the chart automatically: ## Installation +### From OCI Registry (recommended) + +```bash +helm install netbird oci://ghcr.io/kitstream/helms/netbird \ + --version 0.1.0 \ + -n netbird --create-namespace \ + -f my-values.yaml +``` + +### From Source + ```bash helm install netbird ./charts/netbird \ -n netbird --create-namespace \ @@ -241,202 +256,202 @@ curl -H "Authorization: Token nbp_..." https://netbird.example.com/api/groups ### Global -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `nameOverride` | string | `""` | Override the chart name in resource names | -| `fullnameOverride` | string | `""` | Fully override the resource name prefix | -| `imagePullSecrets` | list | `[]` | Global image pull secrets for all pods | -| `serviceAccount.create` | bool | `true` | Create a ServiceAccount | -| `serviceAccount.annotations` | object | `{}` | ServiceAccount annotations | -| `serviceAccount.name` | string | `""` | ServiceAccount name override | +| Key | Type | Default | Description | +|------------------------------|--------|---------|-------------------------------------------| +| `nameOverride` | string | `""` | Override the chart name in resource names | +| `fullnameOverride` | string | `""` | Fully override the resource name prefix | +| `imagePullSecrets` | list | `[]` | Global image pull secrets for all pods | +| `serviceAccount.create` | bool | `true` | Create a ServiceAccount | +| `serviceAccount.annotations` | object | `{}` | ServiceAccount annotations | +| `serviceAccount.name` | string | `""` | ServiceAccount name override | ### Database -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `database.type` | string | `"sqlite"` | Database engine (`sqlite`, `postgresql`, `mysql`) | -| `database.host` | string | `""` | Database hostname (required for postgresql/mysql) | -| `database.port` | string | `""` | Database port (defaults: 5432 for postgresql, 3306 for mysql) | -| `database.user` | string | `""` | Database user (required for postgresql/mysql) | -| `database.name` | string | `""` | Database name (required for postgresql/mysql) | -| `database.passwordSecret.secretName` | string | `""` | Secret containing the database password | -| `database.passwordSecret.secretKey` | string | `"password"` | Key in the Secret | -| `database.sslMode` | string | `"disable"` | SSL mode for PostgreSQL (ignored for mysql/sqlite) | +| Key | Type | Default | Description | +|--------------------------------------|--------|--------------|---------------------------------------------------------------| +| `database.type` | string | `"sqlite"` | Database engine (`sqlite`, `postgresql`, `mysql`) | +| `database.host` | string | `""` | Database hostname (required for postgresql/mysql) | +| `database.port` | string | `""` | Database port (defaults: 5432 for postgresql, 3306 for mysql) | +| `database.user` | string | `""` | Database user (required for postgresql/mysql) | +| `database.name` | string | `""` | Database name (required for postgresql/mysql) | +| `database.passwordSecret.secretName` | string | `""` | Secret containing the database password | +| `database.passwordSecret.secretKey` | string | `"password"` | Key in the Secret | +| `database.sslMode` | string | `"disable"` | SSL mode for PostgreSQL (ignored for mysql/sqlite) | ### PAT (Personal Access Token) -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `pat.enabled` | bool | `false` | Enable PAT seeding via post-install Job | -| `pat.secret.secretName` | string | `""` | Kubernetes Secret containing the plaintext PAT | -| `pat.secret.tokenKey` | string | `"token"` | Key in Secret for the plaintext PAT | -| `pat.name` | string | `"helm-seeded-token"` | Display name for the PAT | -| `pat.userId` | string | `"helm-seed-user"` | User ID for the service user | -| `pat.accountId` | string | `"helm-seed-account"` | Account ID for the service user | -| `pat.expirationDays` | int | `365` | PAT expiration in days from deployment | +| Key | Type | Default | Description | +|-------------------------|--------|-----------------------|------------------------------------------------| +| `pat.enabled` | bool | `false` | Enable PAT seeding via post-install Job | +| `pat.secret.secretName` | string | `""` | Kubernetes Secret containing the plaintext PAT | +| `pat.secret.tokenKey` | string | `"token"` | Key in Secret for the plaintext PAT | +| `pat.name` | string | `"helm-seeded-token"` | Display name for the PAT | +| `pat.userId` | string | `"helm-seed-user"` | User ID for the service user | +| `pat.accountId` | string | `"helm-seed-account"` | Account ID for the service user | +| `pat.expirationDays` | int | `365` | PAT expiration in days from deployment | ### Server -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `server.replicaCount` | int | `1` | Number of server pod replicas | -| `server.image.repository` | string | `"netbirdio/netbird-server"` | Server image repository | -| `server.image.tag` | string | `""` (appVersion) | Server image tag | -| `server.image.pullPolicy` | string | `"IfNotPresent"` | Image pull policy | +| Key | Type | Default | Description | +|-------------------------------|--------|-------------------------------|------------------------------------------------------------------------| +| `server.replicaCount` | int | `1` | Number of server pod replicas | +| `server.image.repository` | string | `"netbirdio/netbird-server"` | Server image repository | +| `server.image.tag` | string | `""` (appVersion) | Server image tag | +| `server.image.pullPolicy` | string | `"IfNotPresent"` | Image pull policy | | `server.initImage.repository` | string | `"ghcr.io/kitstream/initium"` | Init container image ([Initium](https://github.com/KitStream/initium)) | -| `server.initImage.tag` | string | `"1.0.4"` | Init container image tag | -| `server.imagePullSecrets` | list | `[]` | Component-level pull secrets | +| `server.initImage.tag` | string | `"1.0.4"` | Init container image tag | +| `server.imagePullSecrets` | list | `[]` | Component-level pull secrets | #### Server Configuration -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `server.config.listenAddress` | string | `":80"` | Address and port the server listens on | -| `server.config.exposedAddress` | string | `""` | Public URL for peer connections | -| `server.config.stunPorts` | list | `[3478]` | UDP ports for the embedded STUN server | -| `server.config.metricsPort` | int | `9090` | Prometheus metrics port | -| `server.config.healthcheckAddress` | string | `":9000"` | Health check endpoint address | -| `server.config.logLevel` | string | `"info"` | Log verbosity (debug, info, warn, error) | -| `server.config.logFile` | string | `"console"` | Log output destination | -| `server.config.dataDir` | string | `"/var/lib/netbird"` | Data directory for state and DB | -| `server.config.auth.issuer` | string | `""` | OAuth2/OIDC issuer URL | -| `server.config.auth.signKeyRefreshEnabled` | bool | `true` | Auto-refresh IdP signing keys | -| `server.config.auth.dashboardRedirectURIs` | list | `[]` | Dashboard OAuth2 redirect URIs | -| `server.config.auth.cliRedirectURIs` | list | `["http://localhost:53000/"]` | CLI redirect URIs | +| Key | Type | Default | Description | +|--------------------------------------------|--------|-------------------------------|------------------------------------------| +| `server.config.listenAddress` | string | `":80"` | Address and port the server listens on | +| `server.config.exposedAddress` | string | `""` | Public URL for peer connections | +| `server.config.stunPorts` | list | `[3478]` | UDP ports for the embedded STUN server | +| `server.config.metricsPort` | int | `9090` | Prometheus metrics port | +| `server.config.healthcheckAddress` | string | `":9000"` | Health check endpoint address | +| `server.config.logLevel` | string | `"info"` | Log verbosity (debug, info, warn, error) | +| `server.config.logFile` | string | `"console"` | Log output destination | +| `server.config.dataDir` | string | `"/var/lib/netbird"` | Data directory for state and DB | +| `server.config.auth.issuer` | string | `""` | OAuth2/OIDC issuer URL | +| `server.config.auth.signKeyRefreshEnabled` | bool | `true` | Auto-refresh IdP signing keys | +| `server.config.auth.dashboardRedirectURIs` | list | `[]` | Dashboard OAuth2 redirect URIs | +| `server.config.auth.cliRedirectURIs` | list | `["http://localhost:53000/"]` | CLI redirect URIs | #### Server Secrets -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `server.secrets.authSecret.secretName` | string | `""` | Existing Secret name (empty = auto-generate) | -| `server.secrets.authSecret.secretKey` | string | `"authSecret"` | Key in the Secret | -| `server.secrets.authSecret.autoGenerate` | bool | `true` | Auto-generate on first install | -| `server.secrets.storeEncryptionKey.secretName` | string | `""` | Existing Secret name (empty = auto-generate) | -| `server.secrets.storeEncryptionKey.secretKey` | string | `"encryptionKey"` | Key in the Secret | -| `server.secrets.storeEncryptionKey.autoGenerate` | bool | `true` | Auto-generate on first install | +| Key | Type | Default | Description | +|--------------------------------------------------|--------|-------------------|----------------------------------------------| +| `server.secrets.authSecret.secretName` | string | `""` | Existing Secret name (empty = auto-generate) | +| `server.secrets.authSecret.secretKey` | string | `"authSecret"` | Key in the Secret | +| `server.secrets.authSecret.autoGenerate` | bool | `true` | Auto-generate on first install | +| `server.secrets.storeEncryptionKey.secretName` | string | `""` | Existing Secret name (empty = auto-generate) | +| `server.secrets.storeEncryptionKey.secretKey` | string | `"encryptionKey"` | Key in the Secret | +| `server.secrets.storeEncryptionKey.autoGenerate` | bool | `true` | Auto-generate on first install | #### Server Storage -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `server.persistentVolume.enabled` | bool | `true` | Create a PVC for server data | -| `server.persistentVolume.storageClass` | string | `""` | Storage class (empty = cluster default) | -| `server.persistentVolume.accessModes` | list | `["ReadWriteOnce"]` | PVC access modes | -| `server.persistentVolume.size` | string | `"1Gi"` | PVC size | -| `server.persistentVolume.annotations` | object | `{}` | PVC annotations | +| Key | Type | Default | Description | +|----------------------------------------|--------|---------------------|-----------------------------------------| +| `server.persistentVolume.enabled` | bool | `true` | Create a PVC for server data | +| `server.persistentVolume.storageClass` | string | `""` | Storage class (empty = cluster default) | +| `server.persistentVolume.accessModes` | list | `["ReadWriteOnce"]` | PVC access modes | +| `server.persistentVolume.size` | string | `"1Gi"` | PVC size | +| `server.persistentVolume.annotations` | object | `{}` | PVC annotations | #### Server Networking -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `server.stunPort` | int | `3478` | STUN UDP container port | -| `server.service.type` | string | `"ClusterIP"` | Server service type | -| `server.service.port` | int | `80` | Server service port | -| `server.stunService.type` | string | `"LoadBalancer"` | STUN service type | -| `server.stunService.port` | int | `3478` | STUN service port | -| `server.stunService.annotations` | object | `{}` | STUN service annotations | +| Key | Type | Default | Description | +|----------------------------------|--------|------------------|--------------------------| +| `server.stunPort` | int | `3478` | STUN UDP container port | +| `server.service.type` | string | `"ClusterIP"` | Server service type | +| `server.service.port` | int | `80` | Server service port | +| `server.stunService.type` | string | `"LoadBalancer"` | STUN service type | +| `server.stunService.port` | int | `3478` | STUN service port | +| `server.stunService.annotations` | object | `{}` | STUN service annotations | #### Server Ingress -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `server.ingress.enabled` | bool | `false` | Create HTTP ingress (API + OAuth2) | -| `server.ingress.className` | string | `"nginx"` | Ingress class | -| `server.ingress.annotations` | object | `{}` | Ingress annotations | -| `server.ingress.hosts` | list | `[]` | Ingress host rules | -| `server.ingress.tls` | list | `[]` | TLS configuration | -| `server.ingressGrpc.enabled` | bool | `false` | Create gRPC ingress (Signal + Management) | -| `server.ingressGrpc.className` | string | `"nginx"` | Ingress class | -| `server.ingressGrpc.annotations` | object | see values.yaml | GRPC backend annotations | -| `server.ingressGrpc.hosts` | list | `[]` | Ingress host rules | -| `server.ingressGrpc.tls` | list | `[]` | TLS configuration | -| `server.ingressRelay.enabled` | bool | `false` | Create relay/WebSocket ingress | -| `server.ingressRelay.className` | string | `"nginx"` | Ingress class | -| `server.ingressRelay.annotations` | object | `{}` | Ingress annotations | -| `server.ingressRelay.hosts` | list | `[]` | Ingress host rules | -| `server.ingressRelay.tls` | list | `[]` | TLS configuration | +| Key | Type | Default | Description | +|-----------------------------------|--------|-----------------|-------------------------------------------| +| `server.ingress.enabled` | bool | `false` | Create HTTP ingress (API + OAuth2) | +| `server.ingress.className` | string | `"nginx"` | Ingress class | +| `server.ingress.annotations` | object | `{}` | Ingress annotations | +| `server.ingress.hosts` | list | `[]` | Ingress host rules | +| `server.ingress.tls` | list | `[]` | TLS configuration | +| `server.ingressGrpc.enabled` | bool | `false` | Create gRPC ingress (Signal + Management) | +| `server.ingressGrpc.className` | string | `"nginx"` | Ingress class | +| `server.ingressGrpc.annotations` | object | see values.yaml | GRPC backend annotations | +| `server.ingressGrpc.hosts` | list | `[]` | Ingress host rules | +| `server.ingressGrpc.tls` | list | `[]` | TLS configuration | +| `server.ingressRelay.enabled` | bool | `false` | Create relay/WebSocket ingress | +| `server.ingressRelay.className` | string | `"nginx"` | Ingress class | +| `server.ingressRelay.annotations` | object | `{}` | Ingress annotations | +| `server.ingressRelay.hosts` | list | `[]` | Ingress host rules | +| `server.ingressRelay.tls` | list | `[]` | TLS configuration | #### Server Pod -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `server.resources` | object | `{}` | CPU/memory requests and limits | -| `server.nodeSelector` | object | `{}` | Node selector labels | -| `server.tolerations` | list | `[]` | Pod tolerations | -| `server.affinity` | object | `{}` | Pod affinity rules | -| `server.podAnnotations` | object | `{}` | Pod annotations | -| `server.podLabels` | object | `{}` | Additional pod labels | -| `server.podSecurityContext` | object | `{}` | Pod security context | -| `server.securityContext` | object | `{}` | Container security context | -| `server.livenessProbe` | object | TCP check on `http` port | Liveness probe | -| `server.readinessProbe` | object | TCP check on `http` port | Readiness probe | +| Key | Type | Default | Description | +|-----------------------------|--------|--------------------------|--------------------------------| +| `server.resources` | object | `{}` | CPU/memory requests and limits | +| `server.nodeSelector` | object | `{}` | Node selector labels | +| `server.tolerations` | list | `[]` | Pod tolerations | +| `server.affinity` | object | `{}` | Pod affinity rules | +| `server.podAnnotations` | object | `{}` | Pod annotations | +| `server.podLabels` | object | `{}` | Additional pod labels | +| `server.podSecurityContext` | object | `{}` | Pod security context | +| `server.securityContext` | object | `{}` | Container security context | +| `server.livenessProbe` | object | TCP check on `http` port | Liveness probe | +| `server.readinessProbe` | object | TCP check on `http` port | Readiness probe | ### Dashboard -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `dashboard.replicaCount` | int | `1` | Number of dashboard replicas | -| `dashboard.image.repository` | string | `"netbirdio/dashboard"` | Dashboard image | -| `dashboard.image.tag` | string | `"v2.32.4"` | Dashboard image tag | -| `dashboard.image.pullPolicy` | string | `"IfNotPresent"` | Image pull policy | -| `dashboard.imagePullSecrets` | list | `[]` | Component-level pull secrets | +| Key | Type | Default | Description | +|------------------------------|--------|-------------------------|------------------------------| +| `dashboard.replicaCount` | int | `1` | Number of dashboard replicas | +| `dashboard.image.repository` | string | `"netbirdio/dashboard"` | Dashboard image | +| `dashboard.image.tag` | string | `"v2.32.4"` | Dashboard image tag | +| `dashboard.image.pullPolicy` | string | `"IfNotPresent"` | Image pull policy | +| `dashboard.imagePullSecrets` | list | `[]` | Component-level pull secrets | #### Dashboard Configuration -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `dashboard.config.mgmtApiEndpoint` | string | `""` | Management API URL | -| `dashboard.config.mgmtGrpcApiEndpoint` | string | `""` | Management gRPC URL | -| `dashboard.config.authAudience` | string | `"netbird-dashboard"` | OAuth2 audience | -| `dashboard.config.authClientId` | string | `"netbird-dashboard"` | OAuth2 client ID | -| `dashboard.config.authAuthority` | string | `""` | OAuth2 authority / issuer URL | -| `dashboard.config.useAuth0` | string | `"false"` | Use Auth0 as IdP | -| `dashboard.config.authSupportedScopes` | string | `"openid profile email groups"` | OAuth2 scopes | -| `dashboard.config.authRedirectUri` | string | `"/nb-auth"` | Auth redirect path | -| `dashboard.config.authSilentRedirectUri` | string | `"/nb-silent-auth"` | Silent auth redirect path | -| `dashboard.config.nginxSslPort` | string | `"443"` | NGINX SSL port inside the container | -| `dashboard.config.letsencryptDomain` | string | `"none"` | Let's Encrypt domain ("none" = external TLS) | +| Key | Type | Default | Description | +|------------------------------------------|--------|---------------------------------|----------------------------------------------| +| `dashboard.config.mgmtApiEndpoint` | string | `""` | Management API URL | +| `dashboard.config.mgmtGrpcApiEndpoint` | string | `""` | Management gRPC URL | +| `dashboard.config.authAudience` | string | `"netbird-dashboard"` | OAuth2 audience | +| `dashboard.config.authClientId` | string | `"netbird-dashboard"` | OAuth2 client ID | +| `dashboard.config.authAuthority` | string | `""` | OAuth2 authority / issuer URL | +| `dashboard.config.useAuth0` | string | `"false"` | Use Auth0 as IdP | +| `dashboard.config.authSupportedScopes` | string | `"openid profile email groups"` | OAuth2 scopes | +| `dashboard.config.authRedirectUri` | string | `"/nb-auth"` | Auth redirect path | +| `dashboard.config.authSilentRedirectUri` | string | `"/nb-silent-auth"` | Silent auth redirect path | +| `dashboard.config.nginxSslPort` | string | `"443"` | NGINX SSL port inside the container | +| `dashboard.config.letsencryptDomain` | string | `"none"` | Let's Encrypt domain ("none" = external TLS) | #### Dashboard Secrets -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `dashboard.secrets.authClientSecret.value` | string | `""` | Plain-text client secret (when no Secret ref) | -| `dashboard.secrets.authClientSecret.secretName` | string | `""` | Existing Secret name | -| `dashboard.secrets.authClientSecret.secretKey` | string | `"clientSecret"` | Key in the Secret | +| Key | Type | Default | Description | +|-------------------------------------------------|--------|------------------|-----------------------------------------------| +| `dashboard.secrets.authClientSecret.value` | string | `""` | Plain-text client secret (when no Secret ref) | +| `dashboard.secrets.authClientSecret.secretName` | string | `""` | Existing Secret name | +| `dashboard.secrets.authClientSecret.secretKey` | string | `"clientSecret"` | Key in the Secret | #### Dashboard Extra -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `dashboard.extraEnv` | list | `[]` | Additional environment variables | +| Key | Type | Default | Description | +|----------------------|------|---------|----------------------------------| +| `dashboard.extraEnv` | list | `[]` | Additional environment variables | #### Dashboard Networking -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `dashboard.service.type` | string | `"ClusterIP"` | Dashboard service type | -| `dashboard.service.port` | int | `80` | Dashboard service port | -| `dashboard.ingress.enabled` | bool | `false` | Create dashboard ingress | -| `dashboard.ingress.className` | string | `"nginx"` | Ingress class | -| `dashboard.ingress.annotations` | object | `{}` | Ingress annotations | -| `dashboard.ingress.hosts` | list | `[]` | Ingress host rules | -| `dashboard.ingress.tls` | list | `[]` | TLS configuration | +| Key | Type | Default | Description | +|---------------------------------|--------|---------------|--------------------------| +| `dashboard.service.type` | string | `"ClusterIP"` | Dashboard service type | +| `dashboard.service.port` | int | `80` | Dashboard service port | +| `dashboard.ingress.enabled` | bool | `false` | Create dashboard ingress | +| `dashboard.ingress.className` | string | `"nginx"` | Ingress class | +| `dashboard.ingress.annotations` | object | `{}` | Ingress annotations | +| `dashboard.ingress.hosts` | list | `[]` | Ingress host rules | +| `dashboard.ingress.tls` | list | `[]` | TLS configuration | #### Dashboard Pod -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `dashboard.resources` | object | `{}` | CPU/memory requests and limits | -| `dashboard.nodeSelector` | object | `{}` | Node selector labels | -| `dashboard.tolerations` | list | `[]` | Pod tolerations | -| `dashboard.affinity` | object | `{}` | Pod affinity rules | -| `dashboard.podAnnotations` | object | `{}` | Pod annotations | -| `dashboard.podLabels` | object | `{}` | Additional pod labels | -| `dashboard.podSecurityContext` | object | `{}` | Pod security context | -| `dashboard.securityContext` | object | `{}` | Container security context | -| `dashboard.livenessProbe` | object | HTTP GET `/` | Liveness probe | -| `dashboard.readinessProbe` | object | HTTP GET `/` | Readiness probe | +| Key | Type | Default | Description | +|--------------------------------|--------|--------------|--------------------------------| +| `dashboard.resources` | object | `{}` | CPU/memory requests and limits | +| `dashboard.nodeSelector` | object | `{}` | Node selector labels | +| `dashboard.tolerations` | list | `[]` | Pod tolerations | +| `dashboard.affinity` | object | `{}` | Pod affinity rules | +| `dashboard.podAnnotations` | object | `{}` | Pod annotations | +| `dashboard.podLabels` | object | `{}` | Additional pod labels | +| `dashboard.podSecurityContext` | object | `{}` | Pod security context | +| `dashboard.securityContext` | object | `{}` | Container security context | +| `dashboard.livenessProbe` | object | HTTP GET `/` | Liveness probe | +| `dashboard.readinessProbe` | object | HTTP GET `/` | Readiness probe | ## Architecture