Security hardening & DRY refactoring

- Fixed command injection in desktop_display.sh by converting word-splitting loops to safe array-based iteration for LightDM/GDM3 configuration and XFCE package installation.
- Added symlink detection guard before repository file operations in repos.sh to prevent TOCTOU attacks during restore_previous_repos().
- Hardened SUDO_USER resolution with awk validation against /etc/passwd to prevent root fallback and ensure real login users are targeted for sudoers configuration.
- Implemented algorithm (lz4/zstd) and size validation before ZRAM configuration writes in zram.sh to reject invalid inputs.
- Protected grep MemTotal read from /proc/meminfo with 2>/dev/null and default assignment under set -u.
- Added || true guards around apt-cache madison pipelines in firmware.sh, kernel.sh, gpu.sh, and utils.sh to prevent pipefail aborts when backports unavailable.
- Wrapped whiptail installation in if/else blocks to allow offline error messages instead of script termination under set -e.
- Fixed grep -c output duplication in swap.sh with proper || true pattern and default variable assignment.
- Replaced unquoted $cleaned loops with array conversion using while read for secure package iteration across gaming, desktop_display, firmware, and kernel modules.
- Anchored sed regex patterns to space-delimited "main" components to prevent mirror URL corruption in sources.list editing.
- Escaped % characters in _msg() function before passing to whiptail to prevent printf format interpretation crashes.
- Consolidated package version helpers into canonical wrappers: _get_pkg_version, _get_installed_version, _get_backports_version for consistent apt/dpkg queries.
- Created _install_if_missing() and _install_pkg() with proper error handling that respects set -e while providing user feedback on installation failures.
- Removed 6 dead code functions (~51 lines): check_system_time, sync_system_time, get_cpu_summary, get_ram_summary, pkg_versions, get_backports_kernel_version.
- Added detect_displayserver and detect_audio_server to refresh_system_state() for complete state refresh when returning from menus.
- Enhanced _on_interrupt() trap handler to kill lingering apt/dpkg child processes and clean /tmp/debianito.* temporary files on Ctrl+C or TERM.
- Improved restore_previous_repos() with manifest-based backup verification (.backed_up_* markers) to prevent destructive repository file deletion.
- Added mktemp usage for secure temporary deb file downloads in nvidia.sh, heroic.sh, and tools.sh to eliminate TOCTOU vulnerabilities in /tmp.
- Fixed Bluetooth USB dongle misclassification as WiFi devices by excluding "bluetooth" strings from USB_WIFI_DEVS detection in firmware.sh.
- Properly utilized the need array for selective package installation in internet.sh instead of hardcoding full package list.
- Corrected fwupdmgr duplicate execution and grep false positives in system.sh with strict pattern matching for available updates.
- update docs and added quickstart guide
This commit is contained in:
stornic56
2026-09-14 20:31:48 -05:00
committed by GitHub
parent 53088aad4b
commit 54257d5a8a
37 changed files with 750 additions and 234 deletions
+32 -3
View File
@@ -87,6 +87,18 @@ The submenu offers the next categories:
--- ---
## Quick Start Guide
For a streamlined post-installation setup (~15-20 minutes), refer to the [Quick Start Visual Guide](/docs/quickstart.md). It provides:
- Step-by-step recommended order for running Debianito options (Steps 113)
- What each option does and when to enable it
- Screenshots of key dialogs (whiptail menus, confirmations, hardware detection)
- Troubleshooting tips for common issues (WiFi after firmware install, NVIDIA + Wayland, GRUB boot menu hidden, etc.)
**Tip:** If you're unsure where to start, follow Steps 15 from the Quick Start guide. They cover the essentials: system info, permissions, repositories, firmware, and graphics drivers.
---
## File Structure ## File Structure
| Directory/File | Description | | Directory/File | Description |
@@ -110,7 +122,7 @@ The submenu offers the next categories:
│   ├── gaming.md │   ├── gaming.md
│   ├── gpu.md │   ├── gpu.md
│   ├── kernel.md │   ├── kernel.md
│   ├── QUICKSTART.md │   ├── quickstart.md
│   ├── repos_config.md │   ├── repos_config.md
│   ├── retroarch.md │   ├── retroarch.md
│   ├── swap.md │   ├── swap.md
@@ -119,8 +131,25 @@ The submenu offers the next categories:
│   ├── user_priv_feed.md │   ├── user_priv_feed.md
│   └── zram.md │   └── zram.md
├── media ├── media
│   ── gift │   ── gift
│   └── script.gif │   │   └── script.gif
│   └── screenshots
│   ├── 01-system-info.png
│   ├── 02b-pwfeedback.png
│   ├── 02-user-privileges.png
│   ├── 03-system-prefs.png
│   ├── 04b-backports.png
│   ├── 04-repos.png
│   ├── 05-firmware-plan.png
│   ├── 06-gpu-choice.png
│   ├── 07-kernel.png
│   ├── 08-gaming.png
│   ├── 09b-zram-status.png
│   ├── 09-zram-algo.png
│   ├── 10b-swap-status.png
│   ├── 10-swap.png
│   ├── 11b-essential-pack.png
│   └── 11-programs.gif
├── modules ├── modules
│   ├── bluetooth.sh │   ├── bluetooth.sh
│   ├── bullseye │   ├── bullseye
+31 -1
View File
@@ -42,6 +42,26 @@ if [ -d "${MODULES_DIR}/bullseye" ]; then
[ -f "${MODULES_DIR}/bullseye/extras.sh" ] && source "${MODULES_DIR}/bullseye/extras.sh" [ -f "${MODULES_DIR}/bullseye/extras.sh" ] && source "${MODULES_DIR}/bullseye/extras.sh"
fi fi
# ── Interrupt safety: restore repository state on Ctrl+C / TERM ──
_on_interrupt() {
echo -e "${RED}[!] Interrupted. Restoring repository state if possible...${NC}"
if type restore_previous_repos &>/dev/null; then
restore_previous_repos 2>/dev/null || true
fi
# Kill any lingering child processes (apt, dpkg) from interrupted runs
pkill -f "apt.*install" 2>/dev/null || true
pkill -f "dpkg.*configure" 2>/dev/null || true
# Clean up temporary files created during execution
rm -rf /tmp/debianito.* 2>/dev/null || true
echo -e "${YELLOW}[!] Child processes killed and temp files cleaned.${NC}"
exit 130
}
trap _on_interrupt INT TERM
DEBIAN_VERSION="" DEBIAN_VERSION=""
DEBIAN_CODENAME="" DEBIAN_CODENAME=""
@@ -163,7 +183,12 @@ check_root
check_sudo check_sudo
if ! command -v whiptail >/dev/null 2>&1; then if ! command -v whiptail >/dev/null 2>&1; then
echo -e "${YELLOW}[+] whiptail not found. Installing required TUI dependencies...${NC}" echo -e "${YELLOW}[+] whiptail not found. Installing required TUI dependencies...${NC}"
_ensure_apt_updated && sudo apt-get install -y whiptail if _ensure_apt_updated && sudo apt-get install -y whiptail; then
echo -e "${GREEN}[+] whiptail installed.${NC}"
else
echo -e "${RED}[-] Could not install whiptail (no network?).${NC}" >&2
echo -e "${RED} The TUI menu requires it; install manually and re-run.${NC}" >&2
fi
fi fi
if ! _check_network; then if ! _check_network; then
echo -e "${YELLOW}──────────────────────────────────────────${NC}" echo -e "${YELLOW}──────────────────────────────────────────${NC}"
@@ -192,4 +217,9 @@ if [ "$DEBIAN_VERSION" = "11" ] && type check_bullseye_archive_phase &>/dev/null
check_bullseye_archive_phase check_bullseye_archive_phase
fi fi
if ! command -v whiptail >/dev/null 2>&1; then
echo -e "${RED}[-] whiptail is required for the TUI menu. Aborting.${NC}" >&2
exit 1
fi
main_menu main_menu
+331
View File
@@ -0,0 +1,331 @@
# Debianito — Quick Start Visual Guide
> **Scope:** Fresh Debian 11 / 12 / 13 installation → fully configured desktop in ~15-20 minutes.
> **Prerequisites:** Normal user with `sudo` access. Do **not** run the script as root.
> **Related:** [README Overview](../README.md) · [System Info](system_info.md) · [Repositories](repos_config.md) · [Firmware](firmware.md) · [GPU](gpu.md) · [Kernel](kernel.md) · [Gaming](gaming.md)
---
## Before You Start
```bash
git clone https://github.com/stornic56/debianito-post-install
cd debianito-post-install
chmod +x debianito.sh && ./debianito.sh
```
The script automatically checks for `whiptail` and `lsb-release`, installs them if missing, verifies `sudo` access, checks network connectivity, and synchronizes the system clock (`systemd-timesyncd` + `tzdata`).
> ![Script](/media/gift/script.gif)
---
## Recommended Order
Each step builds on the previous one. Steps 1-5 + 10 are the essential path; 6-9 are optional.
### Step 1 — Know Your System
**Menu:** `1 System Information`
Opens a whiptail message box populated by `utils.sh` detection functions (`detect_cpu_ram`, `detect_gpu`, `detect_network`, `detect_storage`, `detect_displayserver`, `detect_desktop_environment`).
You will see:
- Debian version and codename
- CPU model and RAM size
- GPU vendor(s) and device IDs (e.g., `10de:2684` for NVIDIA)
- Network adapters (Ethernet + WiFi chipset) and their state/IP
- Storage topology (NVMe / SSD / HDD / USB-SD)
- Display server (Wayland / X11 / tty) and desktop environment
Remember the **GPU line** (Intel / AMD / NVIDIA) and any **WiFi chipset** — you will need them in Steps 4-5.
> ![Script](/media/screenshots/01-system-info.png) System Information dialog with hardware summary.
### Step 2 — Fix Permissions Early (Optional but Recommended)
**Menu:** `2 User Privileges & Feedback`
A sub-menu with four toggles:
| Option | What It Does | When to Enable |
| -------- | -------------- | ---------------- |
| Sudo group membership | `usermod -aG sudo $USER` | Fresh install where the first user is not in `sudo` |
| Passwordless sudo | Creates `/etc/sudoers.d/$USER-nopasswd` with `NOPASSWD` for `apt`, `systemctl`, etc. | Lab / personal machine; skip on shared systems |
| Repair home ownership | `chown -R $USER:$USER $HOME` | Home files owned by root after a mishandled `sudo` |
| Sudo password feedback | Writes `Defaults pwfeedback` to `/etc/sudoers.d/pwfeedback` — shows `****` while typing | **Enable now** to avoid typos during the many installs that follow |
> ![Script](/media/screenshots/02-user-privileges.png) User Privileges & Feedback menu.
> ![Script](/media/screenshots/02b-pwfeedback.png) Confirmation dialog for "Sudo Password Feedback" with asterisk preview.
### Step 3 — System Preferences (Optional)
**Menu:** `3 System Preferences`
```
1 Date, Time & Timezone
2 Language, Locales & Keyboard
3 Audio & Sound Stack
4 Back to main menu
```
- **Date, Time & Timezone** — Runs `dpkg-reconfigure tzdata` and then `_ensure_time_synced` (NTP + `systemd-timesyncd`). Fix timezone before `apt update` — a wrong clock breaks GPG verification.
- **Language, Locales & Keyboard** — Runs `dpkg-reconfigure locales` and `keyboard-configuration`. Requires re-login to apply `LANG`.
- **Audio & Sound Stack** — Checklist with `pipewire-audio` (or `pipewire` on Bullseye), `alsa-utils`, `pavucontrol` (hidden on TTY), `pulsemixer`, `playerctl`. PipeWire on Trixie offers a Backports vs Stable choice and installs Bluetooth Hi-Res codecs (`LDAC`, `aptX`, `AAC`). See [System Preferences](system_prefs.md).
> ![Script](/media/screenshots/03-system-prefs.png) System Preferences menu.
### Step 4 — Configure Repositories (Required)
**Menu:** `4 Configure Repositories`
```
1 Enable Contrib & Non-Free Components
2 Migrate traditional sources.list to DEB822 format
3 Setup/Update Backports repositories
4 [ADVANCED] Upgrade system branch (Testing / SID)
5 Back to main menu
```
1. Choose **1. Enable Contrib & Non-Free** — adds `contrib`, `non-free`, `non-free-firmware` (Bookworm/Trixie). Required for firmware, NVIDIA drivers, and Steam (`contrib`). The script handles both `/etc/apt/sources.list` (classic) and `/etc/apt/sources.list.d/debian.sources` (DEB822) and validates via `apt update` with automatic rollback.
2. Choose **3. Setup/Update Backports** — answers `Yes` to enable `trixie-backports` / `bookworm-backports` in a separate file (`debian-backports.sources` or `.list`). Backports gives newer kernels, Mesa, and firmware. See [Configure Repositories](repos_config.md).
⚠️ Without `non-free` and `contrib`, Steps 5 and 8 will fail. Do not skip.
> ![Script](/media/screenshots/04-repos.png) Repositories menu.
> ![Script](/media/screenshots/04b-backports.png) Backports confirmation dialog.
### Step 5 — Install Firmware & Wireless Drivers
**Menu:** `5 Firmware, Wireless & Bluetooth`
The script:
- Scans **all** network hardware (PCI `lspci -nn` + USB `lsusb`, filtered for Realtek/Intel/Mediatek/Atheros/Qualcomm) and Bluetooth controllers
- Maps vendors to packages: `firmware-iwlwifi`, `firmware-realtek`, `firmware-mediatek`, `firmware-atheros`, `firmware-intel-misc` — plus `firmware-linux-nonfree` as the base meta-package
- Builds a plan showing detected controllers and planned packages, including Bluetooth handling (KDE → `bluedevil`, XFCE → `blueman`, GNOME → built-in, plus `pipewire-pulse`/`wireplumber` if PipeWire is active)
- Asks for confirmation before installing; offers Stable vs Backports for the base firmware
Accept the plan it shows — it is based on your actual hardware. Broadcom wireless (if present) is handled via `broadcom-sta-dkms` with DKMS build checks and `update-initramfs` + `modprobe wl`.
⚠️ You need internet for this step. If WiFi does not work after firmware install, **reboot** first — the driver needs a fresh load.
> ![Script](/media/screenshots/05-firmware-plan.png) Firmware plan dialog with hardware + package list.
### Step 6 — Install Graphics Drivers
**Menu:** `6 Graphics Drivers & Mesa Stack`
Whiptail radiolist with two options:
```
1 Radeon/Intel Mesa
2 NVIDIA Drivers
```
#### Path A — Radeon/Intel Mesa
Shows a plan with detected GPUs (`Intel firmware + intel-media-va-driver-non-free` or `i965-va-driver-shaders` for Gen7-, `firmware-amd-graphics` for AMD, plus `Mesa Vulkan/OpenGL/VA-API`). Offers legacy AMD GCN 1.0/1.1 migration to `amdgpu` via GRUB params (`radeon.si_support=0 ... amdgpu.si_support=1`). Installs Mesa from Backports or Stable (`_install_mesa_backports`), verifies `mesa-vulkan-drivers`, and offers vendor-specific telemetry (`radeontop`, `intel-gpu-tools`, `vainfo`).
#### Path B — NVIDIA Drivers
Shows detected NVIDIA GPUs and a two-stage menu:
1. **Manage menu** (if a driver is already installed): *Install / Change Driver Version* vs *Remove Driver and Restore Nouveau*.
2. **Version menu** (depends on Debian version):
- Bookworm: `v535` (Recommended) or `v470` (Kepler legacy via `nvidia-tesla-470-driver`)
- Trixie: `v550` (Debian stable, Recommended), `v590` or `v595` (NVIDIA CUDA Repo with `cuda-keyring` + `nvidia-open` + `firmware-nvidia-gsp`)
The script auto-detects the GPU architecture (`kepler` / `fermi` / `maxwell` / `pascal` / `turing` / `ampere` / `ada` / `blackwell`) and enforces compatibility — e.g., Blackwell requires `v590+` on Trixie, Maxwell/Pascal on Trixie + backports kernel is forced to `v550` stable, Fermi is vetoed on Bookworm/Trixie with an explanatory message.
> ![Script](/media/screenshots/06-gpu-choice.png) GPU type selector (Radeon/Intel vs NVIDIA).
After installation, **reboot** before testing. NVIDIA drivers need a fresh kernel load.
### Step 7 — Kernel Configuration (Optional)
**Menu:** `7 Kernel`
```
stable Install linux-image-amd64 (Stable)
backports Install from backports (Trixie only)
rt Install linux-image-rt-amd64 (Preempt-RT)
cloud Install linux-image-cloud-amd64
back Return to main menu
```
- **Stable** — default Debian kernel (6.12 LTS on Trixie).
- **Backports** — only on Trixie; requires `trixie-backports` enabled in Step 4. Newer kernel (e.g., 7.x) for Intel Arrow Lake / AMD Zen 5, Battlemage D3cold, etc.
- **RT** — Preempt-RT low-latency kernel. Warns if `GPU_TYPE == nvidia` (proprietary drivers may not support RT).
- **Cloud** — minimal kernel for VMs/containers (`linux-image-cloud-amd64`).
Each variant installs the matching `linux-headers-*` package atomically (`sudo apt install -y [-t trixie-backports] linux-image-* linux-headers-*`). The script warns about DKMS recompilation if NVIDIA is present.
> ![Script](/media/screenshots/07-kernel.png) — Kernel menu with four variants.
> See [kernel](kernel.md) for backports rationale and bootloader details.
### Step 8 — Gaming Setup (Optional)
**Menu:** `8 Gaming Setup`
A single checklist with all options:
```
[*] i386 Enable 32-bit (i386) architecture
[*] steam Steam (requires 32-bit support)
[*] mangohud Performance overlay (Vulkan/OpenGL)
[ ] gamemode Game performance optimization
[*] goverlay MangoHud config GUI
[ ] heroic Heroic Launcher (Epic/GOG)
[ ] java Minecraft Java Runtime
[ ] openrgb OpenRGB (RGB lighting control)
[ ] lutris Lutris + Wine (requires 32-bit support)
[ ] retroarch RetroArch Emulator Frontend
```
- If `steam`, `lutris`, or `i386` is checked, the script runs `dpkg --add-architecture i386` + `apt update`, then installs 32-bit graphics libraries (`_install_nvidia_32bit` or `_install_mesa_32bit`).
- `steam` checks `contrib` (`ensure_contrib_repo`) and installs `steam-installer`.
- `heroic` fetches the latest `.deb` from GitHub releases (`api.github.com`).
- `openrgb` is Bookworm/Trixie only; handles `i2c-dev`, udev, `i2c` group, and `setcap`.
- `java` offers Temurin 8 / 17 / 21 / 25 via `extrepo adoptium`.
- Requires GUI for some installers (skipped on headless).
> ![Script](/media/screenshots/08-gaming.png) Gaming checklist with i386 + Steam + MangoHud checked.
> See [gaming](gaming.md) and [retroarch](retroarch.md).
### Step 9 — ZRAM Compressed Swap (Optional)
**Menu:** `9 ZRAM`
```
1 View ZRAM status
2 Create / Reconfigure ZRAM
3 Remove ZRAM
4 Back to main menu
```
- **View** — Shows `/etc/default/zramswap` (`ALGO`, `SIZE`, `PRIORITY`) and `zramctl` output.
- **Create / Reconfigure** — Choice of `lz4` (fastest, gaming) vs `zstd` (better ratio). Recommended size is 50% of RAM (≤8 GB) or 4096 MB fixed (>8 GB), configurable. Writes to `/etc/default/zramswap` (`ALGO`, `SIZE`, `PRIORITY=100`) and `systemctl restart zramswap || true`. Priority 100 ensures ZRAM is used before any disk swap (priority 10).
- **Remove** — `systemctl stop zramswap`, `swapoff /dev/zram0`, `modprobe -r zram`, `apt purge zram-tools`, removes `/etc/default/zramswap`.
> ![Script](/media/screenshots/09-zram-algo.png) Algorithm choice (lz4 vs zstd).
> ![Script](/media/screenshots/09b-zram-status.png) `zramctl` status output in whiptail.
> See [zram](zram.md).
### Step 10 — Swap Management (Optional)
**Menu:** `10 Swap Management`
```
1 Show current swap & swappiness
2 Create / resize swapfile
3 Remove swapfile
4 Change swappiness
5 Back to main menu
```
- Uses `/swapfile` with `pri=10` (below ZRAM's 100) and tag `# debianito-managed-swap` in `/etc/fstab`.
- Btrfs: warns about `nodatacow` (`chattr +C`) and hibernation limitations; uses `dd` instead of `fallocate`.
- Fstab is written via a temp file and validated with `findmnt --verify` before replacing `/etc/fstab`.
- Concurrency is guarded by `flock /run/lock/debianito-swap.lock`.
> ![Script](/media/screenshots/10-swap.png) Swap Management menu.
> ![Script](/media/screenshots/10b-swap-status.png) Swap status.
> See [swap](swap.md).
### Step 11 — Install Programs and Software
**Menu:** `11 Install Programs and Software``0 Essential Pack`
One-click install of `htop`, `inxi`, `neofetch`/`fastfetch`, `vlc`, `ufw`, `zip`, `unrar`, `p7zip`, plus `lsb-release` fixes.
After this, browse the other categories as needed:
| Category | Example Packages |
| ---------- | ------------------ |
| Customization System | Desktop themes, icons, cursors, fonts |
| Download & Network | aria2, ytdlp, qBittorrent, Deluge |
| Internet | Firefox, LibreWolf, Chromium, Tor, Thunderbird, RiseUp/Mullvad VPN |
| Communication | Signal, Telegram, HexChat |
| Media Players | VLC, MPV |
| Multimedia & Design | GIMP, Kdenlive, Blender, Audacity, Inkscape |
| Code Editors & IDEs | Neovim, Helix, Emacs, VSCodium, Geany |
| Servers & Dev Tools | Nginx, PostgreSQL, Docker, Temurin JDK, Jellyfin |
| Security & Networking | Wireshark, ClamAV, UFW, fail2ban |
| Software Center & Flatpak | GNOME Software, KDE Discover, Flatpak |
| Office & Productivity | LibreOffice, document tools |
| System Tools | htop/btop, Timeshift, extension-manager, virt-manager |
| Fetch / System Info | fastfetch, neofetch, hyfetch, screenfetch |
> ![Script](/media/screenshots/11-programs.gif) Category menu with 0-14 options.
> ![Script](/media/screenshots/11b-essential-pack.png) Essential Pack confirmation.
### Step 12 — Boot Rescue
**Menu:** `12 Boot Rescue + GRUB`
- **GRUB boot menu settings** — 4 presets (hidden 0s / 3s / 5s / custom) writing `GRUB_TIMEOUT`, `GRUB_TIMEOUT_STYLE`, `GRUB_RECORDFAIL_TIMEOUT`, `GRUB_DISABLE_OS_PROBER` to `/etc/default/grub.d/99_script_override.cfg` with `update-grub` + backup/rollback.
- **UEFI Secure Boot repair** — reinstalls `shim-signed`, `grub-efi-amd64-signed`, `linux-image-amd64`, runs `grub-install` + `update-grub` (UEFI only, checked via `/sys/firmware/efi` + `mokutil`).
- **Initramfs regeneration** — `update-initramfs -u -k all || true`.
> See [boot](boot.md).
### Step 13 — Desktop & Display
**Menu:** `13 Desktop & Display`
```
1 Desktop Environment
2 Display Manager
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.
- **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).
---
## After the Script
1. **Reboot** if you installed firmware, GPU drivers, or a new kernel.
2. Verify:
```bash
sudo zramctl # ZRAM active?
sudo swapon --show # Swap with correct priorities?
vainfo # VA-API acceleration?
nvidia-smi # NVIDIA driver loaded?
systemctl status bluetooth # Bluetooth active?
```
3. For gaming, enable `i386` was handled — verify with `dpkg --print-foreign-architectures | grep i386`.
---
## Troubleshooting Quick Answers
| Symptom | Fix |
| --------- | ----- |
| **WiFi not working after firmware** | Reboot. Check `lspci -nn \| grep Network` and verify the package (`firmware-iwlwifi` etc.) is `ii` via `dpkg -l`. Ensure `non-free` + `non-free-firmware` were enabled in Step 4. |
| **Black screen after NVIDIA + Wayland (GNOME/GDM3)** | At login, select *GNOME on Xorg* (gear icon) or disable Wayland: `sudo nano /etc/gdm3/daemon.conf` → `WaylandEnable=false`. The script only warns about Debian bug #1109409, it does not force X11. |
| **GRUB menu hidden and cannot enter** | Hold `ESC` immediately after power-on. Or boot a live USB, `chroot`, and run `12 Boot Rescue → GRUB boot menu settings → Show 5 seconds`. |
| **Steam fails to start** | Verify `i386` is enabled: `dpkg --print-foreign-architectures`. Check `contrib` is in `/etc/apt/sources.list`. Re-run `8 Gaming Setup` and ensure the checklist had `i386` + `steam` checked. |
| **greetd installed but cannot log in** | This is expected — you must create `/etc/greetd/config.toml` manually. See `man greetd` and `man 5 greetd-sessions`. |
| **Bluetooth tray icon missing** | Reboot or `systemctl restart bluetooth`. Ensure `bluez`, `bluedevil` (KDE) or `blueman` (XFCE) is installed. For PipeWire, check `systemctl --user status pipewire pipewire-pulse wireplumber`. |
| **PipeWire crackling / no Bluetooth Hi-Res codec** | Re-run `3 System Preferences → Audio & Sound → PipeWire Audio Stack`. Verify `libldacbt-*`, `libopenaptx0`, `libfdk-aac2t64` are installed (`dpkg -l \| grep -E 'ldac\|aptx\|fdk'`). |
---
## Taking Screenshots for This Guide
Screenshots are taken from `whiptail` dialogs. To capture them:
1. Run the script inside a terminal that supports image export (e.g., `gnome-terminal` + `gnome-screenshot`, or `asciinema`).
2. For whiptail, press `PrintScreen` or use `import -window root screenshot.png` (ImageMagick).
3. Save under `media/screenshots/` with the filenames referenced above (`01-system-info.png`, `04-repos.png`, etc.).
4. Keep width ≈ 800px; the script uses fixed `TUI_ANCHO=78` and `TUI_ALTO=20` centered dialogs.
> **Note:** Until real screenshots are added, the placeholders above describe the expected content of each image.
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+26 -6
View File
@@ -19,6 +19,7 @@ _install_bluetooth_stack() {
return return
fi fi
local stack_failed=false
if is_installed bluez; then if is_installed bluez; then
echo " → Bluetooth stack already installed." echo " → Bluetooth stack already installed."
service_enable_only=true service_enable_only=true
@@ -30,7 +31,10 @@ _install_bluetooth_stack() {
! is_installed bluez-tools && bt_pkgs+=(bluez-tools) ! is_installed bluez-tools && bt_pkgs+=(bluez-tools)
! is_installed bluez-obexd && bt_pkgs+=(bluez-obexd) ! is_installed bluez-obexd && bt_pkgs+=(bluez-obexd)
if [ ${#bt_pkgs[@]} -gt 0 ]; then if [ ${#bt_pkgs[@]} -gt 0 ]; then
_run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y ${bt_pkgs[*]}" "Installing Bluetooth stack..." if ! _run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y ${bt_pkgs[*]}" "Installing Bluetooth stack..."; then
_msg_red "Bluetooth" "Failed to install the Bluetooth stack."
stack_failed=true
fi
fi fi
fi fi
@@ -44,11 +48,21 @@ _install_bluetooth_stack() {
case "${DESKTOP_ENV:-other}" in case "${DESKTOP_ENV:-other}" in
kde) kde)
if ! is_installed bluedevil; then if ! is_installed bluedevil; then
_run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y bluedevil" "Installing bluedevil..." if ! _run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y bluedevil" "Installing bluedevil..."; then
_msg_red "Bluetooth" "Failed to install bluedevil."
fi
fi fi
if [ "${AUDIO_SERVER:-}" = "pipewire" ]; then if [ "${AUDIO_SERVER:-}" = "pipewire" ]; then
! is_installed pipewire-pulse && _run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y pipewire-pulse" "Installing pipewire-pulse..." if ! is_installed pipewire-pulse; then
! is_installed wireplumber && _run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y wireplumber" "Installing wireplumber..." if ! _run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y pipewire-pulse" "Installing pipewire-pulse..."; then
_msg_red "Bluetooth" "Failed to install pipewire-pulse."
fi
fi
if ! is_installed wireplumber; then
if ! _run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y wireplumber" "Installing wireplumber..."; then
_msg_red "Bluetooth" "Failed to install wireplumber."
fi
fi
fi fi
;; ;;
gnome) gnome)
@@ -56,7 +70,9 @@ _install_bluetooth_stack() {
;; ;;
xfce|other) xfce|other)
if ! is_installed blueman; then if ! is_installed blueman; then
_run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y blueman" "Installing blueman..." if ! _run_cmd "Bluetooth" "sudo DEBIAN_FRONTEND=noninteractive apt install -y blueman" "Installing blueman..."; then
_msg_red "Bluetooth" "Failed to install blueman."
fi
fi fi
;; ;;
esac esac
@@ -68,5 +84,9 @@ _install_bluetooth_stack() {
sudo systemctl start bluetooth 2>/dev/null || true sudo systemctl start bluetooth 2>/dev/null || true
fi fi
_msg "Bluetooth Setup" "Bluetooth stack installed.\n\nA session restart or reboot is\nrecommended to load the desktop\napplets and tray icons." 10 60 if $stack_failed; then
_msg_red "Bluetooth Setup" "Bluetooth setup finished with errors.\n\nThe stack may be incomplete.\nA session restart or reboot is\nrecommended to load the desktop\napplets and tray icons." 10 60
else
_msg "Bluetooth Setup" "Bluetooth stack installed.\n\nA session restart or reboot is\nrecommended to load the desktop\napplets and tray icons." 10 60
fi
} }
+46 -10
View File
@@ -130,7 +130,14 @@ _install_xfce_custom() {
local cleaned local cleaned
cleaned=$(echo "$choices" | tr -d '"') cleaned=$(echo "$choices" | tr -d '"')
[ -z "$cleaned" ] && return [ -z "$cleaned" ] && return
_run_cmd "XFCE Custom" "sudo apt install -y $cleaned" \
# 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..." "Installing selected XFCE packages..."
_xfce_polkit_rules _xfce_polkit_rules
} }
@@ -161,7 +168,13 @@ EOF
if ! getent group backlight >/dev/null 2>&1; then if ! getent group backlight >/dev/null 2>&1; then
sudo groupadd --system backlight || true sudo groupadd --system backlight || true
fi fi
local de_user="${SUDO_USER:-$USER}" # 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 if [ -n "$de_user" ] && ! id -nG "$de_user" 2>/dev/null | grep -qw backlight; then
sudo usermod -aG backlight "$de_user" || true sudo usermod -aG backlight "$de_user" || true
fi fi
@@ -219,7 +232,13 @@ lightdm_config_menu() {
fi fi
local cleaned local cleaned
cleaned=$(echo "$choices" | tr -d '"') cleaned=$(echo "$choices" | tr -d '"')
for item in $cleaned; do # SECURITY: Convert to array to avoid word splitting and command injection.
local -a lm_items=()
while IFS= read -r _item; do
[ -n "$_item" ] && lm_items+=("$_item")
done < <(echo "$cleaned" | tr ' ' '\n')
for item in "${lm_items[@]}"; do
case $item in case $item in
install_lightdm) install_lightdm)
if ! is_installed lightdm || ! is_installed lightdm-gtk-greeter-settings; then if ! is_installed lightdm || ! is_installed lightdm-gtk-greeter-settings; then
@@ -241,7 +260,10 @@ greeter-hide-users=false" | sudo tee "$conf" >/dev/null; then
;; ;;
enable_autologin) enable_autologin)
local dm_conf="/etc/lightdm/lightdm.conf" local dm_conf="/etc/lightdm/lightdm.conf"
local lightdm_user="${SUDO_USER:-$USER}" local lightdm_user="${SUDO_USER:-}"
if [ -z "$lightdm_user" ] || [ "$lightdm_user" = "root" ]; then
lightdm_user=$(awk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd | head -1)
fi
sudo sed -i 's/^#[[:space:]]*autologin-user[[:space:]=].*/autologin-user='"$lightdm_user"'/' "$dm_conf" sudo sed -i 's/^#[[:space:]]*autologin-user[[:space:]=].*/autologin-user='"$lightdm_user"'/' "$dm_conf"
sudo sed -i 's/^#[[:space:]]*autologin-user-timeout[[:space:]=].*/autologin-user-timeout=0/' "$dm_conf" sudo sed -i 's/^#[[:space:]]*autologin-user-timeout[[:space:]=].*/autologin-user-timeout=0/' "$dm_conf"
echo -e "${GREEN}Autologin enabled for user: ${lightdm_user}${NC}" echo -e "${GREEN}Autologin enabled for user: ${lightdm_user}${NC}"
@@ -338,7 +360,14 @@ configure_gdm3() {
local cleaned local cleaned
cleaned=$(echo "$choice" | tr -d '"') cleaned=$(echo "$choice" | tr -d '"')
for item in $cleaned; do
# SECURITY: Convert to array to avoid word splitting and command injection.
local -a gdm_items=()
while IFS= read -r _item; do
[ -n "$_item" ] && gdm_items+=("$_item")
done < <(echo "$cleaned" | tr ' ' '\n')
for item in "${gdm_items[@]}"; do
case $item in case $item in
install) install)
echo "gdm3 shared/default-x-display-manager select gdm3" | sudo debconf-set-selections echo "gdm3 shared/default-x-display-manager select gdm3" | sudo debconf-set-selections
@@ -358,12 +387,16 @@ configure_gdm3() {
autologin) autologin)
local daemon_conf="/etc/gdm3/daemon.conf" local daemon_conf="/etc/gdm3/daemon.conf"
local username local username
username=$(whiptail --title "GDM3 Autologin" \ username=$(_inputbox "GDM3 Autologin" \
--inputbox "Enter username to autologin (leave empty to DISABLE autologin):" \ "Enter username to autologin (leave empty to DISABLE autologin):" 10 60)
10 60 "" 3>&1 1>&2 2>&3 || true)
if [ -n "$username" ]; then if [ -n "$username" ]; then
if ! [[ "$username" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]; then
_msg_red "GDM3 Autologin" "Invalid username: ${username}"
continue
fi
[ -f "$daemon_conf" ] || sudo touch "$daemon_conf"
sudo sed -i 's/^# *AutomaticLoginEnable[[:space:]=].*/AutomaticLoginEnable=true/' "$daemon_conf" sudo sed -i 's/^# *AutomaticLoginEnable[[:space:]=].*/AutomaticLoginEnable=true/' "$daemon_conf"
sudo sed -i 's/^# *AutomaticLogin[[:space:]=].*/AutomaticLogin='"$username"'/' "$daemon_conf" sudo sed -i "s|^# *AutomaticLogin[[:space:]=].*|AutomaticLogin=${username}|" "$daemon_conf"
echo -e "${GREEN}Autologin enabled for user: ${username}${NC}" echo -e "${GREEN}Autologin enabled for user: ${username}${NC}"
else else
sudo sed -i 's/^AutomaticLoginEnable[[:space:]=].*/# AutomaticLoginEnable=false/' "$daemon_conf" sudo sed -i 's/^AutomaticLoginEnable[[:space:]=].*/# AutomaticLoginEnable=false/' "$daemon_conf"
@@ -408,7 +441,10 @@ configure_sddm() {
;; ;;
2) 2)
local sddm_session="" local sddm_session=""
local sddm_user="${SUDO_USER:-$USER}" local sddm_user="${SUDO_USER:-}"
if [ -z "$sddm_user" ] || [ "$sddm_user" = "root" ]; then
sddm_user=$(awk -F: '$3>=1000 && $3<65534 {print $1}' /etc/passwd | head -1)
fi
if [ -f /usr/share/wayland-sessions/plasmawayland.desktop ]; then if [ -f /usr/share/wayland-sessions/plasmawayland.desktop ]; then
sddm_session="plasmawayland" sddm_session="plasmawayland"
elif [ -f /usr/share/wayland-sessions/lxqt-wayland.desktop ]; then elif [ -f /usr/share/wayland-sessions/lxqt-wayland.desktop ]; then
+1 -1
View File
@@ -273,7 +273,7 @@ _cat_internet() {
! is_installed "ca-certificates" && need+=("ca-certificates") ! is_installed "ca-certificates" && need+=("ca-certificates")
! is_installed "xsel" && need+=("xsel") ! is_installed "xsel" && need+=("xsel")
if [ ${#need[@]} -gt 0 ]; then if [ ${#need[@]} -gt 0 ]; then
_run_install_batch w3m w3m-img ca-certificates xsel _run_install_batch "${need[@]}"
else else
echo "w3m already installed." echo "w3m already installed."
fi fi
+5 -2
View File
@@ -178,8 +178,11 @@ _cat_general() {
_run_cmd "fwupd" "sudo fwupdmgr refresh --force" "Refreshing firmware metadata..." _run_cmd "fwupd" "sudo fwupdmgr refresh --force" "Refreshing firmware metadata..."
echo "" echo ""
echo "Checking for firmware updates..." echo "Checking for firmware updates..."
sudo fwupdmgr get-updates 2>&1 || true local _fwupd_out
if sudo fwupdmgr get-updates 2>&1 | grep -q "available"; then _fwupd_out=$(sudo fwupdmgr get-updates 2>&1 || true)
# Strict match: must not trigger on "No updates available"
# or "Devices with the latest available firmware version".
if echo "$_fwupd_out" | grep -Eq 'Upgrade available|New version:'; then
if _confirm "Firmware Update" "Firmware updates are available.\nInstall them now?"; then if _confirm "Firmware Update" "Firmware updates are available.\nInstall them now?"; then
_run_cmd "fwupd" "sudo fwupdmgr update -y" "Installing firmware updates..." _run_cmd "fwupd" "sudo fwupdmgr update -y" "Installing firmware updates..."
else else
+22 -15
View File
@@ -11,8 +11,8 @@ _FW_PLAN_PKG_LINES=()
# ── Network device detection (PCI + USB) ── # ── Network device detection (PCI + USB) ──
_detect_all_network_devices() { _detect_all_network_devices() {
! is_installed pciutils && _run_install_pkg pciutils ! is_installed pciutils && _install_pkg pciutils
! is_installed usbutils && _run_install_pkg usbutils ! is_installed usbutils && _install_pkg usbutils
PCI_NET_DEVS=() PCI_NET_DEVS=()
while IFS= read -r line; do while IFS= read -r line; do
@@ -21,7 +21,11 @@ _detect_all_network_devices() {
USB_WIFI_DEVS=() USB_WIFI_DEVS=()
while IFS= read -r line; do while IFS= read -r line; do
if echo "$line" | grep -qiE 'wireless|wifi|802\.11|bluetooth|wlan'; then # Exclude Bluetooth dongles: many report e.g. "Bluetooth wireless
# interface" and would otherwise be classified as WiFi and mapped
# to firmware-iwlwifi.
if echo "$line" | grep -qiE 'wireless|wifi|802\.11|wlan' &&
! echo "$line" | grep -qi 'bluetooth'; then
USB_WIFI_DEVS+=("$line") USB_WIFI_DEVS+=("$line")
fi fi
done < <(lsusb 2>/dev/null || true) done < <(lsusb 2>/dev/null || true)
@@ -33,10 +37,10 @@ _detect_all_network_devices() {
USB_BT_DEVS=() USB_BT_DEVS=()
while IFS= read -r line; do while IFS= read -r line; do
# All Bluetooth dongles belong here (the WiFi filter above now
# excludes anything containing "bluetooth").
if echo "$line" | grep -qi 'bluetooth'; then if echo "$line" | grep -qi 'bluetooth'; then
if ! echo "$line" | grep -qiE 'wireless|wifi|802\.11|wlan'; then USB_BT_DEVS+=("$line")
USB_BT_DEVS+=("$line")
fi
fi fi
done < <(lsusb 2>/dev/null || true) done < <(lsusb 2>/dev/null || true)
@@ -136,7 +140,7 @@ _build_firmware_plan() {
local fw_line local fw_line
if is_installed firmware-linux-nonfree; then if is_installed firmware-linux-nonfree; then
local cur_ver local cur_ver
cur_ver=$(dpkg -l firmware-linux-nonfree 2>/dev/null | awk '/^ii/{print $3}') cur_ver=$(_get_installed_version "firmware-linux-nonfree")
fw_line=" [+] firmware-linux-nonfree ${cur_ver} (already installed)" fw_line=" [+] firmware-linux-nonfree ${cur_ver} (already installed)"
else else
fw_line=" [+] firmware-linux-nonfree (base meta-package)" fw_line=" [+] firmware-linux-nonfree (base meta-package)"
@@ -198,7 +202,7 @@ _install_detected_firmware() {
continue continue
fi fi
local ver local ver
ver=$(apt-cache policy "$pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}') ver=$(_get_pkg_version "$pkg")
if [ -z "$ver" ] || [ "$ver" = "(none)" ]; then if [ -z "$ver" ] || [ "$ver" = "(none)" ]; then
echo " --> $pkg not available in repositories, skipping." echo " --> $pkg not available in repositories, skipping."
continue continue
@@ -363,16 +367,20 @@ _ensure_nonfree_repo() {
fi fi
else else
if [ -f /etc/apt/sources.list ]; then if [ -f /etc/apt/sources.list ]; then
# Add each missing component after "main", never duplicating sudo cp /etc/apt/sources.list "/etc/apt/sources.list.backup.$(date +%Y%m%d_%H%M%S)"
sudo sed -i -E '/^deb / { /(^|[[:space:]])non-free([[:space:]]|$)/! s/(main[^[:space:]]*)/\1 non-free/ }' /etc/apt/sources.list # Add each missing component after the space-delimited "main"
# component; a bare s/main/ would corrupt mirror URLs that
# contain "main" (e.g. https://main.example.com).
sudo sed -i -E '/^deb / { /(^|[[:space:]])non-free([[:space:]]|$)/! s/ main([[:space:]]|$)/ main non-free\1/ }' /etc/apt/sources.list
# non-free-firmware does not exist on Bullseye # non-free-firmware does not exist on Bullseye
if [ "$DEBIAN_VERSION" != "11" ]; then if [ "$DEBIAN_VERSION" != "11" ]; then
sudo sed -i -E '/^deb / { /(^|[[:space:]])non-free-firmware([[:space:]]|$)/! s/(main[^[:space:]]*)/\1 non-free-firmware/ }' /etc/apt/sources.list sudo sed -i -E '/^deb / { /(^|[[:space:]])non-free-firmware([[:space:]]|$)/! s/ main([[:space:]]|$)/ main non-free-firmware\1/ }' /etc/apt/sources.list
fi fi
fi fi
if [ -d /etc/apt/sources.list.d ]; then if [ -d /etc/apt/sources.list.d ]; then
for f in /etc/apt/sources.list.d/*.sources; do for f in /etc/apt/sources.list.d/*.sources; do
[ -f "$f" ] || continue [ -f "$f" ] || continue
sudo cp "$f" "${f}.backup.$(date +%Y%m%d_%H%M%S)"
sudo sed -i -E '/^Components:/ { /(^|[[:space:]])non-free([[:space:]]|$)/! s/$/ non-free/ }' "$f" sudo sed -i -E '/^Components:/ { /(^|[[:space:]])non-free([[:space:]]|$)/! s/$/ non-free/ }' "$f"
if [ "$DEBIAN_VERSION" != "11" ]; then if [ "$DEBIAN_VERSION" != "11" ]; then
sudo sed -i -E '/^Components:/ { /(^|[[:space:]])non-free-firmware([[:space:]]|$)/! s/$/ non-free-firmware/ }' "$f" sudo sed -i -E '/^Components:/ { /(^|[[:space:]])non-free-firmware([[:space:]]|$)/! s/$/ non-free-firmware/ }' "$f"
@@ -430,16 +438,15 @@ install_firmware() {
# 4. Install base firmware meta-package (unchanged logic) # 4. Install base firmware meta-package (unchanged logic)
local fw_pkg="firmware-linux-nonfree" local fw_pkg="firmware-linux-nonfree"
local fw_bpo local fw_bpo
fw_bpo=$(apt-cache madison "$fw_pkg" 2>/dev/null | fw_bpo=$(_get_backports_version "$fw_pkg")
grep "${DEBIAN_CODENAME}-backports" | awk '{print $3}' | head -1)
local fw_stable local fw_stable
fw_stable=$(apt-cache policy "$fw_pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}') fw_stable=$(_get_pkg_version "$fw_pkg")
if is_installed "$fw_pkg"; then if is_installed "$fw_pkg"; then
if [ -n "$fw_bpo" ]; then if [ -n "$fw_bpo" ]; then
local current_ver local current_ver
current_ver=$(dpkg -l "$fw_pkg" 2>/dev/null | awk '/^ii/{print $3}') current_ver=$(_get_installed_version "$fw_pkg")
if _confirm "Firmware" "firmware-linux-nonfree ${current_ver} already installed.\n\nUpgrade to backports version ${fw_bpo}?\n\nBackports often includes newer hardware support."; then if _confirm "Firmware" "firmware-linux-nonfree ${current_ver} already installed.\n\nUpgrade to backports version ${fw_bpo}?\n\nBackports often includes newer hardware support."; then
_run_cmd "Firmware" "sudo apt install -y -t ${DEBIAN_CODENAME}-backports $fw_pkg" "Upgrading firmware..." || true _run_cmd "Firmware" "sudo apt install -y -t ${DEBIAN_CODENAME}-backports $fw_pkg" "Upgrading firmware..." || true
fi fi
+18 -7
View File
@@ -40,7 +40,10 @@ ensure_contrib_repo() {
fi fi
else else
if [ -f /etc/apt/sources.list ]; then if [ -f /etc/apt/sources.list ]; then
sudo sed -i '/^deb / { /contrib/! s/main/main contrib/ }' /etc/apt/sources.list sudo cp /etc/apt/sources.list "/etc/apt/sources.list.backup.$(date +%Y%m%d_%H%M%S)"
# Anchor to the space-delimited "main" component so mirror URLs
# containing "main" (e.g. https://main.example.com) are untouched.
sudo sed -i -E '/^deb / { /\bcontrib\b/! s/ main([[:space:]]|$)/ main contrib\1/ }' /etc/apt/sources.list
fi fi
if [ -d /etc/apt/sources.list.d ]; then if [ -d /etc/apt/sources.list.d ]; then
for f in /etc/apt/sources.list.d/*.sources; do for f in /etc/apt/sources.list.d/*.sources; do
@@ -94,15 +97,23 @@ install_gaming() {
# 2. Determine if 32-bit is needed (steam, lutris, or explicit i386 toggle) # 2. Determine if 32-bit is needed (steam, lutris, or explicit i386 toggle)
local need_32bit=false local need_32bit=false
for p in $cleaned; do
case $p in steam | lutris) need_32bit=true ;; esac local -a install_pkgs=()
while IFS= read -r _pkg; do
[ -n "$_pkg" ] && install_pkgs+=("$_pkg")
done < <(echo "$cleaned" | tr ' ' '\n')
for p in "${install_pkgs[@]}"; do
case "$p" in steam | lutris) need_32bit=true ;; esac
done done
echo "$cleaned" | grep -qw i386 && need_32bit=true echo "$cleaned" | grep -qw i386 && need_32bit=true
# Strip pseudo-entry "i386" from the install list # Strip pseudo-entry "i386" from the install list
local install_list local -a install_list=()
install_list=$(echo "$cleaned" | tr ' ' '\n' | grep -v '^i386$' | tr '\n' ' ') while IFS= read -r _pkg; do
install_list=${install_list% } [ "$_pkg" = "i386" ] && continue
[ -n "$_pkg" ] && install_list+=("$_pkg")
done < <(echo "$cleaned" | tr ' ' '\n')
# 3. Enable i386 architecture if needed # 3. Enable i386 architecture if needed
if $need_32bit && ! dpkg --print-foreign-architectures 2>/dev/null | grep -q i386; then if $need_32bit && ! dpkg --print-foreign-architectures 2>/dev/null | grep -q i386; then
@@ -122,7 +133,7 @@ install_gaming() {
fi fi
# 5. Install selected packages # 5. Install selected packages
for pkg in $install_list; do for pkg in "${install_list[@]}"; do
case $pkg in case $pkg in
steam) steam)
if ensure_contrib_repo; then if ensure_contrib_repo; then
+3 -2
View File
@@ -2,7 +2,8 @@
# Heroic Games Launcher installation from GitHub releases # Heroic Games Launcher installation from GitHub releases
install_heroic() { install_heroic() {
local heroic_deb="/tmp/heroic.deb" local heroic_deb
heroic_deb=$(mktemp "${TMPDIR:-/tmp}/heroic-XXXXXX.deb") || return 1
local ua="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" local ua="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
_run_cmd "Heroic" "sudo apt install -y curl jq" "Installing dependencies..." _run_cmd "Heroic" "sudo apt install -y curl jq" "Installing dependencies..."
@@ -24,7 +25,7 @@ install_heroic() {
return 1 return 1
fi fi
_run_cmd "Heroic" "curl -sL -H 'User-Agent: $ua' -o '$heroic_deb' '$deb_url'" "Downloading Heroic..." _run_cmd "Heroic" "curl -fsSL -H 'User-Agent: $ua' -o '$heroic_deb' '$deb_url'" "Downloading Heroic..."
if ! dpkg-deb --info "$heroic_deb" >/dev/null 2>&1; then if ! dpkg-deb --info "$heroic_deb" >/dev/null 2>&1; then
_msg "Heroic Error" "Downloaded .deb is corrupted or truncated.\n\nRemoving file." 10 60 _msg "Heroic Error" "Downloaded .deb is corrupted or truncated.\n\nRemoving file." 10 60
+3 -2
View File
@@ -36,7 +36,8 @@ install_openrgb() {
;; ;;
esac esac
local deb_path="/tmp/openrgb.deb" local deb_path
deb_path=$(mktemp "${TMPDIR:-/tmp}/openrgb-XXXXXX.deb") || return 1
local ua="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" local ua="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
_run_cmd "OpenRGB" "sudo apt install -y curl jq" "Installing dependencies..." _run_cmd "OpenRGB" "sudo apt install -y curl jq" "Installing dependencies..."
@@ -62,7 +63,7 @@ install_openrgb() {
return 1 return 1
fi fi
_run_cmd "OpenRGB" "curl -L -o '${deb_path}' -A '${ua}' '${deb_url}'" "Downloading OpenRGB..." _run_cmd "OpenRGB" "curl -fsSL -o '${deb_path}' -A '${ua}' '${deb_url}'" "Downloading OpenRGB..."
if [ -n "$sha256" ]; then if [ -n "$sha256" ]; then
if ! echo "$sha256 $deb_path" | sha256sum -c --strict; then if ! echo "$sha256 $deb_path" | sha256sum -c --strict; then
+1 -1
View File
@@ -34,7 +34,7 @@ _install_amd_intel_stack() {
ref_ver=$(apt-cache policy mesa-vulkan-drivers 2>/dev/null | awk 'NR==3 {print $2; exit}') ref_ver=$(apt-cache policy mesa-vulkan-drivers 2>/dev/null | awk 'NR==3 {print $2; exit}')
local ref_bpo_ver local ref_bpo_ver
ref_bpo_ver=$(apt-cache madison mesa-vulkan-drivers 2>/dev/null | ref_bpo_ver=$(apt-cache madison mesa-vulkan-drivers 2>/dev/null |
grep "${DEBIAN_CODENAME}-backports" | awk '{print $3}' | head -1) grep "${DEBIAN_CODENAME}-backports" | awk '{print $3}' | head -1 || true)
local comp_line="Components: Vulkan, OpenGL, GLX, EGL, VA-API (64-bit)" local comp_line="Components: Vulkan, OpenGL, GLX, EGL, VA-API (64-bit)"
if [ -n "$ref_bpo_ver" ] && [ "$(is_backports_enabled)" == "true" ]; then if [ -n "$ref_bpo_ver" ] && [ "$(is_backports_enabled)" == "true" ]; then
+6 -2
View File
@@ -173,8 +173,12 @@ offer_generic_tools() {
tool_pkgs=$(pkg_versions nvtop vainfo) tool_pkgs=$(pkg_versions nvtop vainfo)
if _confirm "GPU Tools" "Install monitoring and info tools?\n\n${tool_pkgs}"; then if _confirm "GPU Tools" "Install monitoring and info tools?\n\n${tool_pkgs}"; then
_run_cmd "GPU Tools" "sudo apt install -y nvtop vainfo" "Installing GPU tools..." || true _run_cmd "GPU Tools" "sudo apt install -y nvtop vainfo" "Installing GPU tools..." || true
vainfo if command -v vainfo &>/dev/null; then
_pause "vainfo output shown above." vainfo
_pause "vainfo output shown above."
else
echo -e "${YELLOW}vainfo not available, skipping report.${NC}"
fi
else else
echo "Skipping GPU monitoring tools." echo "Skipping GPU monitoring tools."
fi fi
+40 -10
View File
@@ -5,7 +5,9 @@ install_amd_firmware() {
local fw_info local fw_info
fw_info=$(pkg_versions firmware-amd-graphics) fw_info=$(pkg_versions firmware-amd-graphics)
if _confirm "AMD Firmware" "Install AMD GPU firmware?\n\n${fw_info}"; then if _confirm "AMD Firmware" "Install AMD GPU firmware?\n\n${fw_info}"; then
_run_cmd "AMD" "sudo apt install -y firmware-amd-graphics" "Installing AMD GPU firmware..." if ! _run_cmd "AMD" "sudo apt install -y firmware-amd-graphics" "Installing AMD GPU firmware..."; then
_msg_red "AMD Firmware" "Failed to install AMD GPU firmware."
fi
fi fi
} }
@@ -24,15 +26,30 @@ offer_amd_tools() {
return return
fi fi
local tools_failed=false
if [ "$DEBIAN_VERSION" = "11" ]; then if [ "$DEBIAN_VERSION" = "11" ]; then
_run_cmd "AMD Tools" "sudo apt install -y ${amd_tools[*]} vainfo" "Installing AMD tools..." if ! _run_cmd "AMD Tools" "sudo apt install -y ${amd_tools[*]} vainfo" "Installing AMD tools..."; then
_msg_red "AMD Tools" "Failed to install AMD monitoring tools."
tools_failed=true
fi
else else
_run_cmd "AMD Tools" "sudo apt install -y ${amd_tools[*]} nvtop vainfo" "Installing AMD tools..." if ! _run_cmd "AMD Tools" "sudo apt install -y ${amd_tools[*]} nvtop vainfo" "Installing AMD tools..."; then
_msg_red "AMD Tools" "Failed to install AMD monitoring tools."
tools_failed=true
fi
fi
if command -v vainfo &>/dev/null; then
vainfo
_pause "vainfo output shown above."
else
echo -e "${YELLOW}vainfo not available, skipping report.${NC}"
fi fi
vainfo
_pause "vainfo output shown above."
echo -e "${GREEN}AMD tools installed.${NC}" if $tools_failed; then
echo -e "${RED}AMD tools installation failed.${NC}"
else
echo -e "${GREEN}AMD tools installed.${NC}"
fi
} }
install_intel_firmware() { install_intel_firmware() {
@@ -48,7 +65,9 @@ install_intel_firmware() {
local fw_info local fw_info
fw_info=$(pkg_versions firmware-intel-graphics "$va_driver") fw_info=$(pkg_versions firmware-intel-graphics "$va_driver")
if _confirm "Intel Firmware" "Install Intel GPU firmware?\n\n${fw_info}"; then if _confirm "Intel Firmware" "Install Intel GPU firmware?\n\n${fw_info}"; then
_run_cmd "Intel" "sudo apt install -y firmware-intel-graphics $va_driver" "Installing Intel GPU firmware..." if ! _run_cmd "Intel" "sudo apt install -y firmware-intel-graphics $va_driver" "Installing Intel GPU firmware..."; then
_msg_red "Intel Firmware" "Failed to install Intel GPU firmware."
fi
fi fi
} }
@@ -87,9 +106,20 @@ offer_intel_tools() {
pkg_info=$(pkg_versions "${pkg_list[@]}" vainfo) pkg_info=$(pkg_versions "${pkg_list[@]}" vainfo)
if _confirm "Intel Tools" "Intel GPU monitoring tools\n\n${driver_info}\n\nPackages:\n${pkg_info}"; then if _confirm "Intel Tools" "Intel GPU monitoring tools\n\n${driver_info}\n\nPackages:\n${pkg_info}"; then
_run_cmd "Intel Tools" "sudo apt install -y ${pkg_list[*]} vainfo" "Installing Intel monitoring tools..." local intel_failed=false
vainfo if ! _run_cmd "Intel Tools" "sudo apt install -y ${pkg_list[*]} vainfo" "Installing Intel monitoring tools..."; then
_pause "vainfo output shown above." _msg_red "Intel Tools" "Failed to install Intel monitoring tools."
intel_failed=true
fi
if command -v vainfo &>/dev/null; then
vainfo
_pause "vainfo output shown above."
else
echo -e "${YELLOW}vainfo not available, skipping report.${NC}"
fi
if $intel_failed; then
echo -e "${RED}Intel monitoring tools installation failed.${NC}"
fi
else else
echo "Skipping Intel monitoring tools." echo "Skipping Intel monitoring tools."
fi fi
+7 -5
View File
@@ -21,18 +21,20 @@ _enable_cuda_repo() {
if dpkg -s cuda-keyring &>/dev/null; then if dpkg -s cuda-keyring &>/dev/null; then
return 0 # ya instalado → su .list ya existe return 0 # ya instalado → su .list ya existe
fi fi
local tmp_deb
tmp_deb=$(mktemp "${TMPDIR:-/tmp}/cuda-keyring.XXXXXX.deb") || return 1
if ! wget -q "https://developer.download.nvidia.com/compute/cuda/repos/debian13/x86_64/cuda-keyring_1.1-1_all.deb" \ if ! wget -q "https://developer.download.nvidia.com/compute/cuda/repos/debian13/x86_64/cuda-keyring_1.1-1_all.deb" \
-O /tmp/cuda-keyring.deb; then -O "$tmp_deb"; then
rm -f /tmp/cuda-keyring.deb rm -f "$tmp_deb"
_msg "CUDA Repo — Error" "Failed to download cuda-keyring.\n\nNo NVIDIA driver was installed." 10 60 _msg "CUDA Repo — Error" "Failed to download cuda-keyring.\n\nNo NVIDIA driver was installed." 10 60
return 1 return 1
fi fi
if ! sudo dpkg -i /tmp/cuda-keyring.deb; then if ! sudo dpkg -i "$tmp_deb"; then
rm -f /tmp/cuda-keyring.deb rm -f "$tmp_deb"
_msg "CUDA Repo — Error" "Failed to install cuda-keyring.\n\nNo NVIDIA driver was installed." 10 60 _msg "CUDA Repo — Error" "Failed to install cuda-keyring.\n\nNo NVIDIA driver was installed." 10 60
return 1 return 1
fi fi
rm -f /tmp/cuda-keyring.deb rm -f "$tmp_deb"
return 0 return 0
fi fi
+18 -16
View File
@@ -16,18 +16,18 @@ show_kernel_menu() {
clear clear
case "$choice" in case "$choice" in
stable) _install_kernel_package "linux-image-amd64" "Stable" "" ;; stable) _install_kernel_package "linux-image-amd64" "Stable" "" ;;
rt) _install_kernel_package "linux-image-rt-amd64" "RT" "" ;; rt) _install_kernel_package "linux-image-rt-amd64" "RT" "" ;;
cloud) _install_kernel_package "linux-image-cloud-amd64" "Cloud" "" ;; cloud) _install_kernel_package "linux-image-cloud-amd64" "Cloud" "" ;;
backports) backports)
if [ "$(is_backports_enabled)" != "true" ]; then if [ "$(is_backports_enabled)" != "true" ]; then
_msg "Kernel" "Backports repository is not enabled.\n\nUse option 3 (Configure repositories) to enable backports\nbefore installing the backports kernel." _msg "Kernel" "Backports repository is not enabled.\n\nUse option 3 (Configure repositories) to enable backports\nbefore installing the backports kernel."
else else
_install_kernel_package "linux-image-amd64" "Backports" \ _install_kernel_package "linux-image-amd64" "Backports" \
"-t ${DEBIAN_CODENAME}-backports" "-t ${DEBIAN_CODENAME}-backports"
fi fi
;; ;;
back) break ;; back) break ;;
esac esac
done done
} }
@@ -44,7 +44,8 @@ _install_kernel_package() {
if [ "$flavor" = "Backports" ] && [ "$GPU_TYPE" = "nvidia" ]; then if [ "$flavor" = "Backports" ] && [ "$GPU_TYPE" = "nvidia" ]; then
if ! _confirm "Kernel" "WARNING: Backports kernel changes the kernel version.\nYour NVIDIA driver will need recompilation (DKMS).\n\nProceed?"; then if ! _confirm "Kernel" "WARNING: Backports kernel changes the kernel version.\nYour NVIDIA driver will need recompilation (DKMS).\n\nProceed?"; then
echo "Skipping."; return echo "Skipping."
return
fi fi
fi fi
if [ "$flavor" = "RT" ] && [ "$GPU_TYPE" = "nvidia" ]; then if [ "$flavor" = "RT" ] && [ "$GPU_TYPE" = "nvidia" ]; then
@@ -54,8 +55,8 @@ _install_kernel_package() {
local headers_pkg="${pkg_base/linux-image-/linux-headers-}" local headers_pkg="${pkg_base/linux-image-/linux-headers-}"
local ver headers_ver local ver headers_ver
if [ -n "$bpo_flag" ]; then if [ -n "$bpo_flag" ]; then
ver=$(apt-cache madison "$pkg_base" 2>/dev/null | grep "${DEBIAN_CODENAME}-backports" | awk '{print $3}' | head -1) ver=$(apt-cache madison "$pkg_base" 2>/dev/null | grep "${DEBIAN_CODENAME}-backports" | awk '{print $3}' | head -1 || true)
headers_ver=$(apt-cache madison "$headers_pkg" 2>/dev/null | grep "${DEBIAN_CODENAME}-backports" | awk '{print $3}' | head -1) headers_ver=$(apt-cache madison "$headers_pkg" 2>/dev/null | grep "${DEBIAN_CODENAME}-backports" | awk '{print $3}' | head -1 || true)
else else
ver=$(apt-cache show "$pkg_base" 2>/dev/null | sed -n 's/^Version: //p' | grep -v '~bpo' | head -1) ver=$(apt-cache show "$pkg_base" 2>/dev/null | sed -n 's/^Version: //p' | grep -v '~bpo' | head -1)
headers_ver=$(apt-cache show "$headers_pkg" 2>/dev/null | sed -n 's/^Version: //p' | grep -v '~bpo' | head -1) headers_ver=$(apt-cache show "$headers_pkg" 2>/dev/null | sed -n 's/^Version: //p' | grep -v '~bpo' | head -1)
@@ -66,7 +67,8 @@ _install_kernel_package() {
[ -n "$bpo_flag" ] && summary+="\n From: ${DEBIAN_CODENAME^}-backports" [ -n "$bpo_flag" ] && summary+="\n From: ${DEBIAN_CODENAME^}-backports"
if ! _confirm "Kernel — ${flavor}" "$summary"; then if ! _confirm "Kernel — ${flavor}" "$summary"; then
echo "Skipping."; return echo "Skipping."
return
fi fi
_run_cmd "Kernel" "sudo apt install -y ${bpo_flag} ${pkg_base} ${headers_pkg}" \ _run_cmd "Kernel" "sudo apt install -y ${bpo_flag} ${pkg_base} ${headers_pkg}" \
+29 -8
View File
@@ -6,12 +6,18 @@ source "${MODULES_DIR}/repos/migrate.sh" 2>/dev/null || true
REPO_BACKUP_DIR="" REPO_BACKUP_DIR=""
backup_current_repos() { backup_current_repos() {
REPO_BACKUP_DIR=$(mktemp -d) cleanup_repo_backup # never orphan a previous backup by overwriting the pointer
REPO_BACKUP_DIR=$(mktemp -d) || {
REPO_BACKUP_DIR=""
return 1
}
for f in /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources \ for f in /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources \
/etc/apt/sources.list.d/debian-backports.list /etc/apt/sources.list.d/debian-backports.sources; do /etc/apt/sources.list.d/debian-backports.list /etc/apt/sources.list.d/debian-backports.sources; do
if [ -f "$f" ]; then if [ -f "$f" ]; then
mkdir -p "$REPO_BACKUP_DIR/$(dirname "${f#/etc/apt/}")" local rel="${f#/etc/apt/}"
cp "$f" "$REPO_BACKUP_DIR/$(dirname "${f#/etc/apt/}")/$(basename "$f")" mkdir -p "$REPO_BACKUP_DIR/$(dirname "$rel")" || return 1
cp "$f" "$REPO_BACKUP_DIR/$rel" || return 1
touch "$REPO_BACKUP_DIR/.backed_up_$(basename "$rel")"
fi fi
done done
} }
@@ -27,9 +33,19 @@ restore_previous_repos() {
local rel="${f#/etc/apt/}" local rel="${f#/etc/apt/}"
local backup_file="$REPO_BACKUP_DIR/$rel" local backup_file="$REPO_BACKUP_DIR/$rel"
if [ -f "$backup_file" ]; then if [ -f "$backup_file" ]; then
sudo cp "$backup_file" "$f" || true if sudo cp "$backup_file" "$f"; then
found=true found=true
elif [ -f "$f" ]; then else
echo -e "${RED}Failed to restore $f${NC}"
fi
elif [ -f "$f" ] && [ ! -f "$REPO_BACKUP_DIR/.backed_up_$(basename "$f")" ]; then
# Only delete a live file if the manifest proves it did not exist
# when the backup was taken (protects against a failed cp).
# SECURITY: Verify file is not a symlink to prevent TOCTOU attack.
if [ -L "$f" ]; then
echo -e "${RED}[$f] is a symlink. Aborting to prevent TOCTOU attack.${NC}" >&2
continue
fi
sudo rm -f "$f" || true sudo rm -f "$f" || true
found=true found=true
fi fi
@@ -346,10 +362,15 @@ _components_enabled() {
_repos_offer_upgrade() { _repos_offer_upgrade() {
local upgradable local upgradable
upgradable=$(apt list --upgradable 2>/dev/null | grep -c /) upgradable=$(apt list --upgradable 2>/dev/null | grep -c / || true)
# BH-005: grep -c / returns rc=1 if apt list produces no output
# (0 upgradable packages or network failure). pipefail propagates rc=1.
upgradable=${upgradable:-0}
[[ "$upgradable" =~ ^[0-9]+$ ]] || upgradable=0
# Validate that the result is a positive integer.
# If apt list fails, upgradable stays as "0" and the upgrade is skipped.
if [ "$upgradable" -gt 0 ]; then if [ "$upgradable" -gt 0 ]; then
if _confirm "Upgrade System" "$upgradable packages can be upgraded. Upgrade now?"; then if _confirm "Upgrade System" "$upgradable packages can be upgraded. Upgrade now?"; then
sudo apt-mark hold tzdata 2>/dev/null || true
_run_cmd "Upgrade" "sudo apt upgrade -y" "Upgrading system..." _run_cmd "Upgrade" "sudo apt upgrade -y" "Upgrading system..."
sudo apt-mark unhold tzdata 2>/dev/null || true sudo apt-mark unhold tzdata 2>/dev/null || true
sudo apt autoremove -y sudo apt autoremove -y
+20 -13
View File
@@ -2,6 +2,10 @@
# sudo_config.sh — User Privileges & Feedback submenu # sudo_config.sh — User Privileges & Feedback submenu
# License GPL v3 # License GPL v3
# Resolve the invoking user once. USER may be unset in minimal
# environments (SSH sessions, cron), which would abort under set -u.
TARGET_USER="${SUDO_USER:-${USER:-$(id -un)}}"
config_sudo() { config_sudo() {
echo -e "${YELLOW}User Privileges & Feedback${NC}" echo -e "${YELLOW}User Privileges & Feedback${NC}"
@@ -30,12 +34,12 @@ config_sudo() {
# ── Option 1: Sudo Group Membership ── # ── Option 1: Sudo Group Membership ──
_check_sudo_group() { _check_sudo_group() {
if groups "$USER" | grep -qE '\bsudo\b'; then if groups "$TARGET_USER" | grep -qE '\bsudo\b'; then
_msg "Sudo Group" "User '$USER' is already in the sudo group." _msg "Sudo Group" "User '$TARGET_USER' is already in the sudo group."
else else
if _confirm "Sudo Group" \ if _confirm "Sudo Group" \
"User '$USER' is NOT in the sudo group.\n\nAdd to sudo group?"; then "User '$TARGET_USER' is NOT in the sudo group.\n\nAdd to sudo group?"; then
if sudo usermod -aG sudo "$USER"; then if sudo usermod -aG sudo "$TARGET_USER"; then
_msg "Sudo Group" \ _msg "Sudo Group" \
"User added to sudo group.\n\nLog out and back in for\ngroup changes to take effect." 10 60 "User added to sudo group.\n\nLog out and back in for\ngroup changes to take effect." 10 60
else else
@@ -48,7 +52,10 @@ _check_sudo_group() {
# ── Option 2: Passwordless Sudo (NOPASSWD) ── # ── Option 2: Passwordless Sudo (NOPASSWD) ──
_configure_nopasswd() { _configure_nopasswd() {
local nopasswd_file="/etc/sudoers.d/${USER}-nopasswd" # sudo silently ignores /etc/sudoers.d/ files whose name contains
# '.' or '~' (package manager / editor backup guards).
local safe_user="${TARGET_USER//./_}"
local nopasswd_file="/etc/sudoers.d/${safe_user}-nopasswd"
if [ -f "$nopasswd_file" ]; then if [ -f "$nopasswd_file" ]; then
if _confirm "NOPASSWD" \ if _confirm "NOPASSWD" \
@@ -84,13 +91,13 @@ Useful for automation but reduces security." 14 70; then
for cmd in $cleaned; do for cmd in $cleaned; do
case $cmd in case $cmd in
apt) apt)
content+="${USER} ALL=(root) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get, /bin/apt, /bin/apt-get\n" content+="${TARGET_USER} ALL=(root) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get, /bin/apt, /bin/apt-get\n"
;; ;;
systemctl) systemctl)
content+="${USER} ALL=(root) NOPASSWD: /usr/bin/systemctl, /bin/systemctl\n" content+="${TARGET_USER} ALL=(root) NOPASSWD: /usr/bin/systemctl, /bin/systemctl\n"
;; ;;
power) power)
content+="${USER} ALL=(root) NOPASSWD: /usr/sbin/shutdown, /sbin/shutdown, /usr/sbin/reboot, /sbin/reboot, /usr/sbin/halt, /sbin/halt\n" content+="${TARGET_USER} ALL=(root) NOPASSWD: /usr/sbin/shutdown, /sbin/shutdown, /usr/sbin/reboot, /sbin/reboot, /usr/sbin/halt, /sbin/halt\n"
;; ;;
esac esac
done done
@@ -108,7 +115,7 @@ Useful for automation but reduces security." 14 70; then
# ── Option 3: Repair Home Directory Ownership ── # ── Option 3: Repair Home Directory Ownership ──
_repair_home_ownership() { _repair_home_ownership() {
local home local home
home=$(getent passwd "${SUDO_USER:-$USER}" | cut -d: -f6) home=$(getent passwd "$TARGET_USER" | cut -d: -f6)
if [ ! -d "$home" ]; then if [ ! -d "$home" ]; then
_msg "Home Directory" "Home directory '$home' does not exist." 8 60 _msg "Home Directory" "Home directory '$home' does not exist." 8 60
@@ -116,15 +123,15 @@ _repair_home_ownership() {
fi fi
local uid uid_owner local uid uid_owner
uid=$(id -u "$USER" 2>/dev/null) uid=$(id -u "$TARGET_USER" 2>/dev/null)
uid_owner=$(stat -c '%u' "$home" 2>/dev/null || echo "0") uid_owner=$(stat -c '%u' "$home" 2>/dev/null || echo "0")
if [ "$uid_owner" != "$uid" ]; then if [ "$uid_owner" != "$uid" ]; then
local expected_user local expected_user
expected_user=$(id -nu "$uid_owner" 2>/dev/null || echo "UID $uid_owner") expected_user=$(id -nu "$uid_owner" 2>/dev/null || echo "UID $uid_owner")
if _confirm "Home Permissions" \ if _confirm "Home Permissions" \
"Home directory '$home' is owned by\n'$expected_user' (expected: '$USER').\n\nRepair ownership?" 12 65; then "Home directory '$home' is owned by\n'$expected_user' (expected: '$TARGET_USER').\n\nRepair ownership?" 12 65; then
if sudo chown -R "$USER:$USER" "$home"; then if sudo chown -R "$TARGET_USER:$TARGET_USER" "$home"; then
echo -e "${GREEN}Home directory ownership repaired.${NC}" echo -e "${GREEN}Home directory ownership repaired.${NC}"
else else
echo -e "${RED}Failed to repair home directory ownership.${NC}" echo -e "${RED}Failed to repair home directory ownership.${NC}"
@@ -132,7 +139,7 @@ _repair_home_ownership() {
fi fi
fi fi
else else
_msg "Home Permissions" "Home directory ownership is correct\n(owner: $USER)." 8 60 _msg "Home Permissions" "Home directory ownership is correct\n(owner: $TARGET_USER)." 8 60
fi fi
} }
+2 -1
View File
@@ -117,7 +117,8 @@ _swap_create_file() {
_swap_remove_file() { _swap_remove_file() {
local has_tag local has_tag
has_tag=$(grep -c "$SWAP_FSTAB_TAG" /etc/fstab 2>/dev/null || echo 0) has_tag=$(grep -c "$SWAP_FSTAB_TAG" /etc/fstab 2>/dev/null || true)
has_tag=${has_tag:-0}
[ "$has_tag" -eq 0 ] && [ ! -f "$SWAP_FILE" ] && { [ "$has_tag" -eq 0 ] && [ ! -f "$SWAP_FILE" ] && {
_msg "Swap" "No managed swapfile found." 8 50 _msg "Swap" "No managed swapfile found." 8 50
return return
+84 -113
View File
@@ -45,60 +45,6 @@ check_sudo() {
fi fi
} }
# --------------------------------
# Time sync detection + NTP
# --------------------------------
check_system_time() {
command -v timedatectl &>/dev/null || return
local year
year=$(date +%Y)
if [ "$year" -lt 2025 ]; then
local msg="System date/time appears to be incorrect\n"
msg+="($(date '+%Y-%m-%d %H:%M')). This will prevent Debian\n"
msg+="repositories from working properly.\n\n"
msg+="Attempt automatic NTP synchronization?\n"
msg+="(requires network access and timedatectl)"
if _confirm "System Date" "$msg"; then
sync_system_time
else
echo -e "${YELLOW}Warning: System time is incorrect. Package installations may fail.${NC}"
fi
return
fi
local ntp_active
ntp_active=$(timedatectl show --property=NTP --value 2>/dev/null || echo "no")
if [ "$ntp_active" != "yes" ]; then
sync_system_time
fi
}
sync_system_time() {
command -v timedatectl &>/dev/null || return
if ! is_installed systemd-timesyncd; then
sudo DEBIAN_FRONTEND=noninteractive apt install -y systemd-timesyncd || true
fi
if ! systemctl is-enabled systemd-timesyncd &>/dev/null; then
sudo systemctl enable systemd-timesyncd || true
fi
if ! systemctl is-active systemd-timesyncd &>/dev/null; then
sudo systemctl start systemd-timesyncd || true
fi
sudo timedatectl set-ntp true || true
sleep 4
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}"
else
echo -e "${YELLOW}NTP sync did not complete.${NC}"
fi
}
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Robust time sync: NTP + timezone validation + service restart # Robust time sync: NTP + timezone validation + service restart
# ------------------------------------------------------------------- # -------------------------------------------------------------------
@@ -147,7 +93,7 @@ detect_debian_version() {
DEBIAN_CODENAME=$(grep -oP 'VERSION_CODENAME=\K\w+' /etc/os-release 2>/dev/null || echo "") DEBIAN_CODENAME=$(grep -oP 'VERSION_CODENAME=\K\w+' /etc/os-release 2>/dev/null || echo "")
fi fi
if [ -z "$DEBIAN_CODENAME" ]; then if [ -z "$DEBIAN_CODENAME" ]; then
sync_system_time || true _ensure_time_synced || true
sudo apt update -qq 2>/dev/null && sudo apt install -y -qq lsb-release || true sudo apt update -qq 2>/dev/null && sudo apt install -y -qq lsb-release || true
fi fi
fi fi
@@ -170,19 +116,14 @@ detect_debian_version() {
# ---------------------------------- # ----------------------------------
detect_cpu_ram() { detect_cpu_ram() {
CPU_SUMMARY=$(grep -m1 'model name' /proc/cpuinfo | sed 's/.*: //' || true) CPU_SUMMARY=$(grep -m1 'model name' /proc/cpuinfo | sed 's/.*: //' || true)
RAM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}') RAM_KB=$(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}') || RAM_KB=0
# BH-001: If /proc/meminfo is unavailable or grep finds no MemTotal,
# RAM_KB would be empty under set -u. Assign 0 as safe default.
[ -z "$RAM_KB" ] && RAM_KB=0
RAM_GB=$(awk -v kb="$RAM_KB" 'BEGIN { printf "%.2f", kb / 1048576 }') RAM_GB=$(awk -v kb="$RAM_KB" 'BEGIN { printf "%.2f", kb / 1048576 }')
RAM_SUMMARY="${RAM_GB} GB" RAM_SUMMARY="${RAM_GB} GB"
} }
get_cpu_summary() {
echo "$CPU_SUMMARY"
}
get_ram_summary() {
echo "$RAM_SUMMARY"
}
# ---------------------------------- # ----------------------------------
# Check if running backports kernel # Check if running backports kernel
# ---------------------------------- # ----------------------------------
@@ -203,25 +144,35 @@ is_installed() {
dpkg -l "$1" 2>/dev/null | grep -q '^ii' dpkg -l "$1" 2>/dev/null | grep -q '^ii'
} }
_state() { # ----------------------------------
is_installed "$1" && echo "ON" || echo "OFF" # Package version helpers
# ----------------------------------
# Get the stable version of a package from apt-cache policy
# Returns: version string or "" if not found
_get_pkg_version() {
local pkg="$1"
apt-cache policy "$pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}'
} }
# ---------------------------------- # Get the installed version of a package from dpkg
# Package version lookup # Returns: version string or "" if not installed
# ---------------------------------- _get_installed_version() {
pkg_versions() { local pkg="$1"
local result="" dpkg -l "$pkg" 2>/dev/null | awk '/^ii/{print $3; exit}'
for pkg in "$@"; do }
local ver
ver=$(apt-cache policy "$pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}') # Get the backports version of a package
if [ -n "$ver" ] && [ "$ver" != "(none)" ]; then # Returns: version string or "" if not found
result+=" - ${pkg} ${ver}\n" _get_backports_version() {
else local pkg="$1"
result+=" - ${pkg}\n" local codename="${2:-$DEBIAN_CODENAME}"
fi apt-cache madison "$pkg" 2>/dev/null |
done grep "${codename}-backports" | awk '{print $3}' | head -1
echo -e "$result" }
_state() {
is_installed "$1" && echo "ON" || echo "OFF"
} }
# ---------------------------------- # ----------------------------------
@@ -299,14 +250,14 @@ detect_gpu() {
local nv_ver local nv_ver
nv_ver=$(timeout 3 nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1) || true nv_ver=$(timeout 3 nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1) || true
if [ -z "$nv_ver" ]; then if [ -z "$nv_ver" ]; then
nv_ver=$(dpkg -l nvidia-driver 2>/dev/null | awk '/^ii/ {print $3}' | sed 's/-.*//') || true nv_ver=$(_get_installed_version "nvidia-driver" | sed 's/-.*//') || true
fi fi
[ -n "$nv_ver" ] && GPU_VERSION="NVIDIA $nv_ver" [ -n "$nv_ver" ] && GPU_VERSION="NVIDIA $nv_ver"
fi fi
if [ -z "$GPU_VERSION" ]; then if [ -z "$GPU_VERSION" ]; then
local mesa_ver local mesa_ver
mesa_ver=$(dpkg -l libgl1-mesa-dri 2>/dev/null | awk '/^ii/ {print $3; exit}' | sed 's/-.*//') mesa_ver=$(_get_installed_version "libgl1-mesa-dri" | sed 's/-.*//')
[ -n "$mesa_ver" ] && GPU_VERSION="Mesa $mesa_ver" [ -n "$mesa_ver" ] && GPU_VERSION="Mesa $mesa_ver"
fi fi
} }
@@ -338,6 +289,20 @@ declare -a WIFI_IPS=()
declare -a WIFI_SSIDS=() declare -a WIFI_SSIDS=()
detect_network() { detect_network() {
# BH-013: Reset all network arrays at the start.
# Without this, if detect_network() is called more than once (e.g. from
# refresh_system_state()), the arrays ETH_NAMES, WIFI_NAMES, etc. would
# accumulate duplicates instead of being reset, causing incorrect data.
ETH_NAMES=()
ETH_STATES=()
ETH_IPS=()
ETH_DESCS=()
WIFI_NAMES=()
WIFI_STATES=()
WIFI_IPS=()
WIFI_SSIDS=()
WIFI_DESCS=()
local eth_line local eth_line
eth_line=$(echo "$LSPCI_OUTPUT" | grep -i 'Ethernet controller' | head -n1) || true eth_line=$(echo "$LSPCI_OUTPUT" | grep -i 'Ethernet controller' | head -n1) || true
if [ -n "$eth_line" ]; then if [ -n "$eth_line" ]; then
@@ -550,14 +515,13 @@ install_backports_or_stable() {
local bpo_ver="" local bpo_ver=""
if [ "$(is_backports_enabled)" == true ]; then if [ "$(is_backports_enabled)" == true ]; then
bpo_ver=$(apt-cache madison "$pkg" 2>/dev/null | bpo_ver=$(_get_backports_version "$pkg")
grep "${DEBIAN_CODENAME}-backports" | awk '{print $3}' | head -1)
fi fi
if is_installed "$pkg"; then if is_installed "$pkg"; then
if [ -n "$bpo_ver" ]; then if [ -n "$bpo_ver" ]; then
local current_ver local current_ver
current_ver=$(dpkg -l "$pkg" 2>/dev/null | awk '/^ii/{print $3}') current_ver=$(_get_installed_version "$pkg")
if _confirm "Backports: ${pkg}" \ if _confirm "Backports: ${pkg}" \
"${pkg} ${current_ver} installed.\nUpgrade to backports ${bpo_ver}?"; then "${pkg} ${current_ver} installed.\nUpgrade to backports ${bpo_ver}?"; then
_run_cmd "Backports" \ _run_cmd "Backports" \
@@ -572,7 +536,7 @@ install_backports_or_stable() {
if [ -n "$bpo_ver" ]; then if [ -n "$bpo_ver" ]; then
local stable_ver local stable_ver
stable_ver=$(apt-cache policy "$pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}') stable_ver=$(_get_pkg_version "$pkg")
if _confirm_custom "${pkg}" "Install ${pkg_desc}?\n\n Backports: ${bpo_ver} (newer, recommended for gaming/newer HW)\n Stable: ${stable_ver:-N/A}\n\nChoose version:" "Backports" "Stable"; then if _confirm_custom "${pkg}" "Install ${pkg_desc}?\n\n Backports: ${bpo_ver} (newer, recommended for gaming/newer HW)\n Stable: ${stable_ver:-N/A}\n\nChoose version:" "Backports" "Stable"; then
_run_cmd "Backports" \ _run_cmd "Backports" \
"sudo DEBIAN_FRONTEND=noninteractive apt install -y -t ${DEBIAN_CODENAME}-backports $pkg" \ "sudo DEBIAN_FRONTEND=noninteractive apt install -y -t ${DEBIAN_CODENAME}-backports $pkg" \
@@ -583,7 +547,7 @@ install_backports_or_stable() {
return return
fi fi
local stable_ver local stable_ver
stable_ver=$(apt-cache policy "$pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}') stable_ver=$(_get_pkg_version "$pkg")
if _confirm "Install: ${pkg}" "Install ${pkg} ${stable_ver:-}?"; then if _confirm "Install: ${pkg}" "Install ${pkg} ${stable_ver:-}?"; then
_run_cmd "APT" "sudo DEBIAN_FRONTEND=noninteractive apt install -y $pkg" "Installing $pkg..." _run_cmd "APT" "sudo DEBIAN_FRONTEND=noninteractive apt install -y $pkg" "Installing $pkg..."
fi fi
@@ -608,7 +572,11 @@ _confirm_custom() {
} }
_msg() { _msg() {
whiptail --title "$1" --msgbox "$2" "${3:-10}" "${4:-65}" || true local _msg_title="$1"
local _msg_text="$2"
# BH-014: Escape '%' to prevent whiptail from interpreting them as printf format.
_msg_text="${_msg_text//%/%%}"
whiptail --title "$_msg_title" --msgbox "$_msg_text" "${3:-10}" "${4:-65}" || true
} }
_msg_red() { _msg_red() {
@@ -690,23 +658,13 @@ _is_headless() {
[ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ] [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]
} }
_run_install() {
local pkg="$1"
local ver
ver=$(apt-cache policy "$pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}')
[ -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..."
fi
}
_run_install_batch() { _run_install_batch() {
local pkgs=("$@") local pkgs=("$@")
[ ${#pkgs[@]} -eq 0 ] && return 0 [ ${#pkgs[@]} -eq 0 ] && return 0
local ver_list="" local ver_list=""
for pkg in "${pkgs[@]}"; do for pkg in "${pkgs[@]}"; do
local ver local ver
ver=$(apt-cache policy "$pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}') ver=$(_get_pkg_version "$pkg")
ver_list+=" - ${pkg} ${ver:-unknown}\n" ver_list+=" - ${pkg} ${ver:-unknown}\n"
done done
if _confirm "Install" "Install these packages?\n${ver_list}"; then if _confirm "Install" "Install these packages?\n${ver_list}"; then
@@ -714,25 +672,34 @@ _run_install_batch() {
fi fi
} }
_run_install_pkg() { # Install a package with confirmation prompt.
# Handles set -e: failures are caught and reported, not fatal.
_install_pkg() {
local pkg="$1" local pkg="$1"
local ver local ver
ver=$(apt-cache policy "$pkg" 2>/dev/null | awk 'NR==3 {print $2; exit}') ver=$(_get_pkg_version "$pkg")
[ -z "$ver" ] && ver="(unknown)" [ -z "$ver" ] && ver="(version unknown)"
if _confirm "Install: ${pkg}" "Package: ${pkg}\nVersion: ${ver}\n\nProceed with installation?"; then if _confirm "Install: ${pkg}" "Install ${pkg}\nVersion: ${ver}?"; then
_run_cmd "Install" "sudo DEBIAN_FRONTEND=noninteractive apt install -y $pkg" "Installing $pkg..." _run_cmd "Install" "sudo DEBIAN_FRONTEND=noninteractive apt install -y $pkg" "Installing $pkg..."
fi fi
} }
get_backports_kernel_version() { # Install a package if not already installed.
local ver # Returns: 0 if already installed, 1 if install failed, 2 if cancelled
ver=$(apt-cache policy linux-image-amd64 2>/dev/null | _install_if_missing() {
grep -E '^[[:space:]]+[0-9]+\.[0-9]+\.[0-9]+.*~bpo' | head -n1 | awk '{print $1}') local pkg="$1"
if [ -n "$ver" ]; then if is_installed "$pkg"; then
echo "$ver" echo -e "${GREEN}[+]${NC} $pkg already installed."
else return 0
echo "unknown"
fi fi
_run_cmd "Install" "sudo DEBIAN_FRONTEND=noninteractive apt install -y $pkg" \
"Installing $pkg..."
return $?
}
# _run_install() wrapper: redirect to _install_pkg for consistency
_run_install() {
_install_pkg "$@"
} }
# ---------------------------------- # ----------------------------------
@@ -751,7 +718,9 @@ _detect_lang_pkg() {
[ "$lang2" = "en" ] && echo "" && return [ "$lang2" = "en" ] && echo "" && return
local full="${LANG%%.*}" # Defensive: LANG may be unset in minimal environments; do not trip set -u.
local full="${LANG:-C}"
full="${full%%.*}"
local hyphenated_full local hyphenated_full
hyphenated_full=$(echo "$full" | tr '[:upper:]' '[:lower:]' | tr '_' '-') hyphenated_full=$(echo "$full" | tr '[:upper:]' '[:lower:]' | tr '_' '-')
local pkg local pkg
@@ -838,4 +807,6 @@ refresh_system_state() {
detect_cpu_ram detect_cpu_ram
detect_network detect_network
detect_desktop_environment detect_desktop_environment
detect_displayserver
detect_audio_server
} }
+25 -16
View File
@@ -15,10 +15,10 @@ zram_menu() {
clear clear
case "$choice" in case "$choice" in
1) _zram_view ;; 1) _zram_view ;;
2) _zram_create ;; 2) _zram_create ;;
3) _zram_remove ;; 3) _zram_remove ;;
4) break ;; 4) break ;;
esac esac
done done
} }
@@ -33,11 +33,11 @@ _zram_view() {
if [ -f /etc/default/zramswap ]; then if [ -f /etc/default/zramswap ]; then
while IFS='=' read -r key val; do while IFS='=' read -r key val; do
case "$key" in case "$key" in
ALGO) algo=$val ;; ALGO) algo=$val ;;
SIZE) size=$val ;; SIZE) size=$val ;;
PRIORITY) priority=$val ;; PRIORITY) priority=$val ;;
esac esac
done < /etc/default/zramswap done </etc/default/zramswap
fi fi
local info="ZRAM Configuration:\n" local info="ZRAM Configuration:\n"
@@ -68,11 +68,11 @@ _zram_create() {
if [ -f /etc/default/zramswap ]; then if [ -f /etc/default/zramswap ]; then
while IFS='=' read -r key val; do while IFS='=' read -r key val; do
case "$key" in case "$key" in
ALGO) cur_algo=$val ;; ALGO) cur_algo=$val ;;
SIZE) cur_size=$val ;; SIZE) cur_size=$val ;;
PRIORITY) cur_prio=$val ;; PRIORITY) cur_prio=$val ;;
esac esac
done < /etc/default/zramswap done </etc/default/zramswap
fi fi
local cur="ZRAM is already configured:\n" local cur="ZRAM is already configured:\n"
cur+=" Algorithm: ${cur_algo:-not set}\n" cur+=" Algorithm: ${cur_algo:-not set}\n"
@@ -85,18 +85,18 @@ _zram_create() {
fi fi
fi fi
local ram_gb=$(( RAM_KB / 1024 / 1024 )) local ram_gb=$((RAM_KB / 1024 / 1024))
if [ "$ram_gb" -gt 8 ]; then if [ "$ram_gb" -gt 8 ]; then
recommended_mb=4096 recommended_mb=4096
else else
recommended_mb=$(( ((RAM_KB / 1024 / 1024 + 1) / 2) * 1024 )) recommended_mb=$((((RAM_KB / 1024 / 1024 + 1) / 2) * 1024))
fi fi
local algo local algo
algo=$(_menu "ZRAM Configuration" \ algo=$(_menu "ZRAM Configuration" \
"ZRAM creates a compressed swap device in RAM to reduce disk I/O and boost speed. Data is stored compressed in memory. Choose an algorithm below to balance CPU usage and compression ratio:" \ "ZRAM creates a compressed swap device in RAM to reduce disk I/O and boost speed. Data is stored compressed in memory. Choose an algorithm below to balance CPU usage and compression ratio:" \
$TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \ $TUI_ALTO $TUI_ANCHO $TUI_ALTO_LISTA \
"lz4" "Fastest compression. Lowest CPU overhead. (Default)" \ "lz4" "Fastest compression. Lowest CPU overhead. (Default)" \
"zstd" "Higher compression ratio. Saves more RAM, uses more CPU.") "zstd" "Higher compression ratio. Saves more RAM, uses more CPU.")
if [ -z "$algo" ]; then if [ -z "$algo" ]; then
@@ -127,7 +127,16 @@ _zram_create() {
sudo modprobe -r zram 2>/dev/null || true sudo modprobe -r zram 2>/dev/null || true
echo "Writing configuration..." echo "Writing configuration..."
sudo tee /etc/default/zramswap > /dev/null <<EOF # SECURITY: Validate algo and size before writing to system file.
[[ "$algo" =~ ^(lz4|zstd)$ ]] || {
echo "Invalid ZRAM algorithm" >&2
return 1
}
[[ "$zram_size" =~ ^[0-9]+$ ]] || {
echo "Invalid ZRAM size" >&2
return 1
}
sudo tee /etc/default/zramswap >/dev/null <<EOF
ALGO=$algo ALGO=$algo
SIZE=$zram_size SIZE=$zram_size
PRIORITY=100 PRIORITY=100