fix: Replace Codeberg dependency with local Forgejo instance (#611)

- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
  creates admin + bot users (dev-bot, review-bot), generates API
  tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
  FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
  FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
  FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
  →forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
  FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-23 16:57:12 +00:00
parent 39d30faf45
commit a66bd91721
58 changed files with 863 additions and 628 deletions

View file

@ -2,7 +2,7 @@
# =============================================================================
# collect-metrics.sh — Collect factory metrics and write JSON for the dashboard
#
# Queries Codeberg API for PR/issue stats across all managed projects,
# Queries forge API for PR/issue stats across all managed projects,
# counts vault decisions, and checks CI pass rates. Writes a JSON snapshot
# to the live site directory so the dashboard can fetch it.
#
@ -47,12 +47,15 @@ collect_project_metrics() {
repo=$(grep '^repo ' "$project_toml" | head -1 | sed 's/.*= *"//;s/"//')
repo_name=$(grep '^name ' "$project_toml" | head -1 | sed 's/.*= *"//;s/"//')
local api_base="https://codeberg.org/api/v1/repos/${repo}"
local forge_url
forge_url=$(grep '^forge_url ' "$project_toml" | head -1 | sed 's/.*= *"//;s/"//') 2>/dev/null || true
forge_url="${forge_url:-${FORGE_URL:-http://localhost:3000}}"
local api_base="${forge_url}/api/v1/repos/${repo}"
# PRs merged (all time via state=closed + merged marker)
local prs_merged_week=0 prs_merged_month=0 prs_merged_total=0
local closed_prs
closed_prs=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
closed_prs=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${api_base}/pulls?state=closed&sort=updated&limit=50" 2>/dev/null || echo "[]")
prs_merged_total=$(printf '%s' "$closed_prs" | jq '[.[] | select(.merged)] | length' 2>/dev/null || echo 0)
@ -69,7 +72,7 @@ collect_project_metrics() {
# Issues closed
local issues_closed_week=0 issues_closed_month=0
local closed_issues
closed_issues=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
closed_issues=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${api_base}/issues?state=closed&sort=updated&type=issues&limit=50" 2>/dev/null || echo "[]")
if [ -n "$WEEK_AGO" ]; then
@ -82,19 +85,19 @@ collect_project_metrics() {
fi
local total_closed_header
total_closed_header=$(curl -sf -I -H "Authorization: token ${CODEBERG_TOKEN}" \
total_closed_header=$(curl -sf -I -H "Authorization: token ${FORGE_TOKEN}" \
"${api_base}/issues?state=closed&type=issues&limit=1" 2>/dev/null | grep -i 'x-total-count' | tr -d '\r' | awk '{print $2}' || echo "0")
local issues_closed_total="${total_closed_header:-0}"
# Open issues by label
local backlog_count in_progress_count blocked_count
backlog_count=$(curl -sf -I -H "Authorization: token ${CODEBERG_TOKEN}" \
backlog_count=$(curl -sf -I -H "Authorization: token ${FORGE_TOKEN}" \
"${api_base}/issues?state=open&labels=backlog&type=issues&limit=1" 2>/dev/null | \
grep -i 'x-total-count' | tr -d '\r' | awk '{print $2}' || echo "0")
in_progress_count=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
in_progress_count=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${api_base}/issues?state=open&labels=in-progress&type=issues&limit=50" 2>/dev/null | \
jq 'length' 2>/dev/null || echo 0)
blocked_count=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
blocked_count=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${api_base}/issues?state=open&labels=blocked&type=issues&limit=50" 2>/dev/null | \
jq 'length' 2>/dev/null || echo 0)

View file

@ -355,7 +355,7 @@
<div class="footer">
<div>Data refreshed every 6 hours by the metrics collector.</div>
<div style="margin-top:0.5rem">
<a href="https://codeberg.org/johba/disinto">source</a>
<a href="http://localhost:3000/johba/disinto">source</a>
</div>
</div>
@ -421,7 +421,7 @@
var card = el('div', 'project');
var nameDiv = el('div', 'name');
var nameLink = document.createElement('a');
nameLink.href = 'https://codeberg.org/' + p.repo;
nameLink.href = 'http://localhost:3000/' + p.repo;
nameLink.textContent = p.name;
nameDiv.appendChild(nameLink);
card.appendChild(nameDiv);

View file

@ -365,7 +365,7 @@
<!-- Eight Agents -->
<div class="section">
<h2>Eight agents</h2>
<p>Each agent has a single responsibility. They communicate through git, the Codeberg API, and the filesystem.</p>
<p>Each agent has a single responsibility. They communicate through git, the forge API, and the filesystem.</p>
<div class="agents-grid">
<div class="agent-card">
<div class="name">dev-agent</div>
@ -480,7 +480,7 @@
<p><strong>Bash scripts</strong> &mdash; every agent is a shell script. No compiled binaries, no runtimes to install.</p>
<p><strong>Claude CLI</strong> &mdash; AI is invoked via <code>claude -p</code> (one-shot) or <code>claude</code> (persistent tmux sessions).</p>
<p><strong>Cron</strong> &mdash; agents are triggered by cron jobs, not a daemon. Pull-based, not push-based.</p>
<p><strong>Codeberg + Woodpecker</strong> &mdash; git hosting and CI. All state lives in git and the issue tracker. No external databases.</p>
<p><strong>Forgejo + Woodpecker</strong> &mdash; git hosting and CI. All state lives in git and the issue tracker. No external databases.</p>
<p><strong>Single VPS</strong> &mdash; runs on an 8 GB server. Flat cost, no scaling surprises.</p>
</div>
</div>
@ -538,7 +538,7 @@ disinto/
<div class="footer">
<a href="/">&larr; disinto.ai</a> &middot;
<a href="/docs/quickstart">Quickstart</a> &middot;
<a href="https://codeberg.org/johba/disinto">Source</a>
<a href="http://localhost:3000/johba/disinto">Source</a>
</div>
</div>

View file

@ -328,8 +328,8 @@
<div class="label">Prerequisites</div>
<ul>
<li><strong>A VPS or server</strong> &mdash; 8 GB RAM minimum (Ubuntu/Debian recommended)</li>
<li><strong>A Codeberg account</strong> &mdash; with a repo and at least one issue</li>
<li><strong>A second Codeberg account</strong> &mdash; for the review bot (branch protection requires a different reviewer)</li>
<li><strong>A forge instance</strong> &mdash; with a repo and at least one issue</li>
<li><strong>A second forge instance</strong> &mdash; for the review bot (branch protection requires a different reviewer)</li>
<li><strong>Woodpecker CI</strong> &mdash; running and connected to your repo</li>
<li><strong>An Anthropic API key</strong> &mdash; with the <code>claude</code> CLI installed and authenticated</li>
<li><strong>tmux</strong> &mdash; for persistent dev sessions</li>
@ -343,13 +343,13 @@
Clone the factory
</div>
<p>Clone disinto onto your server. This is the factory &mdash; the code that runs your agents.</p>
<pre><code>git clone https://codeberg.org/johba/disinto.git ~/disinto
<pre><code>git clone http://localhost:3000/johba/disinto.git ~/disinto
cd ~/disinto
cp .env.example .env</code></pre>
<p>Edit <code>.env</code> with your tokens:</p>
<pre><code><span class="comment"># Required</span>
CODEBERG_TOKEN=your_codeberg_token
REVIEW_BOT_TOKEN=your_review_bot_token
FORGE_TOKEN=your_codeberg_token
FORGE_REVIEW_TOKEN=your_review_bot_token
<span class="comment"># Woodpecker CI</span>
WOODPECKER_TOKEN=your_woodpecker_token
@ -365,14 +365,14 @@ CLAUDE_TIMEOUT=7200</code></pre>
<span class="step-num">2</span>
Initialize your project
</div>
<p><code>disinto init</code> sets up everything: clones the repo, creates the project config, adds Codeberg labels, and installs cron jobs.</p>
<pre><code>bin/disinto init https://codeberg.org/you/your-project</code></pre>
<p><code>disinto init</code> provisions a local Forgejo instance, clones the repo, creates the project config, adds labels, and installs cron jobs.</p>
<pre><code>bin/disinto init http://localhost:3000/you/your-project</code></pre>
<div class="expected">
<div class="label">Expected output</div>
<code>=== disinto init ===
Project: you/your-project
Name: your-project
Cloning: https://codeberg.org/you/your-project.git -> /home/you/your-project
Cloning: http://localhost:3000/you/your-project.git -> /home/you/your-project
Branch: main
Created: /home/you/disinto/projects/your-project.toml
Creating labels on you/your-project...
@ -406,7 +406,7 @@ Done. Project your-project is ready.</code>
<ol>
<li><strong>A CI pipeline</strong> &mdash; at least one <code>.woodpecker/*.yml</code> file. Agents wait for CI before reviewing or merging.</li>
<li><strong>A CLAUDE.md</strong> &mdash; project context that agents read before every task. Describe your tech stack, how to build/test, coding conventions, and directory layout.</li>
<li><strong>Branch protection</strong> &mdash; on Codeberg, require PR reviews and add the review bot as a write collaborator.</li>
<li><strong>Branch protection</strong> &mdash; on Forgejo, require PR reviews and add the review bot as a write collaborator.</li>
</ol>
<pre><code><span class="comment"># Create CLAUDE.md in your project</span>
cat > ~/your-project/CLAUDE.md &lt;&lt;'EOF'
@ -434,7 +434,7 @@ git push</code></pre>
<span class="step-num">4</span>
File your first issue
</div>
<p>Create an issue on Codeberg with the <code>backlog</code> label. Be specific &mdash; the dev-agent works best with clear acceptance criteria.</p>
<p>Create an issue on the forge with the <code>backlog</code> label. Be specific &mdash; the dev-agent works best with clear acceptance criteria.</p>
<pre><code><span class="comment"># Title: Add health check endpoint</span>
<span class="comment"># Label: backlog</span>
<span class="comment"># Body:</span>
@ -523,7 +523,7 @@ git log --oneline -5</code></pre>
<div class="footer">
<a href="/">&larr; disinto.ai</a> &middot;
<a href="/docs/architecture">Architecture</a> &middot;
<a href="https://codeberg.org/johba/disinto">Source</a>
<a href="http://localhost:3000/johba/disinto">Source</a>
</div>
</div>

View file

@ -661,7 +661,7 @@
<p>
<strong>Bash scripts and Claude.</strong> No Kubernetes, no microservices,
no SaaS dependencies. Runs on an 8GB VPS.
Point it at a <strong>Codeberg repo</strong> with a
Point it at a <strong>forge repo</strong> with a
<strong>Woodpecker CI</strong> pipeline and it starts building.
</p>
<p>
@ -691,8 +691,8 @@
<div class="cta-links">
<a href="/docs/quickstart">Quickstart</a>
<a href="/docs/architecture">Architecture</a>
<a href="https://codeberg.org/johba/disinto">Browse the source</a>
<a href="https://codeberg.org/johba/disinto/issues">See active issues</a>
<a href="http://localhost:3000/johba/disinto">Browse the source</a>
<a href="http://localhost:3000/johba/disinto/issues">See active issues</a>
<a href="/dashboard">Live dashboard</a>
</div>
</div>
@ -702,7 +702,7 @@
<div class="links">
<a href="/docs/quickstart">quickstart</a>
<a href="/docs/architecture">architecture</a>
<a href="https://codeberg.org/johba/disinto">source</a>
<a href="http://localhost:3000/johba/disinto">source</a>
<a href="/dashboard">dashboard</a>
</div>
<div class="under-hood">