From 74bd5abe5403f8b07ef8705443e832af91258017 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Wed, 4 Feb 2026 09:11:01 -0800 Subject: [PATCH] Add IaC for Forgejo Actions secrets via Ansible (#107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - New `forgejo_actions_secrets` Ansible role syncs repository-level Actions secrets from 1Password to Forgejo via the Forgejo API - Replaces manual process of copying secrets from 1Password to Forgejo UI - Documents the one-time PAT setup requirement in forgejo.md ## Manual Setup Required Before this role can run, a Forgejo PAT must be created: 1. Go to https://forge.ops.eblu.me/user/settings/applications 2. Create a new token with `write:repository` scope 3. Store it in 1Password → "Forgejo Secrets" item → `api-token` field This has already been done. ## Test Plan - [x] Ran `mise run provision-indri -- --tags forgejo_actions_secrets` successfully - [x] Verified secret synced (API returned 204 = updated existing) - [x] Ansible-lint passes 🤖 Generated with [Claude Code](https://claude.ai/code) Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/107 --- ansible/playbooks/indri.yml | 30 +++++++++++++++++ .../forgejo_actions_secrets/defaults/main.yml | 15 +++++++++ .../forgejo_actions_secrets/tasks/main.yml | 32 +++++++++++++++++++ ...ature-forgejo-actions-secrets-iac.infra.md | 1 + docs/reference/services/forgejo.md | 25 +++++++++++---- 5 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 ansible/roles/forgejo_actions_secrets/defaults/main.yml create mode 100644 ansible/roles/forgejo_actions_secrets/tasks/main.yml create mode 100644 docs/changelog.d/feature-forgejo-actions-secrets-iac.infra.md diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index 8af2d6c..6fb9c4e 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -61,6 +61,34 @@ no_log: true tags: [forgejo] + # Forgejo Actions secrets (synced to Forgejo via API) + - name: Fetch Forgejo API token + ansible.builtin.command: + cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields api-token --reveal + delegate_to: localhost + register: _forgejo_api_token + changed_when: false + no_log: true + check_mode: false + tags: [forgejo_actions_secrets] + + - name: Fetch ArgoCD auth token for Forgejo Actions + ansible.builtin.command: + cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields argocd_token --reveal + delegate_to: localhost + register: _forgejo_argocd_token + changed_when: false + no_log: true + check_mode: false + tags: [forgejo_actions_secrets] + + - name: Set Forgejo Actions secrets facts + ansible.builtin.set_fact: + forgejo_api_token: "{{ _forgejo_api_token.stdout }}" + forgejo_secret_argocd_token: "{{ _forgejo_argocd_token.stdout }}" + no_log: true + tags: [forgejo_actions_secrets] + # Caddy Gandi token for ACME DNS-01 challenges - name: Fetch Gandi PAT for Caddy ansible.builtin.command: @@ -104,6 +132,8 @@ tags: borgmatic_metrics - role: forgejo tags: forgejo + - role: forgejo_actions_secrets + tags: forgejo_actions_secrets - role: zot tags: zot - role: zot_metrics diff --git a/ansible/roles/forgejo_actions_secrets/defaults/main.yml b/ansible/roles/forgejo_actions_secrets/defaults/main.yml new file mode 100644 index 0000000..dccee3f --- /dev/null +++ b/ansible/roles/forgejo_actions_secrets/defaults/main.yml @@ -0,0 +1,15 @@ +--- +# Forgejo Actions Secrets role configuration +# +# This role syncs repository-level Actions secrets from 1Password to Forgejo +# via the Forgejo API. + +forgejo_actions_secrets_api_url: "https://forge.ops.eblu.me/api/v1" +forgejo_actions_secrets_owner: eblume +forgejo_actions_secrets_repo: blumeops + +# Secrets to sync: list of {name: "SECRET_NAME", value_var: "ansible_fact_name"} +# The value_var references an Ansible fact set in playbook pre_tasks +forgejo_actions_secrets_list: + - name: ARGOCD_AUTH_TOKEN + value_var: forgejo_secret_argocd_token diff --git a/ansible/roles/forgejo_actions_secrets/tasks/main.yml b/ansible/roles/forgejo_actions_secrets/tasks/main.yml new file mode 100644 index 0000000..95b2e13 --- /dev/null +++ b/ansible/roles/forgejo_actions_secrets/tasks/main.yml @@ -0,0 +1,32 @@ +--- +# Forgejo Actions Secrets role +# +# Syncs repository-level Actions secrets from 1Password to Forgejo via API. +# +# NOTE: This role runs on indri, which is also where Forgejo runs. The API +# calls go from indri back to itself (via the public URL through Caddy). +# This is intentional - it keeps the role simple and uses the same URL +# that workflows use. +# +# Secrets (forgejo_api_token, forgejo_secret_*) are fetched from 1Password +# in the playbook pre_tasks to minimize password prompts during provisioning. + +- name: Sync Actions secrets to Forgejo + ansible.builtin.uri: + url: "{{ forgejo_actions_secrets_api_url }}/repos/{{ forgejo_actions_secrets_owner }}/{{ forgejo_actions_secrets_repo }}/actions/secrets/{{ item.name }}" + method: PUT + headers: + Authorization: "token {{ forgejo_api_token }}" + Content-Type: "application/json" + body_format: json + body: + data: "{{ lookup('vars', item.value_var) }}" + status_code: [201, 204] + register: forgejo_actions_secrets_result + # API returns 201 for create, 204 for update. We can't check if value changed + # (secrets are write-only), so only report changed when creating new secrets. + changed_when: forgejo_actions_secrets_result.status == 201 + loop: "{{ forgejo_actions_secrets_list }}" + loop_control: + label: "{{ item.name }}" + no_log: true diff --git a/docs/changelog.d/feature-forgejo-actions-secrets-iac.infra.md b/docs/changelog.d/feature-forgejo-actions-secrets-iac.infra.md new file mode 100644 index 0000000..e092b2e --- /dev/null +++ b/docs/changelog.d/feature-forgejo-actions-secrets-iac.infra.md @@ -0,0 +1 @@ +Add IaC for Forgejo Actions secrets via new `forgejo_actions_secrets` Ansible role, syncing repository secrets from 1Password to Forgejo API diff --git a/docs/reference/services/forgejo.md b/docs/reference/services/forgejo.md index 16bb5f8..311aebc 100644 --- a/docs/reference/services/forgejo.md +++ b/docs/reference/services/forgejo.md @@ -47,16 +47,29 @@ Server configuration secrets managed via 1Password → Ansible: ## Forgejo Actions Secrets -Repository-level secrets for CI/CD workflows. **Not IaC** - managed in Forgejo UI at: -`Settings → Actions → Secrets` +Repository-level secrets for CI/CD workflows, synced from 1Password via Ansible. -| Secret | Used By | Purpose | -|--------|---------|---------| -| `ARGOCD_AUTH_TOKEN` | `build-blumeops.yaml` | Sync docs app after release | +| Secret | 1Password Field | Used By | Purpose | +|--------|-----------------|---------|---------| +| `ARGOCD_AUTH_TOKEN` | `argocd_token` | `build-blumeops.yaml` | Sync docs app after release | These secrets are injected as `${{ secrets.SECRET_NAME }}` in workflow files. -> **Note:** These secrets are also stored in 1Password ("Forgejo Secrets" item) as the source of truth, but were manually copied to Forgejo. They will not auto-update if the 1Password value changes. +**IaC:** The `forgejo_actions_secrets` Ansible role syncs these secrets from 1Password to Forgejo via the Forgejo API. Run with: + +```bash +mise run provision-indri -- --tags forgejo_actions_secrets +``` + +### API Token Setup (Manual, One-Time) + +The Ansible role authenticates to the Forgejo API using a Personal Access Token (PAT). This PAT must be created manually: + +1. Go to https://forge.ops.eblu.me/user/settings/applications +2. Create a new token with `write:repository` scope +3. Store it in 1Password → "Forgejo Secrets" item → `api-token` field + +This is a bootstrapping requirement - the PAT enables IaC for all other secrets. ## Related