From 882bc16e0b6db8fee84405c6307e98ed0d559bd8 Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Sat, 9 Nov 2024 19:44:39 +0100 Subject: [PATCH 01/27] Add telepresence --- cluster/telepresence/Chart.yaml | 7 +++++++ cluster/telepresence/values.yaml | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 cluster/telepresence/Chart.yaml create mode 100644 cluster/telepresence/values.yaml diff --git a/cluster/telepresence/Chart.yaml b/cluster/telepresence/Chart.yaml new file mode 100644 index 00000000..2596ffc2 --- /dev/null +++ b/cluster/telepresence/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +name: telepresence +version: 1.0.0 +dependencies: + - name: telepresence-oss + version: 2.21.3 + repository: oci://ghcr.io/telepresenceio \ No newline at end of file diff --git a/cluster/telepresence/values.yaml b/cluster/telepresence/values.yaml new file mode 100644 index 00000000..72fa2a8d --- /dev/null +++ b/cluster/telepresence/values.yaml @@ -0,0 +1,3 @@ +namespace: telepresence +telepresence: + releaseName: traffic-manager \ No newline at end of file From 946624cba578e4ef177041a64620f44f993613fd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:53:33 +0000 Subject: [PATCH 02/27] Update telepresence-oss Docker tag to v2.25.1 --- cluster/telepresence/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster/telepresence/Chart.yaml b/cluster/telepresence/Chart.yaml index 2596ffc2..34f52f1e 100644 --- a/cluster/telepresence/Chart.yaml +++ b/cluster/telepresence/Chart.yaml @@ -3,5 +3,5 @@ name: telepresence version: 1.0.0 dependencies: - name: telepresence-oss - version: 2.21.3 + version: 2.25.1 repository: oci://ghcr.io/telepresenceio \ No newline at end of file From ba2fbcf584d247710901db1d57fbaaca1a2368d7 Mon Sep 17 00:00:00 2001 From: Sheikah45 <66929319+Sheikah45@users.noreply.github.com> Date: Sun, 23 Mar 2025 15:18:33 -0400 Subject: [PATCH 03/27] Add replays old to content (#204) * Update values.yaml * Update deployment.yaml --- apps/faf-content/templates/deployment.yaml | 7 +++++++ cluster/storage/values.yaml | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/apps/faf-content/templates/deployment.yaml b/apps/faf-content/templates/deployment.yaml index c84d6417..d06d01e6 100644 --- a/apps/faf-content/templates/deployment.yaml +++ b/apps/faf-content/templates/deployment.yaml @@ -33,6 +33,9 @@ spec: - name: faf-replays-pvc mountPath: /data/replays readOnly: true + - name: faf-replays-old-pvc + mountPath: /data/replays-old + readOnly: true - name: faf-maps-pvc mountPath: /data/maps readOnly: true @@ -69,6 +72,10 @@ spec: persistentVolumeClaim: claimName: faf-replays-pvc readOnly: true + - name: faf-replays-old-pvc + persistentVolumeClaim: + claimName: faf-replays-old-pvc + readOnly: true - name: faf-maps-pvc persistentVolumeClaim: claimName: faf-maps-pvc diff --git a/cluster/storage/values.yaml b/cluster/storage/values.yaml index 374e8dd4..5abc8e17 100644 --- a/cluster/storage/values.yaml +++ b/cluster/storage/values.yaml @@ -24,6 +24,12 @@ managedStorages: size: 500Gi pvc: namespace: faf-apps + - pv: + name: faf-replays-old + folderName: replays-old + size: 500Gi + pvc: + namespace: faf-apps - pv: name: faf-maps folderName: maps From 78ac611c49af6bc63c36960be94a3937d9174253 Mon Sep 17 00:00:00 2001 From: Sheikah45 <66929319+Sheikah45@users.noreply.github.com> Date: Sun, 23 Mar 2025 15:32:58 -0400 Subject: [PATCH 04/27] not read only attempy --- apps/faf-content/templates/deployment.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/faf-content/templates/deployment.yaml b/apps/faf-content/templates/deployment.yaml index d06d01e6..77fc7c23 100644 --- a/apps/faf-content/templates/deployment.yaml +++ b/apps/faf-content/templates/deployment.yaml @@ -35,7 +35,6 @@ spec: readOnly: true - name: faf-replays-old-pvc mountPath: /data/replays-old - readOnly: true - name: faf-maps-pvc mountPath: /data/maps readOnly: true @@ -75,7 +74,6 @@ spec: - name: faf-replays-old-pvc persistentVolumeClaim: claimName: faf-replays-old-pvc - readOnly: true - name: faf-maps-pvc persistentVolumeClaim: claimName: faf-maps-pvc From a36c92e9972c56a174689f52ec2ddf7df64d7260 Mon Sep 17 00:00:00 2001 From: Sheikah45 <66929319+Sheikah45@users.noreply.github.com> Date: Sun, 23 Mar 2025 15:33:46 -0400 Subject: [PATCH 05/27] remove replays-old mount to nginx --- apps/faf-content/templates/deployment.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/faf-content/templates/deployment.yaml b/apps/faf-content/templates/deployment.yaml index 77fc7c23..c84d6417 100644 --- a/apps/faf-content/templates/deployment.yaml +++ b/apps/faf-content/templates/deployment.yaml @@ -33,8 +33,6 @@ spec: - name: faf-replays-pvc mountPath: /data/replays readOnly: true - - name: faf-replays-old-pvc - mountPath: /data/replays-old - name: faf-maps-pvc mountPath: /data/maps readOnly: true @@ -71,9 +69,6 @@ spec: persistentVolumeClaim: claimName: faf-replays-pvc readOnly: true - - name: faf-replays-old-pvc - persistentVolumeClaim: - claimName: faf-replays-old-pvc - name: faf-maps-pvc persistentVolumeClaim: claimName: faf-maps-pvc From 97f2d9b1954cf4f40bc21b5965ae9ab8581575fd Mon Sep 17 00:00:00 2001 From: Sheikah45 <66929319+Sheikah45@users.noreply.github.com> Date: Sat, 3 May 2025 18:13:06 -0400 Subject: [PATCH 06/27] Change delay to 30 seconds --- apps/faf-replay-server/config/config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/faf-replay-server/config/config.yaml b/apps/faf-replay-server/config/config.yaml index c46329c7..8c15c806 100644 --- a/apps/faf-replay-server/config/config.yaml +++ b/apps/faf-replay-server/config/config.yaml @@ -17,7 +17,7 @@ storage: replay: forced_timeout_s: 18000 time_with_zero_writers_to_end_replay_s: 30 - delay_s: 300 + delay_s: 30 update_interval_s: 1 merge_quorum_size: 2 - stream_comparison_distance_b: 4096 \ No newline at end of file + stream_comparison_distance_b: 4096 From 06556a0f8e2e0d3df0a8fd5bc60fb571d245428c Mon Sep 17 00:00:00 2001 From: BlackYps <52536103+BlackYps@users.noreply.github.com> Date: Thu, 8 May 2025 20:36:55 +0200 Subject: [PATCH 07/27] Allow top players to match with anyone in the 1v1 queue (#211) * Let top players match with anyone in the 1v1 queue --- apps/faf-lobby-server/config/config.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/faf-lobby-server/config/config.yaml b/apps/faf-lobby-server/config/config.yaml index 5a5679e6..62b83932 100644 --- a/apps/faf-lobby-server/config/config.yaml +++ b/apps/faf-lobby-server/config/config.yaml @@ -40,6 +40,9 @@ NEWBIE_TIME_BONUS: .25 MAXIMUM_NEWBIE_TIME_BONUS: 3.0 MINORITY_BONUS: 1 +LADDER_TOP_PLAYER_SEARCH_EXPANSION_MAX: 1.0 +LADDER_TOP_PLAYER_SEARCH_EXPANSION_STEP: 1.0 + QUEUE_POP_TIME_MAX: 90 # LADDER_VIOLATIONS_ENABLED: false \ No newline at end of file From 474a6625c001b9d0c273493351cf49b4ec71d894 Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Fri, 5 Sep 2025 21:19:47 +0200 Subject: [PATCH 08/27] Enable force relay --- apps/faf-icebreaker/templates/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/faf-icebreaker/templates/config.yaml b/apps/faf-icebreaker/templates/config.yaml index 3f8c43ff..2d9868ac 100644 --- a/apps/faf-icebreaker/templates/config.yaml +++ b/apps/faf-icebreaker/templates/config.yaml @@ -19,5 +19,5 @@ data: XIRSYS_TURN_ENABLED: "true" GEOIPUPDATE_EDITION_IDS: "GeoLite2-City" LOKI_BASE_URL: "http://monitoring-loki-gateway.faf-ops.svc" - FORCE_RELAY: "false" + FORCE_RELAY: "true" QUARKUS_LOG_CATEGORY__COM_FAFOREVER__LEVEL: "DEBUG" \ No newline at end of file From 87aff3e32d702079992b54c00c1d891b62b6377a Mon Sep 17 00:00:00 2001 From: Ivan-Shaml <72102779+Ivan-Shaml@users.noreply.github.com> Date: Mon, 8 Sep 2025 00:13:35 +0300 Subject: [PATCH 09/27] ice-breaker set to trace (attempt 1) --- apps/faf-icebreaker/templates/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/faf-icebreaker/templates/config.yaml b/apps/faf-icebreaker/templates/config.yaml index 2d9868ac..b31258ba 100644 --- a/apps/faf-icebreaker/templates/config.yaml +++ b/apps/faf-icebreaker/templates/config.yaml @@ -20,4 +20,4 @@ data: GEOIPUPDATE_EDITION_IDS: "GeoLite2-City" LOKI_BASE_URL: "http://monitoring-loki-gateway.faf-ops.svc" FORCE_RELAY: "true" - QUARKUS_LOG_CATEGORY__COM_FAFOREVER__LEVEL: "DEBUG" \ No newline at end of file + QUARKUS_LOG_CATEGORY__COM_FAFOREVER__LEVEL: "TRACE" \ No newline at end of file From 3d17dc8053404f82676e03f2bf9de9ad40345f31 Mon Sep 17 00:00:00 2001 From: Pablo <42.pablo.ms@gmail.com> Date: Sun, 21 Sep 2025 17:33:20 +0200 Subject: [PATCH 10/27] Drop promtail --- ops/monitoring/Chart.yaml | 6 +++--- ops/monitoring/values-test.yaml | 3 +++ ops/monitoring/values.yaml | 23 +++++++++++++++++++---- 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 ops/monitoring/values-test.yaml diff --git a/ops/monitoring/Chart.yaml b/ops/monitoring/Chart.yaml index 541e70ff..a787d872 100644 --- a/ops/monitoring/Chart.yaml +++ b/ops/monitoring/Chart.yaml @@ -5,9 +5,9 @@ dependencies: - name: victoria-metrics-k8s-stack version: 0.60.1 repository: https://victoriametrics.github.io/helm-charts/ -- name: promtail - version: 6.17.0 - repository: https://grafana.github.io/helm-charts - name: loki version: 6.40.0 repository: https://grafana.github.io/helm-charts +- name: k8s-monitoring + version: 3.5.1 + repository: https://grafana.github.io/helm-charts diff --git a/ops/monitoring/values-test.yaml b/ops/monitoring/values-test.yaml new file mode 100644 index 00000000..4bce1a76 --- /dev/null +++ b/ops/monitoring/values-test.yaml @@ -0,0 +1,3 @@ +k8s-monitoring: + cluster: + name: faforever-xyz \ No newline at end of file diff --git a/ops/monitoring/values.yaml b/ops/monitoring/values.yaml index 05ce39f8..edb1b096 100644 --- a/ops/monitoring/values.yaml +++ b/ops/monitoring/values.yaml @@ -145,7 +145,22 @@ loki: chunksCache: enabled: false -promtail: - config: - clients: - - url: http://monitoring-loki-gateway/loki/api/v1/push + +k8s-monitoring: + # Where Alloy pushes data to + # We can add our own Loki, Thanos... + destinations: + - name: local-loki + type: loki + url: http://monitoring-loki-gateway/loki/api/v1/push + + # We are using 1% of this chart, just logs for now, as it implements lots of things that vm-stack provides + # We could switch to this + vmsingle + + # Collectors + alloy-logs: + enabled: true + + # Features + podLogs: + enabled: true \ No newline at end of file From 5b225682ad8408d1dff16ed28587b740a5051bb0 Mon Sep 17 00:00:00 2001 From: Pablo <42.pablo.ms@gmail.com> Date: Sun, 21 Sep 2025 17:43:54 +0200 Subject: [PATCH 11/27] Add a values for alloy prod --- ops/monitoring/values-prod.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ops/monitoring/values-prod.yaml diff --git a/ops/monitoring/values-prod.yaml b/ops/monitoring/values-prod.yaml new file mode 100644 index 00000000..063295f5 --- /dev/null +++ b/ops/monitoring/values-prod.yaml @@ -0,0 +1,3 @@ +k8s-monitoring: + cluster: + name: faforever-com \ No newline at end of file From 198e70643796f85ee3050a37e07e41f25c438451 Mon Sep 17 00:00:00 2001 From: Pablo <42.pablo.ms@gmail.com> Date: Sun, 21 Sep 2025 17:47:56 +0200 Subject: [PATCH 12/27] Fix duped yaml key --- ops/monitoring/values.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/ops/monitoring/values.yaml b/ops/monitoring/values.yaml index edb1b096..c94dbd5d 100644 --- a/ops/monitoring/values.yaml +++ b/ops/monitoring/values.yaml @@ -131,9 +131,6 @@ loki: limits_config: retention_period: 168h - limits_config: - retention_period: 168h - # We know it's working test: enabled: false From fda5868c37c5c1be3ee6c5ec9cc44c6e1719afc6 Mon Sep 17 00:00:00 2001 From: Pablo <42.pablo.ms@gmail.com> Date: Sun, 21 Sep 2025 17:54:00 +0200 Subject: [PATCH 13/27] No affinity for loki gw --- ops/monitoring/values.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ops/monitoring/values.yaml b/ops/monitoring/values.yaml index c94dbd5d..f4d526d9 100644 --- a/ops/monitoring/values.yaml +++ b/ops/monitoring/values.yaml @@ -142,6 +142,9 @@ loki: chunksCache: enabled: false + gateway: + affinity: null + k8s-monitoring: # Where Alloy pushes data to From dbeb790a617b9b1891414c40fb3d2d20c978d7e1 Mon Sep 17 00:00:00 2001 From: Pablo <42.pablo.ms@gmail.com> Date: Sun, 21 Sep 2025 18:06:10 +0200 Subject: [PATCH 14/27] No affinity for loki --- ops/monitoring/values.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ops/monitoring/values.yaml b/ops/monitoring/values.yaml index f4d526d9..82155036 100644 --- a/ops/monitoring/values.yaml +++ b/ops/monitoring/values.yaml @@ -142,6 +142,10 @@ loki: chunksCache: enabled: false + # Disable anti-affinity + singleBinary: + affinity: null + gateway: affinity: null From 8e22a57a337b5f34fe1b9ff3026624a5744678a9 Mon Sep 17 00:00:00 2001 From: Pablo <42.pablo.ms@gmail.com> Date: Sun, 21 Sep 2025 18:07:51 +0200 Subject: [PATCH 15/27] Fix yaml --- ops/monitoring/values.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ops/monitoring/values.yaml b/ops/monitoring/values.yaml index 82155036..1113c317 100644 --- a/ops/monitoring/values.yaml +++ b/ops/monitoring/values.yaml @@ -94,8 +94,6 @@ victoria-metrics-k8s-stack: loki: deploymentMode: SingleBinary - singleBinary: - replicas: 1 # Disable Simple Scalable read: @@ -144,6 +142,7 @@ loki: # Disable anti-affinity singleBinary: + replicas: 1 affinity: null gateway: From dff270a85745d56a5bd00859f5a5dfdf32f44385 Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Tue, 21 Oct 2025 22:29:43 +0200 Subject: [PATCH 16/27] Python server log level DEBUG --- apps/faf-lobby-server/config/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/faf-lobby-server/config/config.yaml b/apps/faf-lobby-server/config/config.yaml index 62b83932..77514dc2 100644 --- a/apps/faf-lobby-server/config/config.yaml +++ b/apps/faf-lobby-server/config/config.yaml @@ -1,5 +1,5 @@ CONFIGURATION_REFRESH_TIME: 60 -LOG_LEVEL: "INFO" +LOG_LEVEL: "DEBUG" LISTEN: - ADDRESS: From 1e95f1a1001c4c71ef34db46d132ec126c78615b Mon Sep 17 00:00:00 2001 From: Pablo <42.pablo.ms@gmail.com> Date: Sun, 21 Sep 2025 17:33:20 +0200 Subject: [PATCH 17/27] Drop promtail --- ops/monitoring/Chart.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ops/monitoring/Chart.yaml b/ops/monitoring/Chart.yaml index a787d872..b48a5be9 100644 --- a/ops/monitoring/Chart.yaml +++ b/ops/monitoring/Chart.yaml @@ -11,3 +11,6 @@ dependencies: - name: k8s-monitoring version: 3.5.1 repository: https://grafana.github.io/helm-charts +- name: k8s-monitoring + version: 3.5.1 + repository: https://grafana.github.io/helm-charts From 322d719808efc5a67647655f0166d6778d62a97b Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Sat, 20 Dec 2025 14:46:54 +0100 Subject: [PATCH 18/27] Update faf-icebreaker to 1.2.0-RC2 --- apps/faf-icebreaker/templates/config.yaml | 1 + apps/faf-icebreaker/templates/deployment.yaml | 2 +- apps/faf-icebreaker/templates/local-secret.yaml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/faf-icebreaker/templates/config.yaml b/apps/faf-icebreaker/templates/config.yaml index b31258ba..3a2567cb 100644 --- a/apps/faf-icebreaker/templates/config.yaml +++ b/apps/faf-icebreaker/templates/config.yaml @@ -20,4 +20,5 @@ data: GEOIPUPDATE_EDITION_IDS: "GeoLite2-City" LOKI_BASE_URL: "http://monitoring-loki-gateway.faf-ops.svc" FORCE_RELAY: "true" + REAL_IP_HEADER: "Cf-Connecting-Ip" QUARKUS_LOG_CATEGORY__COM_FAFOREVER__LEVEL: "TRACE" \ No newline at end of file diff --git a/apps/faf-icebreaker/templates/deployment.yaml b/apps/faf-icebreaker/templates/deployment.yaml index f38ed8ef..b9accae9 100644 --- a/apps/faf-icebreaker/templates/deployment.yaml +++ b/apps/faf-icebreaker/templates/deployment.yaml @@ -32,7 +32,7 @@ spec: - name: geolite-db mountPath: /usr/share/GeoIP containers: - - image: faforever/faf-icebreaker:1.1.9 + - image: faforever/faf-icebreaker:1.2.0-RC2 imagePullPolicy: Always name: faf-icebreaker envFrom: diff --git a/apps/faf-icebreaker/templates/local-secret.yaml b/apps/faf-icebreaker/templates/local-secret.yaml index 31d072e5..481deae6 100644 --- a/apps/faf-icebreaker/templates/local-secret.yaml +++ b/apps/faf-icebreaker/templates/local-secret.yaml @@ -13,6 +13,7 @@ stringData: RABBITMQ_PASSWORD: "banana" XIRSYS_IDENT: "banana" XIRSYS_SECRET: "banana" + HETZNER_API_KEY: "banana" JWT_PRIVATE_KEY_PATH: |- -----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDXsCsl9W0vnW2k From 8e94f992973342f41a864430a1c334b7d43fd418 Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Sun, 23 Nov 2025 10:30:24 +0100 Subject: [PATCH 19/27] Enable Cloudflare --- apps/faf-icebreaker/templates/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/faf-icebreaker/templates/config.yaml b/apps/faf-icebreaker/templates/config.yaml index 3a2567cb..5890ab6e 100644 --- a/apps/faf-icebreaker/templates/config.yaml +++ b/apps/faf-icebreaker/templates/config.yaml @@ -14,7 +14,7 @@ data: RABBITMQ_HOST: "rabbitmq" RABBITMQ_USER: "faf-icebreaker" RABBITMQ_PORT: "5672" - CLOUDFLARE_ENABLED: "false" + CLOUDFLARE_ENABLED: "true" XIRSYS_ENABLED: "true" XIRSYS_TURN_ENABLED: "true" GEOIPUPDATE_EDITION_IDS: "GeoLite2-City" From 3a9daa6bb0bd396dd786e3f2b5ba66a20c62faec Mon Sep 17 00:00:00 2001 From: Sheikah45 Date: Sat, 27 Dec 2025 11:24:42 -0500 Subject: [PATCH 20/27] Update icebreaker to RC4 --- apps/faf-icebreaker/templates/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/faf-icebreaker/templates/deployment.yaml b/apps/faf-icebreaker/templates/deployment.yaml index b9accae9..6c0e0c1d 100644 --- a/apps/faf-icebreaker/templates/deployment.yaml +++ b/apps/faf-icebreaker/templates/deployment.yaml @@ -32,7 +32,7 @@ spec: - name: geolite-db mountPath: /usr/share/GeoIP containers: - - image: faforever/faf-icebreaker:1.2.0-RC2 + - image: faforever/faf-icebreaker:1.2.0-RC4 imagePullPolicy: Always name: faf-icebreaker envFrom: From 7fef124c0eb9c675305b8789a5745d080418893d Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Sat, 27 Dec 2025 22:06:47 +0100 Subject: [PATCH 21/27] Fix deploy coop maps --- .../templates/deploy-coop-maps.yaml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml index f1629a38..4e99169a 100644 --- a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml +++ b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml @@ -5,11 +5,9 @@ metadata: labels: app: faf-deploy-coop-maps data: - PATCH_VERSION: "65" + PATCH_VERSION: "v9.0.2" DATABASE_HOST: "mariadb" DATABASE_NAME: "faf_lobby" - "deploy-coop-maps.py": |- -{{ tpl ( .Files.Get "scripts/deploy-coop-maps.py" ) . | indent 4 }} --- @@ -44,13 +42,22 @@ spec: - secretRef: name: faf-legacy-deployment command: [ "sh" ] - args: [ "-c", "pip install mysql-connector-python && python3 /tmp/deploy-coop-maps.py" ] + args: [ "-c", "pip install mysql-connector-python && python3 /tmp/deploy-coop-maps.py $PATCH_VERSION --git-directory /tmp/maps" ] volumeMounts: - mountPath: /tmp/deploy-coop-maps.py - name: faf-deploy-coop-maps + name: faf-deploy-scripts subPath: "deploy-coop-maps.py" + - mountPath: /opt/faf/data/maps + name: faf-maps restartPolicy: Never volumes: + - name: faf-deploy-scripts + configMap: + name: "faf-deploy-scripts" - name: faf-deploy-coop-maps configMap: name: "faf-deploy-coop-maps" + - name: faf-maps + hostPath: + path: /opt/faf/data/maps + type: Directory \ No newline at end of file From 164d34c166abded4b249c54d55d65845c8b4d7c1 Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Sat, 27 Dec 2025 22:24:27 +0100 Subject: [PATCH 22/27] Fix deploy coop maps --- .../faf-legacy-deployment/templates/deploy-coop-maps.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml index 4e99169a..a11b8468 100644 --- a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml +++ b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml @@ -42,13 +42,15 @@ spec: - secretRef: name: faf-legacy-deployment command: [ "sh" ] - args: [ "-c", "pip install mysql-connector-python && python3 /tmp/deploy-coop-maps.py $PATCH_VERSION --git-directory /tmp/maps" ] + args: [ "-c", "pip install mysql-connector-python && python3 /tmp/deploy-coop-maps.py $PATCH_VERSION" ] volumeMounts: - mountPath: /tmp/deploy-coop-maps.py name: faf-deploy-scripts subPath: "deploy-coop-maps.py" - mountPath: /opt/faf/data/maps name: faf-maps + - mountPath: /opt/featured-mods/faf-coop-maps + name: faf-coop-maps restartPolicy: Never volumes: - name: faf-deploy-scripts @@ -60,4 +62,8 @@ spec: - name: faf-maps hostPath: path: /opt/faf/data/maps + type: Directory + - name: faf-coop-maps + hostPath: + path: /opt/faf/data/faf-coop-maps type: Directory \ No newline at end of file From c0a8fdec0de92994c81fd1d021bcc6f82d8c8037 Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Sun, 28 Dec 2025 11:16:20 +0100 Subject: [PATCH 23/27] Remove docker-based run_sql --- .../scripts/deploy-coop-maps.py | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/apps/faf-legacy-deployment/scripts/deploy-coop-maps.py b/apps/faf-legacy-deployment/scripts/deploy-coop-maps.py index b28badef..1b67c9e1 100644 --- a/apps/faf-legacy-deployment/scripts/deploy-coop-maps.py +++ b/apps/faf-legacy-deployment/scripts/deploy-coop-maps.py @@ -195,7 +195,7 @@ def new_file_is_different(old_file_name: str, new_file_name: str) -> bool: return old_file_md5 != new_file_md5 -def update_database(coop_map: CoopMap, new_version: int) -> None: +def update_database(conn, coop_map: CoopMap, new_version: int) -> None: logger.debug(f"Updating coop map {coop_map} in database to version {new_version}") query = f""" @@ -203,7 +203,7 @@ def update_database(coop_map: CoopMap, new_version: int) -> None: SET version = {new_version}, filename = "maps/{coop_map.build_zip_filename(new_version)}" WHERE id = {coop_map.map_id} """ - run_sql(query) + run_sql(conn, query) def copytree(src, dst, symlinks=False, ignore=None): @@ -229,7 +229,7 @@ def create_zip_package(coop_map: CoopMap, version: int, files: List[str], tmp_fo zip_file.write(path, arcname=f"/{coop_map.build_folder_name(version)}/{os.path.relpath(path, tmp_folder_path)}") -def process_coop_map(coop_map: CoopMap, simulate: bool, git_directory:str, coop_maps_path: str): +def process_coop_map(conn, coop_map: CoopMap, simulate: bool, git_directory:str, coop_maps_path: str): logger.info(f"Processing: {coop_map}") temp_dir = TemporaryDirectory() @@ -260,7 +260,7 @@ def process_coop_map(coop_map: CoopMap, simulate: bool, git_directory:str, coop_ zip_file_path = os.path.join(coop_maps_path, coop_map.build_zip_filename(new_version)) create_zip_package(coop_map, new_version, processing_files, temp_dir.name, zip_file_path) - update_database(coop_map, new_version) + update_database(conn, coop_map, new_version) else: logger.info(f"Updating database skipped due to simulation") else: @@ -290,26 +290,6 @@ def run_checked_shell(cmd: List[str]) -> subprocess.CompletedProcess: return subprocess.run(cmd, check=True, stdout=subprocess.PIPE) -def run_sql(sql: str, container: str = "faf-db", database: str = "faf_lobby") -> str: - - """ - Run a sql-query against the faf-db in the docker container - :param database: name of the database where to run the query - :param container: name of the docker container where to run the query - :param sql: the sql-query to run - :return: the query output as string - """ - try: - sql_text_result = run_checked_shell( - ["docker", "exec", "-u", "root", container, "mysql", database, "-e", sql] - ).stdout.decode() # type: str - logger.debug(f"SQL output >>> \n{sql_text_result}<<<") - return sql_text_result - except subprocess.CalledProcessError as e: - logger.error(f"""Executing sql query failed: {sql}\n\t\tError message: {str(e)}""") - exit(1) - - def git_checkout(path: str, tag: str) -> None: """ Checkout a git tag of the git repository. This requires the repo to be checked out in the path folder! @@ -373,9 +353,10 @@ def create_zip(content: List[str], relative_to: str, output_file: str) -> None: args = parser.parse_args() git_checkout(args.git_directory, args.version) + conn = get_db_connection() for coop_map in coop_maps: try: - process_coop_map(coop_map, args.simulate, args.git_directory, args.coop_maps_path) + process_coop_map(conn, coop_map, args.simulate, args.git_directory, args.coop_maps_path) except Exception as error: logger.warning(f"Unable to parse {coop_map}", exc_info=True) From 36a4e15c5b2cc60a70181e3e50fa5de7eafe9aae Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Mon, 29 Dec 2025 14:06:03 +0100 Subject: [PATCH 24/27] Seems to work --- .../scripts/CoopDeployer.kt | 12 +- .../scripts/CoopMapDeployer.kt | 298 ++++++++++++++ .../scripts/build.gradle.kts | 22 +- .../scripts/deploy-coop-maps.py | 362 ------------------ .../templates/deploy-coop-maps.yaml | 35 +- .../templates/deploy-coop.yaml | 2 +- 6 files changed, 337 insertions(+), 394 deletions(-) create mode 100644 apps/faf-legacy-deployment/scripts/CoopMapDeployer.kt delete mode 100644 apps/faf-legacy-deployment/scripts/deploy-coop-maps.py diff --git a/apps/faf-legacy-deployment/scripts/CoopDeployer.kt b/apps/faf-legacy-deployment/scripts/CoopDeployer.kt index 4e3859cb..4a57e91c 100755 --- a/apps/faf-legacy-deployment/scripts/CoopDeployer.kt +++ b/apps/faf-legacy-deployment/scripts/CoopDeployer.kt @@ -1,4 +1,7 @@ -import org.apache.commons.compress.archivers.zip.Zip64Mode +@file:Suppress("PackageDirectoryMismatch") + +package com.faforever.coopdeployer + import org.apache.commons.compress.archivers.zip.ZipArchiveEntry import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream import org.eclipse.jgit.api.Git @@ -18,9 +21,6 @@ import java.security.MessageDigest import java.sql.Connection import java.sql.DriverManager import java.time.Duration -import java.util.zip.CRC32 -import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream import kotlin.io.path.inputStream private val log = LoggerFactory.getLogger("CoopDeployer") @@ -34,7 +34,7 @@ fun Path.setPerm664() { Files.setPosixFilePermissions(this, perms) } -data class FeatureModGitRepo( +data class GitRepo( val workDir: Path, val repoUrl: String, val gitRef: String, @@ -424,7 +424,7 @@ fun main() { log.info("=== Kotlin Coop Deployer v{} ===", PATCH_VERSION) - val repo = FeatureModGitRepo( + val repo = GitRepo( workDir = Paths.get(WORKDIR), repoUrl = REPO_URL, gitRef = GIT_REF diff --git a/apps/faf-legacy-deployment/scripts/CoopMapDeployer.kt b/apps/faf-legacy-deployment/scripts/CoopMapDeployer.kt new file mode 100644 index 00000000..35c2c217 --- /dev/null +++ b/apps/faf-legacy-deployment/scripts/CoopMapDeployer.kt @@ -0,0 +1,298 @@ +@file:Suppress("PackageDirectoryMismatch") + +package com.faforever.coopmapdeployer + +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.Level +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import org.eclipse.jgit.api.Git +import org.slf4j.LoggerFactory +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.attribute.FileTime +import java.security.MessageDigest +import java.sql.Connection +import java.sql.DriverManager +import kotlin.io.path.copyTo +import kotlin.io.path.createDirectories +import kotlin.io.path.exists +import kotlin.io.path.isDirectory +import kotlin.io.path.isRegularFile +import kotlin.io.path.readBytes +import kotlin.io.path.readText +import kotlin.io.path.walk + +private val log = LoggerFactory.getLogger("coop-maps-updater") + + +private const val FIXED_TIMESTAMP = 1078100502L // 2004-03-01T00:21:42Z +private val FIXED_FILE_TIME = FileTime.fromMillis(FIXED_TIMESTAMP) + +data class GitRepo( + val workDir: Path, + val repoUrl: String, + val gitRef: String, +) { + fun checkout(): Path { + if (Files.exists(workDir.resolve(".git"))) { + log.info("Repo exists — fetching and checking out $gitRef...") + Git.open(workDir.toFile()).use { git -> + git.fetch().call() + git.checkout().setName(gitRef).call() + } + } else { + log.info("Cloning repository $repoUrl") + Git.cloneRepository() + .setURI(repoUrl) + .setDirectory(workDir.toFile()) + .call() + log.info("Checking out $gitRef") + Git.open(workDir.toFile()).use { git -> + git.checkout().setName(gitRef).call() + } + } + + return workDir + } +} + + +data class CoopMap( + val folderName: String, + val mapId: Int, + val mapType: Int +) { + fun zipName(version: Int) = + "${folderName.lowercase()}.v${version.toString().padStart(4, '0')}.zip" + + fun folderName(version: Int) = + "${folderName.lowercase()}.v${version.toString().padStart(4, '0')}" +} + +private val coopMaps = listOf( + CoopMap("X1CA_Coop_001", 1, 0), + CoopMap("X1CA_Coop_002", 3, 0), + CoopMap("X1CA_Coop_003", 4, 0), + CoopMap("X1CA_Coop_004", 5, 0), + CoopMap("X1CA_Coop_005", 6, 0), + CoopMap("X1CA_Coop_006", 7, 0), + + CoopMap("SCCA_Coop_A01", 8, 1), + CoopMap("SCCA_Coop_A02", 9, 1), + CoopMap("SCCA_Coop_A03", 10, 1), + CoopMap("SCCA_Coop_A04", 11, 1), + CoopMap("SCCA_Coop_A05", 12, 1), + CoopMap("SCCA_Coop_A06", 13, 1), + + CoopMap("SCCA_Coop_R01", 20, 2), + CoopMap("SCCA_Coop_R02", 21, 2), + CoopMap("SCCA_Coop_R03", 22, 2), + CoopMap("SCCA_Coop_R04", 23, 2), + CoopMap("SCCA_Coop_R05", 24, 2), + CoopMap("SCCA_Coop_R06", 25, 2), + + CoopMap("SCCA_Coop_E01", 14, 3), + CoopMap("SCCA_Coop_E02", 15, 3), + CoopMap("SCCA_Coop_E03", 16, 3), + CoopMap("SCCA_Coop_E04", 17, 3), + CoopMap("SCCA_Coop_E05", 18, 3), + CoopMap("SCCA_Coop_E06", 19, 3), + + CoopMap("FAF_Coop_Prothyon_16", 26, 4), + CoopMap("FAF_Coop_Fort_Clarke_Assault", 27, 4), + CoopMap("FAF_Coop_Theta_Civilian_Rescue", 28, 4), + CoopMap("FAF_Coop_Novax_Station_Assault", 31, 4), + CoopMap("FAF_Coop_Operation_Tha_Atha_Aez", 32, 4), + CoopMap("FAF_Coop_Havens_Invasion", 33, 4), + CoopMap("FAF_Coop_Operation_Rescue", 35, 4), + CoopMap("FAF_Coop_Operation_Uhthe_Thuum_QAI", 36, 4), + CoopMap("FAF_Coop_Operation_Yath_Aez", 37, 4), + CoopMap("FAF_Coop_Operation_Ioz_Shavoh_Kael", 38, 4), + CoopMap("FAF_Coop_Operation_Trident", 39, 4), + CoopMap("FAF_Coop_Operation_Blockade", 40, 4), + CoopMap("FAF_Coop_Operation_Golden_Crystals", 41, 4), + CoopMap("FAF_Coop_Operation_Holy_Raid", 42, 4), + CoopMap("FAF_Coop_Operation_Tight_Spot", 45, 4), + CoopMap("FAF_Coop_Operation_Overlord_Surth_Velsok", 47, 4), + CoopMap("FAF_Coop_Operation_Rebels_Rest", 48, 4), + CoopMap("FAF_Coop_Operation_Red_Revenge", 49, 4), +) + +data class FafDatabase( + val host: String, + val database: String, + val username: String, + val password: String, + val dryRun: Boolean +) : AutoCloseable { + private val connection: Connection = + DriverManager.getConnection( + "jdbc:mariadb://$host/$database?useSSL=false&serverTimezone=UTC", + username, + password + ) + + fun getLatestVersion(map: CoopMap): Int { + connection.createStatement().use { st -> + st.executeQuery("SELECT version FROM coop_map WHERE id=${map.mapId}") + .use { rs -> + if (!rs.next()) error("Map ${map.mapId} not found") + return rs.getInt(1) + } + } + } + + fun updateDatabase(map: CoopMap, version: Int) { + val sql = """ + UPDATE coop_map + SET version=$version, + filename='maps/${map.zipName(version)}' + WHERE id=${map.mapId} + """.trimIndent() + + connection.createStatement().use { it.executeUpdate(sql) } + } + + override fun close() { + connection.close() + } +} + +private fun processCoopMap( + db: FafDatabase, + map: CoopMap, + simulate: Boolean, + gitDir: String, + mapsDir: String +) { + log.info("Processing $map") + + val tmp = Files.createTempDirectory("coop-map") + try { + Files.walk(Path.of(gitDir, map.folderName)).forEach { + val target = tmp.resolve(Path.of(gitDir, map.folderName).relativize(it)) + if (it.isDirectory()) target.createDirectories() + else it.copyTo(target) + } + + val files = tmp.walk().filter { it.isRegularFile() }.toList() + val currentVersion = db.getLatestVersion(map) + + val currentZip = Path.of(mapsDir, map.zipName(currentVersion)) + val tmpZip = tmp.resolve(map.zipName(currentVersion)) + + createZip(map, currentVersion, files, tmp, tmpZip) + + val changed = currentVersion == 0 || + !currentZip.exists() || + md5(currentZip) != md5(tmpZip) + + if (!changed) { + log.info("$map unchanged") + return + } + + val newVersion = currentVersion + 1 + log.info("$map updated → v$newVersion") + + if (!simulate) { + val finalZip = Path.of(mapsDir, map.zipName(newVersion)) + createZip(map, newVersion, files, tmp, finalZip) + db.updateDatabase(map, newVersion) + } + } finally { + tmp.toFile().deleteRecursively() + } +} + +private fun createZip( + map: CoopMap, + version: Int, + files: List, + base: Path, + out: Path +) { + ZipArchiveOutputStream(out.toFile()).use { zip -> + zip.setMethod(ZipArchiveEntry.DEFLATED) + + files.forEach { file -> + val rel = base.relativize(file) + val entryPath = "/${map.folderName(version)}/$rel" + + val bytes = file.readText() + .replace( + "/maps/${map.folderName}/", + "/maps/${map.folderName(version)}/" + ).toByteArray() + + val entry = ZipArchiveEntry(entryPath).apply { + // Ensure deterministic times + setTime(FIXED_FILE_TIME) + setCreationTime(FIXED_FILE_TIME) + setLastModifiedTime(FIXED_FILE_TIME) + setLastAccessTime(FIXED_FILE_TIME) + + size = bytes.size.toLong() + } + + zip.putArchiveEntry(entry) + zip.write(bytes) + zip.closeArchiveEntry() + } + + zip.finish() + } +} + +private fun md5(path: Path): String { + val md = MessageDigest.getInstance("MD5") + md.update(path.readBytes()) + return md.digest().joinToString("") { "%02x".format(it) } +} + +fun main(args: Array) { + val MAP_DIR = System.getenv("MAP_DIR") ?: "/opt/faf/data/faf-coop-maps" + val PATCH_VERSION = System.getenv("PATCH_VERSION") ?: error("PATCH_VERSION required") + val REPO_URL = System.getenv("GIT_REPO_URL") ?: "https://github.com/FAForever/faf-coop-maps" + val GIT_REF = System.getenv("GIT_REF") ?: "v$PATCH_VERSION" + val WORKDIR = System.getenv("GIT_WORKDIR") ?: "/tmp/fa-coop-kt" + val DRYRUN = (System.getenv("DRY_RUN") ?: "false").lowercase() in listOf("1", "true", "yes") + + val DB_HOST = System.getenv("DATABASE_HOST") ?: "localhost" + val DB_NAME = System.getenv("DATABASE_NAME") ?: "faf" + val DB_USER = System.getenv("DATABASE_USERNAME") ?: "root" + val DB_PASS = System.getenv("DATABASE_PASSWORD") ?: "banana" + + val level = System.getenv("LOG_LEVEL") ?: "INFO" + + val root = LoggerFactory + .getLogger(Logger.ROOT_LOGGER_NAME) as Logger + root.level = Level.toLevel(level, Level.INFO) + + Files.createDirectories(Paths.get(MAP_DIR)) + + GitRepo( + workDir = Paths.get(WORKDIR), + repoUrl = REPO_URL, + gitRef = GIT_REF, + ).checkout() + + FafDatabase( + host = DB_HOST, + database = DB_NAME, + username = DB_USER, + password = DB_PASS, + dryRun = DRYRUN + ).use { db -> + coopMaps.forEach { + try { + processCoopMap(db, it, DRYRUN, WORKDIR, MAP_DIR) + } catch (e: Exception) { + log.warn("Failed processing $it", e) + } + } + } + +} diff --git a/apps/faf-legacy-deployment/scripts/build.gradle.kts b/apps/faf-legacy-deployment/scripts/build.gradle.kts index 10f0c44b..de560733 100644 --- a/apps/faf-legacy-deployment/scripts/build.gradle.kts +++ b/apps/faf-legacy-deployment/scripts/build.gradle.kts @@ -12,11 +12,7 @@ dependencies { implementation("org.eclipse.jgit:org.eclipse.jgit:7.5.0.202512021534-r") implementation("org.apache.commons:commons-compress:1.28.0") implementation("org.slf4j:slf4j-api:2.0.13") - runtimeOnly("ch.qos.logback:logback-classic:1.5.23") -} - -application { - mainClass.set("CoopDeployerKt") // filename + Kt + implementation("ch.qos.logback:logback-classic:1.5.23") } // Use the root level for files @@ -24,4 +20,20 @@ sourceSets { main { kotlin.srcDirs(".") } +} + +tasks.register("deployCoop") { + group = "application" + description = "Deploy coop" + + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("com.faforever.coopdeployer.CoopDeployerKt") +} + +tasks.register("deployCoopMaps") { + group = "application" + description = "Deploy coop maps" + + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("com.faforever.coopmapdeployer.CoopMapDeployerKt") } \ No newline at end of file diff --git a/apps/faf-legacy-deployment/scripts/deploy-coop-maps.py b/apps/faf-legacy-deployment/scripts/deploy-coop-maps.py deleted file mode 100644 index 1b67c9e1..00000000 --- a/apps/faf-legacy-deployment/scripts/deploy-coop-maps.py +++ /dev/null @@ -1,362 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- - -""" -clone: https://github.com/FAForever/faf-coop-maps - -FAF coop maps updater - -All default settings are setup for FAF production! -Override the directory settings for local testing. -To get more help run - $ pipenv run patch-coop-maps -h - -Default usage: - $ pipenv run patch-coop-maps -s -""" -import argparse -import hashlib -import logging -import os -import shutil -import subprocess -import sys -import zipfile -from tempfile import TemporaryDirectory -from typing import NamedTuple, List - -import mysql.connector - -logger: logging.Logger = logging.getLogger() -logger.setLevel(logging.DEBUG) - -fixed_file_timestamp = 1078100502 # 2004-03-01T00:21:42Z - - -db_config = { - "host": os.getenv("DATABASE_HOST", "localhost"), - "user": os.getenv("DATABASE_USERNAME", "root"), - "password": os.getenv("DATABASE_PASSWORD", "banana"), - "database": os.getenv("DATABASE_NAME", "faf_lobby"), -} - - -def get_db_connection(): - """Create and return a MySQL connection.""" - try: - conn = mysql.connector.connect(**db_config) - if conn.is_connected(): - logger.debug(f"Connected to MySQL at {db_config['host']}") - return conn - except Error as e: - logger.error(f"MySQL connection failed: {e}") - sys.exit(1) - - -def run_sql(conn, sql: str) -> str: - """ - Run an SQL query directly on the MySQL database instead of via Docker. - Returns output in a string format similar to the old implementation. - """ - logger.debug(f"Executing SQL query:\n{sql}") - try: - with conn.cursor() as cursor: - cursor.execute(sql) - - # If it's a SELECT query, fetch and format results - if sql.strip().lower().startswith("select"): - rows = cursor.fetchall() - column_names = [desc[0] for desc in cursor.description] - # Simulate the Docker mysql CLI tabular text output - lines = ["\t".join(column_names)] - for row in rows: - lines.append("\t".join(str(x) for x in row)) - result = "\n".join(lines) - else: - conn.commit() - result = "Query OK" - - logger.debug(f"SQL result:\n{result}") - return result - - except Error as e: - logger.error(f"SQL execution failed: {e}") - sys.exit(1) - - -class CoopMap(NamedTuple): - folder_name: str - map_id: int - map_type: int - - def build_zip_filename(self, version: int) -> str: - return f"{self.folder_name.lower()}.v{version:04d}.zip" - - def build_folder_name(self, version: int) -> str: - return f"{self.folder_name.lower()}.v{version:04d}" - - -# Coop maps are in db table `coop_map` -coop_maps: List[CoopMap] = [ - # Forged Alliance missions - CoopMap("X1CA_Coop_001", 1, 0), - CoopMap("X1CA_Coop_002", 3, 0), - CoopMap("X1CA_Coop_003", 4, 0), - CoopMap("X1CA_Coop_004", 5, 0), - CoopMap("X1CA_Coop_005", 6, 0), - CoopMap("X1CA_Coop_006", 7, 0), - - # Vanilla Aeon missions - CoopMap("SCCA_Coop_A01", 8, 1), - CoopMap("SCCA_Coop_A02", 9, 1), - CoopMap("SCCA_Coop_A03", 10, 1), - CoopMap("SCCA_Coop_A04", 11, 1), - CoopMap("SCCA_Coop_A05", 12, 1), - CoopMap("SCCA_Coop_A06", 13, 1), - - # Vanilla Cybran missions - CoopMap("SCCA_Coop_R01", 20, 2), - CoopMap("SCCA_Coop_R02", 21, 2), - CoopMap("SCCA_Coop_R03", 22, 2), - CoopMap("SCCA_Coop_R04", 23, 2), - CoopMap("SCCA_Coop_R05", 24, 2), - CoopMap("SCCA_Coop_R06", 25, 2), - - # Vanilla UEF missions - CoopMap("SCCA_Coop_E01", 14, 3), - CoopMap("SCCA_Coop_E02", 15, 3), - CoopMap("SCCA_Coop_E03", 16, 3), - CoopMap("SCCA_Coop_E04", 17, 3), - CoopMap("SCCA_Coop_E05", 18, 3), - CoopMap("SCCA_Coop_E06", 19, 3), - - # Custom missions - CoopMap("FAF_Coop_Prothyon_16", 26, 4), - CoopMap("FAF_Coop_Fort_Clarke_Assault", 27, 4), - CoopMap("FAF_Coop_Theta_Civilian_Rescue", 28, 4), - CoopMap("FAF_Coop_Novax_Station_Assault", 31, 4), - CoopMap("FAF_Coop_Operation_Tha_Atha_Aez", 32, 4), - CoopMap("FAF_Coop_Havens_Invasion", 33, 4), - CoopMap("FAF_Coop_Operation_Rescue", 35, 4), - CoopMap("FAF_Coop_Operation_Uhthe_Thuum_QAI", 36, 4), - CoopMap("FAF_Coop_Operation_Yath_Aez", 37, 4), - CoopMap("FAF_Coop_Operation_Ioz_Shavoh_Kael", 38, 4), - CoopMap("FAF_Coop_Operation_Trident", 39, 4), - CoopMap("FAF_Coop_Operation_Blockade", 40, 4), - CoopMap("FAF_Coop_Operation_Golden_Crystals", 41, 4), - CoopMap("FAF_Coop_Operation_Holy_Raid", 42, 4), - CoopMap("FAF_Coop_Operation_Tight_Spot", 45, 4), - CoopMap("FAF_Coop_Operation_Overlord_Surth_Velsok", 47, 4), - CoopMap("FAF_Coop_Operation_Rebel's_Rest", 48, 4), - CoopMap("FAF_Coop_Operation_Red_Revenge", 49, 4), -] - -def fix_file_timestamps(files: List[str]) -> None: - for file in files: - logger.debug(f"Fixing timestamp in {file}") - os.utime(file, (fixed_file_timestamp, fixed_file_timestamp)) - - -def fix_folder_paths(folder_name: str, files: List[str], new_version: int) -> None: - old_maps_lua_path = f"/maps/{folder_name}/" - new_maps_lua_path = f"/maps/{folder_name.lower()}.v{new_version:04d}/" - - for file in files: - logger.debug(f"Fixing lua folder path in {file}: '{old_maps_lua_path}' -> '{new_maps_lua_path}'") - - with open(file, "rb") as file_handler: - data = file_handler.read() - data = data.replace(old_maps_lua_path.encode(), new_maps_lua_path.encode()) - - with open(file, "wb") as file_handler: - file_handler.seek(0) - file_handler.write(data) - - -def get_latest_map_version(coop_map: CoopMap) -> int: - logger.debug(f"Fetching latest map version for coop map {coop_map}") - - query = f""" - SELECT version FROM coop_map WHERE id = {coop_map.map_id}; - """ - result = run_sql(query).split("\n") - assert len(result) == 3, f"Mysql returned wrong result! Either map id {coop_map.map_id} is not in table coop_map" \ - f" or the where clause is wrong. Result: " + "\n".join(result) - return int(result[1]) - - -def new_file_is_different(old_file_name: str, new_file_name: str) -> bool: - old_file_md5 = calc_md5(old_file_name) - new_file_md5 = calc_md5(new_file_name) - - logger.debug(f"MD5 hash of {old_file_name} is: {old_file_md5}") - logger.debug(f"MD5 hash of {new_file_name} is: {new_file_md5}") - - return old_file_md5 != new_file_md5 - - -def update_database(conn, coop_map: CoopMap, new_version: int) -> None: - logger.debug(f"Updating coop map {coop_map} in database to version {new_version}") - - query = f""" - UPDATE coop_map - SET version = {new_version}, filename = "maps/{coop_map.build_zip_filename(new_version)}" - WHERE id = {coop_map.map_id} - """ - run_sql(conn, query) - - -def copytree(src, dst, symlinks=False, ignore=None): - """ - Reason for that method is because shutil.copytree will raise exception on existing - temporary directory - """ - - for item in os.listdir(src): - s = os.path.join(src, item) - d = os.path.join(dst, item) - if os.path.isdir(s): - shutil.copytree(s, d, symlinks, ignore) - else: - shutil.copy2(s, d) - - -def create_zip_package(coop_map: CoopMap, version: int, files: List[str], tmp_folder_path: str, zip_file_path: str): - fix_folder_paths(coop_map.folder_name, files, version) - fix_file_timestamps(files) - with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_BZIP2) as zip_file: - for path in files: - zip_file.write(path, arcname=f"/{coop_map.build_folder_name(version)}/{os.path.relpath(path, tmp_folder_path)}") - - -def process_coop_map(conn, coop_map: CoopMap, simulate: bool, git_directory:str, coop_maps_path: str): - logger.info(f"Processing: {coop_map}") - - temp_dir = TemporaryDirectory() - copytree(os.path.join(git_directory, coop_map.folder_name), temp_dir.name) - processing_files = [] - for root, dirs, files in os.walk(temp_dir.name): - for f in files: - processing_files.append(os.path.relpath(os.path.join(root, f), temp_dir.name)) - - logger.debug(f"Files to process in {coop_map}: {processing_files}") - current_version = get_latest_map_version(coop_map) - current_file_path = os.path.join(coop_maps_path, coop_map.build_zip_filename(current_version)) - zip_file_path = os.path.join(temp_dir.name, coop_map.build_zip_filename(current_version)) - create_zip_package(coop_map, current_version, processing_files, temp_dir.name, zip_file_path) - if current_version == 0 or new_file_is_different(current_file_path, zip_file_path): - new_version = current_version + 1 - - if current_version == 0: - logger.info(f"{coop_map} first upload. New version: {new_version}") - else: - logger.info(f"{coop_map} has changed. New version: {new_version}") - - if not simulate: - temp_dir.cleanup() - temp_dir = TemporaryDirectory() - copytree(os.path.join(git_directory, coop_map.folder_name), temp_dir.name) - - zip_file_path = os.path.join(coop_maps_path, coop_map.build_zip_filename(new_version)) - create_zip_package(coop_map, new_version, processing_files, temp_dir.name, zip_file_path) - - update_database(conn, coop_map, new_version) - else: - logger.info(f"Updating database skipped due to simulation") - else: - logger.info(f"{coop_map} remains unchanged") - temp_dir.cleanup() - - -def calc_md5(filename: str) -> str: - """ - Calculate the MD5 hash of a file - """ - hash_md5 = hashlib.md5() - with open(filename, "rb") as f: - for chunk in iter(lambda: f.read(4096), b""): - hash_md5.update(chunk) - return hash_md5.hexdigest() - - -def run_checked_shell(cmd: List[str]) -> subprocess.CompletedProcess: - """ - Runs a command as a shell process and checks for success - Output is captured in the result object - :param cmd: command to run - :return: CompletedProcess of the execution - """ - logger.debug("Run shell command: {cmd}".format(cmd=cmd)) - return subprocess.run(cmd, check=True, stdout=subprocess.PIPE) - - -def git_checkout(path: str, tag: str) -> None: - """ - Checkout a git tag of the git repository. This requires the repo to be checked out in the path folder! - - :param path: the path of the git repository to checkout - :param tag: version of the git tag (full name) - :return: nothing - """ - cwd = os.getcwd() - os.chdir(path) - logger.debug(f"Git checkout from path {path}") - - try: - run_checked_shell(["git", "fetch"]) - run_checked_shell(["git", "checkout", tag]) - except subprocess.CalledProcessError as e: - logger.error(f"git checkout failed - please check the error message: {e.stderr}") - exit(1) - finally: - os.chdir(cwd) - - -def create_zip(content: List[str], relative_to: str, output_file: str) -> None: - logger.debug(f"Zipping files to file `{output_file}`: {content}") - - with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zip_file: - for path in content: - if os.path.isdir(path): - cwd = os.getcwd() - os.chdir(path) - - for root, dirs, files in os.walk(path): - for next_file in files: - file_path = os.path.join(root, next_file) - zip_file.write(file_path, os.path.relpath(file_path, relative_to)) - - os.chdir(cwd) - else: - zip_file.write(path, os.path.relpath(path, relative_to)) - - -if __name__ == "__main__": - # Setting up logger - stream_handler = logging.StreamHandler(sys.stdout) - stream_handler.setFormatter(logging.Formatter('%(levelname)-5s - %(message)s')) - logger.addHandler(stream_handler) - - # Setting up CLI arguments - parser = argparse.ArgumentParser(description=__doc__) - - parser.add_argument("version", help="the git tag name of the version") - parser.add_argument("-s", "--simulate", dest="simulate", action="store_true", default=False, - help="only runs a simulation without updating the database") - parser.add_argument("--git-directory", dest="git_directory", action="store", - default="/opt/featured-mods/faf-coop-maps", - help="base directory of the faf-coop-maps repository") - parser.add_argument("--maps-directory", dest="coop_maps_path", action="store", - default="/opt/faf/data/maps", - help="directory of the coop map files (content server)") - - args = parser.parse_args() - - git_checkout(args.git_directory, args.version) - conn = get_db_connection() - - for coop_map in coop_maps: - try: - process_coop_map(conn, coop_map, args.simulate, args.git_directory, args.coop_maps_path) - except Exception as error: - logger.warning(f"Unable to parse {coop_map}", exc_info=True) diff --git a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml index a11b8468..9613e8fa 100644 --- a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml +++ b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml @@ -33,37 +33,32 @@ spec: template: spec: containers: - - image: python:3.13 + - image: gradle:9.2-jdk21 imagePullPolicy: Always - name: faf-coop-deployment + name: faf-deploy-coop + workingDir: /workspace envFrom: - configMapRef: - name: faf-deploy-coop-maps + name: faf-deploy-coop - secretRef: name: faf-legacy-deployment - command: [ "sh" ] - args: [ "-c", "pip install mysql-connector-python && python3 /tmp/deploy-coop-maps.py $PATCH_VERSION" ] + command: [ "gradle", "deployCoopMaps" ] + # We need to mount single files via subpath because Gradle breaks otherwise (symbolic link to read-only directory) volumeMounts: - - mountPath: /tmp/deploy-coop-maps.py + - mountPath: /workspace/build.gradle.kts name: faf-deploy-scripts - subPath: "deploy-coop-maps.py" - - mountPath: /opt/faf/data/maps - name: faf-maps - - mountPath: /opt/featured-mods/faf-coop-maps - name: faf-coop-maps + subPath: "build.gradle.kts" + - mountPath: /workspace/CoopMapDeployer.kt + name: faf-deploy-scripts + subPath: "CoopMapDeployer.kt" + - mountPath: /workspace/legacy-featured-mod-files + name: faf-featured-mods restartPolicy: Never volumes: - name: faf-deploy-scripts configMap: name: "faf-deploy-scripts" - - name: faf-deploy-coop-maps - configMap: - name: "faf-deploy-coop-maps" - - name: faf-maps - hostPath: - path: /opt/faf/data/maps - type: Directory - - name: faf-coop-maps + - name: faf-featured-mods hostPath: - path: /opt/faf/data/faf-coop-maps + path: /opt/faf/data/legacy-featured-mod-files type: Directory \ No newline at end of file diff --git a/apps/faf-legacy-deployment/templates/deploy-coop.yaml b/apps/faf-legacy-deployment/templates/deploy-coop.yaml index c4c7a668..f18254f8 100644 --- a/apps/faf-legacy-deployment/templates/deploy-coop.yaml +++ b/apps/faf-legacy-deployment/templates/deploy-coop.yaml @@ -42,7 +42,7 @@ spec: name: faf-deploy-coop - secretRef: name: faf-legacy-deployment - command: [ "gradle", "run" ] + command: [ "gradle", "deployCoop" ] # We need to mount single files via subpath because Gradle breaks otherwise (symbolic link to read-only directory) volumeMounts: - mountPath: /workspace/build.gradle.kts From 96327547f3ad7dcf0126b4fc1bd72660d0f7dcb0 Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Mon, 29 Dec 2025 14:10:38 +0100 Subject: [PATCH 25/27] Try removing subpath again --- .../templates/deploy-coop-maps.yaml | 15 ++++++++------- .../templates/deploy-coop.yaml | 14 ++++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml index 9613e8fa..077c13ed 100644 --- a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml +++ b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml @@ -5,7 +5,6 @@ metadata: labels: app: faf-deploy-coop-maps data: - PATCH_VERSION: "v9.0.2" DATABASE_HOST: "mariadb" DATABASE_NAME: "faf_lobby" @@ -37,20 +36,22 @@ spec: imagePullPolicy: Always name: faf-deploy-coop workingDir: /workspace + env: + - name: PATCH_VERSION + value: "9.0.2" envFrom: - configMapRef: name: faf-deploy-coop - secretRef: name: faf-legacy-deployment - command: [ "gradle", "deployCoopMaps" ] + command: + - "sh" + - "-c" + - "cp /scripts/* /workspace && gradle deployCoopMaps" # We need to mount single files via subpath because Gradle breaks otherwise (symbolic link to read-only directory) volumeMounts: - - mountPath: /workspace/build.gradle.kts + - mountPath: /scripts name: faf-deploy-scripts - subPath: "build.gradle.kts" - - mountPath: /workspace/CoopMapDeployer.kt - name: faf-deploy-scripts - subPath: "CoopMapDeployer.kt" - mountPath: /workspace/legacy-featured-mod-files name: faf-featured-mods restartPolicy: Never diff --git a/apps/faf-legacy-deployment/templates/deploy-coop.yaml b/apps/faf-legacy-deployment/templates/deploy-coop.yaml index f18254f8..a4ce051c 100644 --- a/apps/faf-legacy-deployment/templates/deploy-coop.yaml +++ b/apps/faf-legacy-deployment/templates/deploy-coop.yaml @@ -37,20 +37,22 @@ spec: imagePullPolicy: Always name: faf-deploy-coop workingDir: /workspace + env: + - name: PATCH_VERSION + value: "66" envFrom: - configMapRef: name: faf-deploy-coop - secretRef: name: faf-legacy-deployment - command: [ "gradle", "deployCoop" ] + command: + - "sh" + - "-c" + - "cp /scripts/* /workspace && gradle deployCoop" # We need to mount single files via subpath because Gradle breaks otherwise (symbolic link to read-only directory) volumeMounts: - - mountPath: /workspace/build.gradle.kts + - mountPath: /workspace name: faf-deploy-scripts - subPath: "build.gradle.kts" - - mountPath: /workspace/CoopDeployer.kt - name: faf-deploy-scripts - subPath: "CoopDeployer.kt" - mountPath: /workspace/legacy-featured-mod-files name: faf-featured-mods restartPolicy: Never From 86676ce4e72425530114259947b1d2d00bd4865c Mon Sep 17 00:00:00 2001 From: Brutus5000 Date: Mon, 29 Dec 2025 15:30:42 +0100 Subject: [PATCH 26/27] Util class --- .../scripts/CoopDeployer.kt | 78 +++------------- .../scripts/CoopMapDeployer.kt | 90 ++++--------------- apps/faf-legacy-deployment/scripts/Utils.kt | 74 +++++++++++++++ .../templates/config.yaml | 9 ++ .../templates/deploy-coop-maps.yaml | 14 +-- .../templates/deploy-coop.yaml | 17 +--- 6 files changed, 114 insertions(+), 168 deletions(-) create mode 100644 apps/faf-legacy-deployment/scripts/Utils.kt create mode 100644 apps/faf-legacy-deployment/templates/config.yaml diff --git a/apps/faf-legacy-deployment/scripts/CoopDeployer.kt b/apps/faf-legacy-deployment/scripts/CoopDeployer.kt index 4a57e91c..4d4747c1 100755 --- a/apps/faf-legacy-deployment/scripts/CoopDeployer.kt +++ b/apps/faf-legacy-deployment/scripts/CoopDeployer.kt @@ -2,9 +2,11 @@ package com.faforever.coopdeployer +import com.faforever.FafDatabase +import com.faforever.GitRepo +import com.faforever.Log import org.apache.commons.compress.archivers.zip.ZipArchiveEntry import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream -import org.eclipse.jgit.api.Git import org.slf4j.LoggerFactory import java.io.IOException import java.net.URI @@ -18,8 +20,6 @@ import java.nio.file.StandardCopyOption import java.nio.file.attribute.FileTime import java.nio.file.attribute.PosixFilePermission import java.security.MessageDigest -import java.sql.Connection -import java.sql.DriverManager import java.time.Duration import kotlin.io.path.inputStream @@ -34,33 +34,6 @@ fun Path.setPerm664() { Files.setPosixFilePermissions(this, perms) } -data class GitRepo( - val workDir: Path, - val repoUrl: String, - val gitRef: String, -) { - fun checkout(): Path { - if (Files.exists(workDir.resolve(".git"))) { - log.info("Repo exists — fetching and checking out $gitRef...") - Git.open(workDir.toFile()).use { git -> - git.fetch().call() - git.checkout().setName(gitRef).call() - } - } else { - log.info("Cloning repository $repoUrl") - Git.cloneRepository() - .setURI(repoUrl) - .setDirectory(workDir.toFile()) - .call() - log.info("Checking out $gitRef") - Git.open(workDir.toFile()).use { git -> - git.checkout().setName(gitRef).call() - } - } - - return workDir - } -} data class GithubReleaseAssetDownloader( val repoOwner: String = "FAForever", @@ -160,25 +133,14 @@ data class GithubReleaseAssetDownloader( } -data class FafDatabase( - val host: String, - val database: String, - val username: String, - val password: String, +data class CoopDatabase( val dryRun: Boolean -) : AutoCloseable { +) : FafDatabase() { /** * Definition of an existing file in the database */ data class PatchFile(val mod: String, val fileId: Int, val name: String, val md5: String, val version: Int) - private val connection: Connection = - DriverManager.getConnection( - "jdbc:mariadb://$host/$database?useSSL=false&serverTimezone=UTC", - username, - password - ) - fun getCurrentPatchFile(mod: String, fileId: Int): PatchFile? { val sql = """ SELECT uf.fileId, uf.name, uf.md5, t.v @@ -191,7 +153,7 @@ data class FafDatabase( WHERE uf.fileId = ? """.trimIndent() - connection.prepareStatement(sql).use { stmt -> + prepareStatement(sql).use { stmt -> stmt.setInt(1, fileId) val rs = stmt.executeQuery() while (rs.next()) { @@ -213,12 +175,12 @@ data class FafDatabase( } val del = "DELETE FROM updates_${mod}_files WHERE fileId=? AND version=?" val ins = "INSERT INTO updates_${mod}_files (fileId, version, name, md5, obselete) VALUES (?, ?, ?, ?, 0)" - connection.prepareStatement(del).use { + prepareStatement(del).use { it.setInt(1, fileId) it.setInt(2, version) it.executeUpdate() } - connection.prepareStatement(ins).use { + prepareStatement(ins).use { it.setInt(1, fileId) it.setInt(2, version) it.setString(3, name) @@ -226,10 +188,6 @@ data class FafDatabase( it.executeUpdate() } } - - override fun close() { - connection.close() - } } private const val MINIMUM_ZIP_DATE = 315532800000L // 1980-01-01 @@ -238,7 +196,7 @@ private val MINIMUM_ZIP_FILE_TIME = FileTime.fromMillis(MINIMUM_ZIP_DATE) class Patcher( val patchVersion: Int, val targetDir: Path, - val db: FafDatabase, + val db: CoopDatabase, val dryRun: Boolean, ) { /** @@ -409,17 +367,13 @@ class Patcher( } fun main() { + Log.init() + val PATCH_VERSION = System.getenv("PATCH_VERSION") ?: error("PATCH_VERSION required") val REPO_URL = System.getenv("GIT_REPO_URL") ?: "https://github.com/FAForever/fa-coop.git" val GIT_REF = System.getenv("GIT_REF") ?: "v$PATCH_VERSION" - val WORKDIR = System.getenv("GIT_WORKDIR") ?: "/tmp/fa-coop-kt" + val WORKDIR = System.getenv("GIT_WORKDIR") ?: "/tmp/fa-coop" val DRYRUN = (System.getenv("DRY_RUN") ?: "false").lowercase() in listOf("1", "true", "yes") - - val DB_HOST = System.getenv("DATABASE_HOST") ?: "localhost" - val DB_NAME = System.getenv("DATABASE_NAME") ?: "faf" - val DB_USER = System.getenv("DATABASE_USERNAME") ?: "root" - val DB_PASS = System.getenv("DATABASE_PASSWORD") ?: "banana" - val TARGET_DIR = Paths.get("./legacy-featured-mod-files") log.info("=== Kotlin Coop Deployer v{} ===", PATCH_VERSION) @@ -476,13 +430,7 @@ fun main() { Patcher.PatchFile(25, "FAF_Coop_Operation_Tight_Spot_VO.v%d.nx2", null), ) - FafDatabase( - host = DB_HOST, - database = DB_NAME, - username = DB_USER, - password = DB_PASS, - dryRun = DRYRUN - ).use { db -> + CoopDatabase(dryRun = DRYRUN).use { db -> val patcher = Patcher( patchVersion = PATCH_VERSION.toInt(), targetDir = TARGET_DIR, diff --git a/apps/faf-legacy-deployment/scripts/CoopMapDeployer.kt b/apps/faf-legacy-deployment/scripts/CoopMapDeployer.kt index 35c2c217..238fd48f 100644 --- a/apps/faf-legacy-deployment/scripts/CoopMapDeployer.kt +++ b/apps/faf-legacy-deployment/scripts/CoopMapDeployer.kt @@ -2,19 +2,17 @@ package com.faforever.coopmapdeployer -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.Level +import com.faforever.FafDatabase +import com.faforever.GitRepo +import com.faforever.Log import org.apache.commons.compress.archivers.zip.ZipArchiveEntry import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream -import org.eclipse.jgit.api.Git import org.slf4j.LoggerFactory import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.nio.file.attribute.FileTime import java.security.MessageDigest -import java.sql.Connection -import java.sql.DriverManager import kotlin.io.path.copyTo import kotlin.io.path.createDirectories import kotlin.io.path.exists @@ -26,38 +24,9 @@ import kotlin.io.path.walk private val log = LoggerFactory.getLogger("coop-maps-updater") - private const val FIXED_TIMESTAMP = 1078100502L // 2004-03-01T00:21:42Z private val FIXED_FILE_TIME = FileTime.fromMillis(FIXED_TIMESTAMP) -data class GitRepo( - val workDir: Path, - val repoUrl: String, - val gitRef: String, -) { - fun checkout(): Path { - if (Files.exists(workDir.resolve(".git"))) { - log.info("Repo exists — fetching and checking out $gitRef...") - Git.open(workDir.toFile()).use { git -> - git.fetch().call() - git.checkout().setName(gitRef).call() - } - } else { - log.info("Cloning repository $repoUrl") - Git.cloneRepository() - .setURI(repoUrl) - .setDirectory(workDir.toFile()) - .call() - log.info("Checking out $gitRef") - Git.open(workDir.toFile()).use { git -> - git.checkout().setName(gitRef).call() - } - } - - return workDir - } -} - data class CoopMap( val folderName: String, @@ -120,22 +89,11 @@ private val coopMaps = listOf( CoopMap("FAF_Coop_Operation_Red_Revenge", 49, 4), ) -data class FafDatabase( - val host: String, - val database: String, - val username: String, - val password: String, +data class CoopMapDatabase( val dryRun: Boolean -) : AutoCloseable { - private val connection: Connection = - DriverManager.getConnection( - "jdbc:mariadb://$host/$database?useSSL=false&serverTimezone=UTC", - username, - password - ) - +) : FafDatabase() { fun getLatestVersion(map: CoopMap): Int { - connection.createStatement().use { st -> + createStatement().use { st -> st.executeQuery("SELECT version FROM coop_map WHERE id=${map.mapId}") .use { rs -> if (!rs.next()) error("Map ${map.mapId} not found") @@ -144,7 +102,7 @@ data class FafDatabase( } } - fun updateDatabase(map: CoopMap, version: Int) { + fun update(map: CoopMap, version: Int) { val sql = """ UPDATE coop_map SET version=$version, @@ -152,16 +110,12 @@ data class FafDatabase( WHERE id=${map.mapId} """.trimIndent() - connection.createStatement().use { it.executeUpdate(sql) } - } - - override fun close() { - connection.close() + createStatement().use { it.executeUpdate(sql) } } } private fun processCoopMap( - db: FafDatabase, + db: CoopMapDatabase, map: CoopMap, simulate: Boolean, gitDir: String, @@ -200,7 +154,7 @@ private fun processCoopMap( if (!simulate) { val finalZip = Path.of(mapsDir, map.zipName(newVersion)) createZip(map, newVersion, files, tmp, finalZip) - db.updateDatabase(map, newVersion) + db.update(map, newVersion) } } finally { tmp.toFile().deleteRecursively() @@ -253,23 +207,16 @@ private fun md5(path: Path): String { } fun main(args: Array) { + Log.init() + val MAP_DIR = System.getenv("MAP_DIR") ?: "/opt/faf/data/faf-coop-maps" val PATCH_VERSION = System.getenv("PATCH_VERSION") ?: error("PATCH_VERSION required") val REPO_URL = System.getenv("GIT_REPO_URL") ?: "https://github.com/FAForever/faf-coop-maps" val GIT_REF = System.getenv("GIT_REF") ?: "v$PATCH_VERSION" - val WORKDIR = System.getenv("GIT_WORKDIR") ?: "/tmp/fa-coop-kt" + val WORKDIR = System.getenv("GIT_WORKDIR") ?: "/tmp/faf-coop-maps" val DRYRUN = (System.getenv("DRY_RUN") ?: "false").lowercase() in listOf("1", "true", "yes") - val DB_HOST = System.getenv("DATABASE_HOST") ?: "localhost" - val DB_NAME = System.getenv("DATABASE_NAME") ?: "faf" - val DB_USER = System.getenv("DATABASE_USERNAME") ?: "root" - val DB_PASS = System.getenv("DATABASE_PASSWORD") ?: "banana" - - val level = System.getenv("LOG_LEVEL") ?: "INFO" - - val root = LoggerFactory - .getLogger(Logger.ROOT_LOGGER_NAME) as Logger - root.level = Level.toLevel(level, Level.INFO) + log.info("=== Kotlin Coop Map Deployer v{} ===", PATCH_VERSION) Files.createDirectories(Paths.get(MAP_DIR)) @@ -279,13 +226,7 @@ fun main(args: Array) { gitRef = GIT_REF, ).checkout() - FafDatabase( - host = DB_HOST, - database = DB_NAME, - username = DB_USER, - password = DB_PASS, - dryRun = DRYRUN - ).use { db -> + CoopMapDatabase(dryRun = DRYRUN).use { db -> coopMaps.forEach { try { processCoopMap(db, it, DRYRUN, WORKDIR, MAP_DIR) @@ -294,5 +235,4 @@ fun main(args: Array) { } } } - } diff --git a/apps/faf-legacy-deployment/scripts/Utils.kt b/apps/faf-legacy-deployment/scripts/Utils.kt new file mode 100644 index 00000000..8b0b9b5a --- /dev/null +++ b/apps/faf-legacy-deployment/scripts/Utils.kt @@ -0,0 +1,74 @@ +package com.faforever + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import org.eclipse.jgit.api.Git +import org.slf4j.LoggerFactory +import java.nio.file.Files +import java.nio.file.Path +import java.sql.Connection +import java.sql.DriverManager +import java.sql.PreparedStatement +import java.sql.Statement + +object Log { + fun init() { + val level = System.getenv("LOG_LEVEL") ?: "INFO" + val root = LoggerFactory + .getLogger(Logger.ROOT_LOGGER_NAME) as Logger + root.level = Level.toLevel(level, Level.INFO) + } +} + +data class GitRepo( + val workDir: Path, + val repoUrl: String, + val gitRef: String, +) { + private val log = LoggerFactory.getLogger(GitRepo::class.simpleName) + + fun checkout(): Path { + if (Files.exists(workDir.resolve(".git"))) { + log.info("Repo exists — fetching and checking out $gitRef...") + Git.open(workDir.toFile()).use { git -> + git.fetch().call() + git.checkout().setName(gitRef).call() + } + } else { + log.info("Cloning repository $repoUrl") + Git.cloneRepository() + .setURI(repoUrl) + .setDirectory(workDir.toFile()) + .call() + log.info("Checking out $gitRef") + Git.open(workDir.toFile()).use { git -> + git.checkout().setName(gitRef).call() + } + } + + return workDir + } +} + +abstract class FafDatabase : AutoCloseable { + private val host = System.getenv("DATABASE_HOST") ?: "localhost" + private val database = System.getenv("DATABASE_NAME") ?: "faf" + private val username = System.getenv("DATABASE_USERNAME") ?: "root" + private val password = System.getenv("DATABASE_PASSWORD") ?: "banana" + + private val connection: Connection = + DriverManager.getConnection( + "jdbc:mariadb://$host/$database?useSSL=false&serverTimezone=UTC", + username, + password + ) + + fun createStatement(): Statement = connection.createStatement() + + fun prepareStatement(sql: String): PreparedStatement = connection.prepareStatement(sql) + + override fun close() { + connection.close() + } +} + diff --git a/apps/faf-legacy-deployment/templates/config.yaml b/apps/faf-legacy-deployment/templates/config.yaml new file mode 100644 index 00000000..4c282829 --- /dev/null +++ b/apps/faf-legacy-deployment/templates/config.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: faf-legacy-deployment + labels: + app: faf-legacy-deployment +data: + DATABASE_HOST: "mariadb" + DATABASE_NAME: "faf_lobby" diff --git a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml index 077c13ed..ce877034 100644 --- a/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml +++ b/apps/faf-legacy-deployment/templates/deploy-coop-maps.yaml @@ -1,15 +1,3 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: faf-deploy-coop-maps - labels: - app: faf-deploy-coop-maps -data: - DATABASE_HOST: "mariadb" - DATABASE_NAME: "faf_lobby" - ---- - kind: CronJob apiVersion: batch/v1 metadata: @@ -41,7 +29,7 @@ spec: value: "9.0.2" envFrom: - configMapRef: - name: faf-deploy-coop + name: faf-legacy-deployment - secretRef: name: faf-legacy-deployment command: diff --git a/apps/faf-legacy-deployment/templates/deploy-coop.yaml b/apps/faf-legacy-deployment/templates/deploy-coop.yaml index a4ce051c..ac01eb55 100644 --- a/apps/faf-legacy-deployment/templates/deploy-coop.yaml +++ b/apps/faf-legacy-deployment/templates/deploy-coop.yaml @@ -1,16 +1,3 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: faf-deploy-coop - labels: - app: faf-deploy-coop -data: - PATCH_VERSION: "66" - DATABASE_HOST: "mariadb" - DATABASE_NAME: "faf_lobby" - ---- - kind: CronJob apiVersion: batch/v1 metadata: @@ -42,7 +29,7 @@ spec: value: "66" envFrom: - configMapRef: - name: faf-deploy-coop + name: faf-legacy-deployment - secretRef: name: faf-legacy-deployment command: @@ -51,7 +38,7 @@ spec: - "cp /scripts/* /workspace && gradle deployCoop" # We need to mount single files via subpath because Gradle breaks otherwise (symbolic link to read-only directory) volumeMounts: - - mountPath: /workspace + - mountPath: /scripts name: faf-deploy-scripts - mountPath: /workspace/legacy-featured-mod-files name: faf-featured-mods From 0aeb92e66db5b0cdfc031d5e2878a56c00f4b380 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 04:27:32 +0000 Subject: [PATCH 27/27] Update Helm release secrets-operator to v0.10.19 --- ops/infisical/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/infisical/Chart.yaml b/ops/infisical/Chart.yaml index 67698d17..2d550c29 100644 --- a/ops/infisical/Chart.yaml +++ b/ops/infisical/Chart.yaml @@ -3,5 +3,5 @@ name: infisical version: 1.0.0 dependencies: - name: secrets-operator - version: 0.10.13 + version: 0.10.19 repository: https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/