1 Commits

Author SHA1 Message Date
107d31730b chore(deps): update alpine docker tag to v3.23 2026-03-14 00:00:27 +00:00
79 changed files with 626 additions and 958 deletions

1
.gitignore vendored
View File

@@ -10,4 +10,3 @@ devenv.local.yaml
# pre-commit
.pre-commit-config.yaml
.opencode

View File

@@ -2,7 +2,6 @@
"recommendations": [
"jnoortheen.nix-ide",
"detachhead.basedpyright",
"mkhl.direnv",
"mermaidchart.vscode-mermaid-chart"
"mkhl.direnv"
]
}

View File

@@ -1,49 +0,0 @@
when:
- event: push
branch: fresh-start
skip_clone: true
steps:
- name: Get kubernetes access from OpenBao
image: quay.io/openbao/openbao:2.5.2
environment:
VAULT_ADDR: https://openbao.lumpiasty.xyz:8200
ROLE_ID:
from_secret: flux_reconcile_role_id
SECRET_ID:
from_secret: flux_reconcile_secret_id
commands:
- bao write -field token auth/approle/login
role_id=$ROLE_ID
secret_id=$SECRET_ID > /woodpecker/.vault_id
- export VAULT_TOKEN=$(cat /woodpecker/.vault_id)
- bao write -format json -f /kubernetes/creds/flux-reconcile > /woodpecker/kube_credentials
- name: Construct Kubeconfig
image: alpine/k8s:1.35.3
environment:
KUBECONFIG: /woodpecker/kubeconfig
commands:
- kubectl config set-cluster cluster
--server=https://$KUBERNETES_SERVICE_HOST
--certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- kubectl config set-credentials cluster
--token=$(jq -r .data.service_account_token /woodpecker/kube_credentials)
- kubectl config set-context cluster
--cluster cluster
--user cluster
--namespace flux-system
- kubectl config use-context cluster
- name: Reconcile git source
image: ghcr.io/fluxcd/flux-cli:v2.8.3
environment:
KUBECONFIG: /woodpecker/kubeconfig
commands:
- flux reconcile source git flux-system
- name: Invalidate OpenBao token
image: quay.io/openbao/openbao:2.5.2
environment:
VAULT_ADDR: https://openbao.lumpiasty.xyz:8200
commands:
- export VAULT_TOKEN=$(cat /woodpecker/.vault_id)
- bao write -f auth/token/revoke-self

View File

@@ -1,7 +1,3 @@
SHELL := /usr/bin/env bash
.PHONY: install-router gen-talos-config apply-talos-config get-kubeconfig
install-router:
ansible-playbook ansible/playbook.yml -i ansible/hosts

View File

@@ -2,8 +2,6 @@
This repo contains configuration and documentation for my homelab setup, which is based on Talos OS for Kubernetes cluster and MikroTik router.
<img src="https://woodpecker.lumpiasty.xyz/api/badges/2/status.svg" alt="Pipeline status">
## Architecture
Physical setup consists of MikroTik router which connects to the internet and serves as a gateway for the cluster and other devices in the home network as shown in the diagram below.
@@ -143,7 +141,7 @@ Currently the k8s cluster consists of single node (hostname anapistula-delrosala
## Software stack
The cluster itself is based on [Talos Linux](https://www.talos.dev/) (which is also a Kubernetes distribution) and uses [Cilium](https://cilium.io/) as CNI, IPAM, kube-proxy replacement, Load Balancer, and BGP control plane. Persistent volumes are managed by [OpenEBS LVM LocalPV](https://openebs.io/docs/user-guides/local-storage-user-guide/local-pv-lvm/lvm-overview). Applications are deployed using GitOps (this repo) and reconciled on cluster using [Flux](https://fluxcd.io/). Git repository is hosted on [Gitea](https://gitea.io/) running on a cluster itself. Secets are kept in [OpenBao](https://openbao.org/) (HashiCorp Vault fork) running on a cluster and synced to cluster objects using [Vault Secrets Operator](https://github.com/hashicorp/vault-secrets-operator). Deployments are kept up to date using self hosted [Renovate](https://www.mend.io/renovate/) bot updating manifests in the Git repository. There is a [Woodpecker](https://woodpecker-ci.org/) instance watching repositories on Gitea and scheduling jobs on cluster. Incoming HTTP traffic is routed to cluster using [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/) and certificates are issued by [cert-manager](https://cert-manager.io/) with [Let's Encrypt](https://letsencrypt.org/) ACME issuer with [cert-manager-webhook-ovh](https://github.com/aureq/cert-manager-webhook-ovh) resolving DNS-01 challanges. Cluster also runs [CloudNativePG](https://cloudnative-pg.io/) operator for managing PostgreSQL databases. Router is running [Mikrotik RouterOS](https://help.mikrotik.com/docs/spaces/ROS/pages/328059/RouterOS) and its configuration is managed via [Ansible](https://docs.ansible.com/) playbook in this repo. High level core cluster software architecture is shown on the diagram below.
The cluster itself is based on [Talos Linux](https://www.talos.dev/) (which is also a Kubernetes distribution) and uses [Cilium](https://cilium.io/) as CNI, IPAM, kube-proxy replacement, Load Balancer, and BGP control plane. Persistent volumes are managed by [OpenEBS LVM LocalPV](https://openebs.io/docs/user-guides/local-storage-user-guide/local-pv-lvm/lvm-overview). Applications are deployed using GitOps (this repo) and reconciled on cluster using [Flux](https://fluxcd.io/). Git repository is hosted on [Gitea](https://gitea.io/) running on a cluster itself. Secets are kept in [OpenBao](https://openbao.org/) (HashiCorp Vault fork) running on a cluster and synced to cluster objects using [Vault Secrets Operator](https://github.com/hashicorp/vault-secrets-operator). Deployments are kept up to date using self hosted [Renovate](https://www.mend.io/renovate/) bot updating manifests in the Git repository. Incoming HTTP traffic is routed to cluster using [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/) and certificates are issued by [cert-manager](https://cert-manager.io/) with [Let's Encrypt](https://letsencrypt.org/) ACME issuer with [cert-manager-webhook-ovh](https://github.com/aureq/cert-manager-webhook-ovh) resolving DNS-01 challanges. Cluster also runs [CloudNativePG](https://cloudnative-pg.io/) operator for managing PostgreSQL databases. Router is running [Mikrotik RouterOS](https://help.mikrotik.com/docs/spaces/ROS/pages/328059/RouterOS) and its configuration is managed via [Ansible](https://docs.ansible.com/) playbook in this repo. High level core cluster software architecture is shown on the diagram below.
> Talos Linux is an immutable Linux distribution purpose-built for running Kubernetes. The OS is distributed as an OCI (Docker) image and does not contain any package manager, shell, SSH, or any other tools for managing the system. Instead, all operations are performed using API, which can be accessed using `talosctl` CLI tool.
@@ -179,23 +177,14 @@ flowchart TD
vault_operator -- "Retrieves secrets" --> vault[OpenBao] -- "Secret storage" --> lv
vault -- "Auth method" --> kubeapi
gitea -- "Receives events" --> woodpecker[Woodpecker CI] -- "Schedules jobs" --> kubeapi
gitea -- "Stores repositories" --> lv
gitea --> renovate[Renovate Bot] -- "Updates manifests" --> gitea
end
```
### Reconcilation paths of each component
- Kubernetes manifests are reconciled using Flux triggerred by Woodpecker CI on push
- RouterOS configs are applied by Ansible <!-- ran by Gitea Action on push -->
- Talos configs are applied using makefile <!-- switch to ansible and trigger on action push -->
- Vault policies are applied by running `synchronize-vault.py` <!-- triggerred by Gitea action on push -->
<!-- - Docker images are built and pushed to registry by Gitea Actions on push -->
<!-- TODO: Backups, monitoring, logging, deployment with ansible etc -->
## Software
@@ -239,7 +228,6 @@ flowchart TD
|------|------|-------------|
| <img src="docs/assets/devenv.svg" alt="devenv" height="50" width="50"> | devenv | Tool for declarative managment of development environment using Nix |
| <img src="docs/assets/renovate.svg" alt="Renovate" height="50" width="50"> | Renovate | Bot for keeping dependencies up to date |
| <img src="docs/assets/woodpecker.svg" alt="Woodpecker" height="50" width="50"> | Woodpecker CI | Continous Integration system |
### AI infrastructure

View File

@@ -1,8 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- postgres-volume.yaml
- postgres-cluster.yaml
- secret.yaml
- release.yaml

View File

@@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: authentik

View File

@@ -1,23 +0,0 @@
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: authentik-postgresql-cluster-lvmhdd
namespace: authentik
spec:
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:17.4
bootstrap:
initdb:
database: authentik
owner: authentik
storage:
pvcTemplate:
storageClassName: hdd-lvmpv
resources:
requests:
storage: 10Gi
volumeName: authentik-postgresql-cluster-lvmhdd-1

View File

@@ -1,61 +0,0 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: authentik
namespace: authentik
spec:
interval: 24h
url: https://charts.goauthentik.io
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: authentik
namespace: authentik
spec:
interval: 30m
chart:
spec:
chart: authentik
version: 2026.2.1
sourceRef:
kind: HelmRepository
name: authentik
namespace: authentik
interval: 12h
values:
authentik:
postgresql:
host: authentik-postgresql-cluster-lvmhdd-rw
name: authentik
user: authentik
global:
env:
- name: AUTHENTIK_SECRET_KEY
valueFrom:
secretKeyRef:
name: authentik-secret
key: secret_key
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-postgresql-cluster-lvmhdd-app
key: password
postgresql:
enabled: false
server:
ingress:
enabled: true
ingressClassName: nginx-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt
hosts:
- authentik.lumpiasty.xyz
tls:
- secretName: authentik-ingress
hosts:
- authentik.lumpiasty.xyz

View File

@@ -1,38 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: authentik-secret
namespace: authentik
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: authentik
namespace: authentik
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: authentik
serviceAccount: authentik-secret
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: authentik-secret
namespace: authentik
spec:
type: kv-v2
mount: secret
path: authentik
destination:
create: true
name: authentik-secret
type: Opaque
transformation:
excludeRaw: true
vaultAuthRef: authentik

View File

@@ -1,48 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: crawl4ai-proxy
namespace: crawl4ai
spec:
replicas: 1
selector:
matchLabels:
app: crawl4ai-proxy
template:
metadata:
labels:
app: crawl4ai-proxy
spec:
containers:
- name: crawl4ai-proxy
image: gitea.lumpiasty.xyz/lumpiasty/crawl4ai-proxy-fit:latest
imagePullPolicy: Always
env:
- name: LISTEN_PORT
value: "8000"
- name: CRAWL4AI_ENDPOINT
value: http://crawl4ai.crawl4ai.svc.cluster.local:11235/crawl
ports:
- name: http
containerPort: 8000
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 3
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 6
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 2
failureThreshold: 6
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi

View File

@@ -1,14 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: crawl4ai-proxy
namespace: crawl4ai
spec:
type: ClusterIP
selector:
app: crawl4ai-proxy
ports:
- name: http
port: 8000
targetPort: 8000
protocol: TCP

View File

@@ -1,62 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: crawl4ai
namespace: crawl4ai
spec:
replicas: 1
selector:
matchLabels:
app: crawl4ai
template:
metadata:
labels:
app: crawl4ai
spec:
containers:
- name: crawl4ai
image: unclecode/crawl4ai:latest
imagePullPolicy: IfNotPresent
env:
- name: CRAWL4AI_API_TOKEN
valueFrom:
secretKeyRef:
name: crawl4ai-secret
key: api_token
optional: false
- name: MAX_CONCURRENT_TASKS
value: "5"
ports:
- name: http
containerPort: 11235
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 6
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 4Gi
volumeMounts:
- name: dshm
mountPath: /dev/shm
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 1Gi

19
apps/garm/configmap.yaml Normal file
View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: garm-provider-k8s-config
namespace: garm
data:
provider-config.yaml: |
kubeConfigPath: ""
runnerNamespace: "garm-runners"
podTemplate:
spec:
restartPolicy: Never
flavors:
default:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 2Gi

122
apps/garm/deployment.yaml Normal file
View File

@@ -0,0 +1,122 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: garm
namespace: garm
spec:
replicas: 1
selector:
matchLabels:
app: garm
template:
metadata:
labels:
app: garm
spec:
serviceAccountName: garm
initContainers:
- name: install-garm-provider-k8s
image: alpine:3.23
command:
- /bin/sh
- -ec
- |
wget -qO /tmp/garm-provider-k8s.tar.gz "https://github.com/mercedes-benz/garm-provider-k8s/releases/download/v0.3.2/garm-provider-k8s_Linux_x86_64.tar.gz"
tar -xzf /tmp/garm-provider-k8s.tar.gz -C /opt/garm/providers.d
chmod 0755 /opt/garm/providers.d/garm-provider-k8s
volumeMounts:
- name: provider-dir
mountPath: /opt/garm/providers.d
- name: render-garm-config
image: alpine:3.23
env:
- name: JWT_AUTH_SECRET
valueFrom:
secretKeyRef:
name: garm-config
key: jwt_auth_secret
- name: DATABASE_PASSPHRASE
valueFrom:
secretKeyRef:
name: garm-config
key: database_passphrase
command:
- /bin/sh
- -ec
- |
cat <<EOF > /etc/garm/config.toml
[default]
enable_webhook_management = true
[logging]
enable_log_streamer = true
log_format = "text"
log_level = "info"
log_source = false
[metrics]
enable = true
disable_auth = false
[jwt_auth]
secret = "${JWT_AUTH_SECRET}"
time_to_live = "8760h"
[apiserver]
bind = "0.0.0.0"
port = 9997
use_tls = false
[apiserver.webui]
enable = true
[database]
backend = "sqlite3"
passphrase = "${DATABASE_PASSPHRASE}"
[database.sqlite3]
db_file = "/data/garm.db"
busy_timeout_seconds = 5
[[provider]]
name = "kubernetes_external"
description = "Kubernetes provider"
provider_type = "external"
[provider.external]
config_file = "/etc/garm/provider-config.yaml"
provider_executable = "/opt/garm/providers.d/garm-provider-k8s"
environment_variables = ["KUBERNETES_"]
EOF
volumeMounts:
- name: config-dir
mountPath: /etc/garm
containers:
- name: garm
image: ghcr.io/cloudbase/garm:v0.1.7
imagePullPolicy: IfNotPresent
command:
- /bin/garm
- --config
- /etc/garm/config.toml
ports:
- name: http
containerPort: 9997
volumeMounts:
- name: data
mountPath: /data
- name: config-dir
mountPath: /etc/garm
- name: provider-dir
mountPath: /opt/garm/providers.d
- name: provider-config
mountPath: /etc/garm/provider-config.yaml
subPath: provider-config.yaml
volumes:
- name: data
persistentVolumeClaim:
claimName: garm-lvmhdd
- name: config-dir
emptyDir: {}
- name: provider-dir
emptyDir: {}
- name: provider-config
configMap:
name: garm-provider-k8s-config

View File

@@ -2,6 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- pvc.yaml
- configmap.yaml
- service.yaml
- rbac.yaml
- secret.yaml
- deployment.yaml
- service.yaml

9
apps/garm/namespace.yaml Normal file
View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: Namespace
metadata:
name: garm
---
apiVersion: v1
kind: Namespace
metadata:
name: garm-runners

View File

@@ -1,12 +1,13 @@
---
apiVersion: local.openebs.io/v1alpha1
kind: LVMVolume
metadata:
labels:
kubernetes.io/nodename: anapistula-delrosalae
name: authentik-postgresql-cluster-lvmhdd-1
name: garm-lvmhdd
namespace: openebs
spec:
capacity: 10Gi
capacity: 5Gi
ownerNodeID: anapistula-delrosalae
shared: "yes"
thinProvision: "no"
@@ -16,10 +17,10 @@ spec:
kind: PersistentVolume
apiVersion: v1
metadata:
name: authentik-postgresql-cluster-lvmhdd-1
name: garm-lvmhdd
spec:
capacity:
storage: 10Gi
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
@@ -28,6 +29,18 @@ spec:
csi:
driver: local.csi.openebs.io
fsType: btrfs
volumeHandle: authentik-postgresql-cluster-lvmhdd-1
volumeHandle: garm-lvmhdd
---
# PVCs are dynamically created by the Postgres operator
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: garm-lvmhdd
namespace: garm
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: hdd-lvmpv
volumeName: garm-lvmhdd

29
apps/garm/rbac.yaml Normal file
View File

@@ -0,0 +1,29 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: garm
namespace: garm
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: garm-provider-k8s
namespace: garm-runners
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "configmaps", "secrets", "events"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: garm-provider-k8s
namespace: garm-runners
subjects:
- kind: ServiceAccount
name: garm
namespace: garm
roleRef:
kind: Role
name: garm-provider-k8s
apiGroup: rbac.authorization.k8s.io

View File

@@ -1,38 +1,32 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: crawl4ai-secret
namespace: crawl4ai
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: crawl4ai
namespace: crawl4ai
name: garm
namespace: garm
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: crawl4ai
serviceAccount: crawl4ai-secret
role: garm
serviceAccount: garm
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: crawl4ai-secret
namespace: crawl4ai
name: garm-config
namespace: garm
spec:
type: kv-v2
mount: secret
path: crawl4ai
path: garm
destination:
create: true
name: crawl4ai-secret
name: garm-config
type: Opaque
transformation:
excludeRaw: true
vaultAuthRef: crawl4ai
vaultAuthRef: garm

View File

@@ -1,14 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: crawl4ai
namespace: crawl4ai
name: garm
namespace: garm
spec:
type: ClusterIP
selector:
app: crawl4ai
app: garm
ports:
- name: http
port: 11235
targetPort: 11235
port: 9997
targetPort: 9997
protocol: TCP

View File

@@ -73,7 +73,7 @@ spec:
ISSUE_INDEXER_TYPE: bleve
REPO_INDEXER_ENABLED: true
webhook:
ALLOWED_HOST_LIST: woodpecker.lumpiasty.xyz
ALLOWED_HOST_LIST: garm.garm.svc.cluster.local
admin:
username: GiteaAdmin
email: gi@tea.com
@@ -90,11 +90,6 @@ spec:
# Requirement for sharing ip with other service
externalTrafficPolicy: Cluster
ipFamilyPolicy: RequireDualStack
http:
type: ClusterIP
# We need the service to be at port 80 specifically
# to work around bug of Actions Runner
port: 80
ingress:
enabled: true
@@ -102,7 +97,7 @@ spec:
annotations:
cert-manager.io/cluster-issuer: letsencrypt
acme.cert-manager.io/http01-edit-in-place: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "1g"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
hosts:
- host: gitea.lumpiasty.xyz
paths:

View File

@@ -18,7 +18,7 @@ spec:
chart:
spec:
chart: immich
version: 1.2.2
version: 1.1.1
sourceRef:
kind: HelmRepository
name: secustor

View File

@@ -1,15 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- crawl4ai
- crawl4ai-proxy
- authentik
- gitea
- renovate
- librechat
- frigate
- llama
- immich
- nas
- searxng
- ispeak3
- openwebui
- woodpecker
- garm

View File

@@ -1,5 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- namespace.yaml
- release.yaml

View File

@@ -2,4 +2,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: woodpecker
name: librechat

120
apps/librechat/release.yaml Normal file
View File

@@ -0,0 +1,120 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: dynomite567-charts
namespace: librechat
spec:
interval: 24h
url: https://dynomite567.github.io/helm-charts/
---
# apiVersion: helm.toolkit.fluxcd.io/v2
# kind: HelmRelease
# metadata:
# name: librechat
# namespace: librechat
# spec:
# interval: 30m
# chart:
# spec:
# chart: librechat
# version: 1.9.1
# sourceRef:
# kind: HelmRepository
# name: dynomite567-charts
# values:
# global:
# librechat:
# existingSecretName: librechat
# librechat:
# configEnv:
# PLUGIN_MODELS: null
# ALLOW_REGISTRATION: "false"
# TRUST_PROXY: "1"
# DOMAIN_CLIENT: https://librechat.lumpiasty.xyz
# SEARCH: "true"
# existingSecretName: librechat
# configYamlContent: |
# version: 1.0.3
# endpoints:
# custom:
# - name: "Llama.cpp"
# apiKey: "llama"
# baseURL: "http://llama.llama.svc.cluster.local:11434/v1"
# models:
# default: [
# "DeepSeek-R1-0528-Qwen3-8B-GGUF",
# "Qwen3-8B-GGUF",
# "Qwen3-8B-GGUF-no-thinking",
# "gemma3n-e4b",
# "gemma3-12b",
# "gemma3-12b-q2",
# "gemma3-12b-novision",
# "gemma3-4b",
# "gemma3-4b-novision",
# "Qwen3-4B-Thinking-2507",
# "Qwen3-4B-Thinking-2507-long-ctx",
# "Qwen2.5-VL-7B-Instruct-GGUF",
# "Qwen2.5-VL-32B-Instruct-GGUF-IQ1_S",
# "Qwen2.5-VL-32B-Instruct-GGUF-Q2_K_L",
# "Qwen3-VL-2B-Instruct-GGUF",
# "Qwen3-VL-2B-Instruct-GGUF-unslothish",
# "Qwen3-VL-2B-Thinking-GGUF",
# "Qwen3-VL-4B-Instruct-GGUF",
# "Qwen3-VL-4B-Instruct-GGUF-unslothish",
# "Qwen3-VL-4B-Thinking-GGUF",
# "Qwen3-VL-8B-Instruct-GGUF",
# "Qwen3-VL-8B-Instruct-GGUF-unslothish",
# "Qwen3-VL-8B-Thinking-GGUF",
# "Huihui-Qwen3-VL-8B-Instruct-abliterated-GGUF",
# "Huihui-Qwen3-VL-8B-Thinking-abliterated-GGUF"
# ]
# titleConvo: true
# titleModel: "gemma3-4b-novision"
# summarize: false
# summaryModel: "gemma3-4b-novision"
# forcePrompt: false
# modelDisplayLabel: "Llama.cpp"
# # ✨ IMPORTANT: let llama-swap/llama-server own all these
# dropParams:
# - "temperature"
# - "top_p"
# - "top_k"
# - "presence_penalty"
# - "frequency_penalty"
# - "stop"
# - "max_tokens"
# imageVolume:
# enabled: true
# size: 10G
# accessModes: ReadWriteOnce
# storageClassName: mayastor-single-hdd
# ingress:
# enabled: true
# className: nginx-ingress
# annotations:
# cert-manager.io/cluster-issuer: letsencrypt
# nginx.ingress.kubernetes.io/proxy-body-size: "0"
# nginx.ingress.kubernetes.io/proxy-buffering: "false"
# nginx.ingress.kubernetes.io/proxy-read-timeout: 30m
# hosts:
# - host: librechat.lumpiasty.xyz
# paths:
# - path: /
# pathType: ImplementationSpecific
# tls:
# - hosts:
# - librechat.lumpiasty.xyz
# secretName: librechat-ingress
# mongodb:
# persistence:
# storageClass: mayastor-single-hdd
# meilisearch:
# persistence:
# storageClass: mayastor-single-hdd
# auth:
# existingMasterKeySecret: librechat

View File

@@ -4,19 +4,22 @@ logToStdout: "both" # proxy and upstream
macros:
base_args: "--no-warmup --port ${PORT}"
common_args: "--fit-target 1536 --no-warmup --port ${PORT}"
ctx_128k: "--ctx-size 131072"
ctx_256k: "--ctx-size 262144"
common_args: "--fit-target 1536 --fit-ctx 65536 --no-warmup --port ${PORT}"
gemma_sampling: "--prio 2 --temp 1.0 --repeat-penalty 1.0 --min-p 0.00 --top-k 64 --top-p 0.95"
qwen35_sampling: "--temp 0.6 --top-p 0.95 --top-k 20 --min-p 0.00 -ctk q8_0 -ctv q8_0"
qwen35_35b_args: "--temp 1.0 --min-p 0.00 --top-p 0.95 --top-k 20 -ctk q8_0 -ctv q8_0"
qwen35_sampling: "--temp 0.6 --top-p 0.95 --top-k 20 --min-p 0.00 -ctk q4_0 -ctv q4_0"
qwen35_35b_args: "--temp 1.0 --min-p 0.00 --top-p 0.95 --top-k 20 -ctk q4_0 -ctv q4_0"
qwen35_35b_heretic_mmproj: "--mmproj-url https://huggingface.co/unsloth/Qwen3.5-35B-A3B-GGUF/resolve/main/mmproj-F16.gguf --mmproj /root/.cache/llama.cpp/unsloth_Qwen3.5-35B-A3B-GGUF_mmproj-F16.gguf"
qwen35_4b_heretic_mmproj: "--mmproj-url https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/mmproj-F16.gguf --mmproj /root/.cache/llama.cpp/unsloth_Qwen3.5-4B-GGUF_mmproj-F16.gguf"
glm47_flash_args: "--temp 0.7 --top-p 1.0 --min-p 0.01 --repeat-penalty 1.0"
gemma4_sampling: "--temp 1.0 --top-p 0.95 --top-k 64"
thinking_on: "--chat-template-kwargs '{\"enable_thinking\": true}'"
thinking_off: "--chat-template-kwargs '{\"enable_thinking\": false}'"
peers:
openrouter:
proxy: https://openrouter.ai/api
apiKey: ${env.OPENROUTER_API_KEY}
models:
- z-ai/glm-5
hooks:
on_startup:
preload:
@@ -35,7 +38,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/gemma-3-12b-it-GGUF:Q4_K_M
${ctx_128k}
${gemma_sampling}
${common_args}
@@ -43,7 +45,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/gemma-3-12b-it-GGUF:Q4_K_M
${ctx_128k}
${gemma_sampling}
--no-mmproj
${common_args}
@@ -52,7 +53,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/gemma-3-4b-it-GGUF:Q4_K_M
${ctx_128k}
${gemma_sampling}
${common_args}
@@ -60,7 +60,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/gemma-3-4b-it-GGUF:Q4_K_M
${ctx_128k}
${gemma_sampling}
--no-mmproj
${common_args}
@@ -76,14 +75,13 @@ models:
--top-p 0.95
--top-k 40
--repeat-penalty 1.0
-ctk q8_0 -ctv q8_0
-ctk q4_0 -ctv q4_0
${common_args}
"Qwen3.5-35B-A3B-GGUF:Q4_K_M":
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-35B-A3B-GGUF:Q4_K_M
${ctx_256k}
${qwen35_35b_args}
${common_args}
@@ -91,7 +89,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-35B-A3B-GGUF:Q4_K_M
${ctx_256k}
${qwen35_35b_args}
${common_args}
${thinking_off}
@@ -103,7 +100,6 @@ models:
/app/llama-server
-hf mradermacher/Qwen3.5-35B-A3B-heretic-GGUF:Q4_K_M
${qwen35_35b_heretic_mmproj}
${ctx_256k}
${qwen35_35b_args}
${common_args}
@@ -112,7 +108,6 @@ models:
/app/llama-server
-hf mradermacher/Qwen3.5-35B-A3B-heretic-GGUF:Q4_K_M
${qwen35_35b_heretic_mmproj}
${ctx_256k}
${qwen35_35b_args}
${common_args}
${thinking_off}
@@ -121,7 +116,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-0.8B-GGUF:Q4_K_XL
${ctx_256k}
${qwen35_sampling}
${base_args}
${thinking_on}
@@ -139,7 +133,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-2B-GGUF:Q4_K_M
${ctx_256k}
${qwen35_sampling}
${common_args}
${thinking_on}
@@ -148,7 +141,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-2B-GGUF:Q4_K_M
${ctx_256k}
${qwen35_sampling}
${common_args}
${thinking_off}
@@ -157,7 +149,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-4B-GGUF:Q4_K_M
${ctx_128k}
${qwen35_sampling}
${common_args}
${thinking_on}
@@ -166,7 +157,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-4B-GGUF:Q4_K_M
${ctx_128k}
${qwen35_sampling}
${common_args}
${thinking_off}
@@ -176,7 +166,6 @@ models:
/app/llama-server
-hf mradermacher/Qwen3.5-4B-heretic-GGUF:Q4_K_M
${qwen35_4b_heretic_mmproj}
${ctx_128k}
${qwen35_sampling}
${common_args}
${thinking_on}
@@ -186,7 +175,6 @@ models:
/app/llama-server
-hf mradermacher/Qwen3.5-4B-heretic-GGUF:Q4_K_M
${qwen35_4b_heretic_mmproj}
${ctx_128k}
${qwen35_sampling}
${common_args}
${thinking_off}
@@ -195,7 +183,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-9B-GGUF:Q4_K_M
${ctx_256k}
${qwen35_sampling}
${common_args}
${thinking_on}
@@ -204,7 +191,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-9B-GGUF:Q4_K_M
${ctx_256k}
${qwen35_sampling}
${common_args}
${thinking_off}
@@ -213,7 +199,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-9B-GGUF:Q3_K_M
${ctx_256k}
${qwen35_sampling}
${common_args}
${thinking_on}
@@ -222,7 +207,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-9B-GGUF:Q3_K_M
${ctx_256k}
${qwen35_sampling}
${common_args}
${thinking_off}
@@ -231,7 +215,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-27B-GGUF:Q3_K_M
${ctx_256k}
${qwen35_sampling}
${common_args}
${thinking_on}
@@ -240,46 +223,6 @@ models:
cmd: |
/app/llama-server
-hf unsloth/Qwen3.5-27B-GGUF:Q3_K_M
${ctx_256k}
${qwen35_sampling}
${common_args}
${thinking_off}
"GLM-4.7-Flash-GGUF:Q4_K_M":
cmd: |
/app/llama-server
-hf unsloth/GLM-4.7-Flash-GGUF:Q4_K_M
${glm47_flash_args}
${common_args}
"gemma-4-26B-A4B-it:UD-Q4_K_XL":
cmd: |
/app/llama-server
-hf unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_XL \
${ctx_256k}
${gemma4_sampling}
${common_args}
"gemma-4-26B-A4B-it:UD-Q2_K_XL":
cmd: |
/app/llama-server
-hf unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q2_K_XL \
${ctx_256k}
${gemma4_sampling}
${common_args}
"unsloth/gemma-4-E4B-it-GGUF:UD-Q4_K_XL":
cmd: |
/app/llama-server
-hf unsloth/gemma-4-E4B-it-GGUF:UD-Q4_K_XL \
${ctx_128k}
${gemma4_sampling}
${common_args}
"unsloth/gemma-4-E2B-it-GGUF:UD-Q4_K_XL":
cmd: |
/app/llama-server
-hf unsloth/gemma-4-E2B-it-GGUF:UD-Q4_K_XL \
${ctx_128k}
${gemma4_sampling}
${common_args}

View File

@@ -18,7 +18,7 @@ spec:
spec:
containers:
- name: llama-swap
image: ghcr.io/mostlygeek/llama-swap:v199-vulkan-b8660
image: ghcr.io/mostlygeek/llama-swap:v197-vulkan-b8248
imagePullPolicy: IfNotPresent
command:
- /app/llama-swap
@@ -29,6 +29,12 @@ spec:
- containerPort: 8080
name: http
protocol: TCP
env:
- name: OPENROUTER_API_KEY
valueFrom:
secretKeyRef:
name: llama-openrouter
key: OPENROUTER_API_KEY
volumeMounts:
- name: models
mountPath: /root/.cache

View File

@@ -7,7 +7,7 @@ metadata:
name: llama-models-lvmssd
namespace: openebs
spec:
capacity: "322122547200"
capacity: 200Gi
ownerNodeID: anapistula-delrosalae
shared: "yes"
thinProvision: "no"
@@ -20,7 +20,7 @@ metadata:
name: llama-models-lvmssd
spec:
capacity:
storage: 300Gi
storage: 200Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
@@ -41,6 +41,6 @@ spec:
- ReadWriteOnce
resources:
requests:
storage: 300Gi
storage: 200Gi
storageClassName: ssd-lvmpv
volumeName: llama-models-lvmssd

View File

@@ -36,3 +36,26 @@ spec:
excludeRaw: true
vaultAuthRef: llama
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: llama-openrouter
namespace: llama
spec:
type: kv-v2
mount: secret
path: openrouter
destination:
create: true
name: llama-openrouter
type: Opaque
transformation:
excludeRaw: true
templates:
OPENROUTER_API_KEY:
text: '{{ get .Secrets "API_KEY" }}'
vaultAuthRef: llama

View File

@@ -4,6 +4,5 @@ resources:
- namespace.yaml
- pvc.yaml
- pvc-pipelines.yaml
- secret.yaml
- release.yaml
- ingress.yaml

View File

@@ -18,7 +18,7 @@ spec:
chart:
spec:
chart: open-webui
version: 13.0.1
version: 12.10.0
sourceRef:
kind: HelmRepository
name: open-webui
@@ -44,30 +44,3 @@ spec:
persistence:
enabled: true
existingClaim: openwebui-pipelines-lvmhdd
# SSO with Authentik
extraEnvVars:
- name: WEBUI_URL
value: "https://openwebui.lumpiasty.xyz"
- name: OAUTH_CLIENT_ID
valueFrom:
secretKeyRef:
name: openwebui-authentik
key: client_id
- name: OAUTH_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: openwebui-authentik
key: client_secret
- name: OAUTH_PROVIDER_NAME
value: "authentik"
- name: OPENID_PROVIDER_URL
value: "https://authentik.lumpiasty.xyz/application/o/open-web-ui/.well-known/openid-configuration"
- name: OPENID_REDIRECT_URI
value: "https://openwebui.lumpiasty.xyz/oauth/oidc/callback"
- name: ENABLE_OAUTH_SIGNUP
value: "true"
- name: ENABLE_LOGIN_FORM
value: "false"
- name: OAUTH_MERGE_ACCOUNTS_BY_EMAIL
value: "true"

View File

@@ -1,43 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: openwebui-secret
namespace: openwebui
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: openwebui
namespace: openwebui
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: openwebui
serviceAccount: openwebui-secret
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: openwebui-authentik
namespace: openwebui
spec:
type: kv-v2
mount: secret
path: authentik/openwebui
destination:
create: true
name: openwebui-authentik
type: Opaque
transformation:
excludeRaw: true
templates:
client_id:
text: '{{ get .Secrets "client_id" }}'
client_secret:
text: '{{ get .Secrets "client_secret" }}'
vaultAuthRef: openwebui

View File

@@ -15,7 +15,7 @@ spec:
- name: renovate
# Update this to the latest available and then enable Renovate on
# the manifest
image: renovate/renovate:43.104.8-full
image: renovate/renovate:43.64.6-full
envFrom:
- secretRef:
name: renovate-gitea-token

View File

@@ -0,0 +1 @@
use_default_settings: true

View File

@@ -0,0 +1,42 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: searxng
namespace: searxng
spec:
replicas: 1
selector:
matchLabels:
app: searxng
template:
metadata:
labels:
app: searxng
spec:
containers:
- name: searxng
image: searxng/searxng:2025.8.12-6b1516d
ports:
- containerPort: 8080
env:
- name: SEARXNG_SECRET
valueFrom:
secretKeyRef:
name: searxng-secret
key: SEARXNG_SECRET
optional: false
volumeMounts:
- name: config-volume
mountPath: /etc/searxng/settings.yml
subPath: settings.yml
readOnly: true
- name: searxng-persistent-data
mountPath: /var/cache/searxng
volumes:
- name: config-volume
configMap:
name: searxng-config
- name: searxng-persistent-data
persistentVolumeClaim:
claimName: searxng-persistent-data-lvmhdd

25
apps/searxng/ingress.yaml Normal file
View File

@@ -0,0 +1,25 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: searxng
name: searxng
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: nginx-ingress
rules:
- host: searxng.lumpiasty.xyz
http:
paths:
- backend:
service:
name: searxng
port:
number: 8080
path: /
pathType: Prefix
tls:
- hosts:
- searxng.lumpiasty.xyz
secretName: searxng-ingress

View File

@@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- pvc.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
configMapGenerator:
- name: searxng-config
namespace: searxng
files:
- settings.yml=configs/settings.yml

View File

@@ -1,4 +1,5 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: crawl4ai
name: searxng

46
apps/searxng/pvc.yaml Normal file
View File

@@ -0,0 +1,46 @@
---
apiVersion: local.openebs.io/v1alpha1
kind: LVMVolume
metadata:
labels:
kubernetes.io/nodename: anapistula-delrosalae
name: searxng-persistent-data-lvmhdd
namespace: openebs
spec:
capacity: 1Gi
ownerNodeID: anapistula-delrosalae
shared: "yes"
thinProvision: "no"
vgPattern: ^openebs-hdd$
volGroup: openebs-hdd
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: searxng-persistent-data-lvmhdd
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: hdd-lvmpv
volumeMode: Filesystem
csi:
driver: local.csi.openebs.io
fsType: btrfs
volumeHandle: searxng-persistent-data-lvmhdd
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: searxng-persistent-data-lvmhdd
namespace: searxng
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: hdd-lvmpv
volumeName: searxng-persistent-data-lvmhdd

14
apps/searxng/service.yaml Normal file
View File

@@ -0,0 +1,14 @@
---
apiVersion: v1
kind: Service
metadata:
name: searxng
namespace: searxng
spec:
selector:
app: searxng
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: ClusterIP

View File

@@ -1,8 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- postgres-volume.yaml
- postgres-cluster.yaml
- release.yaml
- secret.yaml

View File

@@ -1,23 +0,0 @@
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: woodpecker-postgresql-cluster
namespace: woodpecker
spec:
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:17.4
bootstrap:
initdb:
database: woodpecker
owner: woodpecker
storage:
pvcTemplate:
storageClassName: ssd-lvmpv
resources:
requests:
storage: 10Gi
volumeName: woodpecker-postgresql-cluster-lvmssd

View File

@@ -1,33 +0,0 @@
apiVersion: local.openebs.io/v1alpha1
kind: LVMVolume
metadata:
labels:
kubernetes.io/nodename: anapistula-delrosalae
name: woodpecker-postgresql-cluster-lvmssd
namespace: openebs
spec:
capacity: 10Gi
ownerNodeID: anapistula-delrosalae
shared: "yes"
thinProvision: "no"
vgPattern: ^openebs-ssd$
volGroup: openebs-ssd
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: woodpecker-postgresql-cluster-lvmssd
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ssd-lvmpv
volumeMode: Filesystem
csi:
driver: local.csi.openebs.io
fsType: btrfs
volumeHandle: woodpecker-postgresql-cluster-lvmssd
---
# PVC is dynamically created by the Postgres operator

View File

@@ -1,115 +0,0 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: woodpecker
namespace: woodpecker
spec:
interval: 24h
url: https://woodpecker-ci.org/
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: woodpecker
namespace: woodpecker
spec:
interval: 30m
chart:
spec:
chart: woodpecker
version: 3.5.1
sourceRef:
kind: HelmRepository
name: woodpecker
namespace: woodpecker
interval: 12h
values:
server:
enabled: true
statefulSet:
replicaCount: 1
persistentVolume:
enabled: false # Using Postgresql database
env:
WOODPECKER_HOST: "https://woodpecker.lumpiasty.xyz"
# Gitea integration
WOODPECKER_GITEA: "true"
WOODPECKER_GITEA_URL: "https://gitea.lumpiasty.xyz"
# PostgreSQL database configuration
WOODPECKER_DATABASE_DRIVER: postgres
# Password is loaded from woodpecker-postgresql-cluster-app secret (created by CNPG)
WOODPECKER_DATABASE_DATASOURCE:
valueFrom:
secretKeyRef:
name: woodpecker-postgresql-cluster-app
key: fqdn-uri
# Allow logging in from all accounts on Gitea
WOODPECKER_OPEN: "true"
# Make lumpiasty admin
WOODPECKER_ADMIN: GiteaAdmin
createAgentSecret: true
extraSecretNamesForEnvFrom:
- woodpecker-secrets
ingress:
enabled: true
ingressClassName: nginx-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt
acme.cert-manager.io/http01-edit-in-place: "true"
hosts:
- host: woodpecker.lumpiasty.xyz
paths:
- path: /
backend:
serviceName: woodpecker-server
servicePort: 80
tls:
- hosts:
- woodpecker.lumpiasty.xyz
secretName: woodpecker-ingress
resources:
requests:
cpu: 100m
memory: 256Mi
service:
type: ClusterIP
port: 80
agent:
enabled: true
replicaCount: 2
env:
WOODPECKER_SERVER: "woodpecker-server:9000"
WOODPECKER_BACKEND: kubernetes
WOODPECKER_BACKEND_K8S_NAMESPACE: woodpecker
WOODPECKER_BACKEND_K8S_STORAGE_CLASS: ssd-lvmpv
WOODPECKER_BACKEND_K8S_VOLUME_SIZE: 10G
WOODPECKER_BACKEND_K8S_STORAGE_RWX: false
WOODPECKER_CONNECT_RETRY_COUNT: "5"
mapAgentSecret: true
extraSecretNamesForEnvFrom:
- woodpecker-secrets
persistence:
enabled: false
serviceAccount:
create: true
rbac:
create: true
resources:
requests:
cpu: 100m
memory: 128Mi

View File

@@ -1,62 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: woodpecker-secret
namespace: woodpecker
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: woodpecker
namespace: woodpecker
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: woodpecker
serviceAccount: woodpecker-secret
---
# Main woodpecker secrets from Vault
# Requires vault kv put secret/woodpecker \
# WOODPECKER_AGENT_SECRET="$(openssl rand -hex 32)" \
# WOODPECKER_GITEA_CLIENT="<gitea-oauth-client>" \
# WOODPECKER_GITEA_SECRET="<gitea-oauth-secret>"
# Note: Database password comes from CNPG secret (woodpecker-postgresql-cluster-app)
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: woodpecker-secrets
namespace: woodpecker
spec:
type: kv-v2
mount: secret
path: woodpecker
destination:
create: true
name: woodpecker-secrets
type: Opaque
transformation:
excludeRaw: true
vaultAuthRef: woodpecker
---
# Container registry credentials for Kaniko
# Requires vault kv put secret/container-registry \
# REGISTRY_USERNAME="<username>" \
# REGISTRY_PASSWORD="<token>"
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: container-registry
namespace: woodpecker
spec:
type: kv-v2
mount: secret
path: container-registry
destination:
create: true
name: container-registry
type: Opaque
transformation:
excludeRaw: true
vaultAuthRef: woodpecker

View File

@@ -3,11 +3,10 @@
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1775201809,
"narHash": "sha256-WmpoCegCQ6Q2ZyxqO05zlz/7XXjt/l2iut4Nk5Nt+W4=",
"lastModified": 1769881431,
"owner": "cachix",
"repo": "devenv",
"rev": "42a5505d4700e791732e48a38b4cca05a755f94b",
"rev": "72d5e66e2dd5112766ef4c9565872b51094b542d",
"type": "github"
},
"original": {
@@ -17,13 +16,27 @@
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
@@ -35,6 +48,47 @@
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1769069492,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1762808025,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"krew2nix": {
"inputs": {
"flake-utils": "flake-utils",
@@ -45,11 +99,10 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1775175041,
"narHash": "sha256-lYCPSMIV26VazREzl/TIpbWhBXJ+vJ0EJ+308TrX/6w=",
"lastModified": 1769904483,
"owner": "a1994sc",
"repo": "krew2nix",
"rev": "15c594042f1ba80ce97ab190a9c684a44c613590",
"rev": "17d6ad3375899bd3f7d4d298481536155f3ec13c",
"type": "github"
},
"original": {
@@ -60,11 +113,10 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1775036866,
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"lastModified": 1769461804,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
"type": "github"
},
"original": {
@@ -77,14 +129,17 @@
"root": {
"inputs": {
"devenv": "devenv",
"git-hooks": "git-hooks",
"krew2nix": "krew2nix",
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs",
"pre-commit-hooks": [
"git-hooks"
]
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
@@ -99,7 +154,6 @@
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
@@ -119,11 +173,10 @@
]
},
"locked": {
"lastModified": 1773297127,
"narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
"lastModified": 1769691507,
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
"rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
"type": "github"
},
"original": {

View File

@@ -41,18 +41,6 @@ in
openbao
pv-migrate
mermaid-cli
(
# Wrapping opencode to set the OPENCODE_ENABLE_EXA environment variable
runCommand "opencode" {
buildInputs = [ makeWrapper ];
} ''
mkdir -p $out/bin
makeWrapper ${pkgs.opencode}/bin/opencode $out/bin/opencode \
--set OPENCODE_ENABLE_EXA "1"
''
)
tea
woodpecker-cli
];
# Scripts

View File

@@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="284.538" height="253.96">
<style>
@media (prefers-color-scheme: dark) {
path {
fill: white;
}
}
</style>
<path d="M162.51 33.188c-26.77.411-54.004 6.885-71.494 3.745-1.313-.232-2.124 1.338-1.171 2.265 14.749 14.003 20.335 28.16 36.718 30.065l.476.103c-7.567 7.799-14.028 18.018-18.571 31.171-4.89 14.106-6.268 29.421-7.89 47.105-2.445 26.332-5.173 56.152-20.038 93.54a246.489 246.489 0 0 0-13.27 45.946h22.652a221.202 221.202 0 0 1 11.249-37.786c16.049-40.374 19.073-73.257 21.505-99.693 1.493-16.255 2.806-30.309 6.796-41.853 11.647-33.527 39.408-40.889 61.056-36.693 21.004 4.067 41.673 20.502 40.592 44.016-.772 15.985-7.76 23.166-12.87 28.43-2.793 2.883-5.47 5.611-6.731 9.498-3.037 9.19.101 19.434 8.494 27.568 22.24 20.734 34.338 59.717 33.681 106.513h22.176c.592-52.935-13.951-97.839-40.503-122.626-2.097-2.021-2.69-3.604-3.191-3.347 1.222-1.544 3.217-3.346 4.633-4.813 29.382-21.79 77.813-1.892 107.054 9.653 7.58 2.985 11.274-4.338 4.067-8.623-25.097-14.84-76.54-54.016-105.368-79.718-4.029-3.54-6.796-7.8-11.455-11.738-15.547-27.439-41.84-33.127-68.597-32.728Zm35.238 60.27a15.161 15.161 0 0 0-2.008.232 15.161 15.161 0 0 0-1.506 29.434 15.154 15.154 0 0 0 9.473-28.79 15.161 15.161 0 0 0-5.959-.876zm-44.286 147.17a2.033 2.033 0 0 0-1.133.374c-1.08.772-1.93 3.05-.772 5.701 5.38 12.394 9.1 25.445 12.536 40.413h22.484c-5.676-16.629-16.307-34.055-27.851-43.978-2.008-1.737-3.913-2.574-5.251-2.51z" style="stroke-width:12.8704" transform="translate(-67.27 -33.169)"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,32 +0,0 @@
# Roles with needed access for OpenBao's Kubernetes secret engine
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: k8s-full-secrets-abilities
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get"]
- apiGroups: [""]
resources: ["serviceaccounts", "serviceaccounts/token"]
verbs: ["create", "update", "delete"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings", "clusterrolebindings"]
verbs: ["create", "update", "delete"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "clusterroles"]
verbs: ["bind", "escalate", "create", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: openbao-token-creator-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: k8s-full-secrets-abilities
subjects:
- kind: ServiceAccount
name: openbao
namespace: openbao

View File

@@ -18,7 +18,7 @@ spec:
chart:
spec:
chart: cert-manager-webhook-ovh
version: 0.9.6
version: 0.9.4
sourceRef:
kind: HelmRepository
name: cert-manager-webhook-ovh

View File

@@ -23,7 +23,7 @@ spec:
chart:
spec:
chart: cert-manager
version: v1.20.1
version: v1.20.0
sourceRef:
kind: HelmRepository
name: cert-manager

View File

@@ -23,7 +23,7 @@ spec:
chart:
spec:
chart: cilium
version: 1.19.2
version: 1.19.1
sourceRef:
kind: HelmRepository
name: cilium

View File

@@ -23,7 +23,7 @@ spec:
chart:
spec:
chart: k8up
version: 4.9.0
version: 4.8.6
sourceRef:
kind: HelmRepository
name: k8up-io

View File

@@ -23,7 +23,7 @@ spec:
chart:
spec:
chart: ingress-nginx
version: 4.15.1
version: 4.15.0
sourceRef:
kind: HelmRepository
name: ingress-nginx

View File

@@ -23,7 +23,7 @@ spec:
chart:
spec:
chart: openbao
version: 0.26.2
version: 0.25.7
sourceRef:
kind: HelmRepository
name: openbao

View File

@@ -25,4 +25,3 @@ resources:
- configs/openbao-volume.yaml
- controllers/openbao.yaml
- configs/openbao-k8s-se-role.yaml

View File

@@ -2,7 +2,6 @@
import argparse
import os
import pathlib
from typing import Any, cast
import hvac
@@ -43,7 +42,7 @@ def synchronize_auth_kubernetes_config(client: hvac.Client):
def synchronize_kubernetes_roles(client: hvac.Client):
kubernetes = Kubernetes(client.adapter)
policy_dir = os.path.join(os.path.dirname(__file__), '../vault/kubernetes-auth-roles/')
policy_dir = os.path.join(os.path.dirname(__file__), '../vault/kubernetes-roles/')
roles: dict[str, Any] = {} # pyright:ignore[reportExplicitAny]
for filename in os.listdir(policy_dir):
@@ -68,69 +67,6 @@ def synchronize_kubernetes_roles(client: hvac.Client):
# Using write data instead of kubernetes.create_role, we can pass raw yaml
_ = client.write_data(f'/auth/kubernetes/role/{role_name}', data=role_content) # pyright:ignore[reportAny]
def synchronize_approle_auth(client: hvac.Client):
if client.sys.list_auth_methods().get('approle/') is None:
print('Enabling AppRole auth method')
client.sys.enable_auth_method('approle', 'AppRole authorization for CI')
roles_dir = pathlib.Path(__file__).parent.joinpath('../vault/approles/')
roles: dict[str, Any] = {}
for filename in roles_dir.iterdir():
with filename.open('r') as f:
role = yaml.safe_load(f.read())
assert type(role) is dict
roles[filename.stem] = role
roles_on_vault: list[str] = []
roles_response = client.list("auth/approle/roles")
if roles_response is not None:
roles_on_vault = roles_response['data']['keys']
for role in roles_on_vault:
if role not in roles:
print(f'Deleting role: {role}')
client.delete(f'auth/approle/role/{role}')
for role_name, role_content in roles.items():
print(f'Updating role: {role_name}')
client.write_data(f'auth/approle/role/{role_name}', data=role_content)
def synchronize_kubernetes_secretengine(client: hvac.Client):
# Ensure kubernetes secret engine is enabled
if client.sys.list_mounted_secrets_engines().get('kubernetes/') is None:
print('Enabling kubernetes secret engine')
client.sys.enable_secrets_engine('kubernetes', 'kubernetes', 'Cluster access')
# Write empty config (all defaults, working on the same cluster)
client.write('kubernetes/config', None)
policy_dir = pathlib.Path(__file__).parent.joinpath('../vault/kubernetes-se-roles/')
roles: dict[str, Any] = {}
for filename in policy_dir.iterdir():
with filename.open('r') as f:
role = yaml.safe_load(f.read())
assert type(role) is dict
# generated_role_rules must be json or yaml formatted string, convert it
if 'generated_role_rules' in role and type(role['generated_role_rules']) is not str:
role['generated_role_rules'] = yaml.safe_dump(role['generated_role_rules'])
roles[filename.stem] = role
roles_on_vault: list[str] = []
roles_response = client.list("kubernetes/roles")
if roles_response is not None:
roles_on_vault = roles_response['data']['keys']
for role in roles_on_vault:
if role not in roles:
print(f'Deleting role: {role}')
client.delete(f'kubernetes/roles/{role}')
for role_name, role_content in roles.items():
print(f'Updating role: {role_name}')
client.write_data(f'kubernetes/roles/{role_name}', data=role_content)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
prog="synchronizeVault",
@@ -146,11 +82,5 @@ if __name__ == '__main__':
print('Synchronizing kubernetes config')
synchronize_auth_kubernetes_config(client)
print('Synchronizing kubernetes auth roles')
print('Synchronizing kubernetes roles')
synchronize_kubernetes_roles(client)
print('Synchronizing AppRole auth method')
synchronize_approle_auth(client)
print('Synchronizing kubernetes secret engine')
synchronize_kubernetes_secretengine(client)

View File

@@ -1,4 +0,0 @@
token_ttl: 20m
token_max_ttl: 20m
policies:
- flux-reconcile

View File

@@ -1,6 +0,0 @@
bound_service_account_names:
- authentik-secret
bound_service_account_namespaces:
- authentik
token_policies:
- authentik

View File

@@ -1,6 +0,0 @@
bound_service_account_names:
- openwebui-secret
bound_service_account_namespaces:
- openwebui
token_policies:
- openwebui

View File

@@ -1,6 +0,0 @@
bound_service_account_names:
- woodpecker-secret
bound_service_account_namespaces:
- woodpecker
token_policies:
- woodpecker

View File

@@ -1,6 +1,6 @@
bound_service_account_names:
- crawl4ai-secret
- garm
bound_service_account_namespaces:
- crawl4ai
- garm
token_policies:
- crawl4ai
- garm

View File

@@ -1,6 +0,0 @@
allowed_kubernetes_namespaces: flux-system
generated_role_rules:
rules:
- apiGroups: ["source.toolkit.fluxcd.io"]
resources: ["gitrepositories"]
verbs: ["get", "patch", "watch"]

View File

@@ -1,3 +0,0 @@
path "secret/data/authentik" {
capabilities = ["read"]
}

View File

@@ -1,3 +0,0 @@
path "secret/data/crawl4ai" {
capabilities = ["read"]
}

View File

@@ -1,3 +0,0 @@
path "kubernetes/creds/flux-reconcile" {
capabilities = ["update"]
}

7
vault/policy/garm.hcl Normal file
View File

@@ -0,0 +1,7 @@
path "secret/data/garm" {
capabilities = ["read"]
}
path "secret/data/backblaze" {
capabilities = ["read"]
}

View File

@@ -1,3 +0,0 @@
path "secret/data/authentik/openwebui" {
capabilities = ["read"]
}

View File

@@ -1,7 +0,0 @@
path "secret/data/woodpecker" {
capabilities = ["read"]
}
path "secret/data/container-registry" {
capabilities = ["read"]
}