Skip to content

feat: implement parallel repository management workflow#9224

Merged
igorpecovnik merged 1 commit intomainfrom
feature/new-parallel-repo-architecture
Jan 10, 2026
Merged

feat: implement parallel repository management workflow#9224
igorpecovnik merged 1 commit intomainfrom
feature/new-parallel-repo-architecture

Conversation

@igorpecovnik
Copy link
Copy Markdown
Member

@igorpecovnik igorpecovnik commented Jan 10, 2026

Summary

This PR implements a complete parallel repository management system that allows building and publishing Debian repositories in parallel, significantly reducing build time for multiple distributions.

Key Changes

New Commands

  • update-main: Builds common/main component once for all releases
  • update -R <release>: Builds release-specific components in isolated DBs
  • merge: Combines common + release-specific components into final repos

Architecture

  • Isolated databases (aptly-isolated-<release>) avoid locking during parallel builds
  • Common component built once, not duplicated per release
  • Release-specific components (utils, desktop) built independently
  • Final merge combines all components with proper GPG signing

Fixes & Improvements

  • ✅ Fixed GPG signing to target top-level Release files (dists/{release}/Release)
  • ✅ Pool cleanup before publishing avoids "file already exists" errors
  • ✅ Smart package import skips duplicates during merge
  • ✅ Proper handling of empty repositories and missing components
  • ✅ Improved error handling and logging throughout

Workflow

  1. update-main: Build common component (once)
  2. update -R : Parallel workers build release-specific components
  3. merge: Combine all components and publish with GPG signatures

Benefits

This enables GitHub Actions to run multiple release builders in parallel, reducing total repository build time from hours to minutes.

Testing

  • ✅ GPG signing verified for all distributions (InRelease + Release.gpg created)
  • ✅ Pool structure working correctly (main, noble-desktop, bookworm-utils, etc.)
  • ✅ Isolated database isolation verified
  • ✅ Merge command successfully imports and publishes components

Documentation

Documentation references found:

Summary by CodeRabbit

Release Notes

  • Refactor
    • Release workflow restructured: common component building deferred from parallel worker processing to a dedicated merge step
    • Publishing and signing logic updated to align with the two-phase release process, executing during the merge phase
    • Merge process enhanced to perform final integration of common and release-specific components

✏️ Tip: You can customize this high-level summary in your review settings.

This commit implements a complete parallel repository management system
that allows building and publishing Debian repositories in parallel,
significantly reducing build time for multiple distributions.

- `update-main`: Builds common/main component once for all releases
- `update -R <release>`: Builds release-specific components in isolated DBs
- `merge`: Combines common + release-specific components into final repos

- Isolated databases (aptly-isolated-<release>) avoid locking during parallel builds
- Common component built once, not duplicated per release
- Release-specific components (utils, desktop) built independently
- Final merge combines all components with proper GPG signing

- Fixed GPG signing to target top-level Release files (dists/{release}/Release)
- Pool cleanup before publishing avoids "file already exists" errors
- Smart package import skips duplicates during merge
- Proper handling of empty repositories and missing components
- Improved error handling and logging throughout

1. update-main: Build common component (once)
2. update -R <release>: Parallel workers build release-specific components
3. merge: Combine all components and publish with GPG signatures

This enables GitHub Actions to run multiple release builders in parallel,
reducing total repository build time from hours to minutes.

Signed-off-by: Igor Pecovnik <[email protected]>
@github-actions github-actions bot added size/large PR with 250 lines or more 02 Milestone: First quarter release labels Jan 10, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

Walkthrough

The repository script refactors its isolated-mode workflow to defer common component building to a dedicated merge step. Workers now skip common repository operations during parallel builds, focusing only on release-specific components. The merge process is significantly extended to integrate common components with isolated-built releases, handling snapshot importing, re-publishing, and final signing.

Changes

Cohort / File(s) Summary
Repository merge workflow restructuring
tools/repository/repo.sh
Modified SINGLE_RELEASE isolated mode to skip common component building on workers and defer it to a dedicated merge step. Extended merge_repos function to: check main DB for common snapshot, import release-specific snapshots from isolated DBs, re-publish with common components, perform final signing, and update repository control file. Updated publishing/signing logic to respect isolation boundaries and halt early when appropriate.

Sequence Diagram(s)

sequenceDiagram
    participant Workers as Worker Nodes<br/>(Isolated)
    participant IDB as Isolated DBs
    participant MDB as Main DB
    participant Pub as Publishing
    participant Sign as Signing
    participant Repo as Repository<br/>Control

    Workers->>IDB: Build & snapshot<br/>release components
    Workers->>Pub: Skip publishing<br/>(isolated mode)
    Note over Workers: Process exits early
    
    Note over MDB: Merge Step Initiated
    rect rgb(200, 220, 255)
    Pub->>MDB: Create/check main DB config
    Pub->>MDB: Validate common snapshot exists
    Pub->>MDB: Import snapshots from isolated DBs
    IDB->>MDB: Transfer release-specific packages
    MDB->>MDB: Deduplicate against main DB
    Pub->>Pub: Re-publish with common components
    Pub->>Sign: Final signing (non-isolated)
    Sign->>Repo: Update control file
    Repo->>Repo: Show merged repository content
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • iav
  • hzyitc
  • mhoffrog

Poem

🐰 Hops through the code with glee,
Common repos now build just once, you see!
Workers skip the duplication dance,
Merge step weaves them in—one final chance! 🎯✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: implement parallel repository management workflow' is overly broad and doesn't capture the main focus. The PR primarily redesigns release isolation and merge workflows to avoid duplication, not a generic parallel management system. Consider more specific titles like 'feat: implement isolated release builds with deferred common component merge' or 'feat: refactor repository workflow to defer common component merge until final step' to better reflect the actual architectural change.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added Needs review Seeking for review Framework Framework components labels Jan 10, 2026
@igorpecovnik igorpecovnik added Ready to merge Reviewed, tested and ready for merge and removed Needs review Seeking for review labels Jan 10, 2026
@igorpecovnik igorpecovnik merged commit 09ce370 into main Jan 10, 2026
11 of 13 checks passed
@igorpecovnik igorpecovnik deleted the feature/new-parallel-repo-architecture branch January 10, 2026 23:37
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @tools/repository/repo.sh:
- Around line 736-744: The conditional that checks membership using [[ "
${releases[@]} " =~ " ${release} " ]] is unsafe (ShellCheck SC2199); replace it
with a safe membership test such as piping printf '%s\n' "${releases[@]}" into
grep -qx "$release" (or implement an associative array/set for O(1) lookup) and
only append to the releases array when that test fails; update the block around
the releases and release variables accordingly so the code uses the grep/assoc
lookup instead of the regex-on-concatenated-array.
- Around line 808-828: The import loops use the pipeline form find | while read
... which executes the while-body in a subshell so run_aptly failures (which
call exit) only terminate the subshell; change both loops to use process
substitution so the while runs in the main shell (e.g., replace find
"$isolated_pool" -name "*.deb" -type f | while read -r deb_file; do ... done
with while IFS= read -r deb_file; do ... done < <(find "$isolated_pool" -name
"*.deb" -type f) ), ensure you preserve the same variable names (deb_file,
deb_info, deb_key, packages, main_db_packages) and the run_aptly
-config="$main_db_config" repo add calls, and apply the same change to the
desktop import loop to make exit propagate to the parent script.
🧹 Nitpick comments (1)
tools/repository/repo.sh (1)

706-709: Consider trap-based cleanup for temporary files.

Temporary config files (main_db_config, isolated_config) could leak if the script exits unexpectedly. A trap would ensure cleanup on exit.

💡 Suggested improvement
merge_repos() {
    local input_folder="$1"
    local output_folder="$2"
    
    # Track temp files for cleanup
    local temp_files=()
    trap 'rm -f "${temp_files[@]}"' EXIT
    
    local main_db_config
    main_db_config="$(mktemp)"
    temp_files+=("$main_db_config")
    # ... rest of function

This is optional since the current manual cleanup covers the main code paths.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9e7f6dc and 72ec2b1.

📒 Files selected for processing (1)
  • tools/repository/repo.sh
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: igorpecovnik
Repo: armbian/build PR: 9087
File: .github/workflows/pr-check-pictures.yml:138-146
Timestamp: 2025-12-16T13:40:07.649Z
Learning: In the Armbian build repository, when introducing new requirements or checks (like the board assets verification workflow), the project prefers an initial educational-only period where violations post helpful PR comments and warnings but don't block merges. This allows contributors to become familiar with new requirements before enforcement is enabled (typically after ~6 months).
📚 Learning: 2025-12-18T23:40:41.627Z
Learnt from: rpardini
Repo: armbian/build PR: 9101
File: lib/functions/image/compress-checksum.sh:44-48
Timestamp: 2025-12-18T23:40:41.627Z
Learning: In the Armbian build framework, scripts run with set -e, so a non-zero exit status will abort the script. During reviews, assume failures will stop execution unless explicitly handled. Verify that commands that must fail are checked, errors are propagated, and any critical steps have proper error handling (e.g., using pipefail where appropriate, checking exit codes, and not masking failures).

Applied to files:

  • tools/repository/repo.sh
🧬 Code graph analysis (1)
tools/repository/repo.sh (1)
tools/repository/extract-repo.sh (1)
  • log (21-23)
🪛 GitHub Actions: Shellcheck - PR #9224 ("feat: implement parallel repository management workflow")
tools/repository/repo.sh

[error] 739-739: ShellCheck SC2199 (error): Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @).

🔇 Additional comments (11)
tools/repository/repo.sh (11)

312-317: LGTM!

Clear documentation of the workflow change where workers skip common repo building in isolated mode, deferring to the merge command.


374-382: LGTM!

Proper conditional initialization of component arrays - isolated mode correctly excludes the common component which will be merged later.


438-444: LGTM!

Correct early return in isolated mode - workers prepare repos and snapshots but defer publishing to the merge step.


462-466: LGTM!

Good defensive programming - guards against publishing with no components, which would likely cause aptly to fail.


669-686: LGTM!

The signing logic correctly targets only top-level Release files (dists/{release}/Release) using both -maxdepth 2 and slash counting validation. This prevents signing component-level Release files that don't need it.


750-763: LGTM!

Proper validation logic - gracefully handles empty releases (no-op) and correctly errors when common snapshot is missing with helpful guidance.


908-916: LGTM!

Good defensive practice using ${published_pool:?} to prevent accidental rm -rf /* if the variable is unset. Pool cleanup before publishing is the correct approach to avoid "file already exists" conflicts.


918-996: LGTM!

Solid publishing loop implementation with proper package checks, snapshot creation, and error handling with diagnostic output on failure.


998-1019: LGTM!

Proper cleanup of temporary config and complete finalization including signing, GPG key distribution, control file update, and content display.


1228-1242: LGTM!

Documentation correctly reflects the new workflow where workers focus only on release-specific components while common is built once and merged later.


593-597: LGTM!

Improved logging guidance for single-release mode with clear instructions about the update-main command.

Comment on lines +736 to +744
# Skip common distribution
[[ "$release" == "common" ]] && continue
# Add if not already in list
if [[ ! " ${releases[@]} " =~ " ${release} " ]]; then
releases+=("$release")
fi
fi
done
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix ShellCheck SC2199: Array concatenation in [[ ]] context.

The pipeline failure indicates that ${releases[@]} in the regex match implicitly concatenates array elements, which can lead to incorrect matching behavior.

🔧 Proposed fix
-				# Add if not already in list
-				if [[ ! " ${releases[@]} " =~ " ${release} " ]]; then
-					releases+=("$release")
-				fi
+				# Add if not already in list
+				local already_exists=false
+				for existing in "${releases[@]}"; do
+					if [[ "$existing" == "$release" ]]; then
+						already_exists=true
+						break
+					fi
+				done
+				if [[ "$already_exists" == false ]]; then
+					releases+=("$release")
+				fi

Alternatively, a more concise approach using a helper function or associative array for O(1) lookup:

# Using printf and grep (concise but spawns subshell)
if ! printf '%s\n' "${releases[@]}" | grep -qx "$release"; then
    releases+=("$release")
fi
🧰 Tools
🪛 GitHub Actions: Shellcheck - PR #9224 ("feat: implement parallel repository management workflow")

[error] 739-739: ShellCheck SC2199 (error): Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @).

🤖 Prompt for AI Agents
In @tools/repository/repo.sh around lines 736 - 744, The conditional that checks
membership using [[ " ${releases[@]} " =~ " ${release} " ]] is unsafe
(ShellCheck SC2199); replace it with a safe membership test such as piping
printf '%s\n' "${releases[@]}" into grep -qx "$release" (or implement an
associative array/set for O(1) lookup) and only append to the releases array
when that test fails; update the block around the releases and release variables
accordingly so the code uses the grep/assoc lookup instead of the
regex-on-concatenated-array.

Comment on lines +808 to +828
find "$isolated_pool" -name "*.deb" -type f | while read -r deb_file; do
# Get package info to check if it belongs to this repo
local deb_name deb_version deb_arch
deb_info=$(dpkg-deb -f "$deb_file" Package Version Architecture 2>/dev/null)
deb_name=$(echo "$deb_info" | sed -n '1s/Package: //p')
deb_version=$(echo "$deb_info" | sed -n '2s/Version: //p')
deb_arch=$(echo "$deb_info" | sed -n '3s/Architecture: //p')
local deb_key="${deb_name}_${deb_version}_${deb_arch}"

# Check if this package is in the utils repo (isolated)
# aptly output has leading spaces, so grep without anchors
if echo "$packages" | grep -qw "${deb_key}"; then
# Check if package already exists in main DB repo to avoid conflicts
if echo "$main_db_packages" | grep -qw "${deb_key}"; then
# Package already in main DB, skip it
continue
fi
run_aptly -config="$main_db_config" repo add -force-replace "${release}-utils" "$deb_file"
fi
done
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Critical: run_aptly failures inside while read subshell won't terminate the script.

The find ... | while read pattern runs the loop in a subshell. When run_aptly calls exit 1 on failure, it only exits the subshell, not the parent script. The import continues silently even if package additions fail.

🔧 Proposed fix using process substitution
-					find "$isolated_pool" -name "*.deb" -type f | while read -r deb_file; do
+					while read -r deb_file; do
 						# Get package info to check if it belongs to this repo
 						local deb_name deb_version deb_arch
 						deb_info=$(dpkg-deb -f "$deb_file" Package Version Architecture 2>/dev/null)
 						deb_name=$(echo "$deb_info" | sed -n '1s/Package: //p')
 						deb_version=$(echo "$deb_info" | sed -n '2s/Version: //p')
 						deb_arch=$(echo "$deb_info" | sed -n '3s/Architecture: //p')
 						local deb_key="${deb_name}_${deb_version}_${deb_arch}"

 						# Check if this package is in the utils repo (isolated)
 						if echo "$packages" | grep -qw "${deb_key}"; then
 							if echo "$main_db_packages" | grep -qw "${deb_key}"; then
 								continue
 							fi
 							run_aptly -config="$main_db_config" repo add -force-replace "${release}-utils" "$deb_file"
 						fi
-					done
+					done < <(find "$isolated_pool" -name "*.deb" -type f)

The same issue exists in the desktop import loop (lines 854-873). Apply the same fix there.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
find "$isolated_pool" -name "*.deb" -type f | while read -r deb_file; do
# Get package info to check if it belongs to this repo
local deb_name deb_version deb_arch
deb_info=$(dpkg-deb -f "$deb_file" Package Version Architecture 2>/dev/null)
deb_name=$(echo "$deb_info" | sed -n '1s/Package: //p')
deb_version=$(echo "$deb_info" | sed -n '2s/Version: //p')
deb_arch=$(echo "$deb_info" | sed -n '3s/Architecture: //p')
local deb_key="${deb_name}_${deb_version}_${deb_arch}"
# Check if this package is in the utils repo (isolated)
# aptly output has leading spaces, so grep without anchors
if echo "$packages" | grep -qw "${deb_key}"; then
# Check if package already exists in main DB repo to avoid conflicts
if echo "$main_db_packages" | grep -qw "${deb_key}"; then
# Package already in main DB, skip it
continue
fi
run_aptly -config="$main_db_config" repo add -force-replace "${release}-utils" "$deb_file"
fi
done
fi
while read -r deb_file; do
# Get package info to check if it belongs to this repo
local deb_name deb_version deb_arch
deb_info=$(dpkg-deb -f "$deb_file" Package Version Architecture 2>/dev/null)
deb_name=$(echo "$deb_info" | sed -n '1s/Package: //p')
deb_version=$(echo "$deb_info" | sed -n '2s/Version: //p')
deb_arch=$(echo "$deb_info" | sed -n '3s/Architecture: //p')
local deb_key="${deb_name}_${deb_version}_${deb_arch}"
# Check if this package is in the utils repo (isolated)
# aptly output has leading spaces, so grep without anchors
if echo "$packages" | grep -qw "${deb_key}"; then
# Check if package already exists in main DB repo to avoid conflicts
if echo "$main_db_packages" | grep -qw "${deb_key}"; then
# Package already in main DB, skip it
continue
fi
run_aptly -config="$main_db_config" repo add -force-replace "${release}-utils" "$deb_file"
fi
done < <(find "$isolated_pool" -name "*.deb" -type f)
fi
🤖 Prompt for AI Agents
In @tools/repository/repo.sh around lines 808 - 828, The import loops use the
pipeline form find | while read ... which executes the while-body in a subshell
so run_aptly failures (which call exit) only terminate the subshell; change both
loops to use process substitution so the while runs in the main shell (e.g.,
replace find "$isolated_pool" -name "*.deb" -type f | while read -r deb_file; do
... done with while IFS= read -r deb_file; do ... done < <(find "$isolated_pool"
-name "*.deb" -type f) ), ensure you preserve the same variable names (deb_file,
deb_info, deb_key, packages, main_db_packages) and the run_aptly
-config="$main_db_config" repo add calls, and apply the same change to the
desktop import loop to make exit propagate to the parent script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

02 Milestone: First quarter release Framework Framework components Ready to merge Reviewed, tested and ready for merge size/large PR with 250 lines or more

Development

Successfully merging this pull request may close these issues.

1 participant