Skip to content

desktops: optional APT pin preferences in repo block#838

Merged
igorpecovnik merged 11 commits into
mainfrom
desktops-apt-preferences
Apr 12, 2026
Merged

desktops: optional APT pin preferences in repo block#838
igorpecovnik merged 11 commits into
mainfrom
desktops-apt-preferences

Conversation

@igorpecovnik
Copy link
Copy Markdown
Member

Summary

Some vendor archives — concretely, SpacemiT's for bianbu on the K1 RISC-V platform — must outrank the distro archive via /etc/apt/preferences.d/ pins, otherwise apt resolves the wrong versions and the DE install pulls broken ports of platform packages. Bianbu's upstream instructions pin three (origin, suite) pairs at priority 1100–1200.

Rather than shipping per-DE pin-writing logic, add an optional structured preferences: list under the existing repo: block in the desktop YAML schema. The pin file is then derived the same way the sources.list.d entry is — one place in the YAML, one file on disk, removed on uninstall.

repo:
  url: "https://archive.spacemit.com/bianbu-ports"
  key_url: "..."
  keyring: "..."
  preferences:
    - origin: Spacemit
      suite: mantic-spacemit
      priority: 1200
    - origin: Spacemit
      suite: mantic-porting
      priority: 1100
    - origin: Spacemit
      suite: mantic-customization
      priority: 1100

Generates (byte-for-byte matching SpacemiT's documented pin file):

Package: *
Pin: release o=Spacemit, n=mantic-spacemit
Pin-Priority: 1200

Package: *
Pin: release o=Spacemit, n=mantic-porting
Pin-Priority: 1100

Package: *
Pin: release o=Spacemit, n=mantic-customization
Pin-Priority: 1100

Changes

  • parse_desktop_yaml.py — emit DESKTOP_REPO_PREFS_COUNT and indexed DESKTOP_REPO_PREFS_<n>_{ORIGIN,SUITE,PRIORITY} for each entry. Skip malformed entries with a stderr warning (no silent drops).
  • module_desktop_repo.sh — when COUNT > 0, write one stanza per entry to /etc/apt/preferences.d/<de>. Only parsed, validated fields land in the file — raw YAML strings are never shell-eval'd.
  • module_desktops.sh — remove path deletes the preferences file (de-name regex-guarded). Leaving it behind would keep the vendor archive outranking the distro globally after the DE is gone, which is worse than leaving the sources.list.d entry (inert without packages).
  • bianbu.yaml — populate the three SpacemiT pins with a WHY comment.

Schema is a superset of the previous one: desktops with no preferences key emit DESKTOP_REPO_PREFS_COUNT="0" and the module takes the zero-branch. Smoke-tested against xfce (no repo:), kde-neon (repo: without preferences), and bianbu (full 3-entry output).

Docs PR: armbian/documentation#912 carries the schema-table update.

Test plan

  • `python3 tools/modules/desktops/scripts/parse_desktop_yaml.py tools/modules/desktops/yaml bianbu noble riscv64 --tier minimal` emits 3 indexed `DESKTOP_REPO_PREFS_*` blocks
  • Install bianbu on a riscv64 board and check `/etc/apt/preferences.d/bianbu` matches the expected file byte-for-byte
  • `armbian-config --api module_desktops remove de=bianbu` removes `/etc/apt/preferences.d/bianbu`
  • Other DEs (xfce, kde-plasma, gnome, mate, cinnamon, i3-wm, xmonad, enlightenment, kde-neon) install/remove unchanged

Some vendor archives (e.g. SpacemiT for bianbu on the K1 RISC-V
platform) must outrank the distro archive via /etc/apt/preferences.d
pins, otherwise apt resolves the wrong versions and the DE install
pulls broken ports of platform packages. Bianbu's upstream
instructions pin three (origin, suite) pairs at priority 1100-1200.

Add an optional structured `preferences:` list under `repo:` in the
desktop YAML schema, so the pin file is derived the same way the
sources.list.d entry is — one place in the YAML, one file on disk,
removed on uninstall.

- parse_desktop_yaml.py: emit DESKTOP_REPO_PREFS_COUNT and indexed
  DESKTOP_REPO_PREFS_<n>_{ORIGIN,SUITE,PRIORITY} for each entry;
  skip malformed entries with a stderr warning (no silent drops)
- module_desktop_repo.sh: when COUNT > 0, write one stanza per
  entry to /etc/apt/preferences.d/<de>. Only the parsed, validated
  fields land in the file — raw YAML strings are never shell-eval'd
- module_desktops.sh: remove path deletes the preferences file
  (de-name regex-guarded). Leaving it behind would keep a vendor
  archive outranking the distro globally after the DE is gone
- bianbu.yaml: add the three SpacemiT pins with a WHY comment
@github-actions github-actions Bot added size/medium PR with more then 50 and less then 250 lines 05 Milestone: Second quarter release labels Apr 12, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 12, 2026

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

The desktop module now writes APT source entries to /etc/apt/sources.list.d/${de}.list by iterating configured suites and components, using a temp file and atomic mv, with explicit failure checks. When preference pins are configured, it similarly emits /etc/apt/preferences.d/${de} with validated pin stanzas, or removes stale prefs when none are configured. The YAML parser emits validated DESKTOP_REPO_SUITES_COUNT, indexed DESKTOP_REPO_SUITE_<n>, DESKTOP_REPO_COMPONENTS, and DESKTOP_REPO_PREFS_<n>_{ORIGIN,SUITE,PRIORITY} variables. Desktop removal now removes the DE’s preferences file if the DE name is a safe token. bianbu.yaml updates repo URL/key, adds components, suites, and three preference pins.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'desktops: optional APT pin preferences in repo block' clearly describes the main change: adding optional APT preference pinning to the desktop repository configuration.
Description check ✅ Passed The description is thorough and directly related to the changeset, explaining the motivation (vendor archives requiring higher priority), the schema changes, implementation details across multiple files, and a test plan.
Docstring Coverage ✅ Passed Docstring coverage is 100.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
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch desktops-apt-preferences

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.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/modules/desktops/module_desktop_repo.sh`:
- Around line 59-80: The pin-file creation must be executed only when the
corresponding repo was successfully set up and must fail fast on any write
error: wrap the block that reads DESKTOP_REPO_PREFS_COUNT so it only runs when
the repo guard variable (e.g., the same flag set by add_desktop_repo or
DESKTOP_REPO_ADDED) indicates the repo was added, write to a temp file (e.g.,
"${pref_file}.tmp") inside the loop and check each write's exit status (or use a
single here-doc redirected to the temp file and check its result), then
atomically mv the temp to "${pref_file}" and if any write/mv fails call exit 1;
reference DESKTOP_REPO_PREFS_COUNT, pref_file and the loop variables
(origin_var, suite_var, prio_var) when locating the code to modify.

In `@tools/modules/desktops/scripts/parse_desktop_yaml.py`:
- Around line 330-331: The current validation in the block that checks origin,
suite and priority (the if that currently uses isinstance(priority, int))
wrongly accepts booleans and non-positive numbers; update that conditional to
explicitly reject booleans (i.e., ensure not isinstance(priority, bool)) and
enforce positive values (priority must be > 0), so the combined check verifies
origin and suite are present, priority is an int and not a bool, and priority >
0 before proceeding (adjust the same conditional used to print the error and
prevent generating Pin-Priority output).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 879906d3-db49-4238-a960-8ef8d978d708

📥 Commits

Reviewing files that changed from the base of the PR and between 248c42d and 1ba52f6.

📒 Files selected for processing (4)
  • tools/modules/desktops/module_desktop_repo.sh
  • tools/modules/desktops/module_desktops.sh
  • tools/modules/desktops/scripts/parse_desktop_yaml.py
  • tools/modules/desktops/yaml/bianbu.yaml

Comment thread tools/modules/desktops/module_desktop_repo.sh Outdated
Comment thread tools/modules/desktops/scripts/parse_desktop_yaml.py Outdated
Review feedback on PR #838:

1. The pin-file block ran even when the repo setup was skipped
   (empty DESKTOP_REPO_URL), so a DE with `preferences:` but no
   matching `url`/`key_url`/`keyring` would create an orphan file
   under /etc/apt/preferences.d/. Nest the block inside the
   existing `[[ -n \$DESKTOP_REPO_URL ... ]]` guard so pins only
   land when the matching archive was actually configured.

2. The previous `: > \$pref_file` + `>>` loop left a truncated
   file on mid-write failure, which apt would misparse rather
   than ignore. Write to \${pref_file}.tmp, check each printf's
   exit status, and atomically `mv` on success. Any failure
   returns 1 (not exit 1 — that would kill armbian-config).

No change to the emitted file content; smoke-tested against
bianbu, still byte-identical to SpacemiT's documented pin file.
Review feedback on PR #838:

- isinstance(priority, int) also accepts bools because bool
  subclasses int in Python. `priority: true` would slip through
  and render `Pin-Priority: True`, which apt refuses — warn and
  drop the entry instead of letting garbage reach the pin file.
- The inline comment already advertises "priority is a positive
  integer", but the check didn't enforce it. Add `priority > 0`
  so `priority: 0` / `priority: -5` no longer produce silent
  `Pin-Priority: 0` stanzas.

Smoke-tested: a fixture with bool, zero, negative, string, and
empty-origin variants now emits five stderr warnings and keeps
only the valid entry. Bianbu's 1200/1100/1100 unaffected.
The sources.list.d line was hard-coded to `<url> ${DISTROID} main`,
which doesn't fit vendor archives whose layout is not `<codename>
main`. SpacemiT's K1 RISC-V archive pins a frozen snapshot per
Ubuntu release and mirrors all four Ubuntu components — the target
line is:

    deb [signed-by=...] https://archive.spacemit.com/bianbu/ \
        noble/snapshots/v2.2 main universe restricted multiverse

Add two optional fields on the `repo:` block, plus per-release
overrides so the suite can vary across releases (bianbu on noble
uses v2.2; resolute uses v3.0).

- parse_desktop_yaml.py: emit DESKTOP_REPO_SUITE and
  DESKTOP_REPO_COMPONENTS. Resolution order per-release override ->
  repo default -> release codename (for suite) or ["main"] (for
  components). Both regex-validated (`^[A-Za-z0-9._/-]+$` and
  `^[A-Za-z0-9._-]+$`) so nothing shell-parseable reaches the
  source file; invalid values warn and fall back to defaults.
- module_desktop_repo.sh: substitute ${DESKTOP_REPO_SUITE} /
  ${DESKTOP_REPO_COMPONENTS} into the source line, with
  ${DISTROID} / "main" as bash-side fallbacks for older YAMLs
  that don't emit the new vars.
- bianbu.yaml: switch to the bianbu/ base URL, add the four-
  component list, add per-release repo_suite for noble (v2.2)
  and resolute (v3.0). Drop plucky — SpacemiT does not publish
  a plucky snapshot under this archive layout.
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/modules/desktops/module_desktop_repo.sh`:
- Around line 68-98: The prefs-writing block only runs when
DESKTOP_REPO_PREFS_COUNT > 0, leaving a stale /etc/apt/preferences.d/${de}
behind when the count becomes 0; update the logic around the
DESKTOP_REPO_PREFS_COUNT check (where pref_file and pref_tmp are computed and
used) to ensure that when DESKTOP_REPO_PREFS_COUNT is empty or equal to 0 you
explicitly remove any existing pref_file (use rm -f on the same pref_file
variable) before returning, so stale pin files are cleaned up when no prefs are
defined.
- Around line 58-61: The here-doc writing to /etc/apt/sources.list.d/${de}.list
using cat needs explicit failure handling: instead of directly redirecting into
the target, write to a temp file (e.g. /etc/apt/sources.list.d/${de}.list.tmp)
using the same content that references DESKTOP_REPO_KEYRING, DESKTOP_REPO_URL,
repo_suite and repo_components, check the write/move exit status, then
atomically mv the temp file into /etc/apt/sources.list.d/${de}.list (or log an
error and exit 1) so any write failure is detected and the script fails fast
rather than leaving a partial state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2ed686b-d43d-4d52-96df-5287aae7290e

📥 Commits

Reviewing files that changed from the base of the PR and between 1ba52f6 and e9717aa.

📒 Files selected for processing (3)
  • tools/modules/desktops/module_desktop_repo.sh
  • tools/modules/desktops/scripts/parse_desktop_yaml.py
  • tools/modules/desktops/yaml/bianbu.yaml
✅ Files skipped from review due to trivial changes (1)
  • tools/modules/desktops/scripts/parse_desktop_yaml.py

Comment thread tools/modules/desktops/module_desktop_repo.sh Outdated
Comment thread tools/modules/desktops/module_desktop_repo.sh
Armbian provides its own kernel and bootloader for the K1 board.
Letting SpacemiT's u-boot-spacemit or a pinned linux-image-6.1.15
install would collide with or downgrade Armbian's own packages via
the priority-1200 SpacemiT pin.
SpacemiT's bianbu archive fans each snapshot out across five
flavors (base, -security, -updates, -porting, -customization)
plus a floating bianbu-v<ver>-updates channel. Previously the
repo block emitted a single `deb` line, so only one of those
was reachable.

Accept `repo.suite` / `releases.<r>.repo_suite` as either a
string (one source line, existing behavior) or a list of
strings (one source line per entry, all sharing url/keyring/
components).

- parse_desktop_yaml.py: replace single-value DESKTOP_REPO_SUITE
  with DESKTOP_REPO_SUITES_COUNT + indexed DESKTOP_REPO_SUITE_<n>.
  Regex-validate each entry; invalid entries drop with a warning,
  empty list falls back to [release].
- module_desktop_repo.sh: loop over suites, writing one `deb` line
  per entry. Temp + atomic mv matches the preferences-file pattern,
  so a mid-write failure never leaves apt with a partial source
  list.
- bianbu.yaml: list noble's 6 snapshot suites (v2.2) and resolute's
  6 (v3.0, symmetric pattern).
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: 1

♻️ Duplicate comments (1)
tools/modules/desktops/module_desktop_repo.sh (1)

93-123: ⚠️ Potential issue | 🟠 Major

Remove the old pin file when the preferences list becomes empty.

If DESKTOP_REPO_PREFS_COUNT drops to 0—for example after removing repo.preferences or when the parser skips every malformed entry—this branch is skipped and the previous /etc/apt/preferences.d/${de} stays active. That leaves vendor pinning enabled on the next run even though the current desktop config no longer declares any pins.

🛠️ Proposed fix
-				if [[ -n "${DESKTOP_REPO_PREFS_COUNT}" && "${DESKTOP_REPO_PREFS_COUNT}" -gt 0 ]]; then
-					local pref_file="/etc/apt/preferences.d/${de}"
+				local pref_file="/etc/apt/preferences.d/${de}"
+				if [[ -n "${DESKTOP_REPO_PREFS_COUNT}" && "${DESKTOP_REPO_PREFS_COUNT}" -gt 0 ]]; then
 					local pref_tmp="${pref_file}.tmp"
 					local i origin_var suite_var prio_var origin suite prio
 
 					if ! : > "$pref_tmp"; then
 						echo "Error: cannot create ${pref_tmp}" >&2
 						return 1
 					fi
 
 					for (( i=0; i < DESKTOP_REPO_PREFS_COUNT; i++ )); do
 						origin_var="DESKTOP_REPO_PREFS_${i}_ORIGIN"
 						suite_var="DESKTOP_REPO_PREFS_${i}_SUITE"
 						prio_var="DESKTOP_REPO_PREFS_${i}_PRIORITY"
 						origin="${!origin_var}"
 						suite="${!suite_var}"
 						prio="${!prio_var}"
 						if ! printf 'Package: *\nPin: release o=%s, n=%s\nPin-Priority: %s\n\n' \
 							"$origin" "$suite" "$prio" >> "$pref_tmp"; then
 							echo "Error: failed to write preferences for ${de}" >&2
 							rm -f "$pref_tmp"
 							return 1
 						fi
 					done
 
 					if ! mv "$pref_tmp" "$pref_file"; then
 						echo "Error: failed to install ${pref_file}" >&2
 						rm -f "$pref_tmp"
 						return 1
 					fi
+				else
+					rm -f "$pref_file"
 				fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/modules/desktops/module_desktop_repo.sh` around lines 93 - 123, Old pin
file under /etc/apt/preferences.d/${de} is left behind when
DESKTOP_REPO_PREFS_COUNT is zero; detect the empty case and remove the stale
pref_file so vendor pinning is not preserved. After computing pref_file (or
before the existing if [[ -n "${DESKTOP_REPO_PREFS_COUNT}" &&
"${DESKTOP_REPO_PREFS_COUNT}" -gt 0 ]]; then ... fi block) add a branch that
checks if DESKTOP_REPO_PREFS_COUNT is empty or 0 and if the pref_file exists,
then remove it (handle rm failures by logging an error and returning non-zero),
ensuring you reference the same pref_file variable and preserve existing error
handling for pref_tmp and mv operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/modules/desktops/yaml/bianbu.yaml`:
- Around line 15-27: The APT pin "preferences" block uses wrong suite names
("mantic-spacemit", "mantic-porting", "mantic-customization") so the pins never
match the archive Release Codename; either remove the entire preferences block
or update its suite entries to the actual Release codenames (e.g. replace
"mantic-spacemit" → "noble", "mantic-porting" → "noble-porting",
"mantic-customization" → "noble-customization" — and add any missing codenames
like "noble-updates"/"noble-security" if required) so the pins in the
preferences section will correctly match the archive.

---

Duplicate comments:
In `@tools/modules/desktops/module_desktop_repo.sh`:
- Around line 93-123: Old pin file under /etc/apt/preferences.d/${de} is left
behind when DESKTOP_REPO_PREFS_COUNT is zero; detect the empty case and remove
the stale pref_file so vendor pinning is not preserved. After computing
pref_file (or before the existing if [[ -n "${DESKTOP_REPO_PREFS_COUNT}" &&
"${DESKTOP_REPO_PREFS_COUNT}" -gt 0 ]]; then ... fi block) add a branch that
checks if DESKTOP_REPO_PREFS_COUNT is empty or 0 and if the pref_file exists,
then remove it (handle rm failures by logging an error and returning non-zero),
ensuring you reference the same pref_file variable and preserve existing error
handling for pref_tmp and mv operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f3433ab4-2a2c-44db-87c8-4fa1968794fa

📥 Commits

Reviewing files that changed from the base of the PR and between e9717aa and af5ffe7.

📒 Files selected for processing (3)
  • tools/modules/desktops/module_desktop_repo.sh
  • tools/modules/desktops/scripts/parse_desktop_yaml.py
  • tools/modules/desktops/yaml/bianbu.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • tools/modules/desktops/scripts/parse_desktop_yaml.py

Comment thread tools/modules/desktops/yaml/bianbu.yaml
Matches the tier convention other DEs use:

  minimal = bare-bones DE + hardware enablement (firmware, GPU,
            kernel modules, opensbi) — needed on every tier so apt
            doesn't refuse to bring up the board
  mid     = fuller desktop (bianbu-desktop + bianbu-desktop-en +
            bianbu-standard) and optional camera stack (k1x-cam)
  full    = extras — Chinese locale (bianbu-desktop-zh) and
            development tooling (bianbu-development)
Previously module_desktop_repo only wrote /etc/apt/preferences.d/<de>
when DESKTOP_REPO_PREFS_COUNT > 0 and did nothing when the count
was 0 or unset. Uninstall already cleans the file, but install is
supposed to be declarative — after install, the system must match
the YAML. If someone reinstalls a DE after its YAML dropped pins,
the old pref file would linger and keep pinning packages the YAML
no longer declares.

Lift pref_file out of the count branch and add an else that rm -f's
the file so install's post-state tracks the YAML either way.
@github-actions github-actions Bot added size/large PR with 250 lines or more and removed size/medium PR with more then 50 and less then 250 lines labels Apr 12, 2026
@igorpecovnik igorpecovnik merged commit 1b712da into main Apr 12, 2026
1 check was pending
@igorpecovnik igorpecovnik deleted the desktops-apt-preferences branch April 12, 2026 21:05
igorpecovnik added a commit that referenced this pull request Apr 12, 2026
Review feedback on PR #838:

1. The pin-file block ran even when the repo setup was skipped
   (empty DESKTOP_REPO_URL), so a DE with `preferences:` but no
   matching `url`/`key_url`/`keyring` would create an orphan file
   under /etc/apt/preferences.d/. Nest the block inside the
   existing `[[ -n \$DESKTOP_REPO_URL ... ]]` guard so pins only
   land when the matching archive was actually configured.

2. The previous `: > \$pref_file` + `>>` loop left a truncated
   file on mid-write failure, which apt would misparse rather
   than ignore. Write to \${pref_file}.tmp, check each printf's
   exit status, and atomically `mv` on success. Any failure
   returns 1 (not exit 1 — that would kill armbian-config).

No change to the emitted file content; smoke-tested against
bianbu, still byte-identical to SpacemiT's documented pin file.
igorpecovnik added a commit that referenced this pull request Apr 12, 2026
Review feedback on PR #838:

- isinstance(priority, int) also accepts bools because bool
  subclasses int in Python. `priority: true` would slip through
  and render `Pin-Priority: True`, which apt refuses — warn and
  drop the entry instead of letting garbage reach the pin file.
- The inline comment already advertises "priority is a positive
  integer", but the check didn't enforce it. Add `priority > 0`
  so `priority: 0` / `priority: -5` no longer produce silent
  `Pin-Priority: 0` stanzas.

Smoke-tested: a fixture with bool, zero, negative, string, and
empty-origin variants now emits five stderr warnings and keeps
only the valid entry. Bianbu's 1200/1100/1100 unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

05 Milestone: Second quarter release size/large PR with 250 lines or more

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant