93 Commits

Author SHA1 Message Date
Morten Olsen
ca80b72cb1 Update image tag to version 0.0.30 2026-02-07 09:03:40 +01:00
Morten Olsen
556f883bab update glados secrets 2026-02-05 23:52:00 +01:00
Morten Olsen
b7ee31a736 add glados 2026-02-02 16:35:00 +01:00
renovate[bot]
5f2fd756a7 Pin dependencies (#59)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-21 22:05:44 +01:00
Morten Olsen
46753fba60 add tandoor 2026-01-19 05:43:39 +01:00
Morten Olsen
e604e03831 secret templatte documentation 2026-01-19 04:57:59 +01:00
Morten Olsen
8675c34822 disable tubearchivist 2026-01-18 20:20:54 +01:00
Morten Olsen
05f8271e0d disable homarr 2026-01-18 20:18:51 +01:00
Morten Olsen
8be66d92a0 support all in one template 2026-01-18 20:16:40 +01:00
Morten Olsen
0b7d221180 add komga 2026-01-18 13:14:15 +01:00
Morten Olsen
7bf747402d correctly disabled paperless 2026-01-10 18:08:49 +01:00
Morten Olsen
8ff2e8268c enable wildcard on coder 2026-01-10 10:32:29 +01:00
Morten Olsen
68fc164c57 disable paperless 2026-01-10 10:26:13 +01:00
Morten Olsen
b553a9e6ce add intel gpu to ollama 2026-01-08 15:20:13 +01:00
Morten Olsen
d79d3bfaba add valkey to paperless 2026-01-08 12:30:52 +01:00
Morten Olsen
56232a1569 remove secret rotation 2026-01-08 12:26:13 +01:00
Morten Olsen
3e13c355f5 add paperless-ngx 2026-01-08 12:11:37 +01:00
Morten Olsen
284b899ab0 disable auto rotation of secrets 2026-01-06 09:09:40 +01:00
renovate[bot]
1c347e7f14 Pin dependencies (#58)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 16:37:23 +01:00
Morten Olsen
3c3234898d recreate image pvc 2026-01-05 16:22:54 +01:00
Morten Olsen
fc93c01795 add tubearchivist 2026-01-05 15:33:47 +01:00
Morten Olsen
aeb99ce6d5 add kids jellyfin 2026-01-05 13:27:01 +01:00
Morten Olsen
63c863b7e1 use persistent storage class 2026-01-05 13:24:19 +01:00
Morten Olsen
8bfb79bf83 increate jellyfin space 2026-01-05 08:55:42 +01:00
Morten Olsen
f42a167653 add scanopy 2026-01-05 00:00:53 +01:00
Morten Olsen
a46edfee5d zot-fix 2026-01-03 23:40:51 +01:00
Morten Olsen
d091f8feca fix mollysocket issue 2026-01-03 23:31:14 +01:00
Morten Olsen
c7824eb179 use public DNS on uptime kuma 2026-01-03 23:24:03 +01:00
Morten Olsen
9ee9abeadf add cups 2026-01-03 13:30:29 +01:00
Morten Olsen
434d9ed7a2 migrate immich 2026-01-03 12:52:21 +01:00
Morten Olsen
71feab50b1 add database to immich 2026-01-03 12:50:00 +01:00
Morten Olsen
c1c0ae8707 add db to baikal 2026-01-03 08:45:29 +01:00
Morten Olsen
d5a9bec508 remove legacy environment 2026-01-02 14:52:58 +01:00
Morten Olsen
1eaba01644 fix for immich db password 2026-01-02 14:50:16 +01:00
Morten Olsen
c50ce95086 start immich 2026-01-02 14:40:48 +01:00
Morten Olsen
be85172acf stop immich 2026-01-02 14:37:50 +01:00
Morten Olsen
f7a21fe704 initial immich migration 2026-01-02 14:32:30 +01:00
Morten Olsen
4aff162d1a migate bytestash 2026-01-02 13:59:58 +01:00
Morten Olsen
0205a58d2e migrate zot 2026-01-02 13:56:19 +01:00
Morten Olsen
c35196c657 migrate gitea 2026-01-02 13:52:34 +01:00
Morten Olsen
1a3c04df57 migrate reservoir 2026-01-02 13:37:22 +01:00
Morten Olsen
f203b6e4cd migrate umami 2026-01-02 13:35:09 +01:00
Morten Olsen
e15e33e16a migrate vikunja 2026-01-02 13:31:34 +01:00
Morten Olsen
c0c2f580cd migrate metabase 2026-01-02 13:28:11 +01:00
Morten Olsen
bec0a6face migate mealie 2026-01-02 13:24:45 +01:00
Morten Olsen
d150b96082 migrate homebox 2026-01-02 13:19:49 +01:00
Morten Olsen
eb443b9ae5 cleanup 2026-01-02 12:36:56 +01:00
Morten Olsen
8a08bbf793 migate homeassistant 2026-01-02 11:48:09 +01:00
Morten Olsen
1b626def24 migate n8n 2026-01-02 11:43:56 +01:00
Morten Olsen
ee3c103dc4 migate openwebui 2026-01-02 11:39:05 +01:00
Morten Olsen
b7256afac8 migrate homarr 2026-01-02 11:26:11 +01:00
Morten Olsen
b64ce72f3c migrate miniflux 2026-01-02 10:56:51 +01:00
Morten Olsen
4f88506ba7 migrate readeck 2026-01-02 10:36:07 +01:00
Morten Olsen
04f7abf186 remove unused apps 2026-01-02 10:19:50 +01:00
Morten Olsen
a00629694c fix: readd version history limit 2026-01-02 09:31:23 +01:00
Morten Olsen
faaad9dac4 migrate forgejo 2026-01-02 09:22:22 +01:00
Morten Olsen
8f3f96a685 simplified runner script 2026-01-02 00:20:53 +01:00
Morten Olsen
c50095a0b6 add forgejo runner 2026-01-01 22:43:35 +01:00
Morten Olsen
fb70ca519c cleanup 2026-01-01 21:32:20 +01:00
Morten Olsen
432607dfe8 migrate jellyfin 2026-01-01 21:25:52 +01:00
Morten Olsen
6bf0048f5b add new auth to coder 2026-01-01 20:50:57 +01:00
Morten Olsen
4e0b6ed008 moved common outside application set 2026-01-01 20:39:28 +01:00
Morten Olsen
d5a0803eee add database 2026-01-01 20:34:24 +01:00
Morten Olsen
9ac5b61a38 migrate gitea, coder and esphome 2026-01-01 18:01:41 +01:00
Morten Olsen
44f5a93d46 migate clibre-web 2026-01-01 17:56:09 +01:00
Morten Olsen
4ffa29f111 migrate bytestash 2026-01-01 17:54:49 +01:00
Morten Olsen
d7cc5e8d8b migrate blinko 2026-01-01 17:51:23 +01:00
Morten Olsen
bdf7900036 migrate baikal to new helper chart 2026-01-01 17:49:14 +01:00
Morten Olsen
f03dbea746 transform forgejo 2026-01-01 13:28:58 +01:00
Morten Olsen
f4ff0a20da add dns record 2026-01-01 13:18:52 +01:00
Morten Olsen
a96ec5d185 add new auth audiobookshelf 2026-01-01 12:32:49 +01:00
renovate[bot]
a9f963fc63 chore(deps): update docker images (#57)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 12:05:54 +01:00
renovate[bot]
06fc2a097f chore(deps): update docker images (#56)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 12:03:38 +01:00
Morten Olsen
e089935603 pin valkey 2026-01-01 12:02:17 +01:00
renovate[bot]
8a7338693e chore(deps): update docker images (#55)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 11:47:33 +01:00
Morten Olsen
19f81dcfc5 remove external proxy 2026-01-01 10:02:55 +01:00
Morten Olsen
5006edc768 add test OIDC client 2025-12-29 21:58:44 +01:00
Morten Olsen
76270442b4 add test OIDC client 2025-12-29 13:04:29 +01:00
Morten Olsen
e22ba25b7e feat: support public/private gateway 2025-12-29 11:06:01 +01:00
Morten Olsen
42d0fe6050 restrict immich cpu usage 2025-12-29 02:21:00 +01:00
Morten Olsen
5a77a91dcc reenable vaultwarden 2025-12-28 20:09:23 +01:00
Morten Olsen
d35827bcd6 feat: add immich 2025-12-28 20:05:46 +01:00
renovate[bot]
f80b838d41 chore(deps): update docker images (#54)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-26 10:08:32 +01:00
Morten Olsen
1e74c4dbff dexposed vaultwarden from the internet 2025-12-25 21:17:16 +01:00
Morten Olsen
e301dc1ab1 add vaultwarden 2025-12-25 00:38:25 +01:00
renovate[bot]
5c66e1c6a3 chore(deps): update docker images (#51)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-25 00:21:55 +01:00
Morten Olsen
7cb233b845 enable authentik operator 2025-12-24 00:22:55 +01:00
Morten Olsen
d6107d13a0 add authentik server 2025-12-22 23:34:59 +01:00
Morten Olsen
d41163578a add authentik operator 2025-12-22 23:24:51 +01:00
Morten Olsen
c7d9310118 add homelab operator 2025-12-22 21:06:43 +01:00
Morten Olsen
89b7ec1e21 add pictures to syncthing 2025-12-21 23:07:37 +01:00
Morten Olsen
646bb1c694 add syncthing 2025-12-21 22:33:29 +01:00
Morten Olsen
c4fe4ffefb add woodpecker 2025-12-21 21:10:20 +01:00
469 changed files with 7937 additions and 5868 deletions

View File

@@ -0,0 +1,16 @@
{
"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 Normal file
View File

@@ -0,0 +1,11 @@
# 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,6 +58,7 @@ mkdir -p apps/charts/my-app/templates
```
#### Chart.yaml
```yaml
apiVersion: v2
version: 1.0.0
@@ -65,6 +66,7 @@ name: my-app
```
#### values.yaml
```yaml
image:
repository: docker.io/org/my-app
@@ -73,381 +75,5 @@ image:
subdomain: my-app
```
### 2. Core Templates
See ./apps/common/README.md for guide on writing charts
#### 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 Normal file
View File

@@ -0,0 +1,100 @@
# 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,10 +0,0 @@
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,11 +0,0 @@
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,31 +0,0 @@
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

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

View File

@@ -1,15 +0,0 @@
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,4 +0,0 @@
subdomain: appsmith
image:
repository: index.docker.io/appsmith/appsmith-ce
tag: latest@sha256:0776a0a9665919800d22fc736956ec54fedd16a9a30f9d4ad3f3fc0fd8ac8694

View File

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

View File

@@ -1,13 +0,0 @@
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,52 +1 @@
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
{{ include "common.deployment" . }}

View File

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

View File

@@ -1,11 +0,0 @@
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 @@
{{ include "common.oidc" . }}

View File

@@ -1,24 +1 @@
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 }}'
{{ include "common.pvc" . }}

View File

@@ -1,15 +1 @@
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 }}'
{{ include "common.service" . }}

View File

@@ -1,18 +1 @@
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
{{ include "common.virtualService" . }}

View File

@@ -1,5 +1,68 @@
image:
repository: ghcr.io/advplyr/audiobookshelf
tag: 2.31.0@sha256:e23adb24848d99d19cd1e251aee4e1e12ed4f5effc8ccb21754b062b6a06cf66
tag: 2.32.1@sha256:a52dc5db694a5bf041ce38f285dd6c6a660a4b1b21e37ad6b6746433263b2ae5
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 +0,0 @@
apiVersion: v2
version: 1.0.0
name: backup

View File

@@ -1,48 +0,0 @@
{{- $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

@@ -1,43 +0,0 @@
{{- $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

@@ -1,19 +0,0 @@
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,3 +1,7 @@
apiVersion: v2
version: 1.0.0
name: esphome
name: baikal
dependencies:
- name: common
version: 1.0.0
repository: file://../../common

View File

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

View File

@@ -1,46 +1 @@
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"
{{ include "common.deployment" . }}

View File

@@ -1,11 +0,0 @@
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,24 +1 @@
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 }}"
{{ include "common.pvc" . }}

View File

@@ -1,15 +1 @@
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 }}"
{{ include "common.service" . }}

View File

@@ -1,18 +0,0 @@
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,18 +1 @@
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
{{ include "common.virtualService" . }}

View File

@@ -2,4 +2,49 @@ 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

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

View File

@@ -1,10 +0,0 @@
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

@@ -1,57 +0,0 @@
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

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

View File

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

View File

@@ -1,15 +0,0 @@
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

@@ -1,18 +0,0 @@
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,5 +0,0 @@
image:
repository: blinkospace/blinko
tag: latest@sha256:04ad2a67f617e122db98425d39c2d0d901492729b3aee5a7e8c4d351009ee9e9
pullPolicy: IfNotPresent
subdomain: blinko

View File

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

View File

@@ -1,10 +0,0 @@
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,54 +1 @@
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"
{{ include "common.deployment" . }}

View File

@@ -1,11 +0,0 @@
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 @@
{{ include "common.oidc" . }}

View File

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

View File

@@ -1,15 +1 @@
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 }}'
{{ include "common.service" . }}

View File

@@ -1,18 +1 @@
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
{{ include "common.virtualService" . }}

View File

@@ -1,4 +1,74 @@
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,3 +1,7 @@
apiVersion: v2
version: 1.0.0
name: calibre-web
dependencies:
- name: common
version: 1.0.0
repository: file://../../common

View File

@@ -1,10 +0,0 @@
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,44 +1 @@
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
{{ include "common.deployment" . }}

View File

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

View File

@@ -1,15 +1 @@
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 }}"
{{ include "common.service" . }}

View File

@@ -1,18 +1 @@
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
{{ include "common.virtualService" . }}

View File

@@ -2,4 +2,49 @@ 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,3 +1,7 @@
apiVersion: v2
version: 1.0.0
name: openwebui
name: coder
dependencies:
- name: common
version: 1.0.0
repository: file://../../common

View File

@@ -1,10 +0,0 @@
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,74 +1 @@
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"
{{ include "common.deployment" . }}

View File

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

View File

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

View File

@@ -1,15 +1 @@
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 }}'
{{ include "common.service" . }}

View File

@@ -1,18 +1 @@
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
{{ include "common.virtualService" . }}

View File

@@ -2,4 +2,79 @@ 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

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

View File

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

View File

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

View File

@@ -1,80 +0,0 @@
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

@@ -1,11 +0,0 @@
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

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

View File

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

View File

@@ -1,20 +0,0 @@
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

@@ -1,18 +0,0 @@
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,27 +0,0 @@
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

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

View File

@@ -1,9 +0,0 @@
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

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

View File

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

View File

@@ -1,43 +1 @@
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"
{{ include "common.deployment" . }}

View File

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

View File

@@ -1,15 +1 @@
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 }}"
{{ include "common.service" . }}

View File

@@ -1,18 +1 @@
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
{{ include "common.virtualService" . }}

View File

@@ -1,5 +1,43 @@
image:
repository: ghcr.io/esphome/esphome
tag: 2025.12.1@sha256:3a81bf977aca174a74800e33baa11565a77c3f56b574206087555349c6f275bc
tag: 2025.12.4@sha256:a7915def0a60c76506db766b7b733760f09b47ab6a511d5052a6d38bc3f424e3
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,7 +2,6 @@ apiVersion: v2
version: 1.0.0
name: forgejo
dependencies:
- name: woodpecker
version: 1.6.2
repository: https://woodpecker-ci.org/
- name: common
version: 1.0.0
repository: file://../../common

View File

@@ -1,10 +0,0 @@
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,6 +1,2 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: PostgresDatabase
metadata:
name: '{{ .Release.Name }}'
spec:
environment: '{{ .Values.globals.environment }}'
{{ include "common.database" . }}

View File

@@ -1,106 +1,76 @@
{{- if .Values.actions.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}"
name: {{ include "common.fullname" . }}
labels:
{{- include "common.labels" . | nindent 4 }}
spec:
strategy:
type: Recreate
replicas: 1
revisionHistoryLimit: 0
type: {{ include "common.deploymentStrategy" . }}
replicas: {{ .Values.deployment.replicas | default 1 }}
{{- if .Values.deployment.revisionHistoryLimit }}
revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }}
{{- end }}
selector:
matchLabels:
app: "{{ .Release.Name }}"
{{- include "common.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app: "{{ .Release.Name }}"
{{- include "common.selectorLabels" . | nindent 8 }}
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: "{{ .Release.Name }}"
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}
ports:
- name: http
containerPort: 3000
protocol: TCP
- name: ssh
containerPort: 22
protocol: TCP
{{ include "common.containerPorts" . | indent 12 }}
{{- if .Values.container.healthProbe }}
livenessProbe:
tcpSocket:
port: http
{{ include "common.healthProbe" . | indent 12 }}
readinessProbe:
tcpSocket:
port: http
{{ 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 }}
volumeMounts:
- mountPath: /data
name: data
{{ include "common.volumeMounts" . | indent 12 }}
{{- end }}
{{- if or .Values.env .Values.globals.timezone }}
env:
- 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
{{ include "common.env" . | indent 12 }}
- name: FORGEJO_RUNNER_SHARED_SECRET
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-pg-connection"
key: database
- name: FORGEJO__database__HOST
name: "{{ .Release.Name }}-runner-secrets"
key: shared-secret
{{- else }}
env:
- name: FORGEJO_RUNNER_SHARED_SECRET
valueFrom:
secretKeyRef:
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
name: "{{ .Release.Name }}-runner-secrets"
key: shared-secret
{{- end }}
{{- if .Values.volumes }}
volumes:
- name: data
persistentVolumeClaim:
claimName: "{{ .Release.Name }}-data"
{{- include "common.volumes" . | nindent 8 }}
{{- end }}
{{- else }}
{{ include "common.deployment" . }}
{{- end }}

View File

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

View File

@@ -1,11 +0,0 @@
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 @@
{{ include "common.oidc" . }}

View File

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

View File

@@ -0,0 +1,25 @@
{{- 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

@@ -0,0 +1,189 @@
{{- 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 }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.actions.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "{{ .Release.Name }}-runner-data"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.actions.runner.storage.size | default "10Gi" }}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{- if .Values.actions.enabled }}
{{ include "common.externalSecrets.externalSecrets" . }}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{- if .Values.actions.enabled }}
{{ include "common.externalSecrets.passwordGenerators" . }}
{{- end }}

View File

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

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