← pagan-scripts

Commit abd1643

0
plików
+0
dodanych
-0
usuniętych
@@ -1,977 +1,983 @@
1 -#!/usr/bin/env bash
2 -# =============================================================================
3 -# pagan-install – TUI instalator PaganLinux (dialog/whiptail + fallback tekstowy)
4 -#
5 -# Instaluje system z działającego live (USB/ISO) na dysk twardy:
6 -# • wybór języka (PL/EN/DE) • wybór dysku
7 -# • partycjonowanie: automatyczne (GPT+EFI/BIOS) / ręczne (cfdisk) / gotowe
8 -# • system plików (ext4/xfs/btrfs) • kopia systemu (rsync)
9 -# • konfiguracja w chroot: fstab, GRUB, locale, strefa czasowa,
10 -# hasło root, użytkownik
11 -#
12 -# Użycie: pagan-install (TUI)
13 -# LANG=pl pagan-install (wymuszony język)
14 -# PAGAN_LIVE_DIR=/mnt/live pagan-install (źródło systemu)
15 -# =============================================================================
16 -
17 -VERSION="1.0.0"
18 -LOG="/tmp/pagan-install.log"
19 -TARGET="/mnt/pagan"
20 -LIVE_DIR="${PAGAN_LIVE_DIR:-/}"
21 -SWAP_SIZE="${PAGAN_SWAP:-4096}" # MiB
22 -ROOT_MIN="${PAGAN_ROOT_MIN:-4096}" # minimalna partycja root (MiB)
23 -DO_REBOOT="${PAGAN_REBOOT:-ask}"
24 -
25 -# ── Opcje konfiguracji (ustawiane w krokach instalatora) ──
26 -KEYMAP="" # układ klawiatury (loadkeys)
27 -SWAP_MODE="partition" # partition | file | none
28 -BOOT_MODE="grub" # grub | none
29 -ENCRYPT=0 # LUKS na partycji root
30 -CRYPT_UUID="" # UUID LUKS (do /etc/crypttab)
31 -ADMIN=1 # użytkownik w grupie wheel
32 -REPO_URL="" # nadpisanie /etc/pag/repos.conf
33 -
34 -# Fail-fast: natychmiast przerwij na każdym błędzie (krytyczne potoki np. mkfs).
35 -set -eE -o pipefail
36 -
37 -# /tmp jest katalogiem sticky: fs.protected_regular blokuje O_CREAT na pliku
38 -# należącym do innego użytkownika (dotyczy to RÓWNIEŻ roota). Gdy log istnieje
39 -# z innym właścicielem – użyj osobnego logu per-UID zamiast kończyć się EACCES.
40 -if [[ -e "$LOG" && ! -O "$LOG" ]]; then
41 - LOG="/tmp/pagan-install-$(id -u).log"
42 -fi
43 -rm -f "$LOG" 2>/dev/null || true
44 -exec 3>"$LOG"; exec 2>>"$LOG"
45 -log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" >&3; }
46 -
47 -# ── Interfejs: dialog > whiptail > tekstowy ──
48 -UI=""
49 -if command -v dialog >/dev/null 2>&1; then
50 - UI="dialog"
51 -elif command -v whiptail >/dev/null 2>&1; then
52 - UI="whiptail"
53 -fi
54 -
55 -# ── i18n ──
56 -declare -A T
57 -set_lang() {
58 - local l="${1:-}"; [[ -n "$l" ]] || l="${LANG:-en}"
59 - l="${l%%_*}"; l="${l%%@*}"; l="${l%%.*}"
60 - case "$l" in
61 - pl)
62 - T=(
63 - [TITLE]="PaganLinux – Instalator"
64 - [LANG_TITLE]="Język / Language"
65 - [LANG_PROMPT]="Wybierz język instalatora:"
66 - [WELCOME]="Witaj w instalatorze PaganLinux!\n\nTen kreator zainstaluje system z nośnika live na wybrany dysk.\nWszystkie dane na dysku docelowym zostaną USUNIĘTE.\n\nPamiętaj o kopii zapasowej!"
67 - [ROOT_ERR]="Instalator wymaga uprawnień root (sudo)."
68 - [DISK_TITLE]="Wybór dysku"
69 - [DISK_PROMPT]="Wybierz dysk docelowy:"
70 - [NO_DISK]="Nie znaleziono dysków (/dev/sd*, nvme, vd, mmcblk)."
71 - [DISK_SMALL]="Dysk %s jest za mały (%s MiB, wymagane co najmniej %s MiB)."
72 - [EFIVARS_WARN]="Nie można zamontować efivars – wpisy NVRAM nie zostaną zaktualizowane."
73 - [PART_TITLE]="Partycjonowanie"
74 - [PART_PROMPT]="Wybierz metodę partycjonowania:"
75 - [PART_AUTO]="Automatyczne (cały dysk: EFI + swap + root)"
76 - [PART_MANUAL]="Ręczne (cfdisk)"
77 - [PART_EXISTING]="Użyj istniejących partycji"
78 - [FS_TITLE]="System plików"
79 - [FS_PROMPT]="Wybierz system plików dla root:"
80 - [CONFIRM_TITLE]="OSTATECZNE POTWIERDZENIE"
81 - [CONFIRM_WIPE]="Dysk %s zostanie WYMAZANY i zainstalowany na nim PaganLinux.\n\nCzy na pewno kontynuować?"
82 - [ABORT]="Instalacja przerwana."
83 - [FORMATTING]="Formatowanie i montowanie partycji..."
84 - [COPY_TITLE]="Kopiowanie systemu"
85 - [COPY_MSG]="Kopiowanie systemu z nośnika live..."
86 - [COPY_DONE]="Kopiowanie zakończone."
87 - [COPY_ERR]="Błąd kopiowania – sprawdź %s"
88 - [CONF_HOST]="Konfiguracja systemu"
89 - [HOST_PROMPT]="Podaj nazwę komputera (hostname):"
90 - [HOST_ERR]="Nazwa komputera nie może być pusta."
91 - [TZ_TITLE]="Strefa czasowa"
92 - [TZ_PROMPT]="Wybierz strefę czasową:"
93 - [LOC_TITLE]="Ustawienia regionalne"
94 - [LOC_PROMPT]="Wybierz locale systemowe:"
95 - [ROOT_PASS_TITLE]="Hasło root"
96 - [ROOT_PROMPT1]="Podaj hasło root:"
97 - [ROOT_PROMPT2]="Powtórz hasło root:"
98 - [PASS_MISMATCH]="Hasła nie są identyczne!"
99 - [PASS_EMPTY]="Hasło nie może być puste."
100 - [USER_TITLE]="Użytkownik"
101 - [USER_PROMPT]="Podaj nazwę użytkownika (puste = pomiń):"
102 - [USER_PASS1]="Hasło dla użytkownika %s:"
103 - [USER_PASS2]="Powtórz hasło:"
104 - [GRUB_TITLE]="Bootloader"
105 - [GRUB_MSG]="Instalowanie GRUB-a..."
106 - [GRUB_DONE]="GRUB zainstalowany."
107 - [GRUB_ERR]="Instalacja GRUB-a nie powiodła się (log: %s)"
108 - [FINISH]="Instalacja zakończona pomyślnie!\n\nSystem jest zainstalowany w %s.\nPo ponownym uruchomieniu zaloguj się jako utworzony użytkownik."
109 - [REBOOT]="Uruchomić ponownie teraz?"
110 - [EFI_TITLE]="Partycja EFI"
111 - [EFI_PROMPT]="Wybierz partycję EFI (ESP):"
112 - [ROOTPART_TITLE]="Partycja root"
113 - [ROOTPART_PROMPT]="Wybierz partycję na root (/):"
114 - [FORMAT_Q]="Sformatować partycję root?"
115 - [SWAP_SKIP]="Pominięto swap."
116 - [DONE_TITLE]="Gotowe"
117 - [PRESS_OK]="OK"
118 - [CANCEL]="Anuluj"
119 - [YES]="Tak"
120 - [NO]="Nie"
121 - [KEYMAP_TITLE]="Układ klawiatury"
122 - [KEYMAP_PROMPT]="Wybierz układ klawiatury:"
123 - [CUSTOM]="Własny (wpisz nazwę)..."
124 - [SWAP_TITLE]="Pamięć wymiany (swap)"
125 - [SWAP_PROMPT]="Wybierz rodzaj swapu:"
126 - [SWAP_PART_OPT]="Partycja swap"
127 - [SWAP_FILE_OPT]="Plik wymiany (/swapfile)"
128 - [SWAP_NONE_OPT]="Bez swapu"
129 - [SWAP_FILE_SIZE]="Rozmiar pliku swap (MiB):"
130 - [BOOT_TITLE]="Bootloader"
131 - [BOOT_PROMPT]="Wybierz bootloader:"
132 - [BOOT_GRUB_OPT]="GRUB (zalecany)"
133 - [BOOT_NONE_OPT]="Brak (ręczna konfiguracja)"
134 - [CRYPT_TITLE]="Szyfrowanie (LUKS)"
135 - [CRYPT_PROMPT]="Zaszyfrować partycję root (LUKS)?\nUwaga: initramfs musi obsługiwać cryptsetup."
136 - [CRYPT_PASS1]="Hasło szyfrowania:"
137 - [CRYPT_PASS2]="Powtórz hasło szyfrowania:"
138 - [CRYPT_OPEN]="Otwieranie zaszyfrowanej partycji..."
139 - [CRYPT_ERR]="Błąd cryptsetup – instalacja przerwana."
140 - [ADMIN_PROMPT]="Czy użytkownik ma być administratorem (grupa wheel)?"
141 - [REPO_TITLE]="Repozytorium pakietów"
142 - [REPO_PROMPT]="Adres repozytorium pag (pusty = bez zmian):"
143 - [SUMMARY_TITLE]="Podsumowanie konfiguracji"
144 - [SUMMARY_HEAD]="Instalacja rozpocznie się z poniższymi ustawieniami:\n\n"
145 - [SUMMARY_CONFIRM]="Kontynuować?"
146 - [L_DISK]="Dysk:"
147 - ) ;;
148 - de)
149 - T=(
150 - [TITLE]="PaganLinux – Installationsprogramm"
151 - [LANG_TITLE]="Sprache / Language"
152 - [LANG_PROMPT]="Wählen Sie die Sprache:"
153 - [WELCOME]="Willkommen beim PaganLinux-Installationsprogramm!\n\nDieser Assistent installiert das System vom Live-Medium auf die gewählte Festplatte.\nAlle Daten auf der Zielfestplatte werden GELÖSCHT.\n\nErstellen Sie vorher ein Backup!"
154 - [ROOT_ERR]="Der Installer benötigt Root-Rechte (sudo)."
155 - [DISK_TITLE]="Festplattenauswahl"
156 - [DISK_PROMPT]="Zielfestplatte wählen:"
157 - [NO_DISK]="Keine Festplatten gefunden (/dev/sd*, nvme, vd, mmcblk)."
158 - [DISK_SMALL]="Die Festplatte %s ist zu klein (%s MiB, mindestens %s MiB erforderlich)."
159 - [EFIVARS_WARN]="efivars kann nicht eingehängt werden – NVRAM-Einträge werden nicht aktualisiert."
160 - [PART_TITLE]="Partitionierung"
161 - [PART_PROMPT]="Partitionierungsmethode wählen:"
162 - [PART_AUTO]="Automatisch (ganze Platte: EFI + Swap + Root)"
163 - [PART_MANUAL]="Manuell (cfdisk)"
164 - [PART_EXISTING]="Vorhandene Partitionen nutzen"
165 - [FS_TITLE]="Dateisystem"
166 - [FS_PROMPT]="Dateisystem für Root wählen:"
167 - [CONFIRM_TITLE]="LETZTE BESTÄTIGUNG"
168 - [CONFIRM_WIPE]="Die Festplatte %s wird GELÖSCHT und PaganLinux installiert.\n\nWirklich fortfahren?"
169 - [ABORT]="Installation abgebrochen."
170 - [FORMATTING]="Partitionen formatieren und einhängen..."
171 - [COPY_TITLE]="Systemkopie"
172 - [COPY_MSG]="System vom Live-Medium kopieren..."
173 - [COPY_DONE]="Kopieren abgeschlossen."
174 - [COPY_ERR]="Kopierfehler – siehe %s"
175 - [CONF_HOST]="Systemkonfiguration"
176 - [HOST_PROMPT]="Rechnername (Hostname):"
177 - [HOST_ERR]="Der Rechnername darf nicht leer sein."
178 - [TZ_TITLE]="Zeitzone"
179 - [TZ_PROMPT]="Zeitzone wählen:"
180 - [LOC_TITLE]="Gebietsschema"
181 - [LOC_PROMPT]="System-Locale wählen:"
182 - [ROOT_PASS_TITLE]="Root-Passwort"
183 - [ROOT_PROMPT1]="Root-Passwort eingeben:"
184 - [ROOT_PROMPT2]="Root-Passwort wiederholen:"
185 - [PASS_MISMATCH]="Passwörter stimmen nicht überein!"
186 - [PASS_EMPTY]="Das Passwort darf nicht leer sein."
187 - [USER_TITLE]="Benutzer"
188 - [USER_PROMPT]="Benutzername (leer = überspringen):"
189 - [USER_PASS1]="Passwort für Benutzer %s:"
190 - [USER_PASS2]="Passwort wiederholen:"
191 - [GRUB_TITLE]="Bootloader"
192 - [GRUB_MSG]="GRUB wird installiert..."
193 - [GRUB_DONE]="GRUB installiert."
194 - [GRUB_ERR]="GRUB-Installation fehlgeschlagen (Log: %s)"
195 - [FINISH]="Installation erfolgreich abgeschlossen!\n\nDas System befindet sich in %s.\nNach dem Neustart mit dem erstellten Benutzer anmelden."
196 - [REBOOT]="Jetzt neu starten?"
197 - [EFI_TITLE]="EFI-Partition"
198 - [EFI_PROMPT]="EFI-Partition (ESP) wählen:"
199 - [ROOTPART_TITLE]="Root-Partition"
200 - [ROOTPART_PROMPT]="Root-Partition (/) wählen:"
201 - [FORMAT_Q]="Root-Partition formatieren?"
202 - [SWAP_SKIP]="Swap übersprungen."
203 - [DONE_TITLE]="Fertig"
204 - [PRESS_OK]="OK"
205 - [CANCEL]="Abbrechen"
206 - [YES]="Ja"
207 - [NO]="Nein"
208 - [KEYMAP_TITLE]="Tastaturlayout"
209 - [KEYMAP_PROMPT]="Tastaturlayout wählen:"
210 - [CUSTOM]="Eigene (Namen eingeben)..."
211 - [SWAP_TITLE]="Auslagerungsspeicher"
212 - [SWAP_PROMPT]="Swap-Typ wählen:"
213 - [SWAP_PART_OPT]="Swap-Partition"
214 - [SWAP_FILE_OPT]="Swap-Datei (/swapfile)"
215 - [SWAP_NONE_OPT]="Kein Swap"
216 - [SWAP_FILE_SIZE]="Größe der Swap-Datei (MiB):"
217 - [BOOT_TITLE]="Bootloader"
218 - [BOOT_PROMPT]="Bootloader wählen:"
219 - [BOOT_GRUB_OPT]="GRUB (empfohlen)"
220 - [BOOT_NONE_OPT]="Keiner (manuelle Konfiguration)"
221 - [CRYPT_TITLE]="Verschlüsselung (LUKS)"
222 - [CRYPT_PROMPT]="Root-Partition verschlüsseln (LUKS)?\nHinweis: initramfs muss cryptsetup unterstützen."
223 - [CRYPT_PASS1]="Passwort für Verschlüsselung:"
224 - [CRYPT_PASS2]="Passwort wiederholen:"
225 - [CRYPT_OPEN]="Verschlüsselte Partition wird geöffnet..."
226 - [CRYPT_ERR]="cryptsetup-Fehler – Installation abgebrochen."
227 - [ADMIN_PROMPT]="Soll der Benutzer Administrator sein (Gruppe wheel)?"
228 - [REPO_TITLE]="Paket-Repository"
229 - [REPO_PROMPT]="Repository-URL (leer = unverändert):"
230 - [SUMMARY_TITLE]="Konfigurationsübersicht"
231 - [SUMMARY_HEAD]="Die Installation startet mit folgenden Einstellungen:\n\n"
232 - [SUMMARY_CONFIRM]="Fortfahren?"
233 - [L_DISK]="Festplatte:"
234 - ) ;;
235 - *)
236 - T=(
237 - [TITLE]="PaganLinux Installer"
238 - [LANG_TITLE]="Language / Język"
239 - [LANG_PROMPT]="Select installer language:"
240 - [WELCOME]="Welcome to the PaganLinux installer!\n\nThis wizard installs the system from the live media to the selected disk.\nAll data on the target disk will be ERASED.\n\nMake a backup first!"
241 - [ROOT_ERR]="The installer requires root privileges (sudo)."
242 - [DISK_TITLE]="Disk selection"
243 - [DISK_PROMPT]="Select target disk:"
244 - [NO_DISK]="No disks found (/dev/sd*, nvme, vd, mmcblk)."
245 - [DISK_SMALL]="Disk %s is too small (%s MiB, at least %s MiB required)."
246 - [EFIVARS_WARN]="Cannot mount efivars – NVRAM entries will not be updated."
247 - [PART_TITLE]="Partitioning"
248 - [PART_PROMPT]="Select partitioning method:"
249 - [PART_AUTO]="Automatic (whole disk: EFI + swap + root)"
250 - [PART_MANUAL]="Manual (cfdisk)"
251 - [PART_EXISTING]="Use existing partitions"
252 - [FS_TITLE]="Filesystem"
253 - [FS_PROMPT]="Select root filesystem:"
254 - [CONFIRM_TITLE]="FINAL CONFIRMATION"
255 - [CONFIRM_WIPE]="Disk %s will be ERASED and PaganLinux installed on it.\n\nAre you sure you want to continue?"
256 - [ABORT]="Installation aborted."
257 - [FORMATTING]="Formatting and mounting partitions..."
258 - [COPY_TITLE]="Copying system"
259 - [COPY_MSG]="Copying system from live media..."
260 - [COPY_DONE]="Copying finished."
261 - [COPY_ERR]="Copy error – check %s"
262 - [CONF_HOST]="System configuration"
263 - [HOST_PROMPT]="Enter hostname:"
264 - [HOST_ERR]="Hostname cannot be empty."
265 - [TZ_TITLE]="Timezone"
266 - [TZ_PROMPT]="Select timezone:"
267 - [LOC_TITLE]="Locale"
268 - [LOC_PROMPT]="Select system locale:"
269 - [ROOT_PASS_TITLE]="Root password"
270 - [ROOT_PROMPT1]="Enter root password:"
271 - [ROOT_PROMPT2]="Repeat root password:"
272 - [PASS_MISMATCH]="Passwords do not match!"
273 - [PASS_EMPTY]="Password cannot be empty."
274 - [USER_TITLE]="User account"
275 - [USER_PROMPT]="Enter username (empty = skip):"
276 - [USER_PASS1]="Password for user %s:"
277 - [USER_PASS2]="Repeat password:"
278 - [GRUB_TITLE]="Bootloader"
279 - [GRUB_MSG]="Installing GRUB..."
280 - [GRUB_DONE]="GRUB installed."
281 - [GRUB_ERR]="GRUB installation failed (log: %s)"
282 - [FINISH]="Installation completed successfully!\n\nThe system is installed in %s.\nAfter reboot, log in with the created user."
283 - [REBOOT]="Reboot now?"
284 - [EFI_TITLE]="EFI partition"
285 - [EFI_PROMPT]="Select EFI System Partition (ESP):"
286 - [ROOTPART_TITLE]="Root partition"
287 - [ROOTPART_PROMPT]="Select root (/) partition:"
288 - [FORMAT_Q]="Format the root partition?"
289 - [SWAP_SKIP]="Swap skipped."
290 - [DONE_TITLE]="Done"
291 - [PRESS_OK]="OK"
292 - [CANCEL]="Cancel"
293 - [YES]="Yes"
294 - [NO]="No"
295 - [KEYMAP_TITLE]="Keyboard layout"
296 - [KEYMAP_PROMPT]="Select keyboard layout:"
297 - [CUSTOM]="Custom (enter name)..."
298 - [SWAP_TITLE]="Swap"
299 - [SWAP_PROMPT]="Select swap type:"
300 - [SWAP_PART_OPT]="Swap partition"
301 - [SWAP_FILE_OPT]="Swap file (/swapfile)"
302 - [SWAP_NONE_OPT]="No swap"
303 - [SWAP_FILE_SIZE]="Swap file size (MiB):"
304 - [BOOT_TITLE]="Bootloader"
305 - [BOOT_PROMPT]="Select bootloader:"
306 - [BOOT_GRUB_OPT]="GRUB (recommended)"
307 - [BOOT_NONE_OPT]="None (manual setup)"
308 - [CRYPT_TITLE]="Encryption (LUKS)"
309 - [CRYPT_PROMPT]="Encrypt root partition (LUKS)?\nNote: initramfs must support cryptsetup."
310 - [CRYPT_PASS1]="Encryption password:"
311 - [CRYPT_PASS2]="Repeat password:"
312 - [CRYPT_OPEN]="Opening encrypted partition..."
313 - [CRYPT_ERR]="cryptsetup error – installation aborted."
314 - [ADMIN_PROMPT]="Should the user be an administrator (wheel group)?"
315 - [REPO_TITLE]="Package repository"
316 - [REPO_PROMPT]="Repository URL (empty = unchanged):"
317 - [SUMMARY_TITLE]="Configuration summary"
318 - [SUMMARY_HEAD]="The installation will start with these settings:\n\n"
319 - [SUMMARY_CONFIRM]="Continue?"
320 - [L_DISK]="Disk:"
321 - ) ;;
322 - esac
323 -}
324 -T_() { printf '%s' "${T[$1]:-[$1]}"; }
325 -TF_() { local k="$1"; shift; local fmt="${T[$k]:-[$k]}"; fmt="${fmt//\\n/$'\n'}"; printf "$fmt" "$@"; }
326 -_nl() { printf '%s' "${1//\\n/$'\n'}"; }
327 -
328 -# ── Wrapper UI ──
329 -ui_info() { # $1 text
330 - local t; t=$(_nl "$1")
331 - case "$UI" in
332 - dialog) dialog --title "$(T_ TITLE)" --msgbox "$t" 0 0 || true ;;
333 - whiptail) whiptail --title "$(T_ TITLE)" --msgbox "$t" 0 0 || true ;;
334 - *) echo; echo "$t"; echo; read -r -p "$(T_ PRESS_OK) " _ || true ;;
335 - esac
336 -}
337 -ui_menu() { # $1 text; $2 tag1 $3 item1 ... ; wynik -> MENU_RESULT
338 - local text; text=$(_nl "$1")
339 - shift
340 - case "$UI" in
341 - dialog) MENU_RESULT=$(dialog --title "$(T_ TITLE)" --menu "$text" 0 0 0 "$@" 2>&1 >/dev/tty) || MENU_RESULT="" ;;
342 - whiptail) MENU_RESULT=$(whiptail --title "$(T_ TITLE)" --menu "$text" 0 0 0 "$@" 3>&1 1>&2 2>&3) || MENU_RESULT="" ;;
343 - *)
344 - echo; echo "$text"
345 - local args=("$@") i=1 n=1
346 - while [[ $i -lt ${#args[@]} ]]; do
347 - echo " $n) ${args[$i]}"
348 - TAGS[$n]="${args[$((i-1))]}"
349 - i=$((i+2)); n=$((n+1))
350 - done
351 - local c
352 - read -r -p " [1-$((n-1))] " c || true
353 - MENU_RESULT="${TAGS[$c]:-}"
354 - ;;
355 - esac
356 - log "menu=$text -> $MENU_RESULT"
357 -}
358 -ui_input() { # $1 text [$2 domyślne] -> INPUT_RESULT
359 - local text="$1" def="${2:-}"
360 - case "$UI" in
361 - dialog) INPUT_RESULT=$(dialog --title "$(T_ TITLE)" --inputbox "$text" 0 0 "$def" 2>&1 >/dev/tty) || INPUT_RESULT="" ;;
362 - whiptail) INPUT_RESULT=$(whiptail --title "$(T_ TITLE)" --inputbox "$text" 0 0 "$def" 3>&1 1>&2 2>&3) || INPUT_RESULT="" ;;
363 - *) echo; echo "$text"; read -r -p "> " INPUT_RESULT || true ;;
364 - esac
365 - log "input -> $INPUT_RESULT"
366 -}
367 -ui_password() { # $1 text -> PASS_RESULT
368 - local text="$1"
369 - case "$UI" in
370 - dialog) PASS_RESULT=$(dialog --title "$(T_ TITLE)" --insecure --passwordbox "$text" 0 0 2>&1 >/dev/tty) || PASS_RESULT="" ;;
371 - whiptail) PASS_RESULT=$(whiptail --title "$(T_ TITLE)" --passwordbox "$text" 0 0 3>&1 1>&2 2>&3) || PASS_RESULT="" ;;
372 - *) echo; echo "$text"; read -r -s -p "> " PASS_RESULT || true; echo ;;
373 - esac
374 -}
375 -ui_yesno() { # $1 text -> 0=tak 1=nie
376 - local t; t=$(_nl "$1")
377 - case "$UI" in
378 - dialog) dialog --title "$(T_ TITLE)" --yesno "$t" 0 0; return $? ;;
379 - whiptail) whiptail --title "$(T_ TITLE)" --yesno "$t" 0 0; return $? ;;
380 - *) echo; echo "$t"; read -r -p "$(T_ YES)/$(T_ NO) [y/N] " c || true; [[ "$c" == "y" || "$c" == "Y" ]] ;;
381 - esac
382 -}
383 -ui_gauge() { # $1 text; $2 <liczba>0-100
384 - case "$UI" in
385 - dialog) dialog --title "$(T_ TITLE)" --gauge "$1" 0 0 <&0 ;;
386 - whiptail) whiptail --title "$(T_ TITLE)" --gauge "$1" 0 0 <&0 ;;
387 - *) cat >/dev/null ;;
388 - esac
389 -}
390 -
391 -# ── Pomocnicze ──
392 -need_root() {
393 - [[ $EUID -eq 0 ]] || {
394 - # stderr jest przekierowany do logu – pokaż błąd też na konsoli
395 - local m; m="$(T_ ROOT_ERR)"
396 - echo "$m" > /dev/tty 2>/dev/null
397 - echo "$m" >&2
398 - exit 1
399 - }
400 -}
401 -is_uefi() { [[ -d /sys/firmware/efi ]]; }
402 -part_path() { # $1 dysk $2 nr -> /dev/sda1, /dev/nvme0n1p1
403 - local d="$1" n="$2"
404 - case "$d" in
405 - *nvme*|*mmcblk*|*vd*) echo "${d}p${n}" ;;
406 - *) echo "${d}${n}" ;;
407 - esac
408 -}
409 -list_disks() {
410 - lsblk -dn -o NAME,SIZE,MODEL 2>/dev/null | awk '{print "/dev/"$1" "$2" "$3}'
411 -}
412 -confirm_wipe() {
413 - local disk="$1"
414 - ui_yesno "$(TF_ CONFIRM_WIPE "$disk")" || { echo "$(T_ ABORT)"; exit 0; }
415 -}
416 -
417 -# ── Kroki ──
418 -step_language() {
419 - local old="${LANG:-en}"
420 - ui_menu "$(T_ LANG_PROMPT)" \
421 - en "English" pl "Polski" de "Deutsch" || true
422 - case "${MENU_RESULT:-}" in
423 - pl|de|en) set_lang "$MENU_RESULT" ;;
424 - *) set_lang "$old" ;;
425 - esac
426 - export LANG="${LANG%%_*}"
427 -}
428 -
429 -step_keymap() {
430 - ui_menu "$(T_ KEYMAP_PROMPT)" \
431 - us "US (qwerty)" de "Deutsch" pl "Polski" cz "Česky" \
432 - fr "Français" es "Español" it "Italiano" custom "$(T_ CUSTOM)"
433 - KEYMAP="${MENU_RESULT:-}"
434 - if [[ "$KEYMAP" == "custom" ]]; then
435 - ui_input "$(T_ KEYMAP_PROMPT)" "us"
436 - KEYMAP="${INPUT_RESULT:-us}"
437 - fi
438 - if [[ -n "$KEYMAP" ]]; then
439 - loadkeys "$KEYMAP" 2>/dev/null || true
440 - fi
441 -}
442 -
443 -step_swap() {
444 - ui_menu "$(T_ SWAP_PROMPT)" \
445 - partition "$(T_ SWAP_PART_OPT)" \
446 - file "$(T_ SWAP_FILE_OPT)" \
447 - none "$(T_ SWAP_NONE_OPT)"
448 - SWAP_MODE="${MENU_RESULT:-partition}"
449 - if [[ "$SWAP_MODE" == "file" ]]; then
450 - ui_input "$(T_ SWAP_FILE_SIZE)" "$SWAP_SIZE"
451 - SWAP_SIZE="${INPUT_RESULT:-$SWAP_SIZE}"
452 - fi
453 - log "swap-mode=$SWAP_MODE size=$SWAP_SIZE"
454 -}
455 -
456 -step_boot() {
457 - ui_menu "$(T_ BOOT_PROMPT)" \
458 - grub "$(T_ BOOT_GRUB_OPT)" \
459 - none "$(T_ BOOT_NONE_OPT)"
460 - BOOT_MODE="${MENU_RESULT:-grub}"
461 - log "boot-mode=$BOOT_MODE"
462 -}
463 -
464 -step_encrypt() {
465 - if command -v cryptsetup >/dev/null 2>&1; then
466 - if ui_yesno "$(T_ CRYPT_PROMPT)"; then ENCRYPT=1; else ENCRYPT=0; fi
467 - else
468 - ENCRYPT=0
469 - fi
470 - log "encrypt=$ENCRYPT"
471 -}
472 -
473 -step_repo() {
474 - ui_input "$(T_ REPO_PROMPT)" ""
475 - REPO_URL="${INPUT_RESULT:-}"
476 - log "repo=$REPO_URL"
477 -}
478 -
479 -step_summary() {
480 - local s sw b
481 - s="$(T_ SUMMARY_HEAD)"
482 - s+=$(printf '%-24s %s\n' "$(T_ L_DISK)" "$DISK")
483 - s+=$(printf '%-24s %s\n' "$(T_ PART_TITLE):" "$PART_MODE")
484 - s+=$(printf '%-24s %s\n' "$(T_ FS_TITLE):" "$ROOT_FS")
485 - case "$SWAP_MODE" in
486 - file) sw="$(T_ SWAP_FILE_OPT) ($SWAP_SIZE MiB)" ;;
487 - none) sw="$(T_ SWAP_NONE_OPT)" ;;
488 - *) sw="$(T_ SWAP_PART_OPT)" ;;
489 - esac
490 - s+=$(printf '%-24s %s\n' "$(T_ SWAP_TITLE):" "$sw")
491 - if [[ "$BOOT_MODE" == "none" ]]; then b="$(T_ BOOT_NONE_OPT)"; else b="$(T_ BOOT_GRUB_OPT)"; fi
492 - s+=$(printf '%-24s %s\n' "$(T_ BOOT_TITLE):" "$b")
493 - if [[ "$ENCRYPT" == "1" ]]; then
494 - s+=$(printf '%-24s %s\n' "$(T_ CRYPT_TITLE):" "LUKS")
495 - fi
496 - if [[ -n "$KEYMAP" ]]; then
497 - s+=$(printf '%-24s %s\n' "$(T_ KEYMAP_TITLE):" "$KEYMAP")
498 - fi
499 - if [[ -n "$REPO_URL" ]]; then
500 - s+=$(printf '%-24s %s\n' "$(T_ REPO_TITLE):" "$REPO_URL")
501 - fi
502 - s+=$'\n'"$(TF_ CONFIRM_WIPE "$DISK")"
503 - ui_yesno "$s" || { echo "$(T_ ABORT)"; exit 0; }
504 -}
505 -
506 -step_disk() {
507 - local disks line d info items=()
508 - mapfile -t disks < <(list_disks)
509 - [[ ${#disks[@]} -gt 0 ]] || { echo "$(T_ NO_DISK)"; exit 1; }
510 - for line in "${disks[@]}"; do
511 - d=$(echo "$line" | awk '{print $1}')
512 - info=$(echo "$line" | awk '{$1=""; print}' | xargs)
513 - items+=("$d" "$info")
514 - done
515 - ui_menu "$(T_ DISK_PROMPT)" "${items[@]}"
516 - DISK="${MENU_RESULT:-}"
517 - [[ -n "$DISK" && -b "$DISK" ]] || { echo "$(T_ ABORT)"; exit 1; }
518 -}
519 -
520 -step_partition() {
521 - ui_menu "$(T_ PART_PROMPT)" \
522 - auto "$(T_ PART_AUTO)" \
523 - manual "$(T_ PART_MANUAL)" \
524 - existing "$(T_ PART_EXISTING)"
525 - PART_MODE="${MENU_RESULT:-auto}"
526 - log "part-mode=$PART_MODE"
527 -
528 - case "$PART_MODE" in
529 - manual)
530 - if command -v cfdisk >/dev/null 2>&1; then
531 - cfdisk "$DISK" || true
532 - elif command -v fdisk >/dev/null 2>&1; then
533 - fdisk "$DISK" || true
534 - else
535 - echo "Brak cfdisk/fdisk"; exit 1
536 - fi
537 - partprobe "$DISK" 2>/dev/null || true; sleep 1
538 - pick_partitions
539 - ;;
540 - existing)
541 - pick_partitions
542 - ;;
543 - *)
544 - auto_partition
545 - ;;
546 - esac
547 -}
548 -
549 -auto_partition() {
550 - local p1 p2 p3
551 - p1=$(part_path "$DISK" 1); p2=$(part_path "$DISK" 2); p3=$(part_path "$DISK" 3)
552 - log "auto: $DISK -> $p1 $p2 $p3 (swap=$SWAP_MODE)"
553 -
554 - # ── Weryfikacja pojemności nośnika przed partycjonowaniem ──
555 - local disk_bytes total_min
556 - disk_bytes=$(lsblk -bno SIZE "$DISK" 2>/dev/null | head -n1)
557 - if [[ ! "$disk_bytes" =~ ^[0-9]+$ ]]; then
558 - echo "Nie można odczytać rozmiaru dysku $DISK"; exit 1
559 - fi
560 - if is_uefi; then
561 - total_min=$((512 + ROOT_MIN))
562 - else
563 - total_min=$((1 + ROOT_MIN))
564 - fi
565 - if [[ "$SWAP_MODE" == "partition" ]]; then
566 - total_min=$((total_min + SWAP_SIZE))
567 - fi
568 - if (( disk_bytes / 1048576 < total_min )); then
569 - echo "$(TF_ DISK_SMALL "$DISK" "$((disk_bytes/1048576))" "$total_min")"
570 - exit 1
571 - fi
572 -
573 - if command -v sfdisk >/dev/null 2>&1; then
574 - if is_uefi; then
575 - if [[ "$SWAP_MODE" == "partition" ]]; then
576 - sfdisk "$DISK" <<EOF
577 -label: gpt
578 -size=512MiB, type=C12A7328-F81D-11D2-BA4B-00A0C93EC93B, name=EFI
579 -size=${SWAP_SIZE}MiB, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name=swap
580 -type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
581 -EOF
582 - else
583 - sfdisk "$DISK" <<EOF
584 -label: gpt
585 -size=512MiB, type=C12A7328-F81D-11D2-BA4B-00A0C93EC93B, name=EFI
586 -type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
587 -EOF
588 - fi
589 - else
590 - if [[ "$SWAP_MODE" == "partition" ]]; then
591 - sfdisk "$DISK" <<EOF
592 -label: gpt
593 -size=1MiB, type=21686148-6449-6E6F-744E-656564454649, name=bios_boot
594 -size=${SWAP_SIZE}MiB, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name=swap
595 -type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
596 -EOF
597 - else
598 - sfdisk "$DISK" <<EOF
599 -label: gpt
600 -size=1MiB, type=21686148-6449-6E6F-744E-656564454649, name=bios_boot
601 -type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
602 -EOF
603 - fi
604 - fi
605 - elif command -v parted >/dev/null 2>&1; then
606 - parted -s "$DISK" mklabel gpt
607 - if is_uefi; then
608 - parted -s "$DISK" mkpart ESP fat32 1MiB 513MiB
609 - parted -s "$DISK" set 1 esp on
610 - if [[ "$SWAP_MODE" == "partition" ]]; then
611 - parted -s "$DISK" mkpart swap linux-swap 513MiB $((513+SWAP_SIZE))MiB
612 - parted -s "$DISK" mkpart root ext4 $((513+SWAP_SIZE))MiB 100%
613 - else
614 - parted -s "$DISK" mkpart root ext4 513MiB 100%
615 - fi
616 - else
617 - if [[ "$SWAP_MODE" == "partition" ]]; then
618 - parted -s "$DISK" mkpart swap linux-swap 1MiB $((1+SWAP_SIZE))MiB
619 - parted -s "$DISK" mkpart root ext4 $((1+SWAP_SIZE))MiB 100%
620 - parted -s "$DISK" set 2 boot on
621 - else
622 - parted -s "$DISK" mkpart root ext4 1MiB 100%
623 - parted -s "$DISK" set 1 boot on
624 - fi
625 - fi
626 - else
627 - echo "Brak sfdisk/parted"; exit 1
628 - fi
629 - partprobe "$DISK" 2>/dev/null || true
630 - sleep 2
631 -
632 - if is_uefi; then
633 - EFI_PART="$p1"
634 - if [[ "$SWAP_MODE" == "partition" ]]; then
635 - SWAP_PART="$p2"; ROOT_PART="$p3"
636 - else
637 - SWAP_PART=""; ROOT_PART="$p2"
638 - fi
639 - else
640 - EFI_PART=""
641 - if [[ "$SWAP_MODE" == "partition" ]]; then
642 - SWAP_PART="$p1"; ROOT_PART="$p2"
643 - else
644 - SWAP_PART=""; ROOT_PART="$p1"
645 - fi
646 - fi
647 -}
648 -
649 -pick_partitions() {
650 - local sep="" items=() line p fst size
651 - case "$DISK" in *nvme*|*mmcblk*|*vd*) sep="p" ;; esac
652 - while IFS= read -r line; do
653 - [[ -n "$line" ]] || continue
654 - p="$DISK${sep}$(echo "$line" | awk '{print $1}')"
655 - size=$(echo "$line" | awk '{print $2}')
656 - fst=$(echo "$line" | awk '{print $3}')
657 - items+=("$p" "$size ${fst:-brak-fs}")
658 - done < <(lsblk -rn -o NAME,SIZE,FSTYPE "$DISK" | tail -n +2)
659 -
660 - [[ ${#items[@]} -gt 0 ]] || { echo "Brak partycji na $DISK"; exit 1; }
661 -
662 - ui_menu "$(T_ ROOTPART_PROMPT)" "${items[@]}"
663 - ROOT_PART="${MENU_RESULT:-}"
664 -
665 - if is_uefi; then
666 - local esp=() i
667 - for ((i=0; i<${#items[@]}; i+=2)); do
668 - local efst; efst=$(lsblk -rno FSTYPE "${items[$i]}" 2>/dev/null || true)
669 - if [[ "$efst" == "vfat" ]]; then
670 - esp+=("${items[$i]}" "ESP (vfat)")
671 - fi
672 - done
673 - if [[ ${#esp[@]} -gt 0 ]]; then
674 - ui_menu "$(T_ EFI_PROMPT)" "${esp[@]}"
675 - else
676 - ui_menu "$(T_ EFI_PROMPT)" "${items[@]}"
677 - fi
678 - EFI_PART="${MENU_RESULT:-}"
679 - else
680 - EFI_PART=""
681 - fi
682 -
683 - # swap: spróbuj wykryć po FSTYPE
684 - SWAP_PART=""
685 - local j
686 - for ((j=0; j<${#items[@]}; j+=2)); do
687 - local sfst; sfst=$(lsblk -rno FSTYPE "${items[$j]}" 2>/dev/null || true)
688 - if [[ "$sfst" == "swap" ]]; then
689 - SWAP_PART="${items[$j]}"
690 - break
691 - fi
692 - done
693 -
694 - if ui_yesno "$(T_ FORMAT_Q)"; then FORMAT_ROOT=1; else FORMAT_ROOT=0; fi
695 -}
696 -
697 -step_fs() {
698 - ui_menu "$(T_ FS_PROMPT)" ext4 "ext4" xfs "xfs" btrfs "btrfs"
699 - ROOT_FS="${MENU_RESULT:-ext4}"
700 -}
701 -
702 -step_format_mount() {
703 - echo "$(T_ FORMATTING)" >&3
704 - log "format: efi=$EFI_PART swap=$SWAP_PART root=$ROOT_PART fs=$ROOT_FS enc=$ENCRYPT"
705 -
706 - mkdir -p "$TARGET"
707 - # Odmontuj ewentualne stare montowania
708 - umount -R "$TARGET" 2>/dev/null || true
709 -
710 - # ── Szyfrowanie LUKS (przed mkfs) ──
711 - if [[ "$ENCRYPT" == "1" ]]; then
712 - local cp1 cp2
713 - ui_password "$(T_ CRYPT_PASS1)"; cp1="$PASS_RESULT"
714 - ui_password "$(T_ CRYPT_PASS2)"; cp2="$PASS_RESULT"
715 - if [[ "$cp1" != "$cp2" ]]; then
716 - echo "$(T_ PASS_MISMATCH)"; exit 1
717 - fi
718 - [[ -n "$cp1" ]] || { echo "$(T_ PASS_EMPTY)"; exit 1; }
719 - echo "$(T_ CRYPT_OPEN)" >&3
720 - printf '%s\n' "$cp1" | cryptsetup luksFormat -q "$ROOT_PART" - \
721 - || { echo "$(T_ CRYPT_ERR)"; exit 1; }
722 - printf '%s\n' "$cp1" | cryptsetup open "$ROOT_PART" cryptroot \
723 - || { echo "$(T_ CRYPT_ERR)"; exit 1; }
724 - CRYPT_UUID=$(blkid -s UUID -o value "$ROOT_PART")
725 - ROOT_PART="/dev/mapper/cryptroot"
726 - log "luks: cryptroot <- $CRYPT_UUID"
727 - fi
728 -
729 - if [[ -n "$EFI_PART" ]]; then
730 - mkfs.vfat -F32 -n PAGAN_EFI "$EFI_PART" >/dev/null
731 - fi
732 - if [[ -n "$SWAP_PART" ]]; then
733 - mkswap -L PAGAN_SWAP "$SWAP_PART" >/dev/null
734 - elif [[ "$SWAP_MODE" == "none" ]]; then
735 - echo "$(T_ SWAP_SKIP)" >&3
736 - fi
737 - if [[ "$FORMAT_ROOT" != "0" ]]; then
738 - mkfs -t "$ROOT_FS" -L PAGAN "$ROOT_PART" >/dev/null
739 - fi
740 -
741 - mount -t "$ROOT_FS" "$ROOT_PART" "$TARGET" || mount "$ROOT_PART" "$TARGET"
742 - if [[ -n "$EFI_PART" ]]; then
743 - mkdir -p "$TARGET/boot/efi"
744 - mount "$EFI_PART" "$TARGET/boot/efi"
745 - fi
746 - if [[ "$SWAP_MODE" == "partition" && -n "$SWAP_PART" ]]; then
747 - swapon "$SWAP_PART" 2>/dev/null || true
748 - fi
749 -}
750 -
751 -step_copy() {
752 - local src="$LIVE_DIR"
753 - [[ -d "$src" ]] || { echo "Źródło $src nie istnieje"; exit 1; }
754 -
755 - local excl=(--exclude=/dev --exclude=/proc --exclude=/sys --exclude=/run
756 - --exclude=/tmp --exclude=/mnt --exclude=/media --exclude=/lost+found
757 - --exclude="$TARGET" --exclude=/var/cache/pag/repos)
758 -
759 - if command -v rsync >/dev/null 2>&1; then
760 - if [[ "$UI" == "plain" ]]; then
761 - echo "$(T_ COPY_MSG)"
762 - rsync -aAX "$src"/ "$TARGET"/ "${excl[@]}" >/dev/null || { echo "$(TF_ COPY_ERR "$LOG")"; exit 1; }
763 - else
764 - # Gauge: postęp rsync (info=progress2, procenty z \r-końcówek)
765 - local tmpfifo rcfile
766 - tmpfifo=$(mktemp -u); rcfile=$(mktemp)
767 - mkfifo "$tmpfifo"
768 - # set +e w podsabli: zapisz kod wyjścia rsync do rcfile, zanim
769 - # pipefail/subshella ubije potok (grep bez dopasowania -> status 1).
770 - ( set +e
771 - rsync -aAX --info=progress2 "$src"/ "$TARGET"/ "${excl[@]}" 2>&1 \
772 - | tr '\r' '\n' | { grep -oE '[0-9]{1,3}%' || true; } | tr -d '%' > "$tmpfifo"
773 - echo "${PIPESTATUS[0]}" > "$rcfile" ) &
774 - local rpid=$!
775 - ( while read -r p; do echo "$p"; done < "$tmpfifo" ) | ui_gauge "$(T_ COPY_MSG)"
776 - wait "$rpid" 2>/dev/null || true
777 - rm -f "$tmpfifo"
778 - [[ "$(cat "$rcfile")" == "0" ]] || { rm -f "$rcfile"; echo "$(TF_ COPY_ERR "$LOG")"; exit 1; }
779 - rm -f "$rcfile"
780 - fi
781 - else
782 - echo "$(T_ COPY_MSG)"
783 - ( cd "$src" && tar --exclude=./dev --exclude=./proc --exclude=./sys \
784 - --exclude=./run --exclude=./tmp --exclude=./mnt --exclude=./media \
785 - -cf - . ) | ( cd "$TARGET" && tar -xpf - )
786 - fi
787 - # Sprawdź, czy system faktycznie skopiowany
788 - [[ -x "$TARGET/bin/bash" || -x "$TARGET/usr/bin/bash" ]] || { echo "$(TF_ COPY_ERR "$LOG")"; exit 1; }
789 - echo "$(T_ COPY_DONE)" >&3
790 -}
791 -
792 -step_configure() {
793 - # ── montuj systemy wirtualne ──
794 - mount --bind /dev "$TARGET/dev" 2>/dev/null || true
795 - mount --bind /dev/pts "$TARGET/dev/pts" 2>/dev/null || true
796 - mount -t proc proc "$TARGET/proc"
797 - mount -t sysfs sys "$TARGET/sys"
798 - mount -t tmpfs tmpfs "$TARGET/run" 2>/dev/null || true
799 - # Zmienne EFI (efivarfs) – bez nich grub-install nie zaktualizuje NVRAM.
800 - if is_uefi; then
801 - mkdir -p "$TARGET/sys/firmware/efi/efivars"
802 - mount --bind /sys/firmware/efi/efivars "$TARGET/sys/firmware/efi/efivars" \
803 - 2>/dev/null || log "$(T_ EFIVARS_WARN)"
804 - fi
805 - cp -L /etc/resolv.conf "$TARGET/etc/resolv.conf" 2>/dev/null || true
806 -
807 - # ── fstab ──
808 - {
809 - echo "# /etc/fstab – wygenerowane przez pagan-install"
810 - local uuid fs
811 - uuid=$(blkid -s UUID -o value "$ROOT_PART"); fs=$(blkid -s TYPE -o value "$ROOT_PART")
812 - if [[ "$ENCRYPT" == "1" ]]; then
813 - echo "/dev/mapper/cryptroot / $fs defaults,noatime 0 1"
814 - else
815 - echo "UUID=$uuid / $fs defaults,noatime 0 1"
816 - fi
817 - if [[ -n "$EFI_PART" ]]; then
818 - uuid=$(blkid -s UUID -o value "$EFI_PART")
819 - echo "UUID=$uuid /boot/efi vfat umask=0077 0 2"
820 - fi
821 - if [[ "$SWAP_MODE" == "partition" && -n "$SWAP_PART" ]]; then
822 - uuid=$(blkid -s UUID -o value "$SWAP_PART")
823 - echo "UUID=$uuid none swap sw 0 0"
824 - fi
825 - } > "$TARGET/etc/fstab"
826 -
827 - # ── crypttab (LUKS) ──
828 - if [[ "$ENCRYPT" == "1" && -n "$CRYPT_UUID" ]]; then
829 - echo "cryptroot UUID=$CRYPT_UUID none luks" > "$TARGET/etc/crypttab"
830 - fi
831 -
832 - # ── swapfile (gdy wybrano swap jako plik) ──
833 - if [[ "$SWAP_MODE" == "file" ]]; then
834 - if ! fallocate -l "${SWAP_SIZE}M" "$TARGET/swapfile" 2>/dev/null; then
835 - dd if=/dev/zero of="$TARGET/swapfile" bs=1M count="$SWAP_SIZE" status=none
836 - fi
837 - chmod 600 "$TARGET/swapfile"
838 - mkswap "$TARGET/swapfile" >/dev/null
839 - echo "/swapfile none swap sw 0 0" >> "$TARGET/etc/fstab"
840 - fi
841 -
842 - # ── GRUB ──
843 - if [[ "$BOOT_MODE" != "none" ]]; then
844 - local grub_ok=0
845 - if is_uefi; then
846 - chroot "$TARGET" grub-install --target=x86_64-efi --efi-directory=/boot/efi \
847 - --bootloader-id=PaganLinux --recheck >/dev/null 2>&1 && grub_ok=1 || true
848 - else
849 - chroot "$TARGET" grub-install --target=i386-pc "$DISK" >/dev/null 2>&1 && grub_ok=1 || true
850 - fi
851 - if [[ $grub_ok -eq 1 ]]; then
852 - chroot "$TARGET" grub-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1 || true
853 - echo "$(T_ GRUB_DONE)" >&3
854 - else
855 - echo "$(TF_ GRUB_ERR "$LOG")" >&3
856 - fi
857 - else
858 - log "bootloader pominięty (BOOT_MODE=none)"
859 - fi
860 -
861 - # ── układ klawiatury (vconsole) ──
862 - if [[ -n "$KEYMAP" ]]; then
863 - echo "KEYMAP=$KEYMAP" > "$TARGET/etc/vconsole.conf"
864 - fi
865 -
866 - # ── repozytorium pag ──
867 - if [[ -n "$REPO_URL" ]]; then
868 - echo "$REPO_URL" > "$TARGET/etc/pag/repos.conf"
869 - fi
870 -
871 - # ── hostname ──
872 - ui_input "$(T_ HOST_PROMPT)" "pagan"
873 - while [[ -z "${INPUT_RESULT:-}" ]]; do
874 - ui_input "$(T_ HOST_ERR)" "pagan"
875 - done
876 - HOSTNAME="${INPUT_RESULT}"
877 - echo "$HOSTNAME" > "$TARGET/etc/hostname"
878 - sed -i "s/^127.0.1.1.*/127.0.1.1\t$HOSTNAME/" "$TARGET/etc/hosts" 2>/dev/null || \
879 - echo "127.0.1.1 $HOSTNAME" >> "$TARGET/etc/hosts"
880 -
881 - # ── strefa czasowa ──
882 - local tz_items=(UTC UTC Europe/Warsaw Europe/Warsaw Europe/Berlin Europe/Berlin
883 - Europe/London Europe/London Europe/Prague Europe/Prague America/New_York America/New_York
884 - Asia/Tokyo Asia/Tokyo other "$(T_ CUSTOM)")
885 - ui_menu "$(T_ TZ_PROMPT)" "${tz_items[@]}"
886 - local tz="${MENU_RESULT:-UTC}"
887 - if [[ "$tz" == "other" ]]; then
888 - ui_input "$(T_ TZ_PROMPT)" "Europe/Warsaw"; tz="${INPUT_RESULT:-UTC}"
889 - fi
890 - if [[ -f "$TARGET/usr/share/zoneinfo/$tz" ]]; then
891 - ln -sf "/usr/share/zoneinfo/$tz" "$TARGET/etc/localtime"
892 - echo "$tz" > "$TARGET/etc/timezone" 2>/dev/null || true
893 - fi
894 -
895 - # ── locale ──
896 - local loc_items=(en_US.UTF-8 en_US.UTF-8 pl_PL.UTF-8 pl_PL.UTF-8
897 - de_DE.UTF-8 de_DE.UTF-8 C.UTF-8 C.UTF-8 other "$(T_ CUSTOM)")
898 - ui_menu "$(T_ LOC_PROMPT)" "${loc_items[@]}"
899 - local loc="${MENU_RESULT:-C.UTF-8}"
900 - if [[ "$loc" == "other" ]]; then
901 - ui_input "$(T_ LOC_PROMPT)" "pl_PL.UTF-8"; loc="${INPUT_RESULT:-C.UTF-8}"
902 - fi
903 - echo "LANG=$loc" > "$TARGET/etc/locale.conf"
904 - if grep -q "^#$loc " "$TARGET/etc/locale.gen" 2>/dev/null; then
905 - sed -i "s|^#$loc |$loc |" "$TARGET/etc/locale.gen"
906 - chroot "$TARGET" locale-gen >/dev/null 2>&1 || true
907 - fi
908 -
909 - # ── hasło root ──
910 - local p1 p2
911 - ui_password "$(T_ ROOT_PROMPT1)"; p1="$PASS_RESULT"
912 - ui_password "$(T_ ROOT_PROMPT2)"; p2="$PASS_RESULT"
913 - if [[ "$p1" != "$p2" ]]; then
914 - echo "$(T_ PASS_MISMATCH)"; exit 1
915 - fi
916 - [[ -n "$p1" ]] || { echo "$(T_ PASS_EMPTY)"; exit 1; }
917 - echo "root:$p1" | chroot "$TARGET" chpasswd
918 -
919 - # ── użytkownik ──
920 - ui_input "$(T_ USER_PROMPT)" ""
921 - local user="${INPUT_RESULT:-}"
922 - if [[ -n "$user" ]]; then
923 - ui_password "$(TF_ USER_PASS1 "$user")"; p1="$PASS_RESULT"
924 - ui_password "$(T_ USER_PASS2)"; p2="$PASS_RESULT"
925 - [[ "$p1" == "$p2" ]] || { echo "$(T_ PASS_MISMATCH)"; exit 1; }
926 - local ugroups="audio,video,cdrom,plugdev,storage,input"
927 - if ui_yesno "$(T_ ADMIN_PROMPT)"; then
928 - ugroups="wheel,$ugroups"
929 - fi
930 - chroot "$TARGET" useradd -m -G "$ugroups" \
931 - -s /bin/bash "$user" 2>/dev/null || true
932 - echo "$user:$p1" | chroot "$TARGET" chpasswd
933 - fi
934 -
935 - # ── posprzątaj ──
936 - cp "$LOG" "$TARGET/var/log/pagan-install.log" 2>/dev/null || true
937 - if is_uefi; then
938 - umount "$TARGET/sys/firmware/efi/efivars" 2>/dev/null || true
939 - fi
940 - umount -R "$TARGET/dev" "$TARGET/proc" "$TARGET/sys" "$TARGET/run" 2>/dev/null || true
941 -}
942 -
943 -step_finish() {
944 - ui_info "$(TF_ FINISH "$TARGET")"
945 - case "$DO_REBOOT" in
946 - never) ;;
947 - always) reboot ;;
948 - *) if ui_yesno "$(T_ REBOOT)"; then reboot; fi ;;
949 - esac
950 -}
951 -
952 -# ── main ──
953 -main() {
954 - set_lang
955 - need_root
956 - log "=== pagan-install v$VERSION (UI=$UI) ==="
957 - step_language
958 - step_keymap
959 - ui_info "$(T_ WELCOME)"
960 -
961 - step_disk
962 - step_partition
963 - step_fs
964 - step_swap
965 - step_boot
966 - step_encrypt
967 - step_repo
968 - step_summary
969 - step_format_mount
970 - step_copy
971 - step_configure
972 - step_finish
973 -}
974 -
975 -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
976 - main "$@"
977 -fi
1 +#!/usr/bin/env bash
2 +# =============================================================================
3 +# pagan-install – TUI instalator PaganLinux (dialog/whiptail + fallback tekstowy)
4 +#
5 +# Instaluje system z działającego live (USB/ISO) na dysk twardy:
6 +# • wybór języka (PL/EN/DE) • wybór dysku
7 +# • partycjonowanie: automatyczne (GPT+EFI/BIOS) / ręczne (cfdisk) / gotowe
8 +# • system plików (ext4/xfs/btrfs) • kopia systemu (rsync)
9 +# • konfiguracja w chroot: fstab, GRUB, locale, strefa czasowa,
10 +# hasło root, użytkownik
11 +#
12 +# Użycie: pagan-install (TUI)
13 +# LANG=pl pagan-install (wymuszony język)
14 +# PAGAN_LIVE_DIR=/mnt/live pagan-install (źródło systemu)
15 +# =============================================================================
16 +
17 +VERSION="1.0.0"
18 +LOG="/tmp/pagan-install.log"
19 +TARGET="/mnt/pagan"
20 +LIVE_DIR="${PAGAN_LIVE_DIR:-/}"
21 +SWAP_SIZE="${PAGAN_SWAP:-4096}" # MiB
22 +ROOT_MIN="${PAGAN_ROOT_MIN:-4096}" # minimalna partycja root (MiB)
23 +DO_REBOOT="${PAGAN_REBOOT:-ask}"
24 +
25 +# ── Opcje konfiguracji (ustawiane w krokach instalatora) ──
26 +KEYMAP="" # układ klawiatury (loadkeys)
27 +SWAP_MODE="partition" # partition | file | none
28 +BOOT_MODE="grub" # grub | none
29 +ENCRYPT=0 # LUKS na partycji root
30 +CRYPT_UUID="" # UUID LUKS (do /etc/crypttab)
31 +ADMIN=1 # użytkownik w grupie wheel
32 +REPO_URL="" # nadpisanie /etc/pag/repos.conf
33 +
34 +# Fail-fast: natychmiast przerwij na każdym błędzie (krytyczne potoki np. mkfs).
35 +set -eE -o pipefail
36 +
37 +# /tmp jest katalogiem sticky: fs.protected_regular blokuje O_CREAT na pliku
38 +# należącym do innego użytkownika (dotyczy to RÓWNIEŻ roota). Gdy log istnieje
39 +# z innym właścicielem – użyj osobnego logu per-UID zamiast kończyć się EACCES.
40 +if [[ -e "$LOG" && ! -O "$LOG" ]]; then
41 + LOG="/tmp/pagan-install-$(id -u).log"
42 +fi
43 +rm -f "$LOG" 2>/dev/null || true
44 +exec 3>"$LOG"; exec 2>>"$LOG"
45 +log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" >&3; }
46 +
47 +# ── Interfejs: dialog > whiptail > tekstowy ──
48 +UI=""
49 +if command -v dialog >/dev/null 2>&1; then
50 + UI="dialog"
51 +elif command -v whiptail >/dev/null 2>&1; then
52 + UI="whiptail"
53 +fi
54 +
55 +# ── i18n ──
56 +declare -A T
57 +set_lang() {
58 + local l="${1:-}"; [[ -n "$l" ]] || l="${LANG:-en}"
59 + l="${l%%_*}"; l="${l%%@*}"; l="${l%%.*}"
60 + case "$l" in
61 + pl)
62 + T=(
63 + [TITLE]="PaganLinux – Instalator"
64 + [LANG_TITLE]="Język / Language"
65 + [LANG_PROMPT]="Wybierz język instalatora:"
66 + [WELCOME]="Witaj w instalatorze PaganLinux!\n\nTen kreator zainstaluje system z nośnika live na wybrany dysk.\nWszystkie dane na dysku docelowym zostaną USUNIĘTE.\n\nPamiętaj o kopii zapasowej!"
67 + [ROOT_ERR]="Instalator wymaga uprawnień root (sudo)."
68 + [DISK_TITLE]="Wybór dysku"
69 + [DISK_PROMPT]="Wybierz dysk docelowy:"
70 + [NO_DISK]="Nie znaleziono dysków (/dev/sd*, nvme, vd, mmcblk)."
71 + [DISK_SMALL]="Dysk %s jest za mały (%s MiB, wymagane co najmniej %s MiB)."
72 + [EFIVARS_WARN]="Nie można zamontować efivars – wpisy NVRAM nie zostaną zaktualizowane."
73 + [PART_TITLE]="Partycjonowanie"
74 + [PART_PROMPT]="Wybierz metodę partycjonowania:"
75 + [PART_AUTO]="Automatyczne (cały dysk: EFI + swap + root)"
76 + [PART_MANUAL]="Ręczne (cfdisk)"
77 + [PART_EXISTING]="Użyj istniejących partycji"
78 + [FS_TITLE]="System plików"
79 + [FS_PROMPT]="Wybierz system plików dla root:"
80 + [CONFIRM_TITLE]="OSTATECZNE POTWIERDZENIE"
81 + [CONFIRM_WIPE]="Dysk %s zostanie WYMAZANY i zainstalowany na nim PaganLinux.\n\nCzy na pewno kontynuować?"
82 + [ABORT]="Instalacja przerwana."
83 + [FORMATTING]="Formatowanie i montowanie partycji..."
84 + [COPY_TITLE]="Kopiowanie systemu"
85 + [COPY_MSG]="Kopiowanie systemu z nośnika live..."
86 + [COPY_DONE]="Kopiowanie zakończone."
87 + [COPY_ERR]="Błąd kopiowania – sprawdź %s"
88 + [CONF_HOST]="Konfiguracja systemu"
89 + [HOST_PROMPT]="Podaj nazwę komputera (hostname):"
90 + [HOST_ERR]="Nazwa komputera nie może być pusta."
91 + [TZ_TITLE]="Strefa czasowa"
92 + [TZ_PROMPT]="Wybierz strefę czasową:"
93 + [LOC_TITLE]="Ustawienia regionalne"
94 + [LOC_PROMPT]="Wybierz locale systemowe:"
95 + [ROOT_PASS_TITLE]="Hasło root"
96 + [ROOT_PROMPT1]="Podaj hasło root:"
97 + [ROOT_PROMPT2]="Powtórz hasło root:"
98 + [PASS_MISMATCH]="Hasła nie są identyczne!"
99 + [PASS_EMPTY]="Hasło nie może być puste."
100 + [USER_TITLE]="Użytkownik"
101 + [USER_PROMPT]="Podaj nazwę użytkownika (puste = pomiń):"
102 + [USER_PASS1]="Hasło dla użytkownika %s:"
103 + [USER_PASS2]="Powtórz hasło:"
104 + [GRUB_TITLE]="Bootloader"
105 + [GRUB_MSG]="Instalowanie GRUB-a..."
106 + [GRUB_DONE]="GRUB zainstalowany."
107 + [GRUB_ERR]="Instalacja GRUB-a nie powiodła się (log: %s)"
108 + [FINISH]="Instalacja zakończona pomyślnie!\n\nSystem jest zainstalowany w %s.\nPo ponownym uruchomieniu zaloguj się jako utworzony użytkownik."
109 + [REBOOT]="Uruchomić ponownie teraz?"
110 + [EFI_TITLE]="Partycja EFI"
111 + [EFI_PROMPT]="Wybierz partycję EFI (ESP):"
112 + [ROOTPART_TITLE]="Partycja root"
113 + [ROOTPART_PROMPT]="Wybierz partycję na root (/):"
114 + [FORMAT_Q]="Sformatować partycję root?"
115 + [SWAP_SKIP]="Pominięto swap."
116 + [DONE_TITLE]="Gotowe"
117 + [PRESS_OK]="OK"
118 + [CANCEL]="Anuluj"
119 + [YES]="Tak"
120 + [NO]="Nie"
121 + [KEYMAP_TITLE]="Układ klawiatury"
122 + [KEYMAP_PROMPT]="Wybierz układ klawiatury:"
123 + [CUSTOM]="Własny (wpisz nazwę)..."
124 + [SWAP_TITLE]="Pamięć wymiany (swap)"
125 + [SWAP_PROMPT]="Wybierz rodzaj swapu:"
126 + [SWAP_PART_OPT]="Partycja swap"
127 + [SWAP_FILE_OPT]="Plik wymiany (/swapfile)"
128 + [SWAP_NONE_OPT]="Bez swapu"
129 + [SWAP_FILE_SIZE]="Rozmiar pliku swap (MiB):"
130 + [BOOT_TITLE]="Bootloader"
131 + [BOOT_PROMPT]="Wybierz bootloader:"
132 + [BOOT_GRUB_OPT]="GRUB (zalecany)"
133 + [BOOT_NONE_OPT]="Brak (ręczna konfiguracja)"
134 + [CRYPT_TITLE]="Szyfrowanie (LUKS)"
135 + [CRYPT_PROMPT]="Zaszyfrować partycję root (LUKS)?\nUwaga: initramfs musi obsługiwać cryptsetup."
136 + [CRYPT_PASS1]="Hasło szyfrowania:"
137 + [CRYPT_PASS2]="Powtórz hasło szyfrowania:"
138 + [CRYPT_OPEN]="Otwieranie zaszyfrowanej partycji..."
139 + [CRYPT_ERR]="Błąd cryptsetup – instalacja przerwana."
140 + [ADMIN_PROMPT]="Czy użytkownik ma być administratorem (grupa wheel)?"
141 + [REPO_TITLE]="Repozytorium pakietów"
142 + [REPO_PROMPT]="Adres repozytorium pag (pusty = bez zmian):"
143 + [SUMMARY_TITLE]="Podsumowanie konfiguracji"
144 + [SUMMARY_HEAD]="Instalacja rozpocznie się z poniższymi ustawieniami:\n\n"
145 + [SUMMARY_CONFIRM]="Kontynuować?"
146 + [L_DISK]="Dysk:"
147 + ) ;;
148 + de)
149 + T=(
150 + [TITLE]="PaganLinux – Installationsprogramm"
151 + [LANG_TITLE]="Sprache / Language"
152 + [LANG_PROMPT]="Wählen Sie die Sprache:"
153 + [WELCOME]="Willkommen beim PaganLinux-Installationsprogramm!\n\nDieser Assistent installiert das System vom Live-Medium auf die gewählte Festplatte.\nAlle Daten auf der Zielfestplatte werden GELÖSCHT.\n\nErstellen Sie vorher ein Backup!"
154 + [ROOT_ERR]="Der Installer benötigt Root-Rechte (sudo)."
155 + [DISK_TITLE]="Festplattenauswahl"
156 + [DISK_PROMPT]="Zielfestplatte wählen:"
157 + [NO_DISK]="Keine Festplatten gefunden (/dev/sd*, nvme, vd, mmcblk)."
158 + [DISK_SMALL]="Die Festplatte %s ist zu klein (%s MiB, mindestens %s MiB erforderlich)."
159 + [EFIVARS_WARN]="efivars kann nicht eingehängt werden – NVRAM-Einträge werden nicht aktualisiert."
160 + [PART_TITLE]="Partitionierung"
161 + [PART_PROMPT]="Partitionierungsmethode wählen:"
162 + [PART_AUTO]="Automatisch (ganze Platte: EFI + Swap + Root)"
163 + [PART_MANUAL]="Manuell (cfdisk)"
164 + [PART_EXISTING]="Vorhandene Partitionen nutzen"
165 + [FS_TITLE]="Dateisystem"
166 + [FS_PROMPT]="Dateisystem für Root wählen:"
167 + [CONFIRM_TITLE]="LETZTE BESTÄTIGUNG"
168 + [CONFIRM_WIPE]="Die Festplatte %s wird GELÖSCHT und PaganLinux installiert.\n\nWirklich fortfahren?"
169 + [ABORT]="Installation abgebrochen."
170 + [FORMATTING]="Partitionen formatieren und einhängen..."
171 + [COPY_TITLE]="Systemkopie"
172 + [COPY_MSG]="System vom Live-Medium kopieren..."
173 + [COPY_DONE]="Kopieren abgeschlossen."
174 + [COPY_ERR]="Kopierfehler – siehe %s"
175 + [CONF_HOST]="Systemkonfiguration"
176 + [HOST_PROMPT]="Rechnername (Hostname):"
177 + [HOST_ERR]="Der Rechnername darf nicht leer sein."
178 + [TZ_TITLE]="Zeitzone"
179 + [TZ_PROMPT]="Zeitzone wählen:"
180 + [LOC_TITLE]="Gebietsschema"
181 + [LOC_PROMPT]="System-Locale wählen:"
182 + [ROOT_PASS_TITLE]="Root-Passwort"
183 + [ROOT_PROMPT1]="Root-Passwort eingeben:"
184 + [ROOT_PROMPT2]="Root-Passwort wiederholen:"
185 + [PASS_MISMATCH]="Passwörter stimmen nicht überein!"
186 + [PASS_EMPTY]="Das Passwort darf nicht leer sein."
187 + [USER_TITLE]="Benutzer"
188 + [USER_PROMPT]="Benutzername (leer = überspringen):"
189 + [USER_PASS1]="Passwort für Benutzer %s:"
190 + [USER_PASS2]="Passwort wiederholen:"
191 + [GRUB_TITLE]="Bootloader"
192 + [GRUB_MSG]="GRUB wird installiert..."
193 + [GRUB_DONE]="GRUB installiert."
194 + [GRUB_ERR]="GRUB-Installation fehlgeschlagen (Log: %s)"
195 + [FINISH]="Installation erfolgreich abgeschlossen!\n\nDas System befindet sich in %s.\nNach dem Neustart mit dem erstellten Benutzer anmelden."
196 + [REBOOT]="Jetzt neu starten?"
197 + [EFI_TITLE]="EFI-Partition"
198 + [EFI_PROMPT]="EFI-Partition (ESP) wählen:"
199 + [ROOTPART_TITLE]="Root-Partition"
200 + [ROOTPART_PROMPT]="Root-Partition (/) wählen:"
201 + [FORMAT_Q]="Root-Partition formatieren?"
202 + [SWAP_SKIP]="Swap übersprungen."
203 + [DONE_TITLE]="Fertig"
204 + [PRESS_OK]="OK"
205 + [CANCEL]="Abbrechen"
206 + [YES]="Ja"
207 + [NO]="Nein"
208 + [KEYMAP_TITLE]="Tastaturlayout"
209 + [KEYMAP_PROMPT]="Tastaturlayout wählen:"
210 + [CUSTOM]="Eigene (Namen eingeben)..."
211 + [SWAP_TITLE]="Auslagerungsspeicher"
212 + [SWAP_PROMPT]="Swap-Typ wählen:"
213 + [SWAP_PART_OPT]="Swap-Partition"
214 + [SWAP_FILE_OPT]="Swap-Datei (/swapfile)"
215 + [SWAP_NONE_OPT]="Kein Swap"
216 + [SWAP_FILE_SIZE]="Größe der Swap-Datei (MiB):"
217 + [BOOT_TITLE]="Bootloader"
218 + [BOOT_PROMPT]="Bootloader wählen:"
219 + [BOOT_GRUB_OPT]="GRUB (empfohlen)"
220 + [BOOT_NONE_OPT]="Keiner (manuelle Konfiguration)"
221 + [CRYPT_TITLE]="Verschlüsselung (LUKS)"
222 + [CRYPT_PROMPT]="Root-Partition verschlüsseln (LUKS)?\nHinweis: initramfs muss cryptsetup unterstützen."
223 + [CRYPT_PASS1]="Passwort für Verschlüsselung:"
224 + [CRYPT_PASS2]="Passwort wiederholen:"
225 + [CRYPT_OPEN]="Verschlüsselte Partition wird geöffnet..."
226 + [CRYPT_ERR]="cryptsetup-Fehler – Installation abgebrochen."
227 + [ADMIN_PROMPT]="Soll der Benutzer Administrator sein (Gruppe wheel)?"
228 + [REPO_TITLE]="Paket-Repository"
229 + [REPO_PROMPT]="Repository-URL (leer = unverändert):"
230 + [SUMMARY_TITLE]="Konfigurationsübersicht"
231 + [SUMMARY_HEAD]="Die Installation startet mit folgenden Einstellungen:\n\n"
232 + [SUMMARY_CONFIRM]="Fortfahren?"
233 + [L_DISK]="Festplatte:"
234 + ) ;;
235 + *)
236 + T=(
237 + [TITLE]="PaganLinux Installer"
238 + [LANG_TITLE]="Language / Język"
239 + [LANG_PROMPT]="Select installer language:"
240 + [WELCOME]="Welcome to the PaganLinux installer!\n\nThis wizard installs the system from the live media to the selected disk.\nAll data on the target disk will be ERASED.\n\nMake a backup first!"
241 + [ROOT_ERR]="The installer requires root privileges (sudo)."
242 + [DISK_TITLE]="Disk selection"
243 + [DISK_PROMPT]="Select target disk:"
244 + [NO_DISK]="No disks found (/dev/sd*, nvme, vd, mmcblk)."
245 + [DISK_SMALL]="Disk %s is too small (%s MiB, at least %s MiB required)."
246 + [EFIVARS_WARN]="Cannot mount efivars – NVRAM entries will not be updated."
247 + [PART_TITLE]="Partitioning"
248 + [PART_PROMPT]="Select partitioning method:"
249 + [PART_AUTO]="Automatic (whole disk: EFI + swap + root)"
250 + [PART_MANUAL]="Manual (cfdisk)"
251 + [PART_EXISTING]="Use existing partitions"
252 + [FS_TITLE]="Filesystem"
253 + [FS_PROMPT]="Select root filesystem:"
254 + [CONFIRM_TITLE]="FINAL CONFIRMATION"
255 + [CONFIRM_WIPE]="Disk %s will be ERASED and PaganLinux installed on it.\n\nAre you sure you want to continue?"
256 + [ABORT]="Installation aborted."
257 + [FORMATTING]="Formatting and mounting partitions..."
258 + [COPY_TITLE]="Copying system"
259 + [COPY_MSG]="Copying system from live media..."
260 + [COPY_DONE]="Copying finished."
261 + [COPY_ERR]="Copy error – check %s"
262 + [CONF_HOST]="System configuration"
263 + [HOST_PROMPT]="Enter hostname:"
264 + [HOST_ERR]="Hostname cannot be empty."
265 + [TZ_TITLE]="Timezone"
266 + [TZ_PROMPT]="Select timezone:"
267 + [LOC_TITLE]="Locale"
268 + [LOC_PROMPT]="Select system locale:"
269 + [ROOT_PASS_TITLE]="Root password"
270 + [ROOT_PROMPT1]="Enter root password:"
271 + [ROOT_PROMPT2]="Repeat root password:"
272 + [PASS_MISMATCH]="Passwords do not match!"
273 + [PASS_EMPTY]="Password cannot be empty."
274 + [USER_TITLE]="User account"
275 + [USER_PROMPT]="Enter username (empty = skip):"
276 + [USER_PASS1]="Password for user %s:"
277 + [USER_PASS2]="Repeat password:"
278 + [GRUB_TITLE]="Bootloader"
279 + [GRUB_MSG]="Installing GRUB..."
280 + [GRUB_DONE]="GRUB installed."
281 + [GRUB_ERR]="GRUB installation failed (log: %s)"
282 + [FINISH]="Installation completed successfully!\n\nThe system is installed in %s.\nAfter reboot, log in with the created user."
283 + [REBOOT]="Reboot now?"
284 + [EFI_TITLE]="EFI partition"
285 + [EFI_PROMPT]="Select EFI System Partition (ESP):"
286 + [ROOTPART_TITLE]="Root partition"
287 + [ROOTPART_PROMPT]="Select root (/) partition:"
288 + [FORMAT_Q]="Format the root partition?"
289 + [SWAP_SKIP]="Swap skipped."
290 + [DONE_TITLE]="Done"
291 + [PRESS_OK]="OK"
292 + [CANCEL]="Cancel"
293 + [YES]="Yes"
294 + [NO]="No"
295 + [KEYMAP_TITLE]="Keyboard layout"
296 + [KEYMAP_PROMPT]="Select keyboard layout:"
297 + [CUSTOM]="Custom (enter name)..."
298 + [SWAP_TITLE]="Swap"
299 + [SWAP_PROMPT]="Select swap type:"
300 + [SWAP_PART_OPT]="Swap partition"
301 + [SWAP_FILE_OPT]="Swap file (/swapfile)"
302 + [SWAP_NONE_OPT]="No swap"
303 + [SWAP_FILE_SIZE]="Swap file size (MiB):"
304 + [BOOT_TITLE]="Bootloader"
305 + [BOOT_PROMPT]="Select bootloader:"
306 + [BOOT_GRUB_OPT]="GRUB (recommended)"
307 + [BOOT_NONE_OPT]="None (manual setup)"
308 + [CRYPT_TITLE]="Encryption (LUKS)"
309 + [CRYPT_PROMPT]="Encrypt root partition (LUKS)?\nNote: initramfs must support cryptsetup."
310 + [CRYPT_PASS1]="Encryption password:"
311 + [CRYPT_PASS2]="Repeat password:"
312 + [CRYPT_OPEN]="Opening encrypted partition..."
313 + [CRYPT_ERR]="cryptsetup error – installation aborted."
314 + [ADMIN_PROMPT]="Should the user be an administrator (wheel group)?"
315 + [REPO_TITLE]="Package repository"
316 + [REPO_PROMPT]="Repository URL (empty = unchanged):"
317 + [SUMMARY_TITLE]="Configuration summary"
318 + [SUMMARY_HEAD]="The installation will start with these settings:\n\n"
319 + [SUMMARY_CONFIRM]="Continue?"
320 + [L_DISK]="Disk:"
321 + ) ;;
322 + esac
323 +}
324 +T_() { printf '%s' "${T[$1]:-[$1]}"; }
325 +TF_() { local k="$1"; shift; local fmt="${T[$k]:-[$k]}"; fmt="${fmt//\\n/$'\n'}"; printf "$fmt" "$@"; }
326 +_nl() { printf '%s' "${1//\\n/$'\n'}"; }
327 +
328 +# ── Wrapper UI ──
329 +ui_info() { # $1 text
330 + local t; t=$(_nl "$1")
331 + case "$UI" in
332 + dialog) dialog --title "$(T_ TITLE)" --msgbox "$t" 0 0 || true ;;
333 + whiptail) whiptail --title "$(T_ TITLE)" --msgbox "$t" 0 0 || true ;;
334 + *) echo; echo "$t"; echo; read -r -p "$(T_ PRESS_OK) " _ || true ;;
335 + esac
336 +}
337 +ui_menu() { # $1 text; $2 tag1 $3 item1 ... ; wynik -> MENU_RESULT
338 + local text; text=$(_nl "$1")
339 + shift
340 + case "$UI" in
341 + dialog) MENU_RESULT=$(dialog --title "$(T_ TITLE)" --menu "$text" 0 0 0 "$@" 2>&1 >/dev/tty) || MENU_RESULT="" ;;
342 + whiptail) MENU_RESULT=$(whiptail --title "$(T_ TITLE)" --menu "$text" 0 0 0 "$@" 3>&1 1>&2 2>&3) || MENU_RESULT="" ;;
343 + *)
344 + echo; echo "$text"
345 + local args=("$@") i=1 n=1
346 + while [[ $i -lt ${#args[@]} ]]; do
347 + echo " $n) ${args[$i]}"
348 + TAGS[$n]="${args[$((i-1))]}"
349 + i=$((i+2)); n=$((n+1))
350 + done
351 + local c
352 + read -r -p " [1-$((n-1))] " c || true
353 + MENU_RESULT="${TAGS[$c]:-}"
354 + ;;
355 + esac
356 + log "menu=$text -> $MENU_RESULT"
357 +}
358 +ui_input() { # $1 text [$2 domyślne] -> INPUT_RESULT
359 + local text="$1" def="${2:-}"
360 + case "$UI" in
361 + dialog) INPUT_RESULT=$(dialog --title "$(T_ TITLE)" --inputbox "$text" 0 0 "$def" 2>&1 >/dev/tty) || INPUT_RESULT="" ;;
362 + whiptail) INPUT_RESULT=$(whiptail --title "$(T_ TITLE)" --inputbox "$text" 0 0 "$def" 3>&1 1>&2 2>&3) || INPUT_RESULT="" ;;
363 + *) echo; echo "$text"; read -r -p "> " INPUT_RESULT || true ;;
364 + esac
365 + log "input -> $INPUT_RESULT"
366 +}
367 +ui_password() { # $1 text -> PASS_RESULT
368 + local text="$1"
369 + case "$UI" in
370 + dialog) PASS_RESULT=$(dialog --title "$(T_ TITLE)" --insecure --passwordbox "$text" 0 0 2>&1 >/dev/tty) || PASS_RESULT="" ;;
371 + whiptail) PASS_RESULT=$(whiptail --title "$(T_ TITLE)" --passwordbox "$text" 0 0 3>&1 1>&2 2>&3) || PASS_RESULT="" ;;
372 + *) echo; echo "$text"; read -r -s -p "> " PASS_RESULT || true; echo ;;
373 + esac
374 +}
375 +ui_yesno() { # $1 text -> 0=tak 1=nie
376 + local t; t=$(_nl "$1")
377 + case "$UI" in
378 + dialog) dialog --title "$(T_ TITLE)" --yesno "$t" 0 0; return $? ;;
379 + whiptail) whiptail --title "$(T_ TITLE)" --yesno "$t" 0 0; return $? ;;
380 + *) echo; echo "$t"; read -r -p "$(T_ YES)/$(T_ NO) [y/N] " c || true; [[ "$c" == "y" || "$c" == "Y" ]] ;;
381 + esac
382 +}
383 +ui_gauge() { # $1 text; $2 <liczba>0-100
384 + case "$UI" in
385 + dialog) dialog --title "$(T_ TITLE)" --gauge "$1" 0 0 <&0 ;;
386 + whiptail) whiptail --title "$(T_ TITLE)" --gauge "$1" 0 0 <&0 ;;
387 + *) cat >/dev/null ;;
388 + esac
389 +}
390 +
391 +# ── Pomocnicze ──
392 +need_root() {
393 + [[ $EUID -eq 0 ]] || {
394 + # stderr jest przekierowany do logu – pokaż błąd też na konsoli
395 + local m; m="$(T_ ROOT_ERR)"
396 + echo "$m" > /dev/tty 2>/dev/null
397 + echo "$m" >&2
398 + exit 1
399 + }
400 +}
401 +is_uefi() { [[ -d /sys/firmware/efi ]]; }
402 +part_path() { # $1 dysk $2 nr -> /dev/sda1, /dev/nvme0n1p1
403 + local d="$1" n="$2"
404 + case "$d" in
405 + *nvme*|*mmcblk*|*vd*) echo "${d}p${n}" ;;
406 + *) echo "${d}${n}" ;;
407 + esac
408 +}
409 +list_disks() {
410 + lsblk -dn -o NAME,SIZE,MODEL 2>/dev/null | awk '{print "/dev/"$1" "$2" "$3}'
411 +}
412 +confirm_wipe() {
413 + local disk="$1"
414 + ui_yesno "$(TF_ CONFIRM_WIPE "$disk")" || { echo "$(T_ ABORT)"; exit 0; }
415 +}
416 +
417 +# ── Kroki ──
418 +step_language() {
419 + local old="${LANG:-en}"
420 + ui_menu "$(T_ LANG_PROMPT)" \
421 + en "English" pl "Polski" de "Deutsch" || true
422 + case "${MENU_RESULT:-}" in
423 + pl|de|en) set_lang "$MENU_RESULT" ;;
424 + *) set_lang "$old" ;;
425 + esac
426 + export LANG="${LANG%%_*}"
427 +}
428 +
429 +step_keymap() {
430 + ui_menu "$(T_ KEYMAP_PROMPT)" \
431 + us "US (qwerty)" de "Deutsch" pl "Polski" cz "Česky" \
432 + fr "Français" es "Español" it "Italiano" custom "$(T_ CUSTOM)"
433 + KEYMAP="${MENU_RESULT:-}"
434 + if [[ "$KEYMAP" == "custom" ]]; then
435 + ui_input "$(T_ KEYMAP_PROMPT)" "us"
436 + KEYMAP="${INPUT_RESULT:-us}"
437 + fi
438 + if [[ -n "$KEYMAP" ]]; then
439 + loadkeys "$KEYMAP" 2>/dev/null || true
440 + fi
441 +}
442 +
443 +step_swap() {
444 + ui_menu "$(T_ SWAP_PROMPT)" \
445 + partition "$(T_ SWAP_PART_OPT)" \
446 + file "$(T_ SWAP_FILE_OPT)" \
447 + none "$(T_ SWAP_NONE_OPT)"
448 + SWAP_MODE="${MENU_RESULT:-partition}"
449 + if [[ "$SWAP_MODE" == "file" ]]; then
450 + ui_input "$(T_ SWAP_FILE_SIZE)" "$SWAP_SIZE"
451 + SWAP_SIZE="${INPUT_RESULT:-$SWAP_SIZE}"
452 + fi
453 + log "swap-mode=$SWAP_MODE size=$SWAP_SIZE"
454 +}
455 +
456 +step_boot() {
457 + ui_menu "$(T_ BOOT_PROMPT)" \
458 + grub "$(T_ BOOT_GRUB_OPT)" \
459 + none "$(T_ BOOT_NONE_OPT)"
460 + BOOT_MODE="${MENU_RESULT:-grub}"
461 + log "boot-mode=$BOOT_MODE"
462 +}
463 +
464 +step_encrypt() {
465 + if command -v cryptsetup >/dev/null 2>&1; then
466 + if ui_yesno "$(T_ CRYPT_PROMPT)"; then ENCRYPT=1; else ENCRYPT=0; fi
467 + else
468 + ENCRYPT=0
469 + fi
470 + log "encrypt=$ENCRYPT"
471 +}
472 +
473 +step_repo() {
474 + ui_input "$(T_ REPO_PROMPT)" ""
475 + REPO_URL="${INPUT_RESULT:-}"
476 + log "repo=$REPO_URL"
477 +}
478 +
479 +step_summary() {
480 + local s sw b
481 + s="$(T_ SUMMARY_HEAD)"
482 + s+=$(printf '%-24s %s\n' "$(T_ L_DISK)" "$DISK")
483 + s+=$(printf '%-24s %s\n' "$(T_ PART_TITLE):" "$PART_MODE")
484 + s+=$(printf '%-24s %s\n' "$(T_ FS_TITLE):" "$ROOT_FS")
485 + case "$SWAP_MODE" in
486 + file) sw="$(T_ SWAP_FILE_OPT) ($SWAP_SIZE MiB)" ;;
487 + none) sw="$(T_ SWAP_NONE_OPT)" ;;
488 + *) sw="$(T_ SWAP_PART_OPT)" ;;
489 + esac
490 + s+=$(printf '%-24s %s\n' "$(T_ SWAP_TITLE):" "$sw")
491 + if [[ "$BOOT_MODE" == "none" ]]; then b="$(T_ BOOT_NONE_OPT)"; else b="$(T_ BOOT_GRUB_OPT)"; fi
492 + s+=$(printf '%-24s %s\n' "$(T_ BOOT_TITLE):" "$b")
493 + if [[ "$ENCRYPT" == "1" ]]; then
494 + s+=$(printf '%-24s %s\n' "$(T_ CRYPT_TITLE):" "LUKS")
495 + fi
496 + if [[ -n "$KEYMAP" ]]; then
497 + s+=$(printf '%-24s %s\n' "$(T_ KEYMAP_TITLE):" "$KEYMAP")
498 + fi
499 + if [[ -n "$REPO_URL" ]]; then
500 + s+=$(printf '%-24s %s\n' "$(T_ REPO_TITLE):" "$REPO_URL")
501 + fi
502 + s+=$'\n'"$(TF_ CONFIRM_WIPE "$DISK")"
503 + ui_yesno "$s" || { echo "$(T_ ABORT)"; exit 0; }
504 +}
505 +
506 +step_disk() {
507 + local disks line d info items=()
508 + mapfile -t disks < <(list_disks)
509 + [[ ${#disks[@]} -gt 0 ]] || { echo "$(T_ NO_DISK)"; exit 1; }
510 + for line in "${disks[@]}"; do
511 + d=$(echo "$line" | awk '{print $1}')
512 + info=$(echo "$line" | awk '{$1=""; print}' | xargs)
513 + items+=("$d" "$info")
514 + done
515 + ui_menu "$(T_ DISK_PROMPT)" "${items[@]}"
516 + DISK="${MENU_RESULT:-}"
517 + [[ -n "$DISK" && -b "$DISK" ]] || { echo "$(T_ ABORT)"; exit 1; }
518 +}
519 +
520 +step_partition() {
521 + ui_menu "$(T_ PART_PROMPT)" \
522 + auto "$(T_ PART_AUTO)" \
523 + manual "$(T_ PART_MANUAL)" \
524 + existing "$(T_ PART_EXISTING)"
525 + PART_MODE="${MENU_RESULT:-auto}"
526 + log "part-mode=$PART_MODE"
527 +
528 + case "$PART_MODE" in
529 + manual)
530 + if command -v cfdisk >/dev/null 2>&1; then
531 + cfdisk "$DISK" || true
532 + elif command -v fdisk >/dev/null 2>&1; then
533 + fdisk "$DISK" || true
534 + else
535 + echo "Brak cfdisk/fdisk"; exit 1
536 + fi
537 + partprobe "$DISK" 2>/dev/null || true; sleep 1
538 + pick_partitions
539 + ;;
540 + existing)
541 + pick_partitions
542 + ;;
543 + *)
544 + auto_partition
545 + ;;
546 + esac
547 +}
548 +
549 +auto_partition() {
550 + local p1 p2 p3
551 + p1=$(part_path "$DISK" 1); p2=$(part_path "$DISK" 2); p3=$(part_path "$DISK" 3)
552 + log "auto: $DISK -> $p1 $p2 $p3 (swap=$SWAP_MODE)"
553 +
554 + # ── Weryfikacja pojemności nośnika przed partycjonowaniem ──
555 + local disk_bytes total_min
556 + disk_bytes=$(lsblk -bno SIZE "$DISK" 2>/dev/null | head -n1)
557 + if [[ ! "$disk_bytes" =~ ^[0-9]+$ ]]; then
558 + echo "Nie można odczytać rozmiaru dysku $DISK"; exit 1
559 + fi
560 + if is_uefi; then
561 + total_min=$((512 + ROOT_MIN))
562 + else
563 + total_min=$((1 + ROOT_MIN))
564 + fi
565 + if [[ "$SWAP_MODE" == "partition" ]]; then
566 + total_min=$((total_min + SWAP_SIZE))
567 + fi
568 + if (( disk_bytes / 1048576 < total_min )); then
569 + echo "$(TF_ DISK_SMALL "$DISK" "$((disk_bytes/1048576))" "$total_min")"
570 + exit 1
571 + fi
572 +
573 + if command -v sfdisk >/dev/null 2>&1; then
574 + if is_uefi; then
575 + if [[ "$SWAP_MODE" == "partition" ]]; then
576 + sfdisk "$DISK" <<EOF
577 +label: gpt
578 +size=512MiB, type=C12A7328-F81D-11D2-BA4B-00A0C93EC93B, name=EFI
579 +size=${SWAP_SIZE}MiB, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name=swap
580 +type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
581 +EOF
582 + else
583 + sfdisk "$DISK" <<EOF
584 +label: gpt
585 +size=512MiB, type=C12A7328-F81D-11D2-BA4B-00A0C93EC93B, name=EFI
586 +type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
587 +EOF
588 + fi
589 + else
590 + if [[ "$SWAP_MODE" == "partition" ]]; then
591 + sfdisk "$DISK" <<EOF
592 +label: gpt
593 +size=1MiB, type=21686148-6449-6E6F-744E-656564454649, name=bios_boot
594 +size=${SWAP_SIZE}MiB, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name=swap
595 +type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
596 +EOF
597 + else
598 + sfdisk "$DISK" <<EOF
599 +label: gpt
600 +size=1MiB, type=21686148-6449-6E6F-744E-656564454649, name=bios_boot
601 +type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name=root
602 +EOF
603 + fi
604 + fi
605 + elif command -v parted >/dev/null 2>&1; then
606 + parted -s "$DISK" mklabel gpt
607 + if is_uefi; then
608 + parted -s "$DISK" mkpart ESP fat32 1MiB 513MiB
609 + parted -s "$DISK" set 1 esp on
610 + if [[ "$SWAP_MODE" == "partition" ]]; then
611 + parted -s "$DISK" mkpart swap linux-swap 513MiB $((513+SWAP_SIZE))MiB
612 + parted -s "$DISK" mkpart root ext4 $((513+SWAP_SIZE))MiB 100%
613 + else
614 + parted -s "$DISK" mkpart root ext4 513MiB 100%
615 + fi
616 + else
617 + if [[ "$SWAP_MODE" == "partition" ]]; then
618 + parted -s "$DISK" mkpart swap linux-swap 1MiB $((1+SWAP_SIZE))MiB
619 + parted -s "$DISK" mkpart root ext4 $((1+SWAP_SIZE))MiB 100%
620 + parted -s "$DISK" set 2 boot on
621 + else
622 + parted -s "$DISK" mkpart root ext4 1MiB 100%
623 + parted -s "$DISK" set 1 boot on
624 + fi
625 + fi
626 + else
627 + echo "Brak sfdisk/parted"; exit 1
628 + fi
629 + partprobe "$DISK" 2>/dev/null || true
630 + sleep 2
631 +
632 + if is_uefi; then
633 + EFI_PART="$p1"
634 + if [[ "$SWAP_MODE" == "partition" ]]; then
635 + SWAP_PART="$p2"; ROOT_PART="$p3"
636 + else
637 + SWAP_PART=""; ROOT_PART="$p2"
638 + fi
639 + else
640 + EFI_PART=""
641 + if [[ "$SWAP_MODE" == "partition" ]]; then
642 + SWAP_PART="$p1"; ROOT_PART="$p2"
643 + else
644 + SWAP_PART=""; ROOT_PART="$p1"
645 + fi
646 + fi
647 +}
648 +
649 +pick_partitions() {
650 + local sep="" items=() line p fst size
651 + case "$DISK" in *nvme*|*mmcblk*|*vd*) sep="p" ;; esac
652 + while IFS= read -r line; do
653 + [[ -n "$line" ]] || continue
654 + p="$DISK${sep}$(echo "$line" | awk '{print $1}')"
655 + size=$(echo "$line" | awk '{print $2}')
656 + fst=$(echo "$line" | awk '{print $3}')
657 + items+=("$p" "$size ${fst:-brak-fs}")
658 + done < <(lsblk -rn -o NAME,SIZE,FSTYPE "$DISK" | tail -n +2)
659 +
660 + [[ ${#items[@]} -gt 0 ]] || { echo "Brak partycji na $DISK"; exit 1; }
661 +
662 + ui_menu "$(T_ ROOTPART_PROMPT)" "${items[@]}"
663 + ROOT_PART="${MENU_RESULT:-}"
664 +
665 + if is_uefi; then
666 + local esp=() i
667 + for ((i=0; i<${#items[@]}; i+=2)); do
668 + local efst; efst=$(lsblk -rno FSTYPE "${items[$i]}" 2>/dev/null || true)
669 + if [[ "$efst" == "vfat" ]]; then
670 + esp+=("${items[$i]}" "ESP (vfat)")
671 + fi
672 + done
673 + if [[ ${#esp[@]} -gt 0 ]]; then
674 + ui_menu "$(T_ EFI_PROMPT)" "${esp[@]}"
675 + else
676 + ui_menu "$(T_ EFI_PROMPT)" "${items[@]}"
677 + fi
678 + EFI_PART="${MENU_RESULT:-}"
679 + else
680 + EFI_PART=""
681 + fi
682 +
683 + # swap: spróbuj wykryć po FSTYPE
684 + SWAP_PART=""
685 + local j
686 + for ((j=0; j<${#items[@]}; j+=2)); do
687 + local sfst; sfst=$(lsblk -rno FSTYPE "${items[$j]}" 2>/dev/null || true)
688 + if [[ "$sfst" == "swap" ]]; then
689 + SWAP_PART="${items[$j]}"
690 + break
691 + fi
692 + done
693 +
694 + if ui_yesno "$(T_ FORMAT_Q)"; then FORMAT_ROOT=1; else FORMAT_ROOT=0; fi
695 +}
696 +
697 +step_fs() {
698 + ui_menu "$(T_ FS_PROMPT)" ext4 "ext4" xfs "xfs" btrfs "btrfs"
699 + ROOT_FS="${MENU_RESULT:-ext4}"
700 +}
701 +
702 +step_format_mount() {
703 + echo "$(T_ FORMATTING)" >&3
704 + log "format: efi=$EFI_PART swap=$SWAP_PART root=$ROOT_PART fs=$ROOT_FS enc=$ENCRYPT"
705 +
706 + mkdir -p "$TARGET"
707 + # Odmontuj ewentualne stare montowania
708 + umount -R "$TARGET" 2>/dev/null || true
709 +
710 + # ── Szyfrowanie LUKS (przed mkfs) ──
711 + if [[ "$ENCRYPT" == "1" ]]; then
712 + local cp1 cp2
713 + ui_password "$(T_ CRYPT_PASS1)"; cp1="$PASS_RESULT"
714 + ui_password "$(T_ CRYPT_PASS2)"; cp2="$PASS_RESULT"
715 + if [[ "$cp1" != "$cp2" ]]; then
716 + echo "$(T_ PASS_MISMATCH)"; exit 1
717 + fi
718 + [[ -n "$cp1" ]] || { echo "$(T_ PASS_EMPTY)"; exit 1; }
719 + echo "$(T_ CRYPT_OPEN)" >&3
720 + printf '%s\n' "$cp1" | cryptsetup luksFormat -q "$ROOT_PART" - \
721 + || { echo "$(T_ CRYPT_ERR)"; exit 1; }
722 + printf '%s\n' "$cp1" | cryptsetup open "$ROOT_PART" cryptroot \
723 + || { echo "$(T_ CRYPT_ERR)"; exit 1; }
724 + CRYPT_UUID=$(blkid -s UUID -o value "$ROOT_PART")
725 + ROOT_PART="/dev/mapper/cryptroot"
726 + log "luks: cryptroot <- $CRYPT_UUID"
727 + fi
728 +
729 + if [[ -n "$EFI_PART" ]]; then
730 + mkfs.vfat -F32 -n PAGAN_EFI "$EFI_PART" >/dev/null
731 + fi
732 + if [[ -n "$SWAP_PART" ]]; then
733 + mkswap -L PAGAN_SWAP "$SWAP_PART" >/dev/null
734 + elif [[ "$SWAP_MODE" == "none" ]]; then
735 + echo "$(T_ SWAP_SKIP)" >&3
736 + fi
737 + if [[ "$FORMAT_ROOT" != "0" ]]; then
738 + mkfs -t "$ROOT_FS" -L PAGAN "$ROOT_PART" >/dev/null
739 + fi
740 +
741 + mount -t "$ROOT_FS" "$ROOT_PART" "$TARGET" || mount "$ROOT_PART" "$TARGET"
742 + if [[ -n "$EFI_PART" ]]; then
743 + mkdir -p "$TARGET/boot/efi"
744 + mount "$EFI_PART" "$TARGET/boot/efi"
745 + fi
746 + if [[ "$SWAP_MODE" == "partition" && -n "$SWAP_PART" ]]; then
747 + swapon "$SWAP_PART" 2>/dev/null || true
748 + fi
749 +}
750 +
751 +step_copy() {
752 + local src="$LIVE_DIR"
753 + [[ -d "$src" ]] || { echo "Źródło $src nie istnieje"; exit 1; }
754 +
755 + local excl=(--exclude=/dev --exclude=/proc --exclude=/sys --exclude=/run
756 + --exclude=/tmp --exclude=/mnt --exclude=/media --exclude=/lost+found
757 + --exclude="$TARGET" --exclude=/var/cache/pag/repos)
758 +
759 + if command -v rsync >/dev/null 2>&1; then
760 + if [[ "$UI" == "plain" ]]; then
761 + echo "$(T_ COPY_MSG)"
762 + rsync -aAX "$src"/ "$TARGET"/ "${excl[@]}" >/dev/null || { echo "$(TF_ COPY_ERR "$LOG")"; exit 1; }
763 + else
764 + # Gauge: postęp rsync (info=progress2, procenty z \r-końcówek)
765 + local tmpfifo rcfile
766 + tmpfifo=$(mktemp -u); rcfile=$(mktemp)
767 + mkfifo "$tmpfifo"
768 + # set +e w podsabli: zapisz kod wyjścia rsync do rcfile, zanim
769 + # pipefail/subshella ubije potok (grep bez dopasowania -> status 1).
770 + ( set +e
771 + rsync -aAX --info=progress2 "$src"/ "$TARGET"/ "${excl[@]}" 2>&1 \
772 + | tr '\r' '\n' | { grep -oE '[0-9]{1,3}%' || true; } | tr -d '%' > "$tmpfifo"
773 + echo "${PIPESTATUS[0]}" > "$rcfile" ) &
774 + local rpid=$!
775 + ( while read -r p; do echo "$p"; done < "$tmpfifo" ) | ui_gauge "$(T_ COPY_MSG)"
776 + wait "$rpid" 2>/dev/null || true
777 + rm -f "$tmpfifo"
778 + [[ "$(cat "$rcfile")" == "0" ]] || { rm -f "$rcfile"; echo "$(TF_ COPY_ERR "$LOG")"; exit 1; }
779 + rm -f "$rcfile"
780 + fi
781 + else
782 + echo "$(T_ COPY_MSG)"
783 + ( cd "$src" && tar --exclude=./dev --exclude=./proc --exclude=./sys \
784 + --exclude=./run --exclude=./tmp --exclude=./mnt --exclude=./media \
785 + -cf - . ) | ( cd "$TARGET" && tar -xpf - )
786 + fi
787 + # Sprawdź, czy system faktycznie skopiowany
788 + [[ -x "$TARGET/bin/bash" || -x "$TARGET/usr/bin/bash" ]] || { echo "$(TF_ COPY_ERR "$LOG")"; exit 1; }
789 + echo "$(T_ COPY_DONE)" >&3
790 +}
791 +
792 +step_configure() {
793 + # ── montuj systemy wirtualne ──
794 + mount --bind /dev "$TARGET/dev" 2>/dev/null || true
795 + mount --bind /dev/pts "$TARGET/dev/pts" 2>/dev/null || true
796 + mount -t proc proc "$TARGET/proc"
797 + mount -t sysfs sys "$TARGET/sys"
798 + mount -t tmpfs tmpfs "$TARGET/run" 2>/dev/null || true
799 + # Bezwarunkowa gwarancja /dev/null (chroot/configure wymagają urządzenia
800 + # znakowego – bind /dev mógł zawieść, np. w kontenerze).
801 + if [[ ! -c "$TARGET/dev/null" ]]; then
802 + mkdir -p "$TARGET/dev"
803 + mknod -m 666 "$TARGET/dev/null" c 1 3 2>/dev/null || true
804 + fi
805 + # Zmienne EFI (efivarfs) – bez nich grub-install nie zaktualizuje NVRAM.
806 + if is_uefi; then
807 + mkdir -p "$TARGET/sys/firmware/efi/efivars"
808 + mount --bind /sys/firmware/efi/efivars "$TARGET/sys/firmware/efi/efivars" \
809 + 2>/dev/null || log "$(T_ EFIVARS_WARN)"
810 + fi
811 + cp -L /etc/resolv.conf "$TARGET/etc/resolv.conf" 2>/dev/null || true
812 +
813 + # ── fstab ──
814 + {
815 + echo "# /etc/fstab – wygenerowane przez pagan-install"
816 + local uuid fs
817 + uuid=$(blkid -s UUID -o value "$ROOT_PART"); fs=$(blkid -s TYPE -o value "$ROOT_PART")
818 + if [[ "$ENCRYPT" == "1" ]]; then
819 + echo "/dev/mapper/cryptroot / $fs defaults,noatime 0 1"
820 + else
821 + echo "UUID=$uuid / $fs defaults,noatime 0 1"
822 + fi
823 + if [[ -n "$EFI_PART" ]]; then
824 + uuid=$(blkid -s UUID -o value "$EFI_PART")
825 + echo "UUID=$uuid /boot/efi vfat umask=0077 0 2"
826 + fi
827 + if [[ "$SWAP_MODE" == "partition" && -n "$SWAP_PART" ]]; then
828 + uuid=$(blkid -s UUID -o value "$SWAP_PART")
829 + echo "UUID=$uuid none swap sw 0 0"
830 + fi
831 + } > "$TARGET/etc/fstab"
832 +
833 + # ── crypttab (LUKS) ──
834 + if [[ "$ENCRYPT" == "1" && -n "$CRYPT_UUID" ]]; then
835 + echo "cryptroot UUID=$CRYPT_UUID none luks" > "$TARGET/etc/crypttab"
836 + fi
837 +
838 + # ── swapfile (gdy wybrano swap jako plik) ──
839 + if [[ "$SWAP_MODE" == "file" ]]; then
840 + if ! fallocate -l "${SWAP_SIZE}M" "$TARGET/swapfile" 2>/dev/null; then
841 + dd if=/dev/zero of="$TARGET/swapfile" bs=1M count="$SWAP_SIZE" status=none
842 + fi
843 + chmod 600 "$TARGET/swapfile"
844 + mkswap "$TARGET/swapfile" >/dev/null
845 + echo "/swapfile none swap sw 0 0" >> "$TARGET/etc/fstab"
846 + fi
847 +
848 + # ── GRUB ──
849 + if [[ "$BOOT_MODE" != "none" ]]; then
850 + local grub_ok=0
851 + if is_uefi; then
852 + chroot "$TARGET" grub-install --target=x86_64-efi --efi-directory=/boot/efi \
853 + --bootloader-id=PaganLinux --recheck >/dev/null 2>&1 && grub_ok=1 || true
854 + else
855 + chroot "$TARGET" grub-install --target=i386-pc "$DISK" >/dev/null 2>&1 && grub_ok=1 || true
856 + fi
857 + if [[ $grub_ok -eq 1 ]]; then
858 + chroot "$TARGET" grub-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1 || true
859 + echo "$(T_ GRUB_DONE)" >&3
860 + else
861 + echo "$(TF_ GRUB_ERR "$LOG")" >&3
862 + fi
863 + else
864 + log "bootloader pominięty (BOOT_MODE=none)"
865 + fi
866 +
867 + # ── układ klawiatury (vconsole) ──
868 + if [[ -n "$KEYMAP" ]]; then
869 + echo "KEYMAP=$KEYMAP" > "$TARGET/etc/vconsole.conf"
870 + fi
871 +
872 + # ── repozytorium pag ──
873 + if [[ -n "$REPO_URL" ]]; then
874 + echo "$REPO_URL" > "$TARGET/etc/pag/repos.conf"
875 + fi
876 +
877 + # ── hostname ──
878 + ui_input "$(T_ HOST_PROMPT)" "pagan"
879 + while [[ -z "${INPUT_RESULT:-}" ]]; do
880 + ui_input "$(T_ HOST_ERR)" "pagan"
881 + done
882 + HOSTNAME="${INPUT_RESULT}"
883 + echo "$HOSTNAME" > "$TARGET/etc/hostname"
884 + sed -i "s/^127.0.1.1.*/127.0.1.1\t$HOSTNAME/" "$TARGET/etc/hosts" 2>/dev/null || \
885 + echo "127.0.1.1 $HOSTNAME" >> "$TARGET/etc/hosts"
886 +
887 + # ── strefa czasowa ──
888 + local tz_items=(UTC UTC Europe/Warsaw Europe/Warsaw Europe/Berlin Europe/Berlin
889 + Europe/London Europe/London Europe/Prague Europe/Prague America/New_York America/New_York
890 + Asia/Tokyo Asia/Tokyo other "$(T_ CUSTOM)")
891 + ui_menu "$(T_ TZ_PROMPT)" "${tz_items[@]}"
892 + local tz="${MENU_RESULT:-UTC}"
893 + if [[ "$tz" == "other" ]]; then
894 + ui_input "$(T_ TZ_PROMPT)" "Europe/Warsaw"; tz="${INPUT_RESULT:-UTC}"
895 + fi
896 + if [[ -f "$TARGET/usr/share/zoneinfo/$tz" ]]; then
897 + ln -sf "/usr/share/zoneinfo/$tz" "$TARGET/etc/localtime"
898 + echo "$tz" > "$TARGET/etc/timezone" 2>/dev/null || true
899 + fi
900 +
901 + # ── locale ──
902 + local loc_items=(en_US.UTF-8 en_US.UTF-8 pl_PL.UTF-8 pl_PL.UTF-8
903 + de_DE.UTF-8 de_DE.UTF-8 C.UTF-8 C.UTF-8 other "$(T_ CUSTOM)")
904 + ui_menu "$(T_ LOC_PROMPT)" "${loc_items[@]}"
905 + local loc="${MENU_RESULT:-C.UTF-8}"
906 + if [[ "$loc" == "other" ]]; then
907 + ui_input "$(T_ LOC_PROMPT)" "pl_PL.UTF-8"; loc="${INPUT_RESULT:-C.UTF-8}"
908 + fi
909 + echo "LANG=$loc" > "$TARGET/etc/locale.conf"
910 + if grep -q "^#$loc " "$TARGET/etc/locale.gen" 2>/dev/null; then
911 + sed -i "s|^#$loc |$loc |" "$TARGET/etc/locale.gen"
912 + chroot "$TARGET" locale-gen >/dev/null 2>&1 || true
913 + fi
914 +
915 + # ── hasło root ──
916 + local p1 p2
917 + ui_password "$(T_ ROOT_PROMPT1)"; p1="$PASS_RESULT"
918 + ui_password "$(T_ ROOT_PROMPT2)"; p2="$PASS_RESULT"
919 + if [[ "$p1" != "$p2" ]]; then
920 + echo "$(T_ PASS_MISMATCH)"; exit 1
921 + fi
922 + [[ -n "$p1" ]] || { echo "$(T_ PASS_EMPTY)"; exit 1; }
923 + echo "root:$p1" | chroot "$TARGET" chpasswd
924 +
925 + # ── użytkownik ──
926 + ui_input "$(T_ USER_PROMPT)" ""
927 + local user="${INPUT_RESULT:-}"
928 + if [[ -n "$user" ]]; then
929 + ui_password "$(TF_ USER_PASS1 "$user")"; p1="$PASS_RESULT"
930 + ui_password "$(T_ USER_PASS2)"; p2="$PASS_RESULT"
931 + [[ "$p1" == "$p2" ]] || { echo "$(T_ PASS_MISMATCH)"; exit 1; }
932 + local ugroups="audio,video,cdrom,plugdev,storage,input"
933 + if ui_yesno "$(T_ ADMIN_PROMPT)"; then
934 + ugroups="wheel,$ugroups"
935 + fi
936 + chroot "$TARGET" useradd -m -G "$ugroups" \
937 + -s /bin/bash "$user" 2>/dev/null || true
938 + echo "$user:$p1" | chroot "$TARGET" chpasswd
939 + fi
940 +
941 + # ── posprzątaj ──
942 + cp "$LOG" "$TARGET/var/log/pagan-install.log" 2>/dev/null || true
943 + if is_uefi; then
944 + umount "$TARGET/sys/firmware/efi/efivars" 2>/dev/null || true
945 + fi
946 + umount -R "$TARGET/dev" "$TARGET/proc" "$TARGET/sys" "$TARGET/run" 2>/dev/null || true
947 +}
948 +
949 +step_finish() {
950 + ui_info "$(TF_ FINISH "$TARGET")"
951 + case "$DO_REBOOT" in
952 + never) ;;
953 + always) reboot ;;
954 + *) if ui_yesno "$(T_ REBOOT)"; then reboot; fi ;;
955 + esac
956 +}
957 +
958 +# ── main ──
959 +main() {
960 + set_lang
961 + need_root
962 + log "=== pagan-install v$VERSION (UI=$UI) ==="
963 + step_language
964 + step_keymap
965 + ui_info "$(T_ WELCOME)"
966 +
967 + step_disk
968 + step_partition
969 + step_fs
970 + step_swap
971 + step_boot
972 + step_encrypt
973 + step_repo
974 + step_summary
975 + step_format_mount
976 + step_copy
977 + step_configure
978 + step_finish
979 +}
980 +
981 +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
982 + main "$@"
983 +fi