layered-soul/skills/forgejo-operations/SKILL.md

238 lines
10 KiB
Markdown
Raw Normal View History

---
name: forgejo-operations
description: "Manage self-hosted Forgejo/Gitea instances — repos, users, SSH keys, tokens, collaborators, API operations."
version: 1.0.0
author: Sam & Hermes
platforms: [linux]
metadata:
hermes:
tags: [forgejo, gitea, git, self-hosted, devops]
---
# Forgejo Operations
Manage a self-hosted Forgejo instance via API and SSH. Covers repo creation,
user management, SSH key registration, collaborator permissions, and token
lifecycle.
## Instance Details
- URL: `https://code.smilepowered.org`
- SSH: port 2222, user `git`
- SSH config entry: `Host code.smilepowered.org` with `Port 2222`
- API base: `https://code.smilepowered.org/api/v1`
## PR Creation + Merge (API token)
When branch protection blocks direct push, use the API token to create
and merge PRs programmatically. Token needs `write:repository` scope
(minimum). `write:organization` only needed for adjusting branch
protection rules.
**Rule:** No auto-merge until cross-platform validation is posted.
For Colibri: Linux `cargo test --workspace` AND FreeBSD/OSA
`cargo test --workspace` must both pass before merging.
```bash
source ~/.hermes/.env 2>/dev/null
API="https://code.smilepowered.org/api/v1/repos/<owner>/<repo>"
# Create PR
PR=$(curl -s -X POST -H "Authorization: token $FORGEJO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"feat: ...","head":"branch-name","base":"main","body":"..."}' \
"$API/pulls")
NUM=$(echo "$PR" | python3 -c "import sys,json; print(json.load(sys.stdin)['number'])")
# Merge (returns HTTP 200 with empty body on success)
curl -s -X POST -H "Authorization: token $FORGEJO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"Do":"merge","delete_branch_after_merge":true}' "$API/pulls/$NUM/merge"
# Verify (merge response is empty on success — use GET to confirm)
curl -s -H "Authorization: token $FORGEJO_API_TOKEN" "$API/pulls/$NUM" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(f'merged: {d[\"merged\"]}')"
```
**Merge parameter casing:** Forgejo requires `"Do"` (capital D), not `"do"`.
Lowercase returns HTTP 422 `[Do]: Required`.
## Repo Creation
```bash
# Under user
curl -X POST "$API/user/repos" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"repo-name","description":"...","private":false}'
# Under org (needs write:organization scope)
curl -X POST "$API/orgs/<org>/repos" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"repo-name","description":"...","private":false}'
```
## User Management (admin scope required)
```bash
# Create user
curl -X POST "$API/admin/users" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"agent-name","email":"agent@example.com","password":"random","must_change_password":false,"send_notify":false}'
# Add SSH key to another user's account
curl -X POST "$API/admin/users/<username>/keys" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"ssh-ed25519 AAAA...","title":"key-label","read_only":false}'
# List SSH keys on your own account
curl "$API/user/keys" -H "Authorization: token $TOKEN"
# Delete SSH key from your own account
curl -X DELETE "$API/user/keys/<id>" -H "Authorization: token $TOKEN"
```
## Collaborator Management
```bash
# Add/update collaborator with write permission
curl -X PUT "$API/repos/<owner>/<repo>/collaborators/<username>" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"permission":"write"}'
# Returns 204 on success (no body)
# Needs token with write:repository on that repo
```
## Branch Protection & Force Push
Clawdie repos have branch protection on `main`: direct push rejected with
`pre-receive hook declined`. All changes go through pull requests.
### Stacked branch pitfall
Always branch from `main`. If a branch is created from another feature branch,
its PR includes all parent commits. Fix with cherry-pick onto a clean branch:
```bash
git checkout -b clean-branch forgejo/main
git cherry-pick <commit1> <commit2>
git diff --check forgejo/main...HEAD # verify clean
```
## Pitfalls
### Push-to-create is usually off
Forgejo rejects `shallow update not allowed`. Fixes:
- `git fetch --unshallow origin` (needs full history from origin)
- If origin unreachable: `git filter-branch` to make first commit rootless,
then push. This rewrites history — only for migration cutover.
- Best: fresh full clone from Forgejo itself.
### Commit hooks timing out
Some repos have pre-commit/prepare-commit-msg hooks that need Node or other
tools not on PATH. Use `-c core.hooksPath=/dev/null` to bypass all hooks:
```bash
git -c core.hooksPath=/dev/null commit -m "message"
```
### Not an org — check first
`/api/v1/users/<name>` returns a user, `/api/v1/orgs/<name>` returns an org.
If you get "user redirect does not exist" on org creation, the name is already
a user. User and org can't share names in Forgejo.
### SSH key on wrong account
To verify which user a key belongs to: `ssh -T git@host`. The greeting
("Hi there, <username>!") tells you. If wrong, delete from current account,
re-add to correct one via admin API.
## PR State Inspection
```bash
source ~/.hermes/.env 2>/dev/null
API="https://code.smilepowered.org/api/v1/repos/<owner>/<repo>"
# List open PRs
curl -s -H "Authorization: token $FORGEJO_API_TOKEN" "$API/pulls?state=open"
# Check specific PR state
curl -s -H "Authorization: token $FORGEJO_API_TOKEN" "$API/pulls/$PR_NUMBER" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(f'#{d[\"number\"]}: state={d[\"state\"]}, merged={d[\"merged\"]}, mergeable={d.get(\"mergeable\",\"?\")}')"
```
**Pitfall: `read` pipe loses variable in subshell.** Piping into `read` (e.g.
`echo "$PR" | python3 -c "..." | read NUM`) silently fails — the variable
is set in a subshell and lost when the pipe exits. Use command substitution
instead: `NUM=$(echo "$PR" | python3 -c "...")`.
### Merge rate-limit ("Please try again later")
Forgejo may return HTTP 200 with `{\"message\":\"Please try again later\"}`
on the merge endpoint. Wait 3-5 seconds and retry. If it persists after
3 retries, fall back to web UI merge. This appears to be backend-side
processing delay, not a token scope issue.
**Pitfall: HTTP 405 on merge = PR not mergeable OR already merged.**
HTTP 405 from the merge endpoint means either (a) the PR has merge
conflicts Forgejo can't auto-resolve, or (b) the PR was already merged
(merge on an already-merged PR returns 405, not 409). Always check PR
state before retrying — the merge may have succeeded silently on a prior
call (Forgejo sometimes returns empty 200/204 on success, which trips up
JSON parsers). Verify with:
```bash
curl -s -H "Authorization: token $TOKEN" "$API/pulls/$NUM" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(f'mergeable: {d[\"mergeable\"]}')"
```
If `mergeable: false`, conflicts exist. Resolution options:
- **Web UI**: visit the PR page and use Forgejo's conflict resolver
- **Local resolve**: `git merge`, fix conflicts, push resolved branch to a new ref, open fresh PR
- **Superseding branch**: if another branch already contains the same fixes plus more, close this PR and point to the newer branch
Do NOT retry the merge API — 405 on an unmergeable PR will never self-resolve.
This is different from the transient \"Please try again later\" rate-limit response
which returns HTTP 200 with an error message body.
**Pitfall: token display when used in-shell.** When using the token in `curl`
commands via a shell script, load it from env rather than pasting inline.
`source ~/.hermes/.env` works if `.env` is mode 0600. If the token value appears
in the command string (even with `export TOKEN=...`), the agent may redact it
mid-command, resulting in truncated tokens. Use `source ~/.hermes/.env` before
the curl call, or store it in `$FORGEJO_API_TOKEN` and never print it.
## Subtopics
This skill covers Forgejo operations end-to-end. For deep dives into specific workflows, see:
| Topic | Reference | Description |
| -------------------------- | ------------------------------------------- | ----------------------------------------------------------------- |
| Agent Onboarding | `references/agent-onboarding.md` | Create users, register SSH keys, add collaborators, verify access |
| Multi-Agent Infrastructure | `references/multi-agent-infrastructure.md` | Architecture, setup order, SSH config, Vaultwarden integration |
| Branch Protection | `references/branch-protection.md` | Web UI steps for requiring PR on main, verification |
| Branch Protection API | `references/branch-protection-api.md` | Programmatic remove/re-create protection (for force push) |
| SSH Key Transfer | `references/key-transfer.md` | Move an SSH key between Forgejo user accounts |
| Self-Hosted Setup | `references/self-hosted-setup.md` | Codeberg migration, shallow clone fixes, password rotation |
| API Token Scopes | `references/forgejo-token-scopes.md` | Complete scope reference table |
| Git Shallow Fixes | `references/git-shallow-fixes.md` | Unshallowing clones, filter-branch root commits |
| Vaultwarden bw CLI | `references/vaultwarden-bw-cli.md` | bw CLI login/unlock/CRUD for agent secrets |
| API Merge Pattern | `references/api-merge-pattern.md` | Programmatic PR create + merge via curl |
| API Token Setup | `references/api-token-setup.md` | Token creation workflow and storage |
| Colibri Validation | `references/colibri-validation-commands.md` | Cross-platform validation commands |
| Git History Flatten | `references/git-history-flatten.md` | Flatten merge-heavy history via first-parent cherry-pick |
### Template
- `templates/FORGEJO-SETUP.md` — Agent self-bootstrap documentation template