From 0908ddb521373dde986304dc7b1a9cd762c6578e Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 14:55:00 +0000 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20fix:=20Woodpecker=20image=20uses=20:?= =?UTF-8?q?latest=20tag=20which=20no=20longer=20exists=20=E2=80=94=20conta?= =?UTF-8?q?iner=20crash-loops=20(#680)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/disinto b/bin/disinto index 17d6898..c594adc 100755 --- a/bin/disinto +++ b/bin/disinto @@ -175,7 +175,7 @@ services: - disinto-net woodpecker: - image: woodpeckerci/woodpecker-server:latest + image: woodpeckerci/woodpecker-server:v3 restart: unless-stopped security_opt: - apparmor=unconfined @@ -198,7 +198,7 @@ services: - disinto-net woodpecker-agent: - image: woodpeckerci/woodpecker-agent:latest + image: woodpeckerci/woodpecker-agent:v3 restart: unless-stopped security_opt: - apparmor=unconfined From 07616df8a59cbdf7c5dc902cd4efcbd97bd8a8d1 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 15:08:55 +0000 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20fix:=20Dendrite=20crash-loops=20?= =?UTF-8?q?=E2=80=94=20missing=20dendrite.yaml=20config=20file=20(#681)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove Dendrite from the default docker-compose.yml generated by `disinto init`. Most deployments don't need Matrix, so Dendrite is now opt-in via the `--matrix` flag. When `--matrix` is passed: - A minimal dendrite.yaml is generated at docker/dendrite/dendrite.yaml - The Dendrite service is appended to docker-compose.yml with the config file bind-mounted - setup_matrix() provisions the bot user and coordination room Without `--matrix`, no Dendrite container is started and fresh inits no longer crash-loop. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 97 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 19 deletions(-) diff --git a/bin/disinto b/bin/disinto index c594adc..83454c2 100755 --- a/bin/disinto +++ b/bin/disinto @@ -155,7 +155,8 @@ generate_compose() { cat > "$compose_file" <<'COMPOSEEOF' # docker-compose.yml — generated by disinto init -# Brings up Forgejo, Woodpecker, Dendrite (Matrix), and the agent runtime. +# Brings up Forgejo, Woodpecker, and the agent runtime. +# Dendrite (Matrix) is added only when init is called with --matrix. services: forgejo: @@ -213,16 +214,6 @@ services: networks: - disinto-net - dendrite: - image: matrixdotorg/dendrite-monolith:latest - restart: unless-stopped - volumes: - - dendrite-data:/etc/dendrite - environment: - DENDRITE_DOMAIN: disinto.local - networks: - - disinto-net - agents: build: ./docker/agents restart: unless-stopped @@ -238,21 +229,18 @@ services: environment: FORGE_URL: http://forgejo:3000 WOODPECKER_SERVER: http://woodpecker:8000 - MATRIX_HOMESERVER: http://dendrite:8008 DISINTO_CONTAINER: "1" env_file: - .env depends_on: - forgejo - woodpecker - - dendrite networks: - disinto-net volumes: forgejo-data: woodpecker-data: - dendrite-data: agent-data: project-repos: @@ -284,6 +272,70 @@ COMPOSEEOF echo "Created: ${compose_file}" } +# Append Dendrite (Matrix) service to docker-compose.yml and generate config. +# Called only when --matrix flag is passed to init. +append_dendrite_compose() { + local compose_file="${FACTORY_ROOT}/docker-compose.yml" + local dendrite_config_dir="${FACTORY_ROOT}/docker/dendrite" + local dendrite_yaml="${dendrite_config_dir}/dendrite.yaml" + + mkdir -p "$dendrite_config_dir" + + # Generate a minimal dendrite.yaml + cat > "$dendrite_yaml" <<'DENDRITECFG' +# dendrite.yaml — generated by disinto init --matrix +version: 2 +global: + server_name: disinto.local + private_key: matrix_key.pem + database: + connection_string: file:dendrite.db + cache: + max_size_estimated: 512mb + jetstream: + storage_path: /etc/dendrite/jetstream +client_api: + registration_disabled: true +DENDRITECFG + echo "Created: ${dendrite_yaml}" + + # Append dendrite service before the volumes: section + python3 -c " +import sys, pathlib +p = pathlib.Path(sys.argv[1]) +text = p.read_text() +dendrite_service = ''' + dendrite: + image: matrixdotorg/dendrite-monolith:latest + restart: unless-stopped + volumes: + - dendrite-data:/etc/dendrite + - ./docker/dendrite/dendrite.yaml:/etc/dendrite/dendrite.yaml:ro + environment: + DENDRITE_DOMAIN: disinto.local + networks: + - disinto-net +''' +# Insert dendrite service before 'volumes:' line +text = text.replace('\nvolumes:\n', dendrite_service + '\nvolumes:\n', 1) +# Add dendrite-data volume +text = text.replace(' agent-data:', ' dendrite-data:\n agent-data:') +# Add MATRIX_HOMESERVER env var to agents service +text = text.replace( + ' DISINTO_CONTAINER: \"1\"', + ' MATRIX_HOMESERVER: http://dendrite:8008\n DISINTO_CONTAINER: \"1\"' +) +# Add dendrite dependency to agents service +text = text.replace( + ' - woodpecker\n networks:', + ' - woodpecker\n - dendrite\n networks:' +) +p.write_text(text) +" "$compose_file" + + echo "Updated: ${compose_file} (added Dendrite service)" +} + # Generate docker/agents/ files if they don't already exist. generate_agent_docker() { local docker_dir="${FACTORY_ROOT}/docker/agents" @@ -1162,7 +1214,7 @@ disinto_init() { shift # Parse flags - local branch="" repo_root="" ci_id="0" auto_yes=false forge_url_flag="" bare=false + local branch="" repo_root="" ci_id="0" auto_yes=false forge_url_flag="" bare=false enable_matrix=false while [ $# -gt 0 ]; do case "$1" in --branch) branch="$2"; shift 2 ;; @@ -1170,6 +1222,7 @@ disinto_init() { --ci-id) ci_id="$2"; shift 2 ;; --forge-url) forge_url_flag="$2"; shift 2 ;; --bare) bare=true; shift ;; + --matrix) enable_matrix=true; shift ;; --yes) auto_yes=true; shift ;; *) echo "Unknown option: $1" >&2; exit 1 ;; esac @@ -1253,6 +1306,9 @@ p.write_text(text) forge_port=$(printf '%s' "$forge_url" | sed -E 's|.*:([0-9]+)/?$|\1|') forge_port="${forge_port:-3000}" generate_compose "$forge_port" + if [ "$enable_matrix" = true ]; then + append_dendrite_compose + fi generate_agent_docker fi @@ -1344,10 +1400,13 @@ p.write_text(text) echo "" echo "── Starting full stack ────────────────────────────────" docker compose -f "${FACTORY_ROOT}/docker-compose.yml" up -d - echo "Stack: running (forgejo + woodpecker + dendrite + agents)" - - # Provision Matrix now that Dendrite is running - setup_matrix + if [ "$enable_matrix" = true ]; then + echo "Stack: running (forgejo + woodpecker + dendrite + agents)" + # Provision Matrix now that Dendrite is running + setup_matrix + else + echo "Stack: running (forgejo + woodpecker + agents)" + fi # Activate repo in Woodpecker now that stack is running activate_woodpecker_repo "$forge_repo" From f8c8769af3dc7af9132984e9c6c2ed53a0babce1 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 15:20:53 +0000 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20fix:=20disinto=20init=20does=20not?= =?UTF-8?q?=20push=20repo=20to=20local=20Forgejo=20=E2=80=94=20repo=20stay?= =?UTF-8?q?s=20empty=20(#682)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 49 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/bin/disinto b/bin/disinto index 83454c2..0769d75 100755 --- a/bin/disinto +++ b/bin/disinto @@ -654,19 +654,52 @@ setup_forge() { # Push local clone to the Forgejo remote. push_to_forge() { local repo_root="$1" forge_url="$2" repo_slug="$3" - local remote_url="${forge_url}/${repo_slug}.git" + # Build authenticated remote URL: http://dev-bot:@host:port/org/repo.git + if [ -z "${FORGE_TOKEN:-}" ]; then + echo "Error: FORGE_TOKEN not set — cannot push to Forgejo" >&2 + return 1 + fi + local auth_url + auth_url=$(printf '%s' "$forge_url" | sed "s|://|://dev-bot:${FORGE_TOKEN}@|") + local remote_url="${auth_url}/${repo_slug}.git" + # Display URL without token + local display_url="${forge_url}/${repo_slug}.git" + + # Always set the remote URL to ensure credentials are current if git -C "$repo_root" remote get-url forgejo >/dev/null 2>&1; then - echo "Remote: forgejo (already configured)" + git -C "$repo_root" remote set-url forgejo "$remote_url" else - git -C "$repo_root" remote add forgejo "$remote_url" 2>/dev/null || \ - git -C "$repo_root" remote set-url forgejo "$remote_url" - echo "Remote: forgejo -> ${remote_url}" + git -C "$repo_root" remote add forgejo "$remote_url" + fi + echo "Remote: forgejo -> ${display_url}" + + # Push all branches and tags + echo "Pushing: branches to forgejo" + if ! git -C "$repo_root" push forgejo --all 2>&1; then + echo "Error: failed to push branches to Forgejo" >&2 + return 1 + fi + echo "Pushing: tags to forgejo" + if ! git -C "$repo_root" push forgejo --tags 2>&1; then + echo "Error: failed to push tags to Forgejo" >&2 + return 1 fi - # Push all branches - git -C "$repo_root" push forgejo --all 2>/dev/null || true - git -C "$repo_root" push forgejo --tags 2>/dev/null || true + # Verify the repo is no longer empty + local repo_info + repo_info=$(curl -sf --max-time 10 \ + -H "Authorization: token ${FORGE_TOKEN}" \ + "${forge_url}/api/v1/repos/${repo_slug}" 2>/dev/null) || repo_info="" + if [ -n "$repo_info" ]; then + local is_empty + is_empty=$(printf '%s' "$repo_info" | jq -r '.empty // "unknown"') + if [ "$is_empty" = "true" ]; then + echo "Warning: Forgejo repo still reports empty after push" >&2 + return 1 + fi + echo "Verify: repo is not empty (push confirmed)" + fi } # Preflight check — verify all factory requirements before proceeding. From 26df57da1882896afca87a85003b7c074d114a64 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 15:23:02 +0000 Subject: [PATCH 4/5] fix: skip push when local repo has no commits (empty clone) The smoke test clones from an empty Forgejo repo, so there are no refs to push. Skip the push and verification gracefully when HEAD does not resolve. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/disinto b/bin/disinto index 0769d75..e6e1f97 100755 --- a/bin/disinto +++ b/bin/disinto @@ -674,6 +674,12 @@ push_to_forge() { fi echo "Remote: forgejo -> ${display_url}" + # Skip push if local repo has no commits (e.g. cloned from empty Forgejo repo) + if ! git -C "$repo_root" rev-parse HEAD >/dev/null 2>&1; then + echo "Push: skipped (local repo has no commits)" + return 0 + fi + # Push all branches and tags echo "Pushing: branches to forgejo" if ! git -C "$repo_root" push forgejo --all 2>&1; then From 54ce91e09eab158fae17a36c909727d532ce6e48 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 15:35:31 +0000 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20fix:=20create=5Flabels=20creates=20d?= =?UTF-8?q?uplicate=20labels=20on=20re-run=20=E2=80=94=20no=20idempotency?= =?UTF-8?q?=20check=20(#683)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bin/disinto b/bin/disinto index e6e1f97..5a68547 100755 --- a/bin/disinto +++ b/bin/disinto @@ -865,8 +865,20 @@ create_labels() { ) echo "Creating labels on ${repo}..." + + # Fetch existing labels so we can skip duplicates + local existing + existing=$(curl -sf \ + -H "Authorization: token ${FORGE_TOKEN}" \ + "${api}/labels?limit=50" 2>/dev/null \ + | grep -o '"name":"[^"]*"' | cut -d'"' -f4) || existing="" + local name color for name in backlog in-progress blocked tech-debt underspecified vision action; do + if echo "$existing" | grep -qx "$name"; then + echo " . ${name} (already exists)" + continue + fi color="${labels[$name]}" if curl -sf -X POST \ -H "Authorization: token ${FORGE_TOKEN}" \ @@ -875,7 +887,7 @@ create_labels() { -d "{\"name\":\"${name}\",\"color\":\"${color}\"}" >/dev/null 2>&1; then echo " + ${name}" else - echo " . ${name} (already exists)" + echo " ! ${name} (failed to create)" fi done }