Add IaC for Forgejo Actions secrets via Ansible (#107)

## 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
This commit is contained in:
Erich Blume 2026-02-04 09:11:01 -08:00
commit 74bd5abe54
5 changed files with 97 additions and 6 deletions

View file

@ -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

View file

@ -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

View file

@ -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