From 7653c85649eb7a0e1a22c55be980d8dd27d6fd53 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Fri, 23 Jan 2026 15:51:53 -0800 Subject: [PATCH 1/8] Refactor Forgejo config to use 1Password secrets - Move app.ini to ansible template with secrets from 1Password - Enable Forgejo Actions in config - Add DEFAULT_REPO_UNITS with repo.actions - Clean up unused MySQL database fields (using SQLite) Co-Authored-By: Claude Opus 4.5 --- ansible/playbooks/indri.yml | 39 ++++++++++ ansible/roles/forgejo/defaults/main.yml | 51 ++++++++++++++ ansible/roles/forgejo/tasks/main.yml | 33 +++++---- ansible/roles/forgejo/templates/app.ini.j2 | 82 ++++++++++++++++++++++ 4 files changed, 190 insertions(+), 15 deletions(-) create mode 100644 ansible/roles/forgejo/defaults/main.yml create mode 100644 ansible/roles/forgejo/templates/app.ini.j2 diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index c3d5112..6e962f1 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -22,6 +22,45 @@ no_log: true tags: [borgmatic] + # Forgejo secrets + - name: Fetch forgejo LFS JWT secret + ansible.builtin.command: + cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields lfs-jwt-secret --reveal + delegate_to: localhost + register: _forgejo_lfs_jwt + changed_when: false + no_log: true + check_mode: false + tags: [forgejo] + + - name: Fetch forgejo internal token + ansible.builtin.command: + cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields internal-token --reveal + delegate_to: localhost + register: _forgejo_internal_token + changed_when: false + no_log: true + check_mode: false + tags: [forgejo] + + - name: Fetch forgejo OAuth2 JWT secret + ansible.builtin.command: + cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields oauth2-jwt-secret --reveal + delegate_to: localhost + register: _forgejo_oauth2_jwt + changed_when: false + no_log: true + check_mode: false + tags: [forgejo] + + - name: Set forgejo secrets facts + ansible.builtin.set_fact: + forgejo_lfs_jwt_secret: "{{ _forgejo_lfs_jwt.stdout }}" + forgejo_internal_token: "{{ _forgejo_internal_token.stdout }}" + forgejo_oauth2_jwt_secret: "{{ _forgejo_oauth2_jwt.stdout }}" + no_log: true + tags: [forgejo] + roles: - role: alloy tags: alloy diff --git a/ansible/roles/forgejo/defaults/main.yml b/ansible/roles/forgejo/defaults/main.yml new file mode 100644 index 0000000..23396e8 --- /dev/null +++ b/ansible/roles/forgejo/defaults/main.yml @@ -0,0 +1,51 @@ +--- +# Forgejo configuration +# Secrets are fetched from 1Password in the playbook pre_tasks + +forgejo_app_name: Forgejo +forgejo_app_slogan: "Beyond coding. We Forge." +forgejo_run_user: forgejo +forgejo_run_mode: prod + +# Paths (brew-managed for now, will change to mcquack in Phase 3) +forgejo_work_path: /opt/homebrew/var/forgejo +forgejo_config_path: "{{ forgejo_work_path }}/custom/conf/app.ini" +forgejo_data_path: "{{ forgejo_work_path }}/data" +forgejo_repo_root: "{{ forgejo_data_path }}/forgejo-repositories" +forgejo_lfs_path: "{{ forgejo_data_path }}/lfs" +forgejo_log_path: "{{ forgejo_work_path }}/log" + +# Server settings +forgejo_http_addr: 0.0.0.0 +forgejo_http_port: 3001 +forgejo_domain: forge.tail8d86e.ts.net +forgejo_ssh_domain: "{{ forgejo_domain }}" +forgejo_root_url: "https://{{ forgejo_domain }}/" +forgejo_offline_mode: true + +# SSH settings (built-in SSH server) +forgejo_disable_ssh: false +forgejo_start_ssh_server: true +forgejo_builtin_ssh_user: forgejo +forgejo_ssh_port: 22 +forgejo_ssh_listen_port: 2200 +forgejo_lfs_start_server: true + +# Database (SQLite) +forgejo_db_type: sqlite3 +forgejo_db_path: "{{ forgejo_data_path }}/forgejo.db" + +# Service settings +forgejo_disable_registration: true +forgejo_require_signin_view: false + +# Session +forgejo_session_provider: file + +# Logging +forgejo_log_mode: console +forgejo_log_level: info + +# Actions (Forgejo CI) +forgejo_actions_enabled: true +forgejo_actions_default_url: https://code.forgejo.org diff --git a/ansible/roles/forgejo/tasks/main.yml b/ansible/roles/forgejo/tasks/main.yml index 1e021c4..a6d27b9 100644 --- a/ansible/roles/forgejo/tasks/main.yml +++ b/ansible/roles/forgejo/tasks/main.yml @@ -1,26 +1,29 @@ --- -# Note: forgejo config at /opt/homebrew/var/forgejo/custom/conf/app.ini -# is not managed here (contains secrets). It is backed up by borgmatic. +# Forgejo role +# +# Currently uses brew-managed forgejo. Phase 3 of ci-cd-bootstrap will +# transition to mcquack LaunchAgent with CI-built binary. +# +# Secrets (lfs_jwt_secret, internal_token, oauth2_jwt_secret) are fetched +# from 1Password in the playbook pre_tasks. - name: Install forgejo via homebrew community.general.homebrew: name: forgejo state: present -- name: Check forgejo config exists - ansible.builtin.stat: - path: /opt/homebrew/var/forgejo/custom/conf/app.ini - register: forgejo_config +- name: Ensure forgejo config directory exists + ansible.builtin.file: + path: "{{ forgejo_work_path }}/custom/conf" + state: directory + mode: '0755' -- name: Fail if forgejo config is missing - ansible.builtin.fail: - msg: | - Forgejo config not found at /opt/homebrew/var/forgejo/custom/conf/app.ini - This file contains secrets and is not managed by ansible. - To restore from backup, run: - borgmatic --config ~/.config/borgmatic/config.yaml extract --archive latest \ - --path /opt/homebrew/var/forgejo/custom/conf/app.ini - when: not forgejo_config.stat.exists +- name: Deploy forgejo config + ansible.builtin.template: + src: app.ini.j2 + dest: "{{ forgejo_config_path }}" + mode: '0600' + notify: Restart forgejo - name: Ensure forgejo service is started ansible.builtin.command: brew services start forgejo diff --git a/ansible/roles/forgejo/templates/app.ini.j2 b/ansible/roles/forgejo/templates/app.ini.j2 new file mode 100644 index 0000000..ec0c396 --- /dev/null +++ b/ansible/roles/forgejo/templates/app.ini.j2 @@ -0,0 +1,82 @@ +# {{ ansible_managed }} +APP_NAME = {{ forgejo_app_name }} +APP_SLOGAN = {{ forgejo_app_slogan }} +RUN_USER = {{ forgejo_run_user }} +WORK_PATH = {{ forgejo_work_path }} +RUN_MODE = {{ forgejo_run_mode }} + +[server] +HTTP_ADDR = {{ forgejo_http_addr }} +HTTP_PORT = {{ forgejo_http_port }} +SSH_DOMAIN = {{ forgejo_ssh_domain }} +DOMAIN = {{ forgejo_domain }} +ROOT_URL = {{ forgejo_root_url }} +APP_DATA_PATH = {{ forgejo_data_path }} +DISABLE_SSH = {{ forgejo_disable_ssh | lower }} +START_SSH_SERVER = {{ forgejo_start_ssh_server | lower }} +BUILTIN_SSH_SERVER_USER = {{ forgejo_builtin_ssh_user }} +SSH_PORT = {{ forgejo_ssh_port }} +SSH_LISTEN_PORT = {{ forgejo_ssh_listen_port }} +LFS_START_SERVER = {{ forgejo_lfs_start_server | lower }} +LFS_JWT_SECRET = {{ forgejo_lfs_jwt_secret }} +OFFLINE_MODE = {{ forgejo_offline_mode | lower }} + +[database] +DB_TYPE = {{ forgejo_db_type }} +PATH = {{ forgejo_db_path }} +LOG_SQL = false + +[repository] +ROOT = {{ forgejo_repo_root }} +DEFAULT_REPO_UNITS = repo.code,repo.issues,repo.pulls,repo.releases,repo.wiki,repo.projects,repo.packages,repo.actions + +[lfs] +PATH = {{ forgejo_lfs_path }} + +[mailer] +ENABLED = false + +[service] +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = {{ forgejo_disable_registration | lower }} +ALLOW_ONLY_EXTERNAL_REGISTRATION = false +ENABLE_CAPTCHA = false +REQUIRE_SIGNIN_VIEW = {{ forgejo_require_signin_view | lower }} +DEFAULT_KEEP_EMAIL_PRIVATE = false +DEFAULT_ALLOW_CREATE_ORGANIZATION = true +DEFAULT_ENABLE_TIMETRACKING = true +NO_REPLY_ADDRESS = noreply.indri + +[openid] +ENABLE_OPENID_SIGNIN = false +ENABLE_OPENID_SIGNUP = false + +[cron.update_checker] +ENABLED = false + +[session] +PROVIDER = {{ forgejo_session_provider }} + +[log] +MODE = {{ forgejo_log_mode }} +LEVEL = {{ forgejo_log_level }} +ROOT_PATH = {{ forgejo_log_path }} + +[repository.pull-request] +DEFAULT_MERGE_STYLE = merge + +[repository.signing] +DEFAULT_TRUST_MODEL = committer + +[security] +INSTALL_LOCK = true +INTERNAL_TOKEN = {{ forgejo_internal_token }} +PASSWORD_HASH_ALGO = pbkdf2_hi + +[oauth2] +JWT_SECRET = {{ forgejo_oauth2_jwt_secret }} + +[actions] +ENABLED = {{ forgejo_actions_enabled | lower }} +DEFAULT_ACTIONS_URL = {{ forgejo_actions_default_url }} -- 2.50.1 (Apple Git-155) From feff296979aeaf4e474cf78290a51d000f4092b3 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Fri, 23 Jan 2026 16:09:27 -0800 Subject: [PATCH 2/8] Add Forgejo Actions runner k8s deployment - ArgoCD Application for forgejo-runner - Deployment with Docker socket access for running workflow containers - Secret template for runner registration token (via op inject) Co-Authored-By: Claude Opus 4.5 --- argocd/apps/forgejo-runner.yaml | 23 +++++++ .../manifests/forgejo-runner/deployment.yaml | 63 +++++++++++++++++++ .../forgejo-runner/kustomization.yaml | 7 +++ .../manifests/forgejo-runner/namespace.yaml | 4 ++ .../forgejo-runner/secret-token.yaml.tpl | 10 +++ .../forgejo-runner/serviceaccount.yaml | 5 ++ 6 files changed, 112 insertions(+) create mode 100644 argocd/apps/forgejo-runner.yaml create mode 100644 argocd/manifests/forgejo-runner/deployment.yaml create mode 100644 argocd/manifests/forgejo-runner/kustomization.yaml create mode 100644 argocd/manifests/forgejo-runner/namespace.yaml create mode 100644 argocd/manifests/forgejo-runner/secret-token.yaml.tpl create mode 100644 argocd/manifests/forgejo-runner/serviceaccount.yaml diff --git a/argocd/apps/forgejo-runner.yaml b/argocd/apps/forgejo-runner.yaml new file mode 100644 index 0000000..a584d33 --- /dev/null +++ b/argocd/apps/forgejo-runner.yaml @@ -0,0 +1,23 @@ +# Forgejo Actions Runner +# Runs in k8s, polls Forgejo for workflow jobs +# +# Before syncing, create the runner token secret: +# kubectl create namespace forgejo-runner +# op inject -i argocd/manifests/forgejo-runner/secret-token.yaml.tpl | kubectl apply -f - +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: forgejo-runner + namespace: argocd +spec: + project: default + source: + repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + targetRevision: main + path: argocd/manifests/forgejo-runner + destination: + server: https://kubernetes.default.svc + namespace: forgejo-runner + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/argocd/manifests/forgejo-runner/deployment.yaml b/argocd/manifests/forgejo-runner/deployment.yaml new file mode 100644 index 0000000..90914e9 --- /dev/null +++ b/argocd/manifests/forgejo-runner/deployment.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: forgejo-runner + namespace: forgejo-runner +spec: + replicas: 1 + selector: + matchLabels: + app: forgejo-runner + template: + metadata: + labels: + app: forgejo-runner + spec: + serviceAccountName: forgejo-runner + containers: + - name: runner + image: code.forgejo.org/forgejo/runner:3.5.1 + env: + - name: FORGEJO_INSTANCE_URL + value: "https://forge.tail8d86e.ts.net" + - name: RUNNER_NAME + value: "k8s-runner-1" + - name: RUNNER_TOKEN + valueFrom: + secretKeyRef: + name: forgejo-runner-token + key: token + command: + - /bin/sh + - -c + - | + # Register runner if not already registered + if [ ! -f /data/.runner ]; then + forgejo-runner register \ + --instance "$FORGEJO_INSTANCE_URL" \ + --token "$RUNNER_TOKEN" \ + --name "$RUNNER_NAME" \ + --labels "ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://ubuntu:22.04" \ + --no-interactive + fi + # Start the runner daemon + forgejo-runner daemon + volumeMounts: + - name: runner-data + mountPath: /data + - name: docker-sock + mountPath: /var/run/docker.sock + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1000m" + volumes: + - name: runner-data + emptyDir: {} + - name: docker-sock + hostPath: + path: /var/run/docker.sock + type: Socket diff --git a/argocd/manifests/forgejo-runner/kustomization.yaml b/argocd/manifests/forgejo-runner/kustomization.yaml new file mode 100644 index 0000000..558b9ff --- /dev/null +++ b/argocd/manifests/forgejo-runner/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: forgejo-runner +resources: + - namespace.yaml + - serviceaccount.yaml + - deployment.yaml diff --git a/argocd/manifests/forgejo-runner/namespace.yaml b/argocd/manifests/forgejo-runner/namespace.yaml new file mode 100644 index 0000000..19441b1 --- /dev/null +++ b/argocd/manifests/forgejo-runner/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: forgejo-runner diff --git a/argocd/manifests/forgejo-runner/secret-token.yaml.tpl b/argocd/manifests/forgejo-runner/secret-token.yaml.tpl new file mode 100644 index 0000000..427d8df --- /dev/null +++ b/argocd/manifests/forgejo-runner/secret-token.yaml.tpl @@ -0,0 +1,10 @@ +# Template for op inject +# Usage: op inject -i secret-token.yaml.tpl | kubectl apply -f - +apiVersion: v1 +kind: Secret +metadata: + name: forgejo-runner-token + namespace: forgejo-runner +type: Opaque +stringData: + token: "op://blumeops/w3663ffnvkewbftncqxtcpeavy/runner_reg" diff --git a/argocd/manifests/forgejo-runner/serviceaccount.yaml b/argocd/manifests/forgejo-runner/serviceaccount.yaml new file mode 100644 index 0000000..ef8cb25 --- /dev/null +++ b/argocd/manifests/forgejo-runner/serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: forgejo-runner + namespace: forgejo-runner -- 2.50.1 (Apple Git-155) From 266f5944b98586e134dc9a04c899da5b9793f250 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Fri, 23 Jan 2026 16:20:04 -0800 Subject: [PATCH 3/8] Fix Forgejo runner to use internal k8s service URL Use http://forge.tailscale.svc.cluster.local:3001 instead of https://forge.tail8d86e.ts.net - the Tailscale operator provides egress routing to indri via this internal service. Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/forgejo-runner/deployment.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/argocd/manifests/forgejo-runner/deployment.yaml b/argocd/manifests/forgejo-runner/deployment.yaml index 90914e9..aaca92a 100644 --- a/argocd/manifests/forgejo-runner/deployment.yaml +++ b/argocd/manifests/forgejo-runner/deployment.yaml @@ -18,8 +18,9 @@ spec: - name: runner image: code.forgejo.org/forgejo/runner:3.5.1 env: + # Use internal k8s service via Tailscale operator egress - name: FORGEJO_INSTANCE_URL - value: "https://forge.tail8d86e.ts.net" + value: "http://forge.tailscale.svc.cluster.local:3001" - name: RUNNER_NAME value: "k8s-runner-1" - name: RUNNER_TOKEN -- 2.50.1 (Apple Git-155) From 460449326eae223047b3ae9175424ef5ddb27bfd Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Fri, 23 Jan 2026 16:28:34 -0800 Subject: [PATCH 4/8] Switch Forgejo runner to Kubernetes backend - Use k8s pods instead of Docker containers for job execution - Add RBAC for runner to create/manage job pods - Add ConfigMap with runner config for kubernetes backend - Remove Docker socket mount (no longer needed) Co-Authored-By: Claude Opus 4.5 --- .../manifests/forgejo-runner/configmap.yaml | 26 ++++++++++++++++++ .../manifests/forgejo-runner/deployment.yaml | 15 +++++------ .../forgejo-runner/kustomization.yaml | 2 ++ argocd/manifests/forgejo-runner/rbac.yaml | 27 +++++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 argocd/manifests/forgejo-runner/configmap.yaml create mode 100644 argocd/manifests/forgejo-runner/rbac.yaml diff --git a/argocd/manifests/forgejo-runner/configmap.yaml b/argocd/manifests/forgejo-runner/configmap.yaml new file mode 100644 index 0000000..1728edb --- /dev/null +++ b/argocd/manifests/forgejo-runner/configmap.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: forgejo-runner-config + namespace: forgejo-runner +data: + config.yaml: | + log: + level: info + runner: + file: /data/.runner + capacity: 1 + timeout: 3h + container: + # Use Kubernetes to run job pods + backend: kubernetes + kubernetes: + namespace: forgejo-runner + # Job pods use these resource limits + resources: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "1000m" + memory: "1Gi" diff --git a/argocd/manifests/forgejo-runner/deployment.yaml b/argocd/manifests/forgejo-runner/deployment.yaml index aaca92a..4c02da8 100644 --- a/argocd/manifests/forgejo-runner/deployment.yaml +++ b/argocd/manifests/forgejo-runner/deployment.yaml @@ -41,13 +41,13 @@ spec: --labels "ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://ubuntu:22.04" \ --no-interactive fi - # Start the runner daemon - forgejo-runner daemon + # Start the runner daemon with config + forgejo-runner daemon --config /config/config.yaml volumeMounts: - name: runner-data mountPath: /data - - name: docker-sock - mountPath: /var/run/docker.sock + - name: runner-config + mountPath: /config resources: requests: memory: "256Mi" @@ -58,7 +58,6 @@ spec: volumes: - name: runner-data emptyDir: {} - - name: docker-sock - hostPath: - path: /var/run/docker.sock - type: Socket + - name: runner-config + configMap: + name: forgejo-runner-config diff --git a/argocd/manifests/forgejo-runner/kustomization.yaml b/argocd/manifests/forgejo-runner/kustomization.yaml index 558b9ff..eb3839e 100644 --- a/argocd/manifests/forgejo-runner/kustomization.yaml +++ b/argocd/manifests/forgejo-runner/kustomization.yaml @@ -4,4 +4,6 @@ namespace: forgejo-runner resources: - namespace.yaml - serviceaccount.yaml + - rbac.yaml + - configmap.yaml - deployment.yaml diff --git a/argocd/manifests/forgejo-runner/rbac.yaml b/argocd/manifests/forgejo-runner/rbac.yaml new file mode 100644 index 0000000..9f25bca --- /dev/null +++ b/argocd/manifests/forgejo-runner/rbac.yaml @@ -0,0 +1,27 @@ +# RBAC for Forgejo runner to create job pods +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: forgejo-runner + namespace: forgejo-runner +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "create", "delete", "watch"] + - apiGroups: [""] + resources: ["pods/log"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: forgejo-runner + namespace: forgejo-runner +subjects: + - kind: ServiceAccount + name: forgejo-runner + namespace: forgejo-runner +roleRef: + kind: Role + name: forgejo-runner + apiGroup: rbac.authorization.k8s.io -- 2.50.1 (Apple Git-155) From 338456b8204c52eef8a14890ab05008e35947cce Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Fri, 23 Jan 2026 16:40:37 -0800 Subject: [PATCH 5/8] Switch Forgejo runner to host mode - Use host labels (ubuntu-latest:host) instead of docker:// - Remove RBAC (not needed for host mode) - Simplify configmap (no container backend config needed) Host mode runs jobs directly in the runner container, which is acceptable since we control all workflows and the pod provides isolation from indri. Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 5 ++++ .../manifests/forgejo-runner/configmap.yaml | 13 --------- .../manifests/forgejo-runner/deployment.yaml | 2 +- .../forgejo-runner/kustomization.yaml | 1 - argocd/manifests/forgejo-runner/rbac.yaml | 27 ------------------- 5 files changed, 6 insertions(+), 42 deletions(-) delete mode 100644 argocd/manifests/forgejo-runner/rbac.yaml diff --git a/CLAUDE.md b/CLAUDE.md index 67687db..82ed044 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -102,6 +102,11 @@ kubectl --context=minikube-indri logs -n # View logs Note: The user has fish abbreviations `ki` for `kubectl --context=minikube-indri` and `k9i` for `k9s --context=minikube-indri`, but these only work in interactive shells. +**ArgoCD login (when token expires):** +```fish +argocd login argocd.tail8d86e.ts.net --username admin --password "$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get srogeebssulhtb6tnqd7ls6qey --fields password --reveal)" +``` + ### Indri Services (via Ansible) Some services remain on indri outside of Kubernetes: diff --git a/argocd/manifests/forgejo-runner/configmap.yaml b/argocd/manifests/forgejo-runner/configmap.yaml index 1728edb..584efe0 100644 --- a/argocd/manifests/forgejo-runner/configmap.yaml +++ b/argocd/manifests/forgejo-runner/configmap.yaml @@ -11,16 +11,3 @@ data: file: /data/.runner capacity: 1 timeout: 3h - container: - # Use Kubernetes to run job pods - backend: kubernetes - kubernetes: - namespace: forgejo-runner - # Job pods use these resource limits - resources: - requests: - cpu: "100m" - memory: "256Mi" - limits: - cpu: "1000m" - memory: "1Gi" diff --git a/argocd/manifests/forgejo-runner/deployment.yaml b/argocd/manifests/forgejo-runner/deployment.yaml index 4c02da8..d0939de 100644 --- a/argocd/manifests/forgejo-runner/deployment.yaml +++ b/argocd/manifests/forgejo-runner/deployment.yaml @@ -38,7 +38,7 @@ spec: --instance "$FORGEJO_INSTANCE_URL" \ --token "$RUNNER_TOKEN" \ --name "$RUNNER_NAME" \ - --labels "ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://ubuntu:22.04" \ + --labels "ubuntu-latest:host,ubuntu-22.04:host" \ --no-interactive fi # Start the runner daemon with config diff --git a/argocd/manifests/forgejo-runner/kustomization.yaml b/argocd/manifests/forgejo-runner/kustomization.yaml index eb3839e..332c49c 100644 --- a/argocd/manifests/forgejo-runner/kustomization.yaml +++ b/argocd/manifests/forgejo-runner/kustomization.yaml @@ -4,6 +4,5 @@ namespace: forgejo-runner resources: - namespace.yaml - serviceaccount.yaml - - rbac.yaml - configmap.yaml - deployment.yaml diff --git a/argocd/manifests/forgejo-runner/rbac.yaml b/argocd/manifests/forgejo-runner/rbac.yaml deleted file mode 100644 index 9f25bca..0000000 --- a/argocd/manifests/forgejo-runner/rbac.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# RBAC for Forgejo runner to create job pods -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: forgejo-runner - namespace: forgejo-runner -rules: - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "create", "delete", "watch"] - - apiGroups: [""] - resources: ["pods/log"] - verbs: ["get", "list", "watch"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: forgejo-runner - namespace: forgejo-runner -subjects: - - kind: ServiceAccount - name: forgejo-runner - namespace: forgejo-runner -roleRef: - kind: Role - name: forgejo-runner - apiGroup: rbac.authorization.k8s.io -- 2.50.1 (Apple Git-155) From 70f2227313f03415ae7d8daaab7ac15edc45f690 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Fri, 23 Jan 2026 16:51:51 -0800 Subject: [PATCH 6/8] Add test workflow and document CI/CD setup - Create .forgejo/workflows/test.yaml with basic hello world workflow - Update README.md with CI/CD section explaining Forgejo Actions Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/test.yaml | 20 ++++++++++++++++++++ README.md | 4 ++++ 2 files changed, 24 insertions(+) create mode 100644 .forgejo/workflows/test.yaml diff --git a/.forgejo/workflows/test.yaml b/.forgejo/workflows/test.yaml new file mode 100644 index 0000000..dd6e668 --- /dev/null +++ b/.forgejo/workflows/test.yaml @@ -0,0 +1,20 @@ +name: Test CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Hello World + run: | + echo "Hello from Forgejo Actions!" + echo "Runner: $(hostname)" + echo "Repository: ${{ gitea.repository }}" + echo "Branch: ${{ gitea.ref_name }}" diff --git a/README.md b/README.md index 7da378f..61ea208 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ Hooks include: - **TOML**: taplo - **JSON**: prettier +## CI/CD + +This repo uses [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/) for CI/CD. Workflows live in `.forgejo/workflows/` (not `.github/workflows/`). The runner executes jobs in host mode within the Kubernetes cluster. + ## Documentation Detailed documentation lives in my personal zettelkasten, which is not included in this repository. You can view the docs with: -- 2.50.1 (Apple Git-155) From b2c5716e21c5d30b8160484e64c0dab320c3d1bb Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Fri, 23 Jan 2026 16:54:53 -0800 Subject: [PATCH 7/8] Fix test workflow for host mode, add custom runner image plan - Use git clone instead of actions/checkout (no Node.js in runner) - Add Use Case 0 to P4: build custom runner image with Node.js The stock forgejo/runner image lacks Node.js, so standard GitHub Actions don't work in host mode. P4 now includes building a custom runner image as the first step. Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/test.yaml | 8 ++- plans/ci-cd-bootstrap/P4_container_builds.md | 72 ++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/test.yaml b/.forgejo/workflows/test.yaml index dd6e668..a0552bf 100644 --- a/.forgejo/workflows/test.yaml +++ b/.forgejo/workflows/test.yaml @@ -10,7 +10,12 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout (git clone) + run: | + git clone --depth 1 --branch "${{ gitea.ref_name }}" \ + "${{ gitea.server_url }}/${{ gitea.repository }}.git" . + env: + GIT_SSL_NO_VERIFY: "true" - name: Hello World run: | @@ -18,3 +23,4 @@ jobs: echo "Runner: $(hostname)" echo "Repository: ${{ gitea.repository }}" echo "Branch: ${{ gitea.ref_name }}" + ls -la diff --git a/plans/ci-cd-bootstrap/P4_container_builds.md b/plans/ci-cd-bootstrap/P4_container_builds.md index 6e4297c..6a7ced8 100644 --- a/plans/ci-cd-bootstrap/P4_container_builds.md +++ b/plans/ci-cd-bootstrap/P4_container_builds.md @@ -17,6 +17,78 @@ With Forgejo Actions operational, we can now build container images for: --- +## Use Case 0: Custom Runner Image + +### Problem + +The stock `forgejo/runner` image lacks tools needed for standard GitHub Actions: +- **Node.js** - Required by most actions (checkout, setup-*, etc.) +- **Docker CLI** - For building container images +- **Git** - For repository operations +- **Common build tools** - make, gcc, etc. + +In host mode, jobs run directly in the runner container, so these tools must be pre-installed. + +### Solution + +Build a custom runner image with all necessary tools: + +```dockerfile +# argocd/manifests/forgejo-runner/Dockerfile +FROM code.forgejo.org/forgejo/runner:3.5.1 + +# Install Node.js (required for most GitHub Actions) +RUN apt-get update && apt-get install -y \ + nodejs \ + npm \ + git \ + curl \ + docker.io \ + make \ + gcc \ + && rm -rf /var/lib/apt/lists/* +``` + +### Workflow + +Create `.forgejo/workflows/build-runner.yml`: + +```yaml +name: Build Runner Image + +on: + push: + paths: + - 'argocd/manifests/forgejo-runner/Dockerfile' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + run: | + git clone --depth 1 "${{ gitea.server_url }}/${{ gitea.repository }}.git" . + + - name: Build and push + run: | + cd argocd/manifests/forgejo-runner + docker build -t registry.tail8d86e.ts.net/blumeops/forgejo-runner:latest . + docker push registry.tail8d86e.ts.net/blumeops/forgejo-runner:latest +``` + +### Update Deployment + +Once the custom image is built, update `argocd/manifests/forgejo-runner/deployment.yaml`: + +```yaml +image: registry.tail8d86e.ts.net/blumeops/forgejo-runner:latest +``` + +This enables standard GitHub Actions like `actions/checkout@v4` to work in host mode. + +--- + ## Use Case 1: devpi Custom Image ### Current State -- 2.50.1 (Apple Git-155) From 66c20e8eddf874f0744e5561a85289abbf5e5c23 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Fri, 23 Jan 2026 16:56:18 -0800 Subject: [PATCH 8/8] Fix checkout to use head_ref for PRs gitea.ref_name returns PR number for pull_request events, need to use gitea.head_ref to get the actual branch name. Co-Authored-By: Claude Opus 4.5 --- .forgejo/workflows/test.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/test.yaml b/.forgejo/workflows/test.yaml index a0552bf..6f9faf1 100644 --- a/.forgejo/workflows/test.yaml +++ b/.forgejo/workflows/test.yaml @@ -12,7 +12,9 @@ jobs: steps: - name: Checkout (git clone) run: | - git clone --depth 1 --branch "${{ gitea.ref_name }}" \ + # For PRs use head_ref (branch name), for pushes use ref_name + BRANCH="${{ gitea.head_ref || gitea.ref_name }}" + git clone --depth 1 --branch "$BRANCH" \ "${{ gitea.server_url }}/${{ gitea.repository }}.git" . env: GIT_SSL_NO_VERIFY: "true" @@ -22,5 +24,6 @@ jobs: echo "Hello from Forgejo Actions!" echo "Runner: $(hostname)" echo "Repository: ${{ gitea.repository }}" - echo "Branch: ${{ gitea.ref_name }}" + echo "Event: ${{ gitea.event_name }}" + echo "Ref: ${{ gitea.ref }}" ls -la -- 2.50.1 (Apple Git-155)