Compare commits

..

7 Commits

Author SHA1 Message Date
Stefan Strobl
8cc60e3dae docs: update README for v2.0 full CLI installer
Complete documentation rewrite to reflect v2.0 changes:
- Remove void-installer handoff instructions (obsolete)
- Document new automated workflow (11 phases)
- Add locale/timezone/keyboard configuration section
- Add user management documentation
- List default package installation
- Update usage (remove --skip-installer flag)
- Add troubleshooting section for new phases
- Document standard user groups (wheel, audio, video, etc.)

Major documentation changes:
1. Title: "Vollständiger CLI-Installer" (emphasizes automation)
2. Overview: Highlights "ohne manuelle Eingriffe" (no manual intervention)
3. Workflow: 11 phases instead of 7
4. Configuration: Three new sections (Locale, User, Packages)
5. Architecture: Added new modules to structure diagram

Removed sections:
- "Installer Bedienung nach dem Script" (manual installer instructions)
- --skip-installer flag documentation

Added sections:
- Package-Liste (complete list of installed packages)
- User-Konfiguration (user creation and group membership)
- Nach der Installation (post-install steps)
- Fehlerbehebung (troubleshooting for new phases)
- Zukünftige Erweiterungen (roadmap for config file support)

Decision: Keep German language for consistency with existing docs.
Technical terms (packages, services) use English for clarity.
2025-12-24 20:45:20 +01:00
Stefan Strobl
b2031eae4b feat(main): integrate new modules and remove installer phase
Remove dependency on void-installer by integrating new modules:
- Source packages.sh, locale.sh, users.sh, services.sh
- Remove installer.sh sourcing
- Add 4 new phases after mounts: packages, locale, users, services
- Remove run_installer() phase
- Remove SKIP_INSTALLER flag and --skip-installer option

Decision: Execute new phases in logical order.
Packages must install before locale (needs glibc-locales package).
Locale before users (user shell may depend on locale).
Users before services (services may need user context).

Workflow change:
Before: Mounts → void-installer (manual) → Post-Install
After:  Mounts → Packages → Locale → Users → Services → Post-Install

Alternative considered: Keep installer.sh as optional fallback
rejected. Clean break eliminates maintenance burden and reduces
complexity. Users who need custom package selection can modify
packages.sh directly.

Trade-off: Removes flexibility of void-installer's package
selection UI. Accepted because:
1. Requirements explicitly exclude GUI/TUI components
2. Config file support (future) will provide customization
3. Simpler codebase easier to maintain and debug

Module sourcing order preserves dependency chain:
logging → config → sanity → disk ops → installation → rollback
2025-12-24 20:45:07 +01:00
Stefan Strobl
0913adf00e feat(config): add prompts for locale, timezone, and user
Extend configuration module with new interactive prompts:
- LOCALE: System language (default: en_US.UTF-8)
- TIMEZONE: Time zone (default: UTC)
- KEYBOARD: Console keyboard layout (default: us)
- USERNAME: Standard user account (required)
- USER_SHELL: User shell (default: /bin/bash)
- USER_GROUPS: Group membership (default: wheel,audio,video,storage,network)

Decision: Use prompt_with_default for locale/timezone/keyboard
to provide sensible defaults. Use prompt_required for username
since no universal default exists.

Validation additions:
- Locale format check (basic regex for xx_XX.UTF-8 pattern)
- Username validation (must start with letter/underscore, contain
  only lowercase alphanumeric/underscore/hyphen)

Alternative considered: Auto-detect timezone via GeoIP rejected
(unreliable without network, adds complexity).

Trade-off: Locale format validation is lenient (warning only).
Strict validation rejected because locale names vary and false
positives would block installations. Warning informs user of
potential typo but allows override.

Summary display extended to show all new configuration values
for user review before destructive operations begin.
2025-12-24 20:44:53 +01:00
Stefan Strobl
dcd22b04c0 feat(services): add service activation module
Implement runit service activation for essential services:
- dhcpcd enabled by default for network connectivity
- Helper function enable_service() for extensibility
- Service validation before symlink creation

Decision: Enable only dhcpcd by default.
Minimal service activation reduces attack surface and resource
usage. Additional services (sshd, chronyd) can be added via
future config file support.

Alternative considered: NetworkManager rejected as default.
dhcpcd is lighter and sufficient for server/minimal systems.
Desktop users can switch to NetworkManager post-install.

Trade-off: Automatic dhcpcd activation means network works
immediately after boot but may conflict with users who prefer
NetworkManager. Future config will allow service selection.

Service definition validation: Check if /etc/sv/<service> exists
before creating symlink. Prevents broken links if package not
installed. Returns error code but continues installation to
avoid cascading failures from optional services.
2025-12-24 20:44:41 +01:00
Stefan Strobl
4ac64e6659 feat(users): add user management module
Implement user account creation and sudo configuration:
- Interactive root password setup in chroot
- Standard user creation with configurable groups
- Automatic sudo configuration for wheel group
- Password validation via visudo

Decision: Set passwords interactively for security.
Hash-based passwords rejected for initial implementation
(complexity vs security trade-off).

User groups: wheel (sudo), audio, video, storage, network.
Provides hardware access without additional configuration.

Alternative considered: Passwordless sudo rejected (security risk).
Creating user without sudo rejected (user would be unable to
administer system).

Trade-off: Interactive password input requires TTY and manual
entry. More secure than storing hashes but less convenient for
automated installations. Future config file support can add
hash option.

Sudoers validation: Use visudo -c to validate syntax before
committing changes. Rollback to backup if validation fails.
This prevents lockout from broken sudoers file.
2025-12-24 20:44:31 +01:00
Stefan Strobl
56c8234d35 feat(locale): add locale configuration module
Implement locale, timezone, and keyboard configuration for
internationalization support:
- Locale generation via /etc/default/libc-locales
- Timezone symlink to /usr/share/zoneinfo
- Keyboard layout in /etc/rc.conf

Decision: Default to en_US.UTF-8 for broadest compatibility.
Use standard Void Linux configuration methods (libc-locales,
rc.conf) instead of systemd-localed (Void uses runit).

Alternative considered: Generate all locales rejected - wastes
disk space and time. Only generate selected locale.

Trade-off: Timezone validation checks if file exists and falls
back to UTC if not found. This prevents installation failure
but may surprise users if they mistype timezone name.

Fallback behavior ensures installation completes even with
invalid timezone input, prioritizing robustness over strictness.
2025-12-24 20:44:21 +01:00
Stefan Strobl
f295eb5684 feat(packages): add package installation module
Implement package installation in chroot environment to replace
void-installer dependency. Module installs minimal base system:
- Base-System: base-system, linux, linux-firmware, grub, cryptsetup, dracut
- Network: dhcpcd, iproute2, iputils
- Tools: vim, nano, sudo

Decision: Install conservative package set by default.
Base packages are sufficient for bootable CLI system with network.
Desktop environments can be added later via config file support.

Alternative considered: Interactive package selection rejected
(violates CLI-only requirement from requirements document).

Trade-off: Fixed package list means less flexibility but simpler
initial implementation. Future config file support will allow
customization.

Validation: Package verification after installation ensures
critical packages are present before continuing.
2025-12-24 20:43:54 +01:00
8 changed files with 636 additions and 124 deletions

203
README.md
View File

@ -1,59 +1,176 @@
# Void Bootstrapp Wrapper
# Void Bootstrapp - Vollständiger CLI-Installer
Ein Bash-Wrapper, der die kritischen Vorarbeiten fuer eine verschluesselte Void-Linux-Installation erledigt und danach den offiziellen `void-installer` nutzt.
Ein vollautomatischer Bash-Installer für verschlüsselte Void Linux Installationen.
## Ablauf
## Überblick
1. Sanity Checks (Root, UEFI, Zielplatte)
2. Partitionierung (GPT, ESP + Root)
3. LUKS-Verschluesselung und Mapping
4. Dateisysteme (btrfs oder ext4)
5. Mounts fuer den Installer
6. Installer-Handoff (manuelle Mountpoints, kein Reformat)
7. Post-Install (crypttab, dracut, GRUB, optional Swapfile)
Dieser Installer automatisiert die komplette Installation von Void Linux mit LUKS2-Verschlüsselung, btrfs/ext4-Dateisystem und allen erforderlichen System-Konfigurationen - **ohne manuelle Eingriffe**.
## Usage
**Was ist neu (v2.0):**
- Vollständig automatisiert - keine manuelle void-installer Navigation mehr
- Package-Installation (Base-System, Kernel, Tools)
- Locale/Timezone/Keyboard-Konfiguration
- User-Management (Root + Standard-User mit sudo)
- Service-Aktivierung (dhcpcd für Netzwerk)
## Installation-Workflow
Der Installer führt folgende Phasen automatisch aus:
1. **Sanity Checks** - Root-Rechte, UEFI-Modus, Zielplatte validieren
2. **Konfiguration** - Interaktive Eingabe aller Parameter
3. **Partitionierung** - GPT-Layout (ESP + Root-Partition)
4. **Verschlüsselung** - LUKS2/LUKS1 auf Root-Partition
5. **Dateisysteme** - btrfs (mit Subvolumes) oder ext4
6. **Mounts** - Alle Dateisysteme mounten
7. **Packages** - Base-System, Kernel, Network-Tools installieren
8. **Locale** - System-Sprache, Timezone, Keyboard konfigurieren
9. **Users** - Root-Passwort + Standard-User mit sudo anlegen
10. **Services** - dhcpcd (Netzwerk) aktivieren
11. **Post-Install** - initramfs, GRUB, crypttab, Swap-File
## Verwendung
```bash
sudo ./src/main.sh [--dry-run] [--skip-installer]
sudo ./src/main.sh [--dry-run]
```
## Flags
### Flags
- `--dry-run`: Zeigt die Konfiguration und bricht ohne Aenderungen ab.
- `--skip-installer`: Laesst den Start von `void-installer` aus (manuell starten).
- `--dry-run`: Zeigt die Konfiguration und bricht ohne Änderungen ab
## Installer Bedienung nach dem Script
## Konfiguration
Im `void-installer` musst du die vorbereiteten Mounts uebernehmen und darfst nichts formatieren.
Während der Installation werden folgende Parameter abgefragt:
1. Waehle die manuelle Partitionierung.
2. Setze nur die Mountpoints, ohne Formatierung.
3. Root:
- Device: `/dev/mapper/cryptroot`
- Label: `void-root` (btrfs und ext4)
- Mountpoint: `/`
- Format: aus
4. ESP:
- Device: erste Partition der Disk (meist `...1`)
- Label: `EFI`
- Mountpoint: `/boot/efi`
- Format: aus
### Disk-Konfiguration
- **Target Disk**: Zielplatte (z.B. `/dev/sda`)
- **Hostname**: System-Hostname
- **Filesystem**: `btrfs` (Standard) oder `ext4`
- **LUKS Version**: `2` (Standard) oder `1` (für ältere GRUB-Versionen)
- **ESP Size**: EFI System Partition Größe (Standard: `1GiB`)
- **Root End**: Ende der Root-Partition (Standard: `100%`)
- **Swap Size**: Swap-File Größe (Standard: `4GiB`, `0` zum Deaktivieren)
Wenn Labels nicht angezeigt werden, pruefe mit `lsblk -f`, welches Device `void-root` bzw. `EFI` traegt.
Bei btrfs sind Subvolumes bereits angelegt und gemountet: `@`, `@home`, `@var`, `@log`, `@snapshots`, `@swap`.
Der Installer soll keine neuen Subvolumes anlegen oder formatieren, sondern die vorhandenen Mounts unveraendert lassen.
Es reicht, im Installer nur `/` und `/boot/efi` zu setzen; die Subvolumes werden im Post-Install in `/etc/fstab` eingetragen.
Mapping der Subvolumes (gesetzt durch den Wrapper/Post-Install):
- `@` -> `/`
- `@home` -> `/home`
- `@var` -> `/var`
- `@log` -> `/var/log`
- `@snapshots` -> `/.snapshots`
- `@swap` -> `/swap`
### Locale-Konfiguration
- **Locale**: System-Sprache (Standard: `en_US.UTF-8`)
- **Timezone**: Zeitzone (Standard: `UTC`, z.B. `Europe/Berlin`)
- **Keyboard**: Console-Keyboard-Layout (Standard: `us`, z.B. `de-latin1`)
### User-Konfiguration
- **Username**: Name des Standard-Benutzers (erforderlich)
- **Root-Passwort**: Wird interaktiv gesetzt
- **User-Passwort**: Wird interaktiv gesetzt
Der Standard-User wird automatisch folgenden Gruppen zugeordnet:
- `wheel` - sudo-Zugang
- `audio` - Audio-Geräte
- `video` - Video-Geräte
- `storage` - Speicher-Geräte
- `network` - Netzwerk-Konfiguration
## Btrfs Subvolumes
Bei Auswahl von btrfs werden automatisch folgende Subvolumes angelegt:
- `@``/` (Root)
- `@home``/home` (Benutzer-Daten)
- `@var``/var` (Variable Daten)
- `@log``/var/log` (System-Logs)
- `@snapshots``/.snapshots` (Snapshot-Speicher)
- `@swap``/swap` (Swap-File Container)
## Package-Liste
Der Installer installiert standardmäßig folgende Pakete:
**Base-System:**
- `base-system` - Essential base packages
- `linux` - Linux Kernel
- `linux-firmware` - Hardware-Firmware
- `grub` - Bootloader
- `cryptsetup` - LUKS-Tools
- `dracut` - initramfs Generator
**Netzwerk:**
- `dhcpcd` - DHCP-Client
- `iproute2` - IP-Konfiguration
- `iputils` - ping, traceroute
**System-Tools:**
- `vim` - Text-Editor
- `nano` - Alternative Editor
- `sudo` - Privilege Escalation
## Nach der Installation
Nach erfolgreichem Abschluss:
1. System neustarten: `reboot`
2. LUKS-Passphrase beim Boot eingeben
3. Mit Standard-User einloggen
4. Netzwerk ist via dhcpcd automatisch verfügbar
5. `sudo` funktioniert für wheel-Gruppen-Mitglieder
## Systemanforderungen
- **Boot-Modus**: UEFI (BIOS wird nicht unterstützt)
- **Architektur**: x86_64
- **Live-Medium**: Void Linux Live-ISO
- **Netzwerk**: Für Package-Download erforderlich
## Hinweise
- Der Wrapper ist destruktiv und loescht die Zielplatte nach expliziter Bestaetigung.
- UEFI ist Pflicht; BIOS-Installationen sind nicht unterstuetzt.
- Logs landen unter `/tmp/void-wrapper-YYYY-MM-DD-HHMMSS.log`.
- **Destruktiv**: Der Installer löscht die Zielplatte nach expliziter Bestätigung
- **Interaktiv**: Passwörter werden aus Sicherheitsgründen interaktiv abgefragt
- **Logs**: Unter `/tmp/void-wrapper-YYYY-MM-DD-HHMMSS.log`
## Fehlerbehebung
Bei Fehlern während der Installation:
1. **Fehler in Packages-Phase**: Netzwerk-Konnektivität und Mirror-Status prüfen
2. **Fehler in Users-Phase**: Passwort-Eingabe wiederholen
3. **Rollback angeboten**: Bei Fehler werden Mounts und LUKS-Mappings automatisch aufgeräumt
Für detaillierte Fehleranalyse siehe Log-Datei.
## Entwicklung
### Struktur
```
src/
├── main.sh # Orchestrierung aller Phasen
├── logging.sh # Logging-Funktionen
├── config.sh # Konfigurations-Prompts und Validierung
├── sanity.sh # System-Checks
├── partitioning.sh # GPT-Partitionierung
├── encryption.sh # LUKS-Setup
├── filesystems.sh # Dateisystem-Formatierung
├── mounts.sh # Mount-Management
├── packages.sh # Package-Installation
├── locale.sh # Locale/Timezone/Keyboard
├── users.sh # User-Management
├── services.sh # Service-Aktivierung
├── postinstall.sh # initramfs, GRUB, crypttab
└── rollback.sh # Cleanup bei Fehlern
```
### Architektur-Prinzipien
- **Narrative Documentation**: Jedes Modul dokumentiert Motivation, Decisions, Alternatives
- **Modulare Phasen**: Jede Phase ist eigenständig und testbar
- **Error Handling**: `set -euo pipefail` + trap-basiertes Rollback
- **Logging**: Alle Operationen werden geloggt
## Zukünftige Erweiterungen
- Config-File Support (JSON) für unattended Installations
- Desktop-Environment Auswahl (XFCE, KDE, GNOME)
- Zusätzliche Service-Optionen (sshd, chronyd)
- Checkpoint-System für Phase-Wiederaufnahme
## Lizenz
Dieses Projekt ist für Void Linux Installationen optimiert und folgt den Best Practices der Void Linux Dokumentation.

View File

@ -53,6 +53,14 @@ export ESP_MOUNT="/mnt/boot/efi"
export ROOT_LABEL="void-root"
export EFI_LABEL="EFI"
# Locale and user configuration
export LOCALE="en_US.UTF-8"
export TIMEZONE="UTC"
export KEYBOARD="us"
export USERNAME=""
export USER_SHELL="/bin/bash"
export USER_GROUPS="wheel,audio,video,storage,network"
validate_size() {
local size="$1"
# Allow sizes like: 1GiB, 100MB, 50%, 100%
@ -134,6 +142,12 @@ config_prompt_interactive() {
prompt_with_default ESP_SIZE "ESP size (e.g. 1GiB)" "${ESP_SIZE}"
prompt_with_default ROOT_END "Root partition end (e.g. 100% or 200GiB)" "${ROOT_END}"
prompt_with_default SWAP_SIZE "Swap size (e.g. 4GiB, 0 to disable)" "${SWAP_SIZE}"
# Locale and user configuration
prompt_with_default LOCALE "Locale (e.g. en_US.UTF-8, de_DE.UTF-8)" "${LOCALE}"
prompt_with_default TIMEZONE "Timezone (e.g. UTC, Europe/Berlin, America/New_York)" "${TIMEZONE}"
prompt_with_default KEYBOARD "Keyboard layout (e.g. us, de-latin1, fr)" "${KEYBOARD}"
prompt_required USERNAME "Username for standard user" "${USERNAME}"
}
config_validate() {
@ -143,6 +157,9 @@ config_validate() {
if [[ -z "$HOSTNAME" ]]; then
die "Hostname is required."
fi
if [[ -z "$USERNAME" ]]; then
die "Username is required."
fi
case "$FS_TYPE" in
btrfs|ext4) ;;
@ -169,6 +186,16 @@ config_validate() {
if [[ "$SWAP_SIZE" != "0" ]] && ! validate_size "$SWAP_SIZE"; then
die "Invalid swap size format: $SWAP_SIZE (expected: 4GiB, 8GB, 0 to disable)"
fi
# Validate locale format (basic check)
if [[ ! "$LOCALE" =~ ^[a-z]{2}_[A-Z]{2}\.(UTF-8|utf8)$ ]]; then
log_warn "Locale format may be invalid: $LOCALE (expected: en_US.UTF-8)"
fi
# Validate username (alphanumeric, underscore, hyphen)
if [[ ! "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
die "Invalid username: $USERNAME (must start with lowercase letter or underscore, contain only lowercase alphanumeric, underscore, hyphen)"
fi
}
config_print_summary() {
@ -178,6 +205,7 @@ config_print_summary() {
: "${LUKS_VERSION:?LUKS version is required}"
: "${MOUNT_ROOT:?Mount root is required}"
: "${ESP_MOUNT:?ESP mount path is required}"
: "${USERNAME:?Username is required}"
log_info "Configuration summary:"
log_info " Disk: $DISK"
log_info " Hostname: $HOSTNAME"
@ -188,6 +216,10 @@ config_print_summary() {
log_info " Swap size: ${SWAP_SIZE}"
log_info " Mount root: $MOUNT_ROOT"
log_info " ESP mount: $ESP_MOUNT"
log_info " Locale: $LOCALE"
log_info " Timezone: $TIMEZONE"
log_info " Keyboard: $KEYBOARD"
log_info " Username: $USERNAME"
}
config_confirm_destructive() {

View File

@ -1,71 +0,0 @@
#!/usr/bin/env bash
# === Motivation ===
# Keep the official installer as the configuration authority.
# === Problem Statement ===
# We need a clean handoff so the installer uses existing mounts without reformatting.
# === Scope ===
# In scope: instructions and guardrails for the user during the installer run.
# Out of scope: automated installer configuration.
# === Concepts ===
# Handoff: a pause where the wrapper delegates to the installer.
# === Decisions ===
# Provide clear, minimal guidance to avoid overriding prepared filesystems.
# Support only CLI installer flow to keep guidance consistent.
# === Alternatives Considered ===
# Fully scripted installation rejected for this phase.
# === Constraints ===
# The wrapper must not hide or alter installer behavior.
# === Open Questions ===
# Should we provide a checklist or step-by-step guide during the installer handoff?
# How do we detect if the installer reformatted filesystems against our intent?
# Should we monitor the installer process, or fully delegate control?
# === Success Criteria ===
# The installer completes using the prepared mounts without reformatting.
run_installer() {
: "${MOUNT_ROOT:?Mount root is required}"
: "${ESP_MOUNT:?ESP mount path is required}"
if ! findmnt "$MOUNT_ROOT" >/dev/null 2>&1; then
die "Mount root not found: $MOUNT_ROOT"
fi
if ! findmnt "$ESP_MOUNT" >/dev/null 2>&1; then
die "ESP mount not found: $ESP_MOUNT"
fi
log_info "Installer handoff: use the prepared mounts without formatting."
log_info "In the installer: choose manual partitioning and only set mountpoints."
if [[ "${SKIP_INSTALLER:-0}" -eq 1 ]]; then
log_warn "Skipping installer per request."
return 0
fi
read -r -p "Press Enter to launch void-installer..." _
if command -v void-installer >/dev/null 2>&1; then
void-installer
else
log_warn "void-installer not found. Run it manually in another shell."
fi
read -r -p "Press Enter once the installer is complete..." _
# Verify critical mounts are still intact after installer
if ! findmnt "$MOUNT_ROOT" >/dev/null 2>&1; then
die "Root mount disappeared after installer. The installer may have reformatted the filesystem."
fi
if ! findmnt "$ESP_MOUNT" >/dev/null 2>&1; then
die "ESP mount disappeared after installer. The installer may have reformatted the filesystem."
fi
log_info "Mount verification passed."
}

117
src/locale.sh Normal file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env bash
# === Motivation ===
# Configure system locale, timezone, and keyboard layout for international users.
# === Problem Statement ===
# A freshly installed system needs localization configuration to support user language and region.
# === Scope ===
# In scope: locale generation, timezone configuration, keyboard layout.
# Out of scope: X11 keyboard configuration, font selection, language packs.
# === Concepts ===
# Locale: Defines language, character encoding, and formatting conventions (LC_*).
# Timezone: Maps local time to UTC with regional rules (DST, etc.).
# Keyboard layout: Console keymap for character input.
# === Decisions ===
# Default locale is en_US.UTF-8 for broadest compatibility.
# Default timezone will be configured based on user prompt (see config.sh).
# Default keyboard layout is 'us' (can be overridden via config).
# Use /etc/default/libc-locales for locale configuration (Void standard).
# Use symlink for timezone (/etc/localtime → /usr/share/zoneinfo/<zone>).
# Use /etc/rc.conf for console keyboard layout (KEYMAP variable).
# === Alternatives Considered ===
# systemd-localed rejected (Void uses runit).
# Generating all locales rejected (wastes disk space and time).
# Auto-detection of timezone rejected (unreliable without network/GeoIP).
# === Constraints ===
# Target system must be mounted at $MOUNT_ROOT.
# Locale must be generated before it can be used.
# Timezone must exist in /usr/share/zoneinfo.
# === Open Questions ===
# Should we support multiple locales (e.g., en_US + de_DE)?
# Should we validate timezone path before creating symlink?
# Should we configure X11 keyboard layout as well?
# === Success Criteria ===
# Locale is generated and available in the installed system.
# Timezone is correctly configured and persists after reboot.
# Keyboard layout matches user selection on console login.
locale_configure() {
: "${MOUNT_ROOT:?Mount root is required}"
: "${LOCALE:?Locale is required}"
: "${TIMEZONE:?Timezone is required}"
: "${KEYBOARD:?Keyboard layout is required}"
log_info "Locale configuration: setting up locale, timezone, and keyboard"
# Configure locale in /etc/default/libc-locales
log_info "Configuring locale: $LOCALE"
local locale_file="$MOUNT_ROOT/etc/default/libc-locales"
mkdir -p "$(dirname "$locale_file")"
# Ensure locale file exists
if [[ ! -f "$locale_file" ]]; then
touch "$locale_file"
fi
# Uncomment or add the selected locale
if grep -q "^#${LOCALE}" "$locale_file"; then
# Locale exists but is commented - uncomment it
sed -i "s/^#${LOCALE}/${LOCALE}/" "$locale_file"
elif grep -q "^${LOCALE}" "$locale_file"; then
# Locale already enabled
log_info "Locale $LOCALE already enabled"
else
# Add locale to file
echo "$LOCALE UTF-8" >> "$locale_file"
fi
# Generate locales
log_info "Generating locales (this may take a moment)..."
if ! chroot "$MOUNT_ROOT" xbps-reconfigure -f glibc-locales; then
log_warn "Failed to reconfigure glibc-locales, but continuing"
fi
# Configure timezone
log_info "Configuring timezone: $TIMEZONE"
local timezone_source="/usr/share/zoneinfo/$TIMEZONE"
local timezone_target="$MOUNT_ROOT/etc/localtime"
if [[ ! -f "$MOUNT_ROOT$timezone_source" ]]; then
log_warn "Timezone file not found: $timezone_source - using UTC as fallback"
TIMEZONE="UTC"
timezone_source="/usr/share/zoneinfo/UTC"
fi
# Remove existing symlink or file
rm -f "$timezone_target"
# Create symlink
ln -sf "$timezone_source" "$timezone_target"
# Also set timezone in /etc/timezone for compatibility
echo "$TIMEZONE" > "$MOUNT_ROOT/etc/timezone"
# Configure keyboard layout
log_info "Configuring keyboard layout: $KEYBOARD"
local rc_conf="$MOUNT_ROOT/etc/rc.conf"
mkdir -p "$(dirname "$rc_conf")"
touch "$rc_conf"
# Update or add KEYMAP entry in /etc/rc.conf
if grep -q "^KEYMAP=" "$rc_conf"; then
sed -i "s/^KEYMAP=.*/KEYMAP=\"$KEYBOARD\"/" "$rc_conf"
else
echo "KEYMAP=\"$KEYBOARD\"" >> "$rc_conf"
fi
log_info "Locale configuration complete."
}

View File

@ -72,23 +72,27 @@ source "$SCRIPT_DIR/encryption.sh"
source "$SCRIPT_DIR/filesystems.sh"
# shellcheck source=src/mounts.sh
source "$SCRIPT_DIR/mounts.sh"
# shellcheck source=src/installer.sh
source "$SCRIPT_DIR/installer.sh"
# shellcheck source=src/packages.sh
source "$SCRIPT_DIR/packages.sh"
# shellcheck source=src/locale.sh
source "$SCRIPT_DIR/locale.sh"
# shellcheck source=src/users.sh
source "$SCRIPT_DIR/users.sh"
# shellcheck source=src/services.sh
source "$SCRIPT_DIR/services.sh"
# shellcheck source=src/postinstall.sh
source "$SCRIPT_DIR/postinstall.sh"
# shellcheck source=src/rollback.sh
source "$SCRIPT_DIR/rollback.sh"
DRY_RUN=0
SKIP_INSTALLER=0
CURRENT_PHASE=""
usage() {
cat <<USAGE
Usage: ./main.sh [--dry-run] [--skip-installer]
Usage: ./main.sh [--dry-run]
--dry-run Print configuration summary and exit
--skip-installer Skip launching void-installer (manual run)
USAGE
}
@ -98,9 +102,6 @@ parse_args() {
--dry-run)
DRY_RUN=1
;;
--skip-installer)
SKIP_INSTALLER=1
;;
-h|--help)
usage
exit 0
@ -153,10 +154,14 @@ main() {
run_phase "encryption" encrypt_root
run_phase "filesystems" format_filesystems
run_phase "mounts" mount_filesystems
run_phase "installer" run_installer
run_phase "packages" packages_install
run_phase "locale" locale_configure
run_phase "users" users_setup
run_phase "services" services_configure
run_phase "post-install" postinstall_run
log_info "Installation wrapper completed."
log_info "Installation completed successfully."
log_info "System is ready to boot."
log_info "Log file: ${LOG_FILE}"
}

97
src/packages.sh Normal file
View File

@ -0,0 +1,97 @@
#!/usr/bin/env bash
# === Motivation ===
# Install all required packages for a bootable Void Linux system.
# === Problem Statement ===
# The installer must provide a minimal base system without relying on void-installer.
# === Scope ===
# In scope: package installation, repository synchronization, dependency resolution.
# Out of scope: package selection UI, custom repository configuration.
# === Concepts ===
# XBPS: Void's package manager, handles installation and dependency resolution.
# chroot: Execute package installation in the target system environment.
# Repository sync: Update package index before installation.
# === Decisions ===
# Install minimal package set by default for base CLI system.
# Support optional package installation via config (future: config file support).
# Use xbps-install with sync (-S) and yes (-y) flags for non-interactive installation.
# Ensure network connectivity before package installation (handled by live environment).
# Package list is conservative: only essentials for boot + network + basic tools.
# === Alternatives Considered ===
# Interactive package selection rejected (violates CLI-only requirement).
# Separate kernel installation rejected (included in base-system dependency).
# systemd-based packages rejected (Void uses runit).
# === Constraints ===
# Target system must be mounted at $MOUNT_ROOT.
# Repository must be accessible via network.
# xbps must be available in live environment.
# === Open Questions ===
# Should firmware packages beyond linux-firmware be included (e.g., nvidia, AMD)?
# Should we validate package availability before installation?
# How to handle optional packages (desktop environments, development tools)?
# === Success Criteria ===
# All required packages are installed in the target system.
# Package manager can be used in the installed system post-reboot.
# System can boot with network connectivity and basic tools available.
packages_install() {
: "${MOUNT_ROOT:?Mount root is required}"
require_command xbps-install
log_info "Package installation: installing base system"
# Define minimal package set for bootable CLI system
local base_packages=(
"base-system" # Essential base packages (includes kernel via dependency)
"linux" # Linux kernel
"linux-firmware" # Firmware blobs for hardware
"grub" # Bootloader
"cryptsetup" # LUKS encryption tools
"dracut" # initramfs generator
)
local network_packages=(
"dhcpcd" # DHCP client for network
"iproute2" # ip command for network configuration
"iputils" # ping, traceroute, etc.
)
local system_tools=(
"vim" # Text editor
"nano" # Alternative text editor
"sudo" # Privilege escalation
)
# Combine all package arrays
local all_packages=("${base_packages[@]}" "${network_packages[@]}" "${system_tools[@]}")
log_info "Package list: ${all_packages[*]}"
log_info "Installing ${#all_packages[@]} packages (this may take several minutes)..."
# Ensure repository is accessible and synchronized
# Use chroot to install packages into target system
if ! chroot "$MOUNT_ROOT" xbps-install -Syu "${all_packages[@]}"; then
die "Package installation failed. Check network connectivity and repository status."
fi
log_info "Package installation complete."
# Verify critical packages are installed
log_info "Verifying package installation..."
local critical_packages=("base-system" "linux" "grub" "cryptsetup")
for pkg in "${critical_packages[@]}"; do
if ! chroot "$MOUNT_ROOT" xbps-query "$pkg" >/dev/null 2>&1; then
die "Critical package not found: $pkg"
fi
done
log_info "Package verification passed."
}

95
src/services.sh Normal file
View File

@ -0,0 +1,95 @@
#!/usr/bin/env bash
# === Motivation ===
# Enable essential system services for network connectivity and basic functionality.
# === Problem Statement ===
# A freshly installed system needs service activation to function properly at boot.
# === Scope ===
# In scope: network service activation, optional services (SSH, NTP).
# Out of scope: service configuration, firewall rules, custom service creation.
# === Concepts ===
# runit: Void's init system and service supervisor.
# Service directory: /etc/sv/<service> contains service definition.
# Service activation: Symlink from /etc/runit/runsvdir/default/<service> to /etc/sv/<service>.
# dhcpcd: DHCP client for automatic network configuration.
# sshd: OpenSSH server for remote access.
# chronyd: NTP client for time synchronization.
# === Decisions ===
# Enable dhcpcd by default for network connectivity (required for most systems).
# Disable sshd by default for security (can be enabled via config).
# Disable chronyd by default (optional service, can be enabled via config).
# Use absolute symlinks for service activation (Void standard).
# Validate service directory exists before creating symlink.
# === Alternatives Considered ===
# NetworkManager rejected as default (dhcpcd is lighter and sufficient for server/minimal systems).
# systemd-networkd rejected (Void uses runit).
# Enabling all services rejected (security and resource waste).
# Auto-detection of network interface rejected (dhcpcd handles this).
# === Constraints ===
# Target system must be mounted at $MOUNT_ROOT.
# Service packages must be installed before activation.
# Service definitions must exist in /etc/sv/.
# === Open Questions ===
# Should we detect network interface type and choose NetworkManager vs dhcpcd?
# Should we prompt for optional services interactively or only via config?
# Should we enable dbus by default for desktop environments?
# === Success Criteria ===
# dhcpcd is enabled and system has network connectivity after reboot.
# Optional services are enabled if requested via configuration.
# Services start successfully on boot.
services_configure() {
: "${MOUNT_ROOT:?Mount root is required}"
local runsvdir="$MOUNT_ROOT/etc/runit/runsvdir/default"
log_info "Service configuration: enabling system services"
# Ensure runsvdir exists
mkdir -p "$runsvdir"
# Enable dhcpcd for network connectivity
log_info "Enabling dhcpcd service"
enable_service "dhcpcd"
# Optional services (can be extended with config file support)
# For now, we only enable essential services
# Future: Add support for ENABLE_SSHD, ENABLE_CHRONYD via config
log_info "Service configuration complete."
}
enable_service() {
local service="$1"
local service_dir="/etc/sv/$service"
local runsvdir="$MOUNT_ROOT/etc/runit/runsvdir/default"
local service_link="$runsvdir/$service"
# Check if service definition exists
if [[ ! -d "$MOUNT_ROOT$service_dir" ]]; then
log_warn "Service definition not found: $service_dir (service may not be installed)"
return 1
fi
# Check if service is already enabled
if [[ -L "$service_link" ]]; then
log_info "Service $service already enabled"
return 0
fi
# Create symlink to enable service
if ! ln -sf "$service_dir" "$service_link"; then
log_warn "Failed to enable service: $service"
return 1
fi
log_info "Service $service enabled"
return 0
}

120
src/users.sh Normal file
View File

@ -0,0 +1,120 @@
#!/usr/bin/env bash
# === Motivation ===
# Create user accounts with proper permissions for system access.
# === Problem Statement ===
# A bootable system needs at least root access and a non-privileged user account.
# === Scope ===
# In scope: root password, standard user creation, group membership, sudo configuration.
# Out of scope: multiple users, password policies, PAM configuration.
# === Concepts ===
# Root user: Administrative account with UID 0, full system access.
# Standard user: Non-privileged account for daily tasks, can escalate via sudo.
# wheel group: Traditional Unix group for sudo access.
# useradd: Creates user accounts with home directory and group membership.
# === Decisions ===
# Root password is set interactively (security requirement).
# Standard user is created with wheel group for sudo access.
# Additional groups (audio, video, storage, network) are added for hardware access.
# User password is set interactively (same security requirement as root).
# sudo is configured by uncommenting wheel group in /etc/sudoers.
# Default shell is /bin/bash (most common, widely supported).
# === Alternatives Considered ===
# Hash-based password rejected for initial implementation (requires additional config).
# Passwordless sudo rejected (security risk).
# Creating user without sudo rejected (user would be unable to administer system).
# systemd-homed rejected (Void uses traditional user management).
# === Constraints ===
# Target system must be mounted at $MOUNT_ROOT.
# sudo package must be installed before configuration.
# Interactive password input requires TTY.
# === Open Questions ===
# Should we support multiple user creation?
# Should we validate password strength?
# Should we configure shell based on user preference?
# === Success Criteria ===
# Root password is set and login works.
# Standard user can log in with password.
# Standard user can execute sudo commands.
# User has access to audio, video, and network hardware.
users_setup() {
: "${MOUNT_ROOT:?Mount root is required}"
: "${USERNAME:?Username is required}"
local user_shell="${USER_SHELL:-/bin/bash}"
local user_groups="${USER_GROUPS:-wheel,audio,video,storage,network}"
log_info "User setup: configuring root and creating standard user"
# Set root password interactively
log_info "Setting root password"
log_info "Please enter the root password when prompted:"
if ! chroot "$MOUNT_ROOT" passwd; then
die "Failed to set root password"
fi
# Create standard user
log_info "Creating user: $USERNAME"
if chroot "$MOUNT_ROOT" id "$USERNAME" >/dev/null 2>&1; then
log_warn "User $USERNAME already exists, skipping creation"
else
# Create user with home directory and group membership
if ! chroot "$MOUNT_ROOT" useradd -m -G "$user_groups" -s "$user_shell" "$USERNAME"; then
die "Failed to create user: $USERNAME"
fi
log_info "User $USERNAME created with groups: $user_groups"
fi
# Set user password interactively
log_info "Setting password for user: $USERNAME"
log_info "Please enter the password for $USERNAME when prompted:"
if ! chroot "$MOUNT_ROOT" passwd "$USERNAME"; then
die "Failed to set password for user: $USERNAME"
fi
# Configure sudo for wheel group
log_info "Configuring sudo access for wheel group"
local sudoers_file="$MOUNT_ROOT/etc/sudoers"
if [[ ! -f "$sudoers_file" ]]; then
die "Sudoers file not found. Ensure sudo package is installed."
fi
# Backup sudoers file
cp "$sudoers_file" "${sudoers_file}.bak"
# Uncomment wheel group line (allow members of wheel to execute any command)
if grep -q "^# %wheel ALL=(ALL:ALL) ALL" "$sudoers_file"; then
sed -i 's/^# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' "$sudoers_file"
log_info "Enabled sudo for wheel group"
elif grep -q "^%wheel ALL=(ALL:ALL) ALL" "$sudoers_file"; then
log_info "sudo for wheel group already enabled"
elif grep -q "^# %wheel ALL=(ALL) ALL" "$sudoers_file"; then
# Alternative format
sed -i 's/^# %wheel ALL=(ALL) ALL/%wheel ALL=(ALL) ALL/' "$sudoers_file"
log_info "Enabled sudo for wheel group (alternative format)"
else
# Add wheel group entry if not present
echo "%wheel ALL=(ALL:ALL) ALL" >> "$sudoers_file"
log_info "Added sudo configuration for wheel group"
fi
# Validate sudoers file syntax
if ! chroot "$MOUNT_ROOT" visudo -c -f /etc/sudoers >/dev/null 2>&1; then
log_warn "Sudoers file validation failed, restoring backup"
mv "${sudoers_file}.bak" "$sudoers_file"
die "Failed to configure sudo (syntax error)"
fi
rm -f "${sudoers_file}.bak"
log_info "User setup complete."
}