ingress_factory: replace protected bool with auth enum + audit pass across 100 stacks

Phase 3+4 of default-deny ingress plan. Replaces the `protected = bool` (default
false → unprotected) variable in `modules/kubernetes/ingress_factory` with
`auth = string` enum (default "required" → fail-closed). Touches every
ingress_factory caller so the audit decision is recorded explicitly in code.

ingress_factory (Phase 3):
- `auth = "required"`: standard Authentik forward-auth (the legacy
  `protected = true` semantic).
- `auth = "public"`: forward-auth via the new `authentik-forward-auth-public`
  middleware → dedicated public outpost → guest auto-bind. Logged-in users
  keep their real identity.
- `auth = "none"`: no Authentik middleware. For Anubis-fronted content, native
  client APIs (Git, /v2/, WebDAV), webhook receivers, the Authentik outpost
  itself.
- `effective_anti_ai` default flips ON only when `auth = "none"` (auth-gated
  ingresses don't need anti-AI noise; the auth flow already discourages bots).

Audit pass (Phase 4) across 96 ingress_factory call sites:
- 49 explicit `protected = true`     → `auth = "required"`
- 8 explicit `protected = false`     → `auth = "none"` (5) or `auth = "public"` (3)
- 64 previously-default (no protected line) → `auth = "required"` ADDED, then
  reviewed individually:
  * 9 Anubis-fronted (blog, www, kms, travel, f1, cyberchef, jsoncrack,
    homepage, wrongmove UI, privatebin) → `auth = "none"`
  * 22 native-client / programmatic surfaces (Forgejo Git+/v2/, webhook
    handler, claude-memory MCP, Nextcloud WebDAV, Matrix, Vault CLI/OIDC,
    xray VPN, ntfy, woodpecker webhooks, n8n triggers, ntfy push, dawarich
    location ingestion, immich frame kiosk, headscale CP, send anonymous
    drops, rybbit beacon, vaultwarden API, Authentik UI itself + outposts) →
    `auth = "none"`
  * Remaining ~33 → `auth = "required"` confirmed (admin tools, internal
    UIs, services without app-level auth)
- Smoke-test promotions to `auth = "public"`: fire-planner public UI,
  k8s-portal API, insta2spotify callback.

Three call sites in wrapper modules (`stacks/freedify/factory/`,
`stacks/reverse-proxy/modules/reverse_proxy/`) keep their internal `protected`
bool — they translate to `auth` internally, out of scope for this rename.

Behavior change: previously-default ingresses now fail closed (require
Authentik login) unless explicitly flipped to `auth = "none"` or
`auth = "public"`. This is the audit goal — no more accidentally-unprotected
surfaces. Sites that were intentionally public (Anubis content, native APIs,
webhooks) are now explicitly recorded as `auth = "none"`.

Drive-by: `modules/create-vm/main.tf` picked up cosmetic alignment via
`terraform fmt -recursive` during the audit. Behavior-neutral.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-05-10 18:53:49 +00:00
parent 88e57fdddb
commit ff5538a667
100 changed files with 351 additions and 165 deletions

View file

@ -192,9 +192,9 @@ resource "proxmox_vm_qemu" "cloudinit-vm" {
for_each = var.disk_slot == "scsi0" ? [1] : []
content {
disk {
storage = "local-lvm"
size = var.vm_disk_size
discard = true # Enable TRIM passthrough to LVM thin pool reduces CoW overhead
storage = "local-lvm"
size = var.vm_disk_size
discard = true # Enable TRIM passthrough to LVM thin pool reduces CoW overhead
}
}
}
@ -202,9 +202,9 @@ resource "proxmox_vm_qemu" "cloudinit-vm" {
for_each = var.disk_slot == "scsi1" ? [1] : []
content {
disk {
storage = "local-lvm"
size = var.vm_disk_size
discard = true
storage = "local-lvm"
size = var.vm_disk_size
discard = true
}
}
}

View file

@ -31,9 +31,28 @@ variable "tls_secret_name" {}
variable "backend_protocol" {
default = "HTTP"
}
variable "protected" {
type = bool
default = false
variable "auth" {
type = string
default = "required"
description = <<-EOT
Authentik auth posture for this ingress:
* "required" (default): standard Authentik forward-auth login required.
Catches the legacy `protected = true` semantics.
* "public": public-tier auto-bind anonymous requests to the `guest`
Authentik user (no UI prompt), audited but not gated. Logged-in
users keep their real identity in X-authentik-username.
* "none": no Authentik forward-auth middleware at all. Use for
Anubis-fronted content sites, native-client APIs (Git, /v2/, WebDAV),
webhook receivers, and the Authentik outpost itself. Anti-AI
headers are auto-enabled when auth = "none" unless overridden.
Defaulting to "required" enforces "every ingress must have an explicit
auth decision recorded by Authentik" — accidental omission fails closed.
EOT
validation {
condition = contains(["required", "public", "none"], var.auth)
error_message = "auth must be one of: required, public, none."
}
}
variable "ingress_path" {
type = list(string)
@ -142,8 +161,19 @@ variable "homepage_enabled" {
}
locals {
effective_host = var.full_host != null ? var.full_host : "${var.host != null ? var.host : var.name}.${var.root_domain}"
effective_anti_ai = var.anti_ai_scraping != null ? var.anti_ai_scraping : !var.protected
effective_host = var.full_host != null ? var.full_host : "${var.host != null ? var.host : var.name}.${var.root_domain}"
# Anti-AI default: ON only when no Authentik auth is in front of the ingress
# (i.e. auth = "none" public Anubis-fronted content sites, etc.). When
# Authentik gates the request (required/public), the auth flow already
# discourages bots, so anti-AI noise is redundant.
effective_anti_ai = var.anti_ai_scraping != null ? var.anti_ai_scraping : (var.auth == "none")
# Auth middleware selection. "none" attaches no Authentik middleware at all.
auth_middleware = (
var.auth == "required" ? "traefik-authentik-forward-auth@kubernetescrd" :
var.auth == "public" ? "traefik-authentik-forward-auth-public@kubernetescrd" :
null
)
# External monitor enabled by default when the ingress has a public DNS
# record (either CF-proxied or direct A/AAAA). Explicit bool overrides.
@ -254,7 +284,7 @@ resource "kubernetes_ingress_v1" "proxied-ingress" {
var.exclude_crowdsec ? null : "traefik-crowdsec@kubernetescrd",
local.effective_anti_ai ? "traefik-ai-bot-block@kubernetescrd" : null,
local.effective_anti_ai ? "traefik-anti-ai-headers@kubernetescrd" : null,
var.protected ? "traefik-authentik-forward-auth@kubernetescrd" : null,
local.auth_middleware,
var.allow_local_access_only ? "traefik-local-only@kubernetescrd" : null,
var.custom_content_security_policy != null ? "${var.namespace}-custom-csp-${var.name}@kubernetescrd" : null,
var.max_body_size != null ? "${var.namespace}-buffering-${var.name}@kubernetescrd" : null,

View file

@ -87,5 +87,5 @@ module "ingress" {
name = "<app-name>"
tls_secret_name = var.tls_secret_name
dns_type = "proxied" # "proxied" (Cloudflare CDN), "non-proxied" (direct A/AAAA), or "none"
protected = false # Set true to require Authentik login
auth = "required" # "required" (Authentik login), "public" (anonymous bound to guest), or "none" (no auth)
}

View file

@ -149,6 +149,7 @@ resource "kubernetes_service" "actualbudget" {
module "ingress" {
source = "../../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = "actualbudget"
name = "budget-${var.name}"
tls_secret_name = var.tls_secret_name

View file

@ -352,6 +352,7 @@ resource "kubernetes_service" "affine" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "non-proxied"
namespace = kubernetes_namespace.affine.metadata[0].name
name = "affine"

View file

@ -101,8 +101,8 @@ resource "authentik_flow_stage_binding" "public_login" {
# request.context, so the expression policy's mutation would no-op. With
# evaluate_on_plan=false + re_evaluate_policies=true, the policy fires
# right before the stage runs, when flow_plan is fully populated.
evaluate_on_plan = false
re_evaluate_policies = true
evaluate_on_plan = false
re_evaluate_policies = true
}
resource "authentik_policy_binding" "set_guest_before_login" {
@ -198,7 +198,10 @@ resource "authentik_outpost" "public" {
# `Provider for Public` external_host points here, so all redirect_uris in
# the OAuth flow resolve to this hostname.
module "ingress_public_outpost" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Public-tier outpost callback the OAuth flow's redirect_uris all resolve
# here; gating it with forward-auth would loop the public outpost onto itself.
auth = "none"
namespace = "authentik"
name = "public-outpost"
host = "public-auth"

View file

@ -70,8 +70,11 @@ resource "helm_release" "authentik" {
module "ingress" {
source = "../../../../modules/kubernetes/ingress_factory"
dns_type = "proxied"
source = "../../../../modules/kubernetes/ingress_factory"
# Authentik's own UI cannot be gated by Authentik forward-auth that
# creates a chicken-and-egg loop (users can't reach the login page).
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.authentik.metadata[0].name
name = "authentik"
service_name = "goauthentik-server"
@ -91,7 +94,10 @@ module "ingress" {
}
module "ingress-outpost" {
source = "../../../../modules/kubernetes/ingress_factory"
source = "../../../../modules/kubernetes/ingress_factory"
# Authentik forward-auth outpost callback path protecting this with
# forward-auth would loop the outpost back onto itself.
auth = "none"
namespace = kubernetes_namespace.authentik.metadata[0].name
name = "authentik-outpost"
host = "authentik"

View file

@ -416,7 +416,7 @@ module "ingress" {
namespace = kubernetes_namespace.beads.metadata[0].name
name = "dolt-workbench"
tls_secret_name = var.tls_secret_name
protected = false
auth = "none"
exclude_crowdsec = true
extra_annotations = {
"gethomepage.dev/enabled" = "true"
@ -566,7 +566,7 @@ resource "kubernetes_deployment" "beadboard" {
}
container {
name = "beadboard"
name = "beadboard"
# Phase 3 cutover 2026-05-07 Forgejo registry consolidation.
image = "forgejo.viktorbarzin.me/viktor/beadboard:${var.beadboard_image_tag}"
@ -677,7 +677,7 @@ module "beadboard_ingress" {
namespace = kubernetes_namespace.beads.metadata[0].name
name = "beadboard"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
exclude_crowdsec = true
extra_annotations = {
"gethomepage.dev/enabled" = "true"

View file

@ -124,15 +124,16 @@ module "anubis" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
namespace = kubernetes_namespace.website.metadata[0].name
name = "blog"
service_name = module.anubis.service_name
port = module.anubis.service_port
extra_middlewares = ["traefik-x402@kubernetescrd"]
full_host = "viktorbarzin.me"
dns_type = "proxied"
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false # Anubis is the gatekeeper now drop the redundant ai-bot-block forwardAuth.
full_host = "viktorbarzin.me"
dns_type = "proxied"
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false # Anubis is the gatekeeper now drop the redundant ai-bot-block forwardAuth.
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Blog"
@ -145,12 +146,13 @@ module "ingress" {
module "ingress-www" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
namespace = kubernetes_namespace.website.metadata[0].name
name = "blog-www"
service_name = module.anubis.service_name
port = module.anubis.service_port
extra_middlewares = ["traefik-x402@kubernetescrd"]
full_host = "www.viktorbarzin.me"
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
full_host = "www.viktorbarzin.me"
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
}

View file

@ -218,7 +218,7 @@ module "ingress" {
namespace = kubernetes_namespace.changedetection.metadata[0].name
name = "changedetection"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Changedetection"

View file

@ -354,7 +354,7 @@ module "ingress" {
namespace = kubernetes_namespace.chrome_service.metadata[0].name
name = "chrome"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
# noVNC defaults to /vnc.html auto-redirect / there.
ingress_path = ["/"]
extra_annotations = {

View file

@ -99,7 +99,7 @@ module "ingress" {
namespace = "city-guesser"
name = "city-guesser"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "City Guesser"

View file

@ -274,7 +274,10 @@ resource "kubernetes_service" "claude-memory" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# MCP server called by Claude Code (and other tools/agents) via app-layer
# bearer-token auth; forward-auth would break programmatic clients.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.claude-memory.metadata[0].name
name = "claude-memory"

View file

@ -282,7 +282,7 @@ module "ingress" {
dns_type = "proxied"
namespace = kubernetes_namespace.crowdsec.metadata[0].name
name = "crowdsec-web"
protected = true
auth = "required"
tls_secret_name = var.tls_secret_name
exclude_crowdsec = true
}

View file

@ -113,14 +113,15 @@ module "anubis" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
dns_type = "proxied"
namespace = kubernetes_namespace.cyberchef.metadata[0].name
name = "cc"
service_name = module.anubis.service_name
port = module.anubis.service_port
extra_middlewares = ["traefik-x402@kubernetescrd"]
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "CyberChef"

View file

@ -132,5 +132,5 @@ module "ingress" {
namespace = kubernetes_namespace.dashy.metadata[0].name
name = "dashy"
tls_secret_name = var.tls_secret_name
protected = true # hidden as we use homepage now
auth = "required" # hidden as we use homepage now
}

View file

@ -432,7 +432,12 @@ resource "kubernetes_service" "dawarich" {
# }
# }
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# owntracks bridge hook posts to /api/v1/owntracks/points?api_key=... from
# outside the cluster; mobile location apps also POST programmatically with
# an api_key. Forward-auth would 302 these clients into a login they can't
# complete. Dawarich enforces api_key at app layer.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.dawarich.metadata[0].name
name = "dawarich"

View file

@ -791,7 +791,7 @@ module "ingress" {
namespace = kubernetes_namespace.dbaas.metadata[0].name
name = "pma"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {}
}
@ -1461,7 +1461,7 @@ module "ingress-pgadmin" {
namespace = kubernetes_namespace.dbaas.metadata[0].name
name = "pgadmin"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
}

View file

@ -254,7 +254,7 @@ module "ingress" {
namespace = kubernetes_namespace.ebook2audiobook.metadata[0].name
name = "ebook2audiobook"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Ebook2Audiobook"
@ -445,7 +445,7 @@ module "audiblez-web-ingress" {
host = "audiblez"
dns_type = "non-proxied"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
max_body_size = "500m" # Allow large EPUB uploads
extra_annotations = {
"gethomepage.dev/enabled" = "true"

View file

@ -378,6 +378,7 @@ resource "kubernetes_service" "calibre" {
module "calibre_ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.ebooks.metadata[0].name
name = "calibre"
@ -502,7 +503,7 @@ module "stacks_ingress" {
name = "stacks"
service_name = "annas-archive-stacks"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "false"
}
@ -647,6 +648,7 @@ resource "kubernetes_service" "audiobookshelf" {
module "audiobookshelf_ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "non-proxied"
namespace = kubernetes_namespace.ebooks.metadata[0].name
name = "audiobookshelf"
@ -921,7 +923,7 @@ module "book_search_ingress" {
namespace = kubernetes_namespace.ebooks.metadata[0].name
name = "book-search"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Book Search"
@ -940,6 +942,6 @@ module "book_search_api_ingress" {
host = "book-search"
service_name = "book-search"
tls_secret_name = var.tls_secret_name
protected = false
auth = "none"
ingress_path = ["/api/download-url", "/api/download-status", "/api/send-to-kindle", "/shortcut"]
}

View file

@ -102,6 +102,7 @@ resource "kubernetes_service" "echo" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.echo.metadata[0].name
name = "echo"

View file

@ -149,7 +149,7 @@ module "ingress" {
namespace = kubernetes_namespace.excalidraw.metadata[0].name
name = "draw"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Excalidraw"

View file

@ -237,10 +237,10 @@ module "tls_secret" {
# (which load before any user has a chance to solve PoW), CHALLENGE
# everything else the HTML pages.
module "anubis" {
source = "../../modules/kubernetes/anubis_instance"
name = "f1"
namespace = kubernetes_namespace.f1-stream.metadata[0].name
target_url = "http://${kubernetes_service.f1-stream.metadata[0].name}.${kubernetes_namespace.f1-stream.metadata[0].name}.svc.cluster.local"
source = "../../modules/kubernetes/anubis_instance"
name = "f1"
namespace = kubernetes_namespace.f1-stream.metadata[0].name
target_url = "http://${kubernetes_service.f1-stream.metadata[0].name}.${kubernetes_namespace.f1-stream.metadata[0].name}.svc.cluster.local"
policy_yaml = <<-EOT
bots:
- import: (data)/bots/_deny-pathological.yaml
@ -275,6 +275,7 @@ module "anubis" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
dns_type = "non-proxied"
namespace = kubernetes_namespace.f1-stream.metadata[0].name
name = "f1"

View file

@ -432,7 +432,7 @@ module "ingress" {
name = "fire-planner"
port = 8080
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "FIRE Planner"
@ -459,7 +459,7 @@ module "ingress_api" {
port = 8080
ingress_path = ["/api/"]
tls_secret_name = var.tls_secret_name
protected = false
auth = "public"
}
# Plan-time read of the ESO-created K8s Secret for Grafana datasource

View file

@ -65,7 +65,7 @@ module "ingress" {
namespace = kubernetes_namespace.foolery.metadata[0].name
name = "foolery"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Foolery"

View file

@ -194,7 +194,11 @@ resource "kubernetes_service" "forgejo" {
}
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Git + OCI registry (/v2/) native clients (git, docker/podman) use HTTP
# basic-auth / bearer tokens, NOT browser sessions. Forward-auth would 302
# them into a redirect they can't follow.
auth = "none"
dns_type = "non-proxied"
namespace = kubernetes_namespace.forgejo.metadata[0].name
name = "forgejo"

View file

@ -225,7 +225,7 @@ module "ingress" {
name = "music-${var.name}"
tls_secret_name = var.tls_secret_name
dns_type = "non-proxied"
protected = var.protected
auth = var.protected ? "required" : "none"
extra_annotations = var.extra_annotations
}
@ -235,9 +235,9 @@ resource "kubernetes_ingress_v1" "stream-noauth" {
name = "music-${var.name}-stream"
namespace = "freedify"
annotations = {
"traefik.ingress.kubernetes.io/router.middlewares" = "traefik-retry@kubernetescrd,traefik-rate-limit@kubernetescrd"
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
"traefik.ingress.kubernetes.io/router.priority" = "100"
"traefik.ingress.kubernetes.io/router.middlewares" = "traefik-retry@kubernetescrd,traefik-rate-limit@kubernetescrd"
"traefik.ingress.kubernetes.io/router.entrypoints" = "websecure"
"traefik.ingress.kubernetes.io/router.priority" = "100"
}
}
spec {

View file

@ -98,14 +98,14 @@ module "viktor" {
# https://music-emo.viktorbarzin.me/
module "emo" {
source = "./factory"
name = "emo"
tag = "latest"
tls_secret_name = var.tls_secret_name
depends_on = [kubernetes_namespace.freedify]
tier = local.tiers.aux
protected = true
genius_token = lookup(local.credentials["emo"], "genius_token", null)
source = "./factory"
name = "emo"
tag = "latest"
tls_secret_name = var.tls_secret_name
depends_on = [kubernetes_namespace.freedify]
tier = local.tiers.aux
protected = true
genius_token = lookup(local.credentials["emo"], "genius_token", null)
gemini_api_key = lookup(local.credentials["emo"], "gemini_api_key", null)
navidrome_scan_url = data.kubernetes_secret.eso_secrets.data["navidrome_scan_url"]
ha_sofia_url = lookup(data.kubernetes_secret.eso_secrets.data, "ha_sofia_url", "")

View file

@ -215,6 +215,7 @@ resource "kubernetes_service" "freshrss" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = "freshrss"
name = "rss"

View file

@ -288,7 +288,7 @@ module "ingress" {
namespace = kubernetes_namespace.frigate.metadata[0].name
name = "frigate"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Frigate"
@ -303,6 +303,7 @@ module "ingress" {
module "ingress-internal" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.frigate.metadata[0].name
name = "frigate-lan"
host = "frigate-lan"

View file

@ -360,7 +360,7 @@ module "ingress" {
service_name = "grampsweb"
tls_secret_name = var.tls_secret_name
max_body_size = "500m"
protected = true
auth = "required"
external_monitor = false
extra_annotations = {
"gethomepage.dev/enabled" = "true"

View file

@ -191,6 +191,7 @@ resource "kubernetes_service" "hackmd" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.hackmd.metadata[0].name
name = "hackmd"

View file

@ -11,14 +11,14 @@ locals {
}
module "headscale" {
source = "./modules/headscale"
tls_secret_name = var.tls_secret_name
nfs_server = var.nfs_server
headscale_config = data.vault_kv_secret_v2.secrets.data["headscale_config"]
headscale_acl = data.vault_kv_secret_v2.secrets.data["headscale_acl"]
headscale_derp_map = data.vault_kv_secret_v2.secrets.data["headscale_derp_map"]
homepage_token = try(local.homepage_credentials["headscale"]["api_key"], "")
tier = local.tiers.core
ui_cookie_secret = data.vault_kv_secret_v2.secrets.data["headscale_ui_cookie_secret"]
ui_api_key = data.vault_kv_secret_v2.secrets.data["headscale_ui_api_key"]
source = "./modules/headscale"
tls_secret_name = var.tls_secret_name
nfs_server = var.nfs_server
headscale_config = data.vault_kv_secret_v2.secrets.data["headscale_config"]
headscale_acl = data.vault_kv_secret_v2.secrets.data["headscale_acl"]
headscale_derp_map = data.vault_kv_secret_v2.secrets.data["headscale_derp_map"]
homepage_token = try(local.homepage_credentials["headscale"]["api_key"], "")
tier = local.tiers.core
ui_cookie_secret = data.vault_kv_secret_v2.secrets.data["headscale_ui_cookie_secret"]
ui_api_key = data.vault_kv_secret_v2.secrets.data["headscale_ui_api_key"]
}

View file

@ -298,7 +298,13 @@ resource "kubernetes_service" "headscale" {
}
module "ingress" {
source = "../../../../modules/kubernetes/ingress_factory"
source = "../../../../modules/kubernetes/ingress_factory"
# Headscale is the Tailscale control plane native Tailscale clients
# register, exchange keys, and pull DERP maps from headscale.viktorbarzin.me.
# Forward-auth would break every Tailscale client. Headscale has its own
# OIDC + preauth-key auth at the app layer; the web admin UI lives on a
# separate /web ingress that remains auth=required.
auth = "none"
dns_type = "non-proxied"
namespace = kubernetes_namespace.headscale.metadata[0].name
name = "headscale"
@ -354,6 +360,7 @@ resource "kubernetes_manifest" "derp_ingress_route" {
module "ingress-ui" {
source = "../../../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.headscale.metadata[0].name
name = "headscale-ui"
host = "headscale"

View file

@ -174,6 +174,7 @@ resource "kubernetes_service" "health" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "non-proxied"
namespace = kubernetes_namespace.health.metadata[0].name
name = "health"

View file

@ -111,9 +111,9 @@ resource "kubernetes_config_map" "hermes_config" {
# Context compression
compression = {
enabled = true
threshold = 0.50
target_ratio = 0.20
enabled = true
threshold = 0.50
target_ratio = 0.20
protect_last_n = 20
}
@ -411,7 +411,7 @@ module "ingress" {
namespace = kubernetes_namespace.hermes_agent.metadata[0].name
name = "hermes-agent"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Hermes Agent"

View file

@ -146,6 +146,7 @@ module "anubis" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
namespace = kubernetes_namespace.homepage.metadata[0].name
name = "homepage"
host = "home"
@ -153,8 +154,8 @@ module "ingress" {
service_name = module.anubis.service_name
port = module.anubis.service_port
extra_middlewares = ["traefik-x402@kubernetescrd"]
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Homepage"

View file

@ -123,7 +123,11 @@ resource "kubernetes_service" "immich-frame" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Photo-frame kiosk display runs in headless browser mode on a TV/frame
# device and pulls images via an Immich API key (no user login). Forward-auth
# would 302 the device to Authentik with no way to complete login.
auth = "none"
dns_type = "proxied"
namespace = "immich"
name = "highlights-immich"

View file

@ -731,6 +731,7 @@ resource "kubernetes_service" "immich-machine-learning" {
module "ingress-immich" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "non-proxied"
namespace = kubernetes_namespace.immich.metadata[0].name
name = "immich"
@ -954,5 +955,5 @@ resource "kubernetes_cron_job_v1" "postgresql-backup" {
# namespace = kubernetes_namespace.immich.metadata[0].name
# name = "immich-powertools"
# tls_secret_name = var.tls_secret_name
# protected = true
# auth = "required"
# }

View file

@ -236,7 +236,7 @@ module "ingress" {
namespace = kubernetes_namespace.insta2spotify.metadata[0].name
name = "insta2spotify"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
max_body_size = "50m"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
@ -256,7 +256,7 @@ module "ingress_api" {
host = "insta2spotify"
service_name = "insta2spotify"
tls_secret_name = var.tls_secret_name
protected = false
auth = "public"
ingress_path = ["/api/identify", "/api/auth", "/api/health", "/api/history"]
max_body_size = "50m"
}

View file

@ -93,14 +93,15 @@ module "anubis" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
dns_type = "proxied"
namespace = kubernetes_namespace.jsoncrack.metadata[0].name
name = "json"
service_name = module.anubis.service_name
port = module.anubis.service_port
extra_middlewares = ["traefik-x402@kubernetescrd"]
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "JSON Crack"

View file

@ -96,7 +96,7 @@ module "ingress" {
host = "k8s"
dns_type = "proxied"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
backend_protocol = "HTTPS"
port = 443
extra_annotations = {

View file

@ -148,7 +148,7 @@ module "ingress" {
namespace = kubernetes_namespace.k8s_portal.metadata[0].name
name = "k8s-portal"
tls_secret_name = var.tls_secret_name
protected = true # Require Authentik login
auth = "required" # Require Authentik login
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "K8s Portal"
@ -168,5 +168,5 @@ module "ingress_setup_script" {
service_name = "k8s-portal"
ingress_path = ["/setup/script", "/agent"]
tls_secret_name = var.tls_secret_name
protected = false
auth = "public"
}

View file

@ -112,14 +112,15 @@ module "anubis" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
dns_type = "non-proxied"
namespace = kubernetes_namespace.kms.metadata[0].name
name = "kms"
service_name = module.anubis.service_name
port = module.anubis.service_port
extra_middlewares = ["traefik-x402@kubernetescrd"]
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "KMS"

View file

@ -229,6 +229,7 @@ resource "kubernetes_service" "linkwarden" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.linkwarden.metadata[0].name
name = "linkwarden"

View file

@ -267,7 +267,7 @@ module "ingress" {
name = "mail"
service_name = "roundcubemail"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Roundcube Mail"

View file

@ -224,7 +224,12 @@ resource "kubernetes_service" "matrix" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Matrix homeserver both client-server (/_matrix/client) and
# server-server (/_matrix/federation) APIs use bearer tokens / signed
# requests, not browser sessions. Forward-auth would break federation
# and all native Matrix clients.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.matrix.metadata[0].name
name = "matrix"

View file

@ -271,7 +271,7 @@ module "ingress" {
name = "meshcentral"
tls_secret_name = var.tls_secret_name
port = 80
protected = true
auth = "required"
anti_ai_scraping = false
extra_annotations = {
"gethomepage.dev/enabled" = "true"

View file

@ -125,6 +125,7 @@ resource "kubernetes_service" "idrac-redfish-exporter" {
module "idrac-redfish-exporter-ingress" {
source = "../../../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.monitoring.metadata[0].name
name = "idrac-redfish-exporter"
root_domain = "viktorbarzin.lan"

View file

@ -1874,6 +1874,35 @@ serverFiles:
severity: warning
annotations:
summary: "ResourceQuota {{ $labels.namespace }}/{{ $labels.resourcequota }} {{ $labels.resource }} at {{ $value | printf \"%.1f\" }} — workloads may fail to reschedule"
# K8sVersionSkew: kubelet on any node disagrees with the apiserver's gitVersion.
# Catches a half-done kubeadm rollout — e.g. master at 1.34.5 but a worker
# still on 1.34.2 after the agent aborted mid-flight. Distinct gitVersion
# count >1 across kubernetes-nodes + kubernetes-apiservers means skew exists.
# 30m for: gives a normal rolling upgrade (master + 4 workers + 10-min soaks
# ≈ 60-90 min) room to be in mid-progress without firing during a healthy
# run — but only because Prometheus only counts a node post-restart, and the
# agent's soak between workers exceeds 10min anyway.
- alert: K8sVersionSkew
expr: count(count by (git_version) (kubernetes_build_info{job=~"kubernetes-nodes|kubernetes-apiservers"})) > 1
for: 30m
labels:
severity: warning
annotations:
summary: "Kubelet/apiserver gitVersion skew detected — possible half-done k8s upgrade. Inspect: kubectl get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}'"
# EtcdPreUpgradeSnapshotMissing: the k8s-version-upgrade agent pushes
# k8s_upgrade_in_flight=1 when it starts, and k8s_upgrade_snapshot_taken=1
# after the etcdctl snapshot is verified. If we see in_flight=1 with no
# corresponding snapshot_taken=1 after 10 min, the agent has skipped or
# failed the snapshot — that's a critical safety hole.
- alert: EtcdPreUpgradeSnapshotMissing
expr: |
k8s_upgrade_in_flight == 1
unless on() k8s_upgrade_snapshot_taken == 1
for: 10m
labels:
severity: critical
annotations:
summary: "K8s upgrade is in flight but no etcd snapshot was recorded — pipeline pre-flight failed silently"
- name: "Traefik Ingress"
rules:
- alert: TraefikDown

View file

@ -124,6 +124,7 @@ resource "kubernetes_service" "snmp-exporter" {
module "snmp-exporter-ingress" {
source = "../../../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.monitoring.metadata[0].name
name = "snmp-exporter"
root_domain = "viktorbarzin.lan"

View file

@ -386,7 +386,11 @@ resource "kubernetes_service" "n8n" {
}
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# n8n hosts webhook endpoints at /webhook/... (WEBHOOK_URL points here);
# external services POST to trigger workflows. Forward-auth would block
# every webhook trigger. n8n has its own user login + per-webhook auth.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.n8n.metadata[0].name
name = "n8n"

View file

@ -229,6 +229,7 @@ resource "kubernetes_service" "navidrome" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.navidrome.metadata[0].name
name = "navidrome"

View file

@ -232,7 +232,7 @@ module "ingress" {
namespace = kubernetes_namespace.netbox.metadata[0].name
name = "netbox"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Netbox"

View file

@ -103,7 +103,7 @@ module "ingress" {
namespace = kubernetes_namespace.networking-toolbox.metadata[0].name
name = "networking-toolbox"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Networking Toolbox"

View file

@ -219,7 +219,11 @@ module "nfs_nextcloud_backup_host" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Native WebDAV / CalDAV / CardDAV clients (Nextcloud desktop+mobile apps,
# calendar sync) use HTTP basic-auth + app passwords, not browser sessions.
# Nextcloud has strong app-layer auth of its own.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
name = "nextcloud"

View file

@ -217,6 +217,7 @@ resource "kubernetes_service" "novelapp" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "non-proxied"
namespace = kubernetes_namespace.novelapp.metadata[0].name
name = "novelapp"

View file

@ -188,7 +188,11 @@ resource "kubernetes_service" "ntfy" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# ntfy mobile/desktop apps + publisher scripts use HTTP basic-auth / bearer
# tokens against ntfy's own user.db (NTFY_AUTH_DEFAULT_ACCESS=deny-all).
# Forward-auth would block subscribers and publishers alike.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.ntfy.metadata[0].name
name = "ntfy"

View file

@ -218,6 +218,7 @@ resource "kubernetes_service" "nvidia-exporter" {
module "ingress" {
source = "../../../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.nvidia.metadata[0].name
name = "nvidia-exporter"
root_domain = "viktorbarzin.lan"

View file

@ -250,6 +250,7 @@ resource "kubernetes_service" "onlyoffice" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.onlyoffice.metadata[0].name
name = "onlyoffice"

View file

@ -404,8 +404,8 @@ resource "kubernetes_deployment" "openclaw" {
# this the previously-baked kubeconfig retains a SA token bound to a
# long-dead pod and kubectl returns "must be logged in to the server".
init_container {
name = "setup-kubeconfig"
image = "busybox:1.37"
name = "setup-kubeconfig"
image = "busybox:1.37"
command = ["sh", "-c", <<-EOT
cat > /home/node/.openclaw/kubeconfig <<'KUBECONFIG_EOF'
apiVersion: v1
@ -444,8 +444,8 @@ resource "kubernetes_deployment" "openclaw" {
# Main container: OpenClaw
container {
name = "openclaw"
image = "ghcr.io/openclaw/openclaw:2026.5.4"
name = "openclaw"
image = "ghcr.io/openclaw/openclaw:2026.5.4"
# Doctor --fix auto-promotes the highest-tier codex model (gpt-5-pro) after
# auth-profile-based model discovery; pin gpt-5.4-mini back to default after it.
command = ["sh", "-c", "node openclaw.mjs doctor --fix 2>/dev/null; node openclaw.mjs models set openai-codex/gpt-5.4-mini 2>/dev/null; exec node openclaw.mjs gateway --allow-unconfigured --bind lan"]
@ -752,7 +752,7 @@ module "ingress" {
name = "openclaw"
tls_secret_name = var.tls_secret_name
port = 80
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "OpenClaw"
@ -952,6 +952,7 @@ resource "kubernetes_service" "task_webhook" {
module "task_webhook_ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.openclaw.metadata[0].name
name = "task-webhook"
tls_secret_name = var.tls_secret_name
@ -1275,5 +1276,5 @@ module "openlobster_ingress" {
tls_secret_name = var.tls_secret_name
host = "openlobster"
port = 80
protected = true
auth = "required"
}

View file

@ -254,6 +254,7 @@ resource "kubernetes_service" "paperless-ngx" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.paperless-ngx.metadata[0].name
name = "paperless-ngx"
service_name = "paperless-ngx"

View file

@ -234,7 +234,7 @@ module "ingress" {
namespace = kubernetes_namespace.phpipam.metadata[0].name
name = "phpipam"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "phpIPAM"

View file

@ -198,6 +198,7 @@ resource "kubernetes_service" "plotting-book" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "non-proxied"
namespace = kubernetes_namespace.plotting-book.metadata[0].name
name = "plotting-book"

View file

@ -210,6 +210,7 @@ resource "kubernetes_service" "poison_fountain" {
# Deliberately NO rate limiting, NO CrowdSec, NO anti-AI (we WANT scrapers here)
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.poison_fountain.metadata[0].name
name = "poison-fountain"
host = "poison"

View file

@ -136,19 +136,19 @@ resource "helm_release" "postiz" {
# Non-secret env. Note: BACKEND_INTERNAL_URL stays in-pod (Postiz convention).
env = {
MAIN_URL = "https://postiz.viktorbarzin.me"
FRONTEND_URL = "https://postiz.viktorbarzin.me"
NEXT_PUBLIC_BACKEND_URL = "https://postiz.viktorbarzin.me/api"
BACKEND_INTERNAL_URL = "http://localhost:3000"
STORAGE_PROVIDER = "local"
UPLOAD_DIRECTORY = "/uploads"
NEXT_PUBLIC_UPLOAD_DIRECTORY = "/uploads"
MAIN_URL = "https://postiz.viktorbarzin.me"
FRONTEND_URL = "https://postiz.viktorbarzin.me"
NEXT_PUBLIC_BACKEND_URL = "https://postiz.viktorbarzin.me/api"
BACKEND_INTERNAL_URL = "http://localhost:3000"
STORAGE_PROVIDER = "local"
UPLOAD_DIRECTORY = "/uploads"
NEXT_PUBLIC_UPLOAD_DIRECTORY = "/uploads"
# Disabled admin user already created; sign-in only.
DISABLE_REGISTRATION = "true"
IS_GENERAL = "true"
NX_ADD_PLUGINS = "false"
DISABLE_REGISTRATION = "true"
IS_GENERAL = "true"
NX_ADD_PLUGINS = "false"
# Postiz uses Temporal for cron/scheduling bring our own; Helm chart doesn't.
TEMPORAL_ADDRESS = "temporal:7233"
TEMPORAL_ADDRESS = "temporal:7233"
}
# Postiz reads DATABASE_URL/REDIS_URL from this Secret. The chart does
@ -159,13 +159,13 @@ resource "helm_release" "postiz" {
# postiz-redis-password) both Services are ClusterIP, only routable
# from inside the postiz namespace, so the well-known creds are safe.
secrets = {
DATABASE_URL = "postgresql://postiz:postiz-password@postiz-postgresql:5432/postiz"
REDIS_URL = "redis://default:postiz-redis-password@postiz-redis-master:6379"
JWT_SECRET = ""
DATABASE_URL = "postgresql://postiz:postiz-password@postiz-postgresql:5432/postiz"
REDIS_URL = "redis://default:postiz-redis-password@postiz-redis-master:6379"
JWT_SECRET = ""
# IG-via-Facebook OAuth (Postiz Instagram-Business integration). Empty
# placeholder; ESO patches the real values from Vault below.
FACEBOOK_APP_ID = ""
FACEBOOK_APP_SECRET = ""
FACEBOOK_APP_ID = ""
FACEBOOK_APP_SECRET = ""
# IG standalone (Postiz Instagram-Login integration). Uses the modern
# `instagram_business_*` scopes does not require the FB Login dance.
INSTAGRAM_APP_ID = ""
@ -247,7 +247,7 @@ module "ingress_uploads_public" {
host = var.host
service_name = "postiz"
port = 80
protected = false
auth = "none"
ingress_path = ["/uploads"]
tls_secret_name = var.tls_secret_name
}
@ -260,7 +260,7 @@ module "ingress" {
host = var.host
service_name = "postiz"
port = 80
protected = true # Authentik forward-auth on the UI / API path
auth = "required" # Authentik forward-auth on the UI / API path
ingress_path = ["/"]
tls_secret_name = var.tls_secret_name
extra_annotations = {
@ -473,7 +473,7 @@ resource "kubernetes_cron_job_v1" "postgres_backup" {
spec {
restart_policy = "OnFailure"
container {
name = "backup"
name = "backup"
# Same image/pattern as dbaas/postgresql-backup: official postgres
# client tools + apt-installed curl for the Pushgateway push. The
# bitnamilegacy/postgresql variant is stripped (no curl/wget/python),

View file

@ -171,6 +171,6 @@ module "ingress" {
namespace = "priority-pass"
name = "priority-pass"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
max_body_size = "10m"
}

View file

@ -138,7 +138,11 @@ resource "kubernetes_service" "privatebin" {
# the default `anti_ai_scraping` middleware is sufficient protection.
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Public pastebin anyone can create/read pastes. Pastes are client-side
# encrypted; AI scrapers gain nothing from indexing them. anti_ai_scraping
# defaults on for auth=none, which is the existing protection.
auth = "none"
namespace = kubernetes_namespace.privatebin.metadata[0].name
name = "privatebin"
host = "pb"

View file

@ -341,14 +341,15 @@ module "anubis" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
dns_type = "proxied"
namespace = kubernetes_namespace.realestate-crawler.metadata[0].name
name = "wrongmove"
service_name = module.anubis.service_name
port = module.anubis.service_port
extra_middlewares = ["traefik-x402@kubernetescrd"]
anti_ai_scraping = false
tls_secret_name = var.tls_secret_name
anti_ai_scraping = false
tls_secret_name = var.tls_secret_name
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Wrongmove"
@ -360,7 +361,11 @@ module "ingress" {
}
module "ingress-api" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Wrongmove's public UI is Anubis-fronted (auth=none on the / path); this
# /api ingress serves XHRs from that public UI. Forward-auth here would
# break the UI.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.realestate-crawler.metadata[0].name
name = "wrongmove-api"

View file

@ -354,6 +354,7 @@ resource "kubernetes_service" "resume" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.resume.metadata[0].name
name = "resume"

View file

@ -563,6 +563,7 @@ resource "kubernetes_service" "rybbit-client" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.rybbit.metadata[0].name
name = "rybbit"
@ -579,7 +580,11 @@ module "ingress" {
}
module "ingress-api" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Analytics tracker beacon public websites embed Rybbit's /api/script.js
# and post events to /api/event. Forward-auth would 302 every tracking
# request and break analytics collection. Rybbit's site_id is the gate.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.rybbit.metadata[0].name
name = "rybbit-api"

View file

@ -165,7 +165,10 @@ resource "kubernetes_service" "send" {
}
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Send is an end-to-end encrypted file-drop anonymous recipients open a
# share link to download. Forward-auth would block every share-link user.
auth = "none"
dns_type = "non-proxied"
namespace = kubernetes_namespace.send.metadata[0].name
name = "send"

View file

@ -138,11 +138,12 @@ resource "kubernetes_service" "aiostreams" {
module "ingress" {
source = "../../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.aiostreams.metadata[0].name
name = "aiostreams"
tls_secret_name = var.tls_secret_name
# protected = true
# auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "AIOStreams"

View file

@ -80,7 +80,7 @@ module "ingress" {
namespace = "servarr"
name = "flaresolverr"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "FlareSolverr"

View file

@ -170,7 +170,7 @@ module "ingress" {
namespace = "servarr"
name = "lidarr"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
# extra_annotations = {
# "nginx.ingress.kubernetes.io/proxy-body-size" : "1G" // allow uploading .torrent files
# }
@ -183,5 +183,5 @@ module "ingress-deemix" {
namespace = "servarr"
name = "deemix"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
}

View file

@ -132,7 +132,7 @@ module "ingress" {
namespace = "servarr"
name = "listenarr"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Listenarr"

View file

@ -160,7 +160,7 @@ module "ingress" {
namespace = "servarr"
name = "prowlarr"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Prowlarr"

View file

@ -407,7 +407,7 @@ module "ingress" {
namespace = "servarr"
name = "qbittorrent"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "qBittorrent"

View file

@ -147,5 +147,5 @@ module "ingress" {
name = "readarr"
port = 8787
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
}

View file

@ -113,5 +113,5 @@ module "ingress" {
namespace = "servarr"
name = "soulseek"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
}

View file

@ -236,7 +236,7 @@ module "ingress" {
namespace = kubernetes_namespace.speedtest.metadata[0].name
name = "speedtest"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Speedtest"

View file

@ -132,6 +132,7 @@ resource "kubernetes_service" "stirling-pdf" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.stirling-pdf.metadata[0].name
name = "stirling-pdf"

View file

@ -252,6 +252,7 @@ resource "kubernetes_service" "tandoor" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.tandoor.metadata[0].name
name = "tandoor"

View file

@ -329,6 +329,7 @@ resource "kubernetes_service" "technitium_dns_internal" {
module "ingress" {
source = "../../../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.technitium.metadata[0].name
name = "technitium"

View file

@ -65,7 +65,7 @@ module "ingress" {
namespace = kubernetes_namespace.terminal.metadata[0].name
name = "terminal"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Terminal"
@ -206,7 +206,7 @@ module "ingress_ro" {
namespace = kubernetes_namespace.terminal.metadata[0].name
name = "terminal-ro"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Terminal (Read-Only)"

View file

@ -300,7 +300,7 @@ module "torrserver_ingress" {
name = "torrserver"
tls_secret_name = var.tls_secret_name
port = "8090"
protected = true
auth = "required"
external_monitor = false
extra_annotations = {
"gethomepage.dev/enabled" = "true"

View file

@ -617,7 +617,7 @@ module "ingress" {
name = "trading"
service_name = "trading-bot-frontend"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Trading Bot"

View file

@ -291,7 +291,7 @@ module "ingress" {
host = "traefik"
port = 8080
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Traefik"

View file

@ -111,13 +111,14 @@ module "anubis" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "none" # Anubis-fronted; PoW challenge gates bots, no Authentik
namespace = kubernetes_namespace.travel-blog.metadata[0].name
name = "travel"
tls_secret_name = var.tls_secret_name
service_name = module.anubis.service_name
port = module.anubis.service_port
extra_middlewares = ["traefik-x402@kubernetescrd"]
anti_ai_scraping = false
anti_ai_scraping = false
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Travel Blog"

View file

@ -180,6 +180,7 @@ resource "kubernetes_service" "tuya-bridge" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.tuya-bridge.metadata[0].name
name = "tuya-bridge"

View file

@ -182,6 +182,7 @@ resource "kubernetes_service" "uptime-kuma" {
}
module "ingress" {
source = "../../../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.uptime-kuma.metadata[0].name
name = "uptime"

View file

@ -288,6 +288,7 @@ resource "kubernetes_service" "shlink" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "proxied"
namespace = kubernetes_namespace.shlink.metadata[0].name
name = "url"
@ -438,7 +439,7 @@ module "ingress-web" {
name = "shlink"
service_name = "shlink-web"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "false"
"gethomepage.dev/name" = "Shlink Web"

View file

@ -228,7 +228,12 @@ resource "vault_identity_group_alias" "admins" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Vault has its own auth (OIDC, K8s, tokens). External CLI clients,
# `vault login -method=oidc`, the OIDC callback URL, and Terraform providers
# all hit https://vault.viktorbarzin.me forward-auth would block every
# non-browser client and break the OIDC redirect flow itself.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.vault.metadata[0].name
name = "vault"

View file

@ -196,7 +196,12 @@ resource "kubernetes_service" "vaultwarden" {
}
module "ingress" {
source = "../../../../modules/kubernetes/ingress_factory"
source = "../../../../modules/kubernetes/ingress_factory"
# Bitwarden-compatible API mobile app, browser extension, desktop app, and
# CLI all hit /api, /identity, /events with Bitwarden master-password / token
# auth. Forward-auth would block every native client. Vaultwarden's own auth
# is the gate.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.vaultwarden.metadata[0].name
name = "vaultwarden"

View file

@ -111,7 +111,7 @@ module "ingress" {
service_name = "goldilocks-dashboard"
port = 80
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Goldilocks"

View file

@ -481,7 +481,7 @@ module "ingress" {
namespace = kubernetes_namespace.wealthfolio.metadata[0].name
name = "wealthfolio"
tls_secret_name = var.tls_secret_name
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "Wealthfolio"
@ -514,7 +514,7 @@ resource "kubernetes_cron_job_v1" "wealthfolio_sync" {
name = "registry-credentials"
}
container {
name = "sync"
name = "sync"
# Phase 4 of forgejo-registry-consolidation 2026-05-07 +
# post-cutover wealthfolio-sync rebuild: image is now
# produced by /home/wizard/code/broker-sync (Forgejo

View file

@ -259,7 +259,10 @@ resource "kubernetes_service" "webhook_handler" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Webhook receiver third parties (Forgejo, GitHub, etc.) POST events without
# browser sessions. Forward-auth would block all webhook deliveries.
auth = "none"
namespace = kubernetes_namespace.webhook-handler.metadata[0].name
name = "webhook-handler"
host = "webhook"

View file

@ -351,7 +351,11 @@ resource "kubernetes_cron_job_v1" "vault_secret_sync" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
source = "../../modules/kubernetes/ingress_factory"
# Forgejo webhooks + webhook_handler POSTs hit ci.viktorbarzin.me to trigger
# pipelines; the Woodpecker API + OAuth flows also live here. Forward-auth
# would block every machine-driven call. Woodpecker has its own OAuth login.
auth = "none"
dns_type = "non-proxied"
namespace = kubernetes_namespace.woodpecker.metadata[0].name
name = "ci"

View file

@ -217,7 +217,9 @@ resource "kubernetes_service" "xray-reality" {
}
module "ingress_ws" {
source = "../../../../modules/kubernetes/ingress_factory"
source = "../../../../modules/kubernetes/ingress_factory"
# VPN protocol (WebSocket transport) native xray clients, not browsers.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.xray.metadata[0].name
name = "xray-ws"
@ -228,7 +230,9 @@ module "ingress_ws" {
}
module "ingress_grpc" {
source = "../../../../modules/kubernetes/ingress_factory"
source = "../../../../modules/kubernetes/ingress_factory"
# VPN protocol (gRPC transport) native xray clients, not browsers.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.xray.metadata[0].name
name = "xray-grpc"
@ -243,7 +247,9 @@ module "ingress_grpc" {
}
module "ingress_vless" {
source = "../../../../modules/kubernetes/ingress_factory"
source = "../../../../modules/kubernetes/ingress_factory"
# VPN protocol (VLESS) native xray clients, not browsers.
auth = "none"
dns_type = "proxied"
namespace = kubernetes_namespace.xray.metadata[0].name
name = "xray-vless"

View file

@ -176,6 +176,7 @@ resource "kubernetes_service" "ytdlp" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
namespace = kubernetes_namespace.ytdlp.metadata[0].name
name = "ytdlp"
tls_secret_name = var.tls_secret_name
@ -355,7 +356,7 @@ module "highlights_ingress" {
name = "yt-highlights"
tls_secret_name = var.tls_secret_name
host = "yt-highlights"
protected = true
auth = "required"
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/name" = "YT Highlights"