Compare commits

..

1 Commits

Author SHA1 Message Date
Morten Olsen
df61e4f820 more-stuff 2025-09-03 17:09:13 +02:00
272 changed files with 1020 additions and 1548 deletions

View File

@@ -27,9 +27,9 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- uses: actions/setup-node@v5
- uses: actions/setup-node@v4
with:
node-version: "${{ env.NODE_VERSION }}"
registry-url: "${{ env.NODE_REGISTRY }}"
@@ -55,12 +55,10 @@ jobs:
- name: Install dependencies
run: pnpm install
working-directory: images/operator
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Run tests
working-directory: images/operator
run: pnpm test
update-release-draft:
@@ -81,10 +79,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Upload Release Asset
id: upload-release-asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -92,4 +90,4 @@ jobs:
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: ./operator.yaml
asset_name: operator.yaml
asset_content_type: application/yaml
asset_content_type: application/yaml

View File

@@ -1,65 +0,0 @@
name: Publish tag
on:
push:
branches:
- "main"
tags:
- "v*"
env:
environment: test
release_channel: latest
DO_NOT_TRACK: "1"
NODE_VERSION: "23.x"
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}-backup
PNPM_VERSION: 10.6.0
permissions:
contents: read
packages: read
jobs:
release:
permissions:
contents: read
packages: write
attestations: write
id-token: write
pages: write
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Log in to the Container registry
uses: docker/login-action@5b7b28b1cc417bbd34cd8c225a957c9ce9adf7f2
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@032a4b3bda1b716928481836ac5bfe36e1feaad6
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@cb8fc7586f9ad9441b20c33e0f6e8b1b58d8b4c6
with:
context: ./images/backup
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

View File

@@ -3,7 +3,7 @@ name: Publish tag
on:
push:
branches:
- "main"
- 'main'
tags:
- "v*"
@@ -31,12 +31,12 @@ jobs:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Log in to the Container registry
uses: docker/login-action@5b7b28b1cc417bbd34cd8c225a957c9ce9adf7f2
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }}
@@ -44,22 +44,22 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@032a4b3bda1b716928481836ac5bfe36e1feaad6
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@cb8fc7586f9ad9441b20c33e0f6e8b1b58d8b4c6
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: ./images/operator
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
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
push-to-registry: true

View File

@@ -1,16 +0,0 @@
name: Renovate
on:
workflow_dispatch:
schedule:
- cron: "0 */6 * * *"
jobs:
renovate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Self-hosted Renovate
uses: renovatebot/github-action@v43.0.13
with:
token: ${{ secrets.GITHUB_TOKEN }}
configurationFile: ./renovate.json5

40
.gitignore vendored
View File

@@ -1,4 +1,38 @@
/secret.*.yaml
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
/data/
/.envrc
*.DS_Store
/cloudflare.yaml

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

View File

@@ -1,8 +1,6 @@
FROM node:23-slim@sha256:86191b94d2a163be41f3dc7fe5e5fcaca8ba2f1be7275d98a06343483c17414a
FROM node:23-slim
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
COPY patches ./patches
RUN pnpm install --frozen-lockfile --prod
COPY . .
CMD ["node", "src/index.ts"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

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

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.environment }}'
redirectUris:
- path: /api/auth/oidc/callback
subdomain: bytestash
matchingMode: strict

View File

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

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,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,63 @@
apiVersion: apps/v1
kind: StatefulSet
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: 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: bytestash-data
# Defines security context for the pod to avoid running as root.
# securityContext:
# runAsUser: 1000
# runAsGroup: 1000
# fsGroup: 1000
volumeClaimTemplates:
- metadata:
name: bytestash-data
spec:
accessModes: ['ReadWriteOnce']
storageClassName: '{{ .Values.environment }}'
resources:
requests:
storage: 5Gi

View File

@@ -0,0 +1,2 @@
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.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.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,6 @@
image:
repository: docker.io/jellyfin/jellyfin
tag: latest
pullPolicy: IfNotPresent
environment: prod
subdomain: jellyfin

View File

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

View File

@@ -0,0 +1,52 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
replicas: 1
selector:
matchLabels:
app: '{{ .Release.Name }}'
template:
metadata:
labels:
app: '{{ .Release.Name }}'
spec:
containers:
- name: ollama
image: ghcr.io/ollama/ollama:latest # Official image
imagePullPolicy: IfNotPresent
ports:
- containerPort: 11434
name: http
volumeMounts:
- name: ollama-data
mountPath: /root/.ollama
env:
# If you want to prestart a model, set this env var to the
# model name (e.g., "gpt-4o-mini"). The container will download
# it automatically at startup.
# - name: OLLAMA_MODEL
# value: "gpt-4o-mini"
readinessProbe:
httpGet:
scheme: HTTP
path: /api/status
port: 11434
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
volumes:
- name: ollama-data
persistentVolumeClaim:
claimName: '{{ .Release.Name }}-data'

View File

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

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: '{{ .Release.Name }}'
labels:
app: '{{ .Release.Name }}'
spec:
type: LoadBalancer # Set to NodePort/ClusterIP if you prefer
ports:
- name: http
port: 11434
targetPort: http
protocol: TCP
selector:
app: '{{ .Release.Name }}'

View File

@@ -0,0 +1,2 @@
environment: dev
subdomain: bytestash

View File

@@ -1,12 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: '{{ include "homelab-operator.fullname" . }}'
name: {{ include "homelab-operator.fullname" . }}
subjects:
- kind: ServiceAccount
name: '{{ include "homelab-operator.serviceAccountName" . }}'
namespace: "{{ .Release.Namespace }}"
- kind: ServiceAccount
name: {{ include "homelab-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: '{{ include "homelab-operator.fullname" . }}'
name: {{ include "homelab-operator.fullname" . }}
apiGroup: rbac.authorization.k8s.io

View File

@@ -2,7 +2,6 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "homelab-operator.fullname" . }}
namespace: "{{ .Release.Namespace }}"
labels:
{{- include "homelab-operator.labels" . | nindent 4 }}
spec:

View File

@@ -3,7 +3,6 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "homelab-operator.serviceAccountName" . }}
namespace: "{{ .Release.Namespace }}"
labels:
{{- include "homelab-operator.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}

View File

@@ -6,11 +6,11 @@ image:
repository: ghcr.io/morten-olsen/homelab-operator
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: main@sha256:df20d7e4f48bd886cef63ab882de9c6df76b0b297724d1cdf3a79aba8de6f896
tag: main
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
nameOverride: ''
fullnameOverride: ''
storage:
path: /data/volumes
@@ -25,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,32 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: homelab-apps
namespace: '{{ .Values.env }}-argo'
spec:
generators:
- directory:
path: charts/apps
recurse: false
include: '.*'
filter: |
.is_dir == true and (.path | endswith(".disabled") | not)
template:
metadata:
name: '{{path.basename}}'
spec:
project: default
source:
repoURL: '{{ .Values.repo }}'
targetRevision: '{{ .Values.ref }}'
path: charts/apps/{{path.basename}}
helm:
values: |
global: {{ .Values.global | 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,30 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: books
labels:
type: nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: null
nfs:
path: '{{ .Values.books.path }}'
server: '{{ .Values.host }}'
readOnly: true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: books
spec:
storageClassName: manual
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: tvshows
labels:
type: nfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: null
nfs:
path: '{{ .Values.tvshows.path }}'
server: '{{ .Values.host }}'
readOnly: true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: tvshows
spec:
storageClassName: manual
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,32 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: cloudflare
namespace: homelab
data:
token: WDhqQ1Z2WGtHUVh4XzIzb0d2WmNUcWZkWm8zZGpsMXE0dGIxU0J3Zg==
account: ZThkZDYwMDQ5MTI2NDM3MDhhNGZlMDI4YjNkNWEzMzM=
tunnelName: aG9tZWxhYg==
tunnelId: YTI1ZTI1MDEtNzNiNi00MDc1LWI3MjYtZDc1YWViZmE4ZmNk
secret: UWgvRWtGNkY2MUNxSnFwMGlCQXJ3MUxyd245ZldtcTd1RDNrZk1VUEVBVT0=
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: lets-encrypt-prod
annotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: alice@alice.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
cloudflare:
email: alice@alice.com
apiTokenSecretRef:
name: cloudflare
key: token

View File

@@ -1,476 +0,0 @@
# Home Kubernetes Cluster Setup: Monitoring & Security Quickstart
This guide provides a practical, lightweight setup for monitoring and security on your home Kubernetes cluster. It uses Helm for easy installation and focuses on essential features with minimal complexity.
## Overview
This setup includes:
* **Monitoring:** Prometheus + node-exporter + kube-state-metrics + Grafana (via the `kube-prometheus-stack` Helm chart).
* **Image Scanning & Supply-Chain:** Trivy (Trivy Operator) for automated in-cluster image vulnerability scanning.
* **Policy / Admission Control / Pod Security:** Kyverno for policy enforcement and Kubernetes Pod Security Admission (PSA) for baseline security.
* **Runtime Security / IDS:** Falco to detect suspicious syscalls and pod activity.
* **Network Segmentation:** Calico (or Cilium) CNI with basic NetworkPolicy configuration.
* **Ad-Hoc Checks:** kube-bench (CIS benchmarks), kube-linter/kube-score (static analysis), and kube-hunter (penetration testing).
## Prerequisites
* A functional Kubernetes cluster (managed or self-hosted).
* `kubectl` installed and configured to connect to your cluster.
* Helm v3 installed.
## Installation
These instructions assume you have `kubectl` and Helm set up and authenticated to your cluster.
### 1. Monitoring (Prometheus + Grafana)
* Add the Prometheus community Helm repository:
```bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
```
* Create the `monitoring` namespace and install the `kube-prometheus-stack` chart:
```bash
kubectl create ns monitoring
helm install kube-prometheus prometheus-community/kube-prometheus-stack --namespace monitoring
```
*Optional*: Customize the installation by creating a `values.yaml` file to configure persistence, resource limits, and scrape intervals. See *Configuration* below for a potential `values.yaml` you can adapt.
* Access Grafana:
```bash
kubectl -n monitoring port-forward svc/kube-prometheus-grafana 3000:80
```
Open `http://localhost:3000` in your browser. The default `admin` user password can be found in the chart's secrets (check the Helm chart documentation).
This provides node-exporter, kube-state-metrics, a Prometheus server, Alertmanager, and pre-built dashboards for your cluster.
### 2. Image Scanning (Trivy Operator)
* Add the Aqua Security Helm repository:
```bash
helm repo add aqua https://aquasecurity.github.io/helm-charts
helm repo update
```
* Create the `trivy-system` namespace and install the `trivy-operator` chart:
```bash
kubectl create ns trivy-system
helm install trivy-operator aqua/trivy-operator --namespace trivy-system
```
Trivy Operator creates `VulnerabilityReport` and `ConfigAuditReport` CRDs. It scans images running in the cluster for vulnerabilities.
### 3. Policy Admission (Kyverno)
* Create the `kyverno` namespace and install Kyverno:
```bash
kubectl create ns kyverno
kubectl apply -f https://github.com/kyverno/kyverno/releases/latest/download/install.yaml
```
* Apply the example `ClusterPolicy` to deny privileged containers and hostPath mounts:
```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: deny-privileged-and-hostpath
spec:
rules:
- name: deny-privileged
match:
resources:
kinds: ["Pod","PodTemplate","CronJob","Job","Deployment","StatefulSet"]
validate:
message: "Privileged containers are not allowed"
deny:
conditions:
- key: "{{ request.object.spec.containers[].securityContext.privileged }}"
operator: Equals
value: true
- name: deny-hostpath
match:
resources:
kinds: ["Pod","PodTemplate","Deployment","StatefulSet"]
validate:
message: "hostPath volumes are not allowed"
pattern:
spec:
volumes:
- "*":
hostPath: null
```
Save the above as `kyverno-policy.yaml` and apply it:
```bash
kubectl apply -f kyverno-policy.yaml
```
Adapt the `match` section to target specific workload types. See *Example Kyverno Policy* below.
### 4. Pod Security Admission (PSA)
* Apply the `baseline` Pod Security Standard to the `default` namespace:
```bash
kubectl label ns default pod-security.kubernetes.io/enforce=baseline
```
* For a stricter security posture, use the `restricted` profile:
```bash
kubectl label ns default pod-security.kubernetes.io/enforce=restricted
```
PSA provides controls like preventing privileged containers and restricting host networking.
### 5. Runtime Detection (Falco)
* Add the Falco Helm repository:
```bash
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
```
* Create the `falco` namespace and install the `falco` chart:
```bash
kubectl create ns falco
helm install falco falcosecurity/falco --namespace falco
```
Falco detects suspicious container behavior and system calls.
### 6. Network Policy & CNI
* If you haven't already, install a CNI that supports NetworkPolicy, such as Calico:
```bash
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
```
Alternatively, consider Cilium.
* Implement a default-deny NetworkPolicy:
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: my-namespace
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
```
Save the above as `default-deny.yaml` and apply it to your namespace:
```bash
kubectl apply -f default-deny.yaml
```
Follow this up with explicit `allow` policies for necessary services.
### 7. Cluster Hardening & Scans
* **kube-bench (CIS Benchmarks):**
```bash
kubectl run --rm -it --image aquasec/kube-bench:latest kube-bench -- /kube-bench --version 1.23
```
Refer to the kube-bench documentation for running as a Job or Pod.
* **kube-linter / kube-score (Static Manifest Checks):**
Install the CLI tool locally and analyze your Kubernetes manifests.
* **kube-hunter (Penetration Testing):**
```bash
docker run aquasec/kube-hunter:latest --remote <K8S_API_ENDPOINT>
```
## Configuration
This section provides example configuration files and tips to customize the setup for a home Kubernetes cluster.
### Example `values.yaml` for `kube-prometheus-stack`
This reduces resource usage and avoids the need for external object storage for Alertmanager, which is not needed at home. It disables default dashboards you might not need initially and cuts down some Prometheus retention.
```yaml
# values.yaml for kube-prometheus-stack
prometheus:
prometheusSpec:
# reduce resource rqts / limits
resources:
requests:
memory: 1Gi
cpu: 200m
limits:
memory: 2Gi
cpu: 500m
# Reduce storage retention
retention: 7d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: "local-path" # Or your storage class
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi # adjust as needed
alertmanager:
enabled: false # for quick home setup, send directly to telegram etc.
grafana:
enabled: true
defaultDashboardsEnabled: false # Disable default dashboards
sidecar:
dashboards:
enabled: true
provider:
folders:
fromConfigMap: true # Load custom dashboards from ConfigMaps
kube-state-metrics:
enabled: true
nodeExporter:
enabled: true
```
To use this configuration, save it as `values.yaml` and run:
```bash
helm install kube-prometheus prometheus-community/kube-prometheus-stack --namespace monitoring -f values.yaml
```
Adapt the `storageClassName` and storage amounts to your environment.
### Example Kyverno Policy - Disallow Root User / Require Distroless
This example expands on the previous policy. It requires images not run as UID 0 and suggests distroless images. It still requires privilege escalation to be forbidden:
```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-non-root-user-and-distroless
annotations:
policies.kyverno.io/title: Require Non-Root User and Distroless Images
policies.kyverno.io/category: Security
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Containers should not run as root, and ideally, be based on Distroless
images where possible. This policy requires that containers define
`runAsUser`, and that `runAsUser` is not `0`. It also generates a warning
if the image is not based on a distroless image, although does not reject
the deployment.
spec:
validationFailureAction: Enforce
rules:
- name: check-runasnonroot
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Containers must not run as root. Specify a non-zero runAsUser in securityContext."
pattern:
spec:
containers:
- securityContext:
runAsUser: "!0" # not equal to zero
- name: check-allowprivilegeescalation
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Containers must set allowPrivilegeEscalation to false."
pattern:
spec:
containers:
- securityContext:
allowPrivilegeEscalation: "false"
- name: warn-distroless
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "*" # all images
attestations:
- policy:
subjects:
- name: distroless
conditions:
all:
- key: "ghcr.io/distroless/static:latest" # Example - Check if the image is distroless. You can use wildcards
operator: In
value: "{{ image.repoDigests }}"
# You can add other keys and values to check
mutate:
overlay:
metadata:
annotations:
"image.distroless.warn": "This image isn't distroless -- see https://github.com/GoogleContainerTools/distroless"
```
### Alertmanager to Telegram
1. **Create a Telegram Bot:** Search for `@BotFather` on Telegram. Use the `/newbot` command. Give your bot a name and a unique username. BotFather will give you the bot's API token.
2. **Get your Telegram Chat ID:** Send a message to your bot. Then, in a browser, go to `https://api.telegram.org/bot<YOUR_BOT_API_TOKEN>/getUpdates` (replace `<YOUR_BOT_API_TOKEN>`). The `chat.id` value in the JSON response is your chat ID.
3. **Create a Secret in Kubernetes:**
```bash
kubectl create secret generic telegram-secrets \
--from-literal=bot_token="<YOUR_BOT_API_TOKEN>" \
--from-literal=chat_id="<YOUR_CHAT_ID>"
```
Replace the placeholders with the correct values.
4. **Add Alertmanager Configuration:**
You'll need to patch the default Alertmanager configuration provided by `kube-prometheus-stack`. Because we disabled the Alertmanager component from the chart for simplicitly's sake, we'll instead rely on defining an additional prometheusRule that sends alerts to a webhook (and have a small sidecar container forward them to telegram).
Example:
```yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
labels:
prometheus: k8s
role: alert-rules
name: promethus-to-telegram
namespace: monitoring
spec:
groups:
- name: kubernetes-home-cluster
rules:
- alert: PrometheusToTelegramAlert
annotations:
description: 'Alert sent from Prometheus goes to telegram'
expr: vector(1)
labels:
severity: critical
for: 1s
actions:
- name: SendToTelegramAction
url: 'http://localhost:8080/message'
parameters:
text: Alert from Prometheus: {{ .Alerts.Firing | len }} firing alert{{ if gt (len .Alerts.Firing) 1 }}s{{ end }}.\nSeverity: {{ .CommonLabels.severity }}\nDescription: {{ .CommonAnnotations.description }}
```
Now you will create a deployment that runs a small webhook server forwarding these alerts to telegram:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus-telegram
namespace: monitoring
spec:
selector:
matchLabels:
app: prometheus-telegram
replicas: 1
template:
metadata:
labels:
app: prometheus-telegram
spec:
containers:
- name: webhook
image: nginx
ports:
- containerPort: 8080
- name: telegram-forwarder
image: alpine/curl
command: ["/bin/sh"]
args:
- "-c"
- |
while true; do
nc -l -p 8080 | sed 's/text=/text=Alert from Prometheus: /g' | curl -sS --fail -X POST "https://api.telegram.org/bot$(TELEGRAM_BOT_TOKEN)/sendMessage" -d chat_id=$(TELEGRAM_CHAT_ID) -d "$$(cat)"
sleep 1;
done
env:
- name: TELEGRAM_BOT_TOKEN
valueFrom:
secretKeyRef:
name: telegram-secrets
key: bot_token
- name: TELEGRAM_CHAT_ID
valueFrom:
secretKeyRef:
name: telegram-secrets
key: chat_id
```
**Explanation:**
* It creates an Nginx pod for a HTTP listener to avoid unnecessary security errors in Promethues,
* The `telegram-forwarder` container uses `curl` and `nc` to forward the POST from Prometheus to the Telegram API, using the secrets for authentication.
## Operational Tips
* **Resource Management:** Set resource limits and requests for components, especially Prometheus and Grafana. Adjust scrape intervals for Prometheus to reduce load.
* **Persistence:** Use persistent volumes for Grafana and Prometheus to preserve dashboards and historical data.
* **Alerting:** Configure Alertmanager with a Telegram or Discord webhook for notifications. This is *simpler* than email for home setups.
* **Trivy & Image Blocking:** To automatically block vulnerable images, integrate Trivy with admission webhooks (using Kyverno to reject deployments based on Trivy reports).
* **Backups:** Regularly back up etcd (if self-hosting the control plane) and potentially Prometheus/Grafana data.
## Getting Started Quickly
Follow this installation order:
1. Install your `CNI`.
2. Install `kube-prometheus-stack`, using `values.yaml` to reduce resources.
3. Install Grafana and import dashboards.
4. Enable PSA on namespaces.
5. Install Kyverno and create deny policies.
6. Install Trivy Operator for image scanning visibility.
7. Install Falco for runtime detection.
8. Run `kube-bench` and `kube-linter` for initial assessment.
## Useful Resources
* [kube-prometheus-stack (Helm)](https://github.com/prometheus-community/helm-charts)
* [trivy-operator](https://github.com/aquasecurity/trivy-operator)
* [Kyverno](https://kyverno.io/)
* [Falco](https://falco.org/)
* [Calico CNI](https://www.tigera.io/project-calico/)
* [Aqua kube-hunter, kube-bench, kube-linter](https://www.aquasec.com/)
This README provides a solid foundation for setting up monitoring and security on your home Kubernetes cluster. Adapt the configurations and policies to your specific needs and experiment!

View File

@@ -1,216 +0,0 @@
Here's the guide formatted as a `README.md` file, ready for a GitHub repository or local documentation.
```markdown
# Optimizing Debian for K3s
This guide outlines steps to optimize a Debian server for running K3s (Lightweight Kubernetes). Optimization involves a combination of general Linux best practices, K3s-specific recommendations, and considerations for your specific workload.
## Table of Contents
- [1. Debian Base System Optimization](#1-debian-base-system-optimization)
- [a. Kernel Parameters (sysctl.conf)](#a-kernel-parameters-sysctlconf)
- [b. User Limits (ulimit)](#b-user-limits-ulimit)
- [c. Disable Unnecessary Services](#c-disable-unnecessary-services)
- [d. Update System](#d-update-system)
- [e. Swap Configuration](#e-swap-configuration)
- [2. K3s Specific Optimizations](#2-k3s-specific-optimizations)
- [a. Choose a Performant Storage Backend](#a-choose-a-performant-storage-backend)
- [b. Containerd Tuning](#b-containerd-tuning)
- [c. K3s Server and Agent Configuration](#c-k3s-server-and-agent-configuration)
- [d. CNI Choice](#d-cni-choice)
- [3. General Server Best Practices](#3-general-server-best-practices)
- [a. Fast Storage](#a-fast-storage)
- [b. Adequate RAM and CPU](#b-adequate-ram-and-cpu)
- [c. Network Configuration](#c-network-configuration)
- [d. Monitoring](#d-monitoring)
- [e. Logging](#e-logging)
- [4. Post-Optimization Verification](#4-post-optimization-verification)
---
## 1. Debian Base System Optimization
These steps are generally beneficial for any server, but particularly important for containerized environments like K3s.
### a. Kernel Parameters (sysctl.conf)
Edit `/etc/sysctl.conf` and apply changes with `sudo sysctl -p`.
```ini
# Increase maximum open files (for container processes, K3s components)
fs.inotify.max_user_watches = 524288 # For fs-based operations within containers
fs.inotify.max_user_instances = 8192 # For fs-based operations within containers
fs.file-max = 2097152 # Increase overall system file handle limit
# Increase limits for network connections
net.core.somaxconn = 65535 # Max backlog of pending connections
net.ipv4.tcp_tw_reuse = 1 # Allow reuse of TIME_WAIT sockets (caution: can sometimes mask issues)
net.ipv4.tcp_fin_timeout = 30 # Reduce TIME_WAIT duration
net.ipv4.tcp_max_syn_backlog = 65535 # Max number of remembered connection requests
net.ipv4.tcp_keepalive_time=600 # Shorter keepalive interval
net.ipv4.tcp_keepalive_intvl=60 # Keepalive interval
net.ipv4.tcp_keepalive_probes=3 # Keepalive probes
# Increase memory limits for network buffers (especially if high network traffic)
net.core.rmem_max = 26214400
net.core.wmem_max = 26214400
net.core.rmem_default = 26214400
net.core.wmem_default = 26214400
# Other useful parameters
vm.max_map_count = 262144 # Essential for Elasticsearch, MongoDB, etc.
vm.dirty_ratio = 5 # Reduce dirty page percentage for better write performance
vm.dirty_background_ratio = 10 # Reduce dirty page percentage for better write performance
kernel.pid_max = 4194304 # Increase max PIDs
```
**Explanation:**
- `fs.file-max`: K3s and its deployed containers can open a large number of files. Increasing this prevents "Too many open files" errors.
- `net.*`: These parameters help in handling a high number of concurrent network connections crucial for a Kubernetes cluster.
- `vm.max_map_count`: Required by some applications that run on Kubernetes (e.g., Elasticsearch).
### b. User Limits (ulimit)
Edit `/etc/security/limits.conf` (or create a file like `/etc/security/limits.d/k3s.conf`) for all users, or specifically for the user K3s runs as (often `root` by default or a dedicated `k3s` user).
```
# For all users (or a specific k3s user if you configure it)
* soft nofile 65536
* hard nofile 131072
* soft nproc 65536
* hard nproc 131072
```
**Note:** A reboot or logging out/in is often required for these changes to take effect for user sessions. Services typically pick up new limits upon restart.
**Explanation:**
- `nofile` (number of open files): Sets the per-user/per-process limit. K3s and pods need a high limit.
- `nproc` (number of processes): Each container consumes processes. A high limit prevents hitting a ceiling.
### c. Disable Unnecessary Services
Reducing background services frees up CPU, RAM, and I/O.
```bash
sudo systemctl disable --now apache2 # Example, replace with actual unused services
sudo systemctl disable --now nginx # Example
sudo systemctl disable --now cups # If not using printing
sudo systemctl disable --now modemmanager # If not using a modem
sudo systemctl disable --now bluetooth # If no bluetooth devices
# Review active services using:
# systemctl list-unit-files --type=service --state=enabled
```
### d. Update System
Keep your system packages up-to-date for security and performance bug fixes.
```bash
sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y # For major version changes (if applicable)
sudo apt autoremove -y
sudo reboot # After significant kernel or base system updates
```
### e. Swap Configuration
**It is generally recommended to disable swap on K3s nodes, especially worker nodes.** Swapping can severely degrade performance in containerized environments due to unpredictable latency.
If you absolutely must have swap (e.g., very low memory server, not recommended for production):
* Reduce swappiness: `sudo sysctl vm.swappiness=10` (or even `1`). Add `vm.swappiness = 10` to `/etc/sysctl.conf`.
* Preferably, disable swap entirely if you have sufficient RAM:
```bash
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
```
**WARNING:** Only disable swap if your system has sufficient RAM to handle its workload without it. If nodes run out of memory without swap, processes will be OOM-killed, leading to instability.
## 2. K3s Specific Optimizations
### a. Choose a Performant Storage Backend
The choice of K3s's data store significantly impacts performance and availability.
* **SQLite (Default):** Good for single-node setups or small, non-critical clusters. Performance can degrade with many changes or large clusters.
* **External Database (MariaDB/MySQL, PostgreSQL):**
* **Recommended for Production:** Offers high availability and better performance than embedded SQLite for multi-node K3s server configurations.
* **Placement:** Place the external database on a separate server or on a dedicated, fast storage volume.
* **External etcd:** Offers the best performance and scalability, but is more complex to manage and requires its own dedicated etcd cluster.
### b. Containerd Tuning
K3s uses containerd as its container runtime.
* **Fast Storage for Containerd:** Ensure the directories where containerd stores its data are on fast storage (NVMe SSDs are ideal).
* `/var/lib/rancher/k3s/agent/containerd/io.containerd.snapshotter.v1.overlayfs` (K3s specific)
* (`/var/lib/containerd` if using a standalone containerd setup)
This is critical for image pulls, container startup, and overlayfs performance.
### c. K3s Server and Agent Configuration
Configure K3s using a configuration file (e.g., `/etc/rancher/k3s/config.yaml`) or command-line flags.
* **Disable Unused Components:** Reduce resource consumption by disabling features you don't need.
* `--disable traefik`: If using Nginx Ingress Controller or another ingress.
* `--disable servicelb`: If using a cloud provider Load Balancer, MetalLB, or another solution.
* `--disable local-storage`: If using cloud provider storage, NFS, or another remote storage solution.
* `--disable metrics-server`: If using a different metrics solution or don't need it.
* `--disable helm-controller`: If exclusively using `kubectl` for deployments.
**Example `/etc/rancher/k3s/config.yaml` for a server node:**
```yaml
# /etc/rancher/k3s/config.yaml
server: true
disable:
- traefik
- servicelb
- local-storage
- metrics-server
# Example for external database
# datastore-endpoint: "mysql://k3s:password@tcp(db-server:3306)/kube?parseTime=true"
```
### d. CNI Choice
K3s defaults to Flannel (with VXLAN), which is performant for many use cases.
* **Alternative CNIs (Calico, Cilium):** If you require advanced network policies, superior performance in high-throughput scenarios, or specific networking features, consider replacing Flannel. These can offer better raw throughput or lower latency but add complexity.
* If installing K3s, you'd typically skip Flannel installation (`--flannel-backend=none`) then install your chosen CNI.
* Ensure your chosen CNI is optimized with the correct kernel modules and sysctls.
## 3. General Server Best Practices
### a. Fast Storage
* **SSD/NVMe:** Absolutely crucial for K3s performance, especially for the K3s data directory (`$K3S_DATA_DIR`, default: `/var/lib/rancher/k3s`), `/var/lib/containerd`, and the operating system itself. Pod startup times, image pulls, and database operations are heavily I/O bound.
* **RAID:** If using multiple drives, consider RAID1 or RAID10 for redundancy and increased I/O performance.
### b. Adequate RAM and CPU
* **RAM:** K3s servers (especially with embedded SQLite) require more RAM. Worker nodes also need ample RAM for their pods. Err on the side of more RAM.
* **CPU:** Ensure sufficient CPU cores for K3s components, containers, and your workloads.
### c. Network Configuration
* **Gigabit Ethernet (at least):** 10Gbps or faster is ideal for larger clusters or high-bandwidth applications.
* **MTU:** Ensure consistent MTU settings across all nodes and your network infrastructure. K3s default CNI (Flannel VXLAN) might use a smaller MTU (e.g., 1450) due to encapsulation overhead. Misconfigured MTU can lead to packet fragmentation and performance issues.
* **Jumbo Frames:** If your network supports it and all components are configured for it, jumbo frames (e.g., 9000 bytes MTU) can reduce overhead and improve throughput, but requires careful and consistent configuration.
### d. Monitoring
* **Prometheus/Grafana:** Essential for monitoring resource usage (CPU, RAM, disk I/O, network) of your nodes and K3s components. This helps identify and diagnose bottlenecks.
* **Kube-state-metrics:** Provides metrics about Kubernetes objects.
* **Node Exporter:** Provides system-level metrics.
* **cAdvisor (usually bundled with container runtimes):** Provides container-level metrics.
### e. Logging
* **Centralized Logging (ELK Stack, Loki, etc.):** Stream logs from K3s components and pods to a central logging system for easier debugging, troubleshooting, and performance analysis.
## 4. Post-Optimization Verification
1. **Reboot:** After making changes to kernel parameters or `limits.conf`, a full system reboot is often the safest way to ensure all changes are fully applied.
2. **Verify sysctl settings:** `sudo sysctl -a | grep -i <parameter_name>` (e.g., `sudo sysctl -a | grep -i fs.file-max`)
3. **Verify ulimits:** Check `ulimit -n` and `ulimit -u` in a new shell. For specific running processes, inspect `/proc/<pid>/limits`.
4. **Monitor Performance:** Use tools like `htop`, `iostat`, `netstat`, `dstat`, and your installed monitoring stack (Prometheus/Grafana) to observe the impact of your changes. Look for reduced CPU usage, lower I/O wait, improved network throughput, and stable memory usage.
5. **Test Workloads:** Deploy your actual applications and perform load testing to ensure the optimizations yield the desired performance benefits under realistic conditions.
By diligently following these steps, you can establish a robust and highly performant Debian environment for your K3s cluster. Always test changes in a staging or development environment before applying them to production systems.
```

View File

@@ -1,10 +0,0 @@
FROM alpine/git:latest@sha256:bd54f921f6d803dfa3a4fe14b7defe36df1b71349a3e416547e333aa960f86e3
# or a more specific image like a Debian slim or Ubuntu base image.
RUN apk add --no-cache restic fuse-overlayfs
WORKDIR /app
COPY backup.sh /app/backup.sh
COPY cleanup.sh /app/cleanup.sh
# Make scripts executable
RUN chmod +x /app/backup.sh /app/cleanup.sh

View File

@@ -1,35 +0,0 @@
#!/bin/sh
set -e
if [ -z "$RESTIC_PASSWORD" ]; then
echo "Error: RESTIC_PASSWORD environment variable is not set." >&2
exit 1
fi
RESTIC_REPOSITORY="/mnt/backup"
SOURCE_DIR="/mnt/source"
mkdir -p "$SOURCE_DIR"
mkdir -p "/mnt/backup"
echo "Starting Restic backup from $SOURCE_DIR to $RESTIC_REPOSITORY"
echo "Checking/Initializing Restic repository..."
restic init --repo "$RESTIC_REPOSITORY" || true
echo "Running Restic backup..."
restic backup \
-r "$RESTIC_REPOSITORY" \
"$SOURCE_DIR" \
--verbose \
--tag "daily"
if [ $? -eq 0 ]; then
echo "Restic backup completed successfully!"
else
echo "Restic backup failed!"
exit 1
fi
echo "Backup finished."

View File

@@ -1,42 +0,0 @@
#!/bin/sh
set -e
if [ -z "$RESTIC_PASSWORD" ]; then
echo "Error: RESTIC_PASSWORD environment variable is not set." >&2
exit 1
fi
RESTIC_REPOSITORY="/mnt/backup"
echo "Starting Restic cleanup for repository $RESTIC_REPOSITORY"
echo "Checking Restic repository existence..."
restic snapshots --repository "$RESTIC_REPOSITORY"
# Restic forget and prune strategy
# --keep-daily 7: Keep 7 most recent daily backups
# --keep-weekly 4: Keep 4 most recent weekly backups
# --keep-monthly 6: Keep 6 most recent monthly backups
# --keep-yearly 1: Keep 1 most recent yearly backup
# --prune: Actually delete data that's no longer referenced
# --group-by host,paths: Group snapshots for retention by host and path.
echo "Running Restic forget and prune..."
restic forget \
--group-by host,paths \
--tag "daily" \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--keep-yearly 1 \
--prune \
--verbose \
--repository "$RESTIC_REPOSITORY"
if [ $? -eq 0 ]; then
echo "Restic cleanup completed successfully!"
else
echo "Restic cleanup failed!"
exit 1
fi
echo "Cleanup finished."

View File

@@ -1,39 +0,0 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
/data/
/cloudflare.yaml
/secret.*.yaml

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

View File

@@ -1 +0,0 @@
![authentik bg](/static/dist/assets/images/flow_background.jpg)

View File

@@ -5,15 +5,15 @@
"private": true,
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.36.0",
"@eslint/js": "9.32.0",
"@types/deep-equal": "^1.0.4",
"eslint": "9.36.0",
"eslint": "9.32.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.4",
"eslint-plugin-prettier": "5.5.3",
"json-schema-to-typescript": "^15.0.4",
"prettier": "3.6.2",
"typescript": "5.9.2",
"typescript": "5.8.3",
"typescript-eslint": "8.38.0"
},
"peerDependencies": {
@@ -22,7 +22,7 @@
"dependencies": {
"@goauthentik/api": "2025.6.3-1751754396",
"@kubernetes/client-node": "^1.3.0",
"cloudflare": "^5.0.0",
"cloudflare": "^4.5.0",
"cron": "^4.3.3",
"debounce": "^2.2.0",
"deep-equal": "^2.2.3",
@@ -31,7 +31,7 @@
"execa": "^9.6.0",
"knex": "^3.1.0",
"p-queue": "^8.1.0",
"p-retry": "^7.0.0",
"p-retry": "^6.2.1",
"pg": "^8.16.3",
"sqlite3": "^5.1.7",
"yaml": "^2.8.0",
@@ -43,13 +43,13 @@
"#bootstrap/*": "./src/bootstrap/*",
"#utils/*": "./src/utils/*"
},
"packageManager": "pnpm@10.27.0",
"packageManager": "pnpm@10.6.0",
"pnpm": {
"onlyBuiltDependencies": [
"sqlite3"
],
"patchedDependencies": {
"@kubernetes/client-node": "./patches/@kubernetes__client-node.patch"
"@kubernetes/client-node": "patches/@kubernetes__client-node.patch"
}
},
"scripts": {

View File

@@ -4,11 +4,6 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
'@kubernetes/client-node':
hash: 0b0e5d32aa2930107c8c9b45df2639faf53fa12a389a551885d6e42d71f9429d
path: patches/@kubernetes__client-node.patch
importers:
.:
@@ -18,10 +13,10 @@ importers:
version: 2025.6.3-1751754396
'@kubernetes/client-node':
specifier: ^1.3.0
version: 1.3.0(patch_hash=0b0e5d32aa2930107c8c9b45df2639faf53fa12a389a551885d6e42d71f9429d)(encoding@0.1.13)
version: 1.3.0(encoding@0.1.13)
cloudflare:
specifier: ^5.0.0
version: 5.1.0(encoding@0.1.13)
specifier: ^4.5.0
version: 4.5.0(encoding@0.1.13)
cron:
specifier: ^4.3.3
version: 4.3.3
@@ -33,7 +28,7 @@ importers:
version: 2.2.3
dotenv:
specifier: ^17.2.1
version: 17.2.2
version: 17.2.1
eventemitter3:
specifier: ^5.0.1
version: 5.0.1
@@ -45,10 +40,10 @@ importers:
version: 3.1.0(pg@8.16.3)(sqlite3@5.1.7)
p-queue:
specifier: ^8.1.0
version: 8.1.1
version: 8.1.0
p-retry:
specifier: ^7.0.0
version: 7.0.0
specifier: ^6.2.1
version: 6.2.1
pg:
specifier: ^8.16.3
version: 8.16.3
@@ -57,32 +52,32 @@ importers:
version: 5.1.7
yaml:
specifier: ^2.8.0
version: 2.8.1
version: 2.8.0
zod:
specifier: ^4.0.14
version: 4.1.11
version: 4.0.14
devDependencies:
'@eslint/eslintrc':
specifier: 3.3.1
version: 3.3.1
'@eslint/js':
specifier: 9.36.0
version: 9.36.0
specifier: 9.32.0
version: 9.32.0
'@types/deep-equal':
specifier: ^1.0.4
version: 1.0.4
eslint:
specifier: 9.36.0
version: 9.36.0
specifier: 9.32.0
version: 9.32.0
eslint-config-prettier:
specifier: 10.1.8
version: 10.1.8(eslint@9.36.0)
version: 10.1.8(eslint@9.32.0)
eslint-plugin-import:
specifier: 2.32.0
version: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)
version: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint@9.32.0)
eslint-plugin-prettier:
specifier: 5.5.4
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.36.0))(eslint@9.36.0)(prettier@3.6.2)
specifier: 5.5.3
version: 5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2)
json-schema-to-typescript:
specifier: ^15.0.4
version: 15.0.4
@@ -90,11 +85,11 @@ importers:
specifier: 3.6.2
version: 3.6.2
typescript:
specifier: 5.9.2
version: 5.9.2
specifier: 5.8.3
version: 5.8.3
typescript-eslint:
specifier: 8.38.0
version: 8.38.0(eslint@9.36.0)(typescript@5.9.2)
version: 8.38.0(eslint@9.32.0)(typescript@5.8.3)
packages:
@@ -108,12 +103,6 @@ packages:
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/eslint-utils@4.9.0':
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/regexpp@4.12.1':
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -122,28 +111,28 @@ packages:
resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/config-helpers@0.3.1':
resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==}
'@eslint/config-helpers@0.3.0':
resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@0.15.2':
resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==}
'@eslint/core@0.15.1':
resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/eslintrc@3.3.1':
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/js@9.36.0':
resolution: {integrity: sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==}
'@eslint/js@9.32.0':
resolution: {integrity: sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@2.1.6':
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.3.5':
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
'@eslint/plugin-kit@0.3.4':
resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@gar/promisify@1.1.3':
@@ -258,6 +247,9 @@ packages:
'@types/node@22.16.5':
resolution: {integrity: sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==}
'@types/retry@0.12.2':
resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
'@types/stream-buffers@3.0.7':
resolution: {integrity: sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw==}
@@ -503,8 +495,8 @@ packages:
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
engines: {node: '>=6'}
cloudflare@5.1.0:
resolution: {integrity: sha512-J2vT90WHio8VSbGUmuHckJr9pabH6N+nQKQsgjfwydgV2bgyQoV/KqWw1Df+YKZ5T/QqP7KadKr4bxh+WiRafg==}
cloudflare@4.5.0:
resolution: {integrity: sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ==}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
@@ -622,8 +614,8 @@ packages:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
dotenv@17.2.2:
resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==}
dotenv@17.2.1:
resolution: {integrity: sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==}
engines: {node: '>=12'}
dunder-proto@1.0.1:
@@ -725,8 +717,8 @@ packages:
'@typescript-eslint/parser':
optional: true
eslint-plugin-prettier@5.5.4:
resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==}
eslint-plugin-prettier@5.5.3:
resolution: {integrity: sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
'@types/eslint': '>=8.0.0'
@@ -751,8 +743,8 @@ packages:
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint@9.36.0:
resolution: {integrity: sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==}
eslint@9.32.0:
resolution: {integrity: sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
peerDependencies:
@@ -1475,13 +1467,13 @@ packages:
resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
engines: {node: '>=10'}
p-queue@8.1.1:
resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==}
p-queue@8.1.0:
resolution: {integrity: sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw==}
engines: {node: '>=18'}
p-retry@7.0.0:
resolution: {integrity: sha512-3BgO9rjULJYyr0Y0pcsG7FZ+7JB/hfOODO8kx9ppumiO5jprUF92WK/Y7Q0xppZtq4VhTcPiVq7qWLQfIV5aKQ==}
engines: {node: '>=20'}
p-retry@6.2.1:
resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==}
engines: {node: '>=16.17'}
p-timeout@6.1.4:
resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==}
@@ -1660,6 +1652,10 @@ packages:
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
engines: {node: '>= 4'}
retry@0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
engines: {node: '>= 4'}
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -1924,8 +1920,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
typescript@5.9.2:
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
typescript@5.8.3:
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
hasBin: true
@@ -2015,8 +2011,8 @@ packages:
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yaml@2.8.1:
resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
yaml@2.8.0:
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
engines: {node: '>= 14.6'}
hasBin: true
@@ -2028,8 +2024,8 @@ packages:
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
engines: {node: '>=18'}
zod@4.1.11:
resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==}
zod@4.0.14:
resolution: {integrity: sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw==}
snapshots:
@@ -2039,14 +2035,9 @@ snapshots:
'@types/json-schema': 7.0.15
js-yaml: 4.1.0
'@eslint-community/eslint-utils@4.7.0(eslint@9.36.0)':
'@eslint-community/eslint-utils@4.7.0(eslint@9.32.0)':
dependencies:
eslint: 9.36.0
eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.9.0(eslint@9.36.0)':
dependencies:
eslint: 9.36.0
eslint: 9.32.0
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
@@ -2059,9 +2050,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@eslint/config-helpers@0.3.1': {}
'@eslint/config-helpers@0.3.0': {}
'@eslint/core@0.15.2':
'@eslint/core@0.15.1':
dependencies:
'@types/json-schema': 7.0.15
@@ -2079,13 +2070,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@eslint/js@9.36.0': {}
'@eslint/js@9.32.0': {}
'@eslint/object-schema@2.1.6': {}
'@eslint/plugin-kit@0.3.5':
'@eslint/plugin-kit@0.3.4':
dependencies:
'@eslint/core': 0.15.2
'@eslint/core': 0.15.1
levn: 0.4.1
'@gar/promisify@1.1.3':
@@ -2116,7 +2107,7 @@ snapshots:
dependencies:
jsep: 1.4.0
'@kubernetes/client-node@1.3.0(patch_hash=0b0e5d32aa2930107c8c9b45df2639faf53fa12a389a551885d6e42d71f9429d)(encoding@0.1.13)':
'@kubernetes/client-node@1.3.0(encoding@0.1.13)':
dependencies:
'@types/js-yaml': 4.0.9
'@types/node': 22.16.5
@@ -2203,45 +2194,47 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@types/retry@0.12.2': {}
'@types/stream-buffers@3.0.7':
dependencies:
'@types/node': 22.16.5
'@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2)':
'@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint@9.32.0)(typescript@5.8.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.38.0(eslint@9.36.0)(typescript@5.9.2)
'@typescript-eslint/parser': 8.38.0(eslint@9.32.0)(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.38.0
'@typescript-eslint/type-utils': 8.38.0(eslint@9.36.0)(typescript@5.9.2)
'@typescript-eslint/utils': 8.38.0(eslint@9.36.0)(typescript@5.9.2)
'@typescript-eslint/type-utils': 8.38.0(eslint@9.32.0)(typescript@5.8.3)
'@typescript-eslint/utils': 8.38.0(eslint@9.32.0)(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.38.0
eslint: 9.36.0
eslint: 9.32.0
graphemer: 1.4.0
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.1.0(typescript@5.9.2)
typescript: 5.9.2
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.38.0(eslint@9.36.0)(typescript@5.9.2)':
'@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.38.0
'@typescript-eslint/types': 8.38.0
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2)
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.38.0
debug: 4.4.1
eslint: 9.36.0
typescript: 5.9.2
eslint: 9.32.0
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.38.0(typescript@5.9.2)':
'@typescript-eslint/project-service@8.38.0(typescript@5.8.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.9.2)
'@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3)
'@typescript-eslint/types': 8.38.0
debug: 4.4.1
typescript: 5.9.2
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@@ -2250,28 +2243,28 @@ snapshots:
'@typescript-eslint/types': 8.38.0
'@typescript-eslint/visitor-keys': 8.38.0
'@typescript-eslint/tsconfig-utils@8.38.0(typescript@5.9.2)':
'@typescript-eslint/tsconfig-utils@8.38.0(typescript@5.8.3)':
dependencies:
typescript: 5.9.2
typescript: 5.8.3
'@typescript-eslint/type-utils@8.38.0(eslint@9.36.0)(typescript@5.9.2)':
'@typescript-eslint/type-utils@8.38.0(eslint@9.32.0)(typescript@5.8.3)':
dependencies:
'@typescript-eslint/types': 8.38.0
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2)
'@typescript-eslint/utils': 8.38.0(eslint@9.36.0)(typescript@5.9.2)
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
'@typescript-eslint/utils': 8.38.0(eslint@9.32.0)(typescript@5.8.3)
debug: 4.4.1
eslint: 9.36.0
ts-api-utils: 2.1.0(typescript@5.9.2)
typescript: 5.9.2
eslint: 9.32.0
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.38.0': {}
'@typescript-eslint/typescript-estree@8.38.0(typescript@5.9.2)':
'@typescript-eslint/typescript-estree@8.38.0(typescript@5.8.3)':
dependencies:
'@typescript-eslint/project-service': 8.38.0(typescript@5.9.2)
'@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.9.2)
'@typescript-eslint/project-service': 8.38.0(typescript@5.8.3)
'@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3)
'@typescript-eslint/types': 8.38.0
'@typescript-eslint/visitor-keys': 8.38.0
debug: 4.4.1
@@ -2279,19 +2272,19 @@ snapshots:
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.7.2
ts-api-utils: 2.1.0(typescript@5.9.2)
typescript: 5.9.2
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.38.0(eslint@9.36.0)(typescript@5.9.2)':
'@typescript-eslint/utils@8.38.0(eslint@9.32.0)(typescript@5.8.3)':
dependencies:
'@eslint-community/eslint-utils': 4.7.0(eslint@9.36.0)
'@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0)
'@typescript-eslint/scope-manager': 8.38.0
'@typescript-eslint/types': 8.38.0
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2)
eslint: 9.36.0
typescript: 5.9.2
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
eslint: 9.32.0
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@@ -2529,7 +2522,7 @@ snapshots:
clean-stack@2.2.0:
optional: true
cloudflare@5.1.0(encoding@0.1.13):
cloudflare@4.5.0(encoding@0.1.13):
dependencies:
'@types/node': 18.19.123
'@types/node-fetch': 2.6.12
@@ -2658,7 +2651,7 @@ snapshots:
dependencies:
esutils: 2.0.3
dotenv@17.2.2: {}
dotenv@17.2.1: {}
dunder-proto@1.0.1:
dependencies:
@@ -2782,9 +2775,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-config-prettier@10.1.8(eslint@9.36.0):
eslint-config-prettier@10.1.8(eslint@9.32.0):
dependencies:
eslint: 9.36.0
eslint: 9.32.0
eslint-import-resolver-node@0.3.9:
dependencies:
@@ -2794,17 +2787,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.36.0)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0):
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.38.0(eslint@9.36.0)(typescript@5.9.2)
eslint: 9.36.0
'@typescript-eslint/parser': 8.38.0(eslint@9.32.0)(typescript@5.8.3)
eslint: 9.32.0
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0):
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint@9.32.0):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -2813,9 +2806,9 @@ snapshots:
array.prototype.flatmap: 1.3.3
debug: 3.2.7
doctrine: 2.1.0
eslint: 9.36.0
eslint: 9.32.0
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.36.0)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.36.0)
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -2827,20 +2820,20 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
'@typescript-eslint/parser': 8.38.0(eslint@9.36.0)(typescript@5.9.2)
'@typescript-eslint/parser': 8.38.0(eslint@9.32.0)(typescript@5.8.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.36.0))(eslint@9.36.0)(prettier@3.6.2):
eslint-plugin-prettier@5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2):
dependencies:
eslint: 9.36.0
eslint: 9.32.0
prettier: 3.6.2
prettier-linter-helpers: 1.0.0
synckit: 0.11.11
optionalDependencies:
eslint-config-prettier: 10.1.8(eslint@9.36.0)
eslint-config-prettier: 10.1.8(eslint@9.32.0)
eslint-scope@8.4.0:
dependencies:
@@ -2851,16 +2844,16 @@ snapshots:
eslint-visitor-keys@4.2.1: {}
eslint@9.36.0:
eslint@9.32.0:
dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0)
'@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0)
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.21.0
'@eslint/config-helpers': 0.3.1
'@eslint/core': 0.15.2
'@eslint/config-helpers': 0.3.0
'@eslint/core': 0.15.1
'@eslint/eslintrc': 3.3.1
'@eslint/js': 9.36.0
'@eslint/plugin-kit': 0.3.5
'@eslint/js': 9.32.0
'@eslint/plugin-kit': 0.3.4
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3
@@ -3653,14 +3646,16 @@ snapshots:
aggregate-error: 3.1.0
optional: true
p-queue@8.1.1:
p-queue@8.1.0:
dependencies:
eventemitter3: 5.0.1
p-timeout: 6.1.4
p-retry@7.0.0:
p-retry@6.2.1:
dependencies:
'@types/retry': 0.12.2
is-network-error: 1.1.0
retry: 0.13.1
p-timeout@6.1.4: {}
@@ -3829,6 +3824,8 @@ snapshots:
retry@0.12.0:
optional: true
retry@0.13.1: {}
reusify@1.1.0: {}
rfc4648@1.5.4: {}
@@ -4117,9 +4114,9 @@ snapshots:
tr46@0.0.3: {}
ts-api-utils@2.1.0(typescript@5.9.2):
ts-api-utils@2.1.0(typescript@5.8.3):
dependencies:
typescript: 5.9.2
typescript: 5.8.3
tsconfig-paths@3.15.0:
dependencies:
@@ -4169,18 +4166,18 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
typescript-eslint@8.38.0(eslint@9.36.0)(typescript@5.9.2):
typescript-eslint@8.38.0(eslint@9.32.0)(typescript@5.8.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2)
'@typescript-eslint/parser': 8.38.0(eslint@9.36.0)(typescript@5.9.2)
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2)
'@typescript-eslint/utils': 8.38.0(eslint@9.36.0)(typescript@5.9.2)
eslint: 9.36.0
typescript: 5.9.2
'@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0)(typescript@5.8.3))(eslint@9.32.0)(typescript@5.8.3)
'@typescript-eslint/parser': 8.38.0(eslint@9.32.0)(typescript@5.8.3)
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
'@typescript-eslint/utils': 8.38.0(eslint@9.32.0)(typescript@5.8.3)
eslint: 9.32.0
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
typescript@5.9.2: {}
typescript@5.8.3: {}
unbox-primitive@1.1.0:
dependencies:
@@ -4280,10 +4277,10 @@ snapshots:
yallist@4.0.0: {}
yaml@2.8.1: {}
yaml@2.8.0: {}
yocto-queue@0.1.0: {}
yoctocolors@2.1.1: {}
zod@4.1.11: {}
zod@4.0.14: {}

View File

@@ -1,10 +1,9 @@
[project]
name = "trivy-report"
name = "homelab-operator"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"jinja2>=3.1.6",
"weasyprint>=66.0",
"kubediagrams>=0.5.0",
]

View File

@@ -1,35 +0,0 @@
{
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: [
'config:recommended',
],
packageRules: [
{
groupName: 'Docker images',
groupSlug: 'dockerimages',
matchDatasources: [
'docker',
],
pinDigests: true,
},
],
'helm-values': {
managerFilePatterns: [
'/^charts/.*/values\\.yaml$/',
],
},
customManagers: [
{
customType: 'regex',
managerFilePatterns: [
'/^charts/.*/values\\.yaml$/',
],
matchStrings: [
"repository:s*'(?<depName>.*?)'\ns*tag:s*'(?<currentValue>.*?)'",
'repository:s*"(?<depName>.*?)"\ns*tag:s*"(?<currentValue>.*?)"',
'repository:s*(?<depName>.*?)\ns*tag:s*(?<currentValue>.*)',
],
datasourceTemplate: 'docker',
},
],
}

View File

@@ -1,3 +0,0 @@
/security_report.pdf
/transformed_data.json
/all_data.json

View File

@@ -1,24 +0,0 @@
#!/bin/bash
# Data Extraction Function
extract_data() {
crd_type="$1"
kubectl get "$crd_type" -A -o json | jq -r '.items[] | {
namespace: .metadata.namespace,
name: .metadata.name,
report: .report
}'
}
# Vulnerability Reports
vulnerability_data=$(extract_data vulnerabilityreports)
# Example of capturing ConfigAuditReports (adjust jq filter as needed)
config_audit_data=$(extract_data configauditreports)
# Combine the data into a proper JSON array using jq
{
echo "$vulnerability_data"
echo "$config_audit_data"
} | jq -s '.' > all_data.json

View File

@@ -1,82 +0,0 @@
import json
from jinja2 import Environment, FileSystemLoader
import weasyprint
def generate_pdf_report(transformed_data, template_file, output_file):
"""Generates a PDF report from the transformed data and Jinja2 template."""
env = Environment(
loader=FileSystemLoader(".")
) # Load templates from the current directory
template = env.get_template(template_file)
html_output = template.render(transformed_data) # Render the template with the data
# Generate PDF using WeasyPrint
weasyprint.HTML(string=html_output).write_pdf(output_file)
# Load the already transformed JSON data
with open("transformed_data.json", "r") as f:
raw_data = json.load(f)
# Sort by severity (CRITICAL, HIGH, MEDIUM, LOW)
severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
# Group vulnerabilities by CVE ID
vuln_groups = {}
for vuln in raw_data["vulnerabilities"]:
cve_id = vuln["vulnerabilityID"]
if cve_id not in vuln_groups:
vuln_groups[cve_id] = {
"vulnerabilityID": vuln["vulnerabilityID"],
"severity": vuln["severity"],
"title": vuln["title"],
"packagePURL": vuln.get("packagePURL"),
"installedVersion": vuln.get("installedVersion"),
"fixedVersion": vuln.get("fixedVersion"),
"affected_resources": [],
}
vuln_groups[cve_id]["affected_resources"].append(
{"namespace": vuln["namespace"], "resource": vuln["resource"]}
)
# Convert to list and sort by severity
grouped_vulnerabilities = sorted(
list(vuln_groups.values()), key=lambda x: severity_order.get(x["severity"], 4)
)
# Group config issues by checkID
config_groups = {}
for issue in raw_data["config_issues"]:
check_id = issue["checkID"]
if check_id not in config_groups:
config_groups[check_id] = {
"checkID": issue["checkID"],
"severity": issue["severity"],
"title": issue["title"],
"description": issue["description"],
"remediation": issue["remediation"],
"affected_resources": [],
}
config_groups[check_id]["affected_resources"].append(
{"namespace": issue["namespace"], "resource": issue["resource"]}
)
# Convert to list and sort by severity
grouped_config_issues = sorted(
list(config_groups.values()), key=lambda x: severity_order.get(x["severity"], 4)
)
transformed_data = {
"vulnerabilities": grouped_vulnerabilities,
"config_issues": grouped_config_issues,
}
# Generate the PDF report
generate_pdf_report(transformed_data, "report_template.html", "security_report.pdf")
print("PDF report generated successfully: security_report.pdf")

View File

@@ -1,6 +0,0 @@
def main():
print("Hello from trivy-report!")
if __name__ == "__main__":
main()

View File

@@ -1,136 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Kubernetes Security Report</title>
<style>
@page {
size: A4;
margin: 0.5in;
}
body {
font-family: Arial, sans-serif;
font-size: 10px;
line-height: 1.3;
margin: 0;
padding: 0;
}
h1 {
color: #333;
font-size: 16px;
margin-bottom: 10px;
}
h2 {
color: #666;
font-size: 14px;
margin: 15px 0 8px 0;
}
table {
border-collapse: collapse;
width: 100%;
margin-bottom: 20px;
font-size: 8px;
}
th, td {
border: 1px solid #ccc;
padding: 4px;
text-align: left;
vertical-align: top;
word-wrap: break-word;
max-width: 150px;
}
th {
background-color: #f5f5f5;
font-weight: bold;
font-size: 9px;
}
.severity-critical { background-color: #ffebee; color: #c62828; font-weight: bold; }
.severity-high { background-color: #fff3e0; color: #ef6c00; font-weight: bold; }
.severity-medium { background-color: #fff8e1; color: #f57f17; }
.severity-low { background-color: #f3e5f5; color: #7b1fa2; }
.resource-col { max-width: 120px; }
.vuln-id-col { max-width: 80px; }
.severity-col { max-width: 60px; text-align: center; }
.namespace-col { max-width: 80px; }
.description-col { max-width: 200px; font-size: 7px; }
</style>
</head>
<body>
<h1>Kubernetes Security Report</h1>
<h2>Vulnerabilities ({{ vulnerabilities|length }})</h2>
<table>
<thead>
<tr>
<th class="vuln-id-col">CVE ID</th>
<th class="severity-col">Severity</th>
<th style="max-width: 180px;">Title</th>
<th style="max-width: 100px;">Package</th>
<th style="max-width: 60px;">Count</th>
<th style="max-width: 250px;">Affected Resources</th>
</tr>
</thead>
<tbody>
{% for vuln in vulnerabilities %}
<tr>
<td class="vuln-id-col">{{ vuln.vulnerabilityID }}</td>
<td class="severity-col severity-{{ vuln.severity|lower }}">{{ vuln.severity }}</td>
<td style="max-width: 180px;">{{ vuln.title }}</td>
<td style="max-width: 100px;">{{ vuln.packagePURL or 'N/A' }}</td>
<td style="max-width: 60px; text-align: center;">{{ vuln.affected_resources|length }}</td>
<td style="max-width: 250px; font-size: 7px;">
{% for resource in vuln.affected_resources[:10] %}
{{ resource.namespace }}/{{ resource.resource }}{% if not loop.last %}, {% endif %}
{% endfor %}
{% if vuln.affected_resources|length > 10 %}
<br/><em>... and {{ vuln.affected_resources|length - 10 }} more</em>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2>Configuration Issues ({{ config_issues|length }})</h2>
<table>
<thead>
<tr>
<th class="vuln-id-col">Check ID</th>
<th class="severity-col">Severity</th>
<th style="max-width: 180px;">Title</th>
<th style="max-width: 200px;">Remediation</th>
<th style="max-width: 60px;">Count</th>
<th style="max-width: 200px;">Affected Resources</th>
</tr>
</thead>
<tbody>
{% for issue in config_issues %}
<tr>
<td class="vuln-id-col">{{ issue.checkID }}</td>
<td class="severity-col severity-{{ issue.severity|lower }}">{{ issue.severity }}</td>
<td style="max-width: 180px;">{{ issue.title }}</td>
<td style="max-width: 200px; font-size: 7px;">{{ issue.remediation }}</td>
<td style="max-width: 60px; text-align: center;">{{ issue.affected_resources|length }}</td>
<td style="max-width: 200px; font-size: 7px;">
{% for resource in issue.affected_resources[:8] %}
{{ resource.namespace }}/{{ resource.resource }}{% if not loop.last %}, {% endif %}
{% endfor %}
{% if issue.affected_resources|length > 8 %}
<br/><em>... and {{ issue.affected_resources|length - 8 }} more</em>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>

View File

@@ -1,56 +0,0 @@
import json
def transform_data(json_data):
"""Transforms the raw JSON data into a structured format for reporting."""
reports = json.loads(json_data)
all_vulnerabilities = []
all_config_audit_issues = []
for report in reports:
if "vulnerabilities" in report["report"]:
for vuln in report["report"]["vulnerabilities"]:
all_vulnerabilities.append(
{
"namespace": report["namespace"],
"resource": report["name"],
"vulnerabilityID": vuln["vulnerabilityID"],
"severity": vuln["severity"],
"title": vuln["title"],
"packagePURL": vuln.get("packagePURL"),
"installedVersion": vuln.get("installedVersion"),
"fixedVersion": vuln.get("fixedVersion"),
}
)
elif "checks" in report["report"]: # ConfigAuditReports have "checks"
for check in report["report"]["checks"]:
all_config_audit_issues.append(
{
"namespace": report["namespace"],
"resource": report["name"],
"checkID": check["checkID"],
"severity": check["severity"],
"title": check["title"],
"description": check["description"],
"remediation": check["remediation"],
"success": check["success"],
}
)
return {
"vulnerabilities": all_vulnerabilities,
"config_issues": all_config_audit_issues,
}
# Load the JSON data
with open("all_data.json", "r") as f:
raw_data = f.read()
transformed_data = transform_data(raw_data)
# Print the transformed data (for verification)
print(json.dumps(transformed_data, indent=2)) # print for check
# Save it for the next step:
with open("transformed_data.json", "w") as f:
json.dump(transformed_data, f, indent=2)

View File

@@ -4,9 +4,10 @@ metadata:
name: homelab-operator
build:
cluster: {}
artifacts:
- image: zot.olsen.cloud/homelaboperator
context: ./images/operator
- image: homelaboperator
context: .
docker:
dockerfile: Dockerfile
@@ -15,10 +16,9 @@ manifests:
releases:
- name: homelab-operator
chartPath: charts/operator
namespace: homelab
setValueTemplates:
image.repository: "zot.local/homelaboperator"
image.tag: "{{.IMAGE_TAG_zot_olsen_cloud_homelaboperator}}"
image.repository: '{{.IMAGE_REPO_homelaboperator}}'
image.tag: '{{.IMAGE_TAG_homelaboperator}}'
deploy:
# Use kubectl to apply the manifests.

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