diff --git a/lib/generators.sh b/lib/generators.sh index be487f8..58c7dbf 100644 --- a/lib/generators.sh +++ b/lib/generators.sh @@ -26,6 +26,46 @@ PROJECT_NAME="${PROJECT_NAME:-project}" # PRIMARY_BRANCH defaults to main (env.sh may have set it to 'master') PRIMARY_BRANCH="${PRIMARY_BRANCH:-main}" +# Helper: extract woodpecker_repo_id from a project TOML file +# Returns empty string if not found or file doesn't exist +_get_woodpecker_repo_id() { + local toml_file="$1" + if [ -f "$toml_file" ]; then + python3 -c " +import sys, tomllib +try: + with open(sys.argv[1], 'rb') as f: + cfg = tomllib.load(f) + ci = cfg.get('ci', {}) + wp_id = ci.get('woodpecker_repo_id', '0') + print(wp_id) +except Exception: + print('0') +" "$toml_file" 2>/dev/null || echo "0" + else + echo "0" + fi +} + +# Find all project TOML files and extract the highest woodpecker_repo_id +# (used for the main agents service which doesn't have a per-project TOML) +_get_primary_woodpecker_repo_id() { + local projects_dir="${FACTORY_ROOT}/projects" + local max_id="0" + for toml in "${projects_dir}"/*.toml; do + [ -f "$toml" ] || continue + local repo_id + repo_id=$(_get_woodpecker_repo_id "$toml") + if [ -n "$repo_id" ] && [ "$repo_id" != "0" ]; then + # Use the first non-zero repo_id found (or highest if multiple) + if [ "$repo_id" -gt "$max_id" ] 2>/dev/null; then + max_id="$repo_id" + fi + fi + done + echo "$max_id" +} + # Parse project TOML for local-model agents and emit compose services. # Writes service definitions to stdout; caller handles insertion into compose file. _generate_local_model_services() { @@ -40,6 +80,10 @@ _generate_local_model_services() { for toml in "${projects_dir}"/*.toml; do [ -f "$toml" ] || continue + # Get woodpecker_repo_id for this project + local wp_repo_id + wp_repo_id=$(_get_woodpecker_repo_id "$toml") + # Parse [agents.*] sections using Python - output YAML-compatible format while IFS='=' read -r key value; do case "$key" in @@ -90,6 +134,7 @@ _generate_local_model_services() { DISINTO_CONTAINER: "1" PROJECT_REPO_ROOT: /home/agent/repos/${PROJECT_NAME:-project} WOODPECKER_DATA_DIR: /woodpecker-data + WOODPECKER_REPO_ID: "${wp_repo_id}" FORGE_BOT_USER_${service_name^^}: "${forge_user}" POLL_INTERVAL: "${poll_interval_val}" depends_on: @@ -186,6 +231,10 @@ _generate_compose_impl() { return 0 fi + # Extract primary woodpecker_repo_id from project TOML files + local wp_repo_id + wp_repo_id=$(_get_primary_woodpecker_repo_id) + cat > "$compose_file" <<'COMPOSEEOF' # docker-compose.yml — generated by disinto init # Brings up Forgejo, Woodpecker, and the agent runtime. @@ -292,6 +341,7 @@ services: DISINTO_CONTAINER: "1" PROJECT_REPO_ROOT: /home/agent/repos/${PROJECT_NAME:-project} WOODPECKER_DATA_DIR: /woodpecker-data + WOODPECKER_REPO_ID: "PLACEHOLDER_WP_REPO_ID" # IMPORTANT: agents get explicit environment variables (forge tokens, CI tokens, config). # Vault-only secrets (GITHUB_TOKEN, CLAWHUB_TOKEN, deploy keys) live in # .env.vault.enc and are NEVER injected here — only the runner @@ -394,6 +444,15 @@ COMPOSEEOF # (Docker Compose cannot resolve it; it's a shell variable, not a .env var) sed -i "s|\${PROJECT_NAME:-project}|${PROJECT_NAME}|g" "$compose_file" + # Patch WOODPECKER_REPO_ID — interpolate at generation time + # (Docker Compose cannot resolve it; it's a shell variable, not a .env var) + if [ -n "$wp_repo_id" ] && [ "$wp_repo_id" != "0" ]; then + sed -i "s|PLACEHOLDER_WP_REPO_ID|${wp_repo_id}|g" "$compose_file" + else + # Default to empty if no repo_id found (agents will handle gracefully) + sed -i "s|PLACEHOLDER_WP_REPO_ID||g" "$compose_file" + fi + # Patch the forgejo port mapping into the file if non-default if [ "$forge_port" != "3000" ]; then # Add port mapping to forgejo service so it's reachable from host during init