From d7bda1aeb1c8691c05c11f1ada66f4068253c5a9 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 9 Mar 2026 16:44:27 -0500 Subject: [PATCH] feat: add admin argo app, act-runner, and gitea configuration --- k8s/applications/templates/admin.yaml | 22 +++ k8s/jam-cloud-infra/act-runner.yaml | 73 ++++++++++ k8s/jam-cloud-infra/gitea.yaml | 194 ++++++++++++++++++++++++++ k8s/jam-cloud/admin.yaml | 69 +++++++++ scripts/gitea-repos.yaml | 5 + scripts/sync-gitea-repos.rb | 134 ++++++++++++++++++ 6 files changed, 497 insertions(+) create mode 100644 k8s/applications/templates/admin.yaml create mode 100644 k8s/jam-cloud-infra/act-runner.yaml create mode 100644 k8s/jam-cloud-infra/gitea.yaml create mode 100644 k8s/jam-cloud/admin.yaml create mode 100644 scripts/gitea-repos.yaml create mode 100644 scripts/sync-gitea-repos.rb diff --git a/k8s/applications/templates/admin.yaml b/k8s/applications/templates/admin.yaml new file mode 100644 index 0000000..a24c26c --- /dev/null +++ b/k8s/applications/templates/admin.yaml @@ -0,0 +1,22 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: admin + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://bitbucket.org/jamkazam/video-iac.git + targetRevision: HEAD + path: k8s/jam-cloud + directory: + include: 'admin.yaml' + destination: + server: https://kubernetes.default.svc + namespace: jam-cloud + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/k8s/jam-cloud-infra/act-runner.yaml b/k8s/jam-cloud-infra/act-runner.yaml new file mode 100644 index 0000000..ce2b28a --- /dev/null +++ b/k8s/jam-cloud-infra/act-runner.yaml @@ -0,0 +1,73 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: act-runner-config + namespace: jam-cloud-infra +data: + config.yaml: | + log: + level: info + runner: + capacity: 1 + timeout: 3h + container: + network: "" + # Give the job container access to the Docker daemon so Dagger can spin up its engine + options: "-v /var/run/docker.sock:/var/run/docker.sock" + valid_volumes: + - "**" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: act-runner + namespace: jam-cloud-infra + labels: + app: act-runner +spec: + replicas: 1 + selector: + matchLabels: + app: act-runner + template: + metadata: + labels: + app: act-runner + spec: + containers: + - name: runner + image: gitea/act_runner:latest + env: + - name: CONFIG_FILE + value: /etc/act_runner/config.yaml + - name: GITEA_INSTANCE_URL + value: http://gitea.jam-cloud-infra.svc.cluster.local:80 + - name: GITEA_RUNNER_REGISTRATION_TOKEN + value: "UL6SkV1E8cN6M017vNrmN3X2PPGxmcIDjsbbUvuq" + - name: GITEA_RUNNER_NAME + value: "k8s-runner" + - name: GITEA_RUNNER_LABELS + value: "ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye,dagger:docker://nixpkgs/nix:latest" + securityContext: + privileged: true + volumeMounts: + - name: docker-sock + mountPath: /var/run/docker.sock + - name: config + mountPath: /etc/act_runner + - name: dind + image: docker:23.0.5-dind + env: + - name: DOCKER_TLS_CERTDIR + value: "" + securityContext: + privileged: true + volumeMounts: + - name: docker-sock + mountPath: /var/run/docker.sock + volumes: + - name: docker-sock + emptyDir: {} + - name: config + configMap: + name: act-runner-config diff --git a/k8s/jam-cloud-infra/gitea.yaml b/k8s/jam-cloud-infra/gitea.yaml new file mode 100644 index 0000000..61e8a90 --- /dev/null +++ b/k8s/jam-cloud-infra/gitea.yaml @@ -0,0 +1,194 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: gitea-data + namespace: jam-cloud-infra +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: linode-block-storage-retain +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: gitea-config + namespace: jam-cloud-infra +data: + app.ini: | + APP_NAME = Gitea: Git with a cup of tea + RUN_MODE = prod + WORK_PATH = /data/gitea + + [repository] + ROOT = /data/git/repositories + ALLOWED_SCHEMES = http,https,ssh,git + + [repository.local] + LOCAL_COPY_PATH = /data/gitea/tmp/local-repo + + [repository.upload] + TEMP_PATH = /data/gitea/uploads + + [server] + APP_DATA_PATH = /data/gitea + DOMAIN = git.staging.jamkazam.com + SSH_DOMAIN = localhost + HTTP_PORT = 3000 + ROOT_URL = https://git.staging.jamkazam.com/ + DISABLE_SSH = false + SSH_PORT = 22 + SSH_LISTEN_PORT = 22 + LFS_START_SERVER = false + + [database] + PATH = /data/gitea/gitea.db + DB_TYPE = sqlite3 + HOST = localhost:3306 + NAME = gitea + USER = root + PASSWD = + LOG_SQL = false + + [indexer] + ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve + + [session] + PROVIDER_CONFIG = /data/gitea/sessions + + [picture] + AVATAR_UPLOAD_PATH = /data/gitea/avatars + REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars + + [attachment] + PATH = /data/gitea/attachments + + [log] + MODE = console + LEVEL = info + ROOT_PATH = /data/gitea/log + + [security] + INSTALL_LOCK = true + SECRET_KEY = + REVERSE_PROXY_LIMIT = 1 + REVERSE_PROXY_TRUSTED_PROXIES = * + INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3NzMwMDMyODN9.c3kDP5f7-lo3yR-Z8mPiaAsSEsohWc-LxZksIqTcPWw + + [service] + DISABLE_REGISTRATION = false + REQUIRE_SIGNIN_VIEW = false + + [lfs] + PATH = /data/git/lfs + + [openid] + ENABLE_OPENID_SIGNIN = true + ENABLE_OPENID_SIGNUP = true + + [oauth2] + ENABLE = true + JWT_SECRET = HDi5Td6dRBC240L6ryMI4eMnowcwQVpfKrmIPNrEeAI + + [actions] + ENABLED = true + + [migrations] + ALLOW_SSH_MIGRATIONS = true + ALLOWED_DOMAINS = * + ALLOW_LOCALNETWORKS = true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gitea + namespace: jam-cloud-infra + labels: + app: gitea +spec: + replicas: 1 + selector: + matchLabels: + app: gitea + template: + metadata: + labels: + app: gitea + spec: + containers: + - name: gitea + image: gitea/gitea:1.21.7 + ports: + - containerPort: 3000 + name: http + - containerPort: 22 + name: ssh + env: + - name: GITEA_CUSTOM + value: /etc/gitea + volumeMounts: + - name: data + mountPath: /data + - name: config + mountPath: /etc/gitea/conf/ + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "sleep 5; /sbin/su-exec git gitea admin user create --admin --username seth --password changeme123 --email seth@jamkazam.com --must-change-password=false || true"] + volumes: + - name: data + persistentVolumeClaim: + claimName: gitea-data + - name: config + configMap: + name: gitea-config +--- +apiVersion: v1 +kind: Service +metadata: + name: gitea + namespace: jam-cloud-infra +spec: + selector: + app: gitea + ports: + - port: 80 + targetPort: 3000 + name: http + - port: 22 + targetPort: 22 + name: ssh +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gitea + namespace: jam-cloud-infra + annotations: + cert-manager.io/cluster-issuer: letsencrypt-nginx-production + nginx.ingress.kubernetes.io/proxy-body-size: "512m" +spec: + ingressClassName: nginx + tls: + - secretName: gitea-tls + hosts: + - git.staging.jamkazam.com + - idp.staging.jamkazam.com + - console.staging.jamkazam.com + rules: + - host: git.staging.jamkazam.com + http: &gitea_path + paths: + - path: / + pathType: Prefix + backend: + service: + name: gitea + port: + number: 80 + - host: idp.staging.jamkazam.com + http: *gitea_path + - host: console.staging.jamkazam.com + http: *gitea_path diff --git a/k8s/jam-cloud/admin.yaml b/k8s/jam-cloud/admin.yaml new file mode 100644 index 0000000..feaee43 --- /dev/null +++ b/k8s/jam-cloud/admin.yaml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: jam-cloud +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: admin + namespace: jam-cloud +spec: + replicas: 1 + selector: + matchLabels: + app: admin + template: + metadata: + labels: + app: admin + spec: + imagePullSecrets: + - name: gitea-registry + containers: + - name: web + # This will be replaced by the Dagger build pipeline on first run + image: git.staging.jamkazam.com/seth/jam-cloud-admin:latest + ports: + - containerPort: 8080 + env: + - name: DATABASE_URL + value: "postgres://jam:jam@72.14.176.182:5432/jam" +--- +apiVersion: v1 +kind: Service +metadata: + name: admin + namespace: jam-cloud +spec: + selector: + app: admin + ports: + - protocol: TCP + port: 80 + targetPort: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: admin + namespace: jam-cloud + annotations: + cert-manager.io/cluster-issuer: letsencrypt-nginx-production +spec: + ingressClassName: nginx + tls: + - secretName: admin-tls + hosts: + - admin.staging.jamkazam.com + rules: + - host: admin.staging.jamkazam.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: admin + port: + number: 80 diff --git a/scripts/gitea-repos.yaml b/scripts/gitea-repos.yaml new file mode 100644 index 0000000..2119efb --- /dev/null +++ b/scripts/gitea-repos.yaml @@ -0,0 +1,5 @@ +repositories: + - name: "jam-cloud" + url: "https://bitbucket.org/jamkazam/jam-cloud.git" + - name: "video-iac" + url: "https://bitbucket.org/jamkazam/video-iac.git" diff --git a/scripts/sync-gitea-repos.rb b/scripts/sync-gitea-repos.rb new file mode 100644 index 0000000..b3f5021 --- /dev/null +++ b/scripts/sync-gitea-repos.rb @@ -0,0 +1,134 @@ +#!/usr/bin/env ruby +require 'net/http' +require 'json' +require 'uri' +require 'yaml' +require 'optparse' + +# Configuration mapping +ENV_CONFIG = { + 'stg' => { + url: "https://git.staging.jamkazam.com/api/v1", + token_var: "GITEA_TOKEN_STG" + }, + 'prd' => { + url: "https://git.jamkazam.com/api/v1", + token_var: "GITEA_TOKEN_PRD" + } +} + +options = { env: 'stg' } +OptionParser.new do |opts| + opts.banner = "Usage: ruby sync-gitea-repos.rb [options] " + opts.on("-e", "--env ENV", "Environment (stg or prd, default stg)") { |v| options[:env] = v } +end.parse! + +if ARGV.length < 2 + puts "āŒ Error: Missing Bitbucket credentials." + puts "Usage: ruby sync-gitea-repos.rb --env [stg|prd] " + exit 1 +end + +BB_USER = ARGV[0] +BB_PASS = ARGV[1] + +# Select config +config = ENV_CONFIG[options[:env]] +if config.nil? + puts "āŒ Error: Invalid environment '#{options[:env]}'. Use 'stg' or 'prd'." + exit 1 +end + +# Resolve Token +GITEA_TOKEN = ENV[config[:token_var]] || ENV['GITEA_TOKEN'] +GITEA_URL = config[:url] +GITEA_OWNER = "seth" +MANIFEST_FILE = File.expand_path('gitea-repos.yaml', __dir__) + +if GITEA_TOKEN.nil? || GITEA_TOKEN.empty? + puts "āŒ Error: API Token environment variable '#{config[:token_var]}' is not set." + exit 1 +end + +def gitea_request(method, path, payload = nil) + uri = URI.parse("#{GITEA_URL}#{path}") + header = { + 'Content-Type' => 'application/json', + 'Authorization' => "token #{GITEA_TOKEN}" + } + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.read_timeout = 300 + + case method + when :get then req = Net::HTTP::Get.new(uri.request_uri, header) + when :post then req = Net::HTTP::Post.new(uri.request_uri, header); req.body = payload.to_json if payload + when :delete then req = Net::HTTP::Delete.new(uri.request_uri, header) + end + http.request(req) +end + +puts "šŸš€ Reconciling Gitea mirrors for #{options[:env].upcase}..." +puts "šŸ”— API: #{GITEA_URL}" + +# 1. Load Manifest +manifest = YAML.load_file(MANIFEST_FILE) +desired_repos = manifest['repositories'] +desired_names = desired_repos.map { |r| r['name'] } + +# 2. Get Current State from Gitea +puts "šŸ” Fetching current repositories..." +resp = gitea_request(:get, "/users/#{GITEA_OWNER}/repos") +if resp.code != "200" + puts "āŒ Failed to fetch repos: #{resp.code} #{resp.body}" + exit 1 +end +current_repos = JSON.parse(resp.body) +current_names = current_repos.map { |r| r['name'] } + +# 3. Reconcile: Delete repos not in manifest +repos_to_delete = current_names - desired_names +repos_to_delete.each do |name| + puts "šŸ—‘ļø Deleting repository '#{name}' (not in manifest)..." + gitea_request(:delete, "/repos/#{GITEA_OWNER}/#{name}") +end + +# 4. Reconcile: Create/Update mirrors +desired_repos.each do |repo| + name = repo['name'] + url = repo['url'] + + if current_names.include?(name) + repo_info = current_repos.find { |r| r['name'] == name } + if repo_info['empty'] + puts "āš ļø Mirror '#{name}' exists but is EMPTY. Recreating..." + gitea_request(:delete, "/repos/#{GITEA_OWNER}/#{name}") + else + puts "āœ… Mirror '#{name}' already exists. Skipping." + next + end + end + + puts "šŸš€ Creating mirror for '#{name}'..." + payload = { + "clone_addr" => url, + "auth_username" => BB_USER, + "auth_password" => BB_PASS, + "repo_name" => name, + "mirror" => true, + "mirror_interval" => "10m", + "service" => "bitbucket", + "repo_owner" => GITEA_OWNER, + "lfs" => true, + "releases" => true + } + + res = gitea_request(:post, "/repos/migrate", payload) + if res.code == "201" + puts "✨ Successfully created mirror for '#{name}'." + else + puts "āŒ Failed to create mirror for '#{name}': #{res.code} #{res.body}" + end +end + +puts "\nšŸŽÆ Reconcile complete for #{options[:env].upcase}."