Compare commits

...

10 Commits

Author SHA1 Message Date
gilgamezh 45dfbfcfbb Add ArgoCD and Gitea for GitOps workflow implementation
- Deploy ArgoCD with Helm for GitOps continuous delivery
  * Configure LoadBalancer and Ingress access on LAN
  * Enable ArgoCD Image Updater for automatic "latest" tag updates
  * Simplified RBAC for single-user homelab environment

- Deploy Gitea as self-hosted Git server for local repositories
  * PostgreSQL backend with NFS persistent storage
  * SSH and HTTP access via MetalLB LoadBalancer
  * Integration guides for ArgoCD GitOps workflows

- Add example ArgoCD Application with auto-image updates
- Include comprehensive migration guides from Helm to GitOps
- Maintain compatibility with existing Helm-based deployments

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 16:52:10 +02:00
gilgamezh 99e9371969 Update Plex to use new configurable GPU support
- Replace extraVolumeMounts/extraVolumes with new gpu configuration
- Enable GPU support with cleaner, more maintainable config
- Use same /dev/dri paths for hardware-accelerated transcoding
- Leverages new kube-plex GPU feature for better integration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 14:38:21 +02:00
gilgamezh ff3e6f723c Add comprehensive documentation and automated update script
- Add README.md: Complete repository overview, architecture, and usage guide
- Add update.sh: Automated K3s cluster upgrade script for all nodes
- Add CLAUDE.md: Claude Code integration documentation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 14:18:07 +02:00
gilgamezh 620d757f8b Add new applications: Home Assistant Voice LLMs and Ollama
- Add custom Helm chart for Home Assistant Voice LLMs integration
- Add Ollama configuration for local LLM inference
- Support AI voice assistant capabilities in homelab

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 14:13:23 +02:00
gilgamezh 0f61ffae27 Update node selectors and Plex version
- Update Plex: 1.41.3 → 1.41.8
- Fix deprecated node selector: beta.kubernetes.io/arch → kubernetes.io/arch
- Add ARM64 node selector for Transmission

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 14:12:57 +02:00
gilgamezh 3970af8ce2 Update application images to latest versions
- Actual Budget: 25.1.0 → 25.3.1
- Prowlarr: 1.23.1 → 1.37.0.5076-ls121
- Radarr: 5.10.4 → 5.26.2
- Sonarr: 4.0.9 → 4.0.15

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 14:12:28 +02:00
gilgamezh 0e08d89e01 move plex to AMD with GPU 2025-02-16 15:31:50 +01:00
gilgamezh 62f0e703de update actual 2025-01-19 12:25:02 +01:00
gilgamezh 447231f6b0 small fixes and always pull actual-budget image 2025-01-05 09:53:54 +01:00
gilgamezh 7e0a5ee800 deploy jellyfin 2025-01-02 14:04:04 +01:00
26 changed files with 1379 additions and 60 deletions
+112
View File
@@ -0,0 +1,112 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Repository Overview
This repository contains Kubernetes configuration files for a K3s homelab cluster running on TuringPi hardware. It includes Helm charts, values files, and manifests for deploying various self-hosted applications.
## Cluster Architecture
### Hardware Setup
- **turing1**: Control plane + worker (master node, IP: 192.168.222.237)
- **turing2**: Worker node (currently SchedulingDisabled)
- **turing3**: Worker node (also serves as NFS server at turing3.lan)
- **turing4**: Worker node
- **beelink**: Additional worker node
### Core Infrastructure
- **K3s version**: v1.31.6+k3s1
- **Storage**: NFS-backed persistent volumes from turing3.lan:/mnt/ssd
- **Load Balancer**: MetalLB for bare metal LoadBalancer services
- **SSL**: cert-manager with Let's Encrypt (staging/production cluster issuers)
- **Ingress**: Nginx with LAN-only restrictions
## Application Stack
### Media Services
- **Plex**: kube-plex (Kubernetes-native with dynamic transcoding pods)
- **Jellyfin**: Alternative media server
- **Sonarr/Radarr**: TV/Movie management (Bananaspliff charts)
- **Prowlarr**: Indexer management (custom chart)
- **Transmission**: BitTorrent client with OpenVPN
- **FlareSolverr**: Captcha solver service
### Other Applications
- **Actual Budget**: Personal finance (custom chart: my-actual-server/)
- **Home Assistant Voice LLMs**: AI voice integration (custom chart)
- **Ollama**: Local LLM inference
- **Prometheus**: Monitoring stack
- **PostgreSQL**: Database backend
## Common Helm Operations
### Repository Management
```bash
# Key repositories used
helm repo add metallb https://metallb.github.io/metallb
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner
helm repo add jetstack https://charts.jetstack.io
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add bananaspliff https://bananaspliff.github.io/geek-charts
helm repo add k8s-at-home https://k8s-at-home.com/charts/
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add jellyfin https://jellyfin.github.io/jellyfin-helm
helm repo add ollama-helm https://otwld.github.io/ollama-helm/
helm repo update
```
### Application Deployment Pattern
```bash
# Standard deployment with values file
helm upgrade <release-name> <chart> -f <app>_values.yaml -i
# Examples from history:
helm upgrade actual my-actual-server -f actual_values.yaml -i
helm upgrade plex kube-plex/charts/kube-plex --values plex_values.yml
helm upgrade radarr bananaspliff/radarr -f radarr_values.yaml
helm upgrade sonarr bananaspliff/sonarr -f sonarr_values.yaml
helm upgrade prowlarr prowlarr -f prowlarr_values.yml
```
### Development Workflow
```bash
# Chart development
helm create <chart-name>
helm lint <chart-path>
helm template <chart> -f <values> | vim -
# Values inspection
helm show values <chart> > <app>_values.yaml
helm get values <release-name>
helm get manifest <release-name>
```
## File Structure Patterns
- `<app>_values.yaml` - Helm values overrides for each application
- Custom charts in subdirectories (my-actual-server/, home-assistant-voice-llms/, prowlarr/)
- `*_persistent_volume.yml` - PV definitions for applications requiring storage
- Infrastructure manifests: metallb.yml, ingress.yaml, cluster-issuer-*.yaml
## Storage Configuration
- **NFS Server**: turing3.lan serving /mnt/ssd
- **StorageClass**: nfs-client (via nfs-subdir-external-provisioner)
- **Access Mode**: ReadWriteMany for shared media access
- **PVC Pattern**: Applications create their own PVCs or reference pre-existing ones
## Network Setup
- **Pod Network**: Cluster subnet requires allowlisting in Plex for transcoding
- **Ingress**: LAN-only access enforced via limit_ingress_to_lan.yaml
- **Load Balancer**: MetalLB provides external IPs for services
- **DNS**: .lan domain for internal services
## Kube-Plex Specifics
The kube-plex/ directory contains a Go application that replaces the standard Plex transcoder:
- Creates Kubernetes pods for each transcode job
- Requires AMD64 nodes (configured via nodeSelector)
- Mounts shared NFS volumes for media access
- Environment variables: DATA_PVC, CONFIG_PVC, TRANSCODE_PVC, PMS_IMAGE, PMS_INTERNAL_ADDRESS
+144
View File
@@ -0,0 +1,144 @@
# TuringPi K3s Homelab
This repository contains Kubernetes configuration files for a K3s cluster running on TuringPi hardware. It includes Helm charts, values files, and manifests for deploying various self-hosted applications in a homelab environment.
## 🏗️ Cluster Architecture
### Hardware Setup
- **turing1**: Control plane + worker (192.168.222.237)
- **turing2**: Worker node
- **turing3**: Worker node (NFS server at turing3.lan)
- **turing4**: Worker node
- **beelink**: Additional x86_64 worker node
### Infrastructure Stack
- **Kubernetes**: K3s lightweight distribution
- **Storage**: NFS-backed persistent volumes from turing3.lan:/mnt/ssd
- **Load Balancer**: MetalLB for bare metal LoadBalancer services
- **SSL**: cert-manager with Let's Encrypt certificates
- **Ingress**: Nginx with LAN-only access restrictions
## 🚀 Applications
### Media Services
- **Plex**: Via kube-plex (Kubernetes-native with dynamic transcoding)
- **Jellyfin**: Alternative media server
- **Sonarr/Radarr**: TV/Movie management
- **Prowlarr**: Indexer management
- **Transmission**: BitTorrent client with OpenVPN
- **FlareSolverr**: Captcha solver service
### Other Applications
- **Actual Budget**: Personal finance management
- **Home Assistant Voice LLMs**: AI voice integration
- **Ollama**: Local LLM inference
- **Prometheus**: Monitoring and metrics
- **PostgreSQL**: Database backend
## 📁 Repository Structure
```
├── *_values.yaml # Helm values overrides for applications
├── my-actual-server/ # Custom Helm chart for Actual Budget
├── home-assistant-voice-llms/ # Custom Helm chart for Voice AI
├── prowlarr/ # Custom Helm chart for Prowlarr
├── kube-plex/ # Kubernetes-native Plex implementation
├── *.yml # Infrastructure manifests (MetalLB, ingress, etc.)
└── persistent_volume*.yml # Storage definitions
```
## 🔧 Common Operations
### Application Deployment
```bash
# Deploy with Helm using values files
helm upgrade <release-name> <chart> -f <app>_values.yaml -i
# Examples:
helm upgrade actual my-actual-server -f actual_values.yaml -i
helm upgrade plex kube-plex/charts/kube-plex --values plex_values.yml
helm upgrade radarr bananaspliff/radarr -f radarr_values.yaml
```
### Infrastructure Management
```bash
# Apply Kubernetes manifests
kubectl apply -f metallb.yml
kubectl apply -f ingress.yaml
# Check cluster status
kubectl get nodes
kubectl get pods --all-namespaces
```
## 🔄 K3s Cluster Updates
### Automated Update
Run the provided script to update all nodes:
```bash
./update.sh
```
### Manual Update Process
#### 1. Update Master Node (turing1)
```bash
ssh root@turing1 # password: turing
curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false sh -s - \
--write-kubeconfig-mode 644 \
--disable servicelb \
--token torino \
--node-ip 192.168.222.237 \
--disable-cloud-controller \
--disable local-storage
```
#### 2. Update Worker Nodes (turing2, turing3, turing4)
```bash
ssh root@<node> # password: turing
curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false \
K3S_URL=https://192.168.222.237:6443 \
K3S_TOKEN=torino sh -
```
#### 3. Update Beelink Node
```bash
ssh gilgamezh@beelink.lan # no password (SSH keys)
sudo curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false \
K3S_URL=https://192.168.222.237:6443 \
K3S_TOKEN=torino sh -
```
#### 4. Verify Update
```bash
kubectl get nodes # Check all nodes show new version
kubectl get pods --all-namespaces | grep -v Running # Check for issues
```
## 🔑 Access Information
- **Cluster Token**: `torino`
- **Master Node**: `192.168.222.237:6443`
- **SSH Access**:
- TuringPi nodes: `root@<hostname>` (password: `turing`)
- Beelink: `gilgamezh@beelink.lan` (SSH keys)
## 📚 Additional Documentation
- See `CLAUDE.md` for detailed Claude Code integration guide
- Custom Helm charts include their own README files
- Check application-specific `*_values.yaml` files for configuration options
## 🛠️ Development
### Helm Chart Development
```bash
helm create <chart-name>
helm lint <chart-path>
helm template <chart> -f <values> | kubectl apply --dry-run=client -f -
```
### Storage Requirements
- NFS server must be running on turing3.lan
- Applications require ReadWriteMany access for shared media
- Persistent volumes are dynamically provisioned via nfs-subdir-external-provisioner
+4 -20
View File
@@ -1,14 +1,11 @@
# Default values for my-actual-server.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
---
replicaCount: 1
image:
repository: docker.io/actualbudget/actual-server
pullPolicy: IfNotPresent
pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion.
tag: "latest"
tag: "25.3.1"
service:
type: ClusterIP
@@ -16,24 +13,11 @@ service:
ingress:
enabled: false
className: ""
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
kubernetes.io/ingress.class: traefik
hosts:
- host: actual.gilgamezh.me
paths:
- path: /
pathType: ImplementationSpecific
tls:
- hosts:
- actual.gilgamezh.me
secretName: actual-gilgamezh-me
volumes:
- name: "actual-data"
persistentVolumeClaim:
claimName: "actual-data" # PersistentVolumeClaim created earlier
claimName: "actual-data" # PersistentVolumeClaim created earlier
volumeMounts:
- name: "actual-data"
+40
View File
@@ -0,0 +1,40 @@
# Example ArgoCD Application with Image Auto-Update
# This demonstrates how to set up your existing Helm applications in ArgoCD
# with automatic "latest" tag updates
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: plex-example
namespace: argocd
annotations:
# Enable automatic image updates for Plex
argocd-image-updater.argoproj.io/image-list: plex=ghcr.io/k8s-at-home/plex:latest
# Use 'newest-build' strategy for latest images
argocd-image-updater.argoproj.io/plex.update-strategy: newest-build
# Write back to ArgoCD (for testing - production should use git method)
argocd-image-updater.argoproj.io/write-back-method: argocd
spec:
project: default
source:
# Point to your repository (replace with your actual Git repo)
repoURL: https://github.com/munnerz/kube-plex
path: charts/kube-plex
targetRevision: HEAD
helm:
valueFiles:
# This would reference your existing plex_values.yml
# For now, this is just an example structure
- values.yaml
parameters:
- name: image.tag
value: latest
destination:
server: https://kubernetes.default.svc
namespace: plex
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+114
View File
@@ -0,0 +1,114 @@
# ArgoCD Migration Guide for TuringPi Cluster
## ArgoCD Access Information
**Web UI Access:**
- URL: http://192.168.222.25 (LoadBalancer IP)
- Alternative: http://argocd.turing.lan (if you add to your hosts file)
- Username: `admin`
- Password: `fJ3diddVd2yson3W`
## Migration Strategy
Your existing Helm-based applications can be migrated to ArgoCD gradually. Here's how:
### Option 1: Keep Existing Helm + Add GitOps Overlay
1. Keep your current `*_values.yaml` files
2. Create ArgoCD Applications that reference the same charts
3. ArgoCD manages the lifecycle, you keep the familiar structure
### Option 2: Git-First Approach (Recommended for Production)
1. Commit your values files to a Git repository
2. Use ArgoCD's Git source with `argocd-image-updater` writing back to Git
3. Full GitOps workflow with audit trail
## Adding Image Auto-Updates to Your Applications
For any application, add these annotations to the ArgoCD Application manifest:
```yaml
metadata:
annotations:
# Define which images to track
argocd-image-updater.argoproj.io/image-list: myapp=myregistry/myapp:latest
# Use newest-build strategy for "latest" tags
argocd-image-updater.argoproj.io/myapp.update-strategy: newest-build
# Write method: 'argocd' for testing, 'git' for production
argocd-image-updater.argoproj.io/write-back-method: argocd
```
## Example: Converting Your Plex Deployment
Your current command:
```bash
helm upgrade plex kube-plex/charts/kube-plex --values plex_values.yml
```
Becomes this ArgoCD Application:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: plex
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: plex=ghcr.io/k8s-at-home/plex:latest
argocd-image-updater.argoproj.io/plex.update-strategy: newest-build
argocd-image-updater.argoproj.io/write-back-method: argocd
spec:
project: default
source:
repoURL: https://github.com/munnerz/kube-plex # or your fork
path: charts/kube-plex
targetRevision: HEAD
helm:
valueFiles:
- ../../plex_values.yml # Reference your existing values
destination:
server: https://kubernetes.default.svc
namespace: plex
syncPolicy:
automated:
prune: true
selfHeal: true
```
## Quick Start Commands
1. **Access ArgoCD UI**: Visit http://192.168.222.25 with admin/fJ3diddVd2yson3W
2. **Create your first application via CLI**:
```bash
# Install ArgoCD CLI (optional)
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
# Login (using the LoadBalancer IP)
argocd login 192.168.222.25 --insecure --username admin --password fJ3diddVd2yson3W
```
3. **Apply the example application**:
```bash
kubectl apply -f argocd-example-app.yaml
```
## Benefits You Get Immediately
**Keep using Helm** - ArgoCD manages Helm releases
**Auto image updates** - Latest tags update automatically
**Visual UI** - See deployment status, sync state, rollback easily
**GitOps ready** - When you want to commit values to Git
**Rollback capability** - Easy rollback to previous versions
**Multi-environment** - Can manage dev/staging/prod from one place
## Next Steps
1. Access the ArgoCD UI and familiarize yourself with it
2. Create ArgoCD Applications for 1-2 of your existing services
3. Test the image auto-update functionality
4. Once comfortable, migrate more applications
5. Consider setting up a Git repository for full GitOps workflow
Your existing Helm workflow continues to work while you gain GitOps benefits!
+66
View File
@@ -0,0 +1,66 @@
# ArgoCD configuration for TuringPi K3s cluster
# Simplified setup - no RBAC restrictions for single-user environment
global:
# Set domain for your LAN access
domain: argocd.turing.lan
# Server configuration
server:
# Enable ingress for web UI access
ingress:
enabled: true
controller: generic
ingressClassName: nginx
hostname: argocd.turing.lan
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
# Restrict to LAN access (matching your existing pattern)
nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"
# Use LoadBalancer service for direct access via MetalLB
service:
type: LoadBalancer
servicePortHttp: 80
servicePortHttps: 443
# Enable insecure mode since this is a homelab (simpler setup)
extraArgs:
- --insecure
# ApplicationSet controller (for managing multiple apps)
applicationSet:
enabled: true
# Image updater will be installed separately
# This is just the base ArgoCD installation
# Disable HA components for single-node simplicity
redis-ha:
enabled: false
# Use single Redis instance
redis:
enabled: true
# Disable RBAC since you're the only user
rbac:
create: true
# Allow admin access without restrictions
policy.default: role:admin
# No authentication complexity needed for homelab
configs:
secret:
createSecret: true
# Storage for repo data (using your NFS setup)
repoServer:
volumes:
- name: custom-tools
emptyDir: {}
# Monitoring (since you have Prometheus)
prometheus:
enabled: false # Set to true if you want ArgoCD metrics in Prometheus
+159
View File
@@ -0,0 +1,159 @@
# Gitea + ArgoCD Setup Guide
## Gitea Access Information
**Web UI Access:**
- **LoadBalancer URL**: http://192.168.222.27:3000
- **Ingress URL**: http://gitea.turing.lan (add to your hosts file: `192.168.222.27 gitea.turing.lan`)
- **SSH Clone URL**: `git@192.168.222.26:username/repo.git`
**Admin Credentials:**
- **Username**: `admin`
- **Password**: `gitea-admin-pass`
- **Email**: `admin@turing.lan`
## Initial Gitea Setup
1. **Access Gitea**: Visit http://192.168.222.27:3000
2. **Login**: Use admin credentials above
3. **Create Organization**: Create an org for your homelab projects (e.g., "turingpi")
4. **Create Repository**: Create your first repo for ArgoCD manifests
## Setting Up Your First Repository
### Create a Repository for ArgoCD Applications
1. **Create new repo**: `turingpi-argocd-apps`
2. **Clone locally**:
```bash
git clone http://192.168.222.27:3000/admin/turingpi-argocd-apps.git
cd turingpi-argocd-apps
```
3. **Copy your existing values files**:
```bash
# Copy your existing values files to the repo
cp /home/gilgamezh/code/turingpi/*_values.yaml ./helm-values/
mkdir -p apps/
```
4. **Create directory structure**:
```
turingpi-argocd-apps/
├── apps/ # ArgoCD Application manifests
├── helm-values/ # Your existing *_values.yaml files
├── manifests/ # Raw Kubernetes manifests
└── README.md
```
## Migrating Plex to GitOps
### Step 1: Create ArgoCD Application
Create `apps/plex.yaml`:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: plex
namespace: argocd
annotations:
# Enable automatic image updates
argocd-image-updater.argoproj.io/image-list: plex=ghcr.io/k8s-at-home/plex:latest
argocd-image-updater.argoproj.io/plex.update-strategy: newest-build
argocd-image-updater.argoproj.io/write-back-method: git
spec:
project: default
source:
repoURL: http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi-argocd-apps.git
path: helm-values
targetRevision: HEAD
helm:
valueFiles:
- plex_values.yml
destination:
server: https://kubernetes.default.svc
namespace: plex
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
```
### Step 2: Configure ArgoCD to Access Gitea
Add Gitea as a repository in ArgoCD:
1. **Via ArgoCD UI**:
- Go to Settings → Repositories → Connect Repo
- URL: `http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi-argocd-apps.git`
- Username: `admin`
- Password: `gitea-admin-pass`
2. **Via CLI**:
```bash
argocd repo add http://gitea-http.gitea.svc.cluster.local:3000/admin/turingpi-argocd-apps.git \
--username admin --password gitea-admin-pass
```
## Benefits of This Setup
**Version Control**: All your configurations are in Git
**Automatic Updates**: Images update when "latest" tags change
**Audit Trail**: See what changed and when
**Easy Rollbacks**: Git history = deployment history
**Local Control**: No external dependencies
**Team Collaboration**: Others can contribute via Git
## Migration Strategy
1. **Start Small**: Migrate 1-2 applications first
2. **Test Process**: Verify auto-updates work as expected
3. **Bulk Migration**: Move remaining applications
4. **Cleanup**: Remove manual Helm commands once confident
## Git Workflow Examples
### Adding a New Application
```bash
# Create new app manifest
cat > apps/new-app.yaml << EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: new-app
namespace: argocd
spec:
# ... configuration
EOF
# Commit and push
git add apps/new-app.yaml
git commit -m "Add new application: new-app"
git push origin main
```
### Updating Values
```bash
# Edit your values file
vim helm-values/plex_values.yml
# Commit changes
git add helm-values/plex_values.yml
git commit -m "Update Plex CPU limits"
git push origin main
# ArgoCD will auto-sync the changes
```
Your homelab now has enterprise-grade GitOps capabilities while staying completely self-hosted! 🏠✨
## Next Steps
1. **Access Gitea** and create your first repository
2. **Copy your values files** to the new repo
3. **Create your first ArgoCD application** pointing to Gitea
4. **Test the workflow** with a simple change
5. **Migrate more applications** gradually
+117
View File
@@ -0,0 +1,117 @@
# Gitea configuration for TuringPi K3s cluster
# Self-hosted Git server for ArgoCD integration
# Single replica for homelab
replicaCount: 1
# Service configuration - LoadBalancer for direct access
service:
http:
type: LoadBalancer
port: 3000
# MetalLB will assign an IP
ssh:
type: LoadBalancer
port: 22
# For git SSH access
# Ingress for web access
ingress:
enabled: true
className: nginx
pathType: Prefix
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
# Restrict to LAN access (matching your existing pattern)
nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"
hosts:
- host: gitea.turing.lan
paths:
- path: /
pathType: Prefix
# Storage using your NFS setup
persistence:
enabled: true
create: true
storageClass: "nfs-client" # Your existing NFS storage class
size: 20Gi
accessModes:
- ReadWriteOnce
# Database - use PostgreSQL for production-ready setup
postgresql:
enabled: true
auth:
username: gitea
database: gitea
# Password will be auto-generated
primary:
persistence:
enabled: true
storageClass: "nfs-client"
size: 10Gi
# Disable PostgreSQL HA (since we're enabling regular postgresql)
postgresql-ha:
enabled: false
# Disable Valkey cluster (Redis alternative) - not needed for homelab
valkey-cluster:
enabled: false
# Gitea configuration
gitea:
cache:
enabled: false
admin:
username: admin
password: "gitea-admin-pass" # Change this!
email: "admin@turing.lan"
config:
APP_NAME: "TuringPi Gitea"
RUN_MODE: prod
server:
DOMAIN: gitea.turing.lan
SSH_DOMAIN: gitea.turing.lan
ROOT_URL: http://gitea.turing.lan
DISABLE_SSH: false
SSH_PORT: 22
LFS_START_SERVER: true
database:
DB_TYPE: postgres
security:
INSTALL_LOCK: true
service:
DISABLE_REGISTRATION: false # Allow user registration
REQUIRE_SIGNIN_VIEW: false # Allow anonymous viewing of public repos
ui:
DEFAULT_THEME: auto
repository:
DEFAULT_PRIVATE: false # Public repos by default for easier ArgoCD access
# Resource limits (adjust based on your node capacity)
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
# Node affinity (prefer worker nodes, avoid control plane)
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
+23
View File
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
+24
View File
@@ -0,0 +1,24 @@
apiVersion: v2
name: home-assistant-voice-llms
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"
@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "home-assistant-voice-llms.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "home-assistant-voice-llms.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "home-assistant-voice-llms.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "home-assistant-voice-llms.labels" -}}
helm.sh/chart: {{ include "home-assistant-voice-llms.chart" . }}
{{ include "home-assistant-voice-llms.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "home-assistant-voice-llms.selectorLabels" -}}
app.kubernetes.io/name: {{ include "home-assistant-voice-llms.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "home-assistant-voice-llms.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "home-assistant-voice-llms.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
@@ -0,0 +1,97 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-faster-whisper
spec:
replicas: {{ .Values.defaults.replicaCount }}
selector:
matchLabels:
app: faster-whisper
template:
metadata:
labels:
app: faster-whisper
spec:
containers:
- name: faster-whisper
image: {{ .Values.defaults.fasterWhisper.image.repository }}:{{ .Values.defaults.fasterWhisper.image.tag }}
imagePullPolicy: {{ .Values.defaults.fasterWhisper.image.pullPolicy }}
env:
- name: PUID
value: "{{ .Values.defaults.fasterWhisper.env.PUID }}"
- name: PGID
value: "{{ .Values.defaults.fasterWhisper.env.PGID }}"
- name: TZ
value: "{{ .Values.defaults.fasterWhisper.env.TZ }}"
- name: WHISPER_MODEL
value: "{{ .Values.defaults.fasterWhisper.env.WHISPER_MODEL }}"
- name: WHISPER_BEAM
value: "{{ .Values.defaults.fasterWhisper.env.WHISPER_BEAM }}"
- name: WHISPER_LANG
value: "{{ .Values.defaults.fasterWhisper.env.WHISPER_LANG }}"
ports:
- containerPort: 10300
volumeMounts:
- mountPath: {{ .Values.defaults.fasterWhisper.volume.mountPath }}
name: config
{{- if .Values.nodeSelector }}
nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
{{- end }}
volumes:
- name: config
persistentVolumeClaim:
claimName: {{ .Values.defaults.fasterWhisper.volume.claimName }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-piper
spec:
replicas: {{ .Values.defaults.replicaCount }}
selector:
matchLabels:
app: piper
template:
metadata:
labels:
app: piper
spec:
containers:
- name: piper
image: {{ .Values.defaults.piper.image.repository }}:{{ .Values.defaults.piper.image.tag }}
imagePullPolicy: {{ .Values.defaults.piper.image.pullPolicy }}
env:
- name: PUID
value: "{{ .Values.defaults.piper.env.PUID }}"
- name: PGID
value: "{{ .Values.defaults.piper.env.PGID }}"
- name: TZ
value: "{{ .Values.defaults.piper.env.TZ }}"
- name: PIPER_VOICE
value: "{{ .Values.defaults.piper.env.PIPER_VOICE }}"
- name: PIPER_LENGTH
value: "{{ .Values.defaults.piper.env.PIPER_LENGTH }}"
- name: PIPER_NOISE
value: "{{ .Values.defaults.piper.env.PIPER_NOISE }}"
- name: PIPER_NOISEW
value: "{{ .Values.defaults.piper.env.PIPER_NOISEW }}"
- name: PIPER_SPEAKER
value: "{{ .Values.defaults.piper.env.PIPER_SPEAKER }}"
- name: PIPER_PROCS
value: "{{ .Values.defaults.piper.env.PIPER_PROCS }}"
ports:
- containerPort: 10200
volumeMounts:
- mountPath: {{ .Values.defaults.piper.volume.mountPath }}
name: config
volumes:
- name: config
persistentVolumeClaim:
claimName: {{ .Values.defaults.piper.volume.claimName }}
{{- if .Values.nodeSelector }}
nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
{{- end }}
@@ -0,0 +1,25 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Values.defaults.fasterWhisper.volume.claimName }}
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.defaults.fasterWhisper.volume.storage }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Values.defaults.piper.volume.claimName }}
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.defaults.piper.volume.storage }}
@@ -0,0 +1,25 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-faster-whisper
spec:
type: {{ .Values.defaults.fasterWhisper.service.type }}
ports:
- port: {{ .Values.defaults.fasterWhisper.service.port }}
targetPort: 10300
selector:
app: faster-whisper
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-piper
spec:
type: {{ .Values.defaults.piper.service.type }}
ports:
- port: {{ .Values.defaults.piper.service.port }}
targetPort: 10200
selector:
app: piper
+57
View File
@@ -0,0 +1,57 @@
defaults:
replicaCount: 1
fasterWhisper:
image:
repository: lscr.io/linuxserver/faster-whisper
tag: "2.4.0"
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 10300
resources:
limits:
cpu: "3"
memory: "5000Mi"
requests:
cpu: "3"
memory: "2000Mi"
env:
PUID: 1000
PGID: 1000
TZ: Europe/Amsterdam
WHISPER_MODEL: small-int8
WHISPER_BEAM: 1
WHISPER_LANG: en
volume:
mountPath: /config
claimName: faster-whisper-pvc
storage: 1Gi
piper:
image:
repository: lscr.io/linuxserver/piper
tag: "latest"
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 10200
resources:
limits:
cpu: "2"
memory: "4000Mi"
requests:
cpu: "1"
memory: "2000Mi"
env:
PUID: 1000
PGID: 1000
TZ: Europe/Amsterdam
PIPER_VOICE: en_US-lessac-medium
PIPER_LENGTH: 1.0
PIPER_NOISE: 0.667
PIPER_NOISEW: 0.333
PIPER_SPEAKER: 0
PIPER_PROCS: 2
volume:
mountPath: /config
claimName: piper-pvc
storage: 1Gi
+61
View File
@@ -0,0 +1,61 @@
defaults:
replicaCount: 1
fasterWhisper:
image:
repository: lscr.io/linuxserver/faster-whisper
tag: "2.5.0"
pullPolicy: Always
service:
type: LoadBalancer
port: 10300
resources:
limits:
cpu: "3"
memory: "4Gi"
requests:
cpu: "3"
memory: "2Gi"
env:
PUID: 1000
PGID: 1000
TZ: Europe/Amsterdam
WHISPER_MODEL: Zoont/faster-whisper-large-v3-turbo-int8-ct2
WHISPER_BEAM: 1
WHISPER_LANG: en
WHISPER_THREADS value: 4
volume:
mountPath: /config
claimName: faster-whisper-pvc
storage: 1Gi
piper:
image:
repository: lscr.io/linuxserver/piper
tag: "1.5.3"
pullPolicy: Always
service:
type: LoadBalancer
port: 10200
resources:
limits:
cpu: "2"
memory: "4000Mi"
requests:
cpu: "1"
memory: "2000Mi"
env:
PUID: 1000
PGID: 1000
TZ: Europe/Amsterdam
PIPER_VOICE: en_US-lessac-medium
PIPER_LENGTH: 1.0
PIPER_NOISE: 0.667
PIPER_NOISEW: 0.333
PIPER_SPEAKER: 0
PIPER_PROCS: 2
volume:
mountPath: /config
claimName: piper-pvc
storage: 1Gi
nodeSelector:
kubernetes.io/arch: amd64
+3
View File
@@ -0,0 +1,3 @@
master: curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false sh -s - --write-kubeconfig-mode 644 --disable servicelb --token torino --node-ip 192.168.222.237 --disable-cloud-controller --disable local-storage
nodes: curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false K3S_URL=https://192.168.222.237:6443 K3S_TOKEN=torino sh -
-27
View File
@@ -79,30 +79,3 @@ spec:
- hosts:
- sonarr.gilgamezh.me
secretName: sonarr-gilgamezh-me
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
kubernetes.io/ingress.class: traefik
labels:
app: transmission-transmission-openvpn
name: transmission-transmission-openvpn
namespace: default
spec:
rules:
- host: torrent.gilgamezh.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: transmission-transmission-openvpn
port:
number: 9091
tls:
- hosts:
- torrent.gilgamezh.me
secretName: torrent-gilgamezh-me
+25
View File
@@ -0,0 +1,25 @@
---
image:
repository: docker.io/jellyfin/jellyfin
tag: ""
pullPolicy: Always
service:
type: LoadBalancer
port: 8096
persistence:
config:
existingClaim: "plex-config"
media:
existingClaim: "plex-data"
resources:
requests:
memory: "2Gi"
cpu: "3"
ephemeral-storage: "50Mi"
limits:
memory: "6Gi"
cpu: "4"
ephemeral-storage: "1Gi"
+22
View File
@@ -0,0 +1,22 @@
replicaCount: 1
image:
repository: ollama/ollama
pullPolicy: IfNotPresent
tag: "latest"
# Ollama parameters
ollama:
models:
pull:
- TinyLlama
- llama3.1:8b
# Configure Service
service:
# -- Service type
type: LoadBalancer
# -- Service port
port: 11434
nodeSelector:
kubernetes.io/arch: amd64
+11 -6
View File
@@ -2,7 +2,7 @@ claimToken: "claim-Ku2YYmJzDB1mpyG6YD7x"
image:
repository: linuxserver/plex
tag: 1.41.3
tag: 1.41.8
pullPolicy: Always
@@ -19,8 +19,7 @@ rbac:
create: true
nodeSelector:
beta.kubernetes.io/arch: arm64
kubernetes.io/arch: amd64
persistence:
transcode:
@@ -30,14 +29,20 @@ persistence:
config:
claimName: "plex-config"
# GPU support for hardware-accelerated transcoding
gpu:
enabled: true
hostPath: "/dev/dri"
mountPath: "/dev/dri"
resources:
requests:
memory: "2Gi"
cpu: "3"
cpu: "1"
ephemeral-storage: "50Mi"
limits:
memory: "6Gi"
cpu: "4"
memory: "10Gi"
cpu: "3"
ephemeral-storage: "1Gi"
podAnnotations: {}
proxy:
+1 -1
View File
@@ -1,6 +1,6 @@
image:
repository: lscr.io/linuxserver/prowlarr
tag: 1.23.1
tag: 1.37.0.5076-ls121
pullPolicy: Always
env:
+1 -1
View File
@@ -3,7 +3,7 @@ replicaCount: 1
image:
repository: ghcr.io/linuxserver/radarr
tag: 5.10.4
tag: 5.26.2
pullPolicy: Always
env:
+1 -1
View File
@@ -3,7 +3,7 @@ replicaCount: 1
image:
repository: ghcr.io/linuxserver/sonarr
tag: 4.0.9
tag: 4.0.15
pullPolicy: Always
env:
+5 -4
View File
@@ -51,9 +51,10 @@ volumeMounts:
mountPath: "/dev/net/tun" # Needed for VPN
- name: "plex-data"
mountPath: "/etc/openvpn/custom/"
subPath: "airvpn" # Path /mnt/ssd/media/downloads/transmission where transmission downloads Torrents
subPath: "airvpn"
securityContext:
capabilities: # Needed for VPN
add:
- NET_ADMIN
privileged: true
nodeSelector:
kubernetes.io/arch: arm64
Executable
+180
View File
@@ -0,0 +1,180 @@
#!/bin/bash
set -e
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Configuration
MASTER_NODE="turing1"
WORKER_NODES=("turing2" "turing3" "turing4")
BEELINK_NODE="beelink.lan"
MASTER_IP="192.168.222.237"
TOKEN="torino"
SSH_PASSWORD="turing"
# Function to print colored output
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if sshpass is available
check_dependencies() {
if ! command -v sshpass &> /dev/null; then
print_warning "sshpass not found, installing..."
if command -v pacman &> /dev/null; then
sudo pacman -S sshpass --noconfirm
elif command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y sshpass
else
print_error "Cannot install sshpass automatically. Please install it manually."
exit 1
fi
fi
}
# Function to upgrade master node
upgrade_master() {
print_status "Upgrading master node: $MASTER_NODE"
sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no root@$MASTER_NODE '
curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false sh -s - \
--write-kubeconfig-mode 644 \
--disable servicelb \
--token torino \
--node-ip 192.168.222.237 \
--disable-cloud-controller \
--disable local-storage
'
if [ $? -eq 0 ]; then
print_status "Master node $MASTER_NODE upgraded successfully"
else
print_error "Failed to upgrade master node $MASTER_NODE"
exit 1
fi
}
# Function to upgrade worker nodes
upgrade_worker() {
local node=$1
print_status "Upgrading worker node: $node"
sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no root@$node "
curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false \
K3S_URL=https://$MASTER_IP:6443 \
K3S_TOKEN=$TOKEN sh -
"
if [ $? -eq 0 ]; then
print_status "Worker node $node upgraded successfully"
else
print_error "Failed to upgrade worker node $node"
return 1
fi
}
# Function to upgrade beelink node
upgrade_beelink() {
print_status "Upgrading beelink node: $BEELINK_NODE"
ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no gilgamezh@$BEELINK_NODE "
sudo curl -sfL https://get.k3s.io | INSTALL_K3S_SKIP_DOWNLOAD=false \
K3S_URL=https://$MASTER_IP:6443 \
K3S_TOKEN=$TOKEN sh -
"
if [ $? -eq 0 ]; then
print_status "Beelink node $BEELINK_NODE upgraded successfully"
else
print_error "Failed to upgrade beelink node $BEELINK_NODE"
return 1
fi
}
# Function to verify cluster health
verify_cluster() {
print_status "Verifying cluster health..."
# Wait a moment for nodes to register
sleep 10
print_status "Cluster nodes:"
kubectl get nodes
print_status "Checking for unhealthy pods..."
unhealthy_pods=$(kubectl get pods --all-namespaces | grep -v Running | grep -v Completed | wc -l)
if [ "$unhealthy_pods" -gt 1 ]; then # Greater than 1 because header line counts
print_warning "Found unhealthy pods:"
kubectl get pods --all-namespaces | grep -v Running | grep -v Completed
else
print_status "All pods are healthy"
fi
print_status "Cluster upgrade completed successfully!"
}
# Main execution
main() {
print_status "Starting K3s cluster upgrade..."
print_status "This will upgrade all nodes in the TuringPi cluster"
# Check dependencies
check_dependencies
# Show current cluster state
print_status "Current cluster state:"
kubectl get nodes
read -p "Do you want to continue with the upgrade? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_status "Upgrade cancelled"
exit 0
fi
# Upgrade master node first
upgrade_master
# Wait a bit for master to stabilize
sleep 15
# Upgrade worker nodes
failed_workers=0
for worker in "${WORKER_NODES[@]}"; do
if ! upgrade_worker "$worker"; then
((failed_workers++))
fi
sleep 5 # Brief pause between worker upgrades
done
# Upgrade beelink node
if ! upgrade_beelink; then
print_warning "Beelink node upgrade failed, but continuing..."
fi
# Verify cluster health
verify_cluster
if [ $failed_workers -gt 0 ]; then
print_warning "Upgrade completed with $failed_workers failed worker node(s)"
exit 1
else
print_status "All nodes upgraded successfully!"
fi
}
# Run main function
main "$@"