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