variable "tls_secret_name" { type = string sensitive = true } variable "nfs_server" { type = string } locals { namespace = "chrome-service" labels = { app = "chrome-service" } # Pin to the same Playwright minor that the Python client requires. # If you bump this image, also bump `playwright==X.Y.Z` in the client # (currently f1-stream) and re-run the connect smoke test. image = "mcr.microsoft.com/playwright:v1.48.0-noble" } # --- Namespace --- resource "kubernetes_namespace" "chrome_service" { metadata { name = local.namespace labels = { "istio-injection" = "disabled" tier = local.tiers.aux "chrome-service.viktorbarzin.me/server" = "true" } } lifecycle { # KYVERNO_LIFECYCLE_V1: goldilocks-vpa-auto-mode ClusterPolicy stamps this label on every namespace ignore_changes = [metadata[0].labels["goldilocks.fairwinds.com/vpa-update-mode"]] } } # --- Secrets (single-key extract: api_bearer_token) --- resource "kubernetes_manifest" "external_secret" { manifest = { apiVersion = "external-secrets.io/v1beta1" kind = "ExternalSecret" metadata = { name = "chrome-service-secrets" namespace = local.namespace } spec = { refreshInterval = "15m" secretStoreRef = { name = "vault-kv" kind = "ClusterSecretStore" } target = { name = "chrome-service-secrets" } dataFrom = [{ extract = { key = "chrome-service" } }] } } depends_on = [kubernetes_namespace.chrome_service] } # tls-secret for the chrome.viktorbarzin.me ingress is auto-cloned into # every namespace by Kyverno's `sync-tls-secret` ClusterPolicy — no local # module call needed. # --- Encrypted profile PVC --- # Holds Chromium user data: cookies, localStorage, IndexedDB. Sites we # drive may set auth tokens or session cookies — encrypted is correct. resource "kubernetes_persistent_volume_claim" "profile_encrypted" { wait_until_bound = false metadata { name = "chrome-service-profile-encrypted" namespace = kubernetes_namespace.chrome_service.metadata[0].name annotations = { "resize.topolvm.io/threshold" = "10%" "resize.topolvm.io/increase" = "100%" "resize.topolvm.io/storage_limit" = "10Gi" } } spec { access_modes = ["ReadWriteOnce"] storage_class_name = "proxmox-lvm-encrypted" resources { requests = { storage = "2Gi" } } } lifecycle { # The autoresizer expands requests.storage up to storage_limit and # PVCs can't shrink. Without this, every TF apply tries to revert # to the spec value, K8s rejects the shrink, and the PVC ends up # in Terminating-but-in-use limbo. ignore_changes = [spec[0].resources[0].requests] } } # --- NFS backup target --- module "nfs_chrome_service_backup_host" { source = "../../modules/kubernetes/nfs_volume" name = "chrome-service-backup-host" namespace = kubernetes_namespace.chrome_service.metadata[0].name nfs_server = "192.168.1.127" nfs_path = "/srv/nfs/chrome-service-backup" } # --- Deployment --- resource "kubernetes_deployment" "chrome_service" { metadata { name = "chrome-service" namespace = kubernetes_namespace.chrome_service.metadata[0].name labels = merge(local.labels, { tier = local.tiers.aux }) annotations = { "reloader.stakater.com/auto" = "true" } } spec { replicas = 1 strategy { type = "Recreate" } selector { match_labels = local.labels } template { metadata { labels = local.labels } spec { # The noVNC sidecar pulls from registry.viktorbarzin.me which needs # auth. Kyverno's `sync-registry-credentials` ClusterPolicy syncs # the secret into every namespace. image_pull_secrets { name = "registry-credentials" } security_context { run_as_user = 1000 run_as_group = 1000 fs_group = 1000 seccomp_profile { type = "RuntimeDefault" } } # Fix profile dir ownership (PVC may have root-owned files from prior run). init_container { name = "fix-perms" image = "busybox:1.37" command = ["sh", "-c", "chown -R 1000:1000 /profile"] security_context { run_as_user = 0 } volume_mount { name = "profile" mount_path = "/profile" } resources { requests = { memory = "32Mi" } limits = { memory = "64Mi" } } } container { name = "chrome-service" image = local.image image_pull_policy = "IfNotPresent" # `launch-server` (not `run-server`) lets us pin headed mode + # specific args. `run-server` defaults to headless, which the # disable-devtool.js Performance detector trips under Playwright # (CDP adds latency to console.log; lib detects + redirects). # The Microsoft image ships only the browsers, not the playwright # npm package itself — `npx -y playwright@` downloads it on # first start (cached under $HOME/.npm via the PVC) and pins to # the same minor as the Python client. Bump in lockstep. command = ["bash", "-c"] args = [ <<-EOT set -e # `-listen tcp` enables localhost:6099 so the noVNC sidecar can # connect over the pod's shared network namespace (Ubuntu 24.04 # defaults Xvfb to -nolisten tcp). # `-ac` disables X access control so the noVNC sidecar can # attach without an MIT-MAGIC-COOKIE; safe because Xvfb only # listens on localhost (pod's lo). Xvfb :99 -screen 0 1280x720x24 -listen tcp -ac & sleep 1 cat > /tmp/launch.json <