2 Commits

Author SHA1 Message Date
renovate[bot]
9bc0fb78a2 chore(deps): update helm release woodpecker to v1.6.2 2025-12-21 18:52:13 +00:00
Morten Olsen
be53c17847 add woodpecker 2025-12-21 19:51:40 +01:00
469 changed files with 5868 additions and 7937 deletions

View File

@@ -1,16 +0,0 @@
{
"permissions": {
"allow": [
"Bash(git -C /Users/alice/Projects/private/homelab/apps log --oneline -10)",
"Bash(git -C /Users/alice/Projects/private/homelab/apps remote -v)",
"Bash(git -C /Users/alice/Projects/private/homelab/apps config --list)",
"Bash(ls:*)",
"Bash(git -C /Users/alice/Projects/private/homelab/apps log --all --oneline --decorate -15)",
"Bash(git -C /Users/alice/Projects/private/homelab/apps branch -a)",
"Bash(helm dependency:*)",
"Bash(helm lint:*)",
"Bash(helm template:*)",
"Bash(kubectl get:*)"
]
}
}

11
.gitignore vendored
View File

@@ -1,11 +0,0 @@
# Helm chart dependencies (packaged library charts)
# These are generated by 'helm dependency update' and should not be committed
**/charts/*.tgz
charts/*.tgz
**/__pycache__/
__pycache__/
**/Chart.lock
*-local-secret.yaml

380
AGENTS.md
View File

@@ -58,7 +58,6 @@ mkdir -p apps/charts/my-app/templates
```
#### Chart.yaml
```yaml
apiVersion: v2
version: 1.0.0
@@ -66,7 +65,6 @@ name: my-app
```
#### values.yaml
```yaml
image:
repository: docker.io/org/my-app
@@ -75,5 +73,381 @@ image:
subdomain: my-app
```
See ./apps/common/README.md for guide on writing charts
### 2. Core Templates
#### Deployment Template
Create `templates/deployment.yaml`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
spec:
strategy:
type: Recreate
replicas: 1
revisionHistoryLimit: 0
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /data
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"
```
#### Service Template
Create `templates/service.yaml`:
```yaml
apiVersion: v1
kind: Service
metadata:
name: "{{ .Release.Name }}"
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: "{{ .Release.Name }}"
```
#### Persistent Volume Claim
Create `templates/pvc.yaml`:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "{{ .Release.Name }}-data"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
```
## Custom Resource Definitions (CRDs)
This project uses several custom resources that are managed by operators in the cluster:
### 1. OIDC Client (OpenID Connect Authentication)
The `OidcClient` resource automatically provisions OAuth2/OIDC clients with your identity provider.
Create `templates/client.yaml`:
```yaml
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
redirectUris:
- path: /oauth/oidc/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict
```
**What it does:**
- Creates an OIDC client in your identity provider (e.g., Authentik)
- Generates a Kubernetes secret named `{{ .Release.Name }}-client` containing:
- `clientId`: The OAuth client ID
- `clientSecret`: The OAuth client secret
- `configuration`: The OIDC provider URL
**Using in deployment:**
```yaml
env:
- name: OAUTH_CLIENT_ID
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: clientId
- name: OAUTH_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: clientSecret
- name: OPENID_PROVIDER_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: configuration
```
### 2. PostgreSQL Database
The `PostgresDatabase` resource automatically provisions PostgreSQL databases.
Create `templates/database.yaml`:
```yaml
apiVersion: homelab.mortenolsen.pro/v1
kind: PostgresDatabase
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
```
**What it does:**
- Creates a PostgreSQL database with the same name as your release
- Creates a user with appropriate permissions
- Generates a Kubernetes secret named `{{ .Release.Name }}-database` containing:
- `url`: Complete PostgreSQL connection URL
- `host`: Database hostname
- `port`: Database port
- `database`: Database name
- `username`: Database username
- `password`: Database password
**Using in deployment:**
```yaml
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-database"
key: url
```
### 3. Secret Generation
The `GenerateSecret` resource creates secure random secrets.
Create `templates/secret.yaml`:
```yaml
apiVersion: homelab.mortenolsen.pro/v1
kind: GenerateSecret
metadata:
name: "{{ .Release.Name }}-secrets"
spec:
fields:
- name: encryptionkey
encoding: hex # Options: hex, base64, alphanumeric
length: 64 # Length in bytes (before encoding)
- name: apitoken
encoding: base64
length: 32
```
**What it does:**
- Generates cryptographically secure random values
- Creates a Kubernetes secret with the specified fields
- Supports different encoding formats for different use cases
**Using in deployment:**
```yaml
env:
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-secrets"
key: encryptionkey
```
### 4. External HTTP Service
The `ExternalHttpService` resource configures ingress routing for your application.
Create `templates/external-http-service.yaml`:
```yaml
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80
```
**What it does:**
- Creates ingress routes for your application
- Configures subdomain routing (e.g., `myapp.yourdomain.com`)
- Handles TLS termination automatically
- Integrates with your service mesh (if applicable)
## Best Practices
### 1. Naming Conventions
- Use `{{ .Release.Name }}` consistently for all resource names
- Suffix resource names appropriately: `-data`, `-secrets`, `-client`, `-database`
### 2. Container Configuration
- Always specify health checks (liveness and readiness probes)
- Use named ports (e.g., `http`, `grpc`) instead of port numbers
- Set `revisionHistoryLimit: 0` to prevent accumulation of old ReplicaSets
### 3. Environment Variables
- Never hardcode secrets in values.yaml
- Use secretKeyRef to reference generated secrets
- Group related environment variables together
### 4. Persistent Storage
- Always use PVCs for stateful data
- Consider storage requirements carefully (start with reasonable defaults)
- Mount data at standard paths for the application
### 5. OIDC Integration
- Set `ENABLE_SIGNUP: "false"` if using OIDC
- Enable OIDC signup with `ENABLE_OAUTH_SIGNUP: "true"`
- Configure email merging if needed with `OAUTH_MERGE_ACCOUNTS_BY_EMAIL`
### 6. Database Usage
- Only include database.yaml if the app needs PostgreSQL
- Applications should support DATABASE_URL environment variable
- Consider connection pooling settings for production
## Disabling Applications
To temporarily disable an application, rename its directory with `.disabled` suffix:
```bash
mv apps/charts/my-app apps/charts/my-app.disabled
```
The ArgoCD ApplicationSet will automatically exclude directories matching `*.disabled`.
## Testing Your Chart
1. **Lint your chart:**
```bash
helm lint apps/charts/my-app
```
2. **Render templates locally:**
```bash
helm template my-app apps/charts/my-app
```
3. **Dry run installation:**
```bash
helm install my-app apps/charts/my-app --dry-run --debug
```
## Deployment Workflow
**IMPORTANT:** There is no test environment. When creating or modifying applications:
1. **Make changes directly to the files** - The agent will write changes to the actual chart files
2. **User deploys the changes** - After changes are made, the user must deploy them to the cluster
3. **Debug with kubectl** - If issues arise after deployment, agents can use kubectl to:
- Check pod status and logs
- Inspect generated resources
- Verify secret creation
- Troubleshoot configuration issues
**Note:** Agents cannot deploy applications themselves. They can only:
- Create and modify chart files
- Use kubectl to investigate deployment issues
- Provide debugging assistance and recommendations
## Common Patterns
### Application with OIDC + Database
For apps requiring both authentication and database:
- Include `client.yaml` for OIDC
- Include `database.yaml` for PostgreSQL
- Reference both secrets in deployment
### Stateless Applications
For simple stateless apps:
- Omit `pvc.yaml`
- Remove volume mounts from deployment
- Consider using `Deployment` scaling if appropriate
### Background Services
For services without web interface:
- Omit `external-http-service.yaml`
- Omit `client.yaml` (no OIDC needed)
- Focus on service discovery within cluster
## Troubleshooting
### Secret Not Found
If secrets are not being created:
1. Check that the CRD controller is running
2. Verify the `environment` value matches your setup
3. Check controller logs for provisioning errors
### OIDC Issues
1. Verify redirect URIs match exactly
2. Check that the identity provider is accessible
3. Ensure the client secret is being properly mounted
### Database Connection
1. Verify the database operator is running
2. Check network policies between namespaces
3. Ensure the database server has capacity
## Global Values
Applications can access global values through `{{ .Values.globals }}`:
- `environment`: The deployment environment (e.g., "production", "staging")
- Additional values can be added at the root chart level
## Maintenance
### Updating Images
1. Update the tag in `values.yaml`:
```yaml
tag: v1.0.0 # Use semantic version tags only
```
2. **Note:** Do not include SHA digests in tags. Immutable digests are automatically added later by Renovate
### Renovate Integration
The project uses Renovate for automated dependency updates. Configure in `renovate.json5` to:
- Auto-update container images
- Create pull requests for updates
- Group related updates
### Backup Considerations
For applications with persistent data:
1. Consider implementing backup CronJobs
2. Use volume snapshots if available
3. Export data regularly for critical applications
## Contributing
When adding new applications:
1. Follow the existing patterns and conventions
2. Document any special requirements in the chart's README
3. Consider security implications of all configurations
4. Update this document if introducing new patterns
## Maintaining This Document
**IMPORTANT:** When making changes to the project structure, patterns, or custom resources:
- Keep this AGENTS.md file up to date with any changes
- Document new CRDs or custom resources as they are added
- Update examples if the patterns change
- Add new sections for significant new features or patterns
- Ensure all code examples remain accurate and tested
This document serves as the primary reference for creating and maintaining applications in this project. Keeping it current ensures consistency and helps onboard new contributors.

100
CLAUDE.md
View File

@@ -1,100 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Kubernetes Helm-based homelab application deployment system using ArgoCD for GitOps. Contains 40+ containerized applications deployed via Helm charts with a shared common library to minimize template duplication.
## Commands
```bash
# Validate YAML files
yamllint .
# Helm chart operations (run from chart directory)
helm dependency build # Fetch common library dependency
helm lint . # Validate chart syntax
helm template <release> . --set globals.environment=prod --set globals.domain=example.com
# Utility scripts
./scripts/migrate_database.py <source_db> <dest_db> [--clean] # PostgreSQL migration
./scripts/sync_pvc_with_host.sh <host-path> <namespace> <pvc> # PVC sync
```
## Architecture
### Directory Structure
- `apps/charts/` - Individual application Helm charts (deployed to `prod` namespace)
- `apps/common/` - Shared Helm library chart with standardized templates
- `apps/root/` - ArgoCD ApplicationSet for auto-discovery
- `shared/charts/` - Shared infrastructure services (authentik, nats)
- `scripts/` - Python/Bash utility scripts for database migration and PVC sync
### Deployment Model
Three ArgoCD ApplicationSets auto-discover charts from their respective `charts/` directories. Folders suffixed with `.disabled` are excluded from deployment.
### Common Library Pattern
Most charts use the common library (`apps/common/`) which provides standardized templates. A minimal chart needs:
1. `Chart.yaml` with common library dependency:
```yaml
apiVersion: v2
version: 1.0.0
name: my-app
dependencies:
- name: common
version: 1.0.0
repository: file://../../common
```
2. Standardized `values.yaml` (see `apps/common/README.md` for full structure)
3. Template files that include common helpers:
```yaml
# templates/deployment.yaml
{{ include "common.deployment" . }}
```
Or use single file with `{{ include "common.all" . }}` to render all resources automatically.
### Key Templates
- `common.deployment` - Deployment with health probes, volumes, init containers
- `common.service` - Service(s) with port mapping
- `common.pvc` - Persistent volume claims
- `common.virtualService` - Istio routing (public/private gateways)
- `common.oidc` - Authentik OIDC client registration
- `common.database` - PostgreSQL database provisioning
- `common.externalSecrets` - Password generators and secret templates
### Placeholders in values.yaml
- `{release}` - Release name
- `{namespace}` - Release namespace
- `{fullname}` - Full app name
- `{subdomain}` - App subdomain (from `subdomain` value)
- `{domain}` - Global domain
- `{timezone}` - Global timezone
### Secret Naming Conventions
- OIDC credentials: `{release}-oidc-credentials` (clientId, clientSecret, issuer)
- Database connection: `{release}-connection` (url, host, port, user, password)
- Generated secrets: `{release}-secrets`
## Conventions
- Chart and release names use kebab-case
- All container images pinned by SHA256 digest (Renovate manages updates)
- Storage uses `persistent` storageClassName
- Istio VirtualServices route via public/private gateways
- Deployment strategy: `Recreate` for stateful apps, `RollingUpdate` for stateless
## YAML Style
- Max line length: 120 characters
- Indentation: 2 spaces
- Truthy values: `true`, `false`, `on`, `off`
## Documentation
- `AGENTS.md` - Chart creation guidelines
- `apps/common/README.md` - Complete common library reference
- `apps/common/MIGRATION.md` - Guide for migrating charts to common library
- `apps/common/TEMPLATING.md` - Placeholder system documentation

View File

@@ -1,3 +1,3 @@
apiVersion: v2
version: 1.0.0
name: vaultwarden
name: appsmith

View File

@@ -0,0 +1,10 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
redirectUris:
- path: /api/auth/oidc/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict

View File

@@ -0,0 +1,11 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80

View File

@@ -0,0 +1,31 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
labels:
app: "{{ .Release.Name }}"
spec:
replicas: 1
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 80
name: http
env:
volumeMounts:
- mountPath: /appsmith-stacks
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"

View File

@@ -0,0 +1,11 @@
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-data'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -1,11 +1,11 @@
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}-private"
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateways.private }}"
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
@@ -16,4 +16,3 @@ spec:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -0,0 +1,4 @@
subdomain: appsmith
image:
repository: index.docker.io/appsmith/appsmith-ce
tag: latest@sha256:0776a0a9665919800d22fc736956ec54fedd16a9a30f9d4ad3f3fc0fd8ac8694

View File

@@ -1,7 +1,3 @@
apiVersion: v2
version: 1.0.0
name: audiobookshelf
dependencies:
- name: common
version: 1.0.0
repository: file://../../common

View File

@@ -0,0 +1,13 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
redirectUris:
- path: /audiobookshelf/auth/openid/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict
- path: /audiobookshelf/auth/openid/mobile-redirect
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict

View File

@@ -1 +1,52 @@
{{ include "common.deployment" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: '{{ .Release.Name }}'
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: '{{ .Release.Name }}'
template:
metadata:
labels:
app: '{{ .Release.Name }}'
spec:
containers:
- name: '{{ .Release.Name }}'
image: '{{ .Values.image.repository }}:{{ .Values.image.tag }}'
imagePullPolicy: '{{ .Values.image.pullPolicy }}'
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /config
name: config
- mountPath: /metadata
name: metadata
- mountPath: /audiobooks
name: audiobooks
- mountPath: /podcasts
name: podcasts
volumes:
- name: config
persistentVolumeClaim:
claimName: '{{ .Release.Name }}-config'
- name: metadata
persistentVolumeClaim:
claimName: '{{ .Release.Name }}-metadata'
- name: audiobooks
persistentVolumeClaim:
claimName: books
- name: podcasts
persistentVolumeClaim:
claimName: podcasts

View File

@@ -1 +0,0 @@
{{ include "common.dns" . }}

View File

@@ -0,0 +1,11 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80

View File

@@ -1 +0,0 @@
{{ include "common.oidc" . }}

View File

@@ -1 +1,24 @@
{{ include "common.pvc" . }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-config'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-metadata'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -1 +1,15 @@
{{ include "common.service" . }}
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -1 +1,18 @@
{{ include "common.virtualService" . }}
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -1,68 +1,5 @@
image:
repository: ghcr.io/advplyr/audiobookshelf
tag: 2.32.1@sha256:a52dc5db694a5bf041ce38f285dd6c6a660a4b1b21e37ad6b6746433263b2ae5
tag: 2.31.0@sha256:e23adb24848d99d19cd1e251aee4e1e12ed4f5effc8ccb21754b062b6a06cf66
pullPolicy: IfNotPresent
subdomain: audiobookshelf
# Deployment configuration
deployment:
strategy: Recreate
replicas: 1
# Container configuration
container:
port: 80
healthProbe:
type: httpGet
path: /ping
# Service configuration
service:
port: 80
type: ClusterIP
# Volume configuration
volumes:
- name: config
mountPath: /config
persistentVolumeClaim: config # Will be prefixed with release name in template
- name: metadata
mountPath: /metadata
persistentVolumeClaim: metadata # Will be prefixed with release name in template
- name: audiobooks
mountPath: /audiobooks
persistentVolumeClaim: books
- name: podcasts
mountPath: /podcasts
persistentVolumeClaim: podcasts
# Persistent volume claims
persistentVolumeClaims:
- name: config
size: 1Gi
storageClassName: persistent
- name: metadata
size: 5Gi
storageClassName: persistent
# DNS configuration
dns:
enabled: true
type: A
dnsClassRef:
name: private-dns
# OIDC/Authentik configuration
oidc:
enabled: true
redirectUris:
- "/audiobookshelf/auth/openid/callback"
- "/audiobookshelf/auth/openid/mobile-redirect"
# VirtualService configuration
virtualService:
enabled: true
gateways:
public: true
private: true

View File

@@ -1,3 +1,3 @@
apiVersion: v2
version: 1.0.0
name: shared
name: backup

View File

@@ -0,0 +1,48 @@
{{- $values := .Values -}}
{{- $release := .Release -}}
---
{{- range $key, $value := $values.jobs}}
apiVersion: batch/v1
kind: CronJob
metadata:
name: "{{ $release.Name }}-{{ $key }}-backup"
spec:
schedule: "{{ $value.cron.backup }}"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
metadata:
annotations:
sidecar.istio.io/inject: "false"
spec:
containers:
- name: "{{ $release.Name }}-{{ $key }}-backup"
image: ghcr.io/morten-olsen/homelab-operator-backup:main
imagePullPolicy: Always
command: ["/app/backup.sh"]
env:
- name: RESTIC_PASSWORD
valueFrom:
secretKeyRef:
name: "{{ $values.password.name }}"
key: "{{ $values.password.key }}"
volumeMounts:
- name: source
mountPath: "/mnt/source"
- name: target
mountPath: "/mnt/backup"
subPath: "{{ $release.Name }}-{{ $key }}"
volumes:
- name: source
persistentVolumeClaim:
claimName: "{{ $value.source }}"
- name: target
persistentVolumeClaim:
claimName: "{{ $values.target }}"
restartPolicy: OnFailure
---
{{- end }}

View File

@@ -0,0 +1,43 @@
{{- $values := .Values -}}
{{- $release := .Release -}}
---
{{- range $key, $value := $values.jobs}}
apiVersion: batch/v1
kind: CronJob
metadata:
name: "{{ $release.Name }}-{{ $key }}-cleanup"
spec:
schedule: "{{ $value.cron.cleanup }}"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
metadata:
annotations:
sidecar.istio.io/inject: "false"
spec:
containers:
- name: "{{ $release.Name }}-{{ $key }}-cleanup"
image: ghcr.io/morten-olsen/homelab-operator-backup:main
imagePullPolicy: Always
command: ["/app/cleanup.sh"]
env:
- name: RESTIC_PASSWORD
valueFrom:
secretKeyRef:
name: "{{ $values.password.name }}"
key: "{{ $values.password.key }}"
volumeMounts:
- name: target
mountPath: "/mnt/backup"
subPath: "{{ $release.Name }}-{{ $key }}"
volumes:
- name: target
persistentVolumeClaim:
claimName: "{{ $values.target }}"
restartPolicy: OnFailure
---
{{- end }}

View File

@@ -0,0 +1,19 @@
globals:
environment: prod
timezone: Europe/Amsterdam
domain: olsen.cloud
image:
repository: garethgeorge/backrest
tag: latest@sha256:1308397161321b3c5aeca8acc6bf26eccb990df385f2532d3ce0eaa8b483dedf
pullPolicy: IfNotPresent
subdomain: restic
password:
name: backup
key: password
jobs:
pictures:
cron:
backup: "0 2 * * *"
cleanup: "0 4 * * SUN"
source: pictures
target: backups

View File

@@ -1,7 +1,3 @@
apiVersion: v2
version: 1.0.0
name: baikal
dependencies:
- name: common
version: 1.0.0
repository: file://../../common
name: esphome

View File

@@ -1 +1,6 @@
{{ include "common.database" . }}
apiVersion: homelab.mortenolsen.pro/v1
kind: PostgresDatabase
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'

View File

@@ -1 +1,46 @@
{{ include "common.deployment" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
env:
- name: TZ
value: "{{ .Values.globals.timezone }}"
volumeMounts:
- mountPath: /var/www/baikal/Specific
name: data
- mountPath: /var/www/baikal/config
name: config
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"
- name: config
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-config"

View File

@@ -0,0 +1,11 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80

View File

@@ -1 +1,24 @@
{{ include "common.pvc" . }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: "{{ .Release.Name }}-data"
spec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: "1Gi"
storageClassName: "{{ .Values.globals.environment }}"
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: "{{ .Release.Name }}-config"
spec:
accessModes:
- "ReadWriteOnce"
resources:
requests:
storage: "1Gi"
storageClassName: "{{ .Values.globals.environment }}"

View File

@@ -1 +1,15 @@
{{ include "common.service" . }}
apiVersion: v1
kind: Service
metadata:
name: "{{ .Release.Name }}"
labels:
app: "{{ .Release.Name }}"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: "{{ .Release.Name }}"

View File

@@ -0,0 +1,18 @@
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -1 +1,18 @@
{{ include "common.virtualService" . }}
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -2,49 +2,4 @@ image:
repository: docker.io/ckulka/baikal
tag: 0.10.1-nginx@sha256:434bdd162247cc6aa6f878c9b4dce6216e39e79526b980453b13812d5f8ebf4b
pullPolicy: IfNotPresent
subdomain: baikal
# Deployment configuration
deployment:
strategy: Recreate
replicas: 1
# Container configuration
container:
port: 80
healthProbe:
type: tcpSocket
port: http # Use named port
# Database configuration
database:
enabled: true
# Service configuration
service:
port: 80
type: ClusterIP
# Volume configuration
volumes:
- name: data
mountPath: /var/www/baikal/Specific
persistentVolumeClaim: data
- name: config
mountPath: /var/www/baikal/config
persistentVolumeClaim: config
# Persistent volume claims
persistentVolumeClaims:
- name: data
size: 1Gi
- name: config
size: 1Gi
# VirtualService configuration
virtualService:
enabled: true
gateways:
public: true
private: true

View File

@@ -0,0 +1,3 @@
apiVersion: v2
version: 1.0.0
name: blinko

View File

@@ -0,0 +1,10 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: "{{ .Release.Name }}"
spec:
environment: "{{ .Values.globals.environment }}"
redirectUris:
- path: api/auth/callback/authentik
subdomain: "{{ .Values.subdomain }}"
matchingMode: strict

View File

@@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
spec:
strategy:
type: RollingUpdate
replicas: 1
revisionHistoryLimit: 0
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
- name: http
containerPort: 1111
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /data
name: data
env:
- name: TZ
value: "{{ .Values.globals.timezone }}"
- name: NODE_ENV
value: "production"
- name: NEXTAUTH_URL
value: "https://{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- name: NEXT_PUBLIC_BASE_URL
value: "https://{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- name: NEXTAUTH_SECRET
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-secrets"
key: betterauth
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-pg-connection"
key: url
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"

View File

@@ -0,0 +1,11 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80

View File

@@ -0,0 +1,11 @@
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-data'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -0,0 +1,9 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: GenerateSecret
metadata:
name: '{{ .Release.Name }}-secrets'
spec:
fields:
- name: betterauth
encoding: base64
length: 64

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 1111
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,18 @@
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -0,0 +1,5 @@
image:
repository: blinkospace/blinko
tag: latest@sha256:04ad2a67f617e122db98425d39c2d0d901492729b3aee5a7e8c4d351009ee9e9
pullPolicy: IfNotPresent
subdomain: blinko

View File

@@ -1,7 +1,3 @@
apiVersion: v2
version: 1.0.0
name: bytestash
dependencies:
- name: common
version: 1.0.0
repository: file://../../common
name: ByteStash

View File

@@ -0,0 +1,10 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
redirectUris:
- path: /api/auth/oidc/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict

View File

@@ -1 +1,54 @@
{{ include "common.deployment" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
labels:
app: "{{ .Release.Name }}"
spec:
replicas: 1
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 5000
name: http
env:
- name: ALLOW_NEW_ACCOUNTS
value: "true"
- name: DISABLE_INTERNAL_ACCOUNTS
value: "true"
- name: OIDC_ENABLED
value: "true"
- name: OIDC_DISPLAY_NAME
value: OIDC
- name: OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: clientId
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: clientSecret
- name: OIDC_ISSUER_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: configurationIssuer
volumeMounts:
- mountPath: /data/snippets
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"

View File

@@ -0,0 +1,11 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80

View File

@@ -1 +0,0 @@
{{ include "common.oidc" . }}

View File

@@ -1 +1,11 @@
{{ include "common.pvc" . }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-data'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -1 +1,15 @@
{{ include "common.service" . }}
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 5000
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -1 +1,18 @@
{{ include "common.virtualService" . }}
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -1,74 +1,4 @@
subdomain: bytestash
image:
repository: ghcr.io/jordan-dalby/bytestash
tag: 1.5.9@sha256:9c17b5510ca45c976fe23b0d4705ad416aa58d4bf756a70e03ef1f08cf7801fd
pullPolicy: IfNotPresent
subdomain: bytestash
# Deployment configuration
deployment:
strategy: Recreate
replicas: 1
# Container configuration
container:
ports:
- name: http
port: 5000
protocol: TCP
healthProbe:
type: tcpSocket
port: http
# Service configuration
service:
port: 80
type: ClusterIP
# Volume configuration
volumes:
- name: data
mountPath: /data/snippets
persistentVolumeClaim: data
# Persistent volume claims
persistentVolumeClaims:
- name: data
size: 1Gi
storageClassName: persistent
# VirtualService configuration
virtualService:
enabled: true
gateways:
public: true
private: true
# OIDC/Authentik configuration
oidc:
enabled: true
redirectUris:
- "/api/auth/oidc/callback"
subjectMode: user_username
# Environment variables
env:
ALLOW_NEW_ACCOUNTS: "true"
DISABLE_INTERNAL_ACCOUNTS: "true"
OIDC_ENABLED: "true"
OIDC_DISPLAY_NAME: OIDC
OIDC_CLIENT_ID:
valueFrom:
secretKeyRef:
name: "{release}-oidc-credentials"
key: clientId
OIDC_CLIENT_SECRET:
valueFrom:
secretKeyRef:
name: "{release}-oidc-credentials"
key: clientSecret
OIDC_ISSUER_URL:
valueFrom:
secretKeyRef:
name: "{release}-oidc-credentials"
key: issuer

View File

@@ -1,7 +1,3 @@
apiVersion: v2
version: 1.0.0
name: calibre-web
dependencies:
- name: common
version: 1.0.0
repository: file://../../common

View File

@@ -0,0 +1,10 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
redirectUris:
- path: /api/auth/oidc/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict

View File

@@ -1 +1,44 @@
{{ include "common.deployment" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
labels:
app: "{{ .Release.Name }}"
spec:
replicas: 1
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
- containerPort: 8083
name: http
env:
- name: TZ
value: "{{ .Values.globals.timezone }}"
- name: NETWORK_SHARE_MODE
value: "true"
- name: PUID
value: "1000"
- name: PGID
value: "1000"
volumeMounts:
- mountPath: /config
name: data
- mountPath: /calibre-library
name: books
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"
- name: books
persistentVolumeClaim:
claimName: books

View File

@@ -0,0 +1,11 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80

View File

@@ -1 +1,11 @@
{{ include "common.pvc" . }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-data'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -1 +1,15 @@
{{ include "common.service" . }}
apiVersion: v1
kind: Service
metadata:
name: "{{ .Release.Name }}"
labels:
app: "{{ .Release.Name }}"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8083
protocol: TCP
name: http
selector:
app: "{{ .Release.Name }}"

View File

@@ -1 +1,18 @@
{{ include "common.virtualService" . }}
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -2,49 +2,4 @@ image:
repository: crocodilestick/calibre-web-automated
tag: latest@sha256:577e846f104fd21453ef306eefb4a95dd95b3b9ddd2463a150944494284da0fd
pullPolicy: IfNotPresent
subdomain: calibre-web
# Deployment configuration
deployment:
strategy: Recreate
replicas: 1
# Container configuration
container:
port: 8083
healthProbe:
type: tcpSocket
# Service configuration
service:
port: 80
type: ClusterIP
# Volume configuration
volumes:
- name: data
mountPath: /config
persistentVolumeClaim: data # Will be prefixed with release name
- name: books
mountPath: /calibre-library
persistentVolumeClaim: books # External PVC, used as-is
# Persistent volume claims
persistentVolumeClaims:
- name: data
size: 1Gi
storageClassName: persistent
# VirtualService configuration
virtualService:
enabled: true
gateways:
public: true
private: true
# Environment variables
env:
NETWORK_SHARE_MODE: "true"
PUID: "1000"
PGID: "1000"

View File

@@ -1,7 +1,3 @@
apiVersion: v2
version: 1.0.0
name: coder
dependencies:
- name: common
version: 1.0.0
repository: file://../../common
name: openwebui

View File

@@ -0,0 +1,10 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
redirectUris:
- path: /api/v2/users/oidc/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict

View File

@@ -1 +1,74 @@
{{ include "common.deployment" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
spec:
strategy:
type: Recreate
replicas: 1
revisionHistoryLimit: 0
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
serviceAccountName: "{{ .Release.Name }}-serviceaccount"
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
- name: http
containerPort: 7080
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /home/coder/.config
name: data
env:
- name: CODER_HTTP_ADDRESS
value: "0.0.0.0:7080"
- name: CODER_OIDC_ALLOWED_GROUPS
value: admin
- name: CODER_OIDC_GROUP_FIELD
value: groups
- name: CODER_ACCESS_URL
value: https://coder.olsen.cloud
- name: CODER_OIDC_ICON_URL
value: https://authentik.olsen.cloud/static/dist/assets/icons/icon.png
- name: CODER_DISABLE_PASSWORD_AUTH
value: "true"
- name: CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS
value: "false"
- name: CODER_OIDC_SIGN_IN_TEXT
value: "Sign in with OIDC"
- name: CODER_OIDC_SCOPES
value: openid,profile,email,offline_access
- name: CODER_OIDC_ISSUER_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: configurationIssuer
- name: CODER_OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: clientId
- name: CODER_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-client"
key: clientSecret
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"

View File

@@ -1 +0,0 @@
{{ include "common.oidc" . }}

View File

@@ -1 +1,11 @@
{{ include "common.pvc" . }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-data'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -1 +1,15 @@
{{ include "common.service" . }}
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 7080
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -1 +1,18 @@
{{ include "common.virtualService" . }}
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -2,79 +2,4 @@ image:
repository: ghcr.io/coder/coder
tag: v2.29.1@sha256:19b3ecd02510b4ee91ba488c61a3f40a6c164c9aeef38999c855e55fd653097c
pullPolicy: IfNotPresent
subdomain: coder
# Deployment configuration
deployment:
strategy: Recreate
replicas: 1
serviceAccountName: "{release}-serviceaccount" # Will be templated
# Container configuration
container:
port: 7080
healthProbe:
type: tcpSocket
port: http # Use named port
# Service configuration
service:
port: 80
type: ClusterIP
# OIDC client
oidc:
enabled: true
redirectUris:
- "/api/v2/users/oidc/callback"
# Volume configuration
volumes:
- name: data
mountPath: /home/coder/.config
persistentVolumeClaim: data
storageClassName: persistent
# Persistent volume claims
persistentVolumeClaims:
- name: data
size: 1Gi
# VirtualService configuration
virtualService:
enabled: true
allowWildcard: true
gateways:
public: false
private: true
# Environment variables
env:
CODER_HTTP_ADDRESS: "0.0.0.0:7080"
CODER_OIDC_ALLOWED_GROUPS: admin
CODER_OIDC_GROUP_FIELD: groups
CODER_ACCESS_URL:
value: "https://{subdomain}.{domain}"
CODER_WILDCARD_ACCESS_URL:
value: "*.{subdomain}.{domain}"
CODER_OIDC_ICON_URL: "https://{subdomain}.{domain}/static/dist/assets/icons/icon.png"
CODER_DISABLE_PASSWORD_AUTH: "true"
CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS: "false"
CODER_OIDC_SIGN_IN_TEXT: "Sign in with OIDC"
CODER_OIDC_SCOPES: "openid,profile,email,offline_access"
CODER_OIDC_ISSUER_URL:
valueFrom:
secretKeyRef:
name: "{release}-oidc-credentials"
key: issuer
CODER_OIDC_CLIENT_ID:
valueFrom:
secretKeyRef:
name: "{release}-oidc-credentials"
key: clientId
CODER_OIDC_CLIENT_SECRET:
valueFrom:
secretKeyRef:
name: "{release}-oidc-credentials"
key: clientSecret

View File

@@ -0,0 +1,3 @@
apiVersion: v2
version: 1.0.0
name: data

View File

@@ -3,4 +3,4 @@ kind: PostgresDatabase
metadata:
name: '{{ .Release.Name }}'
spec:
environment: prod
environment: '{{ .Values.globals.environment }}'

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,3 @@
apiVersion: v2
version: 1.0.0
name: drip

View File

@@ -0,0 +1,80 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
spec:
strategy:
type: Recreate
replicas: 1
revisionHistoryLimit: 0
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
- name: tcp-tunnel-min
containerPort: {{ .Values.service.tcpPortMin }}
protocol: TCP
- name: tcp-tunnel-max
containerPort: {{ .Values.service.tcpPortMax }}
protocol: TCP
volumeMounts:
- mountPath: /app/data
name: data
env:
- name: TZ
value: UTC
- name: AUTH_TOKEN
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-secrets"
key: authtoken
- name: DOMAIN
value: "{{ .Values.subdomain }}.{{ .Values.globals.environment }}"
command:
- drip-server
- --domain
- "{{ .Values.subdomain }}.{{ .Values.globals.environment }}"
- --port
- "{{ .Values.service.port }}"
- --token
- "$(AUTH_TOKEN)"
- --tcp-port-min
- "{{ .Values.service.tcpPortMin }}"
- --tcp-port-max
- "{{ .Values.service.tcpPortMax }}"
livenessProbe:
exec:
command: {{ .Values.healthcheck.test }}
initialDelaySeconds: {{ .Values.healthcheck.startPeriod | default 0 | trimSuffix "s" | int }}
periodSeconds: {{ .Values.healthcheck.interval | default 10 | trimSuffix "s" | int }}
timeoutSeconds: {{ .Values.healthcheck.timeout | default 1 | trimSuffix "s" | int }}
failureThreshold: {{ .Values.healthcheck.retries | default 3 }}
readinessProbe:
exec:
command: {{ .Values.healthcheck.test }}
initialDelaySeconds: {{ .Values.healthcheck.startPeriod | default 0 | trimSuffix "s" | int }}
periodSeconds: {{ .Values.healthcheck.interval | default 10 | trimSuffix "s" | int }}
timeoutSeconds: {{ .Values.healthcheck.timeout | default 1 | trimSuffix "s" | int }}
failureThreshold: {{ .Values.healthcheck.retries | default 3 }}
resources:
limits:
cpu: "{{ .Values.resources.limits.cpu }}"
memory: "{{ .Values.resources.limits.memory }}"
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"

View File

@@ -0,0 +1,11 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: {{ .Values.service.port }}

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "{{ .Release.Name }}-data"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -0,0 +1,9 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: GenerateSecret
metadata:
name: "{{ .Release.Name }}-secrets"
spec:
fields:
- name: authtoken
encoding: hex
length: 64

View File

@@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: "{{ .Release.Name }}"
spec:
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
- port: {{ .Values.service.tcpPortMin }}
targetPort: tcp-tunnel-min
protocol: TCP
name: tcp-tunnel-min
- port: {{ .Values.service.tcpPortMax }}
targetPort: tcp-tunnel-max
protocol: TCP
name: tcp-tunnel-max
selector:
app: "{{ .Release.Name }}"

View File

@@ -0,0 +1,18 @@
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -0,0 +1,27 @@
image:
repository: ghcr.io/gouryella/drip
tag: latest@sha256:440bcfd7eb75bf0b337d60346e44ae9e5be803e2504697ea7aa1b4f5fce568b9
pullPolicy: IfNotPresent
subdomain: drip
service:
port: 443
tcpPortMin: 20000
tcpPortMax: 20100
resources:
limits:
cpu: 1
memory: 512Mi
healthcheck:
test:
- wget
- --no-verbose
- --tries=1
- --spider
- http://localhost:443/health
interval: 30s
timeout: 3s
retries: 3
startPeriod: 10s

View File

@@ -0,0 +1,3 @@
apiVersion: v2
version: 1.0.0
name: environment

View File

@@ -0,0 +1,9 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: Environment
metadata:
name: "{{ .Values.globals.environment }}"
spec:
domain: "{{ .Values.globals.domain }}"
networkIp: 192.168.20.180
tls:
issuer: lets-encrypt-prod

View File

@@ -0,0 +1,4 @@
globals:
environment: prod
timezone: Europe/Amsterdam
domain: olsen.cloud

View File

@@ -1,7 +1,3 @@
apiVersion: v2
version: 1.0.0
name: esphome
dependencies:
- name: common
version: 1.0.0
repository: file://../../common

View File

@@ -1 +1,43 @@
{{ include "common.deployment" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
spec:
strategy:
type: Recreate
replicas: 1
revisionHistoryLimit: 0
selector:
matchLabels:
app: "{{ .Release.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}"
spec:
hostNetwork: true
containers:
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
- name: http
containerPort: 6052
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
env:
- name: TZ
value: "{{ .Values.globals.timezone }}"
volumeMounts:
- mountPath: /config
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"

View File

@@ -1 +1,11 @@
{{ include "common.pvc" . }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-data'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -1 +1,15 @@
{{ include "common.service" . }}
apiVersion: v1
kind: Service
metadata:
name: "{{ .Release.Name }}"
labels:
app: "{{ .Release.Name }}"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 6052
protocol: TCP
name: http
selector:
app: "{{ .Release.Name }}"

View File

@@ -1 +1,18 @@
{{ include "common.virtualService" . }}
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: "{{ .Release.Name }}"
namespace: "{{ .Release.Namespace }}"
spec:
gateways:
- "{{ .Values.globals.istio.gateway }}"
- mesh
hosts:
- "{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- mesh
http:
- route:
- destination:
host: "{{ .Release.Name }}"
port:
number: 80

View File

@@ -1,43 +1,5 @@
image:
repository: ghcr.io/esphome/esphome
tag: 2025.12.4@sha256:a7915def0a60c76506db766b7b733760f09b47ab6a511d5052a6d38bc3f424e3
tag: 2025.12.1@sha256:3a81bf977aca174a74800e33baa11565a77c3f56b574206087555349c6f275bc
pullPolicy: IfNotPresent
subdomain: esphome
# Deployment configuration
deployment:
strategy: Recreate
replicas: 1
hostNetwork: true # ESPHome needs hostNetwork for device discovery
# Container configuration
container:
port: 6052
healthProbe:
type: tcpSocket
port: http # Use named port
# Service configuration
service:
port: 80
type: ClusterIP
# Volume configuration
volumes:
- name: data
mountPath: /config
persistentVolumeClaim: data
# Persistent volume claims
persistentVolumeClaims:
- name: data
size: 10Gi
storageClassName: persistent
# VirtualService configuration
virtualService:
enabled: true
gateways:
public: false
private: true

View File

@@ -2,6 +2,7 @@ apiVersion: v2
version: 1.0.0
name: forgejo
dependencies:
- name: common
version: 1.0.0
repository: file://../../common
- name: woodpecker
version: 1.6.2
repository: https://woodpecker-ci.org/

View File

@@ -0,0 +1,10 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
redirectUris:
- path: /user/oauth2/Authentik/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict

View File

@@ -1,2 +1,6 @@
{{ include "common.database" . }}
apiVersion: homelab.mortenolsen.pro/v1
kind: PostgresDatabase
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'

View File

@@ -1,76 +1,106 @@
{{- if .Values.actions.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "common.fullname" . }}
labels:
{{- include "common.labels" . | nindent 4 }}
name: "{{ .Release.Name }}"
spec:
strategy:
type: {{ include "common.deploymentStrategy" . }}
replicas: {{ .Values.deployment.replicas | default 1 }}
{{- if .Values.deployment.revisionHistoryLimit }}
revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }}
{{- end }}
type: Recreate
replicas: 1
revisionHistoryLimit: 0
selector:
matchLabels:
{{- include "common.selectorLabels" . | nindent 6 }}
app: "{{ .Release.Name }}"
template:
metadata:
labels:
{{- include "common.selectorLabels" . | nindent 8 }}
app: "{{ .Release.Name }}"
spec:
{{- if .Values.deployment.serviceAccountName }}
serviceAccountName: {{ .Values.deployment.serviceAccountName | replace "{release}" .Release.Name | replace "{fullname}" (include "common.fullname" .) }}
{{- end }}
{{- if .Values.deployment.hostNetwork }}
hostNetwork: {{ .Values.deployment.hostNetwork }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
- name: "{{ .Release.Name }}"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
ports:
{{ include "common.containerPorts" . | indent 12 }}
{{- if .Values.container.healthProbe }}
- name: http
containerPort: 3000
protocol: TCP
- name: ssh
containerPort: 22
protocol: TCP
livenessProbe:
{{ include "common.healthProbe" . | indent 12 }}
tcpSocket:
port: http
readinessProbe:
{{ include "common.healthProbe" . | indent 12 }}
{{- end }}
lifecycle:
postStart:
exec:
command:
- /bin/sh
- -c
- |
sleep 10
su -c "forgejo forgejo-cli actions register --keep-labels --secret ${FORGEJO_RUNNER_SHARED_SECRET}" git || true
{{- if .Values.volumes }}
tcpSocket:
port: http
volumeMounts:
{{ include "common.volumeMounts" . | indent 12 }}
{{- end }}
{{- if or .Values.env .Values.globals.timezone }}
- mountPath: /data
name: data
env:
{{ include "common.env" . | indent 12 }}
- name: FORGEJO_RUNNER_SHARED_SECRET
- name: TZ
value: "{{ .Values.globals.timezone }}"
- name: USER_UID
value: "1000"
- name: USER_GID
value: "1000"
- name: FORGEJO__server__SSH_DOMAIN
value: "ssh-{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- name: FORGEJO__server__SSH_PORT
value: "2206"
- name: FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD
value: "true"
#- name: FORGEJO__service__ENABLE_BASIC_AUTHENTICATION
# value: 'true'
- name: FORGEJO__service__ENABLE_PASSWORD_SIGNIN_FORM
value: "false"
- name: FORGEJO__service__DEFAULT_KEEP_EMAIL_PRIVATE
value: "true"
- name: FORGEJO__service__DEFAULT_USER_IS_RESTRICTED
value: "true"
- name: FORGEJO__service__DEFAULT_USER_VISIBILITY
value: "private"
- name: FORGEJO__service__DEFAULT_ORG_VISIBILITY
value: "private"
- name: FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION
value: "true"
- name: FORGEJO__other__SHOW_FOOTER_POWERED_BY
value: "false"
- name: FORGEJO__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME
value: "false"
- name: FORGEJO__other__SHOW_FOOTER_VERSION
value: "false"
- name: FORGEJO__repository__ENABLE_PUSH_CREATE_USER
value: "true"
- name: FORGEJO__repository__ENABLE_PUSH_CREATE_ORG
value: "true"
- name: FORGEJO__openid__ENABLE_OPENID_SIGNIN
value: "false"
- name: FORGEJO__openid__ENABLE_OPENID_SIGNUP
value: "false"
- name: FORGEJO__database__DB_TYPE
value: postgres
- name: FORGEJO__database__NAME
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-runner-secrets"
key: shared-secret
{{- else }}
env:
- name: FORGEJO_RUNNER_SHARED_SECRET
name: "{{ .Release.Name }}-pg-connection"
key: database
- name: FORGEJO__database__HOST
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-runner-secrets"
key: shared-secret
{{- end }}
{{- if .Values.volumes }}
name: "{{ .Release.Name }}-pg-connection"
key: host
- name: FORGEJO__database__DB_PORT
value: "5432"
- name: FORGEJO__database__USER
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-pg-connection"
key: user
- name: FORGEJO__database__PASSWD
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-pg-connection"
key: password
volumes:
{{- include "common.volumes" . | nindent 8 }}
{{- end }}
{{- else }}
{{ include "common.deployment" . }}
{{- end }}
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"

View File

@@ -1 +0,0 @@
{{ include "common.dns" . }}

View File

@@ -0,0 +1,11 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: ExternalHttpService
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
subdomain: '{{ .Values.subdomain }}'
destination:
host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local'
port:
number: 80

View File

@@ -1 +0,0 @@
{{ include "common.oidc" . }}

View File

@@ -1 +1,11 @@
{{ include "common.pvc" . }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: '{{ .Release.Name }}-data'
spec:
accessModes:
- 'ReadWriteOnce'
resources:
requests:
storage: '1Gi'
storageClassName: '{{ .Values.globals.environment }}'

View File

@@ -1,25 +0,0 @@
{{- if .Values.actions.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ .Release.Name }}-runner-config"
data:
config.yml: |
log:
level: warn
format: text
runner:
file: .runner
container:
network: host
options: -v /certs/client:/certs/client -e DOCKER_HOST=tcp://localhost:2376 -e DOCKER_TLS_VERIFY=1 -e DOCKER_CERT_PATH=/certs/client
valid_volumes:
- /certs/client
envs:
DOCKER_HOST: tcp://localhost:2376
DOCKER_TLS_VERIFY: "1"
DOCKER_CERT_PATH: /certs/client
{{- end }}

View File

@@ -1,189 +0,0 @@
{{- if .Values.actions.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}-runner"
labels:
app: "{{ .Release.Name }}-runner"
spec:
replicas: {{ .Values.actions.runner.replicas | default 1 }}
revisionHistoryLimit: 2
selector:
matchLabels:
app: "{{ .Release.Name }}-runner"
template:
metadata:
labels:
app: "{{ .Release.Name }}-runner"
spec:
hostname: docker
initContainers:
- name: install-jq
image: curlimages/curl:latest
command:
- sh
- -c
- |
# Download static jq binary for Linux amd64
curl -L -o /shared/jq https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64
chmod +x /shared/jq
# Verify it works
/shared/jq --version || echo "Warning: jq download may have failed"
volumeMounts:
- name: shared-tools
mountPath: /shared
containers:
- name: docker-in-docker
image: "{{ .Values.actions.runner.dind.image.repository }}:{{ .Values.actions.runner.dind.image.tag }}"
imagePullPolicy: "{{ .Values.actions.runner.dind.image.pullPolicy }}"
env:
- name: DOCKER_TLS_CERTDIR
value: /certs
- name: DOCKER_HOST
value: docker-in-docker
securityContext:
privileged: true
ports:
- name: docker
containerPort: 2376
protocol: TCP
volumeMounts:
- name: docker-certs
mountPath: /certs
- name: "{{ .Release.Name }}-runner"
image: "{{ .Values.actions.runner.image.repository }}:{{ .Values.actions.runner.image.tag }}"
imagePullPolicy: "{{ .Values.actions.runner.image.pullPolicy }}"
command:
- /bin/sh
- -c
- |
cd /data
# Use jq from shared volume (installed by initContainer)
export PATH="/shared:${PATH}"
export LD_LIBRARY_PATH="/shared/lib:${LD_LIBRARY_PATH}"
if ! /shared/jq --version >/dev/null 2>&1; then
echo "Error: jq is not working (checking dependencies...)"
ldd /shared/jq 2>&1 || true
exit 1
fi
echo "jq is available at /shared/jq"
# Wait for shared secret to be available
while [ -z "${FORGEJO_RUNNER_SHARED_SECRET}" ]; do
echo "Waiting for shared secret..."
sleep 1
done
# Always ensure runner file exists and is up to date
if [ ! -f .runner ]; then
echo "Creating runner file..."
forgejo-runner create-runner-file \
--connect \
--instance "https://{{ .Values.subdomain }}.{{ .Values.globals.domain }}" \
--name "{{ .Values.actions.runner.name | default "default" }}" \
--secret "${FORGEJO_RUNNER_SHARED_SECRET}" || {
echo "Failed to create runner file, will retry..."
sleep 5
exit 1
}
fi
# Always update labels to match configuration
{{- if .Values.actions.runner.labels }}
# Verify jq is available
if ! command -v jq >/dev/null 2>&1; then
echo "Error: jq is not available"
exit 1
fi
LABELS_JSON='[{{- range $index, $label := .Values.actions.runner.labels }}{{- if $index }},{{- end }}"{{ $label }}"{{- end }}]'
echo "Updating runner labels to match configuration..."
echo "New labels: ${LABELS_JSON}"
# Ensure .runner file exists and is readable
if [ ! -f .runner ]; then
echo "Error: .runner file does not exist"
exit 1
fi
# Show current labels before update
CURRENT_LABELS_BEFORE=$(jq -r '.labels // "null"' .runner 2>/dev/null || echo "error reading file")
echo "Current labels before update: ${CURRENT_LABELS_BEFORE}"
# Update labels
if jq --argjson labels "${LABELS_JSON}" '.labels = $labels' .runner > .runner.tmp; then
mv .runner.tmp .runner
echo "Labels updated successfully"
# Verify the update
CURRENT_LABELS_AFTER=$(jq -r '.labels // "null"' .runner)
echo "Current labels after update: ${CURRENT_LABELS_AFTER}"
else
echo "Error: Failed to update labels with jq"
exit 1
fi
{{- end }}
# Always copy config from ConfigMap to ensure it's up to date
echo "Copying config from ConfigMap..."
cp /config/config.yml config.yml || {
echo "Warning: Failed to copy config from ConfigMap, generating default..."
forgejo-runner generate-config > config.yml
}
# Wait for docker-in-docker TCP to be ready
echo "Waiting for docker-in-docker to be ready..."
while ! nc -z localhost 2376 2>/dev/null; do
echo "Docker daemon not ready, waiting..."
sleep 2
done
# Wait for TLS certificates to be available
while [ ! -f /certs/client/ca.pem ]; do
echo "Waiting for TLS certificates..."
sleep 1
done
echo "Docker daemon and certificates ready"
# Verify runner file exists before starting daemon
if [ ! -f .runner ] || [ ! -w .runner ]; then
echo "Error: .runner file is missing or not writable"
exit 1
fi
# Run daemon
echo "Starting runner daemon..."
while : ; do
forgejo-runner --config config.yml daemon || {
echo "Daemon exited, restarting in 5 seconds..."
sleep 5
}
done
env:
- name: FORGEJO_INSTANCE_URL
value: "https://{{ .Values.subdomain }}.{{ .Values.globals.domain }}"
- name: FORGEJO_RUNNER_NAME
value: {{ .Values.actions.runner.name | default "default" | quote }}
- name: FORGEJO_RUNNER_SHARED_SECRET
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-runner-secrets"
key: shared-secret
- name: DOCKER_HOST
value: tcp://localhost:2376
- name: DOCKER_TLS_VERIFY
value: "1"
- name: DOCKER_CERT_PATH
value: /certs/client
securityContext:
runAsUser: 1000
runAsGroup: 1000
volumeMounts:
- name: runner-data
mountPath: /data
- name: docker-certs
mountPath: /certs
- name: runner-config
mountPath: /config
readOnly: true
- name: shared-tools
mountPath: /shared
volumes:
- name: runner-data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-runner-data"
- name: docker-certs
emptyDir: {}
- name: shared-tools
emptyDir: {}
- name: runner-config
configMap:
name: "{{ .Release.Name }}-runner-config"
{{- end }}

Some files were not shown because too many files have changed in this diff Show More