From ce8eeba6fb2925f964e503e7c8e83e6df7cf074f Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:21:06 +0100 Subject: [PATCH 01/13] infra(storage): add GitLab registry and artifacts buckets --- infra/modules/object-storage/main.tf | 27 +++++++++++++++++++++++ infra/modules/object-storage/outputs.tf | 15 +++++++++++++ infra/modules/object-storage/variables.tf | 6 +++++ 3 files changed, 48 insertions(+) diff --git a/infra/modules/object-storage/main.tf b/infra/modules/object-storage/main.tf index a1fc880b7..1d4c386d0 100644 --- a/infra/modules/object-storage/main.tf +++ b/infra/modules/object-storage/main.tf @@ -15,3 +15,30 @@ resource "scaleway_object_bucket" "artifacts" { enabled = false } } + +resource "scaleway_object_bucket" "gitlab_registry" { + name = "${var.bucket_name_prefix}-gitlab-registry" + region = var.region + + versioning { + enabled = false + } +} + +resource "scaleway_object_bucket" "gitlab_artifacts" { + name = "${var.bucket_name_prefix}-gitlab-artifacts" + region = var.region + + lifecycle_rule { + enabled = true + prefix = "tmp/" + + expiration { + days = 7 + } + } + + versioning { + enabled = false + } +} diff --git a/infra/modules/object-storage/outputs.tf b/infra/modules/object-storage/outputs.tf index 7df47b660..35d7c88bd 100644 --- a/infra/modules/object-storage/outputs.tf +++ b/infra/modules/object-storage/outputs.tf @@ -7,3 +7,18 @@ output "endpoint" { description = "S3-compatible endpoint URL for the bucket region" value = "https://s3.${var.region}.scw.cloud" } + +output "artifacts_bucket_name" { + description = "Name of the main artifacts bucket" + value = scaleway_object_bucket.artifacts.name +} + +output "gitlab_registry_bucket_name" { + description = "Name of the GitLab registry bucket" + value = scaleway_object_bucket.gitlab_registry.name +} + +output "gitlab_artifacts_bucket_name" { + description = "Name of the GitLab artifacts bucket" + value = scaleway_object_bucket.gitlab_artifacts.name +} diff --git a/infra/modules/object-storage/variables.tf b/infra/modules/object-storage/variables.tf index b658d4f41..6a87bb178 100644 --- a/infra/modules/object-storage/variables.tf +++ b/infra/modules/object-storage/variables.tf @@ -8,3 +8,9 @@ variable "bucket_name" { type = string default = "foxhunt-artifacts" } + +variable "bucket_name_prefix" { + description = "Prefix for additional buckets (e.g., foxhunt)" + type = string + default = "foxhunt" +} From 1c36941ded1a2d4295e4608ccefcd08b0d76af09 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:21:14 +0100 Subject: [PATCH 02/13] infra(kapsule): add gitlab and ci-build node pools Co-Authored-By: Claude Opus 4.6 --- infra/live/production/kapsule/terragrunt.hcl | 9 ++++++ infra/modules/kapsule/main.tf | 32 ++++++++++++++++++++ infra/modules/kapsule/outputs.tf | 10 ++++++ infra/modules/kapsule/variables.tf | 30 ++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/infra/live/production/kapsule/terragrunt.hcl b/infra/live/production/kapsule/terragrunt.hcl index 81a34817c..c91f1a38b 100644 --- a/infra/live/production/kapsule/terragrunt.hcl +++ b/infra/live/production/kapsule/terragrunt.hcl @@ -19,4 +19,13 @@ inputs = { enable_gpu_inference_pool = true gpu_inference_type = "L4-1-24G" gpu_inference_max_size = 1 + + # GitLab CE pool (dedicated for GitLab + Runner manager) + enable_gitlab_pool = true + gitlab_type = "DEV1-L" + + # CI build pool (ephemeral runner pods, scale-to-zero) + enable_ci_build_pool = true + ci_build_type = "GP1-XS" + ci_build_max_size = 2 } diff --git a/infra/modules/kapsule/main.tf b/infra/modules/kapsule/main.tf index 2c826d260..e1849af30 100644 --- a/infra/modules/kapsule/main.tf +++ b/infra/modules/kapsule/main.tf @@ -90,3 +90,35 @@ resource "scaleway_k8s_pool" "gpu_inference" { ignore_changes = [size] } } + +# GitLab CE node pool (dedicated DEV1-L for GitLab + Runner manager) +resource "scaleway_k8s_pool" "gitlab" { + count = var.enable_gitlab_pool ? 1 : 0 + cluster_id = scaleway_k8s_cluster.foxhunt.id + name = "gitlab" + node_type = var.gitlab_type + size = 1 + min_size = 1 + max_size = 1 + autoscaling = false + autohealing = true + region = var.region +} + +# CI build pod pool (scale-to-zero for ephemeral GitLab Runner build pods) +resource "scaleway_k8s_pool" "ci_build" { + count = var.enable_ci_build_pool ? 1 : 0 + cluster_id = scaleway_k8s_cluster.foxhunt.id + name = "ci-build" + node_type = var.ci_build_type + size = 0 + min_size = 0 + max_size = var.ci_build_max_size + autoscaling = true + autohealing = true + region = var.region + + lifecycle { + ignore_changes = [size] + } +} diff --git a/infra/modules/kapsule/outputs.tf b/infra/modules/kapsule/outputs.tf index 1710d1404..312da09e3 100644 --- a/infra/modules/kapsule/outputs.tf +++ b/infra/modules/kapsule/outputs.tf @@ -33,3 +33,13 @@ output "gpu_inference_pool_id" { description = "ID of the GPU inference node pool" value = var.enable_gpu_inference_pool ? scaleway_k8s_pool.gpu_inference[0].id : "" } + +output "gitlab_pool_id" { + description = "ID of the GitLab node pool" + value = var.enable_gitlab_pool ? scaleway_k8s_pool.gitlab[0].id : "" +} + +output "ci_build_pool_id" { + description = "ID of the CI build node pool" + value = var.enable_ci_build_pool ? scaleway_k8s_pool.ci_build[0].id : "" +} diff --git a/infra/modules/kapsule/variables.tf b/infra/modules/kapsule/variables.tf index 631ed2a58..88ec23e6b 100644 --- a/infra/modules/kapsule/variables.tf +++ b/infra/modules/kapsule/variables.tf @@ -68,3 +68,33 @@ variable "gpu_inference_max_size" { type = number default = 1 } + +variable "enable_gitlab_pool" { + description = "Create dedicated node pool for GitLab CE" + type = bool + default = false +} + +variable "gitlab_type" { + description = "Instance type for the GitLab node pool" + type = string + default = "DEV1-L" +} + +variable "enable_ci_build_pool" { + description = "Create dedicated node pool for CI build pods" + type = bool + default = false +} + +variable "ci_build_type" { + description = "Instance type for the CI build node pool" + type = string + default = "GP1-XS" +} + +variable "ci_build_max_size" { + description = "Maximum number of nodes in the CI build pool" + type = number + default = 2 +} From 1045b190dc6d18b3a6c6074c2b75111c530d1c08 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:22:54 +0100 Subject: [PATCH 03/13] infra(gitlab): postgres init job for GitLab database Co-Authored-By: Claude Opus 4.6 --- infra/k8s/gitlab/postgres-init.yaml | 62 +++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 infra/k8s/gitlab/postgres-init.yaml diff --git a/infra/k8s/gitlab/postgres-init.yaml b/infra/k8s/gitlab/postgres-init.yaml new file mode 100644 index 000000000..c63baeddf --- /dev/null +++ b/infra/k8s/gitlab/postgres-init.yaml @@ -0,0 +1,62 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: gitlab-postgres-init + namespace: foxhunt + labels: + app.kubernetes.io/name: gitlab-postgres-init + app.kubernetes.io/part-of: foxhunt +spec: + ttlSecondsAfterFinished: 300 + template: + spec: + nodeSelector: + k8s.scaleway.com/pool-name: always-on + restartPolicy: Never + containers: + - name: init + image: timescale/timescaledb:latest-pg16 + command: + - bash + - -c + - | + set -euo pipefail + export PGPASSWORD="$POSTGRES_PASSWORD" + + # Wait for postgres to be ready + until pg_isready -h postgres -U foxhunt; do + echo "Waiting for postgres..." + sleep 2 + done + + # Create gitlab database and user + psql -h postgres -U foxhunt -d foxhunt -c " + DO \$\$ + BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'gitlab') THEN + CREATE ROLE gitlab WITH LOGIN PASSWORD '$GITLAB_DB_PASSWORD'; + END IF; + END + \$\$; + " + psql -h postgres -U foxhunt -c " + SELECT 'CREATE DATABASE gitlab OWNER gitlab' + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'gitlab') + \gexec" + psql -h postgres -U foxhunt -d gitlab -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + psql -h postgres -U foxhunt -d gitlab -c "CREATE EXTENSION IF NOT EXISTS btree_gist;" + psql -h postgres -U foxhunt -d gitlab -c "GRANT ALL PRIVILEGES ON DATABASE gitlab TO gitlab;" + psql -h postgres -U foxhunt -d gitlab -c "GRANT ALL ON SCHEMA public TO gitlab;" + + echo "GitLab database initialized successfully." + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: foxhunt-secrets + key: db-password + - name: GITLAB_DB_PASSWORD + valueFrom: + secretKeyRef: + name: gitlab-secrets + key: db-password From 24c1b94fb04ff0505c87ad1d2edc95875261eb84 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:23:05 +0100 Subject: [PATCH 04/13] infra(gitlab): Helm values for GitLab CE deployment Co-Authored-By: Claude Opus 4.6 --- infra/k8s/gitlab/values.yaml | 174 +++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 infra/k8s/gitlab/values.yaml diff --git a/infra/k8s/gitlab/values.yaml b/infra/k8s/gitlab/values.yaml new file mode 100644 index 000000000..8c634afb5 --- /dev/null +++ b/infra/k8s/gitlab/values.yaml @@ -0,0 +1,174 @@ +# GitLab CE - Foxhunt deployment +# Runs on dedicated 'gitlab' node pool (DEV1-L) +# External Postgres + Redis on always-on pool + +global: + edition: ce + hosts: + domain: fxhnt.ai + gitlab: + name: gitlab.fxhnt.ai + registry: + name: gitlab.fxhnt.ai + ingress: + enabled: false + configureCertmanager: false + shell: + port: 2222 + psql: + host: postgres.foxhunt.svc.cluster.local + port: 5432 + database: gitlab + username: gitlab + password: + secret: gitlab-secrets + key: db-password + redis: + host: redis.foxhunt.svc.cluster.local + port: 6379 + password: + enabled: false + registry: + bucket: foxhunt-gitlab-registry + appConfig: + lfs: + bucket: foxhunt-gitlab-artifacts + connection: + secret: gitlab-s3-credentials + key: connection + artifacts: + bucket: foxhunt-gitlab-artifacts + connection: + secret: gitlab-s3-credentials + key: connection + uploads: + bucket: foxhunt-gitlab-artifacts + connection: + secret: gitlab-s3-credentials + key: connection + packages: + bucket: foxhunt-gitlab-artifacts + connection: + secret: gitlab-s3-credentials + key: connection + +# Disable bundled databases — use external +postgresql: + install: false +redis: + install: false + +# Disable components we don't need +certmanager: + install: false +nginx-ingress: + enabled: false +prometheus: + install: false +grafana: + install: false +gitlab-runner: + install: false +minio: + install: false + +# Registry — S3-backed +registry: + storage: + secret: gitlab-s3-credentials + key: registry + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + +# GitLab core components — all on gitlab node pool +gitlab: + webservice: + replicaCount: 1 + workerProcesses: 2 + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + resources: + requests: + cpu: 500m + memory: 2Gi + limits: + cpu: 2000m + memory: 4Gi + extraEnv: + REDIS_URL: "redis://redis.foxhunt.svc.cluster.local:6379/8" + + sidekiq: + replicas: 1 + concurrency: 10 + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + resources: + requests: + cpu: 250m + memory: 1Gi + limits: + cpu: 1000m + memory: 2Gi + extraEnv: + REDIS_URL: "redis://redis.foxhunt.svc.cluster.local:6379/8" + + gitaly: + persistence: + enabled: true + size: 50Gi + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1000m + memory: 2Gi + + gitlab-shell: + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + service: + type: NodePort + nodePort: 32222 + + toolbox: + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + + kas: + enabled: false + + gitlab-exporter: + enabled: false From 678f52622525abd521bb604ef34a9f627e716237 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:23:16 +0100 Subject: [PATCH 05/13] infra(gitlab): Runner Helm values with K8s executor for ci-build pool Co-Authored-By: Claude Opus 4.6 --- infra/k8s/gitlab/runner-values.yaml | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 infra/k8s/gitlab/runner-values.yaml diff --git a/infra/k8s/gitlab/runner-values.yaml b/infra/k8s/gitlab/runner-values.yaml new file mode 100644 index 000000000..f87d46d76 --- /dev/null +++ b/infra/k8s/gitlab/runner-values.yaml @@ -0,0 +1,70 @@ +# GitLab Runner — Kubernetes executor targeting ci-build pool +# Runner manager pod lives on gitlab node pool +# Build pods spawn on ci-build pool (scale-to-zero) + +gitlabUrl: https://gitlab.fxhnt.ai +# runnerToken set via --set at install time + +replicas: 1 + +nodeSelector: + k8s.scaleway.com/pool-name: gitlab +tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + +runners: + config: | + [[runners]] + [runners.kubernetes] + namespace = "foxhunt" + image = "rust:1.89-slim" + privileged = false + + # Build pods on ci-build pool + [runners.kubernetes.node_selector] + "k8s.scaleway.com/pool-name" = "ci-build" + [[runners.kubernetes.node_tolerations]] + key = "ci-build" + operator = "Equal" + value = "true" + effect = "NoSchedule" + + # Resource limits for build pods + cpu_request = "2000m" + cpu_limit = "6000m" + memory_request = "4Gi" + memory_limit = "12Gi" + + # Helper container + helper_cpu_request = "100m" + helper_cpu_limit = "500m" + helper_memory_request = "128Mi" + helper_memory_limit = "512Mi" + + # Service containers (for kaniko sidecar) + [runners.kubernetes.pod_labels] + "app.kubernetes.io/part-of" = "foxhunt-ci" + + # Runner tags for job matching + tags: "kapsule,rust,docker" + +# Concurrency +concurrent: 4 + +# RBAC for runner to spawn pods +rbac: + create: true + rules: + - apiGroups: [""] + resources: ["pods", "pods/exec", "pods/log", "secrets", "configmaps"] + verbs: ["get", "list", "watch", "create", "delete", "update", "patch"] + - apiGroups: [""] + resources: ["pods/attach"] + verbs: ["create", "get"] + +serviceAccount: + create: true + name: gitlab-runner From a082d5231345eb0289478b83295163308eee2407 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:23:26 +0100 Subject: [PATCH 06/13] ci: add GitLab CI/CD pipeline (check, test, build, deploy) Co-Authored-By: Claude Opus 4.6 --- .gitlab-ci.yml | 210 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..4a9cb75e7 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,210 @@ +# GitLab CI/CD — Foxhunt +# Runs on ci-build pool (GP1-XS, scale-to-zero) +# Uses sccache for Rust build caching, Kaniko for Docker builds + +stages: + - check + - test + - build + - deploy + +variables: + SQLX_OFFLINE: "true" + CARGO_TERM_COLOR: always + SCCACHE_BUCKET: $SCCACHE_BUCKET + SCCACHE_ENDPOINT: $SCCACHE_ENDPOINT + SCCACHE_S3_USE_SSL: "true" + AWS_ACCESS_KEY_ID: $SCW_ACCESS_KEY + AWS_SECRET_ACCESS_KEY: $SCW_SECRET_KEY + RUSTC_WRAPPER: /usr/local/bin/sccache + REGISTRY: ${CI_REGISTRY_IMAGE} + +# Base template for Rust jobs +.rust-base: + image: rust:1.89-slim + tags: + - kapsule + - rust + before_script: + - apt-get update && apt-get install -y protobuf-compiler curl pkg-config libssl-dev + - curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.8.1/sccache-v0.8.1-x86_64-unknown-linux-musl.tar.gz + | tar xz --strip-components=1 -C /usr/local/bin sccache-v0.8.1-x86_64-unknown-linux-musl/sccache + - chmod +x /usr/local/bin/sccache + - rustup component add clippy + +# -------------------------------------------------------------------------- +# Stage 1: cargo check + clippy +# -------------------------------------------------------------------------- +check: + extends: .rust-base + stage: check + script: + - cargo check --workspace + - cargo clippy --workspace -- -D warnings + +# -------------------------------------------------------------------------- +# Stage 2: tests +# -------------------------------------------------------------------------- +test: + extends: .rust-base + stage: test + needs: [check] + script: + - cargo test --workspace --lib + +# -------------------------------------------------------------------------- +# Stage 3: Build + push images (main only, Kaniko) +# -------------------------------------------------------------------------- +.kaniko-base: + stage: build + needs: [test] + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + tags: + - kapsule + rules: + - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" + before_script: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf '%s:%s' "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json + +build-trading-service: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=trading_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/trading_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/trading_service:latest" + +build-api-gateway: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=api_gateway + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/api_gateway:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/api_gateway:latest" + +build-broker-gateway: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=broker_gateway_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/broker_gateway_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/broker_gateway_service:latest" + +build-ml-training: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=ml_training_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/ml_training_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/ml_training_service:latest" + +build-backtesting: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=backtesting_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/backtesting_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/backtesting_service:latest" + +build-trading-agent: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.service" + --build-arg SERVICE=trading_agent_service + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/trading_agent_service:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/trading_agent_service:latest" + +build-web-gateway: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.web-gateway" + --destination "${REGISTRY}/web-gateway:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/web-gateway:latest" + +build-training: + extends: .kaniko-base + script: + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/infra/docker/Dockerfile.training" + --build-arg SCCACHE_BUCKET=${SCCACHE_BUCKET} + --build-arg AWS_ACCESS_KEY_ID=${SCW_ACCESS_KEY} + --build-arg AWS_SECRET_ACCESS_KEY=${SCW_SECRET_KEY} + --build-arg SCCACHE_ENDPOINT=${SCCACHE_ENDPOINT} + --destination "${REGISTRY}/training:${CI_COMMIT_SHA}" + --destination "${REGISTRY}/training:latest" + +# -------------------------------------------------------------------------- +# Stage 4: Deploy to Kapsule (main only) +# -------------------------------------------------------------------------- +deploy: + stage: deploy + image: bitnami/kubectl:latest + tags: + - kapsule + needs: + - build-trading-service + - build-api-gateway + - build-broker-gateway + - build-ml-training + - build-backtesting + - build-trading-agent + - build-web-gateway + - build-training + rules: + - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" + before_script: + - mkdir -p ~/.kube + - echo "$KUBECONFIG_DATA" | base64 -d > ~/.kube/config + - chmod 600 ~/.kube/config + script: + - | + for svc in trading-service api-gateway broker-gateway-service ml-training-service backtesting-service trading-agent-service; do + kubectl -n foxhunt set image deployment/${svc} ${svc}=${REGISTRY}/${svc}:${CI_COMMIT_SHA} || true + done + kubectl -n foxhunt set image deployment/web-gateway web-gateway=${REGISTRY}/web-gateway:${CI_COMMIT_SHA} || true + - | + for svc in trading-service api-gateway broker-gateway-service ml-training-service backtesting-service trading-agent-service web-gateway; do + kubectl -n foxhunt rollout status deployment/${svc} --timeout=300s || true + done From 4fb398a5317aad9ba0870f69ff3813c020ace505 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:25:05 +0100 Subject: [PATCH 07/13] infra(k8s): switch image refs from Scaleway registry to GitLab registry Co-Authored-By: Claude Opus 4.6 --- infra/k8s/services/api-gateway.yaml | 4 ++-- infra/k8s/services/backtesting-service.yaml | 4 ++-- infra/k8s/services/broker-gateway.yaml | 4 ++-- infra/k8s/services/ml-training-service-gpu.yaml | 4 ++-- infra/k8s/services/ml-training-service.yaml | 4 ++-- infra/k8s/services/trading-agent-service.yaml | 4 ++-- infra/k8s/services/trading-service-gpu.yaml | 4 ++-- infra/k8s/services/trading-service.yaml | 4 ++-- infra/k8s/services/web-gateway.yaml | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/infra/k8s/services/api-gateway.yaml b/infra/k8s/services/api-gateway.yaml index a485ab015..763fecfdc 100644 --- a/infra/k8s/services/api-gateway.yaml +++ b/infra/k8s/services/api-gateway.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: api-gateway spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: api-gateway - image: rg.fr-par.scw.cloud/foxhunt/api_gateway:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/api_gateway:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/backtesting-service.yaml b/infra/k8s/services/backtesting-service.yaml index 3ed146620..666e05f1f 100644 --- a/infra/k8s/services/backtesting-service.yaml +++ b/infra/k8s/services/backtesting-service.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: backtesting-service spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: backtesting-service - image: rg.fr-par.scw.cloud/foxhunt/backtesting_service:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/backtesting_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/broker-gateway.yaml b/infra/k8s/services/broker-gateway.yaml index c5bbea331..2c0d89127 100644 --- a/infra/k8s/services/broker-gateway.yaml +++ b/infra/k8s/services/broker-gateway.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: broker-gateway spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: broker-gateway - image: rg.fr-par.scw.cloud/foxhunt/broker_gateway_service:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/broker_gateway_service:latest ports: - containerPort: 50056 name: grpc diff --git a/infra/k8s/services/ml-training-service-gpu.yaml b/infra/k8s/services/ml-training-service-gpu.yaml index 176e05410..3817260af 100644 --- a/infra/k8s/services/ml-training-service-gpu.yaml +++ b/infra/k8s/services/ml-training-service-gpu.yaml @@ -26,10 +26,10 @@ spec: operator: Exists effect: NoSchedule imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: ml-training-service - image: rg.fr-par.scw.cloud/foxhunt/ml_training_service:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/ml_training_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/ml-training-service.yaml b/infra/k8s/services/ml-training-service.yaml index 74541355d..71a2571ce 100644 --- a/infra/k8s/services/ml-training-service.yaml +++ b/infra/k8s/services/ml-training-service.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: ml-training-service spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: ml-training-service - image: rg.fr-par.scw.cloud/foxhunt/ml_training_service:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/ml_training_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/trading-agent-service.yaml b/infra/k8s/services/trading-agent-service.yaml index 6a0421a33..5909756c9 100644 --- a/infra/k8s/services/trading-agent-service.yaml +++ b/infra/k8s/services/trading-agent-service.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: trading-agent-service spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: trading-agent-service - image: rg.fr-par.scw.cloud/foxhunt/trading_agent_service:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/trading_agent_service:latest ports: - containerPort: 50055 name: grpc diff --git a/infra/k8s/services/trading-service-gpu.yaml b/infra/k8s/services/trading-service-gpu.yaml index 09cd1d8ab..0fd7162b8 100644 --- a/infra/k8s/services/trading-service-gpu.yaml +++ b/infra/k8s/services/trading-service-gpu.yaml @@ -26,10 +26,10 @@ spec: operator: Exists effect: NoSchedule imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: trading-service - image: rg.fr-par.scw.cloud/foxhunt/trading_service:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/trading_service:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/trading-service.yaml b/infra/k8s/services/trading-service.yaml index 6bbf3f6e4..954b64b0a 100644 --- a/infra/k8s/services/trading-service.yaml +++ b/infra/k8s/services/trading-service.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: trading-service spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: trading-service - image: rg.fr-par.scw.cloud/foxhunt/trading_service:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/trading_service:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/web-gateway.yaml b/infra/k8s/services/web-gateway.yaml index a12a40698..07b1a5dc7 100644 --- a/infra/k8s/services/web-gateway.yaml +++ b/infra/k8s/services/web-gateway.yaml @@ -17,10 +17,10 @@ spec: app.kubernetes.io/name: web-gateway spec: imagePullSecrets: - - name: scw-registry + - name: gitlab-registry containers: - name: web-gateway - image: rg.fr-par.scw.cloud/foxhunt/web-gateway:latest + image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/web-gateway:latest ports: - containerPort: 3000 name: http From 020e85af2bd455222b7dc8d1169f11f04557ad57 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:26:51 +0100 Subject: [PATCH 08/13] =?UTF-8?q?infra:=20add=20DNS=20module=20+=20rename?= =?UTF-8?q?=20gitlab.fxhnt.ai=20=E2=86=92=20git.fxhnt.ai?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep existing git.fxhnt.ai hostname for GitLab CE (repoint DNS to new Tailscale IP after deploy). Add Terraform DNS module to manage the A record via Terragrunt instead of manual scw CLI. Co-Authored-By: Claude Opus 4.6 --- infra/k8s/gitlab/runner-values.yaml | 2 +- infra/k8s/gitlab/values.yaml | 4 ++-- infra/k8s/services/api-gateway.yaml | 2 +- infra/k8s/services/backtesting-service.yaml | 2 +- infra/k8s/services/broker-gateway.yaml | 2 +- infra/k8s/services/ml-training-service-gpu.yaml | 2 +- infra/k8s/services/ml-training-service.yaml | 2 +- infra/k8s/services/trading-agent-service.yaml | 2 +- infra/k8s/services/trading-service-gpu.yaml | 2 +- infra/k8s/services/trading-service.yaml | 2 +- infra/k8s/services/web-gateway.yaml | 2 +- infra/live/production/dns/terragrunt.hcl | 14 ++++++++++++++ infra/modules/dns/main.tf | 9 +++++++++ infra/modules/dns/outputs.tf | 4 ++++ infra/modules/dns/variables.tf | 10 ++++++++++ 15 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 infra/live/production/dns/terragrunt.hcl create mode 100644 infra/modules/dns/main.tf create mode 100644 infra/modules/dns/outputs.tf create mode 100644 infra/modules/dns/variables.tf diff --git a/infra/k8s/gitlab/runner-values.yaml b/infra/k8s/gitlab/runner-values.yaml index f87d46d76..2fabf3125 100644 --- a/infra/k8s/gitlab/runner-values.yaml +++ b/infra/k8s/gitlab/runner-values.yaml @@ -2,7 +2,7 @@ # Runner manager pod lives on gitlab node pool # Build pods spawn on ci-build pool (scale-to-zero) -gitlabUrl: https://gitlab.fxhnt.ai +gitlabUrl: https://git.fxhnt.ai # runnerToken set via --set at install time replicas: 1 diff --git a/infra/k8s/gitlab/values.yaml b/infra/k8s/gitlab/values.yaml index 8c634afb5..c2209e3c8 100644 --- a/infra/k8s/gitlab/values.yaml +++ b/infra/k8s/gitlab/values.yaml @@ -7,9 +7,9 @@ global: hosts: domain: fxhnt.ai gitlab: - name: gitlab.fxhnt.ai + name: git.fxhnt.ai registry: - name: gitlab.fxhnt.ai + name: git.fxhnt.ai ingress: enabled: false configureCertmanager: false diff --git a/infra/k8s/services/api-gateway.yaml b/infra/k8s/services/api-gateway.yaml index 763fecfdc..97d96a825 100644 --- a/infra/k8s/services/api-gateway.yaml +++ b/infra/k8s/services/api-gateway.yaml @@ -20,7 +20,7 @@ spec: - name: gitlab-registry containers: - name: api-gateway - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/api_gateway:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/api_gateway:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/backtesting-service.yaml b/infra/k8s/services/backtesting-service.yaml index 666e05f1f..83e4dd315 100644 --- a/infra/k8s/services/backtesting-service.yaml +++ b/infra/k8s/services/backtesting-service.yaml @@ -20,7 +20,7 @@ spec: - name: gitlab-registry containers: - name: backtesting-service - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/backtesting_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/backtesting_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/broker-gateway.yaml b/infra/k8s/services/broker-gateway.yaml index 2c0d89127..e4a78a7ae 100644 --- a/infra/k8s/services/broker-gateway.yaml +++ b/infra/k8s/services/broker-gateway.yaml @@ -20,7 +20,7 @@ spec: - name: gitlab-registry containers: - name: broker-gateway - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/broker_gateway_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/broker_gateway_service:latest ports: - containerPort: 50056 name: grpc diff --git a/infra/k8s/services/ml-training-service-gpu.yaml b/infra/k8s/services/ml-training-service-gpu.yaml index 3817260af..ae5736ebb 100644 --- a/infra/k8s/services/ml-training-service-gpu.yaml +++ b/infra/k8s/services/ml-training-service-gpu.yaml @@ -29,7 +29,7 @@ spec: - name: gitlab-registry containers: - name: ml-training-service - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/ml_training_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/ml_training_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/ml-training-service.yaml b/infra/k8s/services/ml-training-service.yaml index 71a2571ce..e91de0ea3 100644 --- a/infra/k8s/services/ml-training-service.yaml +++ b/infra/k8s/services/ml-training-service.yaml @@ -20,7 +20,7 @@ spec: - name: gitlab-registry containers: - name: ml-training-service - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/ml_training_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/ml_training_service:latest ports: - containerPort: 50053 name: grpc diff --git a/infra/k8s/services/trading-agent-service.yaml b/infra/k8s/services/trading-agent-service.yaml index 5909756c9..df7533cc2 100644 --- a/infra/k8s/services/trading-agent-service.yaml +++ b/infra/k8s/services/trading-agent-service.yaml @@ -20,7 +20,7 @@ spec: - name: gitlab-registry containers: - name: trading-agent-service - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/trading_agent_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/trading_agent_service:latest ports: - containerPort: 50055 name: grpc diff --git a/infra/k8s/services/trading-service-gpu.yaml b/infra/k8s/services/trading-service-gpu.yaml index 0fd7162b8..2ff3c504f 100644 --- a/infra/k8s/services/trading-service-gpu.yaml +++ b/infra/k8s/services/trading-service-gpu.yaml @@ -29,7 +29,7 @@ spec: - name: gitlab-registry containers: - name: trading-service - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/trading_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/trading_service:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/trading-service.yaml b/infra/k8s/services/trading-service.yaml index 954b64b0a..1b9ce633f 100644 --- a/infra/k8s/services/trading-service.yaml +++ b/infra/k8s/services/trading-service.yaml @@ -20,7 +20,7 @@ spec: - name: gitlab-registry containers: - name: trading-service - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/trading_service:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/trading_service:latest ports: - containerPort: 50051 name: grpc diff --git a/infra/k8s/services/web-gateway.yaml b/infra/k8s/services/web-gateway.yaml index 07b1a5dc7..81e3c10a7 100644 --- a/infra/k8s/services/web-gateway.yaml +++ b/infra/k8s/services/web-gateway.yaml @@ -20,7 +20,7 @@ spec: - name: gitlab-registry containers: - name: web-gateway - image: gitlab.fxhnt.ai:5050/foxhunt/foxhunt/web-gateway:latest + image: git.fxhnt.ai:5050/foxhunt/foxhunt/web-gateway:latest ports: - containerPort: 3000 name: http diff --git a/infra/live/production/dns/terragrunt.hcl b/infra/live/production/dns/terragrunt.hcl new file mode 100644 index 000000000..f3b543b77 --- /dev/null +++ b/infra/live/production/dns/terragrunt.hcl @@ -0,0 +1,14 @@ +include "root" { + path = find_in_parent_folders() +} + +terraform { + source = "../../../modules/dns" +} + +inputs = { + dns_zone = "fxhnt.ai" + # Tailscale subnet router IP — GitLab CE is accessed via this + # Update this after deploying GitLab and finding the routable IP + git_ip = "PLACEHOLDER_UPDATE_AFTER_DEPLOY" +} diff --git a/infra/modules/dns/main.tf b/infra/modules/dns/main.tf new file mode 100644 index 000000000..f6165e2b6 --- /dev/null +++ b/infra/modules/dns/main.tf @@ -0,0 +1,9 @@ +# DNS records for fxhnt.ai zone (Scaleway DNS) + +resource "scaleway_domain_record" "git" { + dns_zone = var.dns_zone + name = "git" + type = "A" + data = var.git_ip + ttl = 300 +} diff --git a/infra/modules/dns/outputs.tf b/infra/modules/dns/outputs.tf new file mode 100644 index 000000000..9473c4078 --- /dev/null +++ b/infra/modules/dns/outputs.tf @@ -0,0 +1,4 @@ +output "git_fqdn" { + description = "FQDN for the GitLab instance" + value = "${scaleway_domain_record.git.name}.${scaleway_domain_record.git.dns_zone}" +} diff --git a/infra/modules/dns/variables.tf b/infra/modules/dns/variables.tf new file mode 100644 index 000000000..fcb240a95 --- /dev/null +++ b/infra/modules/dns/variables.tf @@ -0,0 +1,10 @@ +variable "dns_zone" { + description = "DNS zone to manage records in" + type = string + default = "fxhnt.ai" +} + +variable "git_ip" { + description = "IP address for git.fxhnt.ai (Tailscale subnet router or ClusterIP)" + type = string +} From 6cd46e4796ebc3e32a2cd823bda2530efc86ad2d Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:27:56 +0100 Subject: [PATCH 09/13] ci: deploy stage applies manifests + sets images Deploy now runs kubectl apply on manifests first (picking up config changes), then updates images to the commit SHA. Adds GitLab environment tracking for production. Co-Authored-By: Claude Opus 4.6 --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4a9cb75e7..6b07e94f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -198,12 +198,21 @@ deploy: - mkdir -p ~/.kube - echo "$KUBECONFIG_DATA" | base64 -d > ~/.kube/config - chmod 600 ~/.kube/config + environment: + name: production + kubernetes: + namespace: foxhunt script: + # Apply manifests (picks up any config/resource changes) + - kubectl apply -f infra/k8s/databases/ + - kubectl apply -f infra/k8s/services/ + # Update images to this commit's build - | for svc in trading-service api-gateway broker-gateway-service ml-training-service backtesting-service trading-agent-service; do kubectl -n foxhunt set image deployment/${svc} ${svc}=${REGISTRY}/${svc}:${CI_COMMIT_SHA} || true done kubectl -n foxhunt set image deployment/web-gateway web-gateway=${REGISTRY}/web-gateway:${CI_COMMIT_SHA} || true + # Wait for rollouts - | for svc in trading-service api-gateway broker-gateway-service ml-training-service backtesting-service trading-agent-service web-gateway; do kubectl -n foxhunt rollout status deployment/${svc} --timeout=300s || true From b507b1bdead132f561a937a785a937dfd6fd4521 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:29:11 +0100 Subject: [PATCH 10/13] =?UTF-8?q?infra:=20rename=20root=20terragrunt.hcl?= =?UTF-8?q?=20=E2=86=92=20root.hcl=20(fix=20anti-pattern=20warning)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Terragrunt v0.77+ warns about using terragrunt.hcl as root config. Rename to root.hcl and update all child includes to use find_in_parent_folders("root.hcl"). Co-Authored-By: Claude Opus 4.6 --- infra/live/production/block-storage/terragrunt.hcl | 2 +- infra/live/production/ci-runner/terragrunt.hcl | 2 +- infra/live/production/dns/terragrunt.hcl | 2 +- infra/live/production/kapsule/terragrunt.hcl | 2 +- infra/live/production/object-storage/terragrunt.hcl | 2 +- infra/live/production/registry/terragrunt.hcl | 2 +- infra/live/production/{terragrunt.hcl => root.hcl} | 0 infra/live/production/secrets/terragrunt.hcl | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename infra/live/production/{terragrunt.hcl => root.hcl} (100%) diff --git a/infra/live/production/block-storage/terragrunt.hcl b/infra/live/production/block-storage/terragrunt.hcl index 4fa330572..84ede5bdf 100644 --- a/infra/live/production/block-storage/terragrunt.hcl +++ b/infra/live/production/block-storage/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/ci-runner/terragrunt.hcl b/infra/live/production/ci-runner/terragrunt.hcl index 72fbc9f86..896c4daaa 100644 --- a/infra/live/production/ci-runner/terragrunt.hcl +++ b/infra/live/production/ci-runner/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/dns/terragrunt.hcl b/infra/live/production/dns/terragrunt.hcl index f3b543b77..9fd912045 100644 --- a/infra/live/production/dns/terragrunt.hcl +++ b/infra/live/production/dns/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/kapsule/terragrunt.hcl b/infra/live/production/kapsule/terragrunt.hcl index c91f1a38b..472f4e058 100644 --- a/infra/live/production/kapsule/terragrunt.hcl +++ b/infra/live/production/kapsule/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/object-storage/terragrunt.hcl b/infra/live/production/object-storage/terragrunt.hcl index 502a76939..7009e3a85 100644 --- a/infra/live/production/object-storage/terragrunt.hcl +++ b/infra/live/production/object-storage/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/registry/terragrunt.hcl b/infra/live/production/registry/terragrunt.hcl index 7447e12ae..ddf00b64d 100644 --- a/infra/live/production/registry/terragrunt.hcl +++ b/infra/live/production/registry/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { diff --git a/infra/live/production/terragrunt.hcl b/infra/live/production/root.hcl similarity index 100% rename from infra/live/production/terragrunt.hcl rename to infra/live/production/root.hcl diff --git a/infra/live/production/secrets/terragrunt.hcl b/infra/live/production/secrets/terragrunt.hcl index 4ba75b096..1277d929d 100644 --- a/infra/live/production/secrets/terragrunt.hcl +++ b/infra/live/production/secrets/terragrunt.hcl @@ -1,5 +1,5 @@ include "root" { - path = find_in_parent_folders() + path = find_in_parent_folders("root.hcl") } terraform { From ad4619ae64de5c1f2373581ed7b64dd066d82677 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 24 Feb 2026 23:38:29 +0100 Subject: [PATCH 11/13] fix(gitlab): rewrite postgres init job to avoid psql \gexec --- infra/k8s/gitlab/postgres-init.yaml | 31 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/infra/k8s/gitlab/postgres-init.yaml b/infra/k8s/gitlab/postgres-init.yaml index c63baeddf..8a3c3c962 100644 --- a/infra/k8s/gitlab/postgres-init.yaml +++ b/infra/k8s/gitlab/postgres-init.yaml @@ -8,6 +8,7 @@ metadata: app.kubernetes.io/part-of: foxhunt spec: ttlSecondsAfterFinished: 300 + backoffLimit: 3 template: spec: nodeSelector: @@ -23,28 +24,28 @@ spec: set -euo pipefail export PGPASSWORD="$POSTGRES_PASSWORD" - # Wait for postgres to be ready until pg_isready -h postgres -U foxhunt; do echo "Waiting for postgres..." sleep 2 done - # Create gitlab database and user - psql -h postgres -U foxhunt -d foxhunt -c " - DO \$\$ - BEGIN - IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'gitlab') THEN - CREATE ROLE gitlab WITH LOGIN PASSWORD '$GITLAB_DB_PASSWORD'; - END IF; - END - \$\$; - " - psql -h postgres -U foxhunt -c " - SELECT 'CREATE DATABASE gitlab OWNER gitlab' - WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'gitlab') - \gexec" + echo "Creating gitlab user..." + psql -h postgres -U foxhunt -d foxhunt -tc \ + "SELECT 1 FROM pg_roles WHERE rolname='gitlab'" | grep -q 1 || \ + psql -h postgres -U foxhunt -d foxhunt -c \ + "CREATE ROLE gitlab WITH LOGIN PASSWORD '${GITLAB_DB_PASSWORD}'" + + echo "Creating gitlab database..." + psql -h postgres -U foxhunt -tc \ + "SELECT 1 FROM pg_database WHERE datname='gitlab'" | grep -q 1 || \ + psql -h postgres -U foxhunt -c \ + "CREATE DATABASE gitlab OWNER gitlab" + + echo "Creating extensions..." psql -h postgres -U foxhunt -d gitlab -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" psql -h postgres -U foxhunt -d gitlab -c "CREATE EXTENSION IF NOT EXISTS btree_gist;" + + echo "Granting permissions..." psql -h postgres -U foxhunt -d gitlab -c "GRANT ALL PRIVILEGES ON DATABASE gitlab TO gitlab;" psql -h postgres -U foxhunt -d gitlab -c "GRANT ALL ON SCHEMA public TO gitlab;" From 26e0caf67c3b600c605cfb3c9616bb67d55a5267 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Wed, 25 Feb 2026 00:06:10 +0100 Subject: [PATCH 12/13] fix(gitlab): Helm values schema, Tailscale proxy, DNS record MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix certmanager: use installCertmanager top-level key (not certmanager.install) - Fix redis: global.redis.auth.enabled (not password.enabled) - Remove grafana (not in GitLab chart), remove top-level minio (use global.minio.enabled) - Add toolbox backup objectStorage config (required when minio disabled) - Set HPA minReplicas/maxReplicas=1 for single-node DEV1-L - Add Tailscale proxy (nginx + tailscale sidecar) for private access - DNS: git.fxhnt.ai → 100.90.76.85 (foxhunt-gitlab Tailscale IP) Co-Authored-By: Claude Opus 4.6 --- infra/k8s/gitlab/tailscale-proxy.yaml | 147 +++++++++++++++++++++++ infra/k8s/gitlab/values.yaml | 33 ++++- infra/live/production/dns/terragrunt.hcl | 5 +- 3 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 infra/k8s/gitlab/tailscale-proxy.yaml diff --git a/infra/k8s/gitlab/tailscale-proxy.yaml b/infra/k8s/gitlab/tailscale-proxy.yaml new file mode 100644 index 000000000..fd4a2a3c4 --- /dev/null +++ b/infra/k8s/gitlab/tailscale-proxy.yaml @@ -0,0 +1,147 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tailscale-gitlab + namespace: foxhunt +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: tailscale-gitlab + namespace: foxhunt +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: tailscale-gitlab + namespace: foxhunt +subjects: + - kind: ServiceAccount + name: tailscale-gitlab + namespace: foxhunt +roleRef: + kind: Role + name: tailscale-gitlab + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tailscale-gitlab-proxy + namespace: foxhunt + labels: + app.kubernetes.io/name: tailscale-gitlab-proxy + app.kubernetes.io/part-of: foxhunt +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: tailscale-gitlab-proxy + template: + metadata: + labels: + app.kubernetes.io/name: tailscale-gitlab-proxy + spec: + serviceAccountName: tailscale-gitlab + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + - containerPort: 443 + - containerPort: 5050 + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/conf.d + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + - name: tailscale + image: ghcr.io/tailscale/tailscale:latest + env: + - name: TS_AUTHKEY + valueFrom: + secretKeyRef: + name: tailscale-auth + key: TS_AUTHKEY + - name: TS_KUBE_SECRET + value: tailscale-gitlab-state + - name: TS_USERSPACE + value: "false" + - name: TS_HOSTNAME + value: "foxhunt-gitlab" + - name: TS_ACCEPT_DNS + value: "false" + - name: TS_EXTRA_ARGS + value: "" + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + securityContext: + capabilities: + add: + - NET_ADMIN + - NET_RAW + volumes: + - name: nginx-conf + configMap: + name: tailscale-gitlab-nginx +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: tailscale-gitlab-nginx + namespace: foxhunt +data: + default.conf: | + server { + listen 80; + server_name git.fxhnt.ai; + + client_max_body_size 250m; + + location / { + proxy_pass http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } + + server { + listen 5050; + server_name git.fxhnt.ai; + + client_max_body_size 0; + + location / { + proxy_pass http://gitlab-registry.foxhunt.svc.cluster.local:5000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } diff --git a/infra/k8s/gitlab/values.yaml b/infra/k8s/gitlab/values.yaml index c2209e3c8..3eabe1d8b 100644 --- a/infra/k8s/gitlab/values.yaml +++ b/infra/k8s/gitlab/values.yaml @@ -26,8 +26,10 @@ global: redis: host: redis.foxhunt.svc.cluster.local port: 6379 - password: + auth: enabled: false + minio: + enabled: false registry: bucket: foxhunt-gitlab-registry appConfig: @@ -59,21 +61,22 @@ redis: install: false # Disable components we don't need +installCertmanager: false certmanager: - install: false + installCRDs: false nginx-ingress: enabled: false prometheus: install: false -grafana: - install: false gitlab-runner: install: false -minio: - install: false # Registry — S3-backed registry: + replicaCount: 1 + hpa: + minReplicas: 1 + maxReplicas: 1 storage: secret: gitlab-s3-credentials key: registry @@ -89,6 +92,9 @@ registry: gitlab: webservice: replicaCount: 1 + hpa: + minReplicas: 1 + maxReplicas: 1 workerProcesses: 2 nodeSelector: k8s.scaleway.com/pool-name: gitlab @@ -109,6 +115,9 @@ gitlab: sidekiq: replicas: 1 + hpa: + minReplicas: 1 + maxReplicas: 1 concurrency: 10 nodeSelector: k8s.scaleway.com/pool-name: gitlab @@ -147,6 +156,10 @@ gitlab: memory: 2Gi gitlab-shell: + replicaCount: 1 + hpa: + minReplicas: 1 + maxReplicas: 1 nodeSelector: k8s.scaleway.com/pool-name: gitlab tolerations: @@ -159,6 +172,11 @@ gitlab: nodePort: 32222 toolbox: + backups: + objectStorage: + config: + secret: gitlab-s3-credentials + key: connection nodeSelector: k8s.scaleway.com/pool-name: gitlab tolerations: @@ -169,6 +187,9 @@ gitlab: kas: enabled: false + hpa: + minReplicas: 0 + maxReplicas: 0 gitlab-exporter: enabled: false diff --git a/infra/live/production/dns/terragrunt.hcl b/infra/live/production/dns/terragrunt.hcl index 9fd912045..e9e9e3ff7 100644 --- a/infra/live/production/dns/terragrunt.hcl +++ b/infra/live/production/dns/terragrunt.hcl @@ -8,7 +8,6 @@ terraform { inputs = { dns_zone = "fxhnt.ai" - # Tailscale subnet router IP — GitLab CE is accessed via this - # Update this after deploying GitLab and finding the routable IP - git_ip = "PLACEHOLDER_UPDATE_AFTER_DEPLOY" + # Tailscale IP of foxhunt-gitlab proxy pod + git_ip = "100.90.76.85" } From 9c37d0edbb76b8e34d5cbac02e63f85a16b46a9e Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Wed, 25 Feb 2026 00:29:13 +0100 Subject: [PATCH 13/13] infra(gitlab): enable Prometheus, SSH proxy, explicit always-on type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enable Prometheus server (GitLab chart bundled) on gitlab node pool - Add socat SSH proxy sidecar (port 2222 → gitlab-shell) to Tailscale proxy - Remove nginx stream module (not available in alpine) in favor of socat - Set unlimited nginx client_max_body_size for large git pushes - Add workhorse extraArgs for API limits - Explicit always_on_type = DEV1-M in kapsule terragrunt Co-Authored-By: Claude Opus 4.6 --- infra/k8s/gitlab/tailscale-proxy.yaml | 16 ++++++++++++- infra/k8s/gitlab/values.yaml | 25 +++++++++++++++++++- infra/live/production/kapsule/terragrunt.hcl | 1 + 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/infra/k8s/gitlab/tailscale-proxy.yaml b/infra/k8s/gitlab/tailscale-proxy.yaml index fd4a2a3c4..9ceecdd3c 100644 --- a/infra/k8s/gitlab/tailscale-proxy.yaml +++ b/infra/k8s/gitlab/tailscale-proxy.yaml @@ -74,6 +74,20 @@ spec: limits: cpu: 100m memory: 64Mi + - name: ssh-proxy + image: alpine/socat:latest + args: + - "TCP-LISTEN:2222,fork,reuseaddr" + - "TCP:gitlab-gitlab-shell.foxhunt.svc.cluster.local:2222" + ports: + - containerPort: 2222 + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi - name: tailscale image: ghcr.io/tailscale/tailscale:latest env: @@ -120,7 +134,7 @@ data: listen 80; server_name git.fxhnt.ai; - client_max_body_size 250m; + client_max_body_size 0; location / { proxy_pass http://gitlab-webservice-default.foxhunt.svc.cluster.local:8181; diff --git a/infra/k8s/gitlab/values.yaml b/infra/k8s/gitlab/values.yaml index 3eabe1d8b..f0f0dbfbd 100644 --- a/infra/k8s/gitlab/values.yaml +++ b/infra/k8s/gitlab/values.yaml @@ -67,7 +67,28 @@ certmanager: nginx-ingress: enabled: false prometheus: - install: false + install: true + rbac: + create: true + alertmanager: + enabled: false + server: + nodeSelector: + k8s.scaleway.com/pool-name: gitlab + tolerations: + - key: gitlab + operator: Equal + value: "true" + effect: NoSchedule + persistentVolume: + enabled: false + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi gitlab-runner: install: false @@ -96,6 +117,8 @@ gitlab: minReplicas: 1 maxReplicas: 1 workerProcesses: 2 + workhorse: + extraArgs: "-apiLimit 0 -apiQueueLimit 0" nodeSelector: k8s.scaleway.com/pool-name: gitlab tolerations: diff --git a/infra/live/production/kapsule/terragrunt.hcl b/infra/live/production/kapsule/terragrunt.hcl index 472f4e058..acb6977be 100644 --- a/infra/live/production/kapsule/terragrunt.hcl +++ b/infra/live/production/kapsule/terragrunt.hcl @@ -9,6 +9,7 @@ terraform { inputs = { cluster_name = "foxhunt" k8s_version = "1.34" + always_on_type = "DEV1-M" # GPU training pool (H100 for 10-model ensemble training) enable_gpu_training_pool = true