Compare commits

...

7 commits

Author SHA1 Message Date
Viktor Barzin
3bda3ab956
reduce the frequency of polling idrac and remove some duplicates [ci skip] 2026-01-24 18:47:22 +00:00
Viktor Barzin
6928fd29bb
add mcaptcha [ci skip] 2026-01-24 18:46:47 +00:00
Viktor Barzin
0b58abc7b7
add crowdsec rule ot skip my home ip[ci skip] 2026-01-24 18:46:12 +00:00
Viktor Barzin
fe05b1442c
use hcaptcha with nginx [ci skip] 2026-01-24 18:45:41 +00:00
Viktor Barzin
82ae4b411a
add mcaptcha but disabled as we found another way[ci skip] 2026-01-24 18:43:43 +00:00
Viktor Barzin
6e4cfb4c3a
add ollama-api ingress accessible only locally to allow claude code [ci skip] 2026-01-19 20:15:46 +00:00
Viktor Barzin
d8eab79593
preload immich models so they are always fresh [ci skip] 2026-01-19 20:15:09 +00:00
14 changed files with 446 additions and 20 deletions

View file

@ -136,6 +136,9 @@ variable "aiostreams_database_connection_string" { type = string }
variable "actualbudget_credentials" { type = map(any) }
variable "speedtest_db_password" { type = string }
variable "freedify_credentials" { type = map(any) }
variable "mcaptcha_postgresql_password" { type = string }
variable "mcaptcha_cookie_secret" { type = string }
variable "mcaptcha_captcha_salt" { type = string }
provider "kubernetes" {
config_path = var.prod ? "" : "~/.kube/config"
@ -563,6 +566,10 @@ module "kubernetes_cluster" {
speedtest_db_password = var.speedtest_db_password
freedify_credentials = var.freedify_credentials
mcaptcha_postgresql_password = var.mcaptcha_postgresql_password
mcaptcha_cookie_secret = var.mcaptcha_cookie_secret
mcaptcha_captcha_salt = var.mcaptcha_captcha_salt
}

View file

@ -64,6 +64,28 @@ resource "kubernetes_config_map" "crowdsec_custom_scenarios" {
}
}
# Whitelist for trusted IPs that should never be blocked
resource "kubernetes_config_map" "crowdsec_whitelist" {
metadata {
name = "crowdsec-whitelist"
namespace = kubernetes_namespace.crowdsec.metadata[0].name
labels = {
"app.kubernetes.io/name" = "crowdsec"
}
}
data = {
"whitelist.yaml" = <<-YAML
name: crowdsecurity/whitelist-trusted-ips
description: "Whitelist for trusted IPs that should never be blocked"
whitelist:
reason: "Trusted IP - never block"
ip:
- "176.12.22.76"
YAML
}
}
resource "helm_release" "crowdsec" {
namespace = kubernetes_namespace.crowdsec.metadata[0].name

View file

@ -31,10 +31,17 @@ agent:
mountPath: /etc/crowdsec/scenarios/http-429-abuse.yaml
subPath: "http-429-abuse.yaml"
readonly: true
- name: whitelist
mountPath: /etc/crowdsec/parsers/s02-enrich/whitelist.yaml
subPath: "whitelist.yaml"
readonly: true
extraVolumes:
- name: custom-scenarios
configMap:
name: crowdsec-custom-scenarios
- name: whitelist
configMap:
name: crowdsec-whitelist
lapi:
replicas: 3
extraSecrets:
@ -117,6 +124,34 @@ lapi:
type: RollingUpdate
config:
# Custom profiles: captcha for rate limiting, ban for attacks
profiles.yaml: |
# Captcha for rate limiting and 403 abuse (user can unblock themselves)
name: captcha_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() in ["crowdsecurity/http-429-abuse", "crowdsecurity/http-403-abuse", "crowdsecurity/http-crawl-non_statics", "crowdsecurity/http-sensitive-files"]
decisions:
- type: captcha
duration: 4h
on_success: break
---
# Default: Ban for serious attacks (CVE exploits, scanners, brute force)
name: default_ip_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 4h
on_success: break
---
name: default_range_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Range"
decisions:
- type: ban
duration: 4h
on_success: break
config.yaml.local: |
db_config:
type: mysql

View file

@ -1,9 +1,9 @@
variable namespace {}
variable password {}
variable dockerhub_creds_secret_name {
variable "namespace" {}
variable "password" {}
variable "dockerhub_creds_secret_name" {
default = "dockerhub-creds"
}
variable username {
variable "username" {
default = "viktorbarzin"
}

View file

@ -374,7 +374,7 @@ resource "kubernetes_deployment" "immich-machine-learning" {
}
env {
name = "MACHINE_LEARNING_MODEL_TTL"
value = 0
value = "0"
}
env {
name = "TRANSFORMERS_CACHE"
@ -388,10 +388,24 @@ resource "kubernetes_deployment" "immich-machine-learning" {
name = "MPLCONFIGDIR"
value = "/cache/matplotlib-config"
}
# Preload CLIP models (for smart search)
env {
name = "MACHINE_LEARNING_PRELOAD__CLIP"
name = "MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"
value = "ViT-B-16-SigLIP2__webli"
}
env {
name = "MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"
value = "ViT-B-16-SigLIP2__webli"
}
# Preload facial recognition models
env {
name = "MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"
value = "buffalo_l"
}
env {
name = "MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"
value = "buffalo_l"
}
volume_mount {
name = "cache"

View file

@ -115,6 +115,9 @@ variable "aiostreams_database_connection_string" { type = string }
variable "actualbudget_credentials" { type = map(any) }
variable "speedtest_db_password" { type = string }
variable "freedify_credentials" { type = map(any) }
variable "mcaptcha_postgresql_password" { type = string }
variable "mcaptcha_cookie_secret" { type = string }
variable "mcaptcha_captcha_salt" { type = string }
variable "defcon_level" {
@ -140,7 +143,7 @@ locals {
"url", "excalidraw", "travel_blog", "dashy", "send", "ytdlp", "wealthfolio", "rybbit", "stirling-pdf",
"networking-toolbox", "navidrome", "freshrss", "forgejo", "tor-proxy", "real-estate-crawler", "n8n",
"changedetection", "linkwarden", "matrix", "homepage", "meshcentral", "diun", "cyberchef", "ntfy", "ollama",
"servarr", "jsoncrack", "paperless-ngx", "frigate", "audiobookshelf", "tandoor", "ebook2audiobook", "netbox", "speedtest", "resume", "freedify"
"servarr", "jsoncrack", "paperless-ngx", "frigate", "audiobookshelf", "tandoor", "ebook2audiobook", "netbox", "speedtest", "resume", "freedify", "mcaptcha"
],
}
active_modules = distinct(flatten([
@ -332,6 +335,18 @@ module "privatebin" {
depends_on = [null_resource.core_services]
}
# module "mcaptcha" {
# source = "./mcaptcha"
# for_each = contains(local.active_modules, "mcaptcha") ? { mcaptcha = true } : {}
# tls_secret_name = var.tls_secret_name
# tier = local.tiers.edge
# postgresql_password = var.mcaptcha_postgresql_password
# cookie_secret = var.mcaptcha_cookie_secret
# captcha_salt = var.mcaptcha_captcha_salt
# depends_on = [null_resource.core_services]
# }
# module "vault" {
# source = "./vault"
# tier = local.tiers.edge

View file

@ -0,0 +1,309 @@
variable "tls_secret_name" {}
variable "tier" { type = string }
variable "postgresql_password" {}
variable "cookie_secret" {}
variable "captcha_salt" {}
locals {
domain = "mcaptcha.viktorbarzin.me"
port = 7000
}
resource "kubernetes_namespace" "mcaptcha" {
metadata {
name = "mcaptcha"
labels = {
"istio-injection" : "disabled"
}
}
}
module "tls_secret" {
source = "../setup_tls_secret"
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
tls_secret_name = var.tls_secret_name
}
# mCaptcha requires a special Redis with the mcaptcha/cache module loaded
resource "kubernetes_deployment" "mcaptcha_redis" {
metadata {
name = "mcaptcha-redis"
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
labels = {
app = "mcaptcha-redis"
tier = var.tier
}
}
spec {
replicas = 1
selector {
match_labels = {
app = "mcaptcha-redis"
}
}
strategy {
type = "Recreate"
}
template {
metadata {
labels = {
app = "mcaptcha-redis"
}
}
spec {
container {
image = "mcaptcha/cache:latest"
name = "redis"
port {
container_port = 6379
}
resources {
requests = {
memory = "64Mi"
cpu = "25m"
}
limits = {
memory = "128Mi"
cpu = "200m"
}
}
liveness_probe {
tcp_socket {
port = 6379
}
initial_delay_seconds = 10
period_seconds = 10
}
readiness_probe {
tcp_socket {
port = 6379
}
initial_delay_seconds = 5
period_seconds = 5
}
}
}
}
}
}
resource "kubernetes_service" "mcaptcha_redis" {
metadata {
name = "mcaptcha-redis"
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
labels = {
app = "mcaptcha-redis"
}
}
spec {
selector = {
app = "mcaptcha-redis"
}
port {
name = "redis"
port = 6379
target_port = 6379
}
}
}
resource "kubernetes_deployment" "mcaptcha" {
metadata {
name = "mcaptcha"
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
labels = {
app = "mcaptcha"
tier = var.tier
}
annotations = {
"reloader.stakater.com/search" = "true"
}
}
spec {
replicas = 1
selector {
match_labels = {
app = "mcaptcha"
}
}
strategy {
type = "Recreate"
}
template {
metadata {
labels = {
app = "mcaptcha"
}
annotations = {
"diun.enable" = "true"
"diun.include_tags" = "^\\d+(?:\\.\\d+)?(?:\\.\\d+)?$"
}
}
spec {
container {
image = "mcaptcha/mcaptcha:latest"
name = "mcaptcha"
port {
container_port = local.port
}
# Required configuration
env {
name = "MCAPTCHA_server_DOMAIN"
value = local.domain
}
env {
name = "MCAPTCHA_server_COOKIE_SECRET"
value = var.cookie_secret
}
env {
name = "MCAPTCHA_captcha_SALT"
value = var.captcha_salt
}
# Server configuration
env {
name = "PORT"
value = tostring(local.port)
}
env {
name = "MCAPTCHA_server_IP"
value = "0.0.0.0"
}
env {
name = "MCAPTCHA_server_PROXY_HAS_TLS"
value = "true"
}
# Database configuration (PostgreSQL)
env {
name = "DATABASE_URL"
value = "postgres://mcaptcha:${var.postgresql_password}@postgresql.dbaas.svc.cluster.local:5432/mcaptcha"
}
# Redis configuration (using mcaptcha/cache module)
env {
name = "MCAPTCHA_redis_URL"
value = "redis://mcaptcha-redis.mcaptcha.svc.cluster.local:6379"
}
# Feature flags
env {
name = "MCAPTCHA_allow_registration"
# value = "true"
value = "false"
}
env {
name = "MCAPTCHA_allow_demo"
value = "false"
}
env {
name = "MCAPTCHA_commercial"
value = "false"
}
env {
name = "MCAPTCHA_captcha_ENABLE_STATS"
value = "true"
}
env {
name = "MCAPTCHA_captcha_GC"
value = "30"
}
env {
name = "MCAPTCHA_debug"
value = "false"
}
env {
name = "RUST_BACKTRACE"
value = "1"
}
resources {
requests = {
memory = "64Mi"
cpu = "50m"
}
limits = {
memory = "256Mi"
cpu = "500m"
}
}
# Health checks
liveness_probe {
http_get {
path = "/"
port = local.port
}
initial_delay_seconds = 30
period_seconds = 10
timeout_seconds = 5
failure_threshold = 3
}
readiness_probe {
http_get {
path = "/"
port = local.port
}
initial_delay_seconds = 10
period_seconds = 5
timeout_seconds = 3
failure_threshold = 3
}
}
}
}
}
}
resource "kubernetes_service" "mcaptcha" {
metadata {
name = "mcaptcha"
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
labels = {
"app" = "mcaptcha"
}
}
spec {
selector = {
app = "mcaptcha"
}
port {
name = "http"
port = 80
target_port = local.port
}
}
}
module "ingress" {
source = "../ingress_factory"
namespace = kubernetes_namespace.mcaptcha.metadata[0].name
name = "mcaptcha"
tls_secret_name = var.tls_secret_name
}

View file

@ -21,6 +21,14 @@ resource "kubernetes_config_map" "redfish-config" {
password: calvin
metrics:
all: true
# system: true
# sensors: true
# power: true
# sel: false # Disable SEL - often slow
# storage: true # Disable storage - slowest endpoint
# memory: true
# network: false # Disable network adapters
# firmware: false # Don't need this frequently
EOF
}
}
@ -83,11 +91,11 @@ resource "kubernetes_service" "idrac-redfish-exporter" {
labels = {
"app" = "idrac-redfish-exporter"
}
annotations = {
"prometheus.io/scrape" = "true"
"prometheus.io/path" = "/metrics"
"prometheus.io/port" = "9090"
}
# annotations = {
# "prometheus.io/scrape" = "true"
# "prometheus.io/path" = "/metrics"
# "prometheus.io/port" = "9090"
# }
}
spec {

View file

@ -474,6 +474,8 @@ extraScrapeConfigs: |
- "crowdsec-service.crowdsec.svc.cluster.local:6060"
metrics_path: '/metrics'
- job_name: 'snmp-idrac'
scrape_interval: 1m
scrape_timeout: 45s
static_configs:
- targets:
- "idrac.viktorbarzin.lan:161"
@ -492,7 +494,7 @@ extraScrapeConfigs: |
regex: '(.*)'
replacement: 'r730_idrac_$${1}'
- job_name: 'redfish-idrac'
scrape_interval: 1m
scrape_interval: 3m
scrape_timeout: 45s
metrics_path: /metrics
static_configs:

View file

@ -82,11 +82,11 @@ resource "kubernetes_service" "snmp-exporter" {
labels = {
"app" = "snmp-exporter"
}
annotations = {
"prometheus.io/scrape" = "true"
"prometheus.io/path" = "/snmp?auth=Public0&target=tcp%3A%2F%2F192.%3A161"
"prometheus.io/port" = "9116"
}
# annotations = {
# "prometheus.io/scrape" = "true"
# "prometheus.io/path" = "/snmp?auth=Public0&target=tcp%3A%2F%2F192.%3A161"
# "prometheus.io/port" = "9116"
# }
}
spec {

View file

@ -539,7 +539,7 @@ resource "kubernetes_deployment" "ingress_nginx_controller" {
}
env {
name = "CAPTCHA_PROVIDER"
value = "recaptcha"
value = "hcaptcha"
}
env {
name = "BOUNCING_ON_TYPE"

View file

@ -145,7 +145,7 @@ resource "kubernetes_service" "ollama" {
}
}
# Allow ollama to be connected to from external apps
# Allow ollama to be connected to from external apps (internal LAN only)
module "ollama-ingress" {
source = "../ingress_factory"
namespace = kubernetes_namespace.ollama.metadata[0].name
@ -158,6 +158,20 @@ module "ollama-ingress" {
port = 11434
}
# Ollama API ingress for Claude Code access (restricted to LAN/VPN)
module "ollama-api-ingress" {
source = "../ingress_factory"
namespace = kubernetes_namespace.ollama.metadata[0].name
name = "ollama-api"
service_name = "ollama"
root_domain = "viktorbarzin.lan"
tls_secret_name = var.tls_secret_name
allow_local_access_only = true # Restricts to 10.0.0.0/8, 192.168.1.0/24
ssl_redirect = false
port = 11434
proxy_timeout = 300 # Longer timeout for model inference
}
# Web UI
resource "kubernetes_deployment" "ollama-ui" {
metadata {

Binary file not shown.

Binary file not shown.