Compare commits

..

36 Commits

Author SHA1 Message Date
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
Morten Olsen
a25e0b9ffb updates 2025-08-01 07:52:09 +02:00
Morten Olsen
5782d59f71 add dotenv 2025-07-31 13:23:01 +02:00
Morten Olsen
34bba171ef Migrate to zod 2025-07-31 10:42:09 +02:00
Morten Olsen
85d043aec3 more 2025-07-31 08:51:50 +02:00
Morten Olsen
523637d40f add authentik 2025-07-30 13:42:25 +02:00
Morten Olsen
dd1e5a8124 add deployments 2025-07-28 23:23:34 +02:00
306 changed files with 132229 additions and 2232 deletions

View File

@@ -1,5 +1,5 @@
name-template: "$RESOLVED_VERSION 🌈"
tag-template: "$RESOLVED_VERSION"
tag-template: "v$RESOLVED_VERSION"
categories:
- title: "🚀 Features"
labels:

View File

@@ -71,52 +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 }}
release:
permissions:
contents: read
packages: write
attestations: write
id-token: write
pages: write
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: ./operator.yaml
asset_name: operator.yaml
asset_content_type: application/yaml

View File

@@ -2,8 +2,10 @@ name: Publish tag
on:
push:
branches:
- 'main'
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v*"
env:
environment: test

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,6 +1,6 @@
FROM node:23-alpine
FROM node:23-slim
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
COPY . .
CMD ["node", "src/index.ts"]
CMD ["node", "src/index.ts"]

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

View File

@@ -1,15 +0,0 @@
# homelab-operator
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.16. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

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

0
charts/apps/values.yaml Normal file
View File

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

@@ -6,11 +6,17 @@ image:
repository: ghcr.io/morten-olsen/homelab-operator
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
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

View File

@@ -46,6 +46,6 @@ export default tseslint.config(
},
...compat.extends('plugin:prettier/recommended'),
{
ignores: ['**/node_modules/', '**/dist/', '**/.turbo/', '**/generated/'],
ignores: ['**/node_modules/', '**/dist/', '**/.turbo/', '**/generated/', '**/clients/*.types.ts'],
},
);

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

35
operator.yaml Normal file
View File

@@ -0,0 +1,35 @@
apiVersion: v1
kind: Namespace
metadata:
name: homelab
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: homelab
namespace: homelab
spec:
interval: 60m
url: https://github.com/morten-olsen/homelab-operator
ref:
branch: main
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: operator
namespace: homelab
spec:
releaseName: operator
interval: 60m
chart:
spec:
chart: charts/operator
sourceRef:
kind: GitRepository
name: homelab
namespace: homelab

View File

@@ -4,15 +4,14 @@
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest",
"nodemon": "^3.1.10",
"@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.32.0",
"@pnpm/find-workspace-packages": "6.0.9",
"@types/deep-equal": "^1.0.4",
"eslint": "9.32.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.3",
"json-schema-to-typescript": "^15.0.4",
"prettier": "3.6.2",
"typescript": "5.8.3",
"typescript-eslint": "8.38.0"
@@ -21,17 +20,37 @@
"typescript": "^5"
},
"dependencies": {
"@goauthentik/api": "2025.6.3-1751754396",
"@kubernetes/client-node": "^1.3.0",
"@sinclair/typebox": "^0.34.38",
"cloudflare": "^4.5.0",
"cron": "^4.3.3",
"debounce": "^2.2.0",
"deep-equal": "^2.2.3",
"dotenv": "^17.2.1",
"eventemitter3": "^5.0.1",
"execa": "^9.6.0",
"knex": "^3.1.0",
"p-queue": "^8.1.0",
"p-retry": "^6.2.1",
"pg": "^8.16.3",
"sqlite3": "^5.1.7"
"sqlite3": "^5.1.7",
"yaml": "^2.8.0",
"zod": "^4.0.14"
},
"imports": {
"#services/*": "./src/services/*",
"#resources/*": "./src/resources/*",
"#bootstrap/*": "./src/bootstrap/*",
"#utils/*": "./src/utils/*"
},
"packageManager": "pnpm@10.6.0",
"pnpm": {
"onlyBuiltDependencies": [
"sqlite3"
]
],
"patchedDependencies": {
"@kubernetes/client-node": "patches/@kubernetes__client-node.patch"
}
},
"scripts": {
"test": "echo 'No tests'",

View File

@@ -0,0 +1,14 @@
diff --git a/dist/gen/models/ObjectSerializer.js b/dist/gen/models/ObjectSerializer.js
index 1d798b6a2d7c059165d1df9fbb77b89a8317ebca..c8bacfdc95be0f0146c6505f89a9372e013afea4 100644
--- a/dist/gen/models/ObjectSerializer.js
+++ b/dist/gen/models/ObjectSerializer.js
@@ -2216,6 +2216,9 @@ export class ObjectSerializer {
return transformedData;
}
else if (type === "Date") {
+ if (typeof data === "string") {
+ return data;
+ }
if (format == "date") {
let month = data.getMonth() + 1;
month = month < 10 ? "0" + month.toString() : month.toString();

1638
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
apiVersion: 'homelab.mortenolsen.pro/v1';
kind: 'PostgresDatabase';
name: 'test2';
namespace: 'playground';
foo: 'bar';
foo: 'bar';
{
}

9
pyproject.toml Normal file
View File

@@ -0,0 +1,9 @@
[project]
name = "homelab-operator"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"kubediagrams>=0.5.0",
]

49
scripts/update-manifests.ts Executable file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env node
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { compile } from 'json-schema-to-typescript';
import { K8sService } from '../src/services/k8s/k8s.ts';
import { Services } from '../src/utils/service.ts';
const services = new Services();
const k8s = services.get(K8sService);
const manifests = await k8s.extensionsApi.listCustomResourceDefinition();
const root = join(import.meta.dirname, '..', 'src', '__generated__', 'resources');
await mkdir(root, { recursive: true });
const firstUpsercase = (input: string) => {
const [first, ...rest] = input.split('');
return [first.toUpperCase(), ...rest].join('');
};
for (const manifest of manifests.items) {
for (const version of manifest.spec.versions) {
try {
const schema = version.schema?.openAPIV3Schema;
if (!schema) {
continue;
}
const cleanedSchema = JSON.parse(JSON.stringify(schema));
const kind = manifest.spec.names.kind;
const typeName = `K8S${kind}${firstUpsercase(version.name)}`;
const jsonLocation = join(root, `${typeName}.json`);
await writeFile(jsonLocation, JSON.stringify(schema, null, 2));
const file = await compile(cleanedSchema, typeName, {
declareExternallyReferenced: true,
additionalProperties: false,
$refOptions: {
continueOnError: true,
},
});
const fileLocation = join(root, `${typeName}.ts`);
await writeFile(fileLocation, file, 'utf8');
} catch (err) {
console.error(err);
console.error(`${manifest.metadata?.name} ${version.name} failed`);
}
}
}

25
skaffold.yaml Normal file
View File

@@ -0,0 +1,25 @@
apiVersion: skaffold/v4beta7
kind: Config
metadata:
name: homelab-operator
build:
cluster: {}
artifacts:
- image: homelaboperator
context: .
docker:
dockerfile: Dockerfile
manifests:
helm:
releases:
- name: homelab-operator
chartPath: charts/operator
setValueTemplates:
image.repository: '{{.IMAGE_REPO_homelaboperator}}'
image.tag: '{{.IMAGE_TAG_homelaboperator}}'
deploy:
# Use kubectl to apply the manifests.
kubectl: {}

View File

@@ -0,0 +1,31 @@
{
"description": "Addon is used to track application of a manifest file on disk. It mostly exists so that the wrangler DesiredSet\nApply controller has an object to track as the owner, and ensure that all created resources are tracked when the\nmanifest is modified or removed.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"type": "object"
},
"spec": {
"description": "Spec provides information about the on-disk manifest backing this resource.",
"properties": {
"checksum": {
"description": "Checksum is the SHA256 checksum of the most recently successfully applied manifest file.",
"type": "string"
},
"source": {
"description": "Source is the Path on disk to the manifest file that this Addon tracks.",
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}

View File

@@ -0,0 +1,43 @@
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
/**
* Addon is used to track application of a manifest file on disk. It mostly exists so that the wrangler DesiredSet
* Apply controller has an object to track as the owner, and ensure that all created resources are tracked when the
* manifest is modified or removed.
*/
export interface K8SAddonV1 {
/**
* APIVersion defines the versioned schema of this representation of an object.
* Servers should convert recognized schemas to the latest internal value, and
* may reject unrecognized values.
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
*/
apiVersion?: string;
/**
* Kind is a string value representing the REST resource this object represents.
* Servers may infer this from the endpoint the client submits requests to.
* Cannot be updated.
* In CamelCase.
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
*/
kind?: string;
metadata?: {};
/**
* Spec provides information about the on-disk manifest backing this resource.
*/
spec?: {
/**
* Checksum is the SHA256 checksum of the most recently successfully applied manifest file.
*/
checksum?: string;
/**
* Source is the Path on disk to the manifest file that this Addon tracks.
*/
source?: string;
};
}

View File

@@ -0,0 +1,376 @@
{
"description": "AppProject provides a logical grouping of applications, providing controls for:\n* where the apps may deploy to (cluster whitelist)\n* what may be deployed (repository whitelist, resource whitelist/blacklist)\n* who can access these applications (roles, OIDC group claims bindings)\n* and what they can do (RBAC policies)\n* automation access to these roles (JWT tokens)",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
"type": "string"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"type": "object"
},
"spec": {
"description": "AppProjectSpec is the specification of an AppProject",
"properties": {
"clusterResourceBlacklist": {
"description": "ClusterResourceBlacklist contains list of blacklisted cluster level resources",
"items": {
"description": "GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying\nconcepts during lookup stages without having partially valid types",
"type": "object",
"required": [
"group",
"kind"
],
"properties": {
"group": {
"type": "string"
},
"kind": {
"type": "string"
}
}
},
"type": "array"
},
"clusterResourceWhitelist": {
"description": "ClusterResourceWhitelist contains list of whitelisted cluster level resources",
"items": {
"description": "GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying\nconcepts during lookup stages without having partially valid types",
"type": "object",
"required": [
"group",
"kind"
],
"properties": {
"group": {
"type": "string"
},
"kind": {
"type": "string"
}
}
},
"type": "array"
},
"description": {
"description": "Description contains optional project description",
"type": "string"
},
"destinationServiceAccounts": {
"description": "DestinationServiceAccounts holds information about the service accounts to be impersonated for the application sync operation for each destination.",
"items": {
"description": "ApplicationDestinationServiceAccount holds information about the service account to be impersonated for the application sync operation.",
"type": "object",
"required": [
"defaultServiceAccount",
"server"
],
"properties": {
"defaultServiceAccount": {
"description": "DefaultServiceAccount to be used for impersonation during the sync operation",
"type": "string"
},
"namespace": {
"description": "Namespace specifies the target namespace for the application's resources.",
"type": "string"
},
"server": {
"description": "Server specifies the URL of the target cluster's Kubernetes control plane API.",
"type": "string"
}
}
},
"type": "array"
},
"destinations": {
"description": "Destinations contains list of destinations available for deployment",
"items": {
"description": "ApplicationDestination holds information about the application's destination",
"type": "object",
"properties": {
"name": {
"description": "Name is an alternate way of specifying the target cluster by its symbolic name. This must be set if Server is not set.",
"type": "string"
},
"namespace": {
"description": "Namespace specifies the target namespace for the application's resources.\nThe namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace",
"type": "string"
},
"server": {
"description": "Server specifies the URL of the target cluster's Kubernetes control plane API. This must be set if Name is not set.",
"type": "string"
}
}
},
"type": "array"
},
"namespaceResourceBlacklist": {
"description": "NamespaceResourceBlacklist contains list of blacklisted namespace level resources",
"items": {
"description": "GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying\nconcepts during lookup stages without having partially valid types",
"type": "object",
"required": [
"group",
"kind"
],
"properties": {
"group": {
"type": "string"
},
"kind": {
"type": "string"
}
}
},
"type": "array"
},
"namespaceResourceWhitelist": {
"description": "NamespaceResourceWhitelist contains list of whitelisted namespace level resources",
"items": {
"description": "GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying\nconcepts during lookup stages without having partially valid types",
"type": "object",
"required": [
"group",
"kind"
],
"properties": {
"group": {
"type": "string"
},
"kind": {
"type": "string"
}
}
},
"type": "array"
},
"orphanedResources": {
"description": "OrphanedResources specifies if controller should monitor orphaned resources of apps in this project",
"properties": {
"ignore": {
"description": "Ignore contains a list of resources that are to be excluded from orphaned resources monitoring",
"items": {
"description": "OrphanedResourceKey is a reference to a resource to be ignored from",
"type": "object",
"properties": {
"group": {
"type": "string"
},
"kind": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"type": "array"
},
"warn": {
"description": "Warn indicates if warning condition should be created for apps which have orphaned resources",
"type": "boolean"
}
},
"type": "object"
},
"permitOnlyProjectScopedClusters": {
"description": "PermitOnlyProjectScopedClusters determines whether destinations can only reference clusters which are project-scoped",
"type": "boolean"
},
"roles": {
"description": "Roles are user defined RBAC roles associated with this project",
"items": {
"description": "ProjectRole represents a role that has access to a project",
"type": "object",
"required": [
"name"
],
"properties": {
"description": {
"description": "Description is a description of the role",
"type": "string"
},
"groups": {
"description": "Groups are a list of OIDC group claims bound to this role",
"type": "array",
"items": {
"type": "string"
}
},
"jwtTokens": {
"description": "JWTTokens are a list of generated JWT tokens bound to this role",
"type": "array",
"items": {
"description": "JWTToken holds the issuedAt and expiresAt values of a token",
"type": "object",
"required": [
"iat"
],
"properties": {
"exp": {
"type": "integer",
"format": "int64"
},
"iat": {
"type": "integer",
"format": "int64"
},
"id": {
"type": "string"
}
}
}
},
"name": {
"description": "Name is a name for this role",
"type": "string"
},
"policies": {
"description": "Policies Stores a list of casbin formatted strings that define access policies for the role in the project",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"type": "array"
},
"signatureKeys": {
"description": "SignatureKeys contains a list of PGP key IDs that commits in Git must be signed with in order to be allowed for sync",
"items": {
"description": "SignatureKey is the specification of a key required to verify commit signatures with",
"type": "object",
"required": [
"keyID"
],
"properties": {
"keyID": {
"description": "The ID of the key in hexadecimal notation",
"type": "string"
}
}
},
"type": "array"
},
"sourceNamespaces": {
"description": "SourceNamespaces defines the namespaces application resources are allowed to be created in",
"items": {
"type": "string"
},
"type": "array"
},
"sourceRepos": {
"description": "SourceRepos contains list of repository URLs which can be used for deployment",
"items": {
"type": "string"
},
"type": "array"
},
"syncWindows": {
"description": "SyncWindows controls when syncs can be run for apps in this project",
"items": {
"description": "SyncWindow contains the kind, time, duration and attributes that are used to assign the syncWindows to apps",
"type": "object",
"properties": {
"andOperator": {
"description": "UseAndOperator use AND operator for matching applications, namespaces and clusters instead of the default OR operator",
"type": "boolean"
},
"applications": {
"description": "Applications contains a list of applications that the window will apply to",
"type": "array",
"items": {
"type": "string"
}
},
"clusters": {
"description": "Clusters contains a list of clusters that the window will apply to",
"type": "array",
"items": {
"type": "string"
}
},
"duration": {
"description": "Duration is the amount of time the sync window will be open",
"type": "string"
},
"kind": {
"description": "Kind defines if the window allows or blocks syncs",
"type": "string"
},
"manualSync": {
"description": "ManualSync enables manual syncs when they would otherwise be blocked",
"type": "boolean"
},
"namespaces": {
"description": "Namespaces contains a list of namespaces that the window will apply to",
"type": "array",
"items": {
"type": "string"
}
},
"schedule": {
"description": "Schedule is the time the window will begin, specified in cron format",
"type": "string"
},
"timeZone": {
"description": "TimeZone of the sync that will be applied to the schedule",
"type": "string"
}
}
},
"type": "array"
}
},
"type": "object"
},
"status": {
"description": "AppProjectStatus contains status information for AppProject CRs",
"properties": {
"jwtTokensByRole": {
"additionalProperties": {
"description": "JWTTokens represents a list of JWT tokens",
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"description": "JWTToken holds the issuedAt and expiresAt values of a token",
"type": "object",
"required": [
"iat"
],
"properties": {
"exp": {
"type": "integer",
"format": "int64"
},
"iat": {
"type": "integer",
"format": "int64"
},
"id": {
"type": "string"
}
}
}
}
}
},
"description": "JWTTokensByRole contains a list of JWT tokens issued for a given role",
"type": "object"
}
},
"type": "object"
}
},
"required": [
"metadata",
"spec"
],
"type": "object"
}

View File

@@ -0,0 +1,233 @@
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
/**
* AppProject provides a logical grouping of applications, providing controls for:
* * where the apps may deploy to (cluster whitelist)
* * what may be deployed (repository whitelist, resource whitelist/blacklist)
* * who can access these applications (roles, OIDC group claims bindings)
* * and what they can do (RBAC policies)
* * automation access to these roles (JWT tokens)
*/
export interface K8SAppProjectV1Alpha1 {
/**
* APIVersion defines the versioned schema of this representation of an object.
* Servers should convert recognized schemas to the latest internal value, and
* may reject unrecognized values.
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
*/
apiVersion?: string;
/**
* Kind is a string value representing the REST resource this object represents.
* Servers may infer this from the endpoint the client submits requests to.
* Cannot be updated.
* In CamelCase.
* More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
*/
kind?: string;
metadata: {};
/**
* AppProjectSpec is the specification of an AppProject
*/
spec: {
/**
* ClusterResourceBlacklist contains list of blacklisted cluster level resources
*/
clusterResourceBlacklist?: {
group: string;
kind: string;
}[];
/**
* ClusterResourceWhitelist contains list of whitelisted cluster level resources
*/
clusterResourceWhitelist?: {
group: string;
kind: string;
}[];
/**
* Description contains optional project description
*/
description?: string;
/**
* DestinationServiceAccounts holds information about the service accounts to be impersonated for the application sync operation for each destination.
*/
destinationServiceAccounts?: {
/**
* DefaultServiceAccount to be used for impersonation during the sync operation
*/
defaultServiceAccount: string;
/**
* Namespace specifies the target namespace for the application's resources.
*/
namespace?: string;
/**
* Server specifies the URL of the target cluster's Kubernetes control plane API.
*/
server: string;
}[];
/**
* Destinations contains list of destinations available for deployment
*/
destinations?: {
/**
* Name is an alternate way of specifying the target cluster by its symbolic name. This must be set if Server is not set.
*/
name?: string;
/**
* Namespace specifies the target namespace for the application's resources.
* The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace
*/
namespace?: string;
/**
* Server specifies the URL of the target cluster's Kubernetes control plane API. This must be set if Name is not set.
*/
server?: string;
}[];
/**
* NamespaceResourceBlacklist contains list of blacklisted namespace level resources
*/
namespaceResourceBlacklist?: {
group: string;
kind: string;
}[];
/**
* NamespaceResourceWhitelist contains list of whitelisted namespace level resources
*/
namespaceResourceWhitelist?: {
group: string;
kind: string;
}[];
/**
* OrphanedResources specifies if controller should monitor orphaned resources of apps in this project
*/
orphanedResources?: {
/**
* Ignore contains a list of resources that are to be excluded from orphaned resources monitoring
*/
ignore?: {
group?: string;
kind?: string;
name?: string;
}[];
/**
* Warn indicates if warning condition should be created for apps which have orphaned resources
*/
warn?: boolean;
};
/**
* PermitOnlyProjectScopedClusters determines whether destinations can only reference clusters which are project-scoped
*/
permitOnlyProjectScopedClusters?: boolean;
/**
* Roles are user defined RBAC roles associated with this project
*/
roles?: {
/**
* Description is a description of the role
*/
description?: string;
/**
* Groups are a list of OIDC group claims bound to this role
*/
groups?: string[];
/**
* JWTTokens are a list of generated JWT tokens bound to this role
*/
jwtTokens?: {
exp?: number;
iat: number;
id?: string;
}[];
/**
* Name is a name for this role
*/
name: string;
/**
* Policies Stores a list of casbin formatted strings that define access policies for the role in the project
*/
policies?: string[];
}[];
/**
* SignatureKeys contains a list of PGP key IDs that commits in Git must be signed with in order to be allowed for sync
*/
signatureKeys?: {
/**
* The ID of the key in hexadecimal notation
*/
keyID: string;
}[];
/**
* SourceNamespaces defines the namespaces application resources are allowed to be created in
*/
sourceNamespaces?: string[];
/**
* SourceRepos contains list of repository URLs which can be used for deployment
*/
sourceRepos?: string[];
/**
* SyncWindows controls when syncs can be run for apps in this project
*/
syncWindows?: {
/**
* UseAndOperator use AND operator for matching applications, namespaces and clusters instead of the default OR operator
*/
andOperator?: boolean;
/**
* Applications contains a list of applications that the window will apply to
*/
applications?: string[];
/**
* Clusters contains a list of clusters that the window will apply to
*/
clusters?: string[];
/**
* Duration is the amount of time the sync window will be open
*/
duration?: string;
/**
* Kind defines if the window allows or blocks syncs
*/
kind?: string;
/**
* ManualSync enables manual syncs when they would otherwise be blocked
*/
manualSync?: boolean;
/**
* Namespaces contains a list of namespaces that the window will apply to
*/
namespaces?: string[];
/**
* Schedule is the time the window will begin, specified in cron format
*/
schedule?: string;
/**
* TimeZone of the sync that will be applied to the schedule
*/
timeZone?: string;
}[];
};
/**
* AppProjectStatus contains status information for AppProject CRs
*/
status?: {
/**
* JWTTokensByRole contains a list of JWT tokens issued for a given role
*/
jwtTokensByRole?: {
/**
* JWTTokens represents a list of JWT tokens
*/
[k: string]: {
items?: {
exp?: number;
iat: number;
id?: string;
}[];
};
};
};
}

File diff suppressed because it is too large Load Diff

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