From 067e00b474cb16ea81cc1b3b0584627e2e86fae6 Mon Sep 17 00:00:00 2001 From: Noooste <83548733+Noooste@users.noreply.github.com> Date: Fri, 19 Dec 2025 19:24:40 +0100 Subject: [PATCH] feat: add initial Helm chart for Garage UI deployment Signed-off-by: Noooste <83548733+Noooste@users.noreply.github.com> --- helm/garage-ui/.helmignore | 23 + helm/garage-ui/Chart.yaml | 19 + helm/garage-ui/README.md | 396 +++++++++++++ helm/garage-ui/templates/NOTES.txt | 27 + helm/garage-ui/templates/_helpers.tpl | 51 ++ helm/garage-ui/templates/configmap.yaml | 9 + helm/garage-ui/templates/deployment.yaml | 80 +++ helm/garage-ui/templates/ingress.yaml | 41 ++ helm/garage-ui/templates/networkpolicy.yaml | 26 + helm/garage-ui/templates/service.yaml | 15 + helm/garage-ui/templates/servicemonitor.yaml | 19 + helm/garage-ui/values.yaml | 553 +++++++++++++++++++ 12 files changed, 1259 insertions(+) create mode 100644 helm/garage-ui/.helmignore create mode 100644 helm/garage-ui/Chart.yaml create mode 100644 helm/garage-ui/README.md create mode 100644 helm/garage-ui/templates/NOTES.txt create mode 100644 helm/garage-ui/templates/_helpers.tpl create mode 100644 helm/garage-ui/templates/configmap.yaml create mode 100644 helm/garage-ui/templates/deployment.yaml create mode 100644 helm/garage-ui/templates/ingress.yaml create mode 100644 helm/garage-ui/templates/networkpolicy.yaml create mode 100644 helm/garage-ui/templates/service.yaml create mode 100644 helm/garage-ui/templates/servicemonitor.yaml create mode 100644 helm/garage-ui/values.yaml diff --git a/helm/garage-ui/.helmignore b/helm/garage-ui/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/garage-ui/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/garage-ui/Chart.yaml b/helm/garage-ui/Chart.yaml new file mode 100644 index 0000000..7a20237 --- /dev/null +++ b/helm/garage-ui/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v2 +name: garage-ui +description: A Helm chart for Garage UI - Web interface for Garage S3 object storage +type: application +version: 0.1.0 +appVersion: "latest" +keywords: + - garage + - s3 + - object-storage + - ui + - dashboard + - web-ui +home: https://github.com/Noooste/garage-ui +sources: + - https://github.com/Noooste/garage-ui +maintainers: + - name: Noooste +kubeVersion: ">=1.19.0-0" diff --git a/helm/garage-ui/README.md b/helm/garage-ui/README.md new file mode 100644 index 0000000..80632c9 --- /dev/null +++ b/helm/garage-ui/README.md @@ -0,0 +1,396 @@ +# Garage UI Helm Chart + +A Helm chart for deploying Garage UI, a web interface for [Garage](https://garagehq.deuxfleurs.fr/) S3 object storage. + +## Introduction + +This chart bootstraps a Garage UI deployment on a Kubernetes cluster using the Helm package manager. Garage UI provides a user-friendly web interface for managing your Garage S3 storage, including buckets, objects, users, and cluster monitoring. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ +- A running Garage S3 instance with Admin API access + +## Installing the Chart + +To install the chart with the release name `my-garage-ui`: + +```bash +helm install my-garage-ui ./helm/garage-ui +``` + +The command deploys Garage UI on the Kubernetes cluster with default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +## Uninstalling the Chart + +To uninstall/delete the `my-garage-ui` deployment: + +```bash +helm uninstall my-garage-ui +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +### Common Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `replicaCount` | Number of Garage UI replicas | `1` | +| `image.repository` | Garage UI image repository | `noooste/garage-ui` | +| `image.tag` | Garage UI image tag (overrides Chart appVersion) | `""` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `imagePullSecrets` | Image pull secrets | `[]` | +| `nameOverride` | Override chart name | `""` | +| `fullnameOverride` | Override full resource names | `""` | + +### Service Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.port` | Service port | `80` | + +### Configuration Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `config` | Complete config.yaml content as multiline string | See `values.yaml` | + +**Important:** The `config` parameter contains the entire application configuration including Garage endpoints, authentication settings, CORS, and logging. You must customize this section with your Garage S3 endpoints and admin token. + +### Ingress Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `ingress.enabled` | Enable ingress controller resource | `false` | +| `ingress.className` | Ingress class name | `nginx` | +| `ingress.annotations` | Ingress annotations | `{}` | +| `ingress.hosts` | Ingress hosts configuration | See `values.yaml` | +| `ingress.tls` | Ingress TLS configuration | `[]` | + +### Monitoring Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `serviceMonitor.enabled` | Create ServiceMonitor resource (requires Prometheus Operator) | `false` | +| `serviceMonitor.interval` | Scrape interval | `30s` | +| `serviceMonitor.path` | Metrics endpoint path | `/api/v1/monitoring/metrics` | +| `serviceMonitor.labels` | Additional labels for ServiceMonitor | `{}` | + +### Network Policy Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `networkPolicy.enabled` | Enable NetworkPolicy | `false` | +| `networkPolicy.policyTypes` | Policy types | `["Ingress", "Egress"]` | + +### Resource Management Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `resources.limits.cpu` | CPU limit | `500m` | +| `resources.limits.memory` | Memory limit | `512Mi` | +| `resources.requests.cpu` | CPU request | `100m` | +| `resources.requests.memory` | Memory request | `128Mi` | + +### Health Check Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `livenessProbe.enabled` | Enable liveness probe | `true` | +| `readinessProbe.enabled` | Enable readiness probe | `true` | + +### Other Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `podAnnotations` | Pod annotations | `{}` | +| `podSecurityContext` | Pod security context | See `values.yaml` | +| `securityContext` | Container security context | See `values.yaml` | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `affinity` | Affinity for pod assignment | `{}` | + +## Configuration Examples + +### Example 1: Basic Installation with Custom Garage Endpoints + +Create a file named `custom-values.yaml`: + +```yaml +config: | + server: + host: "0.0.0.0" + port: 8080 + environment: "production" + + garage: + endpoint: "http://garage-s3.garage.svc.cluster.local:3900" + region: "us-east-1" + admin_endpoint: "http://garage-admin.garage.svc.cluster.local:3903" + admin_token: "YOUR_ADMIN_TOKEN_HERE" + + auth: + mode: "none" + + cors: + enabled: true + allowed_origins: + - "*" + + logging: + level: "info" + format: "json" +``` + +Install the chart: + +```bash +helm install my-garage-ui ./helm/garage-ui -f custom-values.yaml +``` + +### Example 2: With Ingress and TLS + +```yaml +config: | + server: + host: "0.0.0.0" + port: 8080 + environment: "production" + + garage: + endpoint: "http://garage:3900" + region: "garage" + admin_endpoint: "http://garage:3903" + admin_token: "YOUR_ADMIN_TOKEN" + + auth: + mode: "none" + + cors: + enabled: true + + logging: + level: "info" + format: "json" + +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: garage-ui.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: garage-ui-tls + hosts: + - garage-ui.example.com +``` + +### Example 3: With Basic Authentication + +```yaml +config: | + server: + host: "0.0.0.0" + port: 8080 + environment: "production" + + garage: + endpoint: "http://garage:3900" + region: "garage" + admin_endpoint: "http://garage:3903" + admin_token: "YOUR_ADMIN_TOKEN" + + auth: + mode: "basic" + basic: + username: "admin" + password: "your-secure-password" + + cors: + enabled: true + + logging: + level: "info" + format: "json" +``` + +### Example 4: With OIDC Authentication (Keycloak) + +```yaml +config: | + server: + host: "0.0.0.0" + port: 8080 + environment: "production" + + garage: + endpoint: "http://garage:3900" + region: "garage" + admin_endpoint: "http://garage:3903" + admin_token: "YOUR_ADMIN_TOKEN" + + auth: + mode: "oidc" + oidc: + enabled: true + provider_name: "Keycloak" + client_id: "garage-ui" + client_secret: "YOUR_OIDC_CLIENT_SECRET" + scopes: + - openid + - email + - profile + issuer_url: "https://keycloak.example.com/realms/master" + auth_url: "https://keycloak.example.com/realms/master/protocol/openid-connect/auth" + token_url: "https://keycloak.example.com/realms/master/protocol/openid-connect/token" + userinfo_url: "https://keycloak.example.com/realms/master/protocol/openid-connect/userinfo" + cookie_secure: true + + cors: + enabled: true + + logging: + level: "info" + format: "json" +``` + +### Example 5: With Prometheus Monitoring + +```yaml +config: | + server: + host: "0.0.0.0" + port: 8080 + environment: "production" + + garage: + endpoint: "http://garage:3900" + region: "garage" + admin_endpoint: "http://garage:3903" + admin_token: "YOUR_ADMIN_TOKEN" + + auth: + mode: "none" + + cors: + enabled: true + + logging: + level: "info" + format: "json" + +serviceMonitor: + enabled: true + interval: 30s + labels: + prometheus: kube-prometheus +``` + +## Accessing the Application + +### Via Port Forward (Development) + +```bash +kubectl port-forward svc/my-garage-ui 8080:80 +``` + +Then visit http://localhost:8080 in your browser. + +### Via Ingress (Production) + +If you've enabled Ingress, access the application at the configured hostname (e.g., https://garage-ui.example.com). + +## Upgrading + +To upgrade the `my-garage-ui` deployment: + +```bash +helm upgrade my-garage-ui ./helm/garage-ui -f custom-values.yaml +``` + +## Configuration Details + +### Garage Endpoints + +The application requires two Garage endpoints: + +- **S3 API Endpoint** (`garage.endpoint`): The Garage S3 API endpoint (default port 3900) +- **Admin API Endpoint** (`garage.admin_endpoint`): The Garage Admin API endpoint (default port 3903) +- **Admin Token** (`garage.admin_token`): Bearer token for Admin API authentication (required) + +### Authentication Modes + +The application supports three authentication modes: + +1. **None** (`auth.mode: "none"`): No authentication required +2. **Basic** (`auth.mode: "basic"`): HTTP Basic authentication with username/password +3. **OIDC** (`auth.mode: "oidc"`): OpenID Connect for enterprise SSO + +### Health Checks + +The application exposes a health check endpoint at `/health` which is used for: +- Kubernetes liveness probes +- Kubernetes readiness probes +- Manual health verification + +### Metrics + +When ServiceMonitor is enabled, Prometheus will scrape metrics from `/api/v1/monitoring/metrics`. This endpoint proxies metrics from the Garage Admin API. + +## Troubleshooting + +### Pods not starting + +Check if the config is valid: +```bash +kubectl logs -l app.kubernetes.io/name=garage-ui +``` + +Common issues: +- Missing or invalid `garage.admin_token` +- Unreachable Garage endpoints +- Invalid OIDC configuration when using `auth.mode: "oidc"` + +### Can't access the UI + +If using Ingress: +```bash +kubectl get ingress +kubectl describe ingress +``` + +If using port-forward: +```bash +kubectl get pods +kubectl port-forward 8080:8080 +``` + +### Configuration not updating + +The deployment includes a checksum annotation for the ConfigMap. Changes to the config will automatically trigger a pod restart. If not: + +```bash +kubectl rollout restart deployment/my-garage-ui +``` + +## License + +This Helm chart is open source and available under the same license as Garage UI. + +## Support + +For issues and questions: +- GitHub Issues: https://github.com/Noooste/garage-ui/issues +- Garage Documentation: https://garagehq.deuxfleurs.fr/ + +## Contributing + +Contributions are welcome! Please submit pull requests to the Garage UI repository. diff --git a/helm/garage-ui/templates/NOTES.txt b/helm/garage-ui/templates/NOTES.txt new file mode 100644 index 0000000..6e78df4 --- /dev/null +++ b/helm/garage-ui/templates/NOTES.txt @@ -0,0 +1,27 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "garage-ui.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "garage-ui.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "garage-ui.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "garage-ui.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} + +2. To view logs: + kubectl logs -f deployment/{{ include "garage-ui.fullname" . }} -n {{ .Release.Namespace }} + +For more information, see the README at https://github.com/Noooste/garage-ui/blob/main/helm/garage-ui/README.md diff --git a/helm/garage-ui/templates/_helpers.tpl b/helm/garage-ui/templates/_helpers.tpl new file mode 100644 index 0000000..453d1b9 --- /dev/null +++ b/helm/garage-ui/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "garage-ui.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "garage-ui.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 "garage-ui.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "garage-ui.labels" -}} +helm.sh/chart: {{ include "garage-ui.chart" . }} +{{ include "garage-ui.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "garage-ui.selectorLabels" -}} +app.kubernetes.io/name: {{ include "garage-ui.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/helm/garage-ui/templates/configmap.yaml b/helm/garage-ui/templates/configmap.yaml new file mode 100644 index 0000000..a6fbc29 --- /dev/null +++ b/helm/garage-ui/templates/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "garage-ui.fullname" . }}-config + labels: + {{- include "garage-ui.labels" . | nindent 4 }} +data: + config.yaml: | +{{- .Values.config | toYaml | nindent 4 }} diff --git a/helm/garage-ui/templates/deployment.yaml b/helm/garage-ui/templates/deployment.yaml new file mode 100644 index 0000000..138406b --- /dev/null +++ b/helm/garage-ui/templates/deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "garage-ui.fullname" . }} + labels: + {{- include "garage-ui.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "garage-ui.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "garage-ui.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + 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: 8080 + protocol: TCP + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.livenessProbe.httpGet.path }} + port: {{ .Values.livenessProbe.httpGet.port }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.httpGet.path }} + port: {{ .Values.readinessProbe.httpGet.port }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /app/config.yaml + subPath: config.yaml + readOnly: true + volumes: + - name: config + configMap: + name: {{ include "garage-ui.fullname" . }}-config + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/garage-ui/templates/ingress.yaml b/helm/garage-ui/templates/ingress.yaml new file mode 100644 index 0000000..92773ff --- /dev/null +++ b/helm/garage-ui/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "garage-ui.fullname" . }} + labels: + {{- include "garage-ui.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + 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 }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "garage-ui.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/garage-ui/templates/networkpolicy.yaml b/helm/garage-ui/templates/networkpolicy.yaml new file mode 100644 index 0000000..412a8db --- /dev/null +++ b/helm/garage-ui/templates/networkpolicy.yaml @@ -0,0 +1,26 @@ +{{- if .Values.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "garage-ui.fullname" . }} + labels: + {{- include "garage-ui.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "garage-ui.selectorLabels" . | nindent 6 }} + policyTypes: + {{- toYaml .Values.networkPolicy.policyTypes | nindent 4 }} + ingress: + - from: [] + ports: + - protocol: TCP + port: 8080 + egress: + - to: [] + ports: + - protocol: TCP + port: 53 + - protocol: UDP + port: 53 +{{- end }} diff --git a/helm/garage-ui/templates/service.yaml b/helm/garage-ui/templates/service.yaml new file mode 100644 index 0000000..aa39b5c --- /dev/null +++ b/helm/garage-ui/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "garage-ui.fullname" . }} + labels: + {{- include "garage-ui.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "garage-ui.selectorLabels" . | nindent 4 }} diff --git a/helm/garage-ui/templates/servicemonitor.yaml b/helm/garage-ui/templates/servicemonitor.yaml new file mode 100644 index 0000000..3a2ef11 --- /dev/null +++ b/helm/garage-ui/templates/servicemonitor.yaml @@ -0,0 +1,19 @@ +{{- if .Values.serviceMonitor.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "garage-ui.fullname" . }} + labels: + {{- include "garage-ui.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "garage-ui.selectorLabels" . | nindent 6 }} + endpoints: + - port: http + path: {{ .Values.serviceMonitor.path }} + interval: {{ .Values.serviceMonitor.interval }} +{{- end }} diff --git a/helm/garage-ui/values.yaml b/helm/garage-ui/values.yaml new file mode 100644 index 0000000..ede94e3 --- /dev/null +++ b/helm/garage-ui/values.yaml @@ -0,0 +1,553 @@ +# Default values for garage-ui Helm chart +# This file contains configuration values for deploying Garage UI +# Customize the values below according to your deployment environment + +# Number of replica pods to run +# Increase for high availability (recommended: 2-3 for production) +replicaCount: 1 + +# Docker image configuration +image: + # Container registry and image name + # Default uses the official Garage UI image from Docker Hub + repository: noooste/garage-ui + + # Image pull policy + # Options: Always, IfNotPresent, Never + # IfNotPresent: Only pull if image is not already present locally + pullPolicy: IfNotPresent + + # Image tag to use (defaults to chart appVersion if empty) + # Example: "1.0.0" or "latest" + tag: "" + +# Credentials for accessing private container registries +# Example: +# imagePullSecrets: +# - name: regcred +imagePullSecrets: [] + +# Override the default chart name in resource names +# Leave empty to use the chart name +nameOverride: "" + +# Override the full resource name (includes release name) +# Leave empty to use the default naming convention +fullnameOverride: "" + +# ============================================================================ +# APPLICATION CONFIGURATION +# ============================================================================ +# This section contains the main application configuration +# Customize according to your Garage deployment and security requirements +# +# TIP: You can now easily override specific values using --set: +# --set config.server.port=9090 +# --set config.garage.admin_token=your-token +# --set config.auth.mode=basic +config: + # ======================================== + # Server Configuration + # ======================================== + server: + # Network interface to bind to + # "0.0.0.0" listens on all interfaces (recommended for containers) + host: "0.0.0.0" + + # Port the application listens on + # Default: 8080 + port: 8080 + + # Deployment environment + # Options: "production", "development", "staging" + environment: "production" + + # ======================================== + # Garage S3 Storage Configuration + # ======================================== + garage: + # Garage S3 API endpoint + # Format: http(s)://hostname:port + # Default port: 3900 + # Example: "http://garage.example.com:3900" or "http://garage:3900" for in-cluster + endpoint: "http://garage:3900" + + # S3 region name + # For Garage, this can be any arbitrary value (Garage ignores regions) + # Default: "garage" + region: "garage" + + # Garage Admin API endpoint + # This is used for administrative operations like bucket and key management + # Default port: 3903 + # Example: "http://garage.example.com:3903" or "http://garage:3903" for in-cluster + admin_endpoint: "http://garage:3903" + + # Admin API bearer token + # REQUIRED: Obtain this from your Garage server configuration + # This token grants administrative access - keep it secure! + # To generate: See Garage documentation for admin token setup + admin_token: "" + + # ======================================== + # Authentication Configuration + # ======================================== + auth: + # Authentication mode + # Options: + # "none" - No authentication (open access - not recommended for production) + # "basic" - Simple username/password authentication + # "oidc" - OpenID Connect integration (recommended for production) + mode: "none" + + # Basic Authentication Settings + # Only used when mode = "basic" + # Provides simple username/password protection + basic: + # Username for basic auth login + username: "admin" + + # Password for basic auth login + # IMPORTANT: Change this default password immediately! + password: "changeme" + + # OpenID Connect (OIDC) Configuration + # Only used when mode = "oidc" + # Integrates with identity providers like Keycloak, Auth0, Okta, etc. + oidc: + # Enable/disable OIDC (must be true when mode = "oidc") + enabled: false + + # Display name of your OIDC provider + # Examples: "Keycloak", "Auth0", "Okta", "Azure AD" + provider_name: "Keycloak" + + # OAuth2 client ID registered with your OIDC provider + # Obtain this from your OIDC provider's application settings + client_id: "garage-ui" + + # OAuth2 client secret registered with your OIDC provider + # IMPORTANT: Keep this secret secure! Consider using Kubernetes secrets + client_secret: "your-client-secret" + + # OAuth2/OIDC scopes to request during authentication + # Standard scopes: openid (required), email, profile + # Add custom scopes as needed by your provider + scopes: + - openid + - email + - profile + + # OIDC Provider Endpoints + # Replace "keycloak.example.com" and realm with your actual values + # For Keycloak: https://your-keycloak/realms/your-realm + # For Auth0: https://your-tenant.auth0.com + # For Okta: https://your-domain.okta.com + + # OIDC issuer URL (base URL for OIDC discovery) + issuer_url: "https://keycloak.example.com/realms/master" + + # Authorization endpoint URL + auth_url: "https://keycloak.example.com/realms/master/protocol/openid-connect/auth" + + # Token endpoint URL + token_url: "https://keycloak.example.com/realms/master/protocol/openid-connect/token" + + # User info endpoint URL + userinfo_url: "https://keycloak.example.com/realms/master/protocol/openid-connect/userinfo" + + # Token Validation Settings + # Set to true only if you have issuer mismatch issues (not recommended) + skip_issuer_check: false + + # Set to true to skip token expiry validation (not recommended for production) + skip_expiry_check: false + + # User Attribute Mappings + # Map OIDC claims to application user attributes + # Adjust these based on your OIDC provider's claim structure + + # Claim containing user's email address + email_attribute: "email" + + # Claim containing user's username/login ID + username_attribute: "preferred_username" + + # Claim containing user's display name + name_attribute: "name" + + # Role-Based Access Control (Optional) + # Path to roles in the OIDC token claims + # Example for Keycloak: "resource_access.garage-ui.roles" + # Example for Auth0: "https://your-app/roles" + role_attribute_path: "resource_access.garage-ui.roles" + + # Role name that grants admin privileges + # Users with this role will have full administrative access + admin_role: "admin" + + # TLS/SSL Configuration + # Skip TLS certificate verification (only for testing - never in production!) + tls_skip_verify: false + + # Session Management + # How long user sessions remain valid without activity + # Value in seconds (86400 = 24 hours) + session_max_age: 86400 + + # Name of the session cookie + cookie_name: "garage_session" + + # Only send cookie over HTTPS connections + # Set to false only for local development without HTTPS + cookie_secure: true + + # Prevent JavaScript access to the cookie (security feature) + # Should remain true for security + cookie_http_only: true + + # SameSite cookie attribute for CSRF protection + # Options: "lax" (recommended), "strict", "none" + # Use "lax" for most cases, "strict" for maximum security + cookie_same_site: "lax" + + # ======================================== + # CORS (Cross-Origin Resource Sharing) + # ======================================== + # Configure which origins can access the API from browsers + cors: + # Enable or disable CORS + # Disable if the frontend and backend are served from the same origin + enabled: true + + # List of allowed origins + # "*" allows all origins (convenient but less secure) + # For production, specify exact origins: + # Example: + # - "https://garage-ui.example.com" + # - "https://app.example.com" + allowed_origins: + - "*" + + # HTTP methods that are allowed in CORS requests + # Include all methods your API uses + allowed_methods: + - GET + - POST + - PUT + - DELETE + - OPTIONS + + # HTTP headers that are allowed in CORS requests + # Add any custom headers your application requires + allowed_headers: + - Origin + - Content-Type + - Accept + - Authorization + + # Allow credentials (cookies, authorization headers) in CORS requests + # Set to true if your frontend needs to send authentication cookies + # NOTE: When true, allowed_origins cannot be "*" + allow_credentials: false + + # How long browsers can cache CORS preflight responses (in seconds) + # 3600 = 1 hour + max_age: 3600 + + # ======================================== + # Logging Configuration + # ======================================== + logging: + # Log verbosity level + # Options: + # "debug" - Verbose logging, useful for troubleshooting + # "info" - Standard logging (recommended for production) + # "warn" - Only warnings and errors + # "error" - Only errors + level: "info" + + # Log output format + # Options: + # "json" - Structured JSON format (recommended for production/log aggregation) + # "text" - Human-readable text format (useful for development) + format: "json" + +# ============================================================================ +# KUBERNETES POD CONFIGURATION +# ============================================================================ + +# Annotations to add to the pod +# Use for integrations with service meshes, monitoring, etc. +# Example: +# podAnnotations: +# prometheus.io/scrape: "true" +# prometheus.io/port: "8080" +podAnnotations: {} + +# Pod-level security context +# Defines security settings for all containers in the pod +podSecurityContext: + # Run containers as non-root user (security best practice) + runAsNonRoot: true + + # User ID to run containers as + # Default: 1000 (non-privileged user) + runAsUser: 1000 + + # Group ID for filesystem access + # Files created by the pod will have this group ownership + fsGroup: 1000 + +# Container-level security context +# Security settings specific to the application container +securityContext: + # Prevent privilege escalation (security best practice) + # Ensures the container cannot gain more privileges than its parent + allowPrivilegeEscalation: false + + # Drop all Linux capabilities and only add what's needed + # This follows the principle of least privilege + capabilities: + drop: + - ALL + + # Allow write access to the root filesystem + # Set to true for read-only root filesystem (more secure but may require adjustments) + readOnlyRootFilesystem: false + +# ============================================================================ +# KUBERNETES SERVICE +# ============================================================================ +service: + # Service type determines how the service is exposed + # Options: + # ClusterIP - Internal only (default, recommended when using Ingress) + # NodePort - Expose on each node's IP at a static port + # LoadBalancer - Expose using a cloud provider's load balancer + type: ClusterIP + + # Port the service listens on + # This is the port other services/ingress will connect to + # Default: 80 + port: 80 + +# ============================================================================ +# INGRESS CONFIGURATION +# ============================================================================ +# Ingress exposes HTTP/HTTPS routes from outside the cluster to the service +ingress: + # Enable or disable ingress + # Set to true to make the application accessible from outside the cluster + enabled: false + + # Ingress class name + # Specifies which ingress controller to use + # Common values: "nginx", "traefik", "alb" (AWS) + # Ensure the specified controller is installed in your cluster + className: "nginx" + + # Additional annotations for the ingress + # Use for configuring ingress controller behavior, SSL, authentication, etc. + # Examples: + # cert-manager.io/cluster-issuer: "letsencrypt-prod" # For automatic SSL certificates + # nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + # nginx.ingress.kubernetes.io/proxy-body-size: "100m" + annotations: {} + + # Hostname and path configuration + hosts: + # Replace "garage-ui.local" with your actual domain + - host: garage-ui.local + paths: + # Path where the application will be accessible + - path: / + # pathType options: Prefix, Exact, ImplementationSpecific + pathType: Prefix + + # TLS/SSL configuration + # Uncomment and configure to enable HTTPS + # Requires a TLS certificate stored in a Kubernetes secret + tls: [] + # - secretName: garage-ui-tls + # hosts: + # - garage-ui.local + # + # To use cert-manager for automatic certificates: + # 1. Install cert-manager in your cluster + # 2. Create a ClusterIssuer (e.g., letsencrypt-prod) + # 3. Add annotation: cert-manager.io/cluster-issuer: "letsencrypt-prod" + # 4. Uncomment the tls section above + +# ============================================================================ +# RESOURCE LIMITS AND REQUESTS +# ============================================================================ +# Configure CPU and memory allocation for the pods +# Requests: Guaranteed resources (used for scheduling) +# Limits: Maximum resources the container can use +resources: + limits: + # Maximum CPU cores the container can use + # 500m = 0.5 CPU cores + # Increase for high-traffic deployments + cpu: 500m + + # Maximum memory the container can use + # Container will be killed if it exceeds this limit + memory: 512Mi + + requests: + # Guaranteed CPU allocated to the container + # 100m = 0.1 CPU cores + # Cluster scheduler ensures this amount is available + cpu: 100m + + # Guaranteed memory allocated to the container + # Cluster scheduler ensures this amount is available + memory: 128Mi + +# ============================================================================ +# HEALTH CHECKS +# ============================================================================ + +# Liveness Probe +# Kubernetes restarts the container if this probe fails +# Detects if the application is running but in a broken state +livenessProbe: + # Enable or disable the liveness probe + enabled: true + + # HTTP endpoint to check + httpGet: + # Health check endpoint path + path: /health + # Port name (defined in the container spec) + port: http + + # Wait time before starting liveness checks after container starts + # Give the application time to initialize + initialDelaySeconds: 30 + + # How often to perform the probe (in seconds) + periodSeconds: 10 + + # Maximum time to wait for the probe to complete + timeoutSeconds: 3 + + # Number of consecutive failures before restarting the container + failureThreshold: 3 + +# Readiness Probe +# Kubernetes stops sending traffic if this probe fails +# Determines when the container is ready to accept traffic +readinessProbe: + # Enable or disable the readiness probe + enabled: true + + # HTTP endpoint to check + httpGet: + # Readiness check endpoint path + path: /health + # Port name (defined in the container spec) + port: http + + # Wait time before starting readiness checks after container starts + initialDelaySeconds: 10 + + # How often to perform the probe (in seconds) + periodSeconds: 5 + + # Maximum time to wait for the probe to complete + timeoutSeconds: 3 + + # Number of consecutive failures before marking as not ready + failureThreshold: 3 + +# ============================================================================ +# MONITORING AND OBSERVABILITY +# ============================================================================ + +# ServiceMonitor for Prometheus Operator +# Automatically configure Prometheus to scrape metrics from the application +# Requires Prometheus Operator to be installed in the cluster +serviceMonitor: + # Enable or disable ServiceMonitor creation + # Set to true if using Prometheus Operator for monitoring + enabled: false + + # How often Prometheus should scrape metrics + # Format: duration string (e.g., "30s", "1m", "5m") + interval: 30s + + # Metrics endpoint path + # The application exposes metrics at this path + path: /api/v1/monitoring/metrics + + # Additional labels for the ServiceMonitor + # Use to match Prometheus scrape configurations + # Example: + # labels: + # prometheus: kube-prometheus + labels: {} + +# ============================================================================ +# NETWORK POLICY +# ============================================================================ +# Controls network traffic to/from the pods +# Requires a network plugin that supports NetworkPolicy (e.g., Calico, Cilium) +networkPolicy: + # Enable or disable NetworkPolicy creation + # Provides network-level security by restricting pod communication + enabled: false + + # Types of policies to enforce + # Ingress: Controls incoming traffic to the pod + # Egress: Controls outgoing traffic from the pod + policyTypes: + - Ingress + - Egress + + # Note: When enabled, by default only allows necessary traffic + # Customize the NetworkPolicy template if you need specific rules + +# ============================================================================ +# POD SCHEDULING +# ============================================================================ + +# Node Selector +# Schedule pods only on nodes with specific labels +# Useful for deploying to specific node pools or hardware types +# Example: +# nodeSelector: +# disktype: ssd +# environment: production +nodeSelector: {} + +# Tolerations +# Allow pods to be scheduled on nodes with matching taints +# Useful for dedicated node pools or special hardware +# Example: +# tolerations: +# - key: "dedicated" +# operator: "Equal" +# value: "garage-ui" +# effect: "NoSchedule" +tolerations: [] + +# Affinity Rules +# Advanced pod scheduling constraints +# Controls which nodes pods can be scheduled on and pod co-location +# Example for pod anti-affinity (spread pods across nodes): +# affinity: +# podAntiAffinity: +# preferredDuringSchedulingIgnoredDuringExecution: +# - weight: 100 +# podAffinityTerm: +# labelSelector: +# matchExpressions: +# - key: app.kubernetes.io/name +# operator: In +# values: +# - garage-ui +# topologyKey: kubernetes.io/hostname +affinity: {}