Files
debianito-post-install/modules/firmware.sh
T
stornic56 89233b0750 Desktop environment support & QA expansion
- `.github/workflows/ci.yml` added as a new workflow: job `lint-and-test` on `runs-on: ubuntu-latest` with `container: debian:trixie`, checking out via `actions/checkout@v4`, installing `bats` and `shellcheck`, running `shellcheck --severity=error debianito.sh modules/**/*.sh` (with `shopt -s globstar`) and `bats tests/`.

- `debianito.sh` CLI parser: `AUTO_YES=0`, `ALLOW_DESTRUCTIVE=0` declared after `MODULES_DIR` and before `_boot_mark`; `_usage()` prints `Usage: debianito.sh [OPTIONS]` with `--yes`, `--allow-destructive` and `--help`; the `while [ $# -gt 0 ]` / `case` loop consumes `--yes` and `--allow-destructive`, prints `Unknown option: $1 (run with --help for usage)` to stderr and `exit 2` for unknown flags, and `--help|-h` runs `_usage` then `exit 0`.

- `modules/utils.sh`: new global `_SELECTION_CONFIRMED=0` and `_confirm_install_list()` (utils.sh:756-785), which renders items capped at 15 with `... and N more`, computes `height` capped at 24, calls `_confirm "$title" "$list" "$height" 65`, arms `_SELECTION_CONFIRMED=${#items[@]}` on yes, and returns 1 for an empty list or on decline. `_install_pkg()` (utils.sh:789-804) decrements the countdown instead of prompting, and only prompts via `elif ! _confirm "Install: ${pkg}" "Install ${pkg}\nVersion: ${ver}?"; then return 0`.

- Bulk retrofit in `modules/extras/**/*.sh` (18 sites): `_confirm_install_list "Título" "${array[@]}" || return` placed right after the `whiptail` selection is parsed, the install loop left intact, and `_SELECTION_CONFIRMED=0` after the loop. Files: office/office.sh:101, system/software_centers.sh:29, system/system.sh:133, communication/communication.sh:68, design/design.sh:42, download/download.sh:48+55, terminals/terminals.sh:31, security/security.sh:30, players/players.sh:20, themes/fonts/fonts.sh:32, dev/dev.sh:44, programming/programming.sh:57, themes/desktop-themes/desktop-themes.sh:30, fetch/fetch.sh:41, themes/icons/icons.sh:47, themes/cursors/cursors.sh:28, internet/internet.sh:197, java.

- `modules/bullseye/extras.sh` (13 sites) plus `modules/bullseye/legacy.sh` (1 site, `install_gaming_bullseye`, guarded with `if [ ${#_install_pkgs[@]} -gt 0 ]` so a i386-only selection does not blank the summary) plus `modules/desktop_display.sh` (1 site, XFCE Custom — a no-loop `_run_cmd` batch with the reset immediately after it). Special cases: download.sh and bullseye/download each have two checklists merged into one combined summary; legacy.sh gaming skips the reset when the list is empty; XFCE Custom resets before `_xfce_polkit_rules` so the counter cannot leak.

- `modules/desktops/desktop_gnome.sh` (new): `gnome_menu()` runs a `_radiolist` with "1" "GNOME (Full Desktop)", "2" "GNOME Core (Minimal Install)", "3" "Back", then `_install_gnome_full()` and `_install_gnome_core()`. Each arms `_confirm_install_list "GNOME Full Desktop" gnome gdm3 || return` (core: `gnome-core gdm3`), keeps an already-configured display manager via `_existing_display_manager` by setting `target_dm="$keep_dm"`, writes the preseed `echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections`, installs `sudo apt install -y gnome gdm3` / `gnome-core gdm3`, enables `gdm3` only when `target_dm = "gdm3"`, resets `_SELECTION_CONFIRMED=0` and calls `_offer_pipewire_after_desktop`.

- `modules/desktops/desktop_kde.sh` (new): `kde_menu()` runs a `_radiolist` with "1" "KDE Plasma (Full Desktop)", "2" "KDE Plasma Desktop (Minimal)", "3" "Back"; `_install_kde_full()` and `_install_kde_minimal()` arm `_confirm_install_list "KDE Plasma Full" kde-standard sddm || return` (minimal: `kde-plasma-desktop sddm`), preseed `sddm shared/default-x-display-manager select sddm`, install `sudo apt install -y kde-standard sddm` / `kde-plasma-desktop sddm`, enable `sddm` only when it is the target DM, reset the counter and offer PipeWire.

- `modules/desktop_display.sh` `desktop_environment_menu()`: `env_items+=("3" "GNOME")`, `env_items+=("4" "KDE Plasma")`, `env_items+=("5" "Back")`; the case adds `3)` loading `gnome` via `_load_module gnome || continue` and calling `gnome_menu`, and `4)` loading `kde` via `_load_module kde || continue` and calling `kde_menu`; the dead TODO comment was removed.

- PipeWire retrofit in `modules/desktop_display.sh` (6 sites): `_install_lxde_full`, `_install_lxde_core`, `_install_xfce_full`, `_install_xfce_minimal`, `_install_xfce_wayland` and `_install_xfce_custom` each call `_offer_pipewire_after_desktop` after their install block.

- `_offer_pipewire_after_desktop()` and `_existing_display_manager()` added at the end of `modules/desktop_display.sh`: `_offer_pipewire_after_desktop` checks `pipewire-audio` (`pipewire` when `DEBIAN_VERSION = 11`), returns 0 via `is_installed`, otherwise `_confirm "PipeWire Audio" "No PipeWire audio server is installed.\n\nInstall the PipeWire audio stack (recommended)?"` calls `_install_pipewire_standard` and returns 0. `_existing_display_manager` reads `${DM_FILE:-/etc/X11/default-display-manager}`, reads the basename as the DM, and otherwise scans `gdm3 lightdm sddm greetd gdm` with `systemctl is-enabled`, printing nothing when none is found.

- `_ensure_apt_updated` added before the GNOME/KDE `apt install` transactions (4 sites, deduplicated per session); `gnome gdm3` and `kde-standard sddm` are installed alongside even when another display manager is kept as the default.

- Extraction to `modules/desktops/`: `desktop_lxde.sh` (38 lines) and `desktop_xfce.sh` (119 lines) created with the `lxde_menu` / `_install_lxde_full` / `_install_lxde_core` and `xfce_menu` / `_install_xfce_full` / `_install_xfce_minimal` / `_install_xfce_wayland` / `_install_xfce_custom` / `_xfce_polkit_rules` blocks moved verbatim; both carry `# License GPL v3` in their headers.

- `modules/desktop_display.sh` reduced from 542 to 394 lines (−154): lines 55-208 removed, the dispatch (desktop_display.sh:39-46) replaced with `_load_module xfce || continue` / `lxde_menu`, and the file left with `manage_desktop_display`, `desktop_environment_menu`, `display_manager_menu`, `lightdm_config_menu`, `configure_greetd`, `configure_gdm3`, `configure_sddm`, the two shared helpers.

- `modules/utils.sh` `_load_module` map: `xfce)` → `${MODULES_DIR}/desktops/desktop_xfce.sh`, `lxde)` → `${MODULES_DIR}/desktops/desktop_lxde.sh`, `gnome)` → `${MODULES_DIR}/desktops/desktop_gnome.sh`, `kde)` → `${MODULES_DIR}/desktops/desktop_kde.sh`; deps for `xfce`, `lxde`, `gnome` and `kde` are `(desktop_display audio)` and `desktop_display` is `(repos audio)`.

- `modules/desktops/desktop_xfce.sh` `_xfce_polkit_rules()` writes `/etc/polkit-1/rules.d/85-suspend.rules` and `89-backlight.rules` via `sudo tee`, adds the `backlight` group with `groupadd --system` fallback, adds the real login user from `/etc/passwd` to `backlight` and restarts `polkit.service`.

- ES→EN user-facing strings: 14 strings across 4 files — `desktop_display.sh` `lxdе_menu` ("LXDE (Full Desktop)", "LXDE Core (Minimal Install)"), `gpu/nvidia.sh` Kepler legacy-driver notice ("Kepler GPU detected (GKxxx)… Install the legacy driver for Kepler?"), `bullseye/legacy.sh` ("Install NVIDIA driver?", "Skipping NVIDIA driver.", "Installing NVIDIA driver ${gpu_gen}...") and `bullseye/extras.sh` ("firmware-linux-nonfree provides drivers for… Install?").

- ES→EN internal comments: ~35 comments across 9 files — section headers in `_install_nvidia_standard`, `gpu/_helpers.sh` (lines 5, 113), `gpu/nvidia.sh` (lines 8-12, 19-22, 68-70, 167-172, 196, 239-253, 262-263, 275, 291, 336-338, 376), `gpu.sh` (lines 15-17), `bullseye/repos.sh` (line 2), `firmware.sh` (line 235, "--- Load verification ---"), `utils.sh` (lines 129, 132, 153 — timezone Paso 1/2/4) and `utils.sh:546,548`.

- set -e abort fix: `debianito.sh:149` now reads `_system_preferences_menu || true`, consistent with the other dispatch chains (14 of the 15 chains already had the guard); a declined pre-apply summary now leaves the TUI alive instead of terminating the script.

- `docs/desktops_display.md`: 4 hunks — intro now lists GNOME/GNOME Core/KDE Plasma/KDE Plasma Desktop/XFCE/XFCE Wayland/XFCE Custom/LXDE, a new "Modular layout" paragraph describing `modules/desktops/` and lazy loading, Block 1 rewritten into four environments, five new bullets (pre-apply summary, existing-DM preservation, PipeWire offer, stale-lists guard) and a Desktop Environment Packages table.

- `docs/quickstart.md` line 284: Desktop Environment entry expanded to pick from XFCE (full / minimal / Wayland `labwc` on Trixie / custom checklist), LXDE (full / core), GNOME (full / core) or KDE Plasma (full / minimal).

- `_existing_display_manager` now takes the config path via `local dm_file="${DM_FILE:-/etc/X11/default-display-manager}"` so the offline test suite never reads the real system file.

- `tests/test_helper.bash`: added `_mock_systemctl()` for `is-enabled` (units in `MOCK_SYSTEMCTL_ENABLED` report `enabled`, everything else `disabled`), `MOCK_SYSTEMCTL_ENABLED=""`, `WHIPTAIL_CALLS`/`MOCK_WHIPTAIL_OUTPUT`/`MOCK_WHIPTAIL_RC`, `MOCK_DPKG_INSTALLED` and the `systemctl` entry in the mock list and in `mock_reset()`.

- `tests/desktops.bats` created with 16 tests: `_existing_display_manager` (file read, file-preferred over an enabled unit, enabled-unit fallback, candidate order, missing file, nothing configured), `_offer_pipewire_after_desktop` (5 tests: skip-installed, accept-installs, decline-returns-0, Bullseye package name, wrong package name on 11), `_load_module` (4 tests for xfce/lxde/gnome/kde) and an unknown-module case; 17→33 tests total.

- `.github/workflows/ci.yml` shellcheck severity raised from `--severity=error` to `--severity=warning`.

- `debianito.sh`: `RED`, `GREEN`, `YELLOW`, `CYAN` and `NC` plus `AUTO_YES`, `ALLOW_DESTRUCTIVE` and `DEBIAN_CODENAME` are now `export`ed as cross-file globals.

- `modules/utils.sh`: `HAS_NVIDIA`, `DESKTOP_ENV`, `AUDIO_SERVER`, `DEBIAN_VERSION` and the other cross-file globals are `export`ed.

- `modules/extras/system/software_centers.sh` gained a `#!/usr/bin/env bash` shebang.

- `shellcheck` triage (v0.11.0, baseline `--severity=warning` 25 findings → 0): removed two dead-code lines (`headless` in `extras/dev/dev.sh`, orphan `dev_id` in `firmware.sh`); `SC2206` in `extras/dev/essential.sh` fixed by expanding `$comp_pkgs` to `"${comp_pkgs[@]}"`; `SC2010`/`SC2046` (3 sites) replaced `ls … | grep` with `compgen -G "…"`; `SC2155` (2 sites, `gpu.sh` and `rescue.sh`) split command substitutions into separate lines; `SC2166` in `repos.sh` replaced `-o` with `{ … || … }`; `SC2221`/`SC2222` (2 sites, `sysinfo.sh` and `utils.sh`) collapsed the `wl*|wlp*|wlo*|wlan*` alternation to `wl*`; `SC1090` in `extras.sh` gained a justification comment on the disable directive.

- Final verification: `bash -n` on all 15 modified `.sh` files and `set -euo pipefail` at `debianito.sh:3` both intact; `bats tests/` reports 33 ok / 0 failed; invariants `offers 11`, `confirm_install_list 39` and `_SELECTION_CONFIRMED=0 40` hold.
2026-10-05 23:17:38 -05:00

431 lines
17 KiB
Bash

#!/usr/bin/env bash
# ── Global arrays ──
# Device arrays (PCI_NET_DEVS, USB_WIFI_DEVS, PCI_BT_DEVS, USB_BT_DEVS)
# and _FW_PLAN_HW_LINES are declared in hw_detect.sh.
_DETECTED_FW_PKGS=()
_FW_PLAN_PKG_LINES=()
# ── Firmware package mapping ──
_detect_firmware_needs() {
local -A pkg_info
_DETECTED_FW_PKGS=()
_FW_PLAN_PKG_LINES=()
local dev_list
dev_list=("${PCI_NET_DEVS[@]}" "${USB_WIFI_DEVS[@]}")
for dev in "${dev_list[@]}"; do
local raw_desc vendor_lc pkg
raw_desc=$(echo "$dev" | sed -E 's/^[^ ]+ [^:]+: //; s/ \[[0-9a-fA-F]{4}:[0-9a-fA-F]{4}\]//; s/ \(rev.*\)//')
vendor_lc=$(echo "$dev" | sed -n 's/^.*]: //p' | awk '{print $1}' | tr '[:upper:]' '[:lower:]')
[[ "$vendor_lc" != *intel* && "$vendor_lc" != *realtek* && "$vendor_lc" != *atheros* && "$vendor_lc" != *qualcomm* && "$vendor_lc" != *mediatek* ]] && continue
case "$vendor_lc" in
*intel*)
if echo "$dev" | grep -qiE 'wireless|wi-fi|wlan|802\.11'; then
pkg="firmware-iwlwifi"
else
pkg="firmware-intel-misc"
fi
;;
*realtek*) pkg="firmware-realtek" ;;
*atheros* | *qualcomm*) pkg="firmware-atheros" ;;
*mediatek*) pkg="firmware-mediatek" ;;
esac
local short_dev
short_dev=$(echo "$raw_desc" | sed 's/ *\[[^]]*\]//g; s/ */ /g')
if [ -z "${pkg_info[$pkg]-}" ]; then
pkg_info[$pkg]="$short_dev"
else
pkg_info[$pkg]+=", $short_dev"
fi
done
for pkg in "${!pkg_info[@]}"; do
_DETECTED_FW_PKGS+=("$pkg")
_FW_PLAN_PKG_LINES+=(" [+] ${pkg} \xe2\x86\x90 ${pkg_info[$pkg]}")
done
mapfile -t _DETECTED_FW_PKGS < <(printf '%s\n' "${_DETECTED_FW_PKGS[@]}" | sort -u)
}
# ── Build plan string ──
_build_firmware_plan() {
local plan=""
plan+="This section installs the essential non-free firmware stack\n"
plan+="for Debian (including CPU microcode, GPU, network, and core\n"
plan+="drivers). The script has scanned your hardware to prepare\n"
plan+="the setup:\n\n"
plan+="Detected controllers:\n"
if [ ${#_FW_PLAN_HW_LINES[@]} -eq 0 ]; then
plan+=" (none detected)\n"
else
for line in "${_FW_PLAN_HW_LINES[@]}"; do
plan+="${line}\n"
done
fi
plan+="\nPlanned firmware packages:\n"
local fw_line
if is_installed firmware-linux-nonfree; then
local cur_ver
cur_ver=$(_get_installed_version "firmware-linux-nonfree")
fw_line=" [+] firmware-linux-nonfree ${cur_ver} (already installed)"
else
fw_line=" [+] firmware-linux-nonfree (base meta-package)"
fi
plan+="${fw_line}\n"
for line in "${_FW_PLAN_PKG_LINES[@]}"; do
plan+="${line}\n"
done
local has_bt=false
[ ${#PCI_BT_DEVS[@]} -gt 0 ] && has_bt=true
[ ${#USB_BT_DEVS[@]} -gt 0 ] && has_bt=true
if ! $has_bt; then
for dev in "${USB_WIFI_DEVS[@]}"; do
if echo "$dev" | grep -qi 'bluetooth'; then
has_bt=true
break
fi
done
fi
plan+="\nBluetooth:\n"
if $has_bt; then
if is_installed bluez; then
plan+=" [+] bluez (already installed)\n"
else
plan+=" [+] bluez + bluez-tools + bluez-obexd (base stack)\n"
fi
case "${DESKTOP_ENV:-other}" in
kde)
plan+=" [+] bluedevil (KDE applet)\n"
if [ "${AUDIO_SERVER:-}" = "pipewire" ]; then
plan+=" → pipewire-pulse + wireplumber (if missing)\n"
fi
;;
gnome) plan+=" (already in gnome-control-center)\n" ;;
*) plan+=" [+] blueman (GTK Bluetooth manager)\n" ;;
esac
plan+=" → Bluetooth service will be enabled\n"
else
plan+=" (no Bluetooth hardware detected)\n"
fi
plan+="\nInstallation order:\n"
plan+=" 1. Base firmware (firmware-linux-nonfree)\n"
plan+=" 2. Network firmware (realtek, iwlwifi, ...)\n"
plan+=" 3. Broadcom / Bluetooth firmware\n"
echo -e "$plan"
}
# ── Install detected firmware packages ──
_install_detected_firmware() {
local to_install=()
for pkg in "${_DETECTED_FW_PKGS[@]}"; do
if is_installed "$pkg"; then
echo " --> $pkg already installed."
continue
fi
local ver
ver=$(_get_pkg_version "$pkg")
if [ -z "$ver" ] || [ "$ver" = "(none)" ]; then
echo " --> $pkg not available in repositories, skipping."
continue
fi
to_install+=("$pkg")
done
if [ ${#to_install[@]} -gt 0 ]; then
_run_cmd "Firmware" "sudo DEBIAN_FRONTEND=noninteractive apt install -y ${to_install[*]}" \
"Installing network firmware packages..." || true
fi
}
# ── Wireless handler (Broadcom single-path wl) ──
_handle_wireless() {
local installed_any=false
local wl_build_failed=false
if [ ${#PCI_NET_DEVS[@]} -eq 0 ] && [ ${#USB_WIFI_DEVS[@]} -eq 0 ]; then
_detect_all_network_devices
fi
for dev in "${PCI_NET_DEVS[@]}"; do
local bcm_id
bcm_id=$(echo "$dev" | grep -oP '14e4:[0-9a-fA-F]+' || true)
[ -z "$bcm_id" ] && continue
# --- Dependencies verification ---
if ! is_installed "linux-headers-amd64" || ! is_installed "dkms"; then
if ! apt-cache show linux-headers-amd64 dkms >/dev/null 2>&1; then
_msg "Broadcom Error" "linux-headers-amd64 or dkms are not available in your repositories. Cannot compile Broadcom driver.\n\nEnsure repositories are enabled and run:\n sudo apt install linux-headers-amd64 dkms"
_pause
continue
fi
fi
# --- Confirmation ---
if ! _confirm "Broadcom WiFi" "Detected Broadcom wireless device.\n\nInstall broadcom-sta-dkms, dkms, and wireless-tools?"; then
continue
fi
# --- Step-by-step installation ---
_run_cmd "Broadcom Deps" "sudo DEBIAN_FRONTEND=noninteractive apt install -y dkms wireless-tools linux-headers-amd64" || true
_run_cmd "Broadcom Driver" "sudo DEBIAN_FRONTEND=noninteractive apt install -y broadcom-sta-dkms" || true
# --- Persist blacklist of conflicting modules ---
local blacklist_conf="/etc/modprobe.d/blacklist-broadcom.conf"
local blacklist_content="blacklist b43\nblacklist b43legacy\nblacklist brcmsmac\nblacklist bcma\nblacklist ssb"
echo -e "$blacklist_content" | sudo tee "$blacklist_conf" >/dev/null || true
# --- Update initramfs and load module ---
_run_cmd "Initramfs" "sudo update-initramfs -u" || true
_run_cmd "Modprobe" "sudo modprobe -r b43 b43legacy b44 bcma brcmsmac brcmfmac ssb wl 2>/dev/null || true" "Removing conflicting modules" || true
_run_cmd "Modprobe" "sudo modprobe wl" || true
# --- Combo BT (unchanged) ---
local has_broadcom_bt=false
local btdev
for btdev in "${PCI_BT_DEVS[@]}"; do
if echo "$btdev" | grep -qi 'broadcom'; then
has_broadcom_bt=true
break
fi
done
if $has_broadcom_bt; then
echo -e "${YELLOW}[+] Broadcom WiFi+BT combo card detected.${NC}"
sudo mkdir -p /etc/modprobe.d
printf 'softdep wl post: btusb\n' | sudo tee /etc/modprobe.d/broadcom-combo.conf >/dev/null || true
echo -e "${YELLOW} A reboot may be required for Bluetooth support.${NC}"
fi
# --- Post-DKMS verification (Fix 5, unchanged) ---
if ! compgen -G "/lib/modules/$(uname -r)/updates/dkms/wl.ko*" >/dev/null; then
_msg "Broadcom DKMS Build Failed" "The wl module was not built by DKMS.\n\nPossible causes:\n- Missing build tools (build-essential, dkms)\n- Kernel update without headers\n- Incompatible kernel version\n\nTry: sudo dpkg-reconfigure broadcom-sta-dkms"
if _confirm "Broadcom" "Rebuild the Broadcom driver now?"; then
_run_cmd "Broadcom" "sudo dpkg-reconfigure broadcom-sta-dkms" || true
if compgen -G "/lib/modules/$(uname -r)/updates/dkms/wl.ko*" >/dev/null; then
:
else
local dmesg_out
dmesg_out=$(sudo dmesg | tail -20 2>/dev/null || echo "(dmesg unavailable)")
_msg "Broadcom DKMS Still Failed" "dpkg-reconfigure did not produce wl.ko either.\n\nLast kernel messages:\n${dmesg_out}\n\nYou may need to check build logs or report a bug against broadcom-sta-dkms."
_pause
wl_build_failed=true
continue
fi
else
wl_build_failed=true
continue
fi
fi
_msg "Network Warning" "The script is about to unload current WiFi kernel modules to load the Broadcom driver.\n\nIf you are connected via SSH over WiFi, YOUR CONNECTION WILL DROP. Please reconnect after a few seconds."
sudo modprobe -r b43 b43legacy b44 bcma brcmsmac brcmfmac ssb wl 2>/dev/null || true
sudo modprobe wl 2>/dev/null || true
# --- Load verification ---
if lsmod | grep -q '^wl '; then
echo -e "${GREEN}[+] Broadcom WiFi activated (wl module loaded).${NC}"
_pause
installed_any=true
else
echo -e "${YELLOW}[+] Broadcom driver installed but wl module did not load.${NC}"
echo -e "${YELLOW} A system reboot is required.${NC}"
_pause
installed_any=true
fi
done
# --- USB Broadcom (unchanged) ---
local usb_dev
for usb_dev in "${USB_WIFI_DEVS[@]}"; do
if echo "$usb_dev" | grep -qi '0a5c'; then
_msg "USB Broadcom" "USB Broadcom device detected (0a5c).\n\nLinux does not have native drivers for most USB Broadcom WiFi chips.\nndiswrapper may be needed as a last resort."
_pause
fi
done
# --- Mensaje final (unchanged) ---
if ! $installed_any && ! $wl_build_failed; then
echo "No special WiFi firmware needed -- base firmware-linux-nonfree covers this system."
_pause
fi
}
# ── Ensure non-free repository is enabled ──
# Token-exact check for the "non-free" component. "non-free-firmware"
# contains the substring but is a different component, so word-boundary
# matching (\b) would produce false positives.
_has_nonfree_component() {
local file="$1"
grep -qE '^[^#]*[[:space:]]non-free([[:space:]]|$)' "$file" 2>/dev/null
}
_ensure_nonfree_repo() {
local nonfree_found=false
if [ -f /etc/apt/sources.list ] && _has_nonfree_component /etc/apt/sources.list; then
nonfree_found=true
fi
if ! $nonfree_found && [ -d /etc/apt/sources.list.d ]; then
for f in /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list; do
[ -f "$f" ] || continue
if _has_nonfree_component "$f"; then
nonfree_found=true
break
fi
done
fi
if $nonfree_found; then
return 0
fi
if ! _confirm "non-free Repository" "Component 'non-free' (and 'non-free-firmware') is required for WiFi/Bluetooth/GPU firmware.\n\nAdd them to your APT repositories?"; then
return 1
fi
# No active sources at all → bootstrap a complete configuration
if ! has_active_deb_sources; then
backup_current_repos
if ! bootstrap_repositories "main contrib non-free non-free-firmware"; then
return 1
fi
else
if [ -f /etc/apt/sources.list ]; then
sudo cp /etc/apt/sources.list "/etc/apt/sources.list.backup.$(date +%Y%m%d_%H%M%S)"
# Add each missing component after the space-delimited "main"
# component; a bare s/main/ would corrupt mirror URLs that
# contain "main" (e.g. https://main.example.com).
sudo sed -i -E '/^deb / { /(^|[[:space:]])non-free([[:space:]]|$)/! s/ main([[:space:]]|$)/ main non-free\1/ }' /etc/apt/sources.list
# non-free-firmware does not exist on Bullseye
if [ "$DEBIAN_VERSION" != "11" ]; then
sudo sed -i -E '/^deb / { /(^|[[:space:]])non-free-firmware([[:space:]]|$)/! s/ main([[:space:]]|$)/ main non-free-firmware\1/ }' /etc/apt/sources.list
fi
fi
if [ -d /etc/apt/sources.list.d ]; then
for f in /etc/apt/sources.list.d/*.sources; do
[ -f "$f" ] || continue
sudo cp "$f" "${f}.backup.$(date +%Y%m%d_%H%M%S)"
sudo sed -i -E '/^Components:/ { /(^|[[:space:]])non-free([[:space:]]|$)/! s/$/ non-free/ }' "$f"
if [ "$DEBIAN_VERSION" != "11" ]; then
sudo sed -i -E '/^Components:/ { /(^|[[:space:]])non-free-firmware([[:space:]]|$)/! s/$/ non-free-firmware/ }' "$f"
fi
done
fi
fi
# Verify the component was actually added before reporting success
local nonfree_ok=false
[ -f /etc/apt/sources.list ] && _has_nonfree_component /etc/apt/sources.list && nonfree_ok=true
if ! $nonfree_ok && [ -d /etc/apt/sources.list.d ]; then
for f in /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list; do
[ -f "$f" ] || continue
if _has_nonfree_component "$f"; then
nonfree_ok=true
break
fi
done
fi
if ! $nonfree_ok; then
echo -e "${RED}Failed to enable non-free repository. Check your APT sources.${NC}"
return 1
fi
_ensure_apt_updated
echo -e "${GREEN}non-free repository enabled.${NC}"
return 0
}
# ── Main entry point ──
install_firmware() {
# Hardware state is detected on demand; the device
# arrays and DESKTOP_ENV/AUDIO_SERVER are read below.
_ensure_state_detected
echo -e "${YELLOW}Base firmware check...${NC}"
if ! _ensure_nonfree_repo; then
_msg "Error" "No 'non-free' repositories were enabled and the user declined to add them.\nPlease enable non-free manually or accept the prompt in the Firmware option." 10 65
return 1
fi
# 1. Detect
_detect_all_network_devices
_detect_firmware_needs
# 2. Plan
local plan
plan=$(_build_firmware_plan)
_msg "Firmware, Wireless & Bluetooth Setup" "$plan" 22 72
# 3. Confirm
if ! _confirm "Firmware" "Apply the network & firmware plan?"; then
echo "Firmware installation skipped."
return
fi
# 4. Install base firmware meta-package (unchanged logic)
local fw_pkg="firmware-linux-nonfree"
local fw_bpo
fw_bpo=$(_get_backports_version "$fw_pkg")
local fw_stable
fw_stable=$(_get_pkg_version "$fw_pkg")
if is_installed "$fw_pkg"; then
if [ -n "$fw_bpo" ]; then
local current_ver
current_ver=$(_get_installed_version "$fw_pkg")
if _confirm "Firmware" "firmware-linux-nonfree ${current_ver} already installed.\n\nUpgrade to backports version ${fw_bpo}?\n\nBackports often includes newer hardware support."; then
_run_cmd "Firmware" "sudo apt install -y -t ${DEBIAN_CODENAME}-backports $fw_pkg" "Upgrading firmware..." || true
fi
else
echo "$fw_pkg already installed."
fi
else
local msg="firmware-linux-nonfree provides hardware drivers for:\n"
msg+=" WiFi, Bluetooth, GPU, audio, webcams, and more.\n\n"
if [ -n "$fw_bpo" ]; then
msg+=" ● Stable: ${fw_stable}\n"
msg+=" Ultra tested, but may lack support for\n"
msg+=" very recent hardware.\n\n"
msg+=" ● Backports: ${fw_bpo} (Recommended)\n"
msg+=" Updated firmware for modern hardware from\n"
msg+=" 2025/2026: recent GPUs, processors, WiFi.\n\n"
msg+="Choose version:"
if _confirm_custom "Firmware" "$msg" "Backports" "Stable"; then
_run_cmd "Firmware" "sudo apt install -y -t ${DEBIAN_CODENAME}-backports $fw_pkg" "Installing firmware from backports..." || true
else
_run_cmd "Firmware" "sudo apt install -y $fw_pkg" "Installing firmware from stable..." || true
fi
else
msg+=" Version: ${fw_stable}\n\n"
msg+="Install it?"
if _confirm "Firmware" "$msg"; then
_run_cmd "Firmware" "sudo apt install -y $fw_pkg" "Installing firmware..." || true
fi
fi
echo -e "${GREEN}Base firmware installed.${NC}"
fi
# 5. Install specific network firmware packages
_install_detected_firmware
# 6. Broadcom wireless handler
_handle_wireless
# 7. Bluetooth stack
_install_bluetooth_stack
# 8. Summary
echo -e "${GREEN}Network & firmware setup complete.${NC}"
_pause
}