Skip to content

Conversation

@igorpecovnik
Copy link
Member

@igorpecovnik igorpecovnik commented Jan 11, 2026

Description

Do not merge, WIP.

Summary by CodeRabbit

  • Chores
    • Redesigned snapshot management system with timestamped snapshots to prevent conflicts
    • Improved release merge and publish workflows for greater consistency and reliability
    • Enhanced error handling and logging throughout the release process

✏️ 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]>
This fixes the case where repositories like debs-beta only have packages
in the main/common component (e.g., sid with only kernel packages).
Previously, the merge command would skip publishing if both utils and
desktop repos were empty, resulting in an incomplete repository.

Now we always publish at minimum the main/common component, ensuring all
distributions with any packages get properly published.
Fixes an issue where subsequent repository runs would fail with 404 errors
for Release files when no new packages were added. The problem was that
snapshots were only created conditionally, leading to missing components
in the published repository.

Changes:
- update_main: always drop and recreate common snapshot (remove check that
  prevented updates if snapshot was already published)
- process_release: always create utils/desktop snapshots even if repos are
  empty, ensuring all components are included in publish
- merge_repos: always create snapshots for all repos and create repos if
  they don't exist, preventing missing components on merge

This ensures the repository structure is complete on every run, regardless
of whether new packages are added.

Signed-off-by: Igor Pecovnik <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 11, 2026

📝 Walkthrough

Walkthrough

This PR refactors tools/repository/repo.sh to introduce a timestamped snapshot system replacing the previous drop-and-recreate approach. It enables parallel builds with isolated common snapshots, implements a merge flow for combining isolated releases with the common component, and updates publish and signing workflows accordingly.

Changes

Cohort / File(s) Change Summary
Repository Snapshot System & Parallel Build Workflow
tools/repository/repo.sh
Implements timestamped common snapshots (common-${timestamp}) with .common-snapshot file-based handoff; adds SINGLE_RELEASE isolated mode where common component is merged later; refactors update-main, process_release, merge, publish, and signing flows to support parallel builds without snapshot conflicts; centralizes snapshot naming across workflows through persistent file state.

Sequence Diagram(s)

sequenceDiagram
    participant Worker as Worker Process
    participant Update as update-main
    participant PubDB as Main DB
    participant Merge as merge/publish Flow
    participant Signing as Signing Flow

    Worker->>Update: Trigger (SINGLE_RELEASE mode)
    Update->>PubDB: Create common-${timestamp} snapshot
    Update->>Update: Save timestamp to .common-snapshot
    Note over PubDB: Isolated workers build release-specific components
    Worker->>PubDB: Build & publish utils/desktop components
    
    Merge->>Update: Read .common-snapshot filename
    Merge->>PubDB: Fetch timestamped common snapshot
    Merge->>Merge: Import isolated release packages into main DB
    Merge->>PubDB: Re-publish all releases with common component
    
    Signing->>Merge: Discover Release files
    Signing->>Signing: Sign top-level and component-level Release files
    Signing->>PubDB: Update signed metadata
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • armbian/build#9163: Overlapping modifications to snapshot/isolated-db handling and process_release/update-main/publish flow logic.
  • armbian/build#9150: Foundational PR implementing per-release isolated workflows and update-main/merge flows that this PR builds upon.
  • armbian/build#9224: Parallel implementation of the same isolated/parallel build workflows with central common snapshot and merge steps.

Suggested Reviewers

  • iav
  • hzyitc
  • mhoffrog

Poem

🐰 With timestamps dancing through the night,
Snapshots bloom in parallel light,
Common states await their merge,
As isolated workers surge,
No more conflicts, just delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title "Repository management jungling" contains a typo ("jungling" should be "juggling") and is vague about the specific changes; it does not clearly convey the main objective of implementing a parallel repository workflow with timestamped snapshots. Revise the title to be more specific and correct the typo, such as: "Implement parallel repository workflow with timestamped snapshots and isolated builds"
✅ 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 83.33% 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.

@igorpecovnik igorpecovnik added the Work in progress Unfinished / work in progress label Jan 11, 2026
@github-actions github-actions bot added size/large PR with 250 lines or more 02 Milestone: First quarter release labels Jan 11, 2026
@github-actions github-actions bot added Needs review Seeking for review Framework Framework components labels Jan 11, 2026
Copy link
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/repository/repo.sh (1)

1261-1262: Bug: Option parsing mismatch for -F and -P flags.

The options are defined as requiring arguments (with : suffix), but they're parsed as boolean flags:

# Definition says they require arguments:
SHORT=...,F:,P:
LONG=...,force-add:,force-publish:

# But parsing treats them as flags (no $2 consumed):
-F | --force-add )
  FORCE_ADD=true
  shift        # Should be shift 2 if argument expected
  ;;

This will cause argument parsing to fail or consume the next option as the argument.

🐛 Fix: Remove the colon to make them boolean flags
-SHORT=i:,l:,o:,c:,p:,r:,h,d,k,R:,F:,P:
-LONG=input:,list:,output:,command:,password:,releases:,help,dry-run,keep-sources,single-release:,force-add:,force-publish:
+SHORT=i:,l:,o:,c:,p:,r:,h,d,k,R:,F,P
+LONG=input:,list:,output:,command:,password:,releases:,help,dry-run,keep-sources,single-release:,force-add,force-publish

Also applies to: 1309-1315

🤖 Fix all issues with AI agents
In @tools/repository/repo.sh:
- Around line 799-820: The variable deb_info is assigned without being declared
local, causing it to leak into global scope; fix by declaring deb_info as local
where it’s used (e.g., add deb_info to the local declaration alongside deb_name,
deb_version, deb_arch in the while loop that processes .deb files and apply the
same local declaration at the other occurrence where deb_info is assigned) so
both instances use a function-local deb_info instead of a global variable.
🧹 Nitpick comments (5)
tools/repository/repo.sh (5)

353-360: Potential issue: stderr suppression may hide aptly errors.

The 2>/dev/null after run_aptly pipes stderr to /dev/null, but run_aptly already exits on failure. However, when piping to logger, any diagnostic output from aptly is lost. Consider capturing both stdout and stderr to the logger:

-		run_aptly -config="${CONFIG}" snapshot drop ${release}-utils | logger -t repo-management 2>/dev/null
+		run_aptly -config="${CONFIG}" snapshot drop ${release}-utils 2>&1 | logger -t repo-management

565-566: Unused variable: COMMON_SNAPSHOT is set but never used.

The comment says "Store in a variable for use later in this function," but COMMON_SNAPSHOT is not referenced after this assignment. The snapshot name is already saved to the file at line 562, which is used by downstream processes. Consider removing the unused variable assignment.

 		# Save the snapshot name to a file for use by publish processes
 		echo "$snapshot_name" > "${2}/.common-snapshot"
 		log "Common snapshot name saved: $snapshot_name"
-
-		# Store in a variable for use later in this function
-		COMMON_SNAPSHOT="$snapshot_name"

748-754: Temp config file not cleaned up on error path.

When the function returns 1 at line 753 due to missing common snapshot, the temp config file at $main_db_config is properly cleaned up at line 752. Good error handling!

However, if any run_aptly command fails later in the function (which calls exit 1), the temp config file won't be cleaned up. Consider using a trap for cleanup.

♻️ Suggested improvement
 merge_repos() {
 	local input_folder="$1"
 	local output_folder="$2"
 
 	log "Merge mode: combining common component with release-specific components"
 
 	# Create a temp config pointing to the main DB (not isolated)
 	local main_db_config
 	main_db_config="$(mktemp)"
+	trap "rm -f '$main_db_config'" RETURN
 	sed 's|"rootDir": ".*"|"rootDir": "'$output_folder'"|g' tools/repository/aptly.conf > "$main_db_config"

Then remove the manual rm -f "$main_db_config" calls at lines 743, 752, and 1013.


823-867: Code duplication: utils and desktop import logic is nearly identical.

The import logic for ${release}-utils (lines 773-821) and ${release}-desktop (lines 823-867) are almost identical. Consider extracting a helper function to reduce duplication and maintenance burden.

♻️ Suggested refactor
# Helper function to import repo from isolated DB to main DB
import_repo_from_isolated() {
    local repo_name="$1"
    local isolated_config="$2"
    local main_db_config="$3"
    local isolated_pool="$4"
    
    if ! aptly -config="$isolated_config" repo show "$repo_name" &>/dev/null; then
        return 0
    fi
    
    log "Importing $repo_name from isolated DB"
    
    # Create repo in main DB if it doesn't exist
    if ! aptly -config="$main_db_config" repo show "$repo_name" &>/dev/null; then
        local release="${repo_name%-*}"
        local component="${repo_name##*-}"
        run_aptly -config="$main_db_config" repo create \
            -component="$repo_name" \
            -distribution="$release" \
            -comment="Armbian $repo_name repository" \
            "$repo_name"
    fi
    
    # ... rest of import logic
}

Then call it for both utils and desktop.


916-928: Redundant file read inside loop.

The common snapshot file is read on every iteration of the releases loop (lines 917-928), but the value doesn't change between iterations. Consider hoisting this read outside the loop for efficiency.

+	# Read the common snapshot name once before the loop
+	local common_snapshot_file="${output_folder}/.common-snapshot"
+	local common_snapshot_name=""
+	if [[ -f "$common_snapshot_file" ]]; then
+		common_snapshot_name=$(cat "$common_snapshot_file")
+		log "Using common snapshot from file: $common_snapshot_name"
+	else
+		log "WARNING: .common-snapshot file not found, using 'common'"
+		common_snapshot_name="common"
+	fi
+
 	for release in "${releases[@]}"; do
 		log "Publishing $release with common component..."
 
 		# Determine which components to publish
 		local components_to_publish=()
 		local snapshots_to_publish=()
 
-		# Read the timestamped common snapshot name from file
-		local common_snapshot_file="${output_folder}/.common-snapshot"
-		if [[ -f "$common_snapshot_file" ]]; then
-			local common_snapshot_name=$(cat "$common_snapshot_file")
-			log "Using common snapshot from file: $common_snapshot_name"
-			components_to_publish=("main")
-			snapshots_to_publish=("$common_snapshot_name")
-		else
-			log "WARNING: .common-snapshot file not found at $common_snapshot_file"
-			log "Falling back to 'common' snapshot name"
-			components_to_publish=("main")
-			snapshots_to_publish=("common")
-		fi
+		components_to_publish=("main")
+		snapshots_to_publish=("$common_snapshot_name")
📜 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 d79582d and d10295a.

📒 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).
Learnt from: EvilOlaf
Repo: armbian/build PR: 8428
File: config/boards/lckfb-taishanpi.csc:5-9
Timestamp: 2025-07-25T03:51:50.830Z
Learning: When reviewing PRs in the Armbian build system, U-Boot defconfig files and patches may be added as part of the PR changes but might not be visible in the current repository clone state during review. It's important to check the actual PR file changes directly via GitHub or the PR API to get the complete picture of what files are being added or modified.
Learnt from: EvilOlaf
Repo: armbian/build PR: 8428
File: config/boards/lckfb-taishanpi.csc:5-9
Timestamp: 2025-07-25T03:51:50.830Z
Learning: When reviewing PRs in the Armbian build system, U-Boot defconfig files and patches may be added as part of the PR changes but might not be visible in the current repository clone state during review. It's important to check the actual PR file changes directly via GitHub API (https://api.github.com/repos/armbian/build/pulls/{pr_number}/files) to get the complete picture of what files are being added or modified, especially for U-Boot patches that will be applied during the build process.
📚 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)
🔇 Additional comments (6)
tools/repository/repo.sh (6)

274-285: LGTM!

The timestamped snapshot approach with file-based handoff is a clean solution for parallel builds. The snapshot name is unique per run (using Unix timestamp), and persisting it to .common-snapshot allows downstream processes to retrieve the correct snapshot without race conditions.


306-311: LGTM!

Clear separation of concerns - isolated workers skip common repo processing, which avoids duplicate work and potential conflicts in parallel execution.


404-410: LGTM!

Early return in isolated mode is the correct behavior - the merge command will handle publishing with the common component included. This cleanly separates the build phase (workers) from the publish phase (merge).


647-660: LGTM with a note on fragility.

The slash-counting approach to identify top-level Release files works correctly for the expected structure (dists/{release}/Release). The logic properly filters out component-level and binary-subdirectory Release files.


994-1008: LGTM!

Good error handling pattern - the publish command failure is caught, diagnostic information is logged, and the function returns non-zero to signal failure upstream.


1242-1257: LGTM!

The updated help text clearly documents the parallel workflow:

  1. Build common once with update-main
  2. Workers build release-specific components only
  3. Final merge combines common with release-specific

This accurately reflects the code changes.

Comment on lines +799 to +820
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
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Variable scope issue: deb_info not declared as local.

At lines 802 and 848, deb_info is assigned without local, making it a global variable. This could cause unexpected behavior if the variable is used elsewhere.

 						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)
+							local deb_info=$(dpkg-deb -f "$deb_file" Package Version Architecture 2>/dev/null)

Apply the same fix at line 848.

🤖 Prompt for AI Agents
In @tools/repository/repo.sh around lines 799 - 820, The variable deb_info is
assigned without being declared local, causing it to leak into global scope; fix
by declaring deb_info as local where it’s used (e.g., add deb_info to the local
declaration alongside deb_name, deb_version, deb_arch in the while loop that
processes .deb files and apply the same local declaration at the other
occurrence where deb_info is assigned) so both instances use a function-local
deb_info instead of a global variable.

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 Needs review Seeking for review size/large PR with 250 lines or more Work in progress Unfinished / work in progress

Development

Successfully merging this pull request may close these issues.

2 participants