From b163660f0545e4eeacc0a7a8cea75fbe60127489 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 2 Jan 2026 23:23:54 +0000 Subject: [PATCH] Release gohoarder 0.0.1 --- charts/gohoarder/Chart.yaml | 22 + charts/gohoarder/LICENSE | 21 + charts/gohoarder/README.md | 499 ++++++++++++++++++ charts/gohoarder/templates/NOTES.txt | 70 +++ charts/gohoarder/templates/_helpers.tpl | 174 ++++++ charts/gohoarder/templates/configmap.yaml | 168 ++++++ .../templates/deployment-frontend.yaml | 85 +++ .../templates/deployment-scanner.yaml | 114 ++++ .../templates/deployment-server.yaml | 194 +++++++ .../gohoarder/templates/imagepullsecret.yaml | 14 + charts/gohoarder/templates/ingress.yaml | 118 +++++ charts/gohoarder/templates/pvc.yaml | 37 ++ charts/gohoarder/templates/secret.yaml | 66 +++ charts/gohoarder/templates/service.yaml | 39 ++ .../gohoarder/templates/serviceaccount.yaml | 12 + charts/gohoarder/values.yaml | 475 +++++++++++++++++ charts/packages/gohoarder-0.0.1.tgz | Bin 0 -> 15829 bytes index.yaml | 30 +- 18 files changed, 2137 insertions(+), 1 deletion(-) create mode 100644 charts/gohoarder/Chart.yaml create mode 100644 charts/gohoarder/LICENSE create mode 100644 charts/gohoarder/README.md create mode 100644 charts/gohoarder/templates/NOTES.txt create mode 100644 charts/gohoarder/templates/_helpers.tpl create mode 100644 charts/gohoarder/templates/configmap.yaml create mode 100644 charts/gohoarder/templates/deployment-frontend.yaml create mode 100644 charts/gohoarder/templates/deployment-scanner.yaml create mode 100644 charts/gohoarder/templates/deployment-server.yaml create mode 100644 charts/gohoarder/templates/imagepullsecret.yaml create mode 100644 charts/gohoarder/templates/ingress.yaml create mode 100644 charts/gohoarder/templates/pvc.yaml create mode 100644 charts/gohoarder/templates/secret.yaml create mode 100644 charts/gohoarder/templates/service.yaml create mode 100644 charts/gohoarder/templates/serviceaccount.yaml create mode 100644 charts/gohoarder/values.yaml create mode 100644 charts/packages/gohoarder-0.0.1.tgz diff --git a/charts/gohoarder/Chart.yaml b/charts/gohoarder/Chart.yaml new file mode 100644 index 0000000..0372bd3 --- /dev/null +++ b/charts/gohoarder/Chart.yaml @@ -0,0 +1,22 @@ +apiVersion: v2 +name: gohoarder +description: A universal package cache proxy supporting npm, PyPI, and Go modules with security scanning +type: application +version: 0.0.1 +appVersion: "0.0.1" +keywords: + - package-manager + - cache + - proxy + - npm + - pypi + - go-modules + - security + - vulnerability-scanning +home: https://github.com/lukaszraczylo/gohoarder +sources: + - https://github.com/lukaszraczylo/gohoarder +maintainers: + - name: Lukasz Raczylo + email: lukasz@raczylo.com +icon: https://raw.githubusercontent.com/lukaszraczylo/gohoarder/main/docs/logo.png diff --git a/charts/gohoarder/LICENSE b/charts/gohoarder/LICENSE new file mode 100644 index 0000000..745270f --- /dev/null +++ b/charts/gohoarder/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Lukasz Raczylo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/charts/gohoarder/README.md b/charts/gohoarder/README.md new file mode 100644 index 0000000..c41e26b --- /dev/null +++ b/charts/gohoarder/README.md @@ -0,0 +1,499 @@ +# GoHoarder Helm Chart + +A universal package cache proxy supporting npm, PyPI, and Go modules with integrated security scanning. + +## Features + +- **Multi-Registry Support**: Proxy for npm, PyPI, and Go modules +- **Security Scanning**: Integrated vulnerability scanning with multiple scanners +- **Flexible Storage**: Support for filesystem, S3, and SMB storage backends +- **Metadata Storage**: SQLite or PostgreSQL for metadata +- **Auto-Configuration**: Generates configuration from Helm values +- **Production Ready**: Includes health checks, resource limits, and security contexts + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ +- PV provisioner support in the underlying infrastructure (for persistent storage) + +## Installation + +### Add Helm Repository + +```bash +helm repo add gohoarder https://lukaszraczylo.github.io/gohoarder +helm repo update +``` + +### Install Chart + +```bash +# Install with default values +helm install gohoarder gohoarder/gohoarder + +# Install with custom values +helm install gohoarder gohoarder/gohoarder -f values.yaml + +# Install in a specific namespace +helm install gohoarder gohoarder/gohoarder -n gohoarder --create-namespace +``` + +## Quick Start Examples + +### Minimal Installation + +```bash +helm install gohoarder gohoarder/gohoarder \ + --set global.domain=example.com \ + --set ingress.enabled=true +``` + +### With Security Scanning + +```bash +helm install gohoarder gohoarder/gohoarder \ + --set security.enabled=true \ + --set security.scanners.trivy.enabled=true \ + --set security.scanners.osv.enabled=true +``` + +### With S3 Storage + +```bash +helm install gohoarder gohoarder/gohoarder \ + --set storage.backend=s3 \ + --set storage.s3.bucket=my-bucket \ + --set storage.s3.region=us-east-1 \ + --set storage.s3.accessKeyId=AKIAIOSFODNN7EXAMPLE \ + --set storage.s3.secretAccessKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY +``` + +### With Private Container Registry + +If using images from a private registry, create an image pull secret and reference it: + +```bash +# Create a Docker registry secret +kubectl create secret docker-registry ghcr-secret \ + --docker-server=ghcr.io \ + --docker-username= \ + --docker-password= \ + --docker-email= \ + -n gohoarder + +# Install with the secret +helm install gohoarder gohoarder/gohoarder \ + --set global.imagePullSecrets[0].name=ghcr-secret \ + -n gohoarder +``` + +Or using a values file to reference existing secrets: + +```yaml +global: + imagePullSecrets: + - name: ghcr-secret + - name: dockerhub-secret # Multiple secrets supported +``` + +**Auto-create secrets** (chart will create them for you): + +```yaml +imageCredentials: + ghcr-secret: + registry: ghcr.io + username: myusername + password: mytoken + email: myemail@example.com + +global: + imagePullSecrets: + - name: ghcr-secret +``` + +> **Note**: Storing credentials in values files is less secure than creating secrets manually. Consider using external secret management solutions like Sealed Secrets or External Secrets Operator for production. + +## Configuration Methods + +GoHoarder supports two configuration methods that can be used together: + +### 1. ConfigMap (Default) + +The chart automatically generates a `config.yaml` from Helm values and mounts it as a ConfigMap. This is the default approach and works out of the box. + +### 2. Environment Variables + +You can override any configuration using environment variables with the format `GOHOARDER_` where dots are replaced with underscores. + +**Example using values file:** + +```yaml +server: + env: + - name: GOHOARDER_STORAGE_BACKEND + value: "s3" + - name: GOHOARDER_STORAGE_S3_BUCKET + value: "my-bucket" + # Reference secrets for sensitive data + - name: GOHOARDER_STORAGE_S3_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: aws-credentials + key: secret-access-key + - name: GOHOARDER_METADATA_POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: password +``` + +**Example using command line:** + +```bash +helm install gohoarder gohoarder/gohoarder \ + --set server.env[0].name=GOHOARDER_STORAGE_BACKEND \ + --set server.env[0].value=s3 \ + --set server.env[1].name=GOHOARDER_LOGGING_LEVEL \ + --set server.env[1].value=debug +``` + +**Benefits of environment variables:** +- Better integration with Kubernetes secrets +- Override specific values without modifying ConfigMap +- Support for secret references (no plain-text passwords) +- Compatible with external secret management (External Secrets Operator, Sealed Secrets) + +**Common environment variable mappings:** + +| Config Path | Environment Variable | +|-------------|---------------------| +| `storage.backend` | `GOHOARDER_STORAGE_BACKEND` | +| `storage.s3.bucket` | `GOHOARDER_STORAGE_S3_BUCKET` | +| `storage.s3.region` | `GOHOARDER_STORAGE_S3_REGION` | +| `storage.s3.access_key_id` | `GOHOARDER_STORAGE_S3_ACCESS_KEY_ID` | +| `storage.s3.secret_access_key` | `GOHOARDER_STORAGE_S3_SECRET_ACCESS_KEY` | +| `metadata.backend` | `GOHOARDER_METADATA_BACKEND` | +| `metadata.postgresql.host` | `GOHOARDER_METADATA_POSTGRESQL_HOST` | +| `metadata.postgresql.password` | `GOHOARDER_METADATA_POSTGRESQL_PASSWORD` | +| `security.enabled` | `GOHOARDER_SECURITY_ENABLED` | +| `security.scanners.trivy.enabled` | `GOHOARDER_SECURITY_SCANNERS_TRIVY_ENABLED` | +| `logging.level` | `GOHOARDER_LOGGING_LEVEL` | +| `logging.format` | `GOHOARDER_LOGGING_FORMAT` | + +## Configuration Reference + +The following table lists the configurable parameters and their default values. + +### Global Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `nameOverride` | Override the name of the chart | `""` | +| `fullnameOverride` | Override the full name of the chart | `""` | +| `global.domain` | Base domain for the deployment | `gohoarder.local` | +| `global.imagePullSecrets` | Image pull secrets (reference existing) | `[]` | +| `imageCredentials` | Auto-create image pull secrets from credentials | `{}` | + +### Replica Count + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `replicaCount.server` | Number of server replicas | `1` | +| `replicaCount.frontend` | Number of frontend replicas | `1` | +| `replicaCount.scanner` | Number of scanner replicas | `1` | + +### Image Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `image.server.repository` | Server image repository | `ghcr.io/lukaszraczylo/gohoarder-server` | +| `image.server.tag` | Server image tag | `latest` | +| `image.server.pullPolicy` | Server image pull policy | `IfNotPresent` | +| `image.frontend.repository` | Frontend image repository | `ghcr.io/lukaszraczylo/gohoarder-frontend` | +| `image.frontend.tag` | Frontend image tag | `latest` | +| `image.frontend.pullPolicy` | Frontend image pull policy | `IfNotPresent` | +| `image.scanner.repository` | Scanner image repository | `ghcr.io/lukaszraczylo/gohoarder-scanner` | +| `image.scanner.tag` | Scanner image tag | `latest` | +| `image.scanner.pullPolicy` | Scanner image pull policy | `IfNotPresent` | + +### Environment Variables + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `server.env` | Additional environment variables for server | `[]` | +| `frontend.env` | Additional environment variables for frontend | `[]` | +| `scanner.env` | Additional environment variables for scanner | `[]` | + +### Storage Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `storage.backend` | Storage backend (filesystem, s3, smb) | `filesystem` | +| `storage.filesystem.storageClass` | Storage class for PVC | `""` | +| `storage.filesystem.size` | Storage size | `100Gi` | +| `storage.filesystem.useHostPath` | Use hostPath instead of PVC | `false` | +| `storage.filesystem.hostPath` | Host path for storage | `/var/lib/gohoarder` | +| `storage.s3.endpoint` | S3 endpoint | `s3.amazonaws.com` | +| `storage.s3.bucket` | S3 bucket name | `gohoarder-cache` | +| `storage.s3.region` | S3 region | `us-east-1` | + +### Metadata Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `metadata.backend` | Metadata backend (sqlite, postgresql) | `sqlite` | +| `metadata.sqlite.persistence.enabled` | Enable persistence for SQLite | `true` | +| `metadata.sqlite.persistence.size` | SQLite storage size | `10Gi` | +| `metadata.postgresql.host` | PostgreSQL host | `localhost` | +| `metadata.postgresql.database` | PostgreSQL database | `gohoarder` | + +### Security Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `security.enabled` | Enable security scanning | `false` | +| `security.blockOnSeverity` | Block packages on severity | `high` | +| `security.scanners.trivy.enabled` | Enable Trivy scanner | `false` | +| `security.scanners.osv.enabled` | Enable OSV scanner | `false` | +| `security.scanners.grype.enabled` | Enable Grype scanner | `false` | + +### Authentication + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `auth.enabled` | Enable authentication | `true` | +| `auth.adminApiKey` | Admin API key (auto-generated if empty) | `""` | +| `auth.existingSecret` | Use existing secret for admin key | `""` | + +### Ingress + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `ingress.enabled` | Enable ingress | `false` | +| `ingress.className` | Ingress class name | `nginx` | +| `ingress.frontend.enabled` | Enable frontend ingress | `true` | +| `ingress.frontend.host` | Frontend hostname | `gohoarder.local` | +| `ingress.frontend.tls.enabled` | Enable TLS for frontend | `false` | + +## High Availability & Scaling + +### Running Multiple Server Replicas + +GoHoarder can run with multiple server replicas for high availability and load distribution, but the configuration must be set correctly to avoid data inconsistency. + +#### ✅ Compatible Configurations (Safe for Multiple Replicas) + +**Storage:** +- ✅ **S3** - Fully compatible, recommended for production HA setups +- ✅ **SMB** - Compatible, shared network storage +- ✅ **Filesystem with RWX** - Compatible when using ReadWriteMany storage classes + - ✅ Examples: Longhorn RWX, NFS, CephFS, GlusterFS, Azure Files + - ✅ Uses atomic rename operations for safe concurrent writes + - ✅ Packages are static/immutable - perfect for shared storage + - ❌ Not compatible with local storage or ReadWriteOnce (RWO) PVCs + +**Metadata:** +- ✅ **PostgreSQL** - Fully compatible, handles concurrent writes, recommended for HA +- ⚠️ **SQLite** - Limited compatibility: + - Uses WAL mode which supports concurrent reads + - Multiple writers can cause lock contention + - Works but may have performance issues under high concurrency + - Only if using shared storage (NFS, etc.) + +#### 📋 Recommended HA Configurations + +**Option 1: Cloud Storage (S3)** + +Best for cloud deployments, object storage: + +```yaml +replicaCount: + server: 3 + +storage: + backend: s3 + s3: + endpoint: s3.amazonaws.com + region: us-east-1 + bucket: gohoarder-cache + +metadata: + backend: postgresql + postgresql: + host: postgres.database.svc.cluster.local + database: gohoarder + +podDisruptionBudget: + enabled: true + minAvailable: 1 +``` + +**Option 2: Shared Filesystem (Longhorn/NFS)** + +Best for on-premises or self-hosted Kubernetes: + +```yaml +replicaCount: + server: 3 + +storage: + backend: filesystem + filesystem: + # Use RWX storage class (Longhorn, NFS, CephFS, etc.) + storageClass: "longhorn" # or "nfs-client", "cephfs", etc. + size: "500Gi" + accessMode: "ReadWriteMany" # RWX - Critical for multiple replicas! + +metadata: + backend: postgresql # Or SQLite with RWX storage + postgresql: + host: postgres.database.svc.cluster.local + database: gohoarder + +podDisruptionBudget: + enabled: true + minAvailable: 1 +``` + +**Why Filesystem with RWX Works:** +- Packages are immutable once cached (static files) +- Filesystem backend uses atomic `rename()` operations +- Race condition safe: If two replicas cache same package, one wins +- Performance: Local filesystem often faster than object storage for reads + +#### ⚠️ What Won't Work with Multiple Replicas + +**Filesystem storage with local volumes:** +```yaml +# ❌ DON'T DO THIS with multiple replicas +storage: + backend: filesystem + filesystem: + useHostPath: true # Each replica gets different storage +``` + +**SQLite with local storage:** +```yaml +# ⚠️ AVOID with multiple replicas +metadata: + backend: sqlite + sqlite: + persistence: + enabled: true # Each replica gets its own database +``` + +#### 🔄 How It Works + +**Request Deduplication:** +- Single replica: Uses `singleflight` to prevent duplicate upstream fetches +- Multiple replicas: Each replica may fetch the same package independently +- **Mitigation**: Package metadata in shared database prevents duplicate downloads once one replica completes + +**Cache Consistency:** +- Storage backend (S3/SMB) ensures all replicas see the same cached packages +- Metadata database ensures consistent package information across replicas +- First replica to cache a package wins, others will use the cached version + +**Session Affinity:** +- Not required - GoHoarder is stateless +- Load balancer can distribute requests randomly + +**Scanner Replicas:** +- Scanner can run as a single replica or multiple +- If multiple scanners enabled, they share work through the metadata database +- Package scans are deduplicated via database state + +#### 🔬 Technical Details: Concurrent Write Safety + +**Filesystem Backend with RWX Storage:** + +The filesystem storage backend uses a **temp-file + atomic rename** pattern: + +```go +1. Write package to: /cache/npm/package@1.0.0.tmp +2. Calculate checksums (MD5, SHA256) +3. Atomic rename: .tmp → /cache/npm/package@1.0.0 +``` + +**Why this is safe for concurrent writes:** +- `os.Rename()` is atomic on POSIX filesystems +- If two replicas cache the same package simultaneously: + - Both write to separate `.tmp` files + - Both attempt atomic rename + - One succeeds, one gets "file exists" error + - Result: Same file content, no corruption + +**Package immutability:** +- Packages are versioned and immutable (npm/pypi/go semantics) +- Same package@version always has identical content +- Concurrent writes produce identical results +- No risk of partial/corrupted files + +**Quota tracking:** +- Per-process mutex (minor inaccuracy across replicas) +- Conservative: May undercount slightly +- Not critical for operation + +## Uninstallation + +```bash +helm uninstall gohoarder -n gohoarder +``` + +## Upgrading + +```bash +helm upgrade gohoarder gohoarder/gohoarder -f values.yaml +``` + +## Package Manager Configuration + +After installation, configure your package managers to use GoHoarder: + +### NPM + +```bash +npm config set registry http:///npm/ +``` + +### Go + +```bash +export GOPROXY=http:///go,direct +``` + +### PyPI + +```bash +pip config set global.index-url http:///pypi/simple +``` + +## Troubleshooting + +### Check Pod Status + +```bash +kubectl get pods -n gohoarder +kubectl logs -n gohoarder +``` + +### Verify Configuration + +```bash +kubectl get configmap -n gohoarder -gohoarder-config -o yaml +``` + +### Get Admin API Key + +```bash +kubectl get secret -n gohoarder -gohoarder-auth -o jsonpath='{.data.admin-api-key}' | base64 -d +``` + +## Contributing + +Contributions are welcome! Please visit [GitHub](https://github.com/lukaszraczylo/gohoarder) for more information. + +## License + +See the [LICENSE](https://github.com/lukaszraczylo/gohoarder/blob/main/LICENSE) file. diff --git a/charts/gohoarder/templates/NOTES.txt b/charts/gohoarder/templates/NOTES.txt new file mode 100644 index 0000000..15f6d50 --- /dev/null +++ b/charts/gohoarder/templates/NOTES.txt @@ -0,0 +1,70 @@ +** GoHoarder has been installed! ** + +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- if .Values.ingress.frontend.enabled }} + http{{ if .Values.ingress.frontend.tls.enabled }}s{{ end }}://{{ .Values.ingress.frontend.host | default (printf "%s.%s" "gohoarder" .Values.global.domain) }} +{{- end }} +{{- else if contains "NodePort" .Values.frontend.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "gohoarder.fullname" . }}-frontend) + 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.frontend.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 "gohoarder.fullname" . }}-frontend' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "gohoarder.fullname" . }}-frontend --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.frontend.service.port }} +{{- else if contains "ClusterIP" .Values.frontend.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "gohoarder.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=frontend" -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. Admin API Key: +{{- if .Values.auth.enabled }} +{{- if .Values.auth.existingSecret }} + The admin API key is stored in the existing secret: {{ .Values.auth.existingSecret }} + + To retrieve it: + kubectl get secret {{ .Values.auth.existingSecret }} -n {{ .Release.Namespace }} -o jsonpath='{.data.{{ .Values.auth.secretKey }}}' | base64 -d +{{- else if .Values.auth.adminApiKey }} + The admin API key you provided: {{ .Values.auth.adminApiKey }} +{{- else }} + A random admin API key has been generated. To retrieve it: + kubectl get secret {{ include "gohoarder.fullname" . }}-auth -n {{ .Release.Namespace }} -o jsonpath='{.data.{{ .Values.auth.secretKey }}}' | base64 -d +{{- end }} +{{- else }} + Authentication is disabled. +{{- end }} + +3. Configuration: + - Storage backend: {{ .Values.storage.backend }} + - Metadata backend: {{ .Values.metadata.backend }} + - Security scanning: {{ if .Values.security.enabled }}enabled{{ else }}disabled{{ end }} + {{- if .Values.security.enabled }} + - Active scanners: + {{- range $scanner, $config := .Values.security.scanners }} + {{- if $config.enabled }} + * {{ $scanner }} + {{- end }} + {{- end }} + {{- end }} + +4. Package Proxies: + Configure your package managers to use GoHoarder: + + NPM: + npm config set registry http://{{ include "gohoarder.fullname" . }}-server.{{ .Release.Namespace }}.svc.cluster.local/npm/ + + Go: + export GOPROXY=http://{{ include "gohoarder.fullname" . }}-server.{{ .Release.Namespace }}.svc.cluster.local/go,direct + + PyPI: + pip config set global.index-url http://{{ include "gohoarder.fullname" . }}-server.{{ .Release.Namespace }}.svc.cluster.local/pypi/simple + +5. Health Checks: + - Server health: http://{{ include "gohoarder.fullname" . }}-server.{{ .Release.Namespace }}.svc.cluster.local/health + - Server ready: http://{{ include "gohoarder.fullname" . }}-server.{{ .Release.Namespace }}.svc.cluster.local/health/ready + +For more information, visit: https://github.com/lukaszraczylo/gohoarder diff --git a/charts/gohoarder/templates/_helpers.tpl b/charts/gohoarder/templates/_helpers.tpl new file mode 100644 index 0000000..b3232b5 --- /dev/null +++ b/charts/gohoarder/templates/_helpers.tpl @@ -0,0 +1,174 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "gohoarder.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "gohoarder.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 "gohoarder.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "gohoarder.labels" -}} +helm.sh/chart: {{ include "gohoarder.chart" . }} +{{ include "gohoarder.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "gohoarder.selectorLabels" -}} +app.kubernetes.io/name: {{ include "gohoarder.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Server labels +*/}} +{{- define "gohoarder.server.labels" -}} +{{ include "gohoarder.labels" . }} +app.kubernetes.io/component: server +{{- end }} + +{{/* +Server selector labels +*/}} +{{- define "gohoarder.server.selectorLabels" -}} +{{ include "gohoarder.selectorLabels" . }} +app.kubernetes.io/component: server +{{- end }} + +{{/* +Frontend labels +*/}} +{{- define "gohoarder.frontend.labels" -}} +{{ include "gohoarder.labels" . }} +app.kubernetes.io/component: frontend +{{- end }} + +{{/* +Frontend selector labels +*/}} +{{- define "gohoarder.frontend.selectorLabels" -}} +{{ include "gohoarder.selectorLabels" . }} +app.kubernetes.io/component: frontend +{{- end }} + +{{/* +Scanner labels +*/}} +{{- define "gohoarder.scanner.labels" -}} +{{ include "gohoarder.labels" . }} +app.kubernetes.io/component: scanner +{{- end }} + +{{/* +Scanner selector labels +*/}} +{{- define "gohoarder.scanner.selectorLabels" -}} +{{ include "gohoarder.selectorLabels" . }} +app.kubernetes.io/component: scanner +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "gohoarder.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "gohoarder.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Generate admin API key +*/}} +{{- define "gohoarder.adminApiKey" -}} +{{- if .Values.auth.adminApiKey }} +{{- .Values.auth.adminApiKey }} +{{- else }} +{{- randAlphaNum 32 }} +{{- end }} +{{- end }} + +{{/* +Storage volume configuration +*/}} +{{- define "gohoarder.storageVolume" -}} +{{- if eq .Values.storage.backend "filesystem" }} +{{- if .Values.storage.filesystem.useHostPath }} +- name: storage + hostPath: + path: {{ .Values.storage.filesystem.hostPath }} + type: DirectoryOrCreate +{{- else if .Values.storage.filesystem.existingClaim }} +- name: storage + persistentVolumeClaim: + claimName: {{ .Values.storage.filesystem.existingClaim }} +{{- else }} +- name: storage + persistentVolumeClaim: + claimName: {{ include "gohoarder.fullname" . }}-storage +{{- end }} +{{- else }} +- name: storage + emptyDir: {} +{{- end }} +{{- end }} + +{{/* +Metadata volume configuration +*/}} +{{- define "gohoarder.metadataVolume" -}} +{{- if and (eq .Values.metadata.backend "sqlite") .Values.metadata.sqlite.persistence.enabled }} +{{- if .Values.metadata.sqlite.persistence.existingClaim }} +- name: metadata + persistentVolumeClaim: + claimName: {{ .Values.metadata.sqlite.persistence.existingClaim }} +{{- else }} +- name: metadata + persistentVolumeClaim: + claimName: {{ include "gohoarder.fullname" . }}-metadata +{{- end }} +{{- else }} +- name: metadata + emptyDir: {} +{{- end }} +{{- end }} + +{{/* +Trivy cache volume configuration +*/}} +{{- define "gohoarder.trivyCacheVolume" -}} +{{- if .Values.security.scanners.trivy.enabled }} +- name: trivy-cache + emptyDir: {} +{{- end }} +{{- end }} diff --git a/charts/gohoarder/templates/configmap.yaml b/charts/gohoarder/templates/configmap.yaml new file mode 100644 index 0000000..cc742a4 --- /dev/null +++ b/charts/gohoarder/templates/configmap.yaml @@ -0,0 +1,168 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "gohoarder.fullname" . }}-config + labels: + {{- include "gohoarder.labels" . | nindent 4 }} +data: + config.yaml: | + server: + host: {{ .Values.server.host | quote }} + port: {{ .Values.server.port }} + read_timeout: {{ .Values.server.readTimeout | quote }} + write_timeout: {{ .Values.server.writeTimeout | quote }} + idle_timeout: {{ .Values.server.idleTimeout | quote }} + tls: + enabled: false + + storage: + backend: {{ .Values.storage.backend | quote }} + {{- if eq .Values.storage.backend "filesystem" }} + path: "/var/cache/gohoarder" + filesystem: + base_path: "/var/cache/gohoarder" + {{- else if eq .Values.storage.backend "s3" }} + s3: + endpoint: {{ .Values.storage.s3.endpoint | quote }} + region: {{ .Values.storage.s3.region | quote }} + bucket: {{ .Values.storage.s3.bucket | quote }} + {{- if .Values.storage.s3.existingSecret }} + access_key_id: "${S3_ACCESS_KEY_ID}" + secret_access_key: "${S3_SECRET_ACCESS_KEY}" + {{- else }} + access_key_id: {{ .Values.storage.s3.accessKeyId | quote }} + secret_access_key: {{ .Values.storage.s3.secretAccessKey | quote }} + {{- end }} + use_ssl: {{ .Values.storage.s3.useSSL }} + {{- else if eq .Values.storage.backend "smb" }} + smb: + host: {{ .Values.storage.smb.host | quote }} + share: {{ .Values.storage.smb.share | quote }} + {{- if .Values.storage.smb.existingSecret }} + username: "${SMB_USERNAME}" + password: "${SMB_PASSWORD}" + {{- else }} + username: {{ .Values.storage.smb.username | quote }} + password: {{ .Values.storage.smb.password | quote }} + {{- end }} + domain: {{ .Values.storage.smb.domain | quote }} + {{- end }} + + metadata: + backend: {{ .Values.metadata.backend | quote }} + {{- if eq .Values.metadata.backend "sqlite" }} + connection: "file:/var/lib/gohoarder/metadata/gohoarder.db?cache=shared&mode=rwc" + sqlite: + path: "/var/lib/gohoarder/metadata/gohoarder.db" + wal_mode: {{ .Values.metadata.sqlite.walMode }} + {{- else if eq .Values.metadata.backend "postgresql" }} + postgresql: + host: {{ .Values.metadata.postgresql.host | quote }} + port: {{ .Values.metadata.postgresql.port }} + database: {{ .Values.metadata.postgresql.database | quote }} + {{- if .Values.metadata.postgresql.existingSecret }} + user: "${POSTGRES_USER}" + password: "${POSTGRES_PASSWORD}" + {{- else }} + user: {{ .Values.metadata.postgresql.username | quote }} + password: {{ .Values.metadata.postgresql.password | quote }} + {{- end }} + ssl_mode: {{ .Values.metadata.postgresql.sslMode | quote }} + {{- end }} + + cache: + default_ttl: {{ .Values.cache.defaultTTL | quote }} + cleanup_interval: {{ .Values.cache.cleanupInterval | quote }} + max_size_bytes: {{ .Values.cache.maxSizeBytes }} + per_project_quota: {{ .Values.cache.perProjectQuota }} + ttl_overrides: + {{- range $key, $value := .Values.cache.ttlOverrides }} + {{ $key }}: {{ $value | quote }} + {{- end }} + + security: + enabled: {{ .Values.security.enabled }} + block_on_severity: {{ .Values.security.blockOnSeverity | quote }} + scan_on_download: {{ .Values.security.scanOnDownload }} + rescan_interval: {{ .Values.security.rescanInterval | quote }} + update_db_on_startup: {{ .Values.security.updateDbOnStartup }} + block_thresholds: + critical: {{ .Values.security.blockThresholds.critical }} + high: {{ .Values.security.blockThresholds.high }} + medium: {{ .Values.security.blockThresholds.medium }} + low: {{ .Values.security.blockThresholds.low }} + scanners: + trivy: + enabled: {{ .Values.security.scanners.trivy.enabled }} + timeout: {{ .Values.security.scanners.trivy.timeout | quote }} + cache_db: {{ .Values.security.scanners.trivy.cacheDb | quote }} + osv: + enabled: {{ .Values.security.scanners.osv.enabled }} + api_url: {{ .Values.security.scanners.osv.apiUrl | quote }} + timeout: {{ .Values.security.scanners.osv.timeout | quote }} + grype: + enabled: {{ .Values.security.scanners.grype.enabled }} + timeout: {{ .Values.security.scanners.grype.timeout | quote }} + govulncheck: + enabled: {{ .Values.security.scanners.govulncheck.enabled }} + timeout: {{ .Values.security.scanners.govulncheck.timeout | quote }} + npm_audit: + enabled: {{ .Values.security.scanners.npmAudit.enabled }} + timeout: {{ .Values.security.scanners.npmAudit.timeout | quote }} + pip_audit: + enabled: {{ .Values.security.scanners.pipAudit.enabled }} + timeout: {{ .Values.security.scanners.pipAudit.timeout | quote }} + ghsa: + enabled: {{ .Values.security.scanners.ghsa.enabled }} + timeout: {{ .Values.security.scanners.ghsa.timeout | quote }} + {{- if or .Values.security.scanners.ghsa.token .Values.security.scanners.ghsa.existingSecret }} + token: "${GHSA_TOKEN}" + {{- end }} + static: + enabled: {{ .Values.security.scanners.static.enabled }} + max_package_size: {{ .Values.security.scanners.static.maxPackageSize }} + check_checksums: {{ .Values.security.scanners.static.checkChecksums }} + block_suspicious: {{ .Values.security.scanners.static.blockSuspicious }} + + auth: + enabled: {{ .Values.auth.enabled }} + key_expiration: {{ .Values.auth.keyExpiration | quote }} + bcrypt_cost: {{ .Values.auth.bcryptCost }} + audit_log: {{ .Values.auth.auditLog }} + + network: + connect_timeout: {{ .Values.network.connectTimeout | quote }} + read_timeout: {{ .Values.network.readTimeout | quote }} + write_timeout: {{ .Values.network.writeTimeout | quote }} + max_idle_conns: {{ .Values.network.maxIdleConns }} + max_conns_per_host: {{ .Values.network.maxConnsPerHost }} + rate_limit: + per_api_key: {{ .Values.network.rateLimit.perApiKey }} + per_ip: {{ .Values.network.rateLimit.perIp }} + burst_size: {{ .Values.network.rateLimit.burstSize }} + circuit_breaker: + threshold: {{ .Values.network.circuitBreaker.threshold }} + timeout: {{ .Values.network.circuitBreaker.timeout | quote }} + reset_interval: {{ .Values.network.circuitBreaker.resetInterval | quote }} + retry: + max_attempts: {{ .Values.network.retry.maxAttempts }} + initial_backoff: {{ .Values.network.retry.initialBackoff | quote }} + max_backoff: {{ .Values.network.retry.maxBackoff | quote }} + + logging: + level: {{ .Values.logging.level | quote }} + format: {{ .Values.logging.format | quote }} + + handlers: + go: + enabled: {{ .Values.handlers.go.enabled }} + upstream_proxy: {{ .Values.handlers.go.upstreamProxy | quote }} + checksum_db: {{ .Values.handlers.go.checksumDb | quote }} + verify_checksums: {{ .Values.handlers.go.verifyChecksums }} + npm: + enabled: {{ .Values.handlers.npm.enabled }} + upstream_registry: {{ .Values.handlers.npm.upstreamRegistry | quote }} + pypi: + enabled: {{ .Values.handlers.pypi.enabled }} + upstream_url: {{ .Values.handlers.pypi.upstreamUrl | quote }} + simple_api_url: {{ .Values.handlers.pypi.simpleApiUrl | quote }} diff --git a/charts/gohoarder/templates/deployment-frontend.yaml b/charts/gohoarder/templates/deployment-frontend.yaml new file mode 100644 index 0000000..5d91921 --- /dev/null +++ b/charts/gohoarder/templates/deployment-frontend.yaml @@ -0,0 +1,85 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "gohoarder.fullname" . }}-frontend + labels: + {{- include "gohoarder.frontend.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount.frontend }} + {{- end }} + selector: + matchLabels: + {{- include "gohoarder.frontend.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "gohoarder.frontend.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "gohoarder.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: frontend + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + image: "{{ .Values.image.frontend.repository }}:{{ .Values.image.frontend.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.frontend.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + env: + - name: API_BASE_URL + value: {{ .Values.frontend.backendUrl | default (printf "http://%s-server:%d" (include "gohoarder.fullname" .) (.Values.server.service.port | int)) | quote }} + - name: APP_VERSION + value: {{ .Chart.AppVersion | quote }} + - name: APP_NAME + value: "GoHoarder" + {{- with .Values.frontend.env }} + {{- toYaml . | nindent 8 }} + {{- end }} + livenessProbe: + {{- toYaml .Values.frontend.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.frontend.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.frontend.resources | nindent 12 }} + volumeMounts: + - name: tmp + mountPath: /tmp + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-run + mountPath: /var/run + volumes: + - name: tmp + emptyDir: {} + - name: nginx-cache + emptyDir: {} + - name: nginx-run + emptyDir: {} + {{- with .Values.frontend.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/gohoarder/templates/deployment-scanner.yaml b/charts/gohoarder/templates/deployment-scanner.yaml new file mode 100644 index 0000000..61e8287 --- /dev/null +++ b/charts/gohoarder/templates/deployment-scanner.yaml @@ -0,0 +1,114 @@ +{{- if .Values.security.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "gohoarder.fullname" . }}-scanner + labels: + {{- include "gohoarder.scanner.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount.scanner }} + selector: + matchLabels: + {{- include "gohoarder.scanner.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "gohoarder.scanner.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "gohoarder.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + - name: init-permissions + image: busybox:latest + command: ['sh', '-c'] + args: + - | + mkdir -p /var/cache/gohoarder /var/lib/gohoarder/metadata /tmp/gohoarder + {{- if .Values.security.scanners.trivy.enabled }} + mkdir -p {{ .Values.security.scanners.trivy.cacheDb }} + chown -R 1000:1000 {{ .Values.security.scanners.trivy.cacheDb }} + {{- end }} + chown -R 1000:1000 /var/cache/gohoarder /var/lib/gohoarder /tmp/gohoarder + chmod 750 /var/cache/gohoarder /var/lib/gohoarder + volumeMounts: + {{- include "gohoarder.storageVolume" . | nindent 8 }} + {{- include "gohoarder.metadataVolume" . | nindent 8 }} + {{- include "gohoarder.trivyCacheVolume" . | nindent 8 }} + - name: tmp + mountPath: /tmp/gohoarder + securityContext: + runAsUser: 0 + containers: + - name: scanner + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.scanner.repository }}:{{ .Values.image.scanner.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.scanner.pullPolicy }} + env: + - name: CONFIG_FILE + value: /etc/gohoarder/config.yaml + {{- if and .Values.security.scanners.ghsa.enabled .Values.security.scanners.ghsa.existingSecret }} + - name: GHSA_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.security.scanners.ghsa.existingSecret }} + key: token + {{- else if and .Values.security.scanners.ghsa.enabled .Values.security.scanners.ghsa.token }} + - name: GHSA_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-ghsa + key: token + {{- end }} + {{- with .Values.scanner.env }} + {{- toYaml . | nindent 8 }} + {{- end }} + resources: + {{- toYaml .Values.scanner.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/gohoarder + readOnly: true + - name: storage + mountPath: /var/cache/gohoarder + - name: metadata + mountPath: /var/lib/gohoarder/metadata + {{- if .Values.security.scanners.trivy.enabled }} + - name: trivy-cache + mountPath: {{ .Values.security.scanners.trivy.cacheDb }} + {{- end }} + - name: tmp + mountPath: /tmp + volumes: + - name: config + configMap: + name: {{ include "gohoarder.fullname" . }}-config + {{- include "gohoarder.storageVolume" . | nindent 6 }} + {{- include "gohoarder.metadataVolume" . | nindent 6 }} + {{- include "gohoarder.trivyCacheVolume" . | nindent 6 }} + - name: tmp + emptyDir: {} + {{- with .Values.scanner.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.scanner.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.scanner.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/gohoarder/templates/deployment-server.yaml b/charts/gohoarder/templates/deployment-server.yaml new file mode 100644 index 0000000..bdb90e5 --- /dev/null +++ b/charts/gohoarder/templates/deployment-server.yaml @@ -0,0 +1,194 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "gohoarder.fullname" . }}-server + labels: + {{- include "gohoarder.server.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount.server }} + {{- end }} + selector: + matchLabels: + {{- include "gohoarder.server.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "gohoarder.server.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "gohoarder.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + - name: init-permissions + image: busybox:latest + command: ['sh', '-c'] + args: + - | + mkdir -p /var/cache/gohoarder /var/lib/gohoarder/metadata /tmp/gohoarder + chown -R 1000:1000 /var/cache/gohoarder /var/lib/gohoarder /tmp/gohoarder + chmod 750 /var/cache/gohoarder /var/lib/gohoarder + volumeMounts: + {{- include "gohoarder.storageVolume" . | nindent 8 }} + {{- include "gohoarder.metadataVolume" . | nindent 8 }} + - name: tmp + mountPath: /tmp/gohoarder + securityContext: + runAsUser: 0 + containers: + - name: server + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.server.repository }}:{{ .Values.image.server.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.server.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.server.port }} + protocol: TCP + env: + - name: CONFIG_FILE + value: /etc/gohoarder/config.yaml + {{- if and .Values.auth.enabled .Values.auth.existingSecret }} + - name: ADMIN_API_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.auth.existingSecret }} + key: {{ .Values.auth.secretKey }} + {{- else if .Values.auth.enabled }} + - name: ADMIN_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-auth + key: {{ .Values.auth.secretKey }} + {{- end }} + {{- if and (eq .Values.storage.backend "s3") .Values.storage.s3.existingSecret }} + - name: S3_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ .Values.storage.s3.existingSecret }} + key: access-key-id + - name: S3_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.storage.s3.existingSecret }} + key: secret-access-key + {{- else if and (eq .Values.storage.backend "s3") .Values.storage.s3.accessKeyId }} + - name: S3_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-s3 + key: access-key-id + - name: S3_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-s3 + key: secret-access-key + {{- end }} + {{- if and (eq .Values.storage.backend "smb") .Values.storage.smb.existingSecret }} + - name: SMB_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.storage.smb.existingSecret }} + key: username + - name: SMB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.storage.smb.existingSecret }} + key: password + {{- else if and (eq .Values.storage.backend "smb") .Values.storage.smb.username }} + - name: SMB_USERNAME + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-smb + key: username + - name: SMB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-smb + key: password + {{- end }} + {{- if and (eq .Values.metadata.backend "postgresql") .Values.metadata.postgresql.existingSecret }} + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: {{ .Values.metadata.postgresql.existingSecret }} + key: username + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.metadata.postgresql.existingSecret }} + key: password + {{- else if and (eq .Values.metadata.backend "postgresql") .Values.metadata.postgresql.username }} + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-postgresql + key: username + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-postgresql + key: password + {{- end }} + {{- if and .Values.security.scanners.ghsa.enabled .Values.security.scanners.ghsa.existingSecret }} + - name: GHSA_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.security.scanners.ghsa.existingSecret }} + key: token + {{- else if and .Values.security.scanners.ghsa.enabled .Values.security.scanners.ghsa.token }} + - name: GHSA_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "gohoarder.fullname" . }}-ghsa + key: token + {{- end }} + {{- with .Values.server.env }} + {{- toYaml . | nindent 8 }} + {{- end }} + livenessProbe: + {{- toYaml .Values.server.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.server.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.server.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/gohoarder + readOnly: true + - name: storage + mountPath: /var/cache/gohoarder + - name: metadata + mountPath: /var/lib/gohoarder/metadata + - name: tmp + mountPath: /tmp + volumes: + - name: config + configMap: + name: {{ include "gohoarder.fullname" . }}-config + {{- include "gohoarder.storageVolume" . | nindent 6 }} + {{- include "gohoarder.metadataVolume" . | nindent 6 }} + - name: tmp + emptyDir: {} + {{- with .Values.server.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/gohoarder/templates/imagepullsecret.yaml b/charts/gohoarder/templates/imagepullsecret.yaml new file mode 100644 index 0000000..6e92009 --- /dev/null +++ b/charts/gohoarder/templates/imagepullsecret.yaml @@ -0,0 +1,14 @@ +{{- if .Values.imageCredentials }} +{{- range $name, $config := .Values.imageCredentials }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ $name }} + labels: + {{- include "gohoarder.labels" $ | nindent 4 }} +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" $config.registry $config.username $config.password $config.email (printf "%s:%s" $config.username $config.password | b64enc) | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/gohoarder/templates/ingress.yaml b/charts/gohoarder/templates/ingress.yaml new file mode 100644 index 0000000..cd4f08c --- /dev/null +++ b/charts/gohoarder/templates/ingress.yaml @@ -0,0 +1,118 @@ +{{- if .Values.ingress.enabled -}} +{{- if .Values.ingress.frontend.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "gohoarder.fullname" . }}-frontend + labels: + {{- include "gohoarder.frontend.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.frontend.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.frontend.host | default (printf "%s.%s" "gohoarder" .Values.global.domain) | quote }} + secretName: {{ .Values.ingress.frontend.tls.secretName }} + {{- end }} + rules: + - host: {{ .Values.ingress.frontend.host | default (printf "%s.%s" "gohoarder" .Values.global.domain) | quote }} + http: + paths: + - path: /npm + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-server + port: + number: {{ .Values.server.service.port }} + - path: /pypi + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-server + port: + number: {{ .Values.server.service.port }} + - path: /go + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-server + port: + number: {{ .Values.server.service.port }} + - path: /api + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-server + port: + number: {{ .Values.server.service.port }} + - path: /ws + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-server + port: + number: {{ .Values.server.service.port }} + - path: /health + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-server + port: + number: {{ .Values.server.service.port }} + - path: /metrics + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-server + port: + number: {{ .Values.server.service.port }} + - path: / + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-frontend + port: + number: {{ .Values.frontend.service.port }} +{{- end }} +--- +{{- if .Values.ingress.api.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "gohoarder.fullname" . }}-api + labels: + {{- include "gohoarder.server.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.api.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.api.host | default (printf "api.%s.%s" "gohoarder" .Values.global.domain) | quote }} + secretName: {{ .Values.ingress.api.tls.secretName }} + {{- end }} + rules: + - host: {{ .Values.ingress.api.host | default (printf "api.%s.%s" "gohoarder" .Values.global.domain) | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "gohoarder.fullname" . }}-server + port: + number: {{ .Values.server.service.port }} +{{- end }} +{{- end }} diff --git a/charts/gohoarder/templates/pvc.yaml b/charts/gohoarder/templates/pvc.yaml new file mode 100644 index 0000000..f16c7c4 --- /dev/null +++ b/charts/gohoarder/templates/pvc.yaml @@ -0,0 +1,37 @@ +{{- if and (eq .Values.storage.backend "filesystem") (not .Values.storage.filesystem.useHostPath) (not .Values.storage.filesystem.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "gohoarder.fullname" . }}-storage + labels: + {{- include "gohoarder.labels" . | nindent 4 }} + app.kubernetes.io/component: storage +spec: + accessModes: + - {{ .Values.storage.filesystem.accessMode }} + {{- if .Values.storage.filesystem.storageClass }} + storageClassName: {{ .Values.storage.filesystem.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.storage.filesystem.size | quote }} +{{- end }} +--- +{{- if and (eq .Values.metadata.backend "sqlite") .Values.metadata.sqlite.persistence.enabled (not .Values.metadata.sqlite.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "gohoarder.fullname" . }}-metadata + labels: + {{- include "gohoarder.labels" . | nindent 4 }} + app.kubernetes.io/component: metadata +spec: + accessModes: + - {{ .Values.metadata.sqlite.persistence.accessMode }} + {{- if .Values.metadata.sqlite.persistence.storageClass }} + storageClassName: {{ .Values.metadata.sqlite.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.metadata.sqlite.persistence.size | quote }} +{{- end }} diff --git a/charts/gohoarder/templates/secret.yaml b/charts/gohoarder/templates/secret.yaml new file mode 100644 index 0000000..cfa1876 --- /dev/null +++ b/charts/gohoarder/templates/secret.yaml @@ -0,0 +1,66 @@ +{{- if and .Values.auth.enabled (not .Values.auth.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "gohoarder.fullname" . }}-auth + labels: + {{- include "gohoarder.labels" . | nindent 4 }} +type: Opaque +data: + {{- if .Values.auth.adminApiKey }} + {{ .Values.auth.secretKey }}: {{ .Values.auth.adminApiKey | b64enc | quote }} + {{- else }} + {{ .Values.auth.secretKey }}: {{ include "gohoarder.adminApiKey" . | b64enc | quote }} + {{- end }} +{{- end }} +--- +{{- if and (eq .Values.storage.backend "s3") (not .Values.storage.s3.existingSecret) .Values.storage.s3.accessKeyId }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "gohoarder.fullname" . }}-s3 + labels: + {{- include "gohoarder.labels" . | nindent 4 }} +type: Opaque +data: + access-key-id: {{ .Values.storage.s3.accessKeyId | b64enc | quote }} + secret-access-key: {{ .Values.storage.s3.secretAccessKey | b64enc | quote }} +{{- end }} +--- +{{- if and (eq .Values.storage.backend "smb") (not .Values.storage.smb.existingSecret) .Values.storage.smb.username }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "gohoarder.fullname" . }}-smb + labels: + {{- include "gohoarder.labels" . | nindent 4 }} +type: Opaque +data: + username: {{ .Values.storage.smb.username | b64enc | quote }} + password: {{ .Values.storage.smb.password | b64enc | quote }} +{{- end }} +--- +{{- if and (eq .Values.metadata.backend "postgresql") (not .Values.metadata.postgresql.existingSecret) .Values.metadata.postgresql.username }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "gohoarder.fullname" . }}-postgresql + labels: + {{- include "gohoarder.labels" . | nindent 4 }} +type: Opaque +data: + username: {{ .Values.metadata.postgresql.username | b64enc | quote }} + password: {{ .Values.metadata.postgresql.password | b64enc | quote }} +{{- end }} +--- +{{- if and .Values.security.scanners.ghsa.enabled (not .Values.security.scanners.ghsa.existingSecret) .Values.security.scanners.ghsa.token }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "gohoarder.fullname" . }}-ghsa + labels: + {{- include "gohoarder.labels" . | nindent 4 }} +type: Opaque +data: + token: {{ .Values.security.scanners.ghsa.token | b64enc | quote }} +{{- end }} diff --git a/charts/gohoarder/templates/service.yaml b/charts/gohoarder/templates/service.yaml new file mode 100644 index 0000000..7cb3848 --- /dev/null +++ b/charts/gohoarder/templates/service.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "gohoarder.fullname" . }}-server + labels: + {{- include "gohoarder.server.labels" . | nindent 4 }} + {{- with .Values.server.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.server.service.type }} + ports: + - port: {{ .Values.server.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "gohoarder.server.selectorLabels" . | nindent 4 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "gohoarder.fullname" . }}-frontend + labels: + {{- include "gohoarder.frontend.labels" . | nindent 4 }} + {{- with .Values.frontend.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "gohoarder.frontend.selectorLabels" . | nindent 4 }} diff --git a/charts/gohoarder/templates/serviceaccount.yaml b/charts/gohoarder/templates/serviceaccount.yaml new file mode 100644 index 0000000..facf516 --- /dev/null +++ b/charts/gohoarder/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gohoarder.serviceAccountName" . }} + labels: + {{- include "gohoarder.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/gohoarder/values.yaml b/charts/gohoarder/values.yaml new file mode 100644 index 0000000..5e8a26d --- /dev/null +++ b/charts/gohoarder/values.yaml @@ -0,0 +1,475 @@ +# Default values for gohoarder +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Override the name of the chart +nameOverride: "" +# Override the full name of the chart +fullnameOverride: "" + +# Global configuration +global: + # Base domain for the deployment + domain: "gohoarder.local" + + # Image pull secrets for private registries + # Reference existing secrets by name: + # imagePullSecrets: + # - name: ghcr-secret + # - name: dockerhub-secret + imagePullSecrets: [] + +# Auto-create image pull secrets from credentials (optional) +# If you want the chart to create the secrets for you, use this instead: +# imageCredentials: +# ghcr-secret: +# registry: ghcr.io +# username: myusername +# password: mytoken +# email: myemail@example.com +# dockerhub-secret: +# registry: https://index.docker.io/v1/ +# username: myusername +# password: mytoken +# email: myemail@example.com +# Then reference them in global.imagePullSecrets: +# - name: ghcr-secret +imageCredentials: {} + +# Deployment replicas +# NOTE: When running multiple server replicas (>1): +# - Use S3 or SMB for storage.backend (not filesystem with local storage) +# - Use PostgreSQL for metadata.backend (SQLite has limited concurrency) +# - See "High Availability & Scaling" section in README +replicaCount: + server: 1 + frontend: 1 + scanner: 1 + +# Image configuration +image: + server: + repository: ghcr.io/lukaszraczylo/gohoarder-server + pullPolicy: IfNotPresent + tag: "0.0.1" + + frontend: + repository: ghcr.io/lukaszraczylo/gohoarder-frontend + pullPolicy: IfNotPresent + tag: "0.0.1" + + scanner: + repository: ghcr.io/lukaszraczylo/gohoarder-scanner + pullPolicy: IfNotPresent + tag: "0.0.1" + +# Service Account +serviceAccount: + create: true + annotations: {} + name: "" + +# Pod annotations +podAnnotations: {} + +# Pod security context +podSecurityContext: + fsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + +# Container security context +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + +# Server configuration +server: + host: "0.0.0.0" + port: 8080 + readTimeout: "5m" + writeTimeout: "5m" + idleTimeout: "2m" + + # Additional environment variables for server container + # Use this to override config via environment variables + # Format: GOHOARDER_ (dots replaced with underscores) + # Examples: + # GOHOARDER_STORAGE_BACKEND: s3 + # GOHOARDER_METADATA_BACKEND: postgresql + # env: + # - name: GOHOARDER_STORAGE_BACKEND + # value: "s3" + # - name: GOHOARDER_STORAGE_S3_BUCKET + # value: "my-bucket" + # - name: GOHOARDER_METADATA_POSTGRESQL_PASSWORD + # valueFrom: + # secretKeyRef: + # name: postgres-secret + # key: password + env: [] + + # Service configuration + service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: {} + + # Resource limits + resources: + limits: + cpu: 2000m + memory: 2Gi + requests: + cpu: 500m + memory: 512Mi + + # Liveness and readiness probes + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + readinessProbe: + httpGet: + path: /health/ready + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + + # Node selector + nodeSelector: {} + + # Tolerations + tolerations: [] + + # Affinity + affinity: {} + +# Frontend configuration +frontend: + # Backend URL for API calls + backendUrl: "" # Auto-configured if empty + + # Additional environment variables for frontend container + # env: + # - name: API_BASE_URL + # value: "https://api.example.com" + env: [] + + # Service configuration + service: + type: ClusterIP + port: 80 + targetPort: 80 + annotations: {} + + # Resource limits + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + + # Liveness and readiness probes + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + + nodeSelector: {} + tolerations: [] + affinity: {} + +# Scanner configuration +scanner: + # Additional environment variables for scanner container + # env: + # - name: GOHOARDER_SECURITY_SCANNERS_TRIVY_ENABLED + # value: "true" + env: [] + + # Resource limits + resources: + limits: + cpu: 2000m + memory: 4Gi + requests: + cpu: 500m + memory: 1Gi + + nodeSelector: {} + tolerations: [] + affinity: {} + +# Storage configuration +storage: + # Storage backend: filesystem, s3, smb + # For multiple server replicas: + # - S3 or SMB (recommended) + # - Filesystem with ReadWriteMany (RWX) storage class (Longhorn, NFS, CephFS) + # - NOT filesystem with ReadWriteOnce (RWO) or local storage + backend: "filesystem" + + # Filesystem storage + filesystem: + # Storage class for PVC + # For multiple replicas: use RWX-capable storage class (longhorn, nfs-client, cephfs, etc.) + storageClass: "" + # Storage size + size: "100Gi" + # Access mode: + # ReadWriteOnce (RWO) - Single replica only + # ReadWriteMany (RWX) - Multiple replicas (requires RWX storage class) + accessMode: "ReadWriteOnce" + # Use hostPath instead of PVC (for single-node testing only) + useHostPath: false + hostPath: "/var/lib/gohoarder" + # Existing PVC name (if you want to use existing PVC) + existingClaim: "" + + # S3 storage + s3: + endpoint: "s3.amazonaws.com" + region: "us-east-1" + bucket: "gohoarder-cache" + accessKeyId: "" + secretAccessKey: "" + # Use existing secret for S3 credentials + existingSecret: "" + useSSL: true + + # SMB storage + smb: + host: "" + share: "" + username: "" + password: "" + domain: "" + # Use existing secret for SMB credentials + existingSecret: "" + +# Metadata storage configuration +metadata: + # Backend: sqlite, postgresql + # For multiple server replicas: postgresql is recommended (sqlite has concurrency limitations) + backend: "sqlite" + + # SQLite configuration + sqlite: + # Use PVC for SQLite database + persistence: + enabled: true + storageClass: "" + size: "10Gi" + accessMode: "ReadWriteOnce" + existingClaim: "" + walMode: true + + # PostgreSQL configuration + postgresql: + # Use bundled PostgreSQL (sets up postgresql subchart) + enabled: false + host: "localhost" + port: 5432 + database: "gohoarder" + username: "gohoarder" + password: "" + sslMode: "disable" + # Use existing secret for PostgreSQL credentials + existingSecret: "" + +# Cache configuration +cache: + defaultTTL: "168h" # 7 days + cleanupInterval: "1h" + maxSizeBytes: 536870912000 # 500GB + perProjectQuota: 53687091200 # 50GB + ttlOverrides: + npm: "168h" + pip: "168h" + go: "168h" + +# Security scanning configuration +security: + enabled: false + blockOnSeverity: "high" # none, low, medium, high, critical + scanOnDownload: true + rescanInterval: "24h" + updateDbOnStartup: false + + blockThresholds: + critical: 0 + high: -1 + medium: -1 + low: -1 + + scanners: + trivy: + enabled: false + timeout: "5m" + cacheDb: "/var/lib/trivy" + + osv: + enabled: false + apiUrl: "https://api.osv.dev" + timeout: "30s" + + grype: + enabled: false + timeout: "5m" + + govulncheck: + enabled: false + timeout: "5m" + + npmAudit: + enabled: false + timeout: "2m" + + pipAudit: + enabled: false + timeout: "2m" + + ghsa: + enabled: false + timeout: "30s" + # GitHub token for higher rate limits + token: "" + existingSecret: "" + + static: + enabled: true + maxPackageSize: 2147483648 # 2GB + checkChecksums: true + blockSuspicious: false + +# Authentication configuration +auth: + enabled: true + keyExpiration: "0" # Never expire + bcryptCost: 10 + auditLog: true + + # Admin API key - will be auto-generated if not provided + adminApiKey: "" + # Use existing secret for admin API key + existingSecret: "" + # Secret key name for admin API key + secretKey: "admin-api-key" + +# Network configuration +network: + connectTimeout: "10s" + readTimeout: "5m" + writeTimeout: "5m" + maxIdleConns: 100 + maxConnsPerHost: 10 + + rateLimit: + perApiKey: 1000 + perIp: 100 + burstSize: 50 + + circuitBreaker: + threshold: 5 + timeout: "30s" + resetInterval: "60s" + + retry: + maxAttempts: 3 + initialBackoff: "1s" + maxBackoff: "30s" + +# Logging configuration +logging: + level: "info" # debug, info, warn, error + format: "json" # json, pretty + +# Package handlers configuration +handlers: + go: + enabled: true + upstreamProxy: "https://proxy.golang.org" + checksumDb: "https://sum.golang.org" + verifyChecksums: true + + npm: + enabled: true + upstreamRegistry: "https://registry.npmjs.org" + + pypi: + enabled: true + upstreamUrl: "https://pypi.org" + simpleApiUrl: "https://pypi.org/simple" + +# Ingress configuration +ingress: + enabled: false + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/proxy-body-size: "2048m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + nginx.ingress.kubernetes.io/proxy-send-timeout: "300" + + # Ingress for frontend + frontend: + enabled: true + host: "gohoarder.local" + tls: + enabled: false + secretName: "gohoarder-frontend-tls" + + # Ingress for API (if you want separate ingress) + api: + enabled: false + host: "api.gohoarder.local" + tls: + enabled: false + secretName: "gohoarder-api-tls" + +# Autoscaling configuration +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +# Pod Disruption Budget +podDisruptionBudget: + enabled: false + minAvailable: 1 + +# Network Policy +networkPolicy: + enabled: false + # Allow external access to server + ingress: + - from: + - namespaceSelector: {} + ports: + - protocol: TCP + port: 8080 diff --git a/charts/packages/gohoarder-0.0.1.tgz b/charts/packages/gohoarder-0.0.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..36662617698b6e3b6a82803f11fcd0844a71dfe8 GIT binary patch literal 15829 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 literal 0 HcmV?d00001 diff --git a/index.yaml b/index.yaml index 8e1d61b..1b4f79b 100644 --- a/index.yaml +++ b/index.yaml @@ -1,5 +1,33 @@ apiVersion: v1 entries: + gohoarder: + - apiVersion: v2 + appVersion: 0.0.1 + created: "2026-01-02T23:23:54.075202022Z" + description: A universal package cache proxy supporting npm, PyPI, and Go modules + with security scanning + digest: 2271641c9d1e9942a02d94fe62b1c49d0835541160bf8abe198a1293ff4b811d + home: https://github.com/lukaszraczylo/gohoarder + icon: https://raw.githubusercontent.com/lukaszraczylo/gohoarder/main/docs/logo.png + keywords: + - package-manager + - cache + - proxy + - npm + - pypi + - go-modules + - security + - vulnerability-scanning + maintainers: + - email: lukasz@raczylo.com + name: Lukasz Raczylo + name: gohoarder + sources: + - https://github.com/lukaszraczylo/gohoarder + type: application + urls: + - https://github.com/lukaszraczylo/helm-charts/releases/download/gohoarder-0.0.1/gohoarder-0.0.1.tgz + version: 0.0.1 jobs-manager: - annotations: artifacthub.io/changes: | @@ -1623,4 +1651,4 @@ entries: urls: - https://github.com/lukaszraczylo/helm-charts/releases/download/kubemirror-0.2.8/kubemirror-0.2.8.tgz version: 0.2.8 -generated: "2025-12-27T03:40:40.629057295Z" +generated: "2026-01-02T23:23:54.07379072Z"