diff --git a/stacks/android-emulator/main.tf b/stacks/android-emulator/main.tf index 1b70b471..10220999 100644 --- a/stacks/android-emulator/main.tf +++ b/stacks/android-emulator/main.tf @@ -231,6 +231,11 @@ module "ingress-internal" { extra_annotations = { "gethomepage.dev/enabled" = "false" } + # noVNC loads ~60 unbundled ES modules in parallel; the default 10/50 + # limiter 429s the tail and the loader hangs forever. Dedicated limiter, + # same pattern as actualbudget/immich. + skip_default_rate_limit = true + extra_middlewares = ["traefik-android-emulator-rate-limit@kubernetescrd"] } # Remote (off-LAN) screen access — Authentik-gated at the edge; WebSockets @@ -246,4 +251,9 @@ module "ingress-public" { host = "android-emulator" service_name = kubernetes_service.novnc.metadata[0].name tls_secret_name = var.tls_secret_name + # noVNC loads ~60 unbundled ES modules in parallel; the default 10/50 + # limiter 429s the tail and the loader hangs forever. Dedicated limiter, + # same pattern as actualbudget/immich. + skip_default_rate_limit = true + extra_middlewares = ["traefik-android-emulator-rate-limit@kubernetescrd"] } diff --git a/stacks/traefik/modules/traefik/middleware.tf b/stacks/traefik/modules/traefik/middleware.tf index ef34f991..0378d0c3 100644 --- a/stacks/traefik/modules/traefik/middleware.tf +++ b/stacks/traefik/modules/traefik/middleware.tf @@ -453,3 +453,27 @@ resource "kubernetes_manifest" "middleware_retry" { depends_on = [helm_release.traefik] } + +# android-emulator noVNC rate limit. noVNC 1.3 ships unbundled: vnc.html +# pulls ~60 ES modules in parallel on every page open, and the default +# 10/50 limiter 429s the tail — the loader then waits forever on the +# missing modules ("stuck on loading", verified 38x429 at a 90-request +# burst on 2026-06-12). Same remedy as actualbudget/immich. +resource "kubernetes_manifest" "middleware_android_emulator_rate_limit" { + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "Middleware" + metadata = { + name = "android-emulator-rate-limit" + namespace = kubernetes_namespace.traefik.metadata[0].name + } + spec = { + rateLimit = { + average = 50 + burst = 300 + } + } + } + + depends_on = [helm_release.traefik] +}