From 89233b0750b2408d5daa1e40ca62b7890afa3d3f Mon Sep 17 00:00:00 2001 From: stornic56 <71296607+stornic56@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:17:38 -0500 Subject: [PATCH] Desktop environment support & QA expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `.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. --- debianito.sh | 60 ++++- docs/desktops_display.md | 29 ++- docs/quickstart.md | 2 +- modules/bullseye/extras.sh | 82 ++++++- modules/bullseye/legacy.sh | 20 +- modules/bullseye/repos.sh | 2 +- modules/desktop_display.sh | 213 ++++++------------ modules/desktops/desktop_gnome.sh | 79 +++++++ modules/desktops/desktop_kde.sh | 80 +++++++ modules/desktops/desktop_lxde.sh | 39 ++++ modules/desktops/desktop_xfce.sh | 125 ++++++++++ modules/extras.sh | 3 + modules/extras/communication/communication.sh | 7 + modules/extras/design/design.sh | 7 + modules/extras/dev/dev.sh | 9 +- modules/extras/download/download.sh | 7 + modules/extras/essential/essential.sh | 8 +- modules/extras/fetch/fetch.sh | 7 + modules/extras/internet/internet.sh | 7 + modules/extras/java.sh | 7 + modules/extras/office/office.sh | 7 + modules/extras/players/players.sh | 7 + modules/extras/programming/programming.sh | 7 + modules/extras/security/security.sh | 7 + modules/extras/system/software_centers.sh | 8 + modules/extras/system/system.sh | 7 + modules/extras/terminals/terminals.sh | 7 + modules/extras/themes/cursors/cursors.sh | 7 + .../themes/desktop-themes/desktop-themes.sh | 7 + modules/extras/themes/fonts/fonts.sh | 7 + modules/extras/themes/icons/icons.sh | 7 + modules/firmware.sh | 9 +- modules/gaming.sh | 12 + modules/gpu.sh | 9 +- modules/gpu/_helpers.sh | 18 +- modules/gpu/nvidia.sh | 101 +++++---- modules/repos.sh | 2 +- modules/rescue.sh | 3 +- modules/sysinfo.sh | 2 +- modules/system/audio.sh | 6 + modules/utils.sh | 118 +++++++--- tests/desktops.bats | 190 ++++++++++++++++ tests/test_helper.bash | 26 ++- 43 files changed, 1095 insertions(+), 272 deletions(-) create mode 100644 modules/desktops/desktop_gnome.sh create mode 100644 modules/desktops/desktop_kde.sh create mode 100644 modules/desktops/desktop_lxde.sh create mode 100644 modules/desktops/desktop_xfce.sh create mode 100644 tests/desktops.bats diff --git a/debianito.sh b/debianito.sh index 4b884d6..2facaab 100644 --- a/debianito.sh +++ b/debianito.sh @@ -2,11 +2,14 @@ # Debianito — simple configurator script set -euo pipefail -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' +# Colour palette shared with every lazily-loaded module: +# exported so shellcheck sees them as cross-file globals +# (SC2034) instead of unused locals. +export RED='\033[0;31m' +export GREEN='\033[0;32m' +export YELLOW='\033[1;33m' +export CYAN='\033[0;36m' +export NC='\033[0m' # TUI dimensions — fixed centered size for whiptail dialogs TUI_ALTO=20 @@ -16,6 +19,49 @@ TUI_ALTO_LISTA=10 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MODULES_DIR="${SCRIPT_DIR}/modules" +# ── CLI flags (unattended mode) ── +# --yes: auto-confirm every prompt. Menus still +# display; only _confirm-style prompts auto-accept. +# --allow-destructive: lift the safety gate on +# destructive operations (e.g. branch migration). +# CLI flags consumed by the TUI helpers in modules/utils.sh +# (_confirm auto-accepts) and by destructive-operation gates. +# Exported because they are cross-file globals (SC2034). +export AUTO_YES=0 +export ALLOW_DESTRUCTIVE=0 + +_usage() { + cat <<'EOF' +Usage: debianito.sh [OPTIONS] + +Debian post-installation configurator (Debian 11/12/13). + +Options: + --yes Auto-confirm all prompts (unattended mode). + Menus still require interaction. + --allow-destructive Allow destructive operations (e.g. branch + migration) without an extra safety gate. + --help Show this help and exit. + +Examples: + ./debianito.sh --yes + ./debianito.sh --yes --allow-destructive +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + --yes) AUTO_YES=1 ;; + --allow-destructive) ALLOW_DESTRUCTIVE=1 ;; + --help|-h) _usage; exit 0 ;; + *) + echo "Unknown option: $1 (run with --help for usage)" >&2 + exit 2 + ;; + esac + shift +done + # Boot profiling: DEBIANITO_PROFILE=1 stamps each boot # step to stderr for before/after comparison. _boot_mark() { @@ -62,7 +108,7 @@ _on_interrupt() { trap _on_interrupt INT TERM DEBIAN_VERSION="" -DEBIAN_CODENAME="" +export DEBIAN_CODENAME="" main_menu() { # Auto-adjust TUI dimensions for small terminals @@ -106,7 +152,7 @@ main_menu() { ;; 3) _load_module system_prefs || continue - _system_preferences_menu + _system_preferences_menu || true STATE_REFRESHED=true ;; 4) diff --git a/docs/desktops_display.md b/docs/desktops_display.md index ede6bdb..93a7180 100644 --- a/docs/desktops_display.md +++ b/docs/desktops_display.md @@ -2,16 +2,23 @@ ### 1. What does this component do? -This component manages both desktop environments and display managers. Desktop Environment installs XFCE/LXDE with polkit integration; Display Manager handles LightDM, GDM3, SDDM, and greetd (login screens). It handles autologin setup, user list visibility toggles, and a specific Wayland override for NVIDIA GPUs. Desktop Environment management supports **XFCE** (full / minimal / Wayland `labwc` on Trixie / custom) and **LXDE** (full / core), with polkit rules for suspend/backlight. +This component manages both desktop environments and display managers. Desktop Environment installs **GNOME, KDE Plasma, XFCE, or LXDE**; Display Manager handles LightDM, GDM3, SDDM, and greetd (login screens). It handles autologin setup, user list visibility toggles, and a specific Wayland override for NVIDIA GPUs. Desktop Environment management supports **XFCE** (full / minimal / Wayland `labwc` on Trixie / custom), **LXDE** (full / core), **GNOME** (full / core) and **KDE Plasma** (full / minimal), with polkit rules for suspend/backlight where applicable. + +**Modular layout:** the menu, the shared helpers (`_existing_display_manager`, `_offer_pipewire_after_desktop`) and the display-manager configuration routines live in `modules/desktop_display.sh`; every desktop installer lives in its own module under `modules/desktops/` (`desktop_xfce.sh`, `desktop_lxde.sh`, `desktop_gnome.sh`, `desktop_kde.sh`), lazily loaded on demand via `_load_module`. ### 2. Logical Flow of Execution The execution logic is divided into five functional blocks: -**Block 1 — Desktop & Display Menu** +**Block 1 — Desktop Environment Selection & Installation** - The main menu (`manage_desktop_display` in `desktop_display.sh`) offers two primary options: `1 Desktop Environment` and `2 Display Manager` (plus `3 Back to main menu`). -- **Desktop Environment** opens `desktop_environment_menu` — XFCE or LXDE. +- **Desktop Environment** opens `desktop_environment_menu` with four environments: + 1. **XFCE** — full / minimal / Wayland (`labwc`, Debian 13 only) / custom package checklist. + 2. **LXDE** — full / core. + 3. **GNOME** — full (`gnome`) / core (`gnome-core`), with GDM3. + 4. **KDE Plasma** — full (`kde-standard`) / minimal (`kde-plasma-desktop`), with SDDM. +- Each environment lives in its own module under `modules/desktops/` and is lazy-loaded from the menu dispatch with `_load_module xfce|lxde|gnome|kde` (dependencies: `desktop_display` + `audio`). - **Display Manager** opens `display_manager_menu` — LightDM, GDM3, SDDM, and greetd (12/13 only). - **Available Managers:** - LightDM (available on all Debian versions). @@ -68,6 +75,10 @@ The component utilizes several intelligent automation features: - **greetd Backports:** On Bookworm, `tuigreet` is installed from backports (newer version) if not already enabled in the system. - **Manual Config Warning:** The script explicitly informs users that greetd requires manual configuration and points to man pages for further details. - **User Detection:** Uses `SUDO_USER` (the real user who ran sudo) instead of `$USER` (which is root during script execution). This ensures the correct user is set for automatic login across all display managers. +- **Pre-apply Summary:** every desktop installer shows a confirmation list of the packages (`_confirm_install_list`) before any debconf or apt transaction starts; declining aborts safely. +- **Existing DM Preservation:** the GNOME and KDE installers detect the current default display manager (`_existing_display_manager` reads `/etc/X11/default-display-manager`, falling back to an enabled DM systemd unit), preseed that manager instead of the new one, and skip `systemctl enable` so an already-configured login screen is never overwritten. +- **PipeWire Offer:** after any desktop install (XFCE, LXDE, GNOME, KDE), `_offer_pipewire_after_desktop` checks for `pipewire-audio` (`pipewire` on Bullseye) and, when missing, asks once before running `_install_pipewire_standard`. Declining is a choice, not an error: the helper always returns 0. +- **Stale-lists Guard:** `_ensure_apt_updated` runs before the GNOME/KDE `apt install` transactions, refreshing the package lists at most once per session. ### 4. Packages and Resources Managed @@ -84,6 +95,18 @@ The component utilizes several intelligent automation features: | greetd + nwg-hello | greetd, nwg-hello | 13 only | | greetd + wlgreet | greetd, wlgreet | 13 only | +**Desktop Environment Packages:** + +| Environment | Variant | Packages | Display Manager | +| --- | --- | --- | --- | +| XFCE | Full | xfce4, xfce4-goodies, xfce4-power-manager | not managed by the installer | +| XFCE | Minimal | xfce4 | not managed by the installer | +| XFCE | Wayland (13 only) | xfce4, labwc | not managed by the installer | +| XFCE | Custom | user-selected checklist | not managed by the installer | +| LXDE | Full / Core | lxde / lxde-core | lightdm (always) | +| GNOME | Full / Core | gnome / gnome-core | gdm3 (installed; existing DM kept as default) | +| KDE Plasma | Full / Minimal | kde-standard / kde-plasma-desktop | sddm (installed; existing DM kept as default) | + **Configuration Files Modified:** | DM | File Path | Configuration Purpose | diff --git a/docs/quickstart.md b/docs/quickstart.md index c6929af..48fdf48 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -281,7 +281,7 @@ After this, browse the other categories as needed: 3 Back to main menu ``` -- **Desktop Environment** — XFCE (full / minimal / Wayland `labwc` on Trixie / custom checklist) or LXDE (full / core). Installs polkit rules (`85-suspend.rules`, `89-backlight.rules`) + `backlight` group. +- **Desktop Environment** — pick one of **XFCE** (full / minimal / Wayland `labwc` on Trixie / custom checklist), **LXDE** (full / core), **GNOME** (full / core) or **KDE Plasma** (full / minimal). Each environment installs from its own module in `modules/desktops/` (lazy-loaded). XFCE installs polkit rules (`85-suspend.rules`, `89-backlight.rules`) + `backlight` group; GNOME/KDE preseed the display manager while keeping any already-configured one as default. After the install, the script offers the PipeWire audio stack if none is present. - **Display Manager** — LightDM (GTK greeter, user list, autologin), GDM3 (user list, autologin, NVIDIA Wayland override via `61-gdm.rules → /dev/null`), SDDM (autologin with session auto-detection `plasmawayland → lxqt-wayland → plasma → lxqt`), greetd (base / tuigreet / gtkgreet / nwg-hello / wlgreet — manual `/etc/greetd/config.toml` required). > See [Desktop & Display](desktops_display.md). diff --git a/modules/bullseye/extras.sh b/modules/bullseye/extras.sh index 8bd1f97..ad51c16 100644 --- a/modules/bullseye/extras.sh +++ b/modules/bullseye/extras.sh @@ -43,10 +43,10 @@ main menu to install proprietary firmwares." 10 65 fw_ver=$(apt-cache policy firmware-linux-nonfree 2>/dev/null | awk 'NR==3 {print $2; exit}') if _confirm "Firmware" \ - "firmware-linux-nonfree proporciona drivers para:\n\ + "firmware-linux-nonfree provides drivers for:\n\ WiFi, Bluetooth, GPU, audio, webcams, etc.\n\n\ Version: ${fw_ver:-unknown}\n\n\ -Instalar?"; then +Install?"; then _run_cmd "Firmware" "sudo apt install -y firmware-linux-nonfree" \ "Installing firmware-linux-nonfree..." echo -e "${GREEN}Firmware installed.${NC}" @@ -101,9 +101,15 @@ _cat_themes_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Desktop Themes (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do ! is_installed "$pkg" && _run_install "$pkg" || echo "$pkg already installed." done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Desktop themes installed.${NC}" } @@ -133,9 +139,15 @@ _cat_icons_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Icon Themes (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do ! is_installed "$pkg" && _run_install "$pkg" || echo "$pkg already installed." done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Icon themes installed.${NC}" } @@ -160,9 +172,15 @@ _cat_cursors_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Cursor Themes (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do ! is_installed "$pkg" && _run_install "$pkg" || echo "$pkg already installed." done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Cursor themes installed.${NC}" } @@ -186,9 +204,15 @@ _cat_fonts_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Fonts (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do ! is_installed "$pkg" && _run_install "$pkg" || echo "$pkg already installed." done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Fonts installed.${NC}" } @@ -231,6 +255,9 @@ _cat_download_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Downloaders & Torrent Clients (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -238,6 +265,9 @@ _cat_download_bullseye() { echo "$pkg already installed." fi done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Download & network tools installed.${NC}" } @@ -268,6 +298,9 @@ _cat_internet_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Internet (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in w3m) @@ -287,6 +320,9 @@ _cat_internet_bullseye() { ;; esac done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Internet tools installed.${NC}" } @@ -308,6 +344,9 @@ _cat_players_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Media Players (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -315,6 +354,9 @@ _cat_players_bullseye() { echo "$pkg already installed." fi done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Media players installed.${NC}" } @@ -347,6 +389,9 @@ _cat_design_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Multimedia & Design (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -354,6 +399,9 @@ _cat_design_bullseye() { echo "$pkg already installed." fi done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Multimedia & design tools installed.${NC}" } @@ -382,6 +430,9 @@ _cat_programming_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Code Editors & IDEs (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -389,6 +440,9 @@ _cat_programming_bullseye() { echo "$pkg already installed." fi done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Code editors & IDEs installed.${NC}" } @@ -422,6 +476,9 @@ _cat_dev_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Servers & Dev Tools (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in docker) @@ -461,6 +518,9 @@ _cat_dev_bullseye() { ;; esac done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Servers & dev tools installed.${NC}" } _cat_security_bullseye() { @@ -484,6 +544,9 @@ _cat_security_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Security & Networking (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in clamav) @@ -498,6 +561,9 @@ _cat_security_bullseye() { ;; esac done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Security & networking tools installed.${NC}" } @@ -539,6 +605,9 @@ _cat_general_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "System Tools (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in compress) @@ -612,6 +681,9 @@ _cat_general_bullseye() { ;; esac done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}System tools installed.${NC}" _pause } @@ -636,6 +708,9 @@ _cat_fetch_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Fetch Tools (Bullseye)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -643,6 +718,9 @@ _cat_fetch_bullseye() { echo "$pkg already installed." fi done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Fetch tools installed.${NC}" } diff --git a/modules/bullseye/legacy.sh b/modules/bullseye/legacy.sh index 8b6001b..18e814d 100644 --- a/modules/bullseye/legacy.sh +++ b/modules/bullseye/legacy.sh @@ -82,17 +82,17 @@ install_nvidia_bullseye() { msg+="Driver: ${gpu_gen} — ${nv_pkg} ${nv_ver:-unknown}\n" msg+="[+] firmware-misc-nonfree\n" msg+="[+] nvidia-settings\n\n" - msg+="Instalar driver NVIDIA?" + msg+="Install NVIDIA driver?" if ! _confirm "NVIDIA — Bullseye" "$msg" 14 70; then - echo "Omitiendo driver NVIDIA." + echo "Skipping NVIDIA driver." NVIDIA_DRIVER_MODE="" return 0 fi _run_cmd "NVIDIA" \ "sudo apt install -y $nv_pkg firmware-misc-nonfree nvidia-settings" \ - "Instalando driver NVIDIA ${gpu_gen}..." + "Installing NVIDIA driver ${gpu_gen}..." local i386_active=false dpkg --print-foreign-architectures | grep -q i386 && i386_active=true @@ -109,8 +109,8 @@ install_nvidia_bullseye() { fi fi - NVIDIA_DRIVER_MODE="stable" - echo -e "${GREEN}Driver NVIDIA ${nv_pkg} instalado. Requiere reinicio.${NC}" + export NVIDIA_DRIVER_MODE="stable" + echo -e "${GREEN}NVIDIA driver ${nv_pkg} installed. Reboot required.${NC}" echo "" echo "──────────────────────────────────────────────" @@ -193,6 +193,13 @@ install_gaming_bullseye() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _install_pkgs+=("$_pkg") done < <(echo "$install_list" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection before the + # package installation starts. Skipped when only the + # i386 pseudo-entry was selected. + if [ ${#_install_pkgs[@]} -gt 0 ]; then + _confirm_install_list "Gaming Setup — Bullseye" "${_install_pkgs[@]}" || return + fi for pkg in "${_install_pkgs[@]}"; do case $pkg in steam) @@ -212,6 +219,9 @@ install_gaming_bullseye() { esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Gaming setup complete.${NC}" _pause } diff --git a/modules/bullseye/repos.sh b/modules/bullseye/repos.sh index 34b776a..357ac50 100644 --- a/modules/bullseye/repos.sh +++ b/modules/bullseye/repos.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# repos.sh — Bullseye: repos clásicos con archive phase +# repos.sh — Bullseye: classic repos with archive phase # License GPL v3 configure_repos_bullseye() { diff --git a/modules/desktop_display.sh b/modules/desktop_display.sh index 2782730..dde9269 100644 --- a/modules/desktop_display.sh +++ b/modules/desktop_display.sh @@ -27,9 +27,9 @@ desktop_environment_menu() { local -a env_items=() env_items+=("1" "XFCE") env_items+=("2" "LXDE") - env_items+=("3" "Back") - # Future desktop environments: add "3" "GNOME", "4" "KDE", ... here - # and a matching case below calling its own menu (e.g. gnome_menu). + env_items+=("3" "GNOME") + env_items+=("4" "KDE Plasma") + env_items+=("5" "Back") local choice choice=$(_menu "Desktop Environment" \ "Select a desktop environment:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ @@ -37,155 +37,27 @@ desktop_environment_menu() { [ -z "$choice" ] && break clear case "$choice" in - 1) xfce_menu ;; - 2) lxde_menu ;; - 3) break ;; - esac - done -} - -lxde_menu() { - local choice - choice=$(_radiolist "LXDE" \ - "Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ - "1" "LXDE (Escritorio completo)" OFF \ - "2" "LXDE Core (Instalación mínima)" OFF) - [ -z "$choice" ] && return 0 - clear - case "$(echo "$choice" | tr -d '"')" in - 1) _install_lxde_full ;; - 2) _install_lxde_core ;; - esac -} - -_install_lxde_full() { - echo -e "${GREEN}Installing LXDE (Full) + LightDM...${NC}" - echo "lightdm shared/default-x-display-manager select lightdm" | sudo debconf-set-selections - _run_cmd "LXDE Full" "sudo apt install -y lxde lightdm" \ - "Installing LXDE (Full Meta Package) + LightDM..." - sudo systemctl enable lightdm - echo -e "${GREEN}LXDE installed. LightDM enabled.${NC}" -} - -_install_lxde_core() { - echo -e "${GREEN}Installing LXDE Core + LightDM...${NC}" - echo "lightdm shared/default-x-display-manager select lightdm" | sudo debconf-set-selections - _run_cmd "LXDE Core" "sudo apt install -y lxde-core lightdm" \ - "Installing LXDE Core (Minimal) + LightDM..." - sudo systemctl enable lightdm - echo -e "${GREEN}LXDE Core installed. LightDM enabled.${NC}" -} - -xfce_menu() { - while true; do - local -a xf_items=() - xf_items+=("1" "XFCE (Full Meta Package)") - xf_items+=("2" "XFCE (Minimal Core)") - [ "$DEBIAN_VERSION" = "13" ] && xf_items+=("3" "XFCE Wayland (Experimental — labwc)") - xf_items+=("4" "XFCE Custom (Choose packages)") - xf_items+=("5" "Back") - local choice - choice=$(_menu "XFCE" \ - "Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ - "${xf_items[@]}") - [ -z "$choice" ] && break - clear - case "$choice" in - 1) _install_xfce_full ;; - 2) _install_xfce_minimal ;; - 3) _install_xfce_wayland ;; - 4) _install_xfce_custom ;; + 1) + _load_module xfce || continue + xfce_menu + ;; + 2) + _load_module lxde || continue + lxde_menu + ;; + 3) + _load_module gnome || continue + gnome_menu + ;; + 4) + _load_module kde || continue + kde_menu + ;; 5) break ;; esac done } -_install_xfce_full() { - _run_cmd "XFCE Full" "sudo apt install -y xfce4 xfce4-goodies xfce4-power-manager" \ - "Installing XFCE (Full Meta Package)..." - _xfce_polkit_rules -} - -_install_xfce_minimal() { - _run_cmd "XFCE Minimal" "sudo apt install -y xfce4" \ - "Installing XFCE (Minimal Core)..." - _xfce_polkit_rules -} - -_install_xfce_wayland() { - _run_cmd "XFCE Wayland" "sudo apt install -y xfce4 labwc" \ - "Installing XFCE + labwc (Wayland, experimental)..." - _msg "XFCE Wayland" "labwc is EXPERIMENTAL on XFCE 4.20.\n\nAfter reboot, select the Wayland session\nfrom the login screen." 12 65 - _xfce_polkit_rules -} - -_install_xfce_custom() { - local -a items=() - for pkg in thunar xfdesktop4 xfwm4 xfce4-panel xfce4-terminal \ - xfce4-screenshooter ristretto mousepad xfce4-session \ - xfce4-settings xfce4-power-manager; do - items+=("$pkg" "$pkg" "$(_state "$pkg")") - done - local choices - choices=$(_checklist "XFCE Custom" \ - "Select the XFCE packages to install:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ - "${items[@]}") - [ -z "$choices" ] && return - local cleaned - cleaned=$(echo "$choices" | tr -d '"') - [ -z "$cleaned" ] && return - - # BH-004: Convert to array to avoid word splitting and injection. - local -a xfce_pkgs=() - while IFS= read -r _pkg; do - [ -n "$_pkg" ] && xfce_pkgs+=("$_pkg") - done < <(echo "$cleaned" | tr ' ' '\n') - - _run_cmd "XFCE Custom" "sudo apt install -y ${xfce_pkgs[*]}" \ - "Installing selected XFCE packages..." - _xfce_polkit_rules -} - -_xfce_polkit_rules() { - is_installed xfce4-power-manager || return 0 - - local rules_dir="/etc/polkit-1/rules.d" - sudo mkdir -p "$rules_dir" - - cat <<'EOF' | sudo tee "$rules_dir/85-suspend.rules" >/dev/null -polkit.addRule(function(action, subject) { - if (action.id == "org.freedesktop.login1.suspend" && - subject.isInGroup("users")) { - return polkit.Result.YES; - } -}); -EOF - cat <<'EOF' | sudo tee "$rules_dir/89-backlight.rules" >/dev/null -polkit.addRule(function(action, subject) { - if (action.id == "org.freedesktop.upower.backlight" && - subject.isInGroup("backlight")) { - return polkit.Result.YES; - } -}); -EOF - - if ! getent group backlight >/dev/null 2>&1; then - sudo groupadd --system backlight || true - fi - # SECURITY: Validate that the target user is a real login user, not root. - # If SUDO_USER is empty (script run directly as root), fall back to the - # first non-system user from /etc/passwd, never to root. - local de_user="${SUDO_USER:-}" - if [ -z "$de_user" ] || [ "$de_user" = "root" ]; then - de_user=$(awk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd | head -1) - fi - if [ -n "$de_user" ] && ! id -nG "$de_user" 2>/dev/null | grep -qw backlight; then - sudo usermod -aG backlight "$de_user" || true - fi - sudo systemctl restart polkit.service 2>/dev/null || true - echo -e "${GREEN}Polkit rules installed (suspend + backlight). User '${de_user}' added to 'backlight' group.${NC}" -} - display_manager_menu() { while true; do local -a dm_items=() @@ -476,3 +348,50 @@ EOF esac done } + +# ── SP2 shared helpers ──────────────────────────── + +# Print the display manager already configured as the +# X11 default (/etc/X11/default-display-manager), or an +# enabled DM systemd unit; print nothing when none is +# found. Desktop installers use this to avoid overwriting +# an existing display manager setup. +_existing_display_manager() { + local dm="" + local dm_path="" + # Config path is overridable for tests (same pattern as + # APT_DIR): the suite must never read the real system file. + local dm_file="${DM_FILE:-/etc/X11/default-display-manager}" + if [ -s "$dm_file" ]; then + dm_path=$(cat "$dm_file" 2>/dev/null) || dm_path="" + [ -n "$dm_path" ] && dm=$(basename "$dm_path") + fi + if [ -z "$dm" ]; then + local cand + for cand in gdm3 lightdm sddm greetd gdm; do + if systemctl is-enabled "$cand" 2>/dev/null | grep -q '^enabled$'; then + dm="$cand" + break + fi + done + fi + printf '%s\n' "$dm" +} + +# Offer the standard PipeWire audio stack right after a +# desktop install, when no PipeWire server is present +# yet. _install_pipewire_standard gates itself internally, +# so no pre-prompt is needed here. Declining is a choice, +# not an error: always ends in return 0 so it is safe as +# the last call of an installer. +_offer_pipewire_after_desktop() { + local pkg_check="pipewire-audio" + [ "$DEBIAN_VERSION" = "11" ] && pkg_check="pipewire" + is_installed "$pkg_check" && return 0 + + if _confirm "PipeWire Audio" \ + "No PipeWire audio server is installed.\n\nInstall the PipeWire audio stack (recommended)?"; then + _install_pipewire_standard + fi + return 0 +} diff --git a/modules/desktops/desktop_gnome.sh b/modules/desktops/desktop_gnome.sh new file mode 100644 index 0000000..af17b34 --- /dev/null +++ b/modules/desktops/desktop_gnome.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# desktop_gnome.sh — GNOME desktop installer (SP2) +# Full: gnome + gdm3 | Core: gnome-core + gdm3 +# Debian ships GNOME 48 (13), 43 (12) and 3.38 (11). +# License GPL v3 + +gnome_menu() { + local choice + choice=$(_radiolist "GNOME" \ + "Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ + "1" "GNOME (Full Desktop)" OFF \ + "2" "GNOME Core (Minimal Install)" OFF \ + "3" "Back" OFF) + [ -z "$choice" ] && return 0 + clear + case "$(echo "$choice" | tr -d '"')" in + 1) _install_gnome_full ;; + 2) _install_gnome_core ;; + 3) return 0 ;; + esac +} + +_install_gnome_full() { + # Pre-apply summary: confirm the selection before any + # debconf or apt transaction starts. + _confirm_install_list "GNOME Full Desktop" gnome gdm3 || return + + echo -e "${GREEN}Installing GNOME (Full Desktop)...${NC}" + + # Keep an already-configured display manager intact. + local target_dm="gdm3" + local keep_dm + keep_dm=$(_existing_display_manager) + if [ -n "$keep_dm" ] && [ "$keep_dm" != "gdm3" ]; then + echo -e "${YELLOW}Display manager '${keep_dm}' is already configured — keeping it as default.${NC}" + target_dm="$keep_dm" + fi + echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections + _ensure_apt_updated + _run_cmd "GNOME Full" "sudo apt install -y gnome gdm3" \ + "Installing GNOME (Full Meta Package) + GDM3..." + if [ "$target_dm" = "gdm3" ]; then + sudo systemctl enable gdm3 + fi + # Selection confirmed once above; release the + # per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}GNOME installed. Default display manager: ${target_dm}.${NC}" + _offer_pipewire_after_desktop +} + +_install_gnome_core() { + # Pre-apply summary: confirm the selection before any + # debconf or apt transaction starts. + _confirm_install_list "GNOME Core" gnome-core gdm3 || return + + echo -e "${GREEN}Installing GNOME Core (Minimal)...${NC}" + + # Keep an already-configured display manager intact. + local target_dm="gdm3" + local keep_dm + keep_dm=$(_existing_display_manager) + if [ -n "$keep_dm" ] && [ "$keep_dm" != "gdm3" ]; then + echo -e "${YELLOW}Display manager '${keep_dm}' is already configured — keeping it as default.${NC}" + target_dm="$keep_dm" + fi + echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections + _ensure_apt_updated + _run_cmd "GNOME Core" "sudo apt install -y gnome-core gdm3" \ + "Installing GNOME Core (Minimal) + GDM3..." + if [ "$target_dm" = "gdm3" ]; then + sudo systemctl enable gdm3 + fi + # Selection confirmed once above; release the + # per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}GNOME Core installed. Default display manager: ${target_dm}.${NC}" + _offer_pipewire_after_desktop +} diff --git a/modules/desktops/desktop_kde.sh b/modules/desktops/desktop_kde.sh new file mode 100644 index 0000000..1976cc1 --- /dev/null +++ b/modules/desktops/desktop_kde.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# desktop_kde.sh — KDE Plasma desktop installer (SP2) +# Full: kde-standard + sddm | Minimal: kde-plasma-desktop + sddm +# Bookworm ships Plasma 5.27.5; Wayland is the default +# session since Debian 12. +# License GPL v3 + +kde_menu() { + local choice + choice=$(_radiolist "KDE Plasma" \ + "Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ + "1" "KDE Plasma (Full Desktop)" OFF \ + "2" "KDE Plasma Desktop (Minimal)" OFF \ + "3" "Back" OFF) + [ -z "$choice" ] && return 0 + clear + case "$(echo "$choice" | tr -d '"')" in + 1) _install_kde_full ;; + 2) _install_kde_minimal ;; + 3) return 0 ;; + esac +} + +_install_kde_full() { + # Pre-apply summary: confirm the selection before any + # debconf or apt transaction starts. + _confirm_install_list "KDE Plasma Full" kde-standard sddm || return + + echo -e "${GREEN}Installing KDE Plasma (Full)...${NC}" + + # Keep an already-configured display manager intact. + local target_dm="sddm" + local keep_dm + keep_dm=$(_existing_display_manager) + if [ -n "$keep_dm" ] && [ "$keep_dm" != "sddm" ]; then + echo -e "${YELLOW}Display manager '${keep_dm}' is already configured — keeping it as default.${NC}" + target_dm="$keep_dm" + fi + echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections + _ensure_apt_updated + _run_cmd "KDE Full" "sudo apt install -y kde-standard sddm" \ + "Installing KDE Plasma (Full Meta Package) + SDDM..." + if [ "$target_dm" = "sddm" ]; then + sudo systemctl enable sddm + fi + # Selection confirmed once above; release the + # per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}KDE Plasma installed. Default display manager: ${target_dm}.${NC}" + _offer_pipewire_after_desktop +} + +_install_kde_minimal() { + # Pre-apply summary: confirm the selection before any + # debconf or apt transaction starts. + _confirm_install_list "KDE Plasma Minimal" kde-plasma-desktop sddm || return + + echo -e "${GREEN}Installing KDE Plasma Desktop (Minimal)...${NC}" + + # Keep an already-configured display manager intact. + local target_dm="sddm" + local keep_dm + keep_dm=$(_existing_display_manager) + if [ -n "$keep_dm" ] && [ "$keep_dm" != "sddm" ]; then + echo -e "${YELLOW}Display manager '${keep_dm}' is already configured — keeping it as default.${NC}" + target_dm="$keep_dm" + fi + echo "${target_dm} shared/default-x-display-manager select ${target_dm}" | sudo debconf-set-selections + _ensure_apt_updated + _run_cmd "KDE Minimal" "sudo apt install -y kde-plasma-desktop sddm" \ + "Installing KDE Plasma Desktop (Minimal) + SDDM..." + if [ "$target_dm" = "sddm" ]; then + sudo systemctl enable sddm + fi + # Selection confirmed once above; release the + # per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}KDE Plasma Desktop installed. Default display manager: ${target_dm}.${NC}" + _offer_pipewire_after_desktop +} diff --git a/modules/desktops/desktop_lxde.sh b/modules/desktops/desktop_lxde.sh new file mode 100644 index 0000000..b57dff8 --- /dev/null +++ b/modules/desktops/desktop_lxde.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# desktop_lxde.sh — LXDE desktop installer (extracted from +# desktop_display.sh for architectural symmetry with +# desktop_gnome.sh and desktop_kde.sh). +# License GPL v3 + +lxde_menu() { + local choice + choice=$(_radiolist "LXDE" \ + "Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ + "1" "LXDE (Full Desktop)" OFF \ + "2" "LXDE Core (Minimal Install)" OFF) + [ -z "$choice" ] && return 0 + clear + case "$(echo "$choice" | tr -d '"')" in + 1) _install_lxde_full ;; + 2) _install_lxde_core ;; + esac +} + +_install_lxde_full() { + echo -e "${GREEN}Installing LXDE (Full) + LightDM...${NC}" + echo "lightdm shared/default-x-display-manager select lightdm" | sudo debconf-set-selections + _run_cmd "LXDE Full" "sudo apt install -y lxde lightdm" \ + "Installing LXDE (Full Meta Package) + LightDM..." + sudo systemctl enable lightdm + echo -e "${GREEN}LXDE installed. LightDM enabled.${NC}" + _offer_pipewire_after_desktop +} + +_install_lxde_core() { + echo -e "${GREEN}Installing LXDE Core + LightDM...${NC}" + echo "lightdm shared/default-x-display-manager select lightdm" | sudo debconf-set-selections + _run_cmd "LXDE Core" "sudo apt install -y lxde-core lightdm" \ + "Installing LXDE Core (Minimal) + LightDM..." + sudo systemctl enable lightdm + echo -e "${GREEN}LXDE Core installed. LightDM enabled.${NC}" + _offer_pipewire_after_desktop +} diff --git a/modules/desktops/desktop_xfce.sh b/modules/desktops/desktop_xfce.sh new file mode 100644 index 0000000..2e4f46d --- /dev/null +++ b/modules/desktops/desktop_xfce.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# desktop_xfce.sh — XFCE desktop installer (extracted from +# desktop_display.sh for architectural symmetry with +# desktop_gnome.sh and desktop_kde.sh). +# License GPL v3 + +xfce_menu() { + while true; do + local -a xf_items=() + xf_items+=("1" "XFCE (Full Meta Package)") + xf_items+=("2" "XFCE (Minimal Core)") + [ "$DEBIAN_VERSION" = "13" ] && xf_items+=("3" "XFCE Wayland (Experimental — labwc)") + xf_items+=("4" "XFCE Custom (Choose packages)") + xf_items+=("5" "Back") + local choice + choice=$(_menu "XFCE" \ + "Select an option:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ + "${xf_items[@]}") + [ -z "$choice" ] && break + clear + case "$choice" in + 1) _install_xfce_full ;; + 2) _install_xfce_minimal ;; + 3) _install_xfce_wayland ;; + 4) _install_xfce_custom ;; + 5) break ;; + esac + done +} + +_install_xfce_full() { + _run_cmd "XFCE Full" "sudo apt install -y xfce4 xfce4-goodies xfce4-power-manager" \ + "Installing XFCE (Full Meta Package)..." + _xfce_polkit_rules + _offer_pipewire_after_desktop +} + +_install_xfce_minimal() { + _run_cmd "XFCE Minimal" "sudo apt install -y xfce4" \ + "Installing XFCE (Minimal Core)..." + _xfce_polkit_rules + _offer_pipewire_after_desktop +} + +_install_xfce_wayland() { + _run_cmd "XFCE Wayland" "sudo apt install -y xfce4 labwc" \ + "Installing XFCE + labwc (Wayland, experimental)..." + _msg "XFCE Wayland" "labwc is EXPERIMENTAL on XFCE 4.20.\n\nAfter reboot, select the Wayland session\nfrom the login screen." 12 65 + _xfce_polkit_rules + _offer_pipewire_after_desktop +} + +_install_xfce_custom() { + local -a items=() + for pkg in thunar xfdesktop4 xfwm4 xfce4-panel xfce4-terminal \ + xfce4-screenshooter ristretto mousepad xfce4-session \ + xfce4-settings xfce4-power-manager; do + items+=("$pkg" "$pkg" "$(_state "$pkg")") + done + local choices + choices=$(_checklist "XFCE Custom" \ + "Select the XFCE packages to install:" $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ + "${items[@]}") + [ -z "$choices" ] && return + local cleaned + cleaned=$(echo "$choices" | tr -d '"') + [ -z "$cleaned" ] && return + + # BH-004: Convert to array to avoid word splitting and injection. + local -a xfce_pkgs=() + while IFS= read -r _pkg; do + [ -n "$_pkg" ] && xfce_pkgs+=("$_pkg") + done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection before + # the batch apt transaction starts. + _confirm_install_list "XFCE Custom" "${xfce_pkgs[@]}" || return + + _run_cmd "XFCE Custom" "sudo apt install -y ${xfce_pkgs[*]}" \ + "Installing selected XFCE packages..." + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + _xfce_polkit_rules + _offer_pipewire_after_desktop +} + +_xfce_polkit_rules() { + is_installed xfce4-power-manager || return 0 + + local rules_dir="/etc/polkit-1/rules.d" + sudo mkdir -p "$rules_dir" + + cat <<'EOF' | sudo tee "$rules_dir/85-suspend.rules" >/dev/null +polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.login1.suspend" && + subject.isInGroup("users")) { + return polkit.Result.YES; + } +}); +EOF + cat <<'EOF' | sudo tee "$rules_dir/89-backlight.rules" >/dev/null +polkit.addRule(function(action, subject) { + if (action.id == "org.freedesktop.upower.backlight" && + subject.isInGroup("backlight")) { + return polkit.Result.YES; + } +}); +EOF + + if ! getent group backlight >/dev/null 2>&1; then + sudo groupadd --system backlight || true + fi + # SECURITY: Validate that the target user is a real login user, not root. + # If SUDO_USER is empty (script run directly as root), fall back to the + # first non-system user from /etc/passwd, never to root. + local de_user="${SUDO_USER:-}" + if [ -z "$de_user" ] || [ "$de_user" = "root" ]; then + de_user=$(awk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd | head -1) + fi + if [ -n "$de_user" ] && ! id -nG "$de_user" 2>/dev/null | grep -qw backlight; then + sudo usermod -aG backlight "$de_user" || true + fi + sudo systemctl restart polkit.service 2>/dev/null || true + echo -e "${GREEN}Polkit rules installed (suspend + backlight). User '${de_user}' added to 'backlight' group.${NC}" +} diff --git a/modules/extras.sh b/modules/extras.sh index b6707b9..e3287cf 100644 --- a/modules/extras.sh +++ b/modules/extras.sh @@ -9,6 +9,9 @@ _load_extras() { shopt -s nullglob for _mod in "${_EXTRAS_DIR}"/*/*.sh "${_EXTRAS_DIR}"/*/*/*.sh; do [ -f "$_mod" ] || continue + # Dynamic module discovery (glob): the concrete paths + # cannot be resolved statically. + # shellcheck disable=SC1090 source "$_mod" done shopt -u nullglob diff --git a/modules/extras/communication/communication.sh b/modules/extras/communication/communication.sh index 539a267..cf0fc69 100644 --- a/modules/extras/communication/communication.sh +++ b/modules/extras/communication/communication.sh @@ -77,6 +77,10 @@ _cat_communication() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Communication" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in signal-desktop) _install_signal ;; @@ -91,5 +95,8 @@ _cat_communication() { ;; esac done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Communication tools installed.${NC}" } diff --git a/modules/extras/design/design.sh b/modules/extras/design/design.sh index 60167a7..916a32c 100644 --- a/modules/extras/design/design.sh +++ b/modules/extras/design/design.sh @@ -50,6 +50,10 @@ _cat_design() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Multimedia & Design" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -58,5 +62,8 @@ _cat_design() { fi done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Multimedia & design tools installed.${NC}" } diff --git a/modules/extras/dev/dev.sh b/modules/extras/dev/dev.sh index 84662df..a34cf8c 100644 --- a/modules/extras/dev/dev.sh +++ b/modules/extras/dev/dev.sh @@ -2,8 +2,6 @@ # dev.sh — Development & Servers (extrepo, zenmap, fail2ban, ufw moved out) _cat_dev() { - local headless=false - _is_headless && headless=true local -a items=() local apache_state; apache_state=$(_state "apache2") local build_state; build_state=$(_state "build-essential") @@ -55,6 +53,10 @@ _cat_dev() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Development & Servers" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in python3-pip) @@ -94,5 +96,8 @@ _cat_dev() { esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Development tools and servers installed.${NC}" } diff --git a/modules/extras/download/download.sh b/modules/extras/download/download.sh index bf851b9..592f83a 100644 --- a/modules/extras/download/download.sh +++ b/modules/extras/download/download.sh @@ -67,6 +67,10 @@ _cat_download() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Downloaders & Torrent Clients" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in yt-dlp) @@ -88,5 +92,8 @@ _cat_download() { esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Download & network tools installed.${NC}" } diff --git a/modules/extras/essential/essential.sh b/modules/extras/essential/essential.sh index 1b3255c..ae1d4b7 100644 --- a/modules/extras/essential/essential.sh +++ b/modules/extras/essential/essential.sh @@ -9,11 +9,11 @@ _quick_install() { fetch_pkg="neofetch" fi - local comp_pkgs + local -a comp_pkgs if [ "$DEBIAN_CODENAME" = "trixie" ]; then - comp_pkgs="7zip 7zip-rar" + comp_pkgs=(7zip 7zip-rar) else - comp_pkgs="p7zip-full p7zip-rar" + comp_pkgs=(p7zip-full p7zip-rar) fi _msg "Essential Pack" \ @@ -21,7 +21,7 @@ _quick_install() { local quick_pkgs=( zip unzip rar unrar - $comp_pkgs + "${comp_pkgs[@]}" "$fetch_pkg" htop inxi curl wget ufw ca-certificates gnupg lsb-release ) diff --git a/modules/extras/fetch/fetch.sh b/modules/extras/fetch/fetch.sh index 7435365..23b16c9 100644 --- a/modules/extras/fetch/fetch.sh +++ b/modules/extras/fetch/fetch.sh @@ -52,6 +52,10 @@ _cat_fetch() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Fetch Tools" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in neofetch|fastfetch) @@ -71,5 +75,8 @@ _cat_fetch() { esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Fetch tools installed.${NC}" } diff --git a/modules/extras/internet/internet.sh b/modules/extras/internet/internet.sh index 2589b95..615e732 100644 --- a/modules/extras/internet/internet.sh +++ b/modules/extras/internet/internet.sh @@ -234,6 +234,10 @@ _cat_internet() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Internet" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in firefox) @@ -298,6 +302,9 @@ _cat_internet() { esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Internet tools installed.${NC}" } diff --git a/modules/extras/java.sh b/modules/extras/java.sh index b54d442..a863cec 100644 --- a/modules/extras/java.sh +++ b/modules/extras/java.sh @@ -33,10 +33,17 @@ install_minecraft_java() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Java Runtimes for Minecraft" "${_pkgs[@]}" || return for ver in "${_pkgs[@]}"; do _run_cmd "Java" "sudo apt install -y temurin-${ver}-jre" \ "Installing Temurin JRE ${ver}..." done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 } _install_dev_java() { diff --git a/modules/extras/office/office.sh b/modules/extras/office/office.sh index e1721db..ce21ad4 100644 --- a/modules/extras/office/office.sh +++ b/modules/extras/office/office.sh @@ -111,6 +111,10 @@ _cat_office() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Office & Productivity" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in onlyoffice) install_onlyoffice ;; @@ -118,4 +122,7 @@ _cat_office() { joplin) install_joplin ;; esac done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 } diff --git a/modules/extras/players/players.sh b/modules/extras/players/players.sh index 5c47d6d..34602f1 100644 --- a/modules/extras/players/players.sh +++ b/modules/extras/players/players.sh @@ -31,6 +31,10 @@ _cat_players() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Media Players" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -39,5 +43,8 @@ _cat_players() { fi done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Media players installed.${NC}" } diff --git a/modules/extras/programming/programming.sh b/modules/extras/programming/programming.sh index c625c86..34f082c 100644 --- a/modules/extras/programming/programming.sh +++ b/modules/extras/programming/programming.sh @@ -69,6 +69,10 @@ _cat_programming() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Programming Applications" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in vscodium) @@ -84,6 +88,9 @@ _cat_programming() { esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Programming applications installed.${NC}" } diff --git a/modules/extras/security/security.sh b/modules/extras/security/security.sh index 8aa101c..e137064 100644 --- a/modules/extras/security/security.sh +++ b/modules/extras/security/security.sh @@ -41,6 +41,10 @@ _cat_security() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Security & Networking" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in zenmap) @@ -59,5 +63,8 @@ _cat_security() { esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Security & networking tools installed.${NC}" } diff --git a/modules/extras/system/software_centers.sh b/modules/extras/system/software_centers.sh index c851cc4..6903531 100644 --- a/modules/extras/system/software_centers.sh +++ b/modules/extras/system/software_centers.sh @@ -1,3 +1,4 @@ +#!/usr/bin/env bash # software_centers.sh — Standalone Software Center + Flatpak installer _cat_software_centers() { @@ -39,6 +40,10 @@ _cat_software_centers() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Software Center & Flatpak" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in gnome-software) @@ -101,5 +106,8 @@ and may look visually inconsistent with your current desktop environment." esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Software center & Flatpak setup complete.${NC}" } diff --git a/modules/extras/system/system.sh b/modules/extras/system/system.sh index 026f4c8..d1ac125 100644 --- a/modules/extras/system/system.sh +++ b/modules/extras/system/system.sh @@ -145,6 +145,10 @@ _cat_general() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "System Tools" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in compress) @@ -278,6 +282,9 @@ _cat_general() { esac done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}System tools installed.${NC}" _pause } diff --git a/modules/extras/terminals/terminals.sh b/modules/extras/terminals/terminals.sh index 89cff9b..b971eeb 100644 --- a/modules/extras/terminals/terminals.sh +++ b/modules/extras/terminals/terminals.sh @@ -38,6 +38,10 @@ _cat_terminals() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$choices" | tr -d '"' | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Terminals" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -45,5 +49,8 @@ _cat_terminals() { echo "$pkg already installed." fi done + + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 echo -e "${GREEN}Terminals installed.${NC}" } diff --git a/modules/extras/themes/cursors/cursors.sh b/modules/extras/themes/cursors/cursors.sh index b486d5a..32c14d2 100644 --- a/modules/extras/themes/cursors/cursors.sh +++ b/modules/extras/themes/cursors/cursors.sh @@ -39,6 +39,10 @@ _cat_cursors() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Cursor Themes" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -47,5 +51,8 @@ _cat_cursors() { fi done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Cursor themes installed.${NC}" } diff --git a/modules/extras/themes/desktop-themes/desktop-themes.sh b/modules/extras/themes/desktop-themes/desktop-themes.sh index 7a47eb7..0b44e95 100644 --- a/modules/extras/themes/desktop-themes/desktop-themes.sh +++ b/modules/extras/themes/desktop-themes/desktop-themes.sh @@ -41,6 +41,10 @@ _cat_themes() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Desktop Themes (GTK/KDE)" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -49,5 +53,8 @@ _cat_themes() { fi done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Desktop themes installed.${NC}" } diff --git a/modules/extras/themes/fonts/fonts.sh b/modules/extras/themes/fonts/fonts.sh index 4ca38a3..075768f 100644 --- a/modules/extras/themes/fonts/fonts.sh +++ b/modules/extras/themes/fonts/fonts.sh @@ -43,6 +43,10 @@ _cat_fonts() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Fonts" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -51,5 +55,8 @@ _cat_fonts() { fi done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Fonts installed.${NC}" } diff --git a/modules/extras/themes/icons/icons.sh b/modules/extras/themes/icons/icons.sh index f7ef6fb..960a5bc 100644 --- a/modules/extras/themes/icons/icons.sh +++ b/modules/extras/themes/icons/icons.sh @@ -58,6 +58,10 @@ _cat_icons() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Icon Themes" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do if ! is_installed "$pkg"; then _run_install "$pkg" @@ -66,5 +70,8 @@ _cat_icons() { fi done + # Release the per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Icon themes installed.${NC}" } diff --git a/modules/firmware.sh b/modules/firmware.sh index ca27121..b32e058 100644 --- a/modules/firmware.sh +++ b/modules/firmware.sh @@ -158,10 +158,9 @@ _handle_wireless() { fi for dev in "${PCI_NET_DEVS[@]}"; do - local bcm_id dev_id + local bcm_id bcm_id=$(echo "$dev" | grep -oP '14e4:[0-9a-fA-F]+' || true) [ -z "$bcm_id" ] && continue - dev_id=$(echo "$bcm_id" | cut -d: -f2 | tr '[:upper:]' '[:lower:]') # --- Dependencies verification --- if ! is_installed "linux-headers-amd64" || ! is_installed "dkms"; then @@ -208,11 +207,11 @@ _handle_wireless() { fi # --- Post-DKMS verification (Fix 5, unchanged) --- - if ! ls /lib/modules/$(uname -r)/updates/dkms/wl.ko* 2>/dev/null | grep -q .; then + 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 ls /lib/modules/$(uname -r)/updates/dkms/wl.ko* 2>/dev/null | grep -q .; then + if compgen -G "/lib/modules/$(uname -r)/updates/dkms/wl.ko*" >/dev/null; then : else local dmesg_out @@ -232,7 +231,7 @@ _handle_wireless() { sudo modprobe -r b43 b43legacy b44 bcma brcmsmac brcmfmac ssb wl 2>/dev/null || true sudo modprobe wl 2>/dev/null || true - # --- Verificación de carga --- + # --- Load verification --- if lsmod | grep -q '^wl '; then echo -e "${GREEN}[+] Broadcom WiFi activated (wl module loaded).${NC}" _pause diff --git a/modules/gaming.sh b/modules/gaming.sh index 731585b..ecfad57 100644 --- a/modules/gaming.sh +++ b/modules/gaming.sh @@ -119,6 +119,14 @@ install_gaming() { [ -n "$_pkg" ] && install_list+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + # Pre-apply summary: confirm the selection before any + # system mutation (i386 architecture, apt update, + # package installation). Skipped when only the i386 + # pseudo-entry was selected. + if [ ${#install_list[@]} -gt 0 ]; then + _confirm_install_list "Gaming Setup" "${install_list[@]}" || return + fi + # 3. Enable i386 architecture if needed if $need_32bit && ! dpkg --print-foreign-architectures 2>/dev/null | grep -q i386; then echo -e "${YELLOW}Enabling i386 architecture (required by selection)...${NC}" @@ -164,6 +172,10 @@ install_gaming() { esac done + # Selection confirmed once above; release the + # per-package prompt suppression. + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Gaming setup complete.${NC}" _pause } diff --git a/modules/gpu.sh b/modules/gpu.sh index d5cc126..2d21eab 100644 --- a/modules/gpu.sh +++ b/modules/gpu.sh @@ -12,9 +12,9 @@ NVIDIA_DRIVER_MODE="" # Set by _show_nvidia_version_menu(): "470" | "535" | "550" | "590" | "595" | "auto" NVIDIA_SELECTED_VERSION="" -# Aviso informativo post-instalación NVIDIA: Debian bug #1109409 -# (GDM3 + NVIDIA + Wayland → pantalla negra). Solo informa, nunca -# cambia configuración de GDM3 ni aborta la instalación. +# Informational post-install notice: NVIDIA Debian bug #1109409 +# (GDM3 + NVIDIA + Wayland → black screen). Only informs, never +# changes GDM3 configuration nor aborts installation. _warn_nvidia_gnome_wayland() { command -v gdm3 &>/dev/null || return 0 gnome-shell --version 2>/dev/null | grep -qi "shell 4[0-9]" || return 0 @@ -150,7 +150,8 @@ _install_amd_intel_stack() { _apply_amd_gcn_grub_fix() { local file="/etc/default/grub" - local backup="${file}.backup.gcn.$(date +%Y%m%d_%H%M%S)" + local backup + backup="${file}.backup.gcn.$(date +%Y%m%d_%H%M%S)" local params="radeon.si_support=0 radeon.cik_support=0 amdgpu.si_support=1 amdgpu.cik_support=1" if grep -q "amdgpu.si_support=1" "$file" 2>/dev/null; then diff --git a/modules/gpu/_helpers.sh b/modules/gpu/_helpers.sh index e137f04..c20dac3 100644 --- a/modules/gpu/_helpers.sh +++ b/modules/gpu/_helpers.sh @@ -2,12 +2,12 @@ # Shared helpers for GPU submodules declare -A NVIDIA_FAMILY_MAP=( - # Fermi / Kepler (Legacy: el shim se encarga de separarlos) + # Fermi / Kepler (Legacy: the shim separates them) ["06"]="legacy" ["0D"]="legacy" ["0E"]="legacy" ["0F"]="legacy" ["10"]="legacy" ["11"]="legacy" ["12"]="legacy" # Maxwell ["13"]="maxwell" ["14"]="maxwell" ["16"]="maxwell" ["17"]="maxwell" - # Pascal + Volta (Misma política de driver clásico) + # Pascal + Volta (same classic driver policy) ["15"]="pascal" ["1B"]="pascal" ["1C"]="pascal" ["1D"]="pascal" # Turing ["1E"]="turing" ["1F"]="turing" ["21"]="turing" @@ -43,8 +43,8 @@ _is_nvidia_kepler_id() { return 1 } -# Normaliza la familia de arquitectura NVIDIA desambiguando Kepler/Fermi -# (detect_nvidia_arch los agrupa como "legacy" vía rango de device IDs) +# Normalizes the NVIDIA architecture family, disambiguating Kepler/Fermi +# (detect_nvidia_arch groups them as "legacy" via device-ID range) _get_nvidia_arch_family() { local fam fam=$(detect_nvidia_arch "$NVIDIA_GPU_DEVICE_ID") @@ -109,8 +109,8 @@ _install_mesa_backports() { fi done - # Mesa >= 25.3.3 unificó VA-API dentro de mesa-libgallium. - # mesa-va-drivers desde backports rompe con la nueva mesa-libgallium. + # Mesa >= 25.3.3 unified VA-API into mesa-libgallium. + # mesa-va-drivers from backports breaks with the new mesa-libgallium. local _bpo_filtered=() for _p in "${bpo_pkgs[@]}"; do [ "$_p" != "mesa-va-drivers" ] && _bpo_filtered+=("$_p") @@ -189,8 +189,8 @@ _is_hybrid_laptop() { gpu_count=$(echo "$LSPCI_OUTPUT" | grep -ciE "VGA compatible|3D controller") || true [ "$gpu_count" -lt 2 ] && return 1 - # Defensa en profundidad: si TODAS las GPUs son NVIDIA (SLI o - # dual-GPU desktop), no hay iGPU Intel/AMD → no es híbrida. + # Defense in depth: if ALL GPUs are NVIDIA (SLI or dual-GPU + # desktop), there is no Intel/AMD iGPU → not hybrid. nvidia_count=$(echo "$LSPCI_OUTPUT" | grep -iE "VGA compatible|3D controller" | grep -c "10de:") || true [ "$nvidia_count" -ge "$gpu_count" ] && return 1 @@ -199,6 +199,6 @@ _is_hybrid_laptop() { 8 | 9 | 10 | 11 | 14 | 30 | 31 | 32) return 0 ;; esac - ls /sys/class/power_supply/ 2>/dev/null | grep -q "^BAT" && return 0 + compgen -G "/sys/class/power_supply/BAT*" >/dev/null && return 0 return 1 } diff --git a/modules/gpu/nvidia.sh b/modules/gpu/nvidia.sh index e2b7607..b157e92 100644 --- a/modules/gpu/nvidia.sh +++ b/modules/gpu/nvidia.sh @@ -4,22 +4,22 @@ # CASE A : Trixie + backports kernel → Official NVIDIA CUDA Repo (Pinned v590) # CASE B : Kernel stable (any distro) → Debian stable # -# Nota: Debian 12 (Bookworm) backports llegaron a EOL (2026-08-09); -# el flujo NVIDIA solo usa el repositorio estable para Debian 12. +# Note: Debian 12 (Bookworm) backports reached EOL (2026-08-09); +# the NVIDIA flow only uses the stable repo for Debian 12. # ------------------------------------------------------------------- # Shared helper: enable NVIDIA CUDA repo -# Debian 13 (Trixie): cuda-keyring (método oficial — extrepo roto) -# Debian 12 (Bookworm): extrepo nvidia-cuda (funciona) +# Debian 13 (Trixie): cuda-keyring (official method — broken extrepo) +# Debian 12 (Bookworm): extrepo nvidia-cuda (works) # ------------------------------------------------------------------- _enable_cuda_repo() { _is_cuda_repo_ready && return 0 if [ "$DEBIAN_VERSION" = "13" ]; then - # Método oficial NVIDIA: cuda-keyring (extrepo no configura - # correctamente el repo en Trixie) + # Official NVIDIA method: cuda-keyring (extrepo does not + # configure the repo correctly on Trixie) if dpkg -s cuda-keyring &>/dev/null; then - return 0 # ya instalado → su .list ya existe + return 0 # already installed → its .list already exists fi local tmp_deb tmp_deb=$(mktemp "${TMPDIR:-/tmp}/cuda-keyring.XXXXXX.deb") || return 1 @@ -65,10 +65,10 @@ _nvidia_secure_boot_enabled() { mokutil --sb-state 2>/dev/null | grep -q "SecureBoot enabled" } -# Aviso no fatal: el módulo DKMS está compilado pero NO firmado. -# El usuario avanzado puede firmarlo con MOK; el novato necesita saber -# por qué verá pantalla negra tras reiniciar. Si mokutil falta, no se -# muestra nada (mokutil no viene preinstalado en Debian). +# Non-fatal notice: the DKMS module is compiled but NOT signed. +# Advanced users can sign it with MOK; novices need to know why +# they will see a black screen after reboot. If mokutil is missing, +# nothing is shown (mokutil is not preinstalled on Debian). _warn_secure_boot() { if _nvidia_secure_boot_enabled; then echo -e "${RED}WARNING: Secure Boot is enabled. The NVIDIA DKMS module is compiled but NOT signed.${NC}" @@ -164,13 +164,13 @@ _show_nvidia_version_menu() { # ------------------------------------------------------------------- # NVIDIA Wayland/KMS base configuration (/etc/modprobe.d/nvidia-wayland.conf) # Applies on Debian 12/13, any driver version (535/550/590/595). -# Arquitectura y detección híbrida son ORTOGONALES: -# - Arquitectura → afecta SOLO fbdev (kepler: el 470 no lo soporta) -# y el color/mensaje informativo. -# - Híbrida vs desktop → afecta NVreg (Preserve / kernel suspend -# notifier), INDEPENDIENTE de la arquitectura. -# 590/595 añaden el kernel suspend notifier (complementa, nunca -# reemplaza, NVreg_PreserveVideoMemoryAllocations). +# Architecture and hybrid detection are ORTHOGONAL: +# - Architecture → affects ONLY fbdev (kepler: the 470 does not +# support it) and the color/info message. +# - Hybrid vs desktop → affects NVreg (Preserve / kernel suspend +# notifier), INDEPENDENT of architecture. +# 590/595 add the kernel suspend notifier (complements, never +# replaces, NVreg_PreserveVideoMemoryAllocations). # ------------------------------------------------------------------- _configure_nvidia_wayland() { local ver="${1:-$NVIDIA_SELECTED_VERSION}" @@ -181,7 +181,7 @@ _configure_nvidia_wayland() { local msg="Wayland config (desktop): KMS + video memory preservation enabled." arch=$(_get_nvidia_arch_family) - # ── Arquitectura: solo fbdev y mensaje de color ── + # ── Architecture: fbdev only and color message ── case "$arch" in kepler) color="${RED}" @@ -193,7 +193,7 @@ _configure_nvidia_wayland() { ;; esac - # ── Híbrida vs desktop: NVreg independiente de la arquitectura ── + # ── Hybrid vs desktop: NVreg independent of architecture ── if _is_hybrid_laptop; then content="options nvidia-drm modeset=1"$'\n' [ "$arch" != "kepler" ] && content+="options nvidia-drm fbdev=1"$'\n' @@ -236,8 +236,9 @@ _install_nvidia_cuda_repo() { return 1 fi - # Step 2: apt update explícito — sin índice actualizado el repo no - # se ve y apt resolvería el candidato Debian (v550) en vez de v${ver}. + # Step 2: explicit apt update — without an updated index the repo + # is invisible and apt would resolve the Debian candidate (v550) + # instead of v${ver}. if ! _run_cmd "CUDA Repo" "sudo apt update" \ "Updating package lists after enabling CUDA repository..."; then NVIDIA_DRIVER_MODE="" @@ -245,12 +246,12 @@ _install_nvidia_cuda_repo() { return 1 fi - # Step 3: Pinning oficial — transacción APT INDEPENDIENTE y - # obligatoria. APT lee /etc/apt/preferences.d/ al inicio de su - # ejecución, no durante la transacción: el pinning debe estar - # instalado ANTES de instalar el driver. Si el repo no lo publica, - # es un problema del repo de NVIDIA: abortar limpiamente en vez de - # instalar una versión que el usuario no eligió. + # Step 3: Official pinning — INDEPENDENT and mandatory APT + # transaction. APT reads /etc/apt/preferences.d/ at startup, not + # during the transaction: the pinning must be installed BEFORE + # installing the driver. If the repo does not publish it, it is + # an NVIDIA repo problem: abort cleanly instead of installing a + # version the user did not choose. if ! _run_cmd "NVIDIA Pinning" \ "sudo apt install -y nvidia-driver-pinning-${ver}" \ "Installing NVIDIA version pinning (${ver})..."; then @@ -259,11 +260,11 @@ _install_nvidia_cuda_repo() { return 1 fi - # Step 4: Instalar el metapaquete (pinning ya activo). Si falla, - # el pinning queda instalado (solo config, no es problema). - # firmware-nvidia-gsp llega como dependencia transitiva obligatoria + # Step 4: Install the metapackage (pinning already active). If it + # fails, the pinning remains installed (config only, not a problem). + # firmware-nvidia-gsp arrives as a mandatory transitive dependency # (nvidia-open → nvidia-kernel-open-dkms → firmware-nvidia-gsp), - # alineado con la doc oficial: apt -V install nvidia-open. + # aligned with the official docs: apt -V install nvidia-open. if ! _run_cmd "NVIDIA CUDA" \ "sudo apt install -y nvidia-open" \ "Installing NVIDIA driver from CUDA repository..."; then @@ -272,7 +273,7 @@ _install_nvidia_cuda_repo() { return 1 fi - # Post-install: el módulo DKMS instalado debe coincidir con la rama ${ver} + # Post-install: the installed DKMS module must match the ${ver} branch local dkms_ver dkms_ver=$(dpkg -l nvidia-kernel-dkms nvidia-kernel-open-dkms 2>/dev/null | awk '$1=="ii" {print $3; exit}') if [[ "$dkms_ver" == ${ver}.* ]]; then @@ -288,7 +289,7 @@ _install_nvidia_cuda_repo() { } # ------------------------------------------------------------------- -# Bookworm Kepler intercepción — fuerza nvidia-legacy-470xx-driver +# Bookworm Kepler interception — forces nvidia-legacy-470xx-driver # ------------------------------------------------------------------- _install_nvidia_bookworm_kepler() { local nv_pkg="nvidia-tesla-470-driver" @@ -297,26 +298,26 @@ _install_nvidia_bookworm_kepler() { echo -e "${YELLOW}Kepler GPU detected — forcing ${nv_pkg}.${NC}" - local msg="Kepler GPU detectada (GKxxx).\n\n" - msg+="En Debian 12 Bookworm, Kepler requiere el driver legacy\n" - msg+="en lugar del moderno. Se usará el paquete:\n" + local msg="Kepler GPU detected (GKxxx).\n\n" + msg+="On Debian 12 Bookworm, Kepler requires the legacy\n" + msg+="driver instead of the modern one. Using package:\n" msg+=" ${nv_pkg} ${nv_ver:-unknown}\n" - msg+="para evitar fallos de pantalla negra.\n\n" + msg+="to avoid black-screen failures.\n\n" msg+=" [USE] ${nv_pkg}\n" msg+=" [+] linux-headers-amd64\n" msg+=" [+] firmware-misc-nonfree\n" msg+=" [+] nvidia-settings\n\n" - msg+="Instalar driver legacy para Kepler?" + msg+="Install the legacy driver for Kepler?" if ! _confirm_custom "NVIDIA Kepler — Bookworm" "$msg" "Install" "Skip" 14 70; then - echo "Omitiendo driver Kepler." + echo "Skipping Kepler driver." NVIDIA_DRIVER_MODE="" return 0 fi if ! _run_cmd "NVIDIA Kepler" \ "sudo apt install -y linux-headers-amd64 $nv_pkg firmware-misc-nonfree nvidia-settings" \ - "Instalando nvidia-legacy-470xx-driver..."; then + "Installing nvidia-legacy-470xx-driver..."; then NVIDIA_DRIVER_MODE="" _msg "NVIDIA Kepler — Error" "Kepler driver installation FAILED.\n\nNo NVIDIA driver was installed." 10 60 return 1 @@ -332,10 +333,10 @@ _install_nvidia_bookworm_kepler() { # CASE B: Kernel stable (any distro) → Debian stable # ------------------------------------------------------------------- _install_nvidia_standard() { - # --- 1. ARQUITECTURA → MÓDULO KERNEL --- - # Solo Turing+ (conocido) usa el módulo abierto. Arquitectura - # unknown/vacía o antigua (Kepler/Fermi/Maxwell/Pascal/Volta) → - # módulo cerrado como fallback seguro. + # --- 1. ARCHITECTURE → KERNEL MODULE --- + # Only Turing+ (known) uses the open module. Unknown/empty or + # old architecture (Kepler/Fermi/Maxwell/Pascal/Volta) → + # closed module as safe fallback. local fam fam=$(_get_nvidia_arch_family) local kernel_pkg="nvidia-kernel-dkms" @@ -343,12 +344,12 @@ _install_nvidia_standard() { turing | ampere | ada | blackwell) kernel_pkg="nvidia-open-kernel-dkms" ;; esac - # --- 2. PAQUETES — UN SOLO apt install --- + # --- 2. PACKAGES — a single apt install --- local extra_pkgs="linux-headers-amd64 nvidia-driver firmware-nvidia-gsp nvidia-vaapi-driver" [ "$DEBIAN_VERSION" = "12" ] && extra_pkgs+=" mesa-vdpau-drivers" local install_pkgs="$kernel_pkg $extra_pkgs" - # --- 3. MENSAJE DE CONFIRMACIÓN --- + # --- 3. CONFIRMATION MESSAGE --- local kernel_ver msg kernel_ver=$(apt-cache policy "$kernel_pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}') || true @@ -364,7 +365,7 @@ _install_nvidia_standard() { return 0 fi - # --- 4. EJECUCIÓN --- + # --- 4. EXECUTION --- if ! _run_cmd "NVIDIA" "sudo apt install -y $install_pkgs" \ "Installing NVIDIA driver from stable..."; then NVIDIA_DRIVER_MODE="" @@ -373,13 +374,13 @@ _install_nvidia_standard() { fi NVIDIA_DRIVER_MODE="stable" - # Fix obligatorio para Debian 12 con módulo abierto + # Mandatory fix for Debian 12 with the open module if [ "$DEBIAN_VERSION" = "12" ] && [[ "$kernel_pkg" == *"open"* ]]; then echo "options nvidia NVreg_OpenRmEnableUnsupportedGpus=1" | sudo tee /etc/modprobe.d/nvidia-open.conf >/dev/null echo "Applied required Open RM parameter for Debian 12." fi - # --- 5. VERIFICACIÓN DKMS POST-INSTALL --- + # --- 5. DKMS POST-INSTALL VERIFICATION --- echo -e "${GREEN}NVIDIA driver installed. Reboot required.${NC}" _verify_nvidia_dkms_build "$kernel_pkg" || true } diff --git a/modules/repos.sh b/modules/repos.sh index 77c5a6e..2283391 100644 --- a/modules/repos.sh +++ b/modules/repos.sh @@ -135,7 +135,7 @@ _write_deb822() { fi # On migration from classic, disable the old file - if [ "$action" = "migrate" ] && [ "$bp_location" = "embedded-classic" -o "$bp_location" = "standalone-classic" ]; then + if [ "$action" = "migrate" ] && { [ "$bp_location" = "embedded-classic" ] || [ "$bp_location" = "standalone-classic" ]; }; then if [ -f /etc/apt/sources.list ]; then if _confirm "Disable Classic" "Migrating to deb822. Disable /etc/apt/sources.list?"; then sudo mv /etc/apt/sources.list /etc/apt/sources.list.disabled diff --git a/modules/rescue.sh b/modules/rescue.sh index ccfca17..df54932 100644 --- a/modules/rescue.sh +++ b/modules/rescue.sh @@ -71,7 +71,8 @@ _set_grub_var() { _apply_grub_setting() { local timeout="$1" style="$2" recordfail="$3" os_prober="$4" local file="/etc/default/grub" - local backup="${file}.backup.$(date +%Y%m%d_%H%M%S)" + local backup + backup="${file}.backup.$(date +%Y%m%d_%H%M%S)" local override="/etc/default/grub.d/99_script_override.cfg" sudo cp "$file" "$backup" diff --git a/modules/sysinfo.sh b/modules/sysinfo.sh index bcbe79f..9576b9c 100644 --- a/modules/sysinfo.sh +++ b/modules/sysinfo.sh @@ -142,7 +142,7 @@ _show_sysinfo() { *) # Fallback: classify by interface name pattern case "$iface" in - wl* | wlp* | wlo* | wlan*) + wl*) has_wifi=true desc="${wifi_descs[$wifi_idx]:-Unknown WiFi chipset}" shown_wifi_descs+=("${wifi_descs[$wifi_idx]:-$desc}") diff --git a/modules/system/audio.sh b/modules/system/audio.sh index 662e66c..3ccbd8e 100644 --- a/modules/system/audio.sh +++ b/modules/system/audio.sh @@ -52,6 +52,10 @@ _prefs_audio_menu() { while IFS= read -r _pkg; do [ -n "$_pkg" ] && _pkgs+=("$_pkg") done < <(echo "$cleaned" | tr ' ' '\n') + + # Pre-apply summary: confirm the selection once, + # before any apt transaction starts. + _confirm_install_list "Audio & Sound" "${_pkgs[@]}" || return for pkg in "${_pkgs[@]}"; do case $pkg in pipewire-audio) _install_pipewire_standard ;; @@ -86,6 +90,8 @@ _prefs_audio_menu() { esac done + _SELECTION_CONFIRMED=0 + echo -e "${GREEN}Audio & Sound setup complete.${NC}" } diff --git a/modules/utils.sh b/modules/utils.sh index d51d94b..c399340 100644 --- a/modules/utils.sh +++ b/modules/utils.sh @@ -4,23 +4,28 @@ # ------------------ # Global variables # ------------------ -CPU_SUMMARY="" -RAM_SUMMARY="" -GPU_TYPE="" -GPU_DESC="" -GPU_VERSION="" -INTEL_GPU_DEVICE_ID="" -NVIDIA_GPU_DEVICE_ID="" -HAS_NVIDIA=false -HAS_AMD=false -HAS_INTEL=false -HAS_AMD_LEGACY_GCN=false -KERNEL_VERSION="" -DISPLAY_SERVER="unknown" -STORAGE_SUMMARY="" -WIFI_CHIPSET="" -DESKTOP_ENV="" -AUDIO_SERVER="" +# Session state shared with every lazily-loaded module: +# exported so shellcheck sees the cross-file usage (SC2034). +export CPU_SUMMARY="" +export RAM_SUMMARY="" +export GPU_TYPE="" +export GPU_DESC="" +export GPU_VERSION="" +export INTEL_GPU_DEVICE_ID="" +export NVIDIA_GPU_DEVICE_ID="" +export HAS_NVIDIA=false +export HAS_AMD=false +export HAS_INTEL=false +export HAS_AMD_LEGACY_GCN=false +export KERNEL_VERSION="" +export DISPLAY_SERVER="unknown" +export STORAGE_SUMMARY="" +export WIFI_CHIPSET="" +export DESKTOP_ENV="" +export AUDIO_SERVER="" +# OS identity populated by detect_debian_version(); read by +# every module (SC2034). +export DEBIAN_VERSION="" # APT update deduplication flag (set to 1 after the first successful apt-get update) APT_UPDATED=0 @@ -36,6 +41,12 @@ STATE_DETECTED=0 # empty = not checked yet (checked on first use). _NET_STATUS="" +# Pre-apply summary support: countdown of installs whose +# per-package prompt was already confirmed by +# _confirm_install_list. 0 = every _install_pkg asks +# individually (the default, safe behavior). +_SELECTION_CONFIRMED=0 + # Lazy module registry (presence of a key = loaded). # Populated by _load_module() so every module is # sourced at most once per session. @@ -67,6 +78,10 @@ _load_module() { audio) path="${MODULES_DIR}/system/audio.sh" ;; java) path="${MODULES_DIR}/extras/java.sh" ;; repo_detect) path="${MODULES_DIR}/repos/repo_detect.sh" ;; + gnome) path="${MODULES_DIR}/desktops/desktop_gnome.sh" ;; + kde) path="${MODULES_DIR}/desktops/desktop_kde.sh" ;; + xfce) path="${MODULES_DIR}/desktops/desktop_xfce.sh" ;; + lxde) path="${MODULES_DIR}/desktops/desktop_lxde.sh" ;; *) path="${MODULES_DIR}/${name}.sh" ;; esac @@ -78,7 +93,11 @@ _load_module() { firmware) deps=(repo_detect bluetooth repos) ;; gaming) deps=(repo_detect java repos) ;; extras) deps=(java) ;; - desktop_display) deps=(repos) ;; + desktop_display) deps=(repos audio) ;; + gnome) deps=(desktop_display audio) ;; + kde) deps=(desktop_display audio) ;; + xfce) deps=(desktop_display audio) ;; + lxde) deps=(desktop_display audio) ;; esac local dep @@ -120,10 +139,10 @@ check_sudo() { _ensure_time_synced() { command -v timedatectl &>/dev/null || return - # ── Paso 1: Forzar NTP activo ── + # ── Step 1: Force NTP active ── sudo timedatectl set-ntp true --no-ask-password 2>/dev/null || true - # ── Paso 2: Validar zona horaria ── + # ── Step 2: Validate timezone ── local tz tz=$(timedatectl show -p Timezone --value 2>/dev/null || echo "") if [ -z "$tz" ] || [ "$tz" = "n/a" ] || [ "$tz" = "Etc/UTC" ]; then @@ -137,14 +156,14 @@ _ensure_time_synced() { fi fi - # ── Paso 3: Instalar/asegurar systemd-timesyncd ── + # ── Step 3: Install/ensure systemd-timesyncd ── if ! is_installed systemd-timesyncd; then sudo DEBIAN_FRONTEND=noninteractive apt install -y systemd-timesyncd || true fi sudo systemctl enable systemd-timesyncd 2>/dev/null || true sudo systemctl restart systemd-timesyncd 2>/dev/null || true - # ── Paso 4: Verificar resultado ── + # ── Step 4: Verify result ── sleep 2 if timedatectl show --property=NTPSynchronized --value 2>/dev/null | grep -q yes; then echo -e "${GREEN}Time synchronized: $(date '+%Y-%m-%d %H:%M')${NC}" @@ -421,7 +440,7 @@ detect_network() { ETH_IPS+=("${ip4:-}") ETH_DESCS+=("${ETH_DESC:-}") ;; - wl* | wlp* | wlo* | wlan*) + wl*) ip4=$(timeout 2 ip -4 -o addr show "$iface" 2>/dev/null | awk '{print $4}') ssid="" [ "$state" = "UP" ] && ssid=$(timeout 2 iwgetid -r "$iface" 2>/dev/null || true) @@ -543,9 +562,9 @@ get_intel_generation() { # ---------------------------------------------------------------------- # Check if backports repository is enabled (active line without #) -# Nota: el flujo NVIDIA Debian 12 (Bookworm) ya NO usa esta función -# (backports EOL 2026-08-09). Sigue activa para kernels, desktop/greetd, -# gaming, audio, comunicación y Mesa (AMD/Intel). +# Note: the Debian 12 (Bookworm) NVIDIA flow no longer uses this function +# (backports EOL 2026-08-09). Still active for kernels, desktop/greetd, +# gaming, audio, communication and Mesa (AMD/Intel). # ---------------------------------------------------------------------- is_backports_enabled() { local codename="${DEBIAN_CODENAME:-}" @@ -742,6 +761,42 @@ _run_install_batch() { fi } +# Pre-apply summary: confirm a parsed selection before +# any apt transaction starts. On success it arms +# _SELECTION_CONFIRMED (a countdown) so _install_pkg +# skips its per-package prompt; checklist flows reset +# the counter to 0 after their install loop. +_confirm_install_list() { + local title="$1" + shift + local -a items=("$@") + [ ${#items[@]} -eq 0 ] && return 1 + + local list="" + local shown=0 + local max_show=15 + local item + for item in "${items[@]}"; do + if [ "$shown" -lt "$max_show" ]; then + list+=" - ${item}\n" + shown=$((shown + 1)) + fi + done + local hidden=$(( ${#items[@]} - shown )) + if [ "$hidden" -gt 0 ]; then + list+="\n ... and ${hidden} more package(s)\n" + fi + list+="\nProceed with the installation?" + + local height=$(( shown + 8 )) + [ "$height" -gt 24 ] && height=24 + if _confirm "$title" "$list" "$height" 65; then + _SELECTION_CONFIRMED=${#items[@]} + return 0 + fi + return 1 +} + # Install a package with confirmation prompt. # Handles set -e: failures are caught and reported, not fatal. _install_pkg() { @@ -749,9 +804,16 @@ _install_pkg() { local ver ver=$(_get_pkg_version "$pkg") [ -z "$ver" ] && ver="(version unknown)" - if _confirm "Install: ${pkg}" "Install ${pkg}\nVersion: ${ver}?"; then - _run_cmd "Install" "sudo DEBIAN_FRONTEND=noninteractive apt install -y $pkg" "Installing $pkg..." + # Pre-apply summary support: when a checklist flow has + # confirmed its full selection (_confirm_install_list), + # the per-package prompt is already answered — the + # countdown expires after exactly N installs. + if [ "$_SELECTION_CONFIRMED" -gt 0 ]; then + _SELECTION_CONFIRMED=$((_SELECTION_CONFIRMED - 1)) + elif ! _confirm "Install: ${pkg}" "Install ${pkg}\nVersion: ${ver}?"; then + return 0 fi + _run_cmd "Install" "sudo DEBIAN_FRONTEND=noninteractive apt install -y $pkg" "Installing $pkg..." } # Install a package if not already installed. diff --git a/tests/desktops.bats b/tests/desktops.bats new file mode 100644 index 0000000..3afadc8 --- /dev/null +++ b/tests/desktops.bats @@ -0,0 +1,190 @@ +#!/usr/bin/env bash +# desktops.bats — Offline tests for the desktop helpers in +# modules/desktop_display.sh (SP2) and the desktop module +# wiring in _load_module (SP2.5). +# +# No `set -euo pipefail` here: BATS controls error handling +# per test (see utils.bats). +# +# State-mutating functions are called DIRECTLY (a `run` +# subshell would lose their globals); pure-output helpers +# use `run`. +# +# 100% offline: dpkg and whiptail come from the PATH mocks +# in test_helper.bash, systemctl uses the helper mock, +# /etc/X11/default-display-manager is redirected through the +# DM_FILE fixture, and the real audio installer is replaced +# by a call-counter stub. + +load test_helper.bash + +# Stub for modules/system/audio.sh (never loaded here): +# count calls instead of running apt. Counter reset in setup(). +_install_pipewire_standard() { + PIPEWIRE_INSTALL_CALLS=$((PIPEWIRE_INSTALL_CALLS + 1)) +} + +setup() { + mock_reset + load_utils + # load_utils() sources utils.sh inside a function, so the + # top-level `declare -A _LOADED_MODULES` (utils.sh:48) was + # function-local and vanished when it returned — the same + # caveat the helper documents for the ETH_* arrays. + # Re-declare it globally for the _load_module tests. + declare -gA _LOADED_MODULES=() + + # Only function definitions at the top level: safe to source. + source "${TEST_PROJECT_ROOT}/modules/desktop_display.sh" + + # Fixture redirect for /etc/X11/default-display-manager: + # the file exists but is empty, so `-s` fails and the + # systemctl fallback is exercised deterministically. + DM_FILE="$(mktemp)" + export DM_FILE + + PIPEWIRE_INSTALL_CALLS=0 +} + +teardown() { + if [ -n "${DM_FILE:-}" ]; then + rm -f "$DM_FILE" + fi + if [ -n "${MOCK_SUDO_LOG:-}" ]; then + rm -f "$MOCK_SUDO_LOG" + fi + if [ -n "${MOCK_APT_ROOT:-}" ]; then + rm -rf "$MOCK_APT_ROOT" + fi +} + +# ── _existing_display_manager ──────────────────────── +@test "_existing_display_manager reads the default DM file" { + printf '/usr/sbin/gdm3\n' >"$DM_FILE" + run _existing_display_manager + [ "$status" -eq 0 ] + [ "$output" = "gdm3" ] +} + +@test "_existing_display_manager prefers the DM file over an enabled unit" { + printf '/usr/sbin/sddm\n' >"$DM_FILE" + export MOCK_SYSTEMCTL_ENABLED="gdm3" + run _existing_display_manager + [ "$status" -eq 0 ] + [ "$output" = "sddm" ] +} + +@test "_existing_display_manager falls back to an enabled systemd unit" { + # DM_FILE exists but is empty: `-s` must fail, not `-e`. + export MOCK_SYSTEMCTL_ENABLED="lightdm" + run _existing_display_manager + [ "$status" -eq 0 ] + [ "$output" = "lightdm" ] +} + +@test "_existing_display_manager evaluates units in candidate order" { + # gdm3 → lightdm → sddm → greetd → gdm, whatever order + # systemctl reports them in. + export MOCK_SYSTEMCTL_ENABLED="sddm lightdm" + run _existing_display_manager + [ "$status" -eq 0 ] + [ "$output" = "lightdm" ] +} + +@test "_existing_display_manager falls back when the DM file is missing" { + rm -f "$DM_FILE" + export MOCK_SYSTEMCTL_ENABLED="greetd" + run _existing_display_manager + [ "$status" -eq 0 ] + [ "$output" = "greetd" ] +} + +@test "_existing_display_manager prints nothing when nothing is configured" { + run _existing_display_manager + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +# ── _offer_pipewire_after_desktop ──────────────────── +@test "_offer_pipewire_after_desktop skips without prompting when pipewire-audio is installed" { + MOCK_DPKG_INSTALLED="pipewire-audio" + WHIPTAIL_CALLS=0 + whiptail() { WHIPTAIL_CALLS=$((WHIPTAIL_CALLS + 1)); } + _offer_pipewire_after_desktop + [ "$WHIPTAIL_CALLS" -eq 0 ] + [ "$PIPEWIRE_INSTALL_CALLS" -eq 0 ] +} + +@test "_offer_pipewire_after_desktop installs the stack when accepted" { + export MOCK_WHIPTAIL_RC=0 # user answers "Yes" + _offer_pipewire_after_desktop + [ "$PIPEWIRE_INSTALL_CALLS" -eq 1 ] +} + +@test "_offer_pipewire_after_desktop returns 0 without installing when declined" { + export MOCK_WHIPTAIL_RC=1 # user answers "No" + offer_rc=0 + _offer_pipewire_after_desktop || offer_rc=$? + [ "$offer_rc" -eq 0 ] + [ "$PIPEWIRE_INSTALL_CALLS" -eq 0 ] +} + +@test "_offer_pipewire_after_desktop checks the pipewire package on Bullseye" { + export DEBIAN_VERSION="11" + MOCK_DPKG_INSTALLED="pipewire" # Bullseye package name + _offer_pipewire_after_desktop + [ "$PIPEWIRE_INSTALL_CALLS" -eq 0 ] +} + +@test "_offer_pipewire_after_desktop ignores pipewire-audio on Bullseye" { + export DEBIAN_VERSION="11" + MOCK_DPKG_INSTALLED="pipewire-audio" # wrong name on 11 + export MOCK_WHIPTAIL_RC=0 # user accepts + _offer_pipewire_after_desktop + [ "$PIPEWIRE_INSTALL_CALLS" -eq 1 ] +} + +# ── _load_module: desktop wiring (SP2.5) ───────────── +# The dependency cascade is not under test here: mark the +# transitive deps of the desktop modules as loaded so only +# the module file itself is sourced. +_load_module_with_deps_marked() { + local module="$1" + declare -gA _LOADED_MODULES=( + [desktop_display]=1 + [audio]=1 + [repos]=1 + [repo_detect]=1 + ) + _load_module "$module" +} + +@test "_load_module xfce sources modules/desktops/desktop_xfce.sh" { + _load_module_with_deps_marked xfce + type xfce_menu >/dev/null + type _install_xfce_custom >/dev/null + type _xfce_polkit_rules >/dev/null +} + +@test "_load_module lxde sources modules/desktops/desktop_lxde.sh" { + _load_module_with_deps_marked lxde + type lxde_menu >/dev/null + type _install_lxde_full >/dev/null +} + +@test "_load_module gnome sources modules/desktops/desktop_gnome.sh" { + _load_module_with_deps_marked gnome + type gnome_menu >/dev/null + type _install_gnome_full >/dev/null +} + +@test "_load_module kde sources modules/desktops/desktop_kde.sh" { + _load_module_with_deps_marked kde + type kde_menu >/dev/null + type _install_kde_minimal >/dev/null +} + +@test "_load_module returns 1 for an unknown module" { + run _load_module no_such_module + [ "$status" -eq 1 ] +} diff --git a/tests/test_helper.bash b/tests/test_helper.bash index 50db187..beb200f 100644 --- a/tests/test_helper.bash +++ b/tests/test_helper.bash @@ -104,6 +104,7 @@ export MODULES_DIR="${TEST_PROJECT_ROOT}/modules" # MOCK_SSID_OUTPUT `iwgetid -r ` output # MOCK_TIMEZONE_OUTPUT `timedatectl show -p Timezone --value` # MOCK_NTP_SYNC_OUTPUT `timedatectl show --property=NTPSynchronized --value` +# MOCK_SYSTEMCTL_ENABLED space-separated units that report "enabled" # MOCK_WHIPTAIL_OUTPUT / _RC whiptail stdout / exit code # MOCK_SUDO_DRY_RUN 1 = log-only sudo (see _mock_sudo) # MOCK_SUDO_LOG log file for dry-run sudo @@ -266,6 +267,28 @@ _mock_sudo() { env "$@" } +# ── systemctl(8) ────────────────────────────────────────────── +# Just enough for _existing_display_manager(): only the +# `is-enabled ` verb is exercised. Units listed in +# $MOCK_SYSTEMCTL_ENABLED report "enabled" (exit 0); every +# other unit reports "disabled" (exit 1), matching systemd. +_mock_systemctl() { + case "${1:-}" in + is-enabled) + local unit="${2:-}" cand + for cand in ${MOCK_SYSTEMCTL_ENABLED:-}; do + if [ "$cand" = "$unit" ]; then + printf 'enabled\n' + return 0 + fi + done + printf 'disabled\n' + return 1 + ;; + esac + return 0 +} + # ── whiptail(1) ─────────────────────────────────────────────── # Prints $MOCK_WHIPTAIL_OUTPUT on stdout and exits with # $MOCK_WHIPTAIL_RC (default 0 = "Yes" for confirm @@ -283,7 +306,7 @@ _mock_whiptail() { # PATH scripts are required. _install_mocks() { local name func - for name in dpkg apt-cache lspci lsusb ip iwgetid timedatectl sudo whiptail; do + for name in dpkg apt-cache lspci lsusb ip iwgetid timedatectl sudo whiptail systemctl; do func="_mock_${name//-/_}" # apt-cache → _mock_apt_cache if ! declare -f "$func" >/dev/null; then echo "test_helper.bash: mock function ${func} is not defined" >&2 @@ -323,6 +346,7 @@ mock_reset() { # systemd export MOCK_TIMEZONE_OUTPUT="UTC" export MOCK_NTP_SYNC_OUTPUT="no" + export MOCK_SYSTEMCTL_ENABLED="" # dialogs export MOCK_WHIPTAIL_OUTPUT=""