Integrity fixes: atomic writes, rc checks, curl

- Verified the repository backup rc in migrate.sh. `_persistent_backup_repos()` now aborts before any destructive step when the tar archive is missing or corrupt, removing the invalid backup itself.
- Verified the restore rc in migrate.sh. `_restore_backup()` now reports failure instead of printing a green "Backup restored" message, and its caller distinguishes a restored backup from a failed restore.
- Made the upgrade phases fail honestly. `apt upgrade` and `apt full-upgrade` now abort with `apt --fix-broken install` + `dpkg --configure -a` instructions instead of reporting "completed successfully" over a partially-upgraded system.
- Made repository source writes atomic. Both writers tee into a `.tmp` file and move into place, and the `/etc/apt/sources.list` removal now happens after the write.
- Corrected the SID warning text in migrate.sh to reflect that security fixes arrive via package uploads rather than a nonexistent sid-security suite.
- Added exit-path cleanup traps for the temp `.deb` files: `trap 'rm -f "$deb_path"' RETURN` in `heroic.sh`and `tools.sh`.
- Tightened Heroic download handling: the API fetch uses `curl -fsS --connect-timeout 10 --max-time 30`, the download uses `curl -fsSL --max-time 300`, and the `.deb` is validated with both `dpkg-deb --info` and `dpkg-deb --contents`; `sudo apt install` is guarded with `if !` before the temporary file is deleted.
- Tightened the OpenRGB install path: the Codeberg API fetch uses `curl -fsS --connect-timeout 10 --max-time 30` and the download `curl -fsSL --max-time 300`, with the same dual `dpkg-deb` integrity check as the Heroic fallback.
- Guarded the OpenRGB post-install side effects: the script now aborts after removing the failed `.deb` if `sudo apt install` returns non-zero, so `modprobe i2c-dev`, `/etc/modules`, the `i2c` group membership, `udevadm`, and the `setcap` step no longer run when the package is absent.
This commit is contained in:
stornic56
2026-10-04 23:39:40 -05:00
committed by GitHub
parent 454207209a
commit 21e9973c2f
22 changed files with 975 additions and 142 deletions
+4
View File
@@ -131,6 +131,10 @@ install_nvidia_bullseye() {
# 32-bit Mesa/NVIDIA + gamemode + mangohud + goverlay + lutris
# ---------------------------------------------------------------------------
install_gaming_bullseye() {
# Hardware state is detected on demand; GPU_TYPE is
# consulted below for the 32-bit driver choice.
_ensure_state_detected
echo -e "${YELLOW}Gaming setup (Bullseye)...${NC}"
local choices
+4
View File
@@ -1,6 +1,10 @@
#!/usr/bin/env bash
manage_desktop_display() {
# Hardware state is detected on demand; HAS_NVIDIA
# (GDM3 Wayland option) is populated by the battery.
_ensure_state_detected
while true; do
local choice
choice=$(_menu "Desktop & Display" \
+4
View File
@@ -346,6 +346,10 @@ _ensure_nonfree_repo() {
# ── Main entry point ──
install_firmware() {
# Hardware state is detected on demand; the device
# arrays and DESKTOP_ENV/AUDIO_SERVER are read below.
_ensure_state_detected
echo -e "${YELLOW}Base firmware check...${NC}"
if ! _ensure_nonfree_repo; then
+4
View File
@@ -69,6 +69,10 @@ ensure_contrib_repo() {
}
install_gaming() {
# Hardware state is detected on demand; GPU_TYPE is
# consulted below for the NVIDIA hints.
_ensure_state_detected
echo -e "${YELLOW}Gaming setup...${NC}"
# 1. Single checklist with ALL options (including i386 toggle)
+17 -4
View File
@@ -4,12 +4,18 @@
install_heroic() {
local heroic_deb
heroic_deb=$(mktemp "${TMPDIR:-/tmp}/heroic-XXXXXX.deb") || return 1
# Remove the temp file on every exit path (RETURN
# traps are not inherited by called functions).
trap 'rm -f "$heroic_deb"' RETURN
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..."
local json
json=$(curl -s --connect-timeout 10 -H "User-Agent: $ua" \
# -f: HTTP >= 400 must fail; --max-time bounds the
# whole transfer (connect-timeout only covers the
# connection phase).
json=$(curl -fsS --connect-timeout 10 --max-time 30 -H "User-Agent: $ua" \
"https://api.github.com/repos/Heroic-Games-Launcher/HeroicGamesLauncher/releases/latest") || {
_msg "Heroic Error" "Could not fetch release data from GitHub API." 8 60
return 1
@@ -28,16 +34,23 @@ install_heroic() {
# Strict URL validation before interpolating into a shell command (_run_cmd uses bash -c)
[[ "$deb_url" =~ ^https://[A-Za-z0-9./_-]+\.deb$ ]] || { _msg "Error" "Invalid download URL: $deb_url"; return 1; }
_run_cmd "Heroic" "curl -fsSL -H 'User-Agent: $ua' -o '$heroic_deb' '$deb_url'" "Downloading Heroic..."
_run_cmd "Heroic" "curl -fsSL --max-time 300 -H 'User-Agent: $ua' -o '$heroic_deb' '$deb_url'" "Downloading Heroic..."
if ! dpkg-deb --info "$heroic_deb" >/dev/null 2>&1; then
# --info reads only the control archive; --contents
# also walks the data archive, catching deb files
# truncated after the control section.
if ! dpkg-deb --info "$heroic_deb" >/dev/null 2>&1 || \
! dpkg-deb --contents "$heroic_deb" >/dev/null 2>&1; then
_msg "Heroic Error" "Downloaded .deb is corrupted or truncated.\n\nRemoving file." 10 60
rm -f "$heroic_deb"
return 1
fi
echo -e "${GREEN}Package integrity verified.${NC}"
_run_cmd "Heroic" "sudo apt install -y '$heroic_deb'" "Installing Heroic..."
if ! _run_cmd "Heroic" "sudo apt install -y '$heroic_deb'" "Installing Heroic..."; then
rm -f "$heroic_deb"
return 1
fi
rm -f "$heroic_deb"
echo -e "${GREEN}Heroic Games Launcher installed.${NC}"
}
+19 -4
View File
@@ -38,12 +38,16 @@ install_openrgb() {
local deb_path
deb_path=$(mktemp "${TMPDIR:-/tmp}/openrgb-XXXXXX.deb") || return 1
# Remove the temp file on every exit path.
trap 'rm -f "$deb_path"' RETURN
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..."
local json=""
json=$(curl -s --connect-timeout 10 \
# -f: HTTP >= 400 must fail; --max-time bounds the
# whole transfer.
json=$(curl -fsS --connect-timeout 10 --max-time 30 \
"https://codeberg.org/api/v1/repos/OpenRGB/OpenRGB/releases?limit=1") || {
echo -e "${RED}Could not fetch OpenRGB releases from Codeberg API.${NC}"
return 1
@@ -66,7 +70,7 @@ install_openrgb() {
# Strict URL validation before interpolating into a shell command (_run_cmd uses bash -c)
[[ "$deb_url" =~ ^https://[A-Za-z0-9./_-]+\.deb$ ]] || { _msg "Error" "Invalid download URL: $deb_url"; return 1; }
_run_cmd "OpenRGB" "curl -fsSL -o '${deb_path}' -A '${ua}' '${deb_url}'" "Downloading OpenRGB..."
_run_cmd "OpenRGB" "curl -fsSL --max-time 300 -o '${deb_path}' -A '${ua}' '${deb_url}'" "Downloading OpenRGB..."
if [ -n "$sha256" ]; then
if ! echo "$sha256 $deb_path" | sha256sum -c --strict; then
@@ -76,7 +80,11 @@ install_openrgb() {
fi
echo -e "${GREEN}SHA256 verified.${NC}"
else
if ! dpkg-deb --info "$deb_path" >/dev/null 2>&1; then
# --contents also walks the data archive,
# catching deb files truncated after the
# control section.
if ! dpkg-deb --info "$deb_path" >/dev/null 2>&1 || \
! dpkg-deb --contents "$deb_path" >/dev/null 2>&1; then
echo -e "${RED}Downloaded .deb is corrupted. Removing.${NC}"
rm -f "$deb_path"
return 1
@@ -84,7 +92,14 @@ install_openrgb() {
echo -e "${YELLOW}No SHA256 in API, validated via dpkg-deb.${NC}"
fi
sudo apt install -y "$deb_path"
# The post-install steps (i2c group, udev rules)
# must only run if the package was actually
# installed; apt can fail on missing dependencies.
if ! sudo apt install -y "$deb_path"; then
rm -f "$deb_path"
echo -e "${RED}OpenRGB installation failed (dependency or dpkg error).${NC}"
return 1
fi
rm -f "$deb_path"
sudo modprobe i2c-dev
+8
View File
@@ -28,6 +28,10 @@ _warn_nvidia_gnome_wayland() {
}
_install_amd_intel_stack() {
# Hardware state is detected on demand; GPU_TYPE and
# LSPCI_OUTPUT must be populated before planning.
_ensure_state_detected
if [ "$GPU_TYPE" = "unknown" ] || [ -z "$GPU_TYPE" ]; then
local mesa_pkgs=(mesa-vulkan-drivers libgl1-mesa-dri libglx-mesa0 libegl-mesa0 mesa-va-drivers)
local ref_ver
@@ -174,6 +178,10 @@ _apply_amd_gcn_grub_fix() {
}
_install_nvidia_stack() {
# Hardware state is detected on demand; HAS_NVIDIA and
# LSPCI_OUTPUT must be populated before planning.
_ensure_state_detected
if ! $HAS_NVIDIA; then
_msg "NVIDIA Not Found" "No NVIDIA GPU was detected.\n\nPlease check your hardware and try again." 10 60
return
+4
View File
@@ -33,6 +33,10 @@ show_kernel_menu() {
}
_install_kernel_package() {
# Hardware state is detected on demand; GPU_TYPE drives
# the NVIDIA DKMS/RT warnings below.
_ensure_state_detected
local pkg_base="$1"
local flavor="$2"
local bpo_flag="$3"
+68 -16
View File
@@ -13,7 +13,13 @@ _persistent_backup_repos() {
[ -f /etc/apt/sources.list ] && files+=("/etc/apt/sources.list")
[ -d /etc/apt/sources.list.d ] && files+=("/etc/apt/sources.list.d")
[ ${#files[@]} -eq 0 ] && return 1
sudo tar czf "$_MIGRATE_BACKUP" "${files[@]}" 2>/dev/null
# The rollback promise depends on this archive:
# a failed backup must abort before anything is
# deleted or rewritten.
if ! sudo tar czf "$_MIGRATE_BACKUP" "${files[@]}" 2>/dev/null; then
rm -f "$_MIGRATE_BACKUP"
return 1
fi
echo "Backup: $_MIGRATE_BACKUP"
}
@@ -23,7 +29,13 @@ _restore_backup() {
return 1
fi
echo -e "${YELLOW}Restoring repository backup...${NC}"
sudo tar xzf "$_MIGRATE_BACKUP" -C / 2>/dev/null
if ! sudo tar xzf "$_MIGRATE_BACKUP" -C / 2>/dev/null; then
_msg_red "Restore Failed" \
"Extraction of $_MIGRATE_BACKUP failed.\n\
/etc/apt sources may be missing or inconsistent.\n\n\
Restore manually:\n sudo tar xzf $_MIGRATE_BACKUP -C /" 12 70
return 1
fi
echo -e "${GREEN}Backup restored from $_MIGRATE_BACKUP${NC}"
}
@@ -64,7 +76,14 @@ _write_deb822_branch() {
fi
sudo mkdir -p /etc/apt/sources.list.d
echo -e "$main_content" | sudo tee "$main_file" >/dev/null
# Stage to a temp file and move atomically: an
# interrupted write never leaves a partial sources.
if ! echo -e "$main_content" | sudo tee "${main_file}.tmp" >/dev/null; then
return 1
fi
if ! sudo mv "${main_file}.tmp" "$main_file"; then
return 1
fi
echo "Wrote $main_file"
}
@@ -80,7 +99,14 @@ _write_classic_branch() {
main_content+="deb https://security.debian.org/debian-security ${target}-security main contrib non-free non-free-firmware\n"
fi
echo -e "$main_content" | sudo tee "$main_file" >/dev/null
# Stage to a temp file and move atomically: an
# interrupted write never leaves a partial sources.
if ! echo -e "$main_content" | sudo tee "${main_file}.tmp" >/dev/null; then
return 1
fi
if ! sudo mv "${main_file}.tmp" "$main_file"; then
return 1
fi
echo "Wrote $main_file"
}
@@ -93,7 +119,8 @@ Risks include:\n\
• NVIDIA / DKMS drivers may break\n\
• System may fail to boot after reboot\n\
• Some packages may be removed or replaced\n\
• SID (unstable) receives NO security updates\n\n\
• SID (unstable) has no release cycle; security\n\
fixes arrive via package uploads, not DSAs\n\n\
A full persistent backup will be saved to /var/backups/\n\
so you can restore if things go wrong." 16 70
@@ -154,16 +181,20 @@ so you can restore if things go wrong." 16 70
[ -f /etc/apt/sources.list.d/debian-backports.sources ] && sudo rm -f /etc/apt/sources.list.d/debian-backports.sources
[ -f /etc/apt/sources.list.d/debian-backports.list ] && sudo rm -f /etc/apt/sources.list.d/debian-backports.list
# 4c. Remove any old classic source files to avoid conflicts
[ -f /etc/apt/sources.list ] && sudo rm -f /etc/apt/sources.list
# 4d. Write new sources
# 4c. Write new sources
if ! _write_branch_sources "$target"; then
echo -e "${RED}[-]${NC} Failed to write new sources. Restoring backup..."
_restore_backup || true
return 1
fi
# 4d. Remove the old classic file AFTER the new
# sources are live: an interrupt between steps
# leaves both files (recoverable), not none.
# Only needed on the deb822 path; the classic
# writer replaced sources.list atomically.
[ "$DEBIAN_VERSION" = "13" ] && [ -f /etc/apt/sources.list ] && sudo rm -f /etc/apt/sources.list
# 4e. SID guardrails: install bug alerts before upgrade
if [ "$target" = "sid" ]; then
echo -e "${YELLOW}Installing apt-listbugs and apt-listchanges (SID guardrails)...${NC}"
@@ -175,19 +206,40 @@ so you can restore if things go wrong." 16 70
echo -e "${YELLOW}Running apt update...${NC}"
if ! sudo apt update; then
echo -e "${RED}apt update failed. Restoring backup...${NC}"
_restore_backup
_msg_red "Migration Failed" \
"apt update failed. Backup has been restored from:\n\
if _restore_backup; then
_msg_red "Migration Failed" \
"apt update failed. Backup has been restored from:\n\
$_MIGRATE_BACKUP\n\n\
Your system should be back to its previous state.\n\
Run 'sudo apt update' manually to verify." 12 70
return
else
_msg_red "Migration Failed" \
"apt update failed AND the backup could not be\n\
restored from $_MIGRATE_BACKUP.\n\n\
Restore it manually:\n sudo tar xzf $_MIGRATE_BACKUP -C /\n\
then run: sudo apt update" 12 70
fi
return 1
fi
# 4g. Full upgrade
_run_cmd "Upgrade" "sudo apt upgrade -y" "Upgrading packages..."
_run_cmd "Full-Upgrade" "sudo apt full-upgrade -y" "Running full-upgrade..."
_run_cmd "Autoremove" "sudo apt autoremove -y" "Removing obsolete packages..."
# A failed upgrade leaves packages half-installed.
# Restoring only the sources would be worse (stable
# sources + testing packages), so stop with an
# honest report and manual-recovery instructions.
if ! _run_cmd "Upgrade" "sudo apt upgrade -y" "Upgrading packages..."; then
_msg_red "Migration Incomplete" \
"Package upgrade failed. The system is partially\n\
upgraded to ${target}. Do NOT reboot yet.\n\n\
Repair with:\n sudo apt --fix-broken install\n sudo dpkg --configure -a\n\n\
To roll back the branch manually, restore:\n sudo tar xzf $_MIGRATE_BACKUP -C /" 14 70
return 1
fi
if ! _run_cmd "Full-Upgrade" "sudo apt full-upgrade -y" "Running full-upgrade..."; then
_msg_red "Migration Incomplete" "full-upgrade failed. Repair with:\n sudo apt --fix-broken install\nBackup: $_MIGRATE_BACKUP" 10 70
return 1
fi
_run_cmd "Autoremove" "sudo apt autoremove -y" "Removing obsolete packages..." || true
# 4h. Re-run detection to reflect new branch
echo -e "${YELLOW}Re-running system detection for new branch...${NC}"
+32 -27
View File
@@ -5,12 +5,13 @@
# Any active (non-commented) source of any format, in any file?
# Returns: 0 if at least one active source exists, 1 otherwise
has_active_deb_sources() {
local apt_dir="${APT_DIR:-/etc/apt}"
local f
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do
for f in "$apt_dir/sources.list" "$apt_dir/sources.list.d/"*.list; do
[ -f "$f" ] || continue
grep -qE '^[^#]*\bdeb\b' "$f" 2>/dev/null && return 0
done
for f in /etc/apt/sources.list.d/*.sources; do
for f in "$apt_dir/sources.list.d/"*.sources; do
[ -f "$f" ] || continue
grep -qE '^Types:.*\bdeb\b' "$f" 2>/dev/null && \
grep -qE '^URIs:' "$f" 2>/dev/null && return 0
@@ -22,10 +23,11 @@ has_active_deb_sources() {
# DEB822 is only valid on Debian 13 (Trixie); Debian 11/12 are classic-only.
# Returns: "deb822", "classic", or "none"
detect_repo_format() {
if [ "$DEBIAN_VERSION" = "13" ] && [ -f /etc/apt/sources.list.d/debian.sources ] && \
grep -qE '^Types:.*\bdeb\b' /etc/apt/sources.list.d/debian.sources 2>/dev/null; then
local apt_dir="${APT_DIR:-/etc/apt}"
if [ "$DEBIAN_VERSION" = "13" ] && [ -f "$apt_dir/sources.list.d/debian.sources" ] && \
grep -qE '^Types:.*\bdeb\b' "$apt_dir/sources.list.d/debian.sources" 2>/dev/null; then
echo "deb822"
elif [ -f /etc/apt/sources.list ] && grep -qE '^[^#]*\bdeb\b' /etc/apt/sources.list 2>/dev/null; then
elif [ -f "$apt_dir/sources.list" ] && grep -qE '^[^#]*\bdeb\b' "$apt_dir/sources.list" 2>/dev/null; then
echo "classic"
else
echo "none"
@@ -35,17 +37,18 @@ detect_repo_format() {
# Detect the components currently active in the main repo file
# Returns: components list (e.g. "main contrib non-free non-free-firmware")
detect_active_components() {
if [ "$DEBIAN_VERSION" = "13" ] && [ -f /etc/apt/sources.list.d/debian.sources ]; then
local apt_dir="${APT_DIR:-/etc/apt}"
if [ "$DEBIAN_VERSION" = "13" ] && [ -f "$apt_dir/sources.list.d/debian.sources" ]; then
local comps
comps=$(grep "^Components:" /etc/apt/sources.list.d/debian.sources 2>/dev/null | head -1 | cut -d: -f2- | sed 's/^[[:space:]]*//')
comps=$(grep "^Components:" "$apt_dir/sources.list.d/debian.sources" 2>/dev/null | head -1 | cut -d: -f2- | sed 's/^[[:space:]]*//')
if [ -n "$comps" ]; then
echo "$comps"
return
fi
fi
if [ -f /etc/apt/sources.list ]; then
if [ -f "$apt_dir/sources.list" ]; then
local comps
comps=$(grep "^[^#]*deb .* main" /etc/apt/sources.list 2>/dev/null | head -1 | sed 's/.*main\s*//')
comps=$(grep "^[^#]*deb .* main" "$apt_dir/sources.list" 2>/dev/null | head -1 | sed 's/.*main\s*//')
if [ -n "$comps" ]; then
echo "main $comps"
return
@@ -61,17 +64,18 @@ detect_active_components() {
# Check whether backports are currently enabled (any format, any file)
# Returns: 0 if enabled, 1 otherwise
detect_backports_status() {
local apt_dir="${APT_DIR:-/etc/apt}"
local codename="$1"
if [ "$DEBIAN_VERSION" = "13" ] && [ -f /etc/apt/sources.list.d/debian.sources ]; then
grep -qE "^Suites:.*${codename}-backports" /etc/apt/sources.list.d/debian.sources 2>/dev/null && return 0
if [ "$DEBIAN_VERSION" = "13" ] && [ -f "$apt_dir/sources.list.d/debian.sources" ]; then
grep -qE "^Suites:.*${codename}-backports" "$apt_dir/sources.list.d/debian.sources" 2>/dev/null && return 0
fi
if [ -f /etc/apt/sources.list ]; then
grep -qE "^[^#]*${codename}-backports" /etc/apt/sources.list 2>/dev/null && return 0
if [ -f "$apt_dir/sources.list" ]; then
grep -qE "^[^#]*${codename}-backports" "$apt_dir/sources.list" 2>/dev/null && return 0
fi
if [ -d /etc/apt/sources.list.d ]; then
grep -qrE "^Suites:.*${codename}-backports" /etc/apt/sources.list.d/*.sources 2>/dev/null && return 0
grep -qrE "^[^#]*${codename}-backports" /etc/apt/sources.list.d/*.list 2>/dev/null && return 0
if [ -d "$apt_dir/sources.list.d" ]; then
grep -qrE "^Suites:.*${codename}-backports" "$apt_dir/sources.list.d/"*.sources 2>/dev/null && return 0
grep -qrE "^[^#]*${codename}-backports" "$apt_dir/sources.list.d/"*.list 2>/dev/null && return 0
fi
return 1
@@ -84,24 +88,25 @@ detect_backports_status() {
# "embedded-classic" (inside sources.list),
# "none"
detect_backports_location() {
local apt_dir="${APT_DIR:-/etc/apt}"
local codename="$1"
if [ "$DEBIAN_VERSION" = "13" ] && [ -f /etc/apt/sources.list.d/debian-backports.sources ] && \
grep -qE "^Suites:.*${codename}-backports" /etc/apt/sources.list.d/debian-backports.sources 2>/dev/null; then
if [ "$DEBIAN_VERSION" = "13" ] && [ -f "$apt_dir/sources.list.d/debian-backports.sources" ] && \
grep -qE "^Suites:.*${codename}-backports" "$apt_dir/sources.list.d/debian-backports.sources" 2>/dev/null; then
echo "standalone-deb822"
elif [ -f /etc/apt/sources.list.d/debian-backports.list ] && \
grep -qE "^[^#]*${codename}-backports" /etc/apt/sources.list.d/debian-backports.list 2>/dev/null; then
elif [ -f "$apt_dir/sources.list.d/debian-backports.list" ] && \
grep -qE "^[^#]*${codename}-backports" "$apt_dir/sources.list.d/debian-backports.list" 2>/dev/null; then
echo "standalone-classic"
elif [ "$DEBIAN_VERSION" = "13" ] && [ -f /etc/apt/sources.list.d/debian.sources ] && \
grep -qE "^Suites:.*${codename}-backports" /etc/apt/sources.list.d/debian.sources 2>/dev/null; then
elif [ "$DEBIAN_VERSION" = "13" ] && [ -f "$apt_dir/sources.list.d/debian.sources" ] && \
grep -qE "^Suites:.*${codename}-backports" "$apt_dir/sources.list.d/debian.sources" 2>/dev/null; then
echo "embedded-deb822"
elif [ -f /etc/apt/sources.list ] && \
grep -qE "^[^#]*${codename}-backports" /etc/apt/sources.list 2>/dev/null; then
elif [ -f "$apt_dir/sources.list" ] && \
grep -qE "^[^#]*${codename}-backports" "$apt_dir/sources.list" 2>/dev/null; then
echo "embedded-classic"
elif [ -d /etc/apt/sources.list.d ]; then
if grep -qrE "^Suites:.*${codename}-backports" /etc/apt/sources.list.d/*.sources 2>/dev/null; then
elif [ -d "$apt_dir/sources.list.d" ]; then
if grep -qrE "^Suites:.*${codename}-backports" "$apt_dir/sources.list.d/"*.sources 2>/dev/null; then
echo "embedded-deb822"
elif grep -qrE "^[^#]*${codename}-backports" /etc/apt/sources.list.d/*.list 2>/dev/null; then
elif grep -qrE "^[^#]*${codename}-backports" "$apt_dir/sources.list.d/"*.list 2>/dev/null; then
echo "embedded-classic"
else
echo "none"
+4
View File
@@ -154,6 +154,10 @@ _swap_set_swappiness() {
# ── Entry point ──
manage_swap() {
# Hardware state is detected on demand; RAM_GB
# drives the size recommendation.
_ensure_state_detected
exec 9>"$SWAP_LOCK"
flock -n 9 || {
_msg "Busy" "Another swap operation is already running." 8 55
+4
View File
@@ -1,6 +1,10 @@
#!/usr/bin/env bash
_show_sysinfo() {
# Hardware state is detected on demand; populate it
# before reading any DETECT_* global.
_ensure_state_detected
local msg=""
# ── OS Block ──
+127 -46
View File
@@ -28,6 +28,75 @@ APT_UPDATED=0
# Cached output of `lspci -nn` for the whole session (populated once via _init_lspci_cache)
LSPCI_OUTPUT=""
# Deferred hardware state: the detection battery runs on
# first need (_ensure_state_detected) instead of at boot.
STATE_DETECTED=0
# Network check result cache: 0 = online, 1 = offline,
# empty = not checked yet (checked on first use).
_NET_STATUS=""
# Lazy module registry (presence of a key = loaded).
# Populated by _load_module() so every module is
# sourced at most once per session.
declare -A _LOADED_MODULES=()
# ── Lazy module loading ──────────────────────────
# Sources a module on first use and remembers it in
# _LOADED_MODULES. Call at the top of a main_menu()
# case branch, before calling into the module:
# _load_module <name> || continue
# Cross-module dependencies (functions called at
# runtime that live in another module) are declared
# in the deps case below, so a module never calls
# an unloaded function.
_load_module() {
# Defensive: an empty name would produce a bad
# array subscript under `set -u`.
local name="${1:-}"
[ -n "$name" ] || return 1
# Already loaded in this session?
[ -n "${_LOADED_MODULES[$name]+x}" ] && return 0
# Module file path. Most modules live directly in
# modules/; a few have a dedicated subdirectory.
local path
case "$name" in
system_prefs) path="${MODULES_DIR}/system/system_prefs.sh" ;;
audio) path="${MODULES_DIR}/system/audio.sh" ;;
java) path="${MODULES_DIR}/extras/java.sh" ;;
repo_detect) path="${MODULES_DIR}/repos/repo_detect.sh" ;;
*) path="${MODULES_DIR}/${name}.sh" ;;
esac
# Cross-module dependencies, loaded first.
local -a deps=()
case "$name" in
system_prefs) deps=(audio) ;;
repos) deps=(repo_detect) ;;
firmware) deps=(repo_detect bluetooth repos) ;;
gaming) deps=(repo_detect java repos) ;;
extras) deps=(java) ;;
desktop_display) deps=(repos) ;;
esac
local dep
for dep in "${deps[@]}"; do
_load_module "$dep" || return 1
done
if [ ! -f "$path" ]; then
echo -e "${RED}[-] Module not found: ${path}${NC}" >&2
return 1
fi
# shellcheck disable=SC1090
source "$path"
_LOADED_MODULES["$name"]=1
return 0
}
# --------------------------
# Pre-flight checks
# --------------------------
@@ -487,21 +556,22 @@ is_backports_enabled() {
local c_pattern="^[^#]*${codename}-backports[[:space:]]+"
local d_pattern="Suites:.*${codename}-backports"
local apt_dir="${APT_DIR:-/etc/apt}"
# Classic embedded (sources.list)
if [ -f /etc/apt/sources.list ] && grep -Eq "$c_pattern" /etc/apt/sources.list 2>/dev/null; then
if [ -f "$apt_dir/sources.list" ] && grep -Eq "$c_pattern" "$apt_dir/sources.list" 2>/dev/null; then
echo true
return
fi
# Classic standalone (any .list file in sources.list.d)
if [ -d /etc/apt/sources.list.d ] && grep -qrE "$c_pattern" /etc/apt/sources.list.d/*.list 2>/dev/null; then
if [ -d "$apt_dir/sources.list.d" ] && grep -qrE "$c_pattern" "$apt_dir/sources.list.d/"*.list 2>/dev/null; then
echo true
return
fi
# Deb822 any .sources file
if [ -d /etc/apt/sources.list.d ] && grep -qr "$d_pattern" /etc/apt/sources.list.d/*.sources 2>/dev/null; then
if [ -d "$apt_dir/sources.list.d" ] && grep -qr "$d_pattern" "$apt_dir/sources.list.d/"*.sources 2>/dev/null; then
echo true
return
fi
@@ -738,19 +808,29 @@ _detect_lang_pkg() {
_check_network() {
local target="${1:-deb.debian.org}"
if command -v ping &>/dev/null; then
ping -c 1 -W 3 "$target" &>/dev/null && return 0
fi
# Single bounded probe (worst case 3 s; the old
# ping/wget/curl cascade could block up to 13 s).
[[ "$target" =~ ^[a-zA-Z0-9.-]+$ ]] || return 1
timeout 3 bash -c "</dev/tcp/${target}/80" &>/dev/null
}
if command -v wget &>/dev/null; then
wget -q --timeout=5 --spider "http://${target}" &>/dev/null && return 0
# ── On-demand network check (non-blocking boot) ──
# The connectivity banner moved from boot to the first
# apt operation. _NET_STATUS caches the result so the
# probe runs at most once per session.
_require_network() {
if [ -z "$_NET_STATUS" ]; then
if _check_network; then
_NET_STATUS=0
else
_NET_STATUS=1
echo -e "${YELLOW}──────────────────────────────────────────${NC}"
echo -e "${YELLOW} No internet connectivity detected.${NC}"
echo -e "${YELLOW} Package installation will fail without network.${NC}"
echo -e "${YELLOW}──────────────────────────────────────────${NC}"
fi
fi
if command -v curl &>/dev/null; then
curl -s --connect-timeout 5 -o /dev/null "http://${target}" &>/dev/null && return 0
fi
return 1
return "$_NET_STATUS"
}
# ----------------------------------
@@ -763,6 +843,15 @@ _ensure_apt_updated() {
echo -e "${GREEN}[+]${NC} APT package lists already refreshed this session."
return 0
fi
# Advisory: probe the network once (cached in
# _NET_STATUS) before attempting an apt operation.
_require_network || true
# A correct clock is required for HTTPS certificate
# validation; only runs when apt is actually used.
_ensure_time_synced
echo -e "${GREEN}[+]${NC} Refreshing APT package lists..."
if sudo apt-get update; then
APT_UPDATED=1
@@ -772,41 +861,33 @@ _ensure_apt_updated() {
return 1
}
# ----------------------------------
# LightDM configuration
# ----------------------------------
_configure_lightdm() {
command -v lightdm &>/dev/null || return 0
if _confirm "LightDM" "Configure LightDM to show the user list on the login screen?\n\nThis disables greeter-hide-users."; then
if ! is_installed lightdm-gtk-greeter-settings; then
echo -e "${YELLOW}Installing lightdm-gtk-greeter-settings...${NC}"
if ! sudo DEBIAN_FRONTEND=noninteractive apt install -y lightdm-gtk-greeter-settings; then
echo -e "${YELLOW}lightdm-gtk-greeter-settings could not be installed — LightDM configuration skipped.${NC}"
return 0
fi
fi
local conf_dir="/etc/lightdm/lightdm.conf.d"
local conf_file="${conf_dir}/99-show-users.conf"
if [ -f "$conf_file" ] && grep -q '^greeter-hide-users=false' "$conf_file"; then
return
fi
sudo mkdir -p "$conf_dir"
printf '[Seat:*]\ngreeter-hide-users=false\n' | sudo tee "$conf_file" >/dev/null
echo -e "${GREEN}LightDM configured to show user list.${NC}"
fi
# ── Deferred hardware state (lazy pre-flight) ──
# The detection battery runs on first need instead of at
# boot. Call _ensure_state_detected() before reading any
# DETECT_* global (LSPCI_OUTPUT, GPU_*, ETH_NAMES,
# DESKTOP_ENV, AUDIO_SERVER, ...).
_run_detection_battery() {
_init_lspci_cache
detect_gpu
detect_network
detect_displayserver
detect_storage
detect_desktop_environment
detect_audio_server
detect_cpu_ram
detect_kernel
}
_ensure_state_detected() {
[ "$STATE_DETECTED" -eq 1 ] && return 0
_run_detection_battery
STATE_DETECTED=1
}
# ── Lazy system state refresh ──
refresh_system_state() {
detect_debian_version
detect_gpu
detect_cpu_ram
detect_network
detect_desktop_environment
detect_displayserver
detect_audio_server
# Forced re-run: a menu action may have changed the
# hardware or repo state since the first detection.
_run_detection_battery
STATE_DETECTED=1
}
+4
View File
@@ -2,6 +2,10 @@
# zram.sh — ZRAM submenu: view, create/reconfigure, remove
zram_menu() {
# Hardware state is detected on demand; RAM_KB
# and RAM_SUMMARY are populated by the battery.
_ensure_state_detected
while true; do
local choice
choice=$(_menu "ZRAM Configuration" \