layered-soul/skills/forgejo-operations/references/self-hosted-setup.md
Sam & Claude 4d8ce07fa7 docs: apply Prettier to current markdown (Sam & Codex)
Normalize markdown formatting after the latest main updates.\n\nChecks: python3 scripts/layered_soul.py validate .; npx --yes prettier@3 --check '**/*.md'; git diff --check.
2026-06-14 01:48:32 +02:00

4.8 KiB

Self-Hosted Setup & Codeberg Migration

Workflow for setting up a self-hosted Forgejo instance and migrating repos from Codeberg.

SSH Setup

Forgejo often runs SSH on a non-standard port (e.g. 2222). Always probe:

ssh -T git@<host>             # port 22 — may return password prompt
ssh -T -p 2222 git@<host>     # try 2222

Once confirmed, add to ~/.ssh/config:

Host code.<domain>
    HostName code.<domain>
    User git
    Port 2222
    IdentityFile ~/.ssh/<key>
    IdentitiesOnly yes

Org vs User Namespace

If the admin account name matches the desired org name (e.g. user clawdie cannot have org clawdie), repos go under the user: code.<domain>/clawdie/repo. Same URL structure, same SSH path. Multi-agent audit trail comes from machine users, not org membership.

Push-to-Create

Usually disabled by default. Check:

# If push fails with "Push to create is not enabled"
curl -s "https://code.<domain>/api/v1/settings/ui" | jq .

Create repos via API or web UI instead.

Shallow Clone → Push (Codeberg Migration)

Forgejo rejects shallow pushes unless ALLOW_SHALLOW_UPDATES is enabled (off by default). For repos cloned shallow from Codeberg:

Small repos (few commits, full objects present)

# Check if all objects are present
git fsck --connectivity-only

# Remove shallow marker and rewrite root commit to be parentless
echo "<root-commit> " > .git/info/grafts
git filter-branch -f -- --all
rm .git/info/grafts .git/shallow

Large repos (many objects missing)

# Fetch full history via HTTPS (more reliable than SSH for large fetches)
git remote set-url origin https://codeberg.org/<owner>/<repo>.git
git fetch --unshallow origin
git remote set-url origin git@codeberg.org:<owner>/<repo>.git  # restore

Machine-User Permissions

Pattern for multi-agent git access:

User Host Permissions
<agent>-<host> hostname write:repository token + SSH key
  • Never copy private SSH keys between hosts.
  • Each machine user gets their own SSH key registered on Forgejo.
  • Passwords: only needed if agent uses browser to access web UI (e.g. PR review). Others: random, no force-change.
  • Encode the permissions table in the repo's AGENTS.md for agent self-discovery.

Browser-Based Agent Access

Some agents (e.g. Hermes on debby) need web UI access for PR review, issue triage, repo settings, and CI status checks. For these, set a real password (untick "require password change") and store it in Vaultwarden. Other agents (Claude, Codex) only need SSH keys — random passwords are fine.

Password Rotation via Browser

When admin-created user has "require password change" ticked, the agent can handle first-login rotation:

# Generate strong password
openssl rand -base64 24

Then use browser tools to:

  1. Navigate to https://code.<domain>/user/login
  2. Type username + temp password
  3. Forgejo redirects to "Update password" page
  4. Type new password in both fields, submit
  5. Verify landing on dashboard

Special characters (šđžčć, etc.) work fine in Forgejo passwords.

Git Hooks Bypass When Node Not on PATH

When node isn't on PATH (e.g. in a tool-call shell that doesn't source .bashrc), git hooks like pre-commit and prepare-commit-msg hang with node: not found. Workaround:

GIT_AUTHOR_NAME="Name" GIT_AUTHOR_EMAIL="email" \
GIT_COMMITTER_NAME="Name" GIT_COMMITTER_EMAIL="email" \
git -c core.hooksPath=/dev/null commit -m "message"

Use --no-verify only for pre-commit and commit-msg; core.hooksPath=/dev/null kills all hooks including prepare-commit-msg which otherwise still fires and hangs.

Pitfalls

  • Forgejo SSH on non-standard port: always probe port 2222 first. The default port 22 often hits system SSH, not Forgejo's, producing confusing password prompts.
  • Token is immutable: no edit after creation. Get scopes right on first generate. Wrong scope = new token.
  • Org name collision with username: Forgejo won't let you create an org named the same as an existing user. Use the user namespace instead.
  • Shallow clones from Codeberg: unshallow before pushing, or rewrite root commits. Forgejo blocks shallow updates by default.
  • git fetch --unshallow timeouts: Codeberg can be slow. Use HTTPS URL (not SSH) for large unshallow operations — often faster and more reliable.
  • git filter-branch grafts are deprecated: the echo "<hash> " > .git/info/grafts + git filter-branch -f -- --all pattern works but is deprecated. Prefer git replace --convert-graft-file by default.
  • Git hooks hang when node not on PATH: use -c core.hooksPath=/dev/null (not just --no-verify — that skips pre-commit but not prepare-commit-msg).