diff --git a/modules/firmware.sh b/modules/firmware.sh index 7d20a26..7394e78 100644 --- a/modules/firmware.sh +++ b/modules/firmware.sh @@ -340,7 +340,17 @@ _ensure_nonfree_repo() { return 0 fi - if _confirm "non-free Repository" "Component 'non-free' (and 'non-free-firmware') is required for WiFi/Bluetooth/GPU firmware.\n\nAdd them to your APT repositories?"; then + if ! _confirm "non-free Repository" "Component 'non-free' (and 'non-free-firmware') is required for WiFi/Bluetooth/GPU firmware.\n\nAdd them to your APT repositories?"; then + return 1 + fi + + # No active sources at all → bootstrap a complete configuration + if ! has_active_deb_sources; then + backup_current_repos + if ! bootstrap_repositories "main contrib non-free non-free-firmware"; then + return 1 + fi + else if [ -f /etc/apt/sources.list ]; then sudo sed -i '/^deb / { /non-free/! s/\(main[^ ]*\)/\1 non-free non-free-firmware/ }' /etc/apt/sources.list fi @@ -350,11 +360,21 @@ _ensure_nonfree_repo() { sudo sed -i '/^Components:/ { /non-free/! s/$/ non-free non-free-firmware/ }' "$f" done fi - sudo apt update - echo -e "${GREEN}non-free repository enabled.${NC}" - return 0 fi - return 1 + + # Verify the component was actually added before reporting success + local nonfree_ok=false + [ -f /etc/apt/sources.list ] && grep -Eq '^[^#]*\bnon-free\b' /etc/apt/sources.list 2>/dev/null && nonfree_ok=true + [ -d /etc/apt/sources.list.d ] && grep -qr 'Components:.*\bnon-free\b' /etc/apt/sources.list.d/*.sources 2>/dev/null && nonfree_ok=true + [ -d /etc/apt/sources.list.d ] && grep -qrE '^[^#]*\bnon-free\b' /etc/apt/sources.list.d/*.list 2>/dev/null && nonfree_ok=true + if ! $nonfree_ok; then + echo -e "${RED}Failed to enable non-free repository. Check your APT sources.${NC}" + return 1 + fi + + sudo apt update + echo -e "${GREEN}non-free repository enabled.${NC}" + return 0 } # ── Main entry point ── diff --git a/modules/gaming.sh b/modules/gaming.sh index 860943a..8bef5cf 100644 --- a/modules/gaming.sh +++ b/modules/gaming.sh @@ -27,7 +27,18 @@ ensure_contrib_repo() { return 0 fi - if _confirm "contrib Repository" "Component 'contrib' is required for Steam.\n\nAdd 'contrib' to your APT repositories?"; then + if ! _confirm "contrib Repository" "Component 'contrib' is required for Steam.\n\nAdd 'contrib' to your APT repositories?"; then + echo -e "${YELLOW}contrib repository not enabled. Steam installation may fail.${NC}" + return 1 + fi + + # No active sources at all → bootstrap a complete configuration + if ! has_active_deb_sources; then + backup_current_repos + if ! bootstrap_repositories "main contrib"; then + return 1 + fi + else if [ -f /etc/apt/sources.list ]; then sudo sed -i '/^deb / { /contrib/! s/main/main contrib/ }' /etc/apt/sources.list fi @@ -37,13 +48,21 @@ ensure_contrib_repo() { sudo sed -i '/^Components:/ { /contrib/! s/$/ contrib/ }' "$f" done fi - sudo apt update - echo -e "${GREEN}contrib repository enabled.${NC}" - return 0 fi - echo -e "${YELLOW}contrib repository not enabled. Steam installation may fail.${NC}" - return 1 + # Verify the component was actually added before reporting success + local contrib_ok=false + [ -f /etc/apt/sources.list ] && grep -Eq '^[^#]*\bcontrib\b' /etc/apt/sources.list 2>/dev/null && contrib_ok=true + [ -d /etc/apt/sources.list.d ] && grep -qr 'Components:.*\bcontrib\b' /etc/apt/sources.list.d/*.sources 2>/dev/null && contrib_ok=true + [ -d /etc/apt/sources.list.d ] && grep -qrE '^[^#]*\bcontrib\b' /etc/apt/sources.list.d/*.list 2>/dev/null && contrib_ok=true + if ! $contrib_ok; then + echo -e "${RED}Failed to enable contrib repository. Check your APT sources.${NC}" + return 1 + fi + + sudo apt update + echo -e "${GREEN}contrib repository enabled.${NC}" + return 0 } install_gaming() { diff --git a/modules/repos.sh b/modules/repos.sh index c0a9a0e..89fbc32 100644 --- a/modules/repos.sh +++ b/modules/repos.sh @@ -353,18 +353,93 @@ _repos_offer_upgrade() { fi } +# Bootstrap a complete repository configuration from scratch when no active +# sources exist. Caller must run backup_current_repos() first so that a failed +# apt update can restore the previous state. +# Returns: 0 on success, 1 if skipped, declined or failed +bootstrap_repositories() { + local components="$1" + local bp_enabled="${2:-false}" + local bp_location="${3:-none}" + + if [ -z "$DEBIAN_CODENAME" ]; then + _msg "Error" "Cannot bootstrap repositories: Debian codename unknown." 10 65 + return 1 + fi + + # non-free-firmware does not exist on Bullseye + if [ "$DEBIAN_VERSION" = "11" ]; then + components=$(echo "$components" | sed 's/ non-free-firmware//g') + fi + + if ! _confirm "Bootstrap Repositories" \ + "No active APT sources were found (empty or missing sources.list).\n\n\ +Configure repositories from scratch?" 10 65; then + echo "Repository configuration skipped." + cleanup_repo_backup + return 1 + fi + + if has_active_deb_sources; then + if ! _confirm "Custom Sources Found" \ + "Active sources exist in other files (e.g. sources.list.d/*.sources).\n\n\ +Adding deb.debian.org may duplicate your current mirror configuration. Continue?" 10 65; then + cleanup_repo_backup + return 1 + fi + fi + + # DEB822 is Trixie-only; Debian 11/12 always use classic + local use_deb822=false + if [ "$DEBIAN_VERSION" = "13" ]; then + local choice + choice=$(_menu "Repo Format" "Choose the repository format:" 12 60 3 \ + "deb822" "Native DEB822 format (sources.list.d/debian.sources)" \ + "classic" "Classic format (/etc/apt/sources.list)") + if [ "$choice" = "deb822" ]; then + use_deb822=true + fi + fi + + if $use_deb822; then + _write_deb822 "$DEBIAN_CODENAME" "write" "$bp_enabled" "$bp_location" "$components" || { cleanup_repo_backup; return 1; } + else + _write_classic "$DEBIAN_CODENAME" "write" "$bp_enabled" "$bp_location" "$components" || { cleanup_repo_backup; return 1; } + fi + + # Tidy: with deb822 chosen, an empty classic file is no longer needed + if $use_deb822 && [ -f /etc/apt/sources.list ] && ! grep -qE '^[^#]*\bdeb\b' /etc/apt/sources.list 2>/dev/null; then + if _confirm "Disable Classic" "An empty /etc/apt/sources.list is no longer needed. Move it aside (sources.list.disabled)?"; then + sudo mv /etc/apt/sources.list /etc/apt/sources.list.disabled + echo "Empty sources.list renamed to sources.list.disabled" + fi + fi + + echo "Updating package lists..." + if sudo apt update; then + REPOS_CONFIGURED=true + cleanup_repo_backup + echo -e "${GREEN}Repository components configured.${NC}" + return 0 + else + restore_previous_repos + echo -e "${RED}apt update failed. Previous configuration restored.${NC}" + return 1 + fi +} + _repos_enable_components() { local current_format bp_enabled bp_location components current_format=$(detect_repo_format) if _components_enabled; then - if ! _confirm "Disable Components" \ + if _confirm "Disable Components" \ "Contrib and non-free are already enabled. Disable them?"; then - echo "No changes made." - _pause - return + components="main" + else + components=$(detect_active_components) + echo "Keeping current components: $components" fi - components="main" else if ! _confirm "Enable Components" \ "Enable contrib and non-free components?\n\n\ @@ -390,7 +465,32 @@ popular software (like gaming platforms and proprietary tools)." 12 60; then backup_current_repos - if [ "$current_format" = "deb822" ] || [ "$current_format" = "none" ]; then + if [ "$current_format" = "none" ]; then + if ! bootstrap_repositories "$components" "$bp_enabled" "$bp_location"; then + _pause + return + fi + _repos_offer_upgrade + echo -e "${GREEN}Cleaning old packages...${NC}" + sudo apt autoremove -y + sudo apt autoclean + echo -e "${GREEN}System packages cleaned.${NC}" + _pause + return + fi + + echo "Verifying repository configuration (main, updates, security)..." + if [ "$current_format" = "deb822" ]; then + # Mixed-state guard: active classic lines alongside deb822 would duplicate sources + if [ -f /etc/apt/sources.list ] && grep -qE '^[^#]*\bdeb\b' /etc/apt/sources.list 2>/dev/null; then + if ! _confirm "Duplicate Config" \ + "Active classic sources were also found in /etc/apt/sources.list.\n\n\ +Writing debian.sources may duplicate your configuration. Continue?" 10 65; then + echo "No changes made." + _pause + return + fi + fi _write_deb822 "$DEBIAN_CODENAME" "write" "$bp_enabled" "$bp_location" "$components" else _write_classic "$DEBIAN_CODENAME" "write" "$bp_enabled" "$bp_location" "$components" diff --git a/modules/repos/repo_detect.sh b/modules/repos/repo_detect.sh index c16d51d..0b4350b 100644 --- a/modules/repos/repo_detect.sh +++ b/modules/repos/repo_detect.sh @@ -2,34 +2,76 @@ # repo_detect.sh – Detection-only helpers for idempotent repository configuration. # Part A of the two-part architecture. No user dialogs, no writes. -# Detect the format of the main repo file +# 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 f + for f in /etc/apt/sources.list /etc/apt/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 + [ -f "$f" ] || continue + grep -qE '^Types:.*\bdeb\b' "$f" 2>/dev/null && \ + grep -qE '^URIs:' "$f" 2>/dev/null && return 0 + done + return 1 +} + +# Detect the format of the main repo file (content-aware). +# DEB822 is only valid on Debian 13 (Trixie); Debian 11/12 are classic-only. # Returns: "deb822", "classic", or "none" detect_repo_format() { - if [ -f /etc/apt/sources.list.d/debian.sources ]; then + 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 echo "deb822" - elif [ -f /etc/apt/sources.list ]; then + elif [ -f /etc/apt/sources.list ] && grep -qE '^[^#]*\bdeb\b' /etc/apt/sources.list 2>/dev/null; then echo "classic" else echo "none" fi } -# Check whether backports are currently enabled (any 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 comps + comps=$(grep "^Components:" /etc/apt/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 + local comps + comps=$(grep "^[^#]*deb .* main" /etc/apt/sources.list 2>/dev/null | head -1 | sed 's/.*main\s*//') + if [ -n "$comps" ]; then + echo "main $comps" + return + fi + fi + if [ "$DEBIAN_VERSION" = "11" ]; then + echo "main contrib non-free" + else + echo "main contrib non-free non-free-firmware" + fi +} + +# Check whether backports are currently enabled (any format, any file) # Returns: 0 if enabled, 1 otherwise detect_backports_status() { local codename="$1" - if [ -f /etc/apt/sources.list.d/debian.sources ]; then + 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 fi if [ -f /etc/apt/sources.list ]; then grep -qE "^[^#]*${codename}-backports" /etc/apt/sources.list 2>/dev/null && return 0 fi - if [ -f /etc/apt/sources.list.d/debian-backports.sources ]; then - grep -qE "^Suites:.*${codename}-backports" /etc/apt/sources.list.d/debian-backports.sources 2>/dev/null && return 0 - fi - if [ -f /etc/apt/sources.list.d/debian-backports.list ]; then - grep -qE "^[^#]*${codename}-backports" /etc/apt/sources.list.d/debian-backports.list 2>/dev/null && return 0 + 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 fi return 1 @@ -44,18 +86,26 @@ detect_backports_status() { detect_backports_location() { local codename="$1" - if [ -f /etc/apt/sources.list.d/debian-backports.sources ] && \ + 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 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 echo "standalone-classic" - elif [ -f /etc/apt/sources.list.d/debian.sources ] && \ + 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 echo "embedded-deb822" elif [ -f /etc/apt/sources.list ] && \ grep -qE "^[^#]*${codename}-backports" /etc/apt/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 + echo "embedded-deb822" + elif grep -qrE "^[^#]*${codename}-backports" /etc/apt/sources.list.d/*.list 2>/dev/null; then + echo "embedded-classic" + else + echo "none" + fi else echo "none" fi diff --git a/modules/utils.sh b/modules/utils.sh index 3e6eb9a..f5d07a4 100644 --- a/modules/utils.sh +++ b/modules/utils.sh @@ -505,14 +505,13 @@ is_backports_enabled() { echo true; return fi - # Classic standalone (new — debian-backports.list) - if [ -f /etc/apt/sources.list.d/debian-backports.list ] && \ - grep -Eq "$c_pattern" /etc/apt/sources.list.d/debian-backports.list 2>/dev/null; then + # 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 echo true; return fi # Deb822 any .sources file - if grep -qr "$d_pattern" /etc/apt/sources.list.d/*.sources 2>/dev/null; then + if [ -d /etc/apt/sources.list.d ] && grep -qr "$d_pattern" /etc/apt/sources.list.d/*.sources 2>/dev/null; then echo true; return fi