From fdd6755c2fe6a82e717f1b252a89b683c329f8a9 Mon Sep 17 00:00:00 2001 From: Lumpiasty Date: Fri, 3 Apr 2026 23:20:36 +0200 Subject: [PATCH] rip out all garm related stuff --- Makefile | 18 +- apps/garm/README.md | 49 ----- apps/garm/configmap.yaml | 19 -- apps/garm/deployment.yaml | 106 ---------- apps/garm/image-source.env | 5 - apps/garm/ingress.yaml | 24 --- apps/garm/kustomization.yaml | 11 -- apps/garm/namespace.yaml | 9 - apps/garm/pvc.yaml | 46 ----- apps/garm/rbac.yaml | 51 ----- apps/garm/secret.yaml | 32 ---- apps/garm/service.yaml | 14 -- apps/gitea/release.yaml | 2 +- apps/kustomization.yaml | 1 - apps/renovate/configmap.yaml | 1 - devenv.nix | 3 - docker/garm/Dockerfile | 28 --- nix/garm-cli.nix | 45 ----- renovate.json | 49 ----- utils/update-garm-cli-hash.mjs | 320 ------------------------------- utils/update-garm-image-pin.mjs | 91 --------- vault/kubernetes-roles/garm.yaml | 6 - vault/policy/garm.hcl | 7 - 23 files changed, 2 insertions(+), 935 deletions(-) delete mode 100644 apps/garm/README.md delete mode 100644 apps/garm/configmap.yaml delete mode 100644 apps/garm/deployment.yaml delete mode 100644 apps/garm/image-source.env delete mode 100644 apps/garm/ingress.yaml delete mode 100644 apps/garm/kustomization.yaml delete mode 100644 apps/garm/namespace.yaml delete mode 100644 apps/garm/pvc.yaml delete mode 100644 apps/garm/rbac.yaml delete mode 100644 apps/garm/secret.yaml delete mode 100644 apps/garm/service.yaml delete mode 100644 docker/garm/Dockerfile delete mode 100644 nix/garm-cli.nix delete mode 100644 utils/update-garm-cli-hash.mjs delete mode 100644 utils/update-garm-image-pin.mjs delete mode 100644 vault/kubernetes-roles/garm.yaml delete mode 100644 vault/policy/garm.hcl diff --git a/Makefile b/Makefile index f519d36..739ddea 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL := /usr/bin/env bash -.PHONY: install-router gen-talos-config apply-talos-config get-kubeconfig garm-image-build garm-image-push garm-image-build-push +.PHONY: install-router gen-talos-config apply-talos-config get-kubeconfig install-router: ansible-playbook ansible/playbook.yml -i ansible/hosts @@ -27,19 +27,3 @@ apply-talos-config: get-kubeconfig: talosctl -n anapistula-delrosalae kubeconfig talos/generated/kubeconfig - -garm-image-build: - set -euo pipefail; \ - source apps/garm/image-source.env; \ - docker build \ - -f docker/garm/Dockerfile \ - --build-arg GARM_COMMIT=$$GARM_COMMIT \ - -t $$GARM_IMAGE \ - . - -garm-image-push: - set -euo pipefail; \ - source apps/garm/image-source.env; \ - docker push $$GARM_IMAGE - -garm-image-build-push: garm-image-build garm-image-push diff --git a/apps/garm/README.md b/apps/garm/README.md deleted file mode 100644 index cd7ae30..0000000 --- a/apps/garm/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# garm - -This app deploys `garm` with external `garm-provider-k8s`. - -- API/UI ingress: `https://garm.lumpiasty.xyz` -- Internal service DNS: `http://garm.garm.svc.cluster.local:9997` - -## Vault secret requirements - -`VaultStaticSecret` reads `secret/data/garm` and expects at least: - -- `jwt_auth_secret` -- `database_passphrase` (must be 32 characters) - -## Connect garm to Gitea - -After Flux reconciles this app, initialize garm and add Gitea endpoint/credentials. - -```bash -# 1) Initialize garm (from your local devenv shell) -garm-cli init \ - --name homelab \ - --url https://garm.lumpiasty.xyz \ - --username admin \ - --email admin@lumpiasty.xyz \ - --password '' \ - --metadata-url http://garm.garm.svc.cluster.local:9997/api/v1/metadata \ - --callback-url http://garm.garm.svc.cluster.local:9997/api/v1/callbacks \ - --webhook-url http://garm.garm.svc.cluster.local:9997/webhooks - -# 2) Add Gitea endpoint -garm-cli gitea endpoint create \ - --name local-gitea \ - --description 'Cluster Gitea' \ - --base-url http://gitea-http.gitea.svc.cluster.local:80 \ - --api-base-url http://gitea-http.gitea.svc.cluster.local:80/api/v1 - -# 3) Add Gitea PAT credentials -garm-cli gitea credentials add \ - --name gitea-pat \ - --description 'PAT for garm' \ - --endpoint local-gitea \ - --auth-type pat \ - --pat-oauth-token '' -``` - -Then add repositories/orgs and create pools against provider `kubernetes_external`. - -If Gitea refuses webhook installation to cluster-local URLs, set `gitea.config.webhook.ALLOWED_HOST_LIST` in `apps/gitea/release.yaml`. diff --git a/apps/garm/configmap.yaml b/apps/garm/configmap.yaml deleted file mode 100644 index 7b80fb9..0000000 --- a/apps/garm/configmap.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: garm-provider-k8s-config - namespace: garm -data: - provider-config.yaml: | - kubeConfigPath: "" - runnerNamespace: "garm-runners" - podTemplate: - spec: - restartPolicy: Never - flavors: - default: - requests: - cpu: 100m - memory: 512Mi - limits: - memory: 2Gi diff --git a/apps/garm/deployment.yaml b/apps/garm/deployment.yaml deleted file mode 100644 index 3f8f374..0000000 --- a/apps/garm/deployment.yaml +++ /dev/null @@ -1,106 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: garm - namespace: garm -spec: - replicas: 1 - selector: - matchLabels: - app: garm - template: - metadata: - labels: - app: garm - spec: - serviceAccountName: garm - initContainers: - - name: render-garm-config - image: alpine:3.23 - env: - - name: JWT_AUTH_SECRET - valueFrom: - secretKeyRef: - name: garm-config - key: jwt_auth_secret - - name: DATABASE_PASSPHRASE - valueFrom: - secretKeyRef: - name: garm-config - key: database_passphrase - command: - - /bin/sh - - -ec - - | - cat < /etc/garm/config.toml - [default] - enable_webhook_management = true - - [logging] - enable_log_streamer = true - log_format = "text" - log_level = "info" - log_source = false - - [metrics] - enable = true - disable_auth = false - - [jwt_auth] - secret = "${JWT_AUTH_SECRET}" - time_to_live = "8760h" - - [apiserver] - bind = "0.0.0.0" - port = 9997 - use_tls = false - [apiserver.webui] - enable = true - - [database] - backend = "sqlite3" - passphrase = "${DATABASE_PASSPHRASE}" - [database.sqlite3] - db_file = "/data/garm.db" - busy_timeout_seconds = 5 - - [[provider]] - name = "kubernetes_external" - description = "Kubernetes provider" - provider_type = "external" - [provider.external] - config_file = "/etc/garm/provider-config.yaml" - provider_executable = "/opt/garm/providers.d/garm-provider-k8s" - environment_variables = ["KUBERNETES_"] - EOF - volumeMounts: - - name: config-dir - mountPath: /etc/garm - containers: - - name: garm - image: gitea.lumpiasty.xyz/lumpiasty/garm-k8s:r1380 - imagePullPolicy: IfNotPresent - command: - - /bin/garm - - --config - - /etc/garm/config.toml - ports: - - name: http - containerPort: 9997 - volumeMounts: - - name: data - mountPath: /data - - name: config-dir - mountPath: /etc/garm - - name: provider-config - mountPath: /etc/garm/provider-config.yaml - subPath: provider-config.yaml - volumes: - - name: data - persistentVolumeClaim: - claimName: garm-lvmhdd - - name: config-dir - emptyDir: {} - - name: provider-config - configMap: - name: garm-provider-k8s-config diff --git a/apps/garm/image-source.env b/apps/garm/image-source.env deleted file mode 100644 index b75ffec..0000000 --- a/apps/garm/image-source.env +++ /dev/null @@ -1,5 +0,0 @@ -# renovate: datasource=github-refs depName=cloudbase/garm versioning=git -GARM_COMMIT=818a9dddccba5f2843f185e6a846770988f31fc5 -GARM_COMMIT_NUMBER=1380 -GARM_IMAGE_REPO=gitea.lumpiasty.xyz/lumpiasty/garm-k8s -GARM_IMAGE=gitea.lumpiasty.xyz/lumpiasty/garm-k8s:r1380 diff --git a/apps/garm/ingress.yaml b/apps/garm/ingress.yaml deleted file mode 100644 index 8479226..0000000 --- a/apps/garm/ingress.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - namespace: garm - name: garm - annotations: - cert-manager.io/cluster-issuer: letsencrypt -spec: - ingressClassName: nginx-ingress - rules: - - host: garm.lumpiasty.xyz - http: - paths: - - backend: - service: - name: garm - port: - number: 9997 - path: / - pathType: Prefix - tls: - - hosts: - - garm.lumpiasty.xyz - secretName: garm-ingress diff --git a/apps/garm/kustomization.yaml b/apps/garm/kustomization.yaml deleted file mode 100644 index e7bacb3..0000000 --- a/apps/garm/kustomization.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - namespace.yaml - - pvc.yaml - - configmap.yaml - - service.yaml - - ingress.yaml - - rbac.yaml - - secret.yaml - - deployment.yaml diff --git a/apps/garm/namespace.yaml b/apps/garm/namespace.yaml deleted file mode 100644 index d3e29ef..0000000 --- a/apps/garm/namespace.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: garm ---- -apiVersion: v1 -kind: Namespace -metadata: - name: garm-runners diff --git a/apps/garm/pvc.yaml b/apps/garm/pvc.yaml deleted file mode 100644 index 6da9b22..0000000 --- a/apps/garm/pvc.yaml +++ /dev/null @@ -1,46 +0,0 @@ ---- -apiVersion: local.openebs.io/v1alpha1 -kind: LVMVolume -metadata: - labels: - kubernetes.io/nodename: anapistula-delrosalae - name: garm-lvmhdd - namespace: openebs -spec: - capacity: 5Gi - ownerNodeID: anapistula-delrosalae - shared: "yes" - thinProvision: "no" - vgPattern: ^openebs-hdd$ - volGroup: openebs-hdd ---- -kind: PersistentVolume -apiVersion: v1 -metadata: - name: garm-lvmhdd -spec: - capacity: - storage: 5Gi - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - storageClassName: hdd-lvmpv - volumeMode: Filesystem - csi: - driver: local.csi.openebs.io - fsType: btrfs - volumeHandle: garm-lvmhdd ---- -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: garm-lvmhdd - namespace: garm -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 5Gi - storageClassName: hdd-lvmpv - volumeName: garm-lvmhdd diff --git a/apps/garm/rbac.yaml b/apps/garm/rbac.yaml deleted file mode 100644 index 2a37b2b..0000000 --- a/apps/garm/rbac.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: garm - namespace: garm ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: garm-provider-k8s - namespace: garm-runners -rules: - - apiGroups: [""] - resources: ["pods", "pods/log", "configmaps", "secrets", "events"] - verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: garm-provider-k8s - namespace: garm-runners -subjects: - - kind: ServiceAccount - name: garm - namespace: garm -roleRef: - kind: Role - name: garm-provider-k8s - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: garm-namespace-manager -rules: - - apiGroups: [""] - resources: ["namespaces"] - verbs: ["get"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: garm-namespace-manager -subjects: - - kind: ServiceAccount - name: garm - namespace: garm -roleRef: - kind: ClusterRole - name: garm-namespace-manager - apiGroup: rbac.authorization.k8s.io diff --git a/apps/garm/secret.yaml b/apps/garm/secret.yaml deleted file mode 100644 index ebc47bb..0000000 --- a/apps/garm/secret.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -apiVersion: secrets.hashicorp.com/v1beta1 -kind: VaultAuth -metadata: - name: garm - namespace: garm -spec: - method: kubernetes - mount: kubernetes - kubernetes: - role: garm - serviceAccount: garm ---- -apiVersion: secrets.hashicorp.com/v1beta1 -kind: VaultStaticSecret -metadata: - name: garm-config - namespace: garm -spec: - type: kv-v2 - - mount: secret - path: garm - - destination: - create: true - name: garm-config - type: Opaque - transformation: - excludeRaw: true - - vaultAuthRef: garm diff --git a/apps/garm/service.yaml b/apps/garm/service.yaml deleted file mode 100644 index 1fad383..0000000 --- a/apps/garm/service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: garm - namespace: garm -spec: - type: ClusterIP - selector: - app: garm - ports: - - name: http - port: 9997 - targetPort: 9997 - protocol: TCP diff --git a/apps/gitea/release.yaml b/apps/gitea/release.yaml index 641d276..8401c07 100644 --- a/apps/gitea/release.yaml +++ b/apps/gitea/release.yaml @@ -73,7 +73,7 @@ spec: ISSUE_INDEXER_TYPE: bleve REPO_INDEXER_ENABLED: true webhook: - ALLOWED_HOST_LIST: garm.garm.svc.cluster.local,woodpecker.lumpiasty.xyz + ALLOWED_HOST_LIST: woodpecker.lumpiasty.xyz admin: username: GiteaAdmin email: gi@tea.com diff --git a/apps/kustomization.yaml b/apps/kustomization.yaml index 26a4a9a..ea12ddb 100644 --- a/apps/kustomization.yaml +++ b/apps/kustomization.yaml @@ -14,5 +14,4 @@ resources: - searxng - ispeak3 - openwebui - - garm - woodpecker diff --git a/apps/renovate/configmap.yaml b/apps/renovate/configmap.yaml index e97cb27..324e1ba 100644 --- a/apps/renovate/configmap.yaml +++ b/apps/renovate/configmap.yaml @@ -9,4 +9,3 @@ data: RENOVATE_ENDPOINT: https://gitea.lumpiasty.xyz/api/v1 RENOVATE_PLATFORM: gitea RENOVATE_GIT_AUTHOR: Renovate Bot - RENOVATE_ALLOWED_COMMANDS: '["^node utils/update-garm-cli-hash\\.mjs$", "^node utils/update-garm-image-pin\\.mjs$"]' diff --git a/devenv.nix b/devenv.nix index 72e5939..a9408ed 100644 --- a/devenv.nix +++ b/devenv.nix @@ -6,8 +6,6 @@ let hvac librouteros ]); - - garm-cli = pkgs.callPackage ./nix/garm-cli.nix { }; in { # Overlays - apply krew2nix to get kubectl with krew support @@ -53,7 +51,6 @@ in --set OPENCODE_ENABLE_EXA "1" '' ) - garm-cli tea woodpecker-cli ]; diff --git a/docker/garm/Dockerfile b/docker/garm/Dockerfile deleted file mode 100644 index 2f00503..0000000 --- a/docker/garm/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -FROM golang:1.26-alpine AS build - -ARG GARM_COMMIT -ARG GARM_PROVIDER_K8S_VERSION=0.3.2 - -RUN apk add --no-cache ca-certificates git wget tar build-base util-linux-dev linux-headers - -WORKDIR /src -RUN git clone https://github.com/cloudbase/garm.git . && git checkout "${GARM_COMMIT}" - -RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \ - go build -trimpath \ - -tags osusergo,netgo,sqlite_omit_load_extension \ - -ldflags="-linkmode external -extldflags '-static' -s -w" \ - -o /out/garm ./cmd/garm - -RUN mkdir -p /out/providers.d \ - && wget -qO /tmp/garm-provider-k8s.tar.gz "https://github.com/mercedes-benz/garm-provider-k8s/releases/download/v${GARM_PROVIDER_K8S_VERSION}/garm-provider-k8s_Linux_x86_64.tar.gz" \ - && tar -xzf /tmp/garm-provider-k8s.tar.gz -C /out/providers.d \ - && chmod 0755 /out/providers.d/garm-provider-k8s - -FROM busybox - -COPY --from=build /out/garm /bin/garm -COPY --from=build /out/providers.d/garm-provider-k8s /opt/garm/providers.d/garm-provider-k8s -COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ - -ENTRYPOINT ["/bin/garm"] diff --git a/nix/garm-cli.nix b/nix/garm-cli.nix deleted file mode 100644 index c42412d..0000000 --- a/nix/garm-cli.nix +++ /dev/null @@ -1,45 +0,0 @@ -{ lib, buildGoModule, fetchFromGitHub, installShellFiles }: - -buildGoModule rec { - pname = "garm-cli"; - version = "r1380"; - garmCommit = "818a9dddccba5f2843f185e6a846770988f31fc5"; - - src = fetchFromGitHub { - owner = "cloudbase"; - repo = "garm"; - rev = garmCommit; - hash = "sha256-CTqqabNYUMSrmnQVCWml1/vkDw+OP1uJo1KFhBSZpYY="; - }; - - subPackages = [ "cmd/garm-cli" ]; - - nativeBuildInputs = [ installShellFiles ]; - - vendorHash = null; - - ldflags = [ - "-s" - "-w" - "-X main.version=${version}" - ]; - - postInstall = '' - # We need to set a temporary HOME for the completion scripts as workaround - # because garm-cli tries to write config to the home directory - # when generating the completion scripts - export HOME="$(mktemp -d)" - - installShellCompletion --cmd garm-cli \ - --bash <($out/bin/garm-cli completion bash) \ - --fish <($out/bin/garm-cli completion fish) \ - --zsh <($out/bin/garm-cli completion zsh) - ''; - - meta = { - description = "CLI for GitHub Actions Runner Manager"; - homepage = "https://github.com/cloudbase/garm"; - license = lib.licenses.asl20; - mainProgram = "garm-cli"; - }; -} diff --git a/renovate.json b/renovate.json index 839dc39..b5fca19 100644 --- a/renovate.json +++ b/renovate.json @@ -10,57 +10,8 @@ "gotk-components\\.ya?ml$" ] }, - "customManagers": [ - { - "customType": "regex", - "description": "Track garm-cli pinned main commit", - "managerFilePatterns": ["^nix/garm-cli\\.nix$"], - "matchStrings": ["garmCommit = \\\"(?[a-f0-9]{40})\\\";"], - "depNameTemplate": "cloudbase/garm", - "datasourceTemplate": "github-refs", - "versioningTemplate": "git" - }, - { - "customType": "regex", - "description": "Track garm-provider-k8s release in garm image Dockerfile", - "managerFilePatterns": ["^docker/garm/Dockerfile$"], - "matchStrings": ["ARG GARM_PROVIDER_K8S_VERSION=(?[0-9]+\\.[0-9]+\\.[0-9]+)"], - "depNameTemplate": "mercedes-benz/garm-provider-k8s", - "datasourceTemplate": "github-releases", - "versioningTemplate": "semver" - }, - { - "customType": "regex", - "description": "Track pinned garm main commit", - "managerFilePatterns": ["^apps/garm/image-source\\.env$"], - "matchStrings": ["GARM_COMMIT=(?[a-f0-9]{40})"], - "depNameTemplate": "cloudbase/garm", - "datasourceTemplate": "github-refs", - "versioningTemplate": "git" - } - ], "prHourlyLimit": 9, "packageRules": [ - { - "matchManagers": ["custom.regex"], - "matchDepNames": ["cloudbase/garm"], - "matchFileNames": ["nix/garm-cli.nix"], - "postUpgradeTasks": { - "commands": ["node utils/update-garm-cli-hash.mjs"], - "fileFilters": ["nix/garm-cli.nix"], - "executionMode": "update" - } - }, - { - "matchManagers": ["custom.regex"], - "matchDepNames": ["cloudbase/garm"], - "matchFileNames": ["apps/garm/image-source.env"], - "postUpgradeTasks": { - "commands": ["node utils/update-garm-image-pin.mjs"], - "fileFilters": ["apps/garm/image-source.env", "apps/garm/deployment.yaml"], - "executionMode": "update" - } - }, { "matchDatasources": ["docker"], "matchPackageNames": ["ghcr.io/mostlygeek/llama-swap"], diff --git a/utils/update-garm-cli-hash.mjs b/utils/update-garm-cli-hash.mjs deleted file mode 100644 index 258a794..0000000 --- a/utils/update-garm-cli-hash.mjs +++ /dev/null @@ -1,320 +0,0 @@ -import { createHash } from "node:crypto"; -import { Buffer } from "node:buffer"; -import fs from "node:fs"; -import https from "node:https"; -import zlib from "node:zlib"; - -const nixFile = "nix/garm-cli.nix"; - -function die(message) { - console.error(message); - process.exit(1); -} - -function readText(filePath) { - try { - return fs.readFileSync(filePath, "utf8"); - } catch { - die(`Missing ${filePath}`); - } -} - -function extractVersion(text) { - const match = text.match(/^\s*version\s*=\s*"([^"]+)";/m); - if (!match) { - die(`Unable to extract version from ${nixFile}`); - } - return match[1]; -} - -function extractCommit(text) { - const match = text.match(/^\s*garmCommit\s*=\s*"([a-f0-9]{40})";/m); - return match ? match[1] : null; -} - -function writeU64LE(hash, value) { - const buf = Buffer.alloc(8); - buf.writeBigUInt64LE(BigInt(value), 0); - hash.update(buf); -} - -function writeNarString(hash, data) { - writeU64LE(hash, data.length); - hash.update(data); - const pad = (8 - (data.length % 8)) % 8; - if (pad) { - hash.update(Buffer.alloc(pad)); - } -} - -function writeNarText(hash, text) { - writeNarString(hash, Buffer.from(text, "utf8")); -} - -function parseOctal(field) { - const clean = field.toString("ascii").replace(/\0.*$/, "").trim(); - if (!clean) { - return 0; - } - return Number.parseInt(clean, 8); -} - -function parseTarHeader(block) { - const name = block.subarray(0, 100).toString("utf8").replace(/\0.*$/, ""); - const mode = parseOctal(block.subarray(100, 108)); - const size = parseOctal(block.subarray(124, 136)); - const typeflagRaw = block[156]; - const typeflag = typeflagRaw === 0 ? "0" : String.fromCharCode(typeflagRaw); - const linkname = block.subarray(157, 257).toString("utf8").replace(/\0.*$/, ""); - const prefix = block.subarray(345, 500).toString("utf8").replace(/\0.*$/, ""); - return { - name: prefix ? `${prefix}/${name}` : name, - mode, - size, - typeflag, - linkname, - }; -} - -function parsePax(data) { - const out = {}; - let i = 0; - while (i < data.length) { - let sp = i; - while (sp < data.length && data[sp] !== 0x20) sp += 1; - if (sp >= data.length) break; - const len = Number.parseInt(data.subarray(i, sp).toString("utf8"), 10); - if (!Number.isFinite(len) || len <= 0) break; - const record = data.subarray(sp + 1, i + len).toString("utf8"); - const eq = record.indexOf("="); - if (eq > 0) { - const key = record.slice(0, eq); - const value = record.slice(eq + 1).replace(/\n$/, ""); - out[key] = value; - } - i += len; - } - return out; -} - -function parseTarEntries(archiveBuffer) { - const gz = zlib.gunzipSync(archiveBuffer); - const entries = []; - let i = 0; - let pendingPax = null; - let longName = null; - let longLink = null; - - while (i + 512 <= gz.length) { - const header = gz.subarray(i, i + 512); - i += 512; - - if (header.every((b) => b === 0)) { - break; - } - - const h = parseTarHeader(header); - const data = gz.subarray(i, i + h.size); - const dataPad = (512 - (h.size % 512)) % 512; - i += h.size + dataPad; - - if (h.typeflag === "x") { - pendingPax = parsePax(data); - continue; - } - if (h.typeflag === "g") { - continue; - } - if (h.typeflag === "L") { - longName = data.toString("utf8").replace(/\0.*$/, ""); - continue; - } - if (h.typeflag === "K") { - longLink = data.toString("utf8").replace(/\0.*$/, ""); - continue; - } - - const path = pendingPax?.path ?? longName ?? h.name; - const linkpath = pendingPax?.linkpath ?? longLink ?? h.linkname; - - entries.push({ - path, - typeflag: h.typeflag, - mode: h.mode, - linkname: linkpath, - data, - }); - - pendingPax = null; - longName = null; - longLink = null; - } - - return entries; -} - -function stripTopDir(path) { - const cleaned = path.replace(/^\.?\//, "").replace(/\/$/, ""); - const idx = cleaned.indexOf("/"); - if (idx === -1) return ""; - return cleaned.slice(idx + 1); -} - -function ensureDir(root, relPath) { - if (!relPath) return root; - const parts = relPath.split("/").filter(Boolean); - let cur = root; - for (const part of parts) { - let child = cur.children.get(part); - if (!child) { - child = { kind: "directory", children: new Map() }; - cur.children.set(part, child); - } - if (child.kind !== "directory") { - die(`Path conflict while building tree at ${relPath}`); - } - cur = child; - } - return cur; -} - -function buildTree(entries) { - const root = { kind: "directory", children: new Map() }; - for (const entry of entries) { - const rel = stripTopDir(entry.path); - if (!rel) { - continue; - } - - const parts = rel.split("/").filter(Boolean); - const name = parts.pop(); - const parent = ensureDir(root, parts.join("/")); - - if (entry.typeflag === "5") { - const existing = parent.children.get(name); - if (!existing) { - parent.children.set(name, { kind: "directory", children: new Map() }); - } else if (existing.kind !== "directory") { - die(`Path conflict at ${rel}`); - } - continue; - } - - if (entry.typeflag === "2") { - parent.children.set(name, { kind: "symlink", target: entry.linkname }); - continue; - } - - if (entry.typeflag === "0") { - parent.children.set(name, { - kind: "regular", - executable: (entry.mode & 0o111) !== 0, - contents: Buffer.from(entry.data), - }); - continue; - } - } - return root; -} - -function compareUtf8(a, b) { - return Buffer.from(a, "utf8").compare(Buffer.from(b, "utf8")); -} - -function narDump(hash, node) { - if (node.kind === "directory") { - writeNarText(hash, "("); - writeNarText(hash, "type"); - writeNarText(hash, "directory"); - const names = [...node.children.keys()].sort(compareUtf8); - for (const name of names) { - writeNarText(hash, "entry"); - writeNarText(hash, "("); - writeNarText(hash, "name"); - writeNarString(hash, Buffer.from(name, "utf8")); - writeNarText(hash, "node"); - narDump(hash, node.children.get(name)); - writeNarText(hash, ")"); - } - writeNarText(hash, ")"); - return; - } - - if (node.kind === "symlink") { - writeNarText(hash, "("); - writeNarText(hash, "type"); - writeNarText(hash, "symlink"); - writeNarText(hash, "target"); - writeNarString(hash, Buffer.from(node.target, "utf8")); - writeNarText(hash, ")"); - return; - } - - writeNarText(hash, "("); - writeNarText(hash, "type"); - writeNarText(hash, "regular"); - if (node.executable) { - writeNarText(hash, "executable"); - writeNarText(hash, ""); - } - writeNarText(hash, "contents"); - writeNarString(hash, node.contents); - writeNarText(hash, ")"); -} - -function fetchBuffer(url) { - return new Promise((resolve, reject) => { - https - .get(url, (res) => { - if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { - const redirectUrl = new URL(res.headers.location, url).toString(); - res.resume(); - fetchBuffer(redirectUrl).then(resolve, reject); - return; - } - if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) { - reject(new Error(`Failed to fetch ${url}: ${res.statusCode ?? "unknown"}`)); - res.resume(); - return; - } - const chunks = []; - res.on("data", (chunk) => chunks.push(chunk)); - res.on("end", () => resolve(Buffer.concat(chunks))); - }) - .on("error", reject); - }); -} - -function computeSRIFromGitHubTar(ref) { - const url = `https://github.com/cloudbase/garm/archive/${ref}.tar.gz`; - return fetchBuffer(url).then((archive) => { - const entries = parseTarEntries(archive); - const root = buildTree(entries); - const hash = createHash("sha256"); - writeNarText(hash, "nix-archive-1"); - narDump(hash, root); - return `sha256-${hash.digest("base64")}`; - }); -} - -function updateHash(text, sri) { - const pattern = /(^\s*hash\s*=\s*")sha256-[^"]+(";)/m; - if (!pattern.test(text)) { - die(`Unable to update hash in ${nixFile}`); - } - const next = text.replace(pattern, `$1${sri}$2`); - return next; -} - -async function main() { - const text = readText(nixFile); - const version = extractVersion(text); - const commit = extractCommit(text); - const ref = commit ?? `v${version}`; - const sri = await computeSRIFromGitHubTar(ref); - const updated = updateHash(text, sri); - fs.writeFileSync(nixFile, updated, "utf8"); - console.log(`Updated ${nixFile} hash to ${sri}`); -} - -main().catch((err) => die(err.message)); diff --git a/utils/update-garm-image-pin.mjs b/utils/update-garm-image-pin.mjs deleted file mode 100644 index 7a949f3..0000000 --- a/utils/update-garm-image-pin.mjs +++ /dev/null @@ -1,91 +0,0 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { execFileSync } from "node:child_process"; - -const pinFile = "apps/garm/image-source.env"; -const deploymentFile = "apps/garm/deployment.yaml"; - -function fail(message) { - console.error(message); - process.exit(1); -} - -function parseEnvFile(content) { - const vars = {}; - for (const line of content.split(/\r?\n/)) { - if (!line || line.startsWith("#")) { - continue; - } - const idx = line.indexOf("="); - if (idx === -1) { - continue; - } - const key = line.slice(0, idx).trim(); - const value = line.slice(idx + 1).trim(); - vars[key] = value; - } - return vars; -} - -function updateOrAdd(content, key, value) { - const pattern = new RegExp(`^${key}=.*$`, "m"); - if (pattern.test(content)) { - return content.replace(pattern, `${key}=${value}`); - } - return `${content.trimEnd()}\n${key}=${value}\n`; -} - -function gitOut(args, options = {}) { - return execFileSync("git", args, { - encoding: "utf8", - ...options, - }).trim(); -} - -function gitRun(args, options = {}) { - execFileSync("git", args, options); -} - -const pinContent = fs.readFileSync(pinFile, "utf8"); -const vars = parseEnvFile(pinContent); -const commit = vars.GARM_COMMIT; -const imageRepo = vars.GARM_IMAGE_REPO || "gitea.lumpiasty.xyz/lumpiasty/garm-k8s"; - -if (!commit || !/^[0-9a-f]{40}$/.test(commit)) { - fail(`Invalid or missing GARM_COMMIT in ${pinFile}`); -} - -const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "garm-main-")); -let commitNumber; -try { - gitRun(["clone", "--filter=blob:none", "https://github.com/cloudbase/garm.git", tmpDir], { - stdio: "ignore", - }); - commitNumber = gitOut(["-C", tmpDir, "rev-list", "--count", commit]); -} finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); -} - -if (!/^\d+$/.test(commitNumber)) { - fail(`Unable to resolve commit number for ${commit}`); -} - -const image = `${imageRepo}:r${commitNumber}`; - -let nextPin = pinContent; -nextPin = updateOrAdd(nextPin, "GARM_COMMIT_NUMBER", commitNumber); -nextPin = updateOrAdd(nextPin, "GARM_IMAGE_REPO", imageRepo); -nextPin = updateOrAdd(nextPin, "GARM_IMAGE", image); -fs.writeFileSync(pinFile, nextPin, "utf8"); - -const deployment = fs.readFileSync(deploymentFile, "utf8"); -const imagePattern = /image:\s*(?:ghcr\.io\/cloudbase\/garm:[^\s]+|gitea\.lumpiasty\.xyz\/(?:Lumpiasty|lumpiasty)\/garm(?:-k8s)?:[^\s]+)/; -if (!imagePattern.test(deployment)) { - fail(`Unable to update garm image in ${deploymentFile}`); -} - -const updatedDeployment = deployment.replace(imagePattern, `image: ${image}`); - -fs.writeFileSync(deploymentFile, updatedDeployment, "utf8"); -console.log(`Pinned garm image to ${image}`); diff --git a/vault/kubernetes-roles/garm.yaml b/vault/kubernetes-roles/garm.yaml deleted file mode 100644 index 615f841..0000000 --- a/vault/kubernetes-roles/garm.yaml +++ /dev/null @@ -1,6 +0,0 @@ -bound_service_account_names: - - garm -bound_service_account_namespaces: - - garm -token_policies: - - garm diff --git a/vault/policy/garm.hcl b/vault/policy/garm.hcl deleted file mode 100644 index 35d5005..0000000 --- a/vault/policy/garm.hcl +++ /dev/null @@ -1,7 +0,0 @@ -path "secret/data/garm" { - capabilities = ["read"] -} - -path "secret/data/backblaze" { - capabilities = ["read"] -}