Compare commits

..

31 Commits

Author SHA1 Message Date
Morten Olsen
21262705a7 fixes 2025-09-03 23:06:59 +02:00
Morten Olsen
4d46998668 more-charts 2025-09-03 21:41:58 +02:00
Morten Olsen
00d90bfa21 more-stuff 2025-09-03 17:24:27 +02:00
Morten Olsen
03e406322f more stuff 2025-09-03 15:16:50 +02:00
Morten Olsen
5ee7a76443 more stuff 2025-09-03 14:33:48 +02:00
mortenolsenzn
683de402ff Merge pull request #1 from morten-olsen/rewrite2
Rewrite2
2025-09-03 12:24:40 +02:00
Morten Olsen
e8e939ad19 fixes 2025-08-22 11:44:53 +02:00
Morten Olsen
1b5b5145b0 stuff 2025-08-22 07:35:50 +02:00
Morten Olsen
cfd2d76873 more 2025-08-20 22:45:30 +02:00
Morten Olsen
9e5081ed9b updates 2025-08-20 14:58:34 +02:00
Morten Olsen
3ab2b1969a stuff 2025-08-19 22:05:41 +02:00
Morten Olsen
a27b563113 rewrite2 2025-08-18 08:02:48 +02:00
Morten Olsen
295472a028 update 2025-08-15 22:01:18 +02:00
Morten Olsen
91298b3cf7 update 2025-08-15 21:20:23 +02:00
Morten Olsen
638c288a5c update 2025-08-15 20:52:17 +02:00
Morten Olsen
2be6bdca84 update 2025-08-15 20:45:28 +02:00
Morten Olsen
f362f4afc4 fix: missing permissions 2025-08-13 09:01:30 +02:00
Morten Olsen
9fadbf75fb publish operator yaml 2025-08-13 08:50:17 +02:00
Morten Olsen
2add15d283 fix: authentik port 2025-08-12 23:25:03 +02:00
Morten Olsen
5426495be5 updates 2025-08-12 23:22:47 +02:00
Morten Olsen
b8bb16ccbb updates 2025-08-12 22:32:09 +02:00
Morten Olsen
d4b56007f1 add authentik connection crd 2025-08-12 08:36:29 +02:00
Morten Olsen
130bfec468 fix reconciliation of db 2025-08-11 20:00:01 +02:00
Morten Olsen
ddb3c79657 fix pg db 2025-08-11 15:00:06 +02:00
Morten Olsen
47cf43b44e Added storage provisioner 2025-08-11 12:07:36 +02:00
Morten Olsen
aa6d14738a simplify 2025-08-07 23:26:33 +02:00
Morten Olsen
9cdbaf7929 stuff 2025-08-07 22:21:33 +02:00
Morten Olsen
cfb90f7c9f more 2025-08-06 21:18:02 +02:00
Morten Olsen
757b2fcfac lot more stuff 2025-08-04 23:44:14 +02:00
Morten Olsen
daf0ea21bb update 2025-08-01 14:47:53 +02:00
Morten Olsen
26b58a59c0 lot of updates 2025-08-01 14:40:16 +02:00
329 changed files with 132411 additions and 60903 deletions

View File

@@ -71,9 +71,23 @@ jobs:
environment: release
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6
- id: create-release
uses: release-drafter/release-drafter@v6
with:
config-name: release-drafter-config.yml
publish: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: ./operator.yaml
asset_name: operator.yaml
asset_content_type: application/yaml

4
.gitignore vendored
View File

@@ -32,3 +32,7 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Finder (MacOS) folder config
.DS_Store
/data/
/cloudflare.yaml

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

View File

@@ -1,4 +1,4 @@
FROM node:23-alpine
FROM node:23-slim
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod

14
Makefile Normal file
View File

@@ -0,0 +1,14 @@
.PHONY: dev-recreate dev-destroy server-install
dev-destroy:
colima delete -f
dev-recreate: dev-destroy
colima start --network-address --kubernetes -m 8 --k3s-arg="--disable helm-controller,local-storage,traefik --docker" # --mount ${PWD}/data:/data:w
flux install --components="source-controller,helm-controller"
setup-flux:
flux install --components="source-controller,helm-controller"
server-install:
curl -sfL https://get.k3s.io | sh -s - --disable traefik,local-storage,helm-controller

282
README.md
View File

@@ -1,282 +0,0 @@
# homelab-operator
A Kubernetes operator designed for homelab environments that simplifies the
management of PostgreSQL databases and Kubernetes secrets. Built with TypeScript
and designed to run efficiently in resource-constrained environments.
## Features
- **PostgreSQL Database Management**: Automatically create and manage PostgreSQL
databases and roles
- **Secret Management**: Generate and manage Kubernetes secrets with
configurable data
- **Owner References**: Automatic cleanup when resources are deleted
- **Status Tracking**: Comprehensive status conditions and error reporting
- **Lightweight**: Minimal resource footprint suitable for homelab environments
## Architecture
The operator manages two main Custom Resource Definitions (CRDs):
### PostgresDatabase
Manages PostgreSQL databases and their associated roles:
- Creates a PostgreSQL role with a secure random password
- Creates a database owned by that role
- Generates a Kubernetes secret containing database credentials
- Ensures proper cleanup through owner references
### SecretRequest
Generates Kubernetes secrets with configurable data:
- Supports custom secret names
- Configurable data fields with various encodings
- Automatic secret lifecycle management
## Installation
### Prerequisites
- Kubernetes cluster (1.20+)
- PostgreSQL instance accessible from the cluster
- Helm 3.x (for chart-based installation)
### Using Helm Chart
1. Clone the repository:
```bash
git clone <repository-url>
cd homelab-operator
```
2. Install using Helm:
```bash
helm install homelab-operator ./chart \
--set-string env.POSTGRES_HOST=<your-postgres-host> \
--set-string env.POSTGRES_USER=<admin-user> \
--set-string env.POSTGRES_PASSWORD=<admin-password>
```
### Using kubectl
1. Build and push the Docker image:
```bash
docker build -t your-registry/homelab-operator:latest .
docker push your-registry/homelab-operator:latest
```
2. Apply the Kubernetes manifests:
```bash
kubectl apply -f chart/templates/
```
## Configuration
The operator is configured through environment variables:
| Variable | Description | Required | Default |
| ------------------- | ---------------------------------------- | -------- | ------- |
| `POSTGRES_HOST` | PostgreSQL server hostname | Yes | - |
| `POSTGRES_USER` | PostgreSQL admin username | Yes | - |
| `POSTGRES_PASSWORD` | PostgreSQL admin password | Yes | - |
| `POSTGRES_PORT` | PostgreSQL server port | No | 5432 |
| `LOG_LEVEL` | Logging level (debug, info, warn, error) | No | info |
## Usage
### PostgreSQL Database
Create a PostgreSQL database with an associated role:
```yaml
apiVersion: homelab.mortenolsen.pro/v1
kind: PostgresDatabase
metadata:
name: my-app-db
namespace: my-namespace
spec: {}
```
This will create:
- A PostgreSQL role named `my-app-db`
- A PostgreSQL database named `my-namespace_my-app-db` owned by the role
- A Kubernetes secret `postgres-database-my-app-db` containing:
- `name`: Base64-encoded database name
- `user`: Base64-encoded username
- `password`: Base64-encoded password
### Secret Request
Generate a Kubernetes secret with custom data:
```yaml
apiVersion: homelab.mortenolsen.pro/v1
kind: SecretRequest
metadata:
name: my-secret
namespace: my-namespace
spec:
secretName: app-config
data:
- key: api-key
value: "my-api-key"
encoding: base64
- key: database-url
value: "postgresql://user:pass@host:5432/db"
- key: random-token
length: 32
chars: "abcdefghijklmnopqrstuvwxyz0123456789"
```
### Accessing Created Resources
To retrieve database credentials:
```bash
# Get the secret
kubectl get secret postgres-database-my-app-db -o jsonpath='{.data.user}' | base64 -d
kubectl get secret postgres-database-my-app-db -o jsonpath='{.data.password}' | base64 -d
kubectl get secret postgres-database-my-app-db -o jsonpath='{.data.name}' | base64 -d
```
## Development
### Prerequisites
- [Bun](https://bun.sh/) runtime
- [pnpm](https://pnpm.io/) package manager
- Docker (for building images)
- Access to a Kubernetes cluster for testing
### Setup
1. Clone the repository:
```bash
git clone <repository-url>
cd homelab-operator
```
2. Install dependencies:
```bash
pnpm install
```
3. Set up development environment:
```bash
cp .env.example .env
# Edit .env with your PostgreSQL connection details
```
### Running Locally
For development, you can run the operator locally against a remote cluster:
```bash
# Ensure kubectl is configured for your development cluster
export KUBECONFIG=~/.kube/config
# Set PostgreSQL connection environment variables
export POSTGRES_HOST=localhost
export POSTGRES_USER=postgres
export POSTGRES_PASSWORD=yourpassword
# Run the operator
bun run src/index.ts
```
### Development with Docker Compose
A development environment with PostgreSQL is provided:
```bash
docker-compose -f docker-compose.dev.yaml up -d
```
### Building
Build the Docker image:
```bash
docker build -t homelab-operator:latest .
```
### Testing
```bash
# Run linting
pnpm run test:lint
# Apply test resources
kubectl apply -f test.yaml
```
## Contributing
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/new-feature`
3. Make your changes and add tests
4. Run linting: `pnpm run test:lint`
5. Commit your changes: `git commit -am 'Add new feature'`
6. Push to the branch: `git push origin feature/new-feature`
7. Submit a pull request
## Project Structure
```
├── chart/ # Helm chart for deployment
├── src/
│ ├── crds/ # Custom Resource Definitions
│ │ ├── postgres/ # PostgreSQL database management
│ │ └── secrets/ # Secret generation
│ ├── custom-resource/ # Base CRD framework
│ ├── database/ # Database migrations
│ ├── services/ # Core services
│ │ ├── config/ # Configuration management
│ │ ├── k8s.ts # Kubernetes API client
│ │ ├── log/ # Logging service
│ │ ├── postgres/ # PostgreSQL service
│ │ └── secrets/ # Secret management
│ └── utils/ # Utilities and constants
├── Dockerfile # Container build configuration
└── docker-compose.dev.yaml # Development environment
```
## License
This project is licensed under the MIT License - see the LICENSE file for
details.
## Support
For support and questions:
- Create an issue in the GitHub repository
- Check existing issues for similar problems
- Review the logs using `kubectl logs -l app=homelab-operator`
## Status Monitoring
Monitor the operator status:
```bash
# Check operator logs
kubectl logs -l app=homelab-operator -f
# Check CRD status
kubectl get postgresdatabases
kubectl get secretrequests
# Describe resources for detailed status
kubectl describe postgresdatabase my-app-db
kubectl describe secretrequest my-secret
```

View File

@@ -1,14 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "homelab-operator.fullname" . }}
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "get", "watch", "list"]
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "watch", "list", "patch"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "create", "replace"]

View File

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

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

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

@@ -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,24 @@
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

@@ -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

@@ -0,0 +1,7 @@
globals:
environment: prod
image:
repository: ghcr.io/advplyr/audiobookshelf
tag: 2.26.1
pullPolicy: IfNotPresent
subdomain: audiobookshelf

View File

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

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}-headless'
labels:
app: '{{ .Release.Name }}'
spec:
clusterIP: None
ports:
- port: 5000
name: http
selector:
app: '{{ .Release.Name }}'

View File

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

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: bytestash
matchingMode: strict

View File

@@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
serviceName: '{{ .Release.Name }}-headless'
replicas: 1
selector:
matchLabels:
app: '{{ .Release.Name }}'
template:
metadata:
labels:
app: '{{ .Release.Name }}'
spec:
containers:
- name: '{{ .Release.Name }}'
image: ghcr.io/jordan-dalby/bytestash:latest
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: configuration
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

@@ -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: 5000
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,3 @@
globals:
environment: prod
subdomain: bytestash

View File

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

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

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

View File

@@ -0,0 +1,96 @@
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: 3000
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /data
name: data
env:
- name: TZ
value: '{{ .Values.globals.timezone }}'
- name: USER_UID
value: '1000'
- name: USER_GID
value: '1000'
- name: GITEA__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD
value: 'true'
- name: GITEA__service__ENABLE_BASIC_AUTHENTICATION
value: 'true'
- name: GITEA__service__ENABLE_PASSWORD_SIGNIN_FORM
value: 'false'
- name: GITEA__service__DEFAULT_KEEP_EMAIL_PRIVATE
value: 'true'
- name: GITEA__service__DEFAULT_USER_IS_RESTRICTED
value: 'true'
- name: GITEA__service__DEFAULT_USER_VISIBILITY
value: 'private'
- name: GITEA__service__DEFAULT_ORG_VISIBILITY
value: 'private'
- name: GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION
value: 'true'
- name: GITEA__other__SHOW_FOOTER_POWERED_BY
value: 'false'
- name: GITEA__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME
value: 'false'
- name: GITEA__other__SHOW_FOOTER_VERSION
value: 'false'
- name: GITEA__repository__ENABLE_PUSH_CREATE_USER
value: 'true'
- name: GITEA__repository__ENABLE_PUSH_CREATE_ORG
value: 'true'
- name: GITEA__openid__ENABLE_OPENID_SIGNIN
value: 'false'
- name: GITEA__openid__ENABLE_OPENID_SIGNUP
value: 'false'
- name: GITEA__database__DB_TYPE
value: postgres
- name: GITEA__database__NAME
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: database
- name: GITEA__database__HOST
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: host
- name: GITEA__database__USER
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: user
- name: GITEA__database__PASSWD
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: password
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,15 @@
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 }}'

View File

@@ -0,0 +1,8 @@
globals:
environment: prod
timezone: Europe/Amsterdam
image:
repository: docker.gitea.com/gitea
tag: latest
pullPolicy: IfNotPresent
subdomain: gitea

View File

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

View File

@@ -0,0 +1 @@
https://www.authelia.com/integration/openid-connect/clients/jellyfin/

View File

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

View File

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

View File

@@ -0,0 +1,52 @@
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: 8096
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /config
name: config
- mountPath: /media/movies
name: movies
- mountPath: /media/tv-shows
name: tvshows
- mountPath: /media/music
name: music
volumes:
- name: config
persistentVolumeClaim:
claimName: '{{ .Release.Name }}-config'
- name: movies
persistentVolumeClaim:
claimName: movies
- name: tvshows
persistentVolumeClaim:
claimName: tvshows
- name: music
persistentVolumeClaim:
claimName: music

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,15 @@
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8096
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,7 @@
globals:
environment: prod
image:
repository: docker.io/jellyfin/jellyfin
tag: latest
pullPolicy: IfNotPresent
subdomain: jellyfin

View File

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

View File

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

View File

@@ -0,0 +1,79 @@
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: 12008
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /data
name: data
env:
- name: TZ
value: '{{ .Values.globals.timezone }}'
- name: APP_URL
value: https://metamcp.olsen.cloud # TODO: Change
- name: NEXT_PUBLIC_APP_URL
value: https://metamcp.olsen.cloud # TODO: Change
- name: BETTER_AUTH_SECRET
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-secrets'
key: betterauth
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: url
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: database
- name: POSTGRES_HOST
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: host
- name: POSTGRES_PORT
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: port
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: password
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: 12008
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,8 @@
globals:
environment: prod
timezone: Europe/Amsterdam
image:
repository: ghcr.io/metatool-ai/metamcp
tag: latest
pullPolicy: IfNotPresent
subdomain: metamcp

View File

@@ -0,0 +1,3 @@
apiVersion: v2
version: 1.0.0
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: bytestash
matchingMode: strict

View File

@@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
serviceName: '{{ .Release.Name }}-headless'
replicas: 1
selector:
matchLabels:
app: '{{ .Release.Name }}'
template:
metadata:
labels:
app: '{{ .Release.Name }}'
spec:
containers:
- name: '{{ .Release.Name }}'
image: ghcr.io/miniflux/miniflux:latest
ports:
- containerPort: 8080
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: configuration
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

@@ -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: 8080
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,3 @@
globals:
environment: prod
subdomain: miniflux

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
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: 5678
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /home/node/.n8n
name: data
env:
- name: TZ
value: '{{ .Values.globals.timezone }}'
- name: GENERIC_TIMEZONE
value: '{{ .Values.globals.timezone }}'
- name: N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS
value: 'true'
- name: N8N_RUNNERS_ENABLED
value: 'true'
- name: DB_TYPE
value: postgresdb
- name: DB_POSTGRESDB_DATABASE
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: database
- name: DB_POSTGRESDB_HOST
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: host
- name: DB_POSTGRESDB_PORT
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: port
- name: DB_POSTGRESDB_USER
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: user
- name: DB_POSTGRESDB_PASSWORD
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-pg-connection'
key: password
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,15 @@
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 5678
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,8 @@
globals:
environment: prod
timezone: Europe/Amsterdam
image:
repository: docker.n8n.io/n8nio/n8n
tag: latest
pullPolicy: IfNotPresent
subdomain: n8n

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
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: 11434
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /root/.ollama
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: 11434
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,7 @@
globals:
environment: prod
image:
repository: ollama/ollama
tag: 0.11.8
pullPolicy: IfNotPresent
subdomain: openwebui

View File

@@ -0,0 +1,3 @@
apiVersion: v2
version: 1.0.0
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: /oauth/oidc/callback
subdomain: '{{ .Values.subdomain }}'
matchingMode: strict

View File

@@ -0,0 +1,70 @@
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: 8080
protocol: TCP
livenessProbe:
tcpSocket:
port: http
readinessProbe:
tcpSocket:
port: http
volumeMounts:
- mountPath: /app/backend/data
name: data
env:
- name: ENABLE_SIGNUP
value: 'false'
- name: WEBUI_URL # TODO: remove
value: https://openwebui.olsen.cloud
- name: ENABLE_OAUTH_PERSISTENT_CONFIG
value: 'false'
- name: ENABLE_OAUTH_SIGNUP
value: 'true'
- name: OAUTH_MERGE_ACCOUNTS_BY_EMAIL
value: 'true'
- name: OAUTH_PROVIDER_NAME
value: authentik
- name: OPENID_PROVIDER_URL
valueFrom:
secretKeyRef:
name: '{{ .Release.Name }}-client'
key: configuration
- 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: ENABLE_LOGIN_FORM
value: 'false'
- name: OPENID_REDIRECT
value: https://openwebui.olsen.cloud/oauth/oidc/callback
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,15 @@
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,7 @@
globals:
environment: prod
image:
repository: ghcr.io/open-webui/open-webui
tag: main
pullPolicy: IfNotPresent
subdomain: openwebui

View File

@@ -0,0 +1,12 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: {{ include "homelab-operator.fullname" . }}-local-path
labels:
{{- include "homelab-operator.labels" . | nindent 4 }}
provisioner: reuse-local-path-provisioner
parameters:
# Add any provisioner-specific parameters here
reclaimPolicy: {{ .Values.storage.reclaimPolicy | default "Retain" }}
allowVolumeExpansion: {{ .Values.storage.allowVolumeExpansion | default false }}
volumeBindingMode: {{ .Values.storage.volumeBindingMode | default "WaitForFirstConsumer" }}

View File

@@ -0,0 +1,32 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "homelab-operator.fullname" . }}
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "get", "watch", "list"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete", "patch", "update"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["persistentvolumeclaims/status"]
verbs: ["update", "patch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "watch", "list", "patch", "create", "update", "replace"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "create", "update", "replace", "patch"]

View File

@@ -33,6 +33,14 @@ spec:
imagePullPolicy: {{ .Values.image.pullPolicy }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: data-volumes
mountPath: {{ .Values.storage.path }}
volumes:
- name: data-volumes
hostPath:
path: {{ .Values.storage.path }}
type: DirectoryOrCreate
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}

View File

@@ -4,13 +4,19 @@
image:
repository: ghcr.io/morten-olsen/homelab-operator
pullPolicy: Always
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: main
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
nameOverride: ''
fullnameOverride: ''
storage:
path: /data/volumes
reclaimPolicy: Retain
allowVolumeExpansion: false
volumeBindingMode: WaitForFirstConsumer
serviceAccount:
# Specifies whether a service account should be created
@@ -19,7 +25,7 @@ serviceAccount:
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
name: ''
podAnnotations: {}

3
charts/root/Chart.yaml Normal file
View File

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

View File

@@ -0,0 +1,33 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: homelab-apps
namespace: '{{ .Values.env }}-argo'
spec:
generators:
- git:
repoURL: '{{ .Values.repo }}'
revision: '{{ .Values.ref }}'
directories:
- path: charts/apps/*
include: '.*'
exclude: '.*.disabled'
template:
metadata:
name: '{{`{{path.basename}}`}}'
spec:
project: default
source:
repoURL: '{{ .Values.repo }}'
targetRevision: '{{ .Values.ref }}'
path: charts/apps/{{`{{path.basename}}`}}
helm:
values: |
globals: {{ .Values.globals | toYaml | nindent 14 }}
destination:
server: https://kubernetes.default.svc
namespace: '{{ .Values.globals.env }}'
syncPolicy:
automated:
prune: true
selfHeal: true

View File

@@ -0,0 +1,21 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: homelab-root
namespace: '{{ .Values.globals.env }}-argo'
spec:
project: default
source:
repoURL: '{{ .Values.repo }}'
targetRevision: '{{ .Values.ref }}'
path: charts/root
helm:
valueFiles:
- values.yaml
destination:
server: https://kubernetes.default.svc
namespace: '{{ .Values.globals.env }}-argo'
syncPolicy:
automated:
prune: true
selfHeal: true

4
charts/root/values.yaml Normal file
View File

@@ -0,0 +1,4 @@
globals:
env: prod
repo: https://github.com/morten-olsen/homelab-operator.git
ref: HEAD

View File

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

View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: books
labels:
type: nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual-books
nfs:
path: '{{ .Values.books.path }}'
server: '{{ .Values.host }}'
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: books
spec:
storageClassName: manual-books
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi

View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: movies
labels:
type: nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual-movies
nfs:
path: '{{ .Values.movies.path }}'
server: '{{ .Values.host }}'
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: movies
spec:
storageClassName: manual-movies
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi

View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: music
labels:
type: nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual-music
nfs:
path: '{{ .Values.music.path }}'
server: '{{ .Values.host }}'
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: music
spec:
storageClassName: manual-music
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi

View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: podcasts
labels:
type: nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual-podcasts
nfs:
path: '{{ .Values.podcasts.path }}'
server: '{{ .Values.host }}'
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: podcasts
spec:
storageClassName: manual-podcasts
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi

View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: tvshows
labels:
type: nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual-tvshows
nfs:
path: '{{ .Values.tvshows.path }}'
server: '{{ .Values.host }}'
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: tvshows
spec:
storageClassName: manual-tvshows
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi

View File

@@ -0,0 +1,11 @@
host: 192.168.20.106
movies:
path: /mnt/HDD/Movies
tvshows:
path: /mnt/HDD/TV-Shows
music:
path: /mnt/HDD/Music2
books:
path: /mnt/HDD/Books
podcasts:
path: /mnt/HDD/Podcasts

View File

@@ -1,12 +0,0 @@
name: homelab
services:
postgres:
image: postgres:17
ports:
- 5432:5432
environment:
POSTGRES_USER: $POSTGRES_USER
POSTGRES_PASSWORD: $POSTGRES_PASSWORD
POSTGRES_DB: ${POSTGRES_DB:-postgres}
volumes:
- $PWD/.data/local/postgres:/var/lib/postgresql/data

22
istio-test.yaml Normal file
View File

@@ -0,0 +1,22 @@
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: dev-authentik-override
namespace: dev
spec:
hosts:
- authentik.mortenolsen.nett
ports:
- number: 443
name: https
protocol: HTTPS
- number: 80
name: http
protocol: HTTP
location: MESH_EXTERNAL
resolution: STATIC
endpoints:
- address: 1.1.1.1
ports:
https: 443
http: 80

9
manifests/client.yaml Normal file
View File

@@ -0,0 +1,9 @@
apiVersion: homelab.mortenolsen.pro/v1
kind: OidcClient
metadata:
name: test-client
spec:
environment: dev
redirectUris:
- url: https://localhost:3000/api/v1/authentik/oauth2/callback
matchingMode: strict

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Namespace
metadata:
name: dev
---
apiVersion: homelab.mortenolsen.pro/v1
kind: Environment
metadata:
name: prod
spec:
domain: olsen.cloud
networkIp: 192.168.20.180
tls:
issuer: lets-encrypt-prod

View File

@@ -0,0 +1,39 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: example-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: homelab-operator-local-path
---
apiVersion: v1
kind: Pod
metadata:
name: example-pod
namespace: default
spec:
containers:
- name: example-container
image: alpine
command: ["/bin/sh", "-c", "sleep infinity"]
volumeMounts:
- name: example-volume
mountPath: /data
resources:
limits:
memory: 100Mi
cpu: "0.1"
requests:
memory: 50Mi
cpu: "0.05"
volumes:
- name: example-volume
persistentVolumeClaim:
claimName: example-pvc

View File

@@ -0,0 +1,14 @@
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: test-example-com
namespace: dev
spec:
hosts:
- authentik.one.dev.olsen.cloud
# (the address field is optional if you use 'resolution: DNS')
ports:
- number: 80
name: https
protocol: HTTPS
resolution: DNS

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