Zuletzt aktiv 1792486473

install-wifi.sh Orginalformat
1#!/usr/bin/env bash
2# Adaptive Wi-Fi installer for Debian 12/13 (PCI and USB).
3# v0.2.0 - automatic, logged Debian APT component configuration.
4# Usage: sudo bash install-wifi.sh [--dry-run] [--force]
5set -Eeuo pipefail
6export DEBIAN_FRONTEND=noninteractive
7DRY=0; FORCE=0
8for arg in "$@"; do case "$arg" in --dry-run) DRY=1;; --force) FORCE=1;; -h|--help) echo 'Usage: sudo bash install-wifi.sh [--dry-run] [--force]'; exit 0;; *) echo "Unknown argument: $arg" >&2; exit 2;; esac; done
9log(){ printf '[wifi] %s\n' "$*"; }
10warn(){ printf '[wifi] WARNING: %s\n' "$*" >&2; }
11die(){ printf '[wifi] ERROR: %s\n' "$*" >&2; exit 1; }
12run(){ if ((DRY)); then printf '[dry-run]'; printf ' %q' "$@"; printf '\n'; else "$@"; fi; }
13[[ $EUID -eq 0 ]] || die 'Run with sudo.'
14[[ -r /etc/os-release ]] || die 'Cannot identify distribution.'
15# shellcheck source=/dev/null
16. /etc/os-release
17[[ ${ID:-} == debian ]] || die 'This script supports Debian only.'
18case "${VERSION_ID:-}" in 12|13) ;; *) die 'Supported Debian versions: 12 and 13.';; esac
19for cmd in lspci lsusb modprobe apt-get awk grep sed python3; do
20 command -v "$cmd" >/dev/null || { if [[ $cmd == lspci || $cmd == lsusb ]]; then warn "$cmd not installed; detection may be incomplete (install pciutils/usbutils)."; else die "Missing command: $cmd"; fi; }
21done
22log "Debian ${VERSION_ID}; kernel $(uname -r)"
23[[ -d /sys/class/net ]] || die 'Network sysfs is unavailable.'
24# Find Wi-Fi interfaces, including interfaces that are currently down.
25mapfile -t WIFI_IFACES < <(find /sys/class/net -mindepth 1 -maxdepth 1 -type l -printf '%f\n' | while read -r iface; do [[ -d /sys/class/net/$iface/wireless || -d /sys/class/net/$iface/phy80211 ]] && echo "$iface"; done)
26if ((${#WIFI_IFACES[@]})); then
27 log "Wi-Fi interface(s): ${WIFI_IFACES[*]}"
28 if (( ! FORCE )); then log 'Driver is exposing a Wi-Fi interface. No packages changed. Use --force to attempt firmware installation.'; exit 0; fi
29fi
30# Scan PCI wireless network controllers by PCI class 0280 (and 0200 with matching wireless descriptions).
31PCI_LINES=''; USB_LINES=''
32if command -v lspci >/dev/null; then PCI_LINES=$(lspci -nn 2>/dev/null | grep -Ei 'Network controller|Wireless|802\.11|Wi-Fi' || true); fi
33if command -v lsusb >/dev/null; then USB_LINES=$(lsusb 2>/dev/null || true); fi
34log 'PCI Wi-Fi candidates:'; [[ -z $PCI_LINES ]] && log ' (none found)' || printf '%s\n' "$PCI_LINES"
35log 'USB devices (review for USB Wi-Fi adapters):'; [[ -z $USB_LINES ]] && log ' (none found)' || printf '%s\n' "$USB_LINES"
36# Collect possible vendors from PCI network candidates and USB vendor IDs.
37PACKAGES=(); NEED_WL=0; UNKNOWN=0
38add_pkg(){ local p; for p in "$@"; do local found=0 x; for x in "${PACKAGES[@]:-}"; do [[ $x == "$p" ]] && found=1; done; ((found)) || PACKAGES+=("$p"); done; }
39# PCI: match exact Broadcom BCM4360 first; generic vendor families after.
40if [[ $PCI_LINES =~ \[14e4:43a0\] ]]; then
41 log 'Detected Broadcom BCM4360 (14e4:43a0): proprietary wl/DKMS driver required.'
42 add_pkg broadcom-sta-dkms dkms "linux-headers-$(uname -r)"; NEED_WL=1
43fi
44if [[ $PCI_LINES =~ \[8086: ]]; then log 'Detected Intel PCI wireless hardware.'; add_pkg firmware-iwlwifi; fi
45if [[ $PCI_LINES =~ \[14c3: ]]; then log 'Detected MediaTek PCI wireless hardware.'; add_pkg firmware-misc-nonfree; fi
46if [[ $PCI_LINES =~ \[10ec: ]]; then log 'Detected Realtek PCI wireless hardware.'; add_pkg firmware-realtek; fi
47if [[ $PCI_LINES =~ \[168c: || $PCI_LINES =~ \[17cb: ]]; then log 'Detected Qualcomm/Atheros PCI wireless hardware.'; add_pkg firmware-atheros; fi
48if [[ $PCI_LINES =~ \[14e4: && $NEED_WL -eq 0 ]]; then
49 warn 'Broadcom PCI device detected but chipset-specific driver choice is uncertain.'
50 UNKNOWN=1
51fi
52# USB vendors alone are not enough for reliable chipset/driver choice; firmware candidates are safe,
53# but USB IDs can represent non-Wi-Fi devices. Require wireless descriptors in lsusb output.
54if [[ $USB_LINES =~ [Ii]ntel.*[Ww]ireless || $USB_LINES =~ [Ww]ireless.*8087: ]]; then add_pkg firmware-iwlwifi; fi
55if [[ $USB_LINES =~ [Rr]ealtek.*([Ww]ireless|802\.11|[Ww]LAN) ]]; then add_pkg firmware-realtek; fi
56if [[ $USB_LINES =~ [Mm]edia[Tt]ek.*([Ww]ireless|802\.11|[Ww]LAN) ]]; then add_pkg firmware-misc-nonfree; fi
57if [[ $USB_LINES =~ [Aa]theros.*([Ww]ireless|802\.11|[Ww]LAN) ]]; then add_pkg firmware-atheros; fi
58if ((${#PACKAGES[@]} == 0)); then
59 warn 'No confidently supported chipset mapping found. No system changes made.'
60 warn 'Run: lspci -nnk; lsusb; journalctl -k -b | grep -Ei "wifi|wlan|firmware"'
61 exit 3
62fi
63((UNKNOWN)) && warn 'At least one adapter requires manual driver verification.'
64log "Candidate packages: ${PACKAGES[*]}"
65# Enable required components on Debian's own active repositories only.
66# Supports both legacy .list and Deb822 .sources formats, including the Debian 13 default.
67# Third-party repositories and disabled entries are never modified.
68log 'APT repository policy: ensure contrib, non-free, and non-free-firmware on official Debian entries.'
69log 'Any changed source file will be backed up alongside the original before modification.'
70if ((DRY)); then
71 log '[dry-run] Would inspect Debian .list/.sources and show required component changes; no files modified.'
72else
73 python3 - <<'PYAPT'
74from pathlib import Path
75from datetime import datetime
76import re
77import shutil
78import sys
79
80needed = ('main', 'contrib', 'non-free', 'non-free-firmware')
81paths = [Path('/etc/apt/sources.list')]
82d = Path('/etc/apt/sources.list.d')
83if d.is_dir():
84 paths += sorted(list(d.glob('*.list')) + list(d.glob('*.sources')))
85
86def official(uri):
87 # Only Debian-owned hosts; never alter PPAs or vendor repositories.
88 from urllib.parse import urlparse
89 u = urlparse(uri)
90 host = (u.hostname or '').lower()
91 return u.scheme in ('http', 'https') and (host in ('deb.debian.org', 'security.debian.org', 'ftp.debian.org', 'archive.debian.org') or host.endswith('.debian.org'))
92
93def update_legacy(text):
94 out = []
95 for line in text.splitlines(keepends=True):
96 # Keep comments, disabled lines, and cdrom entries unchanged.
97 m = re.match(r'^(\s*deb(?:-src)?\s+)(?:\[[^]]+\]\s+)?', line)
98 if not m:
99 out.append(line); continue
100 # Extract URI, allowing [options] blocks.
101 body = line[m.end():]
102 parts = body.split(None, 2)
103 if len(parts) < 3 or not official(parts[0]):
104 out.append(line); continue
105 uri, suite, rest = parts
106 if not (suite.startswith(('bookworm', 'trixie'))):
107 out.append(line); continue
108 comps, sep, comment = rest.partition('#')
109 existing = comps.split()
110 missing = [x for x in needed if x not in existing]
111 if missing:
112 newline = '\n' if line.endswith('\n') else ''
113 suffix = (' #' + comment.rstrip('\n')) if sep else ''
114 line = line[:m.end()] + uri + ' ' + suite + ' ' + ' '.join(existing + missing) + suffix + newline
115 out.append(line)
116 return ''.join(out)
117
118def update_deb822(text):
119 # Process stanzas, preserving comments and unrelated fields.
120 blocks = re.split(r'(\n\s*\n)', text)
121 for i in range(0, len(blocks), 2):
122 block = blocks[i]
123 if re.search(r'^Enabled:\s*no\s*$', block, re.I | re.M):
124 continue
125 uris = re.search(r'^URIs:\s*(.+)$', block, re.M)
126 suites = re.search(r'^Suites:\s*(.+)$', block, re.M)
127 if not uris or not suites:
128 continue
129 if not all(official(uri) for uri in uris.group(1).split()):
130 continue
131 if not all(s.startswith(('bookworm', 'trixie')) for s in suites.group(1).split()):
132 continue
133 comp = re.search(r'^(Components:\s*)([^\n]*)', block, re.M)
134 if comp:
135 existing = comp.group(2).split()
136 missing = [x for x in needed if x not in existing]
137 if missing:
138 block = block[:comp.start(2)] + ' '.join(existing + missing) + block[comp.end(2):]
139 else:
140 block = block.rstrip('\n') + '\nComponents: ' + ' '.join(needed) + '\n'
141 blocks[i] = block
142 return ''.join(blocks)
143
144changed = 0
145for path in paths:
146 if not path.is_file():
147 continue
148 original = path.read_text()
149 updated = update_deb822(original) if path.suffix == '.sources' else update_legacy(original)
150 if updated == original:
151 continue
152 stamp = datetime.now().strftime('%Y%m%d-%H%M%S')
153 backup = path.with_name(path.name + '.bak-' + stamp)
154 try:
155 shutil.copy2(path, backup)
156 path.write_text(updated)
157 changed += 1
158 print(f'[wifi] APT SOURCE UPDATED: {path}')
159 print(f'[wifi] BACKUP CREATED: {backup}')
160 print('[wifi] Enabled components: main contrib non-free non-free-firmware')
161 except OSError as e:
162 print(f'[wifi] ERROR updating {path}: {e}', file=sys.stderr)
163 sys.exit(1)
164if not changed:
165 print('[wifi] No Debian APT source changes required (already configured or no eligible official Debian source found).')
166PYAPT
167fi
168log 'Internet or an offline APT mirror is required to install packages.'
169run apt-get update
170# Check package availability before attempting installation.
171if (( ! DRY )); then
172 for pkg in "${PACKAGES[@]}"; do
173 if [[ $(apt-cache policy "$pkg" | awk '/Candidate:/{print $2}') == '(none)' ]]; then
174 die "Package $pkg unavailable. Check APT sources or kernel headers; no driver changes attempted."
175 fi
176 done
177fi
178run apt-get install -y "${PACKAGES[@]}"
179if ((NEED_WL)); then
180 log 'Broadcom wl installed. Loading may require unloading conflicting modules and/or reboot.'
181 log 'Not unloading modules automatically to avoid disrupting networking.'
182fi
183log 'Installation finished. Reboot, then verify: lspci -nnk; ip link; nmcli device status'
184