Enable Forgejo Actions (Phase 1) (#48)
All checks were successful
Test CI / test (push) Successful in 0s
All checks were successful
Test CI / test (push) Successful in 0s
## Summary - Refactor Forgejo app.ini to be managed by ansible with secrets from 1Password - Enable Forgejo Actions in config (`[actions] ENABLED = true`) - Add `repo.actions` to DEFAULT_REPO_UNITS - Clean up unused MySQL database fields (we use SQLite) ## Phase 1 Progress This PR covers the first part of Phase 1 (ci-cd-bootstrap plan): - [x] Refactor app.ini to ansible template - [x] Store secrets in 1Password - [x] Enable Actions in config - [ ] Deploy config changes (pending review) - [ ] Create runner registration token - [ ] Deploy runner to k8s - [ ] Test with simple workflow ## Deployment and Testing - [ ] Run `mise run provision-indri -- --tags forgejo` to deploy - [ ] Verify Forgejo restarts correctly - [ ] Verify Actions tab appears in repo settings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.tail8d86e.ts.net/eblume/blumeops/pulls/48
This commit is contained in:
parent
016f1043c8
commit
7893c41020
15 changed files with 426 additions and 15 deletions
29
.forgejo/workflows/test.yaml
Normal file
29
.forgejo/workflows/test.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: Test CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout (git clone)
|
||||
run: |
|
||||
# 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"
|
||||
|
||||
- name: Hello World
|
||||
run: |
|
||||
echo "Hello from Forgejo Actions!"
|
||||
echo "Runner: $(hostname)"
|
||||
echo "Repository: ${{ gitea.repository }}"
|
||||
echo "Event: ${{ gitea.event_name }}"
|
||||
echo "Ref: ${{ gitea.ref }}"
|
||||
ls -la
|
||||
|
|
@ -102,6 +102,11 @@ kubectl --context=minikube-indri logs -n <namespace> <pod> # 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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
51
ansible/roles/forgejo/defaults/main.yml
Normal file
51
ansible/roles/forgejo/defaults/main.yml
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
82
ansible/roles/forgejo/templates/app.ini.j2
Normal file
82
ansible/roles/forgejo/templates/app.ini.j2
Normal file
|
|
@ -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 }}
|
||||
23
argocd/apps/forgejo-runner.yaml
Normal file
23
argocd/apps/forgejo-runner.yaml
Normal file
|
|
@ -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
|
||||
13
argocd/manifests/forgejo-runner/configmap.yaml
Normal file
13
argocd/manifests/forgejo-runner/configmap.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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
|
||||
63
argocd/manifests/forgejo-runner/deployment.yaml
Normal file
63
argocd/manifests/forgejo-runner/deployment.yaml
Normal file
|
|
@ -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:
|
||||
# Use internal k8s service via Tailscale operator egress
|
||||
- name: FORGEJO_INSTANCE_URL
|
||||
value: "http://forge.tailscale.svc.cluster.local:3001"
|
||||
- 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:host,ubuntu-22.04:host" \
|
||||
--no-interactive
|
||||
fi
|
||||
# Start the runner daemon with config
|
||||
forgejo-runner daemon --config /config/config.yaml
|
||||
volumeMounts:
|
||||
- name: runner-data
|
||||
mountPath: /data
|
||||
- name: runner-config
|
||||
mountPath: /config
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
volumes:
|
||||
- name: runner-data
|
||||
emptyDir: {}
|
||||
- name: runner-config
|
||||
configMap:
|
||||
name: forgejo-runner-config
|
||||
8
argocd/manifests/forgejo-runner/kustomization.yaml
Normal file
8
argocd/manifests/forgejo-runner/kustomization.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: forgejo-runner
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- configmap.yaml
|
||||
- deployment.yaml
|
||||
4
argocd/manifests/forgejo-runner/namespace.yaml
Normal file
4
argocd/manifests/forgejo-runner/namespace.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: forgejo-runner
|
||||
10
argocd/manifests/forgejo-runner/secret-token.yaml.tpl
Normal file
10
argocd/manifests/forgejo-runner/secret-token.yaml.tpl
Normal file
|
|
@ -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"
|
||||
5
argocd/manifests/forgejo-runner/serviceaccount.yaml
Normal file
5
argocd/manifests/forgejo-runner/serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: forgejo-runner
|
||||
namespace: forgejo-runner
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue