diff --git a/infrastructure/terraform/act-runner.tf b/infrastructure/terraform/act-runner.tf index b3e75ff..12af361 100644 --- a/infrastructure/terraform/act-runner.tf +++ b/infrastructure/terraform/act-runner.tf @@ -1,2 +1,193 @@ -# Act runner disabled — postponed until dedicated server/VPS. -# See git history for the full configuration. +resource "kubernetes_service_account" "act_runner" { + count = var.enable_gitea ? 1 : 0 + metadata { + name = "act-runner" + namespace = kubernetes_namespace.domains["gitea"].metadata[0].name + } +} + +resource "kubernetes_cluster_role" "act_runner" { + count = var.enable_gitea ? 1 : 0 + metadata { + name = "act-runner" + } + rule { + api_groups = ["apps"] + resources = ["deployments"] + verbs = ["get", "list", "patch", "update"] + } + rule { + api_groups = [""] + resources = ["pods", "pods/log"] + verbs = ["get", "list"] + } + rule { + api_groups = ["batch"] + resources = ["jobs"] + verbs = ["create", "get", "list", "watch", "delete"] + } +} + +resource "kubernetes_cluster_role_binding" "act_runner" { + count = var.enable_gitea ? 1 : 0 + metadata { + name = "act-runner" + } + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "ClusterRole" + name = kubernetes_cluster_role.act_runner[0].metadata[0].name + } + subject { + kind = "ServiceAccount" + name = kubernetes_service_account.act_runner[0].metadata[0].name + namespace = kubernetes_namespace.domains["gitea"].metadata[0].name + } +} + +resource "kubernetes_config_map" "act_runner" { + count = var.enable_gitea ? 1 : 0 + metadata { + name = "act-runner-config" + namespace = kubernetes_namespace.domains["gitea"].metadata[0].name + } + data = { + "config.yaml" = yamlencode({ + log = { level = "info" } + runner = { + capacity = 2 + fetch_timeout = "5s" + fetch_interval = "2s" + report_interval = "1s" + envs = {} + } + cache = { enabled = false } + container = { + network = "host" + valid_volumes = ["/var/run/secrets/kubernetes.io/serviceaccount"] + docker_host = "tcp://localhost:2375" + } + }) + } +} + +resource "kubernetes_deployment" "act_runner" { + count = var.enable_gitea ? 1 : 0 + depends_on = [helm_release.gitea, kubernetes_secret.gitea_runner_token] + + metadata { + name = "act-runner" + namespace = kubernetes_namespace.domains["gitea"].metadata[0].name + labels = { app = "act-runner" } + } + + spec { + replicas = 1 + selector { + match_labels = { app = "act-runner" } + } + template { + metadata { + labels = { app = "act-runner" } + } + spec { + service_account_name = kubernetes_service_account.act_runner[0].metadata[0].name + + container { + name = "runner" + image = "gitea/act_runner:latest" + + command = ["/bin/sh", "-c"] + args = [<<-EOT + set -e + if [ ! -f /data/.runner ]; then + act_runner register \ + --no-interactive \ + --instance http://gitea-http.gitea.svc.cluster.local:3000 \ + --token "$(cat /etc/runner-token/token)" \ + --name "k3d-runner-$(hostname)" \ + --labels ubuntu-latest + fi + exec act_runner daemon --config /etc/act-runner/config.yaml + EOT + ] + + env { + name = "DOCKER_HOST" + value = "tcp://localhost:2375" + } + env { + name = "KUBERNETES_SERVICE_HOST" + value_from { + field_ref { field_path = "status.hostIP" } + } + } + + volume_mount { + name = "runner-data" + mount_path = "/data" + } + volume_mount { + name = "runner-config" + mount_path = "/etc/act-runner" + } + volume_mount { + name = "runner-token" + mount_path = "/etc/runner-token" + read_only = true + } + + resources { + requests = { cpu = "100m", memory = "128Mi" } + limits = { cpu = "500m", memory = "512Mi" } + } + } + + container { + name = "dind" + image = "docker:27-dind" + + security_context { + privileged = true + } + args = ["--insecure-registry=gitea-http.gitea.svc.cluster.local:3000"] + env { + name = "DOCKER_TLS_CERTDIR" + value = "" + } + + volume_mount { + name = "docker-storage" + mount_path = "/var/lib/docker" + } + + resources { + requests = { cpu = "200m", memory = "256Mi" } + limits = { cpu = "1", memory = "1Gi" } + } + } + + volume { + name = "runner-data" + empty_dir {} + } + volume { + name = "docker-storage" + empty_dir {} + } + volume { + name = "runner-config" + config_map { + name = kubernetes_config_map.act_runner[0].metadata[0].name + } + } + volume { + name = "runner-token" + secret { + secret_name = kubernetes_secret.gitea_runner_token[0].metadata[0].name + } + } + } + } + } +} diff --git a/infrastructure/terraform/gitea.tf b/infrastructure/terraform/gitea.tf index fc26e5a..88a3067 100644 --- a/infrastructure/terraform/gitea.tf +++ b/infrastructure/terraform/gitea.tf @@ -1,2 +1,156 @@ -# Gitea and registry pull secrets disabled — postponed until dedicated server/VPS. -# See git history for the full configuration. +resource "random_password" "gitea_admin" { + count = var.enable_gitea ? 1 : 0 + length = 24 + special = false +} + +resource "kubernetes_secret" "gitea_admin" { + count = var.enable_gitea ? 1 : 0 + metadata { + name = "gitea-admin" + namespace = kubernetes_namespace.domains["gitea"].metadata[0].name + } + data = { + username = "admin" + password = random_password.gitea_admin[0].result + email = "admin@homelab.local" + } +} + +resource "helm_release" "gitea" { + count = var.enable_gitea ? 1 : 0 + name = "gitea" + namespace = kubernetes_namespace.domains["gitea"].metadata[0].name + repository = "https://dl.gitea.com/charts/" + chart = "gitea" + version = "~> 12.0" + atomic = true + timeout = 300 + + values = [yamlencode({ + gitea = { + admin = { + existingSecret = kubernetes_secret.gitea_admin[0].metadata[0].name + } + config = { + APP_NAME = "Homelab Git" + server = { + DOMAIN = "git.homelab.local" + ROOT_URL = "http://git.homelab.local" + SSH_DOMAIN = "localhost" + SSH_PORT = 30001 + } + database = { DB_TYPE = "sqlite3" } + queue = { TYPE = "level" } + cache = { ADAPTER = "memory" } + session = { PROVIDER = "memory" } + packages = { ENABLED = "true" } + service = { DISABLE_REGISTRATION = "true" } + log = { LEVEL = "Warn" } + } + } + + "postgresql-ha" = { enabled = false } + "valkey-cluster" = { enabled = false } + + ingress = { + enabled = true + className = "traefik" + hosts = [{ + host = "git.homelab.local" + paths = [{ path = "/", pathType = "Prefix" }] + }] + } + + # NodePort 30002: used by k3d containerd registry mirror (see k3d/config.yaml) + service = { + http = { + type = "NodePort" + port = 3000 + nodePort = 30002 + } + ssh = { + type = "NodePort" + port = 22 + nodePort = 30001 + } + } + + persistence = { + enabled = true + size = "10Gi" + storageClass = "local-path" + } + + resources = { + requests = { cpu = "100m", memory = "256Mi" } + limits = { cpu = "500m", memory = "512Mi" } + } + })] +} + +resource "kubernetes_secret" "gitea_runner_token" { + count = var.enable_gitea ? 1 : 0 + metadata { + name = "gitea-runner-token" + namespace = kubernetes_namespace.domains["gitea"].metadata[0].name + } + data = { token = "" } + lifecycle { + ignore_changes = [data] + } +} + +resource "terraform_data" "gitea_runner_registration" { + count = var.enable_gitea ? 1 : 0 + depends_on = [helm_release.gitea, kubernetes_secret.gitea_runner_token] + + triggers_replace = [random_password.gitea_admin[0].id] + + provisioner "local-exec" { + interpreter = ["/bin/sh", "-c"] + command = <<-EOT + set -e + echo "Waiting for Gitea to be ready..." + until curl -sf "http://git.homelab.local/api/v1/version" > /dev/null 2>&1; do + sleep 5 + done + + PASSWORD=$(kubectl get secret gitea-admin -n gitea \ + -o jsonpath='{.data.password}' | base64 -d) + + TOKEN=$(curl -sf \ + -u "admin:$PASSWORD" \ + "http://git.homelab.local/api/v1/admin/runners/registration-token" \ + | grep -o '"token":"[^"]*"' | cut -d'"' -f4) + + kubectl patch secret gitea-runner-token -n gitea \ + -p "{\"data\":{\"token\":\"$(printf '%s' "$TOKEN" | base64)\"}}" + + echo "Runner registration token written to gitea-runner-token secret." + EOT + } +} + +locals { + app_namespaces = ["auth", "finance", "home", "test"] +} + +resource "kubernetes_secret" "gitea_registry" { + for_each = var.enable_gitea ? toset(local.app_namespaces) : toset([]) + + metadata { + name = "gitea-registry" + namespace = kubernetes_namespace.domains[each.value].metadata[0].name + } + type = "kubernetes.io/dockerconfigjson" + data = { + ".dockerconfigjson" = jsonencode({ + auths = { + "git.homelab.local" = { + auth = base64encode("admin:${random_password.gitea_admin[0].result}") + } + } + }) + } +} diff --git a/infrastructure/terraform/namespaces.tf b/infrastructure/terraform/namespaces.tf index 1b55955..7422fa5 100644 --- a/infrastructure/terraform/namespaces.tf +++ b/infrastructure/terraform/namespaces.tf @@ -1,5 +1,8 @@ locals { - namespaces = ["auth", "home", "finance", "test", "monitoring", "infrastructure"] + namespaces = concat( + ["auth", "home", "finance", "test", "monitoring", "infrastructure"], + var.enable_gitea ? ["gitea"] : [] + ) } resource "kubernetes_namespace" "domains" { diff --git a/infrastructure/terraform/variables.tf b/infrastructure/terraform/variables.tf index 66bae69..f7c6e1d 100644 --- a/infrastructure/terraform/variables.tf +++ b/infrastructure/terraform/variables.tf @@ -1,3 +1,5 @@ -# No manual secrets required — passwords are generated by random_password resources -# and stored in Terraform state. Runner token is fetched from the Gitea API -# automatically after the first deploy. +variable "enable_gitea" { + description = "Deploy Gitea and the act runner. Set to false to skip (e.g. on a dev laptop without a dedicated server)." + type = bool + default = false +}