diff --git a/.woodpecker/agent-smoke.sh b/.woodpecker/agent-smoke.sh index c280006..4ba7444 100644 --- a/.woodpecker/agent-smoke.sh +++ b/.woodpecker/agent-smoke.sh @@ -82,7 +82,7 @@ while IFS= read -r -d '' f; do printf 'FAIL [syntax] %s\n' "$f" FAILED=1 fi -done < <(find dev gardener review planner supervisor lib vault action -name "*.sh" -print0 2>/dev/null) +done < <(find dev gardener review planner supervisor lib vault action exec -name "*.sh" -print0 2>/dev/null) echo "syntax check done" # ── 2. Function-resolution check ───────────────────────────────────────────── @@ -213,6 +213,9 @@ check_script action/action-agent.sh dev/phase-handler.sh check_script supervisor/supervisor-run.sh check_script supervisor/preflight.sh check_script predictor/predictor-run.sh +check_script exec/exec-session.sh +check_script exec/exec-inject.sh +check_script exec/exec-briefing.sh echo "function resolution check done" diff --git a/bin/disinto b/bin/disinto index 39a8c77..0578cbe 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: @@ -175,7 +176,7 @@ services: - disinto-net woodpecker: - image: woodpeckerci/woodpecker-server:latest + image: woodpeckerci/woodpecker-server:v3 restart: unless-stopped security_opt: - apparmor=unconfined @@ -198,7 +199,7 @@ services: - disinto-net woodpecker-agent: - image: woodpeckerci/woodpecker-agent:latest + image: woodpeckerci/woodpecker-agent:v3 restart: unless-stopped security_opt: - apparmor=unconfined @@ -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" @@ -602,19 +654,58 @@ 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}" + + # 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 - git -C "$repo_root" push forgejo --all 2>/dev/null || true - git -C "$repo_root" push forgejo --tags 2>/dev/null || true + # 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 + + # 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. @@ -774,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}" \ @@ -784,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 } @@ -1162,7 +1265,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 +1273,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 +1357,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 +1451,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"