24 Commits
Author SHA1 Message Date
DragonSlayer_14andClaude Sonnet 5 4b8299fede Fix: XanMod-Repo-Suite "releases" existiert nicht mehr
deb.xanmod.org/dists/releases liefert 404; das Repository ist mittlerweile
pro Debian-Codename organisiert (sid, trixie, bookworm, ...). Da dieses
Framework Debian Unstable (Sid) als Zielsystem hat, wird jetzt direkt die
Suite "sid" verwendet (per curl gegen deb.xanmod.org verifiziert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 23:01:05 +02:00
DragonSlayer_14andClaude Sonnet 5 7296489c11 Feature: Optionale Verlegung des Mesa/DXVK-Shader-Caches
Neue Variable SHADER_CACHE_DIR in config/setup.conf erlaubt es, den
Mesa- (RADV/OpenGL) und DXVK-Shader-Cache (Wine/Proton) auf einen
separaten Speicherort zu verlegen, um Nachcompilierungs-Ruckler nach
Neustarts zu vermeiden. Standardmäßig leer, damit kein hartkodierter
Pfad (z. B. /mnt/Data) vorausgesetzt wird - ohne Angabe bleiben Mesa
und DXVK bei ihren eigenen, überall vorhandenen Cache-Pfaden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 22:53:36 +02:00
DragonSlayer_14andClaude Sonnet 5 7932bb2482 Feature: vkBasalt Vulkan-Postprocessing integriert
Rollt vkBasalt (inkl. 32-Bit-Variante für Proton/Wine) mit einer
vorkonfigurierten Contrast-Adaptive-Sharpening-Config aus. Der MangoHud-
Overlay zeigt den Aktivierungsstatus mit an; Aktivierung erfolgt pro
Spiel über die Steam-Launch-Option ENABLE_VKBASALT=1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 22:52:36 +02:00
DragonSlayer_14andClaude Sonnet 5 0cc710f997 Feature: sched-ext Gaming-Scheduler (scx_lavd) aktiviert
Installiert scx-scheds aus dem bereits konfigurierten XanMod-Repository
und aktiviert den für Gaming/interaktive Desktop-Workloads gebauten
Latency-criticality Aware Virtual Deadline Scheduler im Autopilot-Modus.
Benötigt einen Kernel mit CONFIG_SCHED_CLASS_EXT (bei XanMod bereits
aktiv); nach frischer Kernel-Installation ggf. Neustart nötig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 22:51:29 +02:00
DragonSlayer_14andClaude Sonnet 5 fec46ef46f Feature: systemd-oomd gegen Einfrieren bei Speicherdruck aktiviert
Ergänzt das bestehende zram-Swap um einen modernen, cgroup-v2/PSI-
basierten Out-of-Memory-Schutz, um Systemfreezes bei gleichzeitiger
Speicher-/Swap-Auslastung (z. B. Spiel + Discord + Browser) zu
verhindern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 22:50:54 +02:00
DragonSlayer_14andClaude Sonnet 5 287e4d815b Feature: GameMode-Tuning, MangoHud-Default & CoreCtrl für Gaming-Setup
Deployt eine getunte /etc/gamemode.ini (Performance-Governor, Renice,
I/O-Priorität, AMD-GPU-Performance-Level) und ergänzt den Zielbenutzer
um die Gruppen render/gamemode. MangoHud erhält ein vorkonfiguriertes
Overlay über den bestehenden Skel-Sync. CoreCtrl wird installiert und
per PolicyKit-Regel ohne wiederholte Passwortabfrage für sudo-Mitglieder
freigegeben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 22:23:29 +02:00
DragonSlayer_14andClaude Sonnet 5 c1f3851086 Feature: Bei fehlgeschlagenem Befehl Wiederholen/Ignorieren/Abbrechen abfragen
Der ERR-Trap fragt den Benutzer nun interaktiv, wie mit einem
fehlgeschlagenen Befehl verfahren werden soll, statt die Installation
sofort abzubrechen. Zusätzlich aktiviert setup_err_trap jetzt
errtrace/functrace, damit Fehler auch innerhalb von lib/*.sh-Funktionen
abgefangen werden statt das Skript stillschweigend zu beenden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:54:47 +02:00
DragonSlayer_14andClaude Sonnet 5 0bf2e81063 Fix: Ollama-eigenen Standardpfad statt /mnt/Data als Vorschlag anbieten
Bisher wurde in allen drei Dialogvarianten (whiptail-Yesno, whiptail-
Inputbox, CLI-Fallback) derselbe konfigurierte Pfad
(/mnt/Data/Software/ollama/models) sowohl als "Standard" beworben als
auch als vorausgefüllter Eingabewert verwendet - der tatsächliche
Ollama-Standardspeicherort kam nie zum Zug.

prompt_ollama_models_path() unterscheidet jetzt zwischen dem echten
Ollama-Standardpfad (/usr/share/ollama/.ollama/models, verwendet vom
offiziellen Installationsskript / systemd-Dienst) und dem in
OLLAMA_MODELS_DEFAULT konfigurierten Pfadvorschlag. Letzterer wird nur
noch als Vorbelegung im Eingabefeld angezeigt, nachdem der Standardpfad
explizit abgelehnt wurde.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:37:27 +02:00
DragonSlayer_14andClaude Sonnet 5 950eb5d361 Refactor: Verschachtelung im Ollama-Speicherort-Dialog reduziert
Der whiptail-Zweig verschachtelte vier Ebenen tief und verteilte drei
identische chosen="$default_path"-Zuweisungen über if/else-Paare, um
eine einzige lineare Entscheidung abzubilden. Die Ja/Nein-Abfrage und
der Eingabedialog folgen jetzt beide dem Guard-Clause-Muster
(if [[ -z "$chosen" ]]; then ... fi), das bereits für den CLI-Fallback
weiter unten in derselben Funktion verwendet wird.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:33:48 +02:00
DragonSlayer_14andClaude Sonnet 5 9278241dd0 Refactor: Ja/Nein-Abfrage in gemeinsame Hilfsfunktion ausgelagert
prompt_target_user und prompt_ollama_models_path implementierten die
gleiche CLI-Ja/Nein-Abfrage (read + Trim + Regex-Prüfung) unabhängig
voneinander. Beide Kopien waren bereits leicht auseinandergelaufen
(unterschiedliches Fallback-Zeichen, unterschiedliche Regex-
Reihenfolge). confirm_yes_no() bündelt die Logik an einer Stelle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:32:42 +02:00
DragonSlayer_14andClaude Sonnet 5 1fa3770e6e Fix: ESC im Ollama-Speicherort-Dialog fällt auf Standardpfad zurück
whiptail liefert bei ESC den Exit-Code 255, der bisher wie ein
explizites "Nein" (Exit-Code 1) behandelt und in den Eingabedialog für
einen eigenen Pfad geleitet wurde. Das widersprach dem direkt
folgenden Eingabedialog, in dem ESC/Abbruch stattdessen den
Standardpfad übernimmt. ESC verhält sich jetzt in beiden Dialogen
gleich.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:31:53 +02:00
DragonSlayer_14andClaude Sonnet 5 775b03e9de Fix: Trimmen der Ja/Nein-Antwort per xargs durch sed ersetzt
xargs interpretiert Anführungszeichen/Backslashes in der Eingabe und
schlägt bei ungültiger Eingabe mit leerer Ausgabe fehl. Das ließ eine
eigentlich verneinende Antwort fälschlich als "Ja" (Standardpfad)
durchgehen. sed wird bereits an allen anderen Trim-Stellen in dieser
Funktion verwendet und kennt dieses Fehlverhalten nicht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:31:38 +02:00
DragonSlayer_14andClaude Sonnet 5 d9ec12dba3 Feat: Bestätigungsabfrage vor Ollama-Speicherort-Dialog hinzufügen
prompt_ollama_models_path() fragt jetzt zuerst per Ja/Nein, ob der
konfigurierte Standard-Speicherort (OLLAMA_MODELS_DEFAULT) verwendet werden
soll - analog zum bestehenden Muster in tui_prompt_target_user(). Nur bei
"Eigenen Pfad angeben" erscheint der Eingabedialog, vorbelegt mit genau
diesem Standardpfad. Gilt für whiptail-TUI und den CLI-Fallback gleichermaßen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:15:29 +02:00
DragonSlayer_14andClaude Sonnet 5 1723803e9c Feat: Linutil als letzten Install-Schritt (Stage 11) hinzufügen
Ergänzt stages/11-linutil.sh, das Linutil (Chris Titus Tech System Toolbox,
curl -fsSL https://christitus.com/linux | sh) als Zielbenutzer ausführt -
analog zum bestehenden Hyprland-Installer-Muster in Stage 10, da Linutil
selbst per sudo eskaliert wo nötig. Überspringt sich mit klarer Meldung,
falls kein Terminal verfügbar ist, statt an der interaktiven TUI zu hängen.

--all läuft jetzt 00 bis 11, die whiptail-Checklist sowie Hilfetext,
README.md und AGENTS.md wurden entsprechend ergänzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:07:22 +02:00
DragonSlayer_14andClaude Sonnet 5 2e470d676d Feat: Claude Code & Awesome LLM Skills zur Ersteinrichtung hinzufügen
Ergänzt die bestehende Ersteinrichtung (Dotfiles, Oh-My-Zsh, JetBrains Junie)
um zwei weitere Schritte, analog zum bereits vorhandenen Junie-Muster:

- setup-claude-cli.sh installiert Claude Code über den offiziellen nativen
  Installer (curl -fsSL https://claude.ai/install.sh | bash).
- setup-claude-skills.sh klont/aktualisiert Awesome LLM Skills
  (https://github.com/Prat011/awesome-llm-skills) nach
  ~/.claude/skills/awesome-llm-skills und verlinkt alle Unterordner mit
  SKILL.md direkt nach ~/.claude/skills, damit Claude Code sie erkennt.

first-login-setup.sh ruft beide Schritte zusätzlich auf; Beschreibungen im
systemd-Service und in stages/10-hyprland.sh entsprechend erweitert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 21:02:49 +02:00
DragonSlayer_14andClaude Sonnet 5 8761e1c673 Fix: Ersteinrichtung (Dotfiles, Zsh, Junie) in klare Skripte aufgeteilt und von Hyprland entkoppelt
Das bisherige install-dots-prompt.sh vereinte Dotfiles-Auswahl, Oh-My-Zsh,
JetBrains Junie CLI und AwesomeJunieSkills in einem Skript und wurde per
exec-once direkt von Hyprland gestartet.

Aufgeteilt in eigenständige Skripte (setup-dotfiles.sh, setup-shell.sh,
setup-junie-cli.sh, setup-junie-skills.sh) mit gemeinsamer first-login-setup.sh
als Orchestrator. Systemweit unter /usr/local/lib/first-login-setup installiert
(eine Kopie für alle Nutzer statt Duplikate pro Home) und über einen global
aktivierten systemd --user Service (/etc/systemd/user/) gestartet - bewusst
nicht über hyprland.conf, damit es unabhängig vom gewählten Compositor
funktioniert und Hyprland selbst es nicht mehr ausführt. Der Service sucht das
Wayland-Socket eigenständig, statt sich auf importierte Umgebungsvariablen zu
verlassen, und wiederholt sich bei Bedarf bis die grafische Sitzung bereit ist.

Der Fortschritt liegt in ~/.local/state/first-login-setup (statt ~/.config),
da setup-dotfiles.sh ~/.config komplett durch ein eigenes Dotfiles-Repository
ersetzen kann - das darf den Fortschritt nicht mit sich reißen. Zusätzlich
fragt das Skript jetzt explizit "Jetzt/Später": nur bei "Jetzt" wird der
Marker gesetzt, bei "Später" wird beim nächsten Login erneut gefragt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 20:58:20 +02:00
DragonSlayer_14andClaude Sonnet 5 5a8dd73d32 Fix: whiptail-TUI reagiert nicht auf Tastatureingaben bei curl | bash
sudo, aus einer nicht-interaktiven gepipten Shell heraus aufgerufen, gibt die
Terminal-Vordergrundgruppe nicht zuverlässig an den Kindprozess weiter.
setup.sh lief bisher komplett (inkl. aller whiptail-Dialoge) innerhalb von
sudo, wodurch das Menü zwar sichtbar war, aber keine Tasten mehr ankamen.

setup.sh fragt jetzt alle interaktiven Eingaben (Stage-Auswahl, Zielbenutzer,
Ollama-Pfad) zuerst als normaler Nutzer ab und eskaliert erst danach per
exec sudo, wobei die bereits getroffenen Entscheidungen als Flags/Env-Variablen
durchgereicht werden. install.sh eskaliert dadurch nicht mehr selbst.
Zusätzlich wird die fehlerhafte "-c /dev/tty"-Interaktivitätsprüfung
(prüfte nur Existenz der Gerätedatei, nicht ob ein Terminal verbunden ist)
durch has_tty() ersetzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
2026-09-06 20:31:54 +02:00
DragonSlayer_14andJunie 116a33a876 fix(tui): bind whiptail dialogs to controlling terminal
Interactive stage selection did not receive any keyboard input when the
setup was started through a pipe (curl ... | bash), because the dialogs
inherited the pipe as stdin/stdout and ENTER/SPACE went to the shell.

- lib/utils.sh: add has_tty, ensure_term, get_term_size and a run_whiptail
  wrapper that binds dialog I/O to /dev/tty and returns results via
  --output-fd 3
- lib/tui.sh: run all dialogs through run_whiptail, add tui_is_available
  and tui_calc_geometry to clamp dialog geometry to the terminal size
- setup.sh: re-attach stdin/stdout to /dev/tty when piped, normalize TERM
  and use has_tty for the interactive check
- install.sh: reconnect stdout as well and normalize TERM before running
  setup.sh

Co-authored-by: Junie <junie@jetbrains.com>
2026-09-06 17:37:59 +02:00
DragonSlayer_14andJunie e87342d3b7 fix(install): reconnect stdin to tty for interactive TUI in piped execution
Co-authored-by: Junie <junie@jetbrains.com>
2026-09-06 17:16:50 +02:00
DragonSlayer_14andJunie c219988011 fix(install): use direct process redirection to /dev/tty for setup child process
Co-authored-by: Junie <junie@jetbrains.com>
2026-09-06 17:05:59 +02:00
DragonSlayer_14andJunie 174014cae0 fix(tui): fix whiptail dialog progression, output descriptor handling and terminal scaling
Co-authored-by: Junie <junie@jetbrains.com>
2026-09-06 16:54:17 +02:00
DragonSlayer_14andJunie 2f02c1b74a fix(tui): fix initial dialog interaction and user detection in interactive mode
Co-authored-by: Junie <junie@jetbrains.com>
2026-09-06 16:30:28 +02:00
DragonSlayer_14andJunie f093cce7dc fix(install): prevent stdin stream truncation when running via pipe
Co-authored-by: Junie <junie@jetbrains.com>
2026-09-06 16:13:51 +02:00
DragonSlayer_14andJunie 85408020c6 Feat: Automatische Konfiguration aller LAN-Schnittstellen und Loopback im Debian-Installer-Standard
Co-authored-by: Junie <junie@jetbrains.com>
2026-09-06 15:56:54 +02:00
32 changed files with 1781 additions and 557 deletions
+26 -6
View File
@@ -26,6 +26,7 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F
│ ├── utils.sh # Logging, Error-Traps, Benutzer- und Pfadermittlung, Backups │ ├── utils.sh # Logging, Error-Traps, Benutzer- und Pfadermittlung, Backups
│ ├── apt.sh # Deb822-Quellen, Keyrings (/etc/apt/keyrings), APT-Pinning, Paketinstallation │ ├── apt.sh # Deb822-Quellen, Keyrings (/etc/apt/keyrings), APT-Pinning, Paketinstallation
│ ├── btrfs.sh # Btrfs Root- & Subvolume-Erkennung, Migration (@, @home), fstab-Verwaltung │ ├── btrfs.sh # Btrfs Root- & Subvolume-Erkennung, Migration (@, @home), fstab-Verwaltung
│ ├── network.sh # Loopback- & LAN-Schnittstellenkonfiguration (Debian Installer Standard)
│ └── tui.sh # Whiptail-TUI-Menüs und Dialoge │ └── tui.sh # Whiptail-TUI-Menüs und Dialoge
├── config/ # Konfigurationsdateien & Paketlisten ├── config/ # Konfigurationsdateien & Paketlisten
│ ├── setup.conf # Globale Umgebungsvariablen und Standardwerte │ ├── setup.conf # Globale Umgebungsvariablen und Standardwerte
@@ -36,26 +37,29 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F
│ ├── 04-virtualization.list # QEMU, KVM, Libvirt, Bridge-Tools │ ├── 04-virtualization.list # QEMU, KVM, Libvirt, Bridge-Tools
│ ├── 05-multimedia.list # VLC, Amberol, GIMP, Loupe, LibreOffice │ ├── 05-multimedia.list # VLC, Amberol, GIMP, Loupe, LibreOffice
│ └── 06-flatpaks.list # Flathub App-IDs │ └── 06-flatpaks.list # Flathub App-IDs
├── stages/ # Sequenziell und modular ausführbare Phasen (00 bis 10) ├── stages/ # Sequenziell und modular ausführbare Phasen (00 bis 11)
│ ├── 00-preflight.sh # Systemvoraussetzungen, Root/User-Erkennung, Internetverbindung │ ├── 00-preflight.sh # Systemvoraussetzungen, Root/User-Erkennung, Internetverbindung
│ ├── 01-apt-unstable.sh # Deb822-Quellen, Keyrings, Pinning, apt-listbugs, dist-upgrade │ ├── 01-apt-unstable.sh # Deb822-Quellen, Keyrings, Pinning, apt-listbugs, dist-upgrade
│ ├── 02-kernel-hardware.sh # XanMod-Kernel, CPU P-State, Microcode, auto-cpufreq │ ├── 02-kernel-hardware.sh # XanMod-Kernel, CPU P-State, Microcode, auto-cpufreq, Shader-Cache-Pfad
│ ├── 03-bootloader.sh # GRUB Kernel-Parameter, Vimix-Theme, Plymouth Solar │ ├── 03-bootloader.sh # GRUB Kernel-Parameter, Vimix-Theme, Plymouth Solar
│ ├── 04-packages.sh # Installation der thematischen Paketlisten │ ├── 04-packages.sh # Installation der thematischen Paketlisten
│ ├── 05-fonts.sh # Download & Installation von Coding- & Nerd-Fonts │ ├── 05-fonts.sh # Download & Installation von Coding- & Nerd-Fonts
│ ├── 06-services.sh # Aktivierung von AppArmor, Docker, Libvirt, zram, Ollama │ ├── 06-services.sh # Aktivierung von AppArmor, Docker, Libvirt, zram, Ollama, GameMode/CoreCtrl, systemd-oomd, scx-Scheduler
│ ├── 07-skel-and-user.sh # /etc/skel Vorlagen, ZSH-Shell-Setzen, Dotfiles-Sync │ ├── 07-skel-and-user.sh # /etc/skel Vorlagen, ZSH-Shell-Setzen, Dotfiles-Sync
│ ├── 08-flatpaks.sh # Flathub Setup, Flatpak-Paketlisten-Installation │ ├── 08-flatpaks.sh # Flathub Setup, Flatpak-Paketlisten-Installation
│ ├── 09-apps.sh # Spotify, Waydroid, OpenDeck, Desktop-Starter, MIME-Types │ ├── 09-apps.sh # Spotify, Waydroid, OpenDeck, Desktop-Starter, MIME-Types
── 10-hyprland.sh # LinuxBeginnings Debian-Hyprland Auto-Installer & Desktop-Setup ── 10-hyprland.sh # LinuxBeginnings Debian-Hyprland Auto-Installer & Desktop-Setup
│ └── 11-linutil.sh # Linutil (Chris Titus Tech System Toolbox), letzter Install-Schritt
├── data/ # Statische Konfigurationsdateien & Vorlagen ├── data/ # Statische Konfigurationsdateien & Vorlagen
│ ├── apt/ │ ├── apt/
│ │ ├── sources.list.d/ # Deb822-Quellen (debian-unstable.sources, xanmod.sources) │ │ ├── sources.list.d/ # Deb822-Quellen (debian-unstable.sources, xanmod.sources)
│ │ └── preferences.d/ # Pinning-Konfigurationen │ │ └── preferences.d/ # Pinning-Konfigurationen
│ ├── etc/ │ ├── etc/
│ │ ├── environment.d/ # Globale Umgebungsvariablen (Raytracing, Upscaling) │ │ ├── environment.d/ # Globale Umgebungsvariablen (Raytracing, Upscaling)
│ │ ├── gamemode.ini # GameMode Performance-Daemon Defaults (Governor, Renice, GPU)
│ │ ├── polkit-1/rules.d/ # 90-corectrl.rules (passwortlose CoreCtrl-Freigabe für sudo-Gruppe)
│ │ └── profile.d/ # Globale Profil-Skripte (z. B. XDG User Dirs Update) │ │ └── profile.d/ # Globale Profil-Skripte (z. B. XDG User Dirs Update)
│ ├── skel/ # Vorlagen für /etc/skel (.zshrc, Dokumentvorlagen) │ ├── skel/ # Vorlagen für /etc/skel (.zshrc, .config/MangoHud, .config/vkBasalt, Dokumentvorlagen)
│ └── usr/share/applications/ # Desktop-Starter-Dateien │ └── usr/share/applications/ # Desktop-Starter-Dateien
├── README.md # Projektdokumentation für Endanwender ├── README.md # Projektdokumentation für Endanwender
└── LICENSE # MIT-Lizenz └── LICENSE # MIT-Lizenz
@@ -89,6 +93,7 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F
source "$SCRIPT_DIR/../lib/utils.sh" source "$SCRIPT_DIR/../lib/utils.sh"
setup_err_trap setup_err_trap
``` ```
- `setup_err_trap` aktiviert `errtrace`/`functrace`, damit Fehler auch innerhalb von `lib/*.sh`-Funktionen abgefangen werden, und registriert `error_handler` als ERR-Trap. Schlägt ein Befehl fehl, wird der Benutzer interaktiv gefragt, ob der Befehl **wiederholt**, **ignoriert** oder die **Installation abgebrochen** werden soll (`ask_retry_ignore_abort`). In nicht-interaktiven Läufen (`CI=1`, `DEBIAN_FRONTEND=noninteractive`, `AUTO_CONFIRM=1`, keine TTY) wird immer automatisch abgebrochen, wie zuvor.
- Verwende für temporäre Verzeichnisse immer `mktemp -d` und stelle sicher, dass am Skriptende oder bei Fehlern aufgeräumt wird: - Verwende für temporäre Verzeichnisse immer `mktemp -d` und stelle sicher, dass am Skriptende oder bei Fehlern aufgeräumt wird:
```bash ```bash
TMP_DIR="$(mktemp -d)" TMP_DIR="$(mktemp -d)"
@@ -121,6 +126,8 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F
- `log_error "Nachricht"`: Rote Fehlermeldung (`[ERROR]`). - `log_error "Nachricht"`: Rote Fehlermeldung (`[ERROR]`).
- `log_step "Titel"`: Hervorgehobener Abschnitts-Header. - `log_step "Titel"`: Hervorgehobener Abschnitts-Header.
- `log_substep "Titel"`: Eingerückter Unterabschnitt. - `log_substep "Titel"`: Eingerückter Unterabschnitt.
- `setup_err_trap`: Aktiviert `errtrace`/`functrace` und registriert `error_handler` als ERR-Trap für das gesamte Skript (inkl. `lib/*.sh`-Funktionen).
- `error_handler` / `ask_retry_ignore_abort` / `resume_after_error`: Zentrale Fehlerbehandlung. Bei einem fehlgeschlagenen Befehl wird interaktiv nach Wiederholen/Ignorieren/Abbrechen gefragt; nicht-interaktiv wird immer abgebrochen.
- `require_root`: Bricht ab, wenn das Skript nicht mit Root-Rechten ausgeführt wird. - `require_root`: Bricht ab, wenn das Skript nicht mit Root-Rechten ausgeführt wird.
- `command_exists "tool"`: Prüft, ob ein Befehl im Pfad verfügbar ist. - `command_exists "tool"`: Prüft, ob ein Befehl im Pfad verfügbar ist.
- `prompt_target_user`: Erkennt `$SUDO_USER` oder fragt interaktiv nach dem Zielbenutzer. - `prompt_target_user`: Erkennt `$SUDO_USER` oder fragt interaktiv nach dem Zielbenutzer.
@@ -145,8 +152,21 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F
- `ensure_grub_btrfs`: Installiert und konfiguriert `grub-btrfs` sowie den `grub-btrfsd`-Dienst für die automatische Generierung von Snapshot-Bootmenüeinträgen in GRUB. - `ensure_grub_btrfs`: Installiert und konfiguriert `grub-btrfs` sowie den `grub-btrfsd`-Dienst für die automatische Generierung von Snapshot-Bootmenüeinträgen in GRUB.
- `create_timeshift_snapshot "comment" [tags]`: Erstellt einen Timeshift-Btrfs-Snapshot an kritischen Setup-Meilensteinen (idempotent, steuerbar über `ENABLE_TIMESHIFT_SNAPSHOTS` in `config/setup.conf`). - `create_timeshift_snapshot "comment" [tags]`: Erstellt einen Timeshift-Btrfs-Snapshot an kritischen Setup-Meilensteinen (idempotent, steuerbar über `ENABLE_TIMESHIFT_SNAPSHOTS` in `config/setup.conf`).
### `lib/network.sh`
- `get_lan_interfaces`: Erkennt alle physischen LAN/Ethernet-Schnittstellen dynamisch auf jedem System (unter Ausschluss von Loopback, Wi-Fi, Bridges und virtuellen Containern).
- `is_interface_configured "iface"`: Prüft, ob eine Schnittstelle bereits in `/etc/network/interfaces` oder `/etc/network/interfaces.d/` konfiguriert ist.
- `configure_loopback_interface`: Richtet das Loopback-Interface `lo` in `/etc/network/interfaces` (`auto lo`, `iface lo inet loopback`) sowie `/etc/hosts` ein und aktiviert es.
- `configure_lan_interfaces`: Richtet alle erkannten physischen LAN-Schnittstellen im Debian-Installer-Standard (`allow-hotplug <iface>`, `iface <iface> inet dhcp`, `iface <iface> inet6 auto`) ein und aktiviert diese.
- `configure_network_manager`: Konfiguriert `/etc/NetworkManager/NetworkManager.conf` mit `[ifupdown] managed=true` für nahtlose Desktop- und Waybar-Integration.
- `enable_network_services`: Aktiviert und startet `networking.service` und `NetworkManager.service`.
- `configure_network_all`: Zentraler Orchestrator zur vollständigen Netzwerkeinrichtung.
### `lib/tui.sh` ### `lib/tui.sh`
- `tui_show_stage_selection`: Zeigt ein `whiptail` Checklist-Menü zur interaktiven Auswahl der auszuführenden Phasen. - `tui_check_whiptail`: Stellt sicher, dass `whiptail` installiert ist.
- `tui_welcome`: Zeigt den Begrüßungsdialog.
- `tui_select_stages`: Zeigt ein `whiptail` Checklist-Menü zur interaktiven Auswahl der auszuführenden Phasen.
- `tui_prompt_target_user [default_user]`: Bestätigt oder erfragt den Desktop-Zielbenutzer per Dialog und exportiert `TARGET_USER`.
- `tui_confirm_execution "stages" "user"`: Bestätigungsdialog vor dem Start der Installation.
--- ---
+39 -9
View File
@@ -13,7 +13,7 @@ Das Setup kann direkt ohne vorheriges manuelles Klonen via `curl` gestartet werd
# Interaktiver Modus (whiptail Menü) # Interaktiver Modus (whiptail Menü)
curl -fsSL https://gitea.creative-dragonslayer.de/Scripts/Setup/raw/branch/main/install.sh | bash curl -fsSL https://gitea.creative-dragonslayer.de/Scripts/Setup/raw/branch/main/install.sh | bash
# Vollautomatischer Modus (alle Stages 00 bis 10) # Vollautomatischer Modus (alle Stages 00 bis 11)
curl -fsSL https://gitea.creative-dragonslayer.de/Scripts/Setup/raw/branch/main/install.sh | bash -s -- --all curl -fsSL https://gitea.creative-dragonslayer.de/Scripts/Setup/raw/branch/main/install.sh | bash -s -- --all
# Gezielte Modulauswahl (z. B. Paketlisten und Schriften) # Gezielte Modulauswahl (z. B. Paketlisten und Schriften)
@@ -23,29 +23,31 @@ curl -fsSL https://gitea.creative-dragonslayer.de/Scripts/Setup/raw/branch/main/
### 2. Manuelles Klonen & Starten ### 2. Manuelles Klonen & Starten
Alternativ kann das Repository manuell geklont und ausgeführt werden: Alternativ kann das Repository manuell geklont und ausgeführt werden:
```bash ```bash
git clone https://gitea.creative-dragonslayer.de/Scripts/Setup.git && cd Setup && sudo ./setup.sh git clone https://gitea.creative-dragonslayer.de/Scripts/Setup.git && cd Setup && ./setup.sh
``` ```
Oder direkt vollautomatisch alle Stages (`00` bis `10`) ausführen: Oder direkt vollautomatisch alle Stages (`00` bis `10`) ausführen:
```bash ```bash
git clone https://gitea.creative-dragonslayer.de/Scripts/Setup.git && cd Setup && sudo ./setup.sh --all git clone https://gitea.creative-dragonslayer.de/Scripts/Setup.git && cd Setup && ./setup.sh --all
``` ```
> `setup.sh` fragt zuerst alle nötigen Eingaben (Modulauswahl, Zielbenutzer, ...) direkt am Terminal ab und eskaliert erst danach selbstständig per `sudo`. Ein manuelles `sudo` davor ist nicht nötig, funktioniert aber ebenfalls.
### 3. Interaktiver TUI-Modus ### 3. Interaktiver TUI-Modus
Starte das grafische Terminal-Menü (`whiptail`) zur flexiblen Auswahl einzelner Module: Starte das grafische Terminal-Menü (`whiptail`) zur flexiblen Auswahl einzelner Module:
```bash ```bash
sudo ./setup.sh ./setup.sh
``` ```
### 4. Vollautomatischer CLI-Modus ### 4. Vollautomatischer CLI-Modus
Führe alle Phasen (`00` bis `10`) ohne Interaktion aus: Führe alle Phasen (`00` bis `10`) ohne Interaktion aus:
```bash ```bash
sudo ./setup.sh --all ./setup.sh --all
``` ```
### 5. Gezielte Modulauswahl ### 5. Gezielte Modulauswahl
Installiere nur bestimmte Phasen (z. B. nur Paketlisten und Schriften): Installiere nur bestimmte Phasen (z. B. nur Paketlisten und Schriften):
```bash ```bash
sudo ./setup.sh --stages 04,05 ./setup.sh --stages 04,05
``` ```
### 6. Simulationslauf (Dry-Run) ### 6. Simulationslauf (Dry-Run)
@@ -78,6 +80,7 @@ sudo ./setup.sh --stages 04,05
│ ├── utils.sh # Logging, Error-Traps (set -euo pipefail), Root/User-Erkennung & Abfrage │ ├── utils.sh # Logging, Error-Traps (set -euo pipefail), Root/User-Erkennung & Abfrage
│ ├── apt.sh # APT-Keyring-Management (/etc/apt/keyrings/), Deb822, Pinning │ ├── apt.sh # APT-Keyring-Management (/etc/apt/keyrings/), Deb822, Pinning
│ ├── btrfs.sh # Btrfs Root- & Subvolume-Erkennung, Migration (@, @home), Timeshift-Snapshots │ ├── btrfs.sh # Btrfs Root- & Subvolume-Erkennung, Migration (@, @home), Timeshift-Snapshots
│ ├── network.sh # Loopback- & LAN-Schnittstellenkonfiguration (Debian Installer Standard)
│ └── tui.sh # Whiptail-Menüs und Dialoge │ └── tui.sh # Whiptail-Menüs und Dialoge
├── config/ # Konfigurationsdateien & Paketlisten ├── config/ # Konfigurationsdateien & Paketlisten
│ ├── setup.conf # Globale Variablen (Kernel, GRUB-Theme, Plymouth) │ ├── setup.conf # Globale Variablen (Kernel, GRUB-Theme, Plymouth)
@@ -99,15 +102,18 @@ sudo ./setup.sh --stages 04,05
│ ├── 07-skel-and-user.sh # /etc/skel Vorlagen, ZSH-Shell-Setzen, Dotfiles-Sync │ ├── 07-skel-and-user.sh # /etc/skel Vorlagen, ZSH-Shell-Setzen, Dotfiles-Sync
│ ├── 08-flatpaks.sh # Flathub Setup, Flatpak-Paketlisten-Installation │ ├── 08-flatpaks.sh # Flathub Setup, Flatpak-Paketlisten-Installation
│ ├── 09-apps.sh # Spotify, Waydroid, OpenDeck, Desktop-Starter, MIME-Types │ ├── 09-apps.sh # Spotify, Waydroid, OpenDeck, Desktop-Starter, MIME-Types
── 10-hyprland.sh # LinuxBeginnings Debian-Hyprland Auto-Installer & Dotfiles ── 10-hyprland.sh # LinuxBeginnings Debian-Hyprland Auto-Installer & Dotfiles
│ └── 11-linutil.sh # Linutil (Chris Titus Tech System Toolbox), letzter Install-Schritt
└── data/ # Statische Konfigurationsdateien & Vorlagen └── data/ # Statische Konfigurationsdateien & Vorlagen
├── apt/ ├── apt/
│ ├── sources.list.d/ # debian-unstable.sources, xanmod.sources │ ├── sources.list.d/ # debian-unstable.sources, xanmod.sources
│ └── preferences.d/ # Pinning-Konfigurationen (unstable, xanmod) │ └── preferences.d/ # Pinning-Konfigurationen (unstable, xanmod)
├── etc/ ├── etc/
│ ├── environment.d/ # 99-raytracing.conf, 99-upscaling.conf │ ├── environment.d/ # 99-raytracing.conf, 99-upscaling.conf (99-shader-cache.conf wird optional zur Laufzeit generiert)
│ ├── gamemode.ini # GameMode Performance-Daemon Defaults
│ ├── polkit-1/rules.d/ # 90-corectrl.rules (passwortlose CoreCtrl-Freigabe für sudo-Gruppe)
│ └── profile.d/ # Bereinigte System-Profile-Skripte │ └── profile.d/ # Bereinigte System-Profile-Skripte
├── skel/ # .zshrc, .config/hypr, Templates für Dokumente & Skripte ├── skel/ # .zshrc, .config/hypr, .config/MangoHud, .config/vkBasalt, Templates für Dokumente & Skripte
└── usr/share/applications/ # Desktop-Dateien (z. B. Spotify) └── usr/share/applications/ # Desktop-Dateien (z. B. Spotify)
``` ```
@@ -131,11 +137,32 @@ sudo ./setup.sh --stages 04,05
- Installation von `apt-listbugs` und `apt-listchanges` vor dem `dist-upgrade` schützt vor bekannten kritischen Paketproblemen. - Installation von `apt-listbugs` und `apt-listchanges` vor dem `dist-upgrade` schützt vor bekannten kritischen Paketproblemen.
5. **Sichere Shell-Verwaltung**: 5. **Sichere Shell-Verwaltung**:
- Standardkonforme Änderung der Login-Shell via `chsh -s` statt direkter `/etc/passwd`-Modifikation. - Standardkonforme Änderung der Login-Shell via `chsh -s` statt direkter `/etc/passwd`-Modifikation.
6. **Netzwerk- & Schnittstellenkonfiguration (Debian Installer Standard)**:
- Automatische Erkennung und Konfiguration aller physischen LAN-Netzwerkschnittstellen via DHCP und IPv6 SLAAC (`allow-hotplug <iface>`, `iface <iface> inet dhcp`, `iface <iface> inet6 auto`).
- Standardkonforme Einrichtung des Loopback-Interfaces (`auto lo`, `iface lo inet loopback`) in `/etc/network/interfaces` und `/etc/hosts`.
- Nahtlose NetworkManager-Integration (`managed=true`) und Berechtigung des Zielbenutzers via `netdev`-Gruppe.
--- ---
## 🎮 Gaming & Performance-Optimierungen ## 🎮 Gaming & Performance-Optimierungen
### GameMode, MangoHud & CoreCtrl
- **GameMode**: `stages/06-services.sh` rollt eine getunte `/etc/gamemode.ini` aus (`desiredgov=performance`, Renice, I/O-Priorität, AMD-GPU-Performance-Level `high`) und fügt den Zielbenutzer automatisch den Gruppen `gamemode` (Renice/Core-Parking) und `render` (Vulkan/GPU-Compute) hinzu.
- **MangoHud**: `data/skel/.config/MangoHud/MangoHud.conf` liefert ein vorkonfiguriertes Overlay (FPS, Frametime, CPU/GPU-Temperatur & -Takt, VRAM/RAM, GameMode-Indikator), das über `stages/07-skel-and-user.sh` automatisch für neue und bestehende Benutzer bereitgestellt wird. Umschalten mit `Shift+F12` (Position mit `Shift+F11`).
- **CoreCtrl**: Für Fankurven, Power-States und Undervolting von AMD-CPU/GPU. Eine PolicyKit-Regel (`90-corectrl.rules`) erlaubt Mitgliedern der `sudo`-Gruppe den Start ohne wiederholte Passwortabfrage.
### systemd-oomd (Out-of-Memory-Schutz)
- Wird aktiviert, um Einfrieren des Systems bei gleichzeitiger Speicher-/Swap-Auslastung (Spiel + Discord + Browser) zu verhindern ergänzt das bestehende zram-Swap.
### Sched-ext Gaming-Scheduler
- **sched-ext (`scx-scheds`)**: `stages/06-services.sh` installiert das Paket aus dem bereits konfigurierten XanMod-Repository und aktiviert den Scheduler `scx_lavd` (Latency-criticality Aware Virtual Deadline speziell für Gaming/interaktive Desktop-Workloads) im `--autopilot`-Modus über `/etc/default/scx` und `scx.service`. Benötigt einen Kernel mit `CONFIG_SCHED_CLASS_EXT` (bei XanMod bereits aktiv); nach einer frischen Kernel-Installation ist ggf. ein Neustart nötig.
### vkBasalt (Vulkan-Postprocessing)
- ReShade-ähnliche Vulkan-Postprocessing-Shader (Contrast Adaptive Sharpening als Standard) mit vorkonfigurierter `~/.config/vkBasalt/vkBasalt.conf`. Aktivierung pro Spiel über die Steam-Launch-Option `ENABLE_VKBASALT=1 %command%`; Status wird von MangoHud mit angezeigt.
### Persistenter Shader-Cache (optional)
- Standardmäßig bleiben Mesa (RADV/OpenGL) und DXVK (Wine/Proton) bei ihren eigenen Cache-Pfaden (`~/.cache/mesa_shader_cache`, `~/.cache/dxvk`). Wird `SHADER_CACHE_DIR` in `config/setup.conf` gesetzt (z. B. auf eine separate/schnelle Festplatte), verlegt `stages/02-kernel-hardware.sh` beide Caches dorthin, um Nachcompilierungs-Ruckler nach Neustarts zu vermeiden.
### CPU P-State & Kernel-Parameter ### CPU P-State & Kernel-Parameter
In `stages/03-bootloader.sh` wird der CPU-Hersteller automatisch ermittelt und in `/etc/default/grub` eingetragen: In `stages/03-bootloader.sh` wird der CPU-Hersteller automatisch ermittelt und in `/etc/default/grub` eingetragen:
- **AMD CPUs**: `amd_pstate=active` - **AMD CPUs**: `amd_pstate=active`
@@ -149,6 +176,9 @@ gamemoderun gamescope -W 2560 -H 1440 --force-grab-cursor --hdr-enabled --adapti
# Raytracing für DX12 Titel (RADV / Mesa) # Raytracing für DX12 Titel (RADV / Mesa)
RADV_PERFTEST=gpl,rt,ngg_streamout VKD3D_CONFIG=dxr,dxr11 DXVK_ASYNC=1 AMD_VULKAN_ICD=RADV gamemoderun gamescope -W 2560 -H 1440 --force-grab-cursor --hdr-enabled --adaptive-sync -f -o 240 -r 240 -- mangohud %command% RADV_PERFTEST=gpl,rt,ngg_streamout VKD3D_CONFIG=dxr,dxr11 DXVK_ASYNC=1 AMD_VULKAN_ICD=RADV gamemoderun gamescope -W 2560 -H 1440 --force-grab-cursor --hdr-enabled --adaptive-sync -f -o 240 -r 240 -- mangohud %command%
# Mit vkBasalt-Postprocessing (Schärfung via Contrast Adaptive Sharpening)
ENABLE_VKBASALT=1 gamemoderun gamescope -W 2560 -H 1440 --force-grab-cursor --hdr-enabled --adaptive-sync -f -o 240 -r 240 -- mangohud %command%
``` ```
--- ---
+9
View File
@@ -31,6 +31,7 @@ cryptomator
curl curl
dconf-editor dconf-editor
debian-goodies debian-goodies
ethtool
fastfetch fastfetch
file-roller file-roller
firmware-linux firmware-linux
@@ -49,11 +50,16 @@ gparted
htop htop
hunspell hunspell
hunspell-de-de-frami hunspell-de-de-frami
ifupdown
inotify-tools inotify-tools
iproute2
irqbalance irqbalance
isc-dhcp-client
keepassxc keepassxc
lsd lsd
man man
net-tools
network-manager
network-manager-gnome network-manager-gnome
plymouth plymouth
plymouth-themes plymouth-themes
@@ -63,6 +69,7 @@ ripgrep
rpi-imager rpi-imager
shellcheck shellcheck
sl sl
systemd-oomd
systemd-timesyncd systemd-timesyncd
tar tar
tealdeer tealdeer
@@ -71,6 +78,8 @@ topgrade
tree tree
unrar unrar
unzip unzip
wireless-regdb
wpasupplicant
zram-tools zram-tools
zsh zsh
zsh-autosuggestions zsh-autosuggestions
+3
View File
@@ -2,6 +2,7 @@
# config/packages/03-gaming.list - Gaming tools, Vulkan drivers, Wine & Emulation # config/packages/03-gaming.list - Gaming tools, Vulkan drivers, Wine & Emulation
# ============================================================================== # ==============================================================================
corectrl
gameconqueror gameconqueror
gamemode gamemode
gamescope gamescope
@@ -17,6 +18,8 @@ protontricks
radeontop radeontop
steam-installer steam-installer
steam-devices steam-devices
vkbasalt
vkbasalt:i386
vulkan-tools vulkan-tools
wine wine
winetricks winetricks
+10
View File
@@ -23,7 +23,17 @@ GRUB_TIMEOUT_STYLE="menu"
# Shell & Userland # Shell & Userland
DEFAULT_SHELL="zsh" DEFAULT_SHELL="zsh"
# Gaming: Persistent Shader Cache (Mesa & DXVK)
# Optional relocation of the shader/pipeline cache onto separate/bulk storage.
# Leave empty to keep Mesa's and DXVK's own default cache locations
# (~/.cache/mesa_shader_cache, ~/.cache/dxvk) - recommended unless you have a
# specific reason (small root disk, dedicated fast scratch drive, ...).
# Example: SHADER_CACHE_DIR="/mnt/Data/Software/mesa_shader_cache"
SHADER_CACHE_DIR=""
# Ollama AI Configuration # Ollama AI Configuration
# Suggested custom models path, pre-filled only if the user declines
# Ollama's own default storage location (/usr/share/ollama/.ollama/models).
OLLAMA_MODELS_DEFAULT="/mnt/Data/Software/ollama/models" OLLAMA_MODELS_DEFAULT="/mnt/Data/Software/ollama/models"
OLLAMA_FLASH_ATTENTION="1" OLLAMA_FLASH_ATTENTION="1"
OLLAMA_KV_CACHE_TYPE="q8_0" OLLAMA_KV_CACHE_TYPE="q8_0"
+1 -1
View File
@@ -1,5 +1,5 @@
Types: deb Types: deb
URIs: http://deb.xanmod.org URIs: http://deb.xanmod.org
Suites: releases Suites: sid
Components: main Components: main
Signed-By: /etc/apt/keyrings/xanmod-archive-keyring.gpg Signed-By: /etc/apt/keyrings/xanmod-archive-keyring.gpg
+34
View File
@@ -0,0 +1,34 @@
# ==============================================================================
# data/etc/gamemode.ini - GameMode performance daemon defaults
# ==============================================================================
# Deployed to /etc/gamemode.ini by stages/06-services.sh
# Full reference: /usr/share/gamemode/gamemode.ini
[general]
; Use the "performance" CPU governor while a game is running instead of
; "powersave"/"ondemand", restoring the previous governor once it exits.
desiredgov=performance
; Renice game processes for smoother frame pacing (0-20, applied as a negated
; nice value). Requires the target user to be a member of the "gamemode"
; group, which the setup framework adds automatically.
renice=5
; Lower I/O priority class while gaming (0 = highest Best-Effort priority).
ioprio=0
; Keep the screen from blanking/locking while a game has focus.
inhibit_screensaver=1
[gpu]
; Apply AMD/NVIDIA GPU performance-level optimisations while gaming.
apply_gpu_optimisations=accept-responsibility
gpu_device=0
; AMD: force the "high" DPM performance level during GameMode sessions
; (requires an up-to-date amdgpu kernel module).
amd_performance_level=high
; NVIDIA-specific PowerMizer tuning (nv_powermizer_mode, nv_core_clock_mhz_offset,
; nv_mem_clock_mhz_offset) requires the "coolbits" Xorg option and is not
; configured by default, since this setup targets AMD/Intel GPUs.
@@ -0,0 +1,10 @@
// Allow members of the "sudo" administrative group to start/stop CoreCtrl's
// privileged helper without an interactive PolicyKit password prompt.
// Deployed by stages/06-services.sh.
polkit.addRule(function(action, subject) {
if ((action.id == "org.corectrl.helper.init" ||
action.id == "org.corectrl.helperkiller.init") &&
subject.isInGroup("sudo")) {
return polkit.Result.YES;
}
});
@@ -0,0 +1,24 @@
[Unit]
Description=Ersteinrichtung: Dotfiles, Oh-My-Zsh, JetBrains Junie, Claude Code & Skills
# Systemweit installiert unter /etc/systemd/user/ und global aktiviert
# (siehe stages/10-hyprland.sh) - gilt fuer alle Benutzer, ohne dass etwas
# in einzelne Home-Verzeichnisse kopiert werden muss. Bewusst NICHT an
# Hyprland gebunden - laeuft ueber den systemd --user Manager, unabhaengig
# davon, welcher Compositor/welche Desktop-Sitzung gestartet wird.
After=default.target
StartLimitIntervalSec=300
StartLimitBurst=60
[Service]
Type=oneshot
ExecStart=/usr/local/lib/first-login-setup/first-login-setup.sh
# first-login-setup.sh beendet sich mit Exit-Code 1, solange die grafische
# Sitzung (Wayland-Socket) noch nicht bereit ist - in diesem Fall einfach
# erneut versuchen, bis sie verfuegbar ist. Das Skript selbst entscheidet
# anhand von ~/.local/state/first-login-setup/completed (pro Benutzer), ob
# es fuer den aktuellen Benutzer ueberhaupt noch etwas zu tun gibt.
Restart=on-failure
RestartSec=3
[Install]
WantedBy=default.target
+35
View File
@@ -0,0 +1,35 @@
### MangoHud default overlay configuration
### Deployed to ~/.config/MangoHud/MangoHud.conf via stages/07-skel-and-user.sh
### Full reference: https://github.com/flightlessmango/MangoHud/blob/master/data/MangoHud.conf
# Toggle the overlay on/off with Shift+F12, move it with Shift+F11
toggle_hud=Shift_L+F12
toggle_hud_position=Shift_L+F11
# Performance metrics
fps
frametime
frame_timing
cpu_stats
cpu_temp
cpu_power
gpu_stats
gpu_temp
gpu_power
gpu_core_clock
gpu_mem_clock
vram
ram
io_read
io_write
# GameMode / vkBasalt indicators
gamemode
vkbasalt
# Presentation
position=top-left
background_alpha=0.4
font_size=20
round_corners=6
table_columns=3
+7 -4
View File
@@ -90,8 +90,11 @@ input {
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Autostart nach der Anmeldung # Ersteinrichtung
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Fragt nach der ersten Anmeldung nach der gewünschten Dotfiles-Konfiguration # Dotfiles-Auswahl, Oh-My-Zsh, JetBrains Junie CLI & AwesomeJunieSkills starten
# (LinuxBeginnings Hyprland-Dots oder eigenes Git-Repository für ~/.config) # automatisch beim Login über den systemweiten systemd --user Service
exec-once = ~/.config/hypr/scripts/install-dots-prompt.sh # 'first-login-setup.service' (/etc/systemd/user/) - bewusst NICHT hier per
# exec-once, damit die Ersteinrichtung unabhängig vom gewählten Compositor
# funktioniert. Manueller Aufruf jederzeit möglich mit:
# /usr/local/lib/first-login-setup/first-login-setup.sh --force
@@ -1,357 +0,0 @@
#!/usr/bin/env bash
# ==============================================================================
# ~/.config/hypr/scripts/install-dots-prompt.sh
# Ersteinrichtung beim ersten Login:
# 1) Desktop-Konfiguration (LinuxBeginnings Hyprland-Dots oder eigenes Git-Repo)
# 2) Installation der JetBrains Junie CLI & AwesomeJunieSkills
# ==============================================================================
set -euo pipefail
MARKER_FILE="$HOME/.config/hypr/.dots_prompted"
FORCE=0
for arg in "$@"; do
case "$arg" in
-f|--force)
FORCE=1
;;
esac
done
# Wenn bereits abgefragt und kein --force übergeben wurde, beenden
if [[ "$FORCE" -eq 0 && -f "$MARKER_FILE" ]]; then
exit 0
fi
# Wenn nicht in einem interaktiven Terminal ausgeführt (z. B. via Hyprland exec-once),
# starte das Skript in einem Kitty-Fenster:
if [[ ! -t 0 || ! -t 1 ]]; then
if command -v kitty >/dev/null 2>&1; then
exec kitty --title "System-Ersteinrichtung & Dotfiles" bash -c "$0 --in-terminal; echo; read -r -p 'Drücke Enter zum Schließen...' || true"
else
exit 0
fi
fi
# ANSI Farben
CLR_RESET='\033[0m'
CLR_BOLD='\033[1m'
CLR_CYAN='\033[0;36m'
CLR_GREEN='\033[0;32m'
CLR_YELLOW='\033[0;33m'
CLR_RED='\033[0;31m'
CLR_BLUE='\033[0;34m'
CLR_MAGENTA='\033[0;35m'
mark_as_prompted() {
mkdir -p "$(dirname "$MARKER_FILE")" 2>/dev/null || true
touch "$MARKER_FILE" 2>/dev/null || true
}
install_oh_my_zsh() {
local zsh_dir="$HOME/.oh-my-zsh"
printf "\n"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}==============================================================================${CLR_RESET}"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN} Oh-My-Zsh & Shell-Konfiguration ${CLR_RESET}"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}==============================================================================${CLR_RESET}"
printf "\n"
if [[ ! -d "$zsh_dir" ]]; then
printf "%b\n" "${CLR_CYAN}[INFO] Richte Oh-My-Zsh für $USER ein...${CLR_RESET}"
if command -v git >/dev/null 2>&1; then
if git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "$zsh_dir"; then
mkdir -p "$zsh_dir/custom/plugins"
git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "$zsh_dir/custom/plugins/zsh-autosuggestions" 2>/dev/null || true
git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting "$zsh_dir/custom/plugins/zsh-syntax-highlighting" 2>/dev/null || true
printf "%b\n" "${CLR_GREEN}[✓] Oh-My-Zsh erfolgreich eingerichtet.${CLR_RESET}"
else
printf "%b\n" "${CLR_YELLOW}[WARNUNG] Oh-My-Zsh konnte nicht geklont werden.${CLR_RESET}"
fi
else
printf "%b\n" "${CLR_RED}[FEHLER] 'git' ist nicht installiert. Oh-My-Zsh konnte nicht eingerichtet werden.${CLR_RESET}"
fi
else
# Plugins sicherstellen
if [[ ! -d "$zsh_dir/custom/plugins/zsh-autosuggestions" ]] && command -v git >/dev/null 2>&1; then
git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "$zsh_dir/custom/plugins/zsh-autosuggestions" 2>/dev/null || true
fi
if [[ ! -d "$zsh_dir/custom/plugins/zsh-syntax-highlighting" ]] && command -v git >/dev/null 2>&1; then
git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting "$zsh_dir/custom/plugins/zsh-syntax-highlighting" 2>/dev/null || true
fi
printf "%b\n" "${CLR_GREEN}[✓] Oh-My-Zsh ist bereits vorhanden und konfiguriert.${CLR_RESET}"
fi
}
install_junie_cli_and_skills() {
printf "\n"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}==============================================================================${CLR_RESET}"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN} JetBrains Junie CLI & AwesomeJunieSkills Installation ${CLR_RESET}"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}==============================================================================${CLR_RESET}"
printf "\n"
# 1. JetBrains Junie CLI installieren
printf "%b\n" "${CLR_CYAN}[1/2] Installiere JetBrains Junie CLI...${CLR_RESET}"
if command -v curl >/dev/null 2>&1; then
if curl -fsSL https://junie.jetbrains.com/install.sh | bash; then
printf "%b\n" "${CLR_GREEN}[✓] JetBrains Junie CLI erfolgreich installiert.${CLR_RESET}"
else
printf "%b\n" "${CLR_YELLOW}[WARNUNG] Installation von JetBrains Junie CLI fehlgeschlagen oder unterbrochen.${CLR_RESET}"
fi
else
printf "%b\n" "${CLR_RED}[FEHLER] 'curl' ist nicht installiert. Junie CLI konnte nicht geladen werden.${CLR_RESET}"
fi
# 2. AwesomeJunieSkills im Benutzer-Skillverzeichnis (~/.junie/skills) klonen / aktualisieren
printf "\n"
printf "%b\n" "${CLR_CYAN}[2/2] Installiere AwesomeJunieSkills im Benutzer-Skillverzeichnis (~/.junie/skills)...${CLR_RESET}"
local skills_dir="$HOME/.junie/skills"
local repo_dir="$skills_dir/awesome-junie-skills"
mkdir -p "$skills_dir"
if command -v git >/dev/null 2>&1; then
if [[ -d "$repo_dir/.git" ]]; then
printf "%b\n" "${CLR_BLUE}[INFO] Repository existiert bereits in $repo_dir. Führe 'git pull' aus...${CLR_RESET}"
if git -C "$repo_dir" pull --ff-only 2>/dev/null; then
printf "%b\n" "${CLR_GREEN}[✓] AwesomeJunieSkills erfolgreich aktualisiert.${CLR_RESET}"
else
printf "%b\n" "${CLR_YELLOW}[WARNUNG] 'git pull' in $repo_dir fehlgeschlagen.${CLR_RESET}"
fi
else
printf "%b\n" "${CLR_BLUE}[INFO] Klone AwesomeJunieSkills nach $repo_dir...${CLR_RESET}"
rm -rf "$repo_dir" 2>/dev/null || true
if git clone https://github.com/alebaffa/awesome-junie-skills.git "$repo_dir"; then
printf "%b\n" "${CLR_GREEN}[✓] AwesomeJunieSkills erfolgreich nach $repo_dir geklont.${CLR_RESET}"
else
printf "%b\n" "${CLR_YELLOW}[WARNUNG] Klonen von AwesomeJunieSkills fehlgeschlagen.${CLR_RESET}"
fi
fi
# Symlinks für alle Skills im Skill-Hauptordner erstellen
if [[ -d "$repo_dir" ]]; then
local count=0
for skill_path in "$repo_dir"/*; do
if [[ -d "$skill_path" && -f "$skill_path/SKILL.md" ]]; then
local s_name
s_name="$(basename "$skill_path")"
ln -sfn "$skill_path" "$skills_dir/$s_name"
((count++)) || true
fi
done
printf "%b\n" "${CLR_GREEN}[✓] $count Skills verknüpft in '$skills_dir' (kann via 'git pull' in '$repo_dir' aktualisiert werden).${CLR_RESET}"
fi
# Junie config.json konfigurieren (skill-locations)
mkdir -p "$HOME/.junie"
local config_file="$HOME/.junie/config.json"
if command -v python3 >/dev/null 2>&1; then
python3 -c "
import json, os
cfg_path = os.path.expanduser('$config_file')
repo_path = os.path.expanduser('$repo_dir')
data = {}
if os.path.exists(cfg_path):
try:
with open(cfg_path, 'r', encoding='utf-8') as f:
data = json.load(f)
except Exception:
data = {}
locs = data.get('skill-locations', [])
if repo_path not in locs:
locs.append(repo_path)
data['skill-locations'] = locs
with open(cfg_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
" 2>/dev/null || true
fi
else
printf "%b\n" "${CLR_RED}[FEHLER] 'git' ist nicht installiert. AwesomeJunieSkills konnte nicht eingerichtet werden.${CLR_RESET}"
fi
}
install_linuxbeginnings() {
printf "\n"
printf "%b\n" "${CLR_GREEN}${CLR_BOLD}[INFO] Starte Installation der LinuxBeginnings Hyprland-Dotfiles...${CLR_RESET}"
printf "\n"
if command -v curl >/dev/null 2>&1; then
sh <(curl -fsSL https://raw.githubusercontent.com/LinuxBeginnings/Hyprland-Dots/main/Distro-Hyprland.sh)
else
printf "%b\n" "${CLR_RED}[FEHLER] 'curl' ist nicht installiert. Bitte installieren Sie curl und wiederholen Sie den Vorgang.${CLR_RESET}"
fi
}
clone_custom_repo() {
if ! command -v git >/dev/null 2>&1; then
printf "\n%b\n" "${CLR_RED}[FEHLER] 'git' ist nicht installiert. Bitte installieren Sie git zuerst.${CLR_RESET}"
return 1
fi
while true; do
printf "\n"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}--- Eigenes Git-Repository für ~/.config klonen ---${CLR_RESET}"
printf "%b\n" "Geben Sie die Git-URL Ihres Dotfiles-/Config-Repositories an."
printf "%b\n" "Beispiele:"
printf "%b\n" " HTTPS : ${CLR_BLUE}https://github.com/Benutzername/mein-config-repo.git${CLR_RESET}"
printf "%b\n" " SSH : ${CLR_BLUE}git@github.com:Benutzername/mein-config-repo.git${CLR_RESET}"
printf "\n"
printf "%b\n" "${CLR_YELLOW}Hinweise zur Authentifizierung:${CLR_RESET}"
printf "%b\n" " - ${CLR_BOLD}HTTPS:${CLR_RESET} Bei privaten Repositories fragt Git nach Benutzername & Personal Access Token (PAT)."
printf "%b\n" " - ${CLR_BOLD}SSH:${CLR_RESET} Erfordert einen hinterlegten SSH-Schlüssel (z. B. in ~/.ssh/ oder via ssh-agent)."
printf "\n"
read -r -p "Git-Repository-URL (oder 'q' zum Abbrechen): " repo_url
repo_url="$(echo "$repo_url" | xargs)"
if [[ "$repo_url" == "q" || "$repo_url" == "Q" || -z "$repo_url" ]]; then
printf "%b\n" "${CLR_YELLOW}[INFO] Vorgang abgebrochen. Kehre zum Hauptmenü zurück.${CLR_RESET}"
return 0
fi
# SSH-Prüfung und Hilfestellung
if [[ "$repo_url" =~ ^git@ || "$repo_url" =~ ^ssh:// ]]; then
local ssh_keys_found=0
if [[ -d "$HOME/.ssh" ]]; then
for k in "$HOME/.ssh"/id_*; do
if [[ -f "$k" && ! "$k" =~ \.pub$ ]]; then
ssh_keys_found=1
break
fi
done
fi
if [[ "$ssh_keys_found" -eq 0 ]]; then
printf "\n"
printf "%b\n" "${CLR_YELLOW}[WARNUNG] Es wurde kein lokaler SSH-Privatschlüssel in ~/.ssh/ gefunden.${CLR_RESET}"
read -r -p "Möchten Sie vor dem Klonen ein neues SSH-Schlüsselpaar (ed25519) generieren? [j/N]: " gen_ssh || gen_ssh="n"
if [[ "$gen_ssh" =~ ^[JjYy] ]]; then
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
ssh-keygen -t ed25519 -f "$HOME/.ssh/id_ed25519" -C "$USER@$(hostname)"
printf "\n%b\n" "${CLR_GREEN}Neuer öffentlicher SSH-Schlüssel (~/.ssh/id_ed25519.pub):${CLR_RESET}"
cat "$HOME/.ssh/id_ed25519.pub"
printf "\n%b\n" "Bitte fügen Sie diesen Schlüssel bei GitHub/GitLab zu Ihrem Account hinzu."
if command -v wl-copy >/dev/null 2>&1; then
if wl-copy < "$HOME/.ssh/id_ed25519.pub" 2>/dev/null; then
printf "%b\n" "${CLR_GREEN}(In die Zwischenablage kopiert!)${CLR_RESET}"
fi
fi
read -r -p "Drücken Sie Enter, sobald der SSH-Key hinterlegt wurde..."
fi
fi
fi
# Backup und Klon-Vorgang vorbereiten
local timestamp
timestamp="$(date +%Y%m%d_%H%M%S)"
local backup_dir="$HOME/.config.backup_$timestamp"
printf "\n"
printf "%b\n" "${CLR_CYAN}[1/3] Sichere bestehendes ~/.config-Verzeichnis nach ${backup_dir}...${CLR_RESET}"
if [[ -d "$HOME/.config" ]]; then
mv "$HOME/.config" "$backup_dir"
fi
printf "%b\n" "${CLR_CYAN}[2/3] Klone Repository direkt nach ~/.config...${CLR_RESET}"
# Git ausführen mit aktivierter Terminal-Eingabeaufforderung für Authentifizierung
if env GIT_TERMINAL_PROMPT=1 git clone --recurse-submodules "$repo_url" "$HOME/.config"; then
printf "\n"
printf "%b\n" "${CLR_GREEN}${CLR_BOLD}[ERFOLG] Repository wurde erfolgreich nach ~/.config geklont!${CLR_RESET}"
printf "%b\n" " - Ihr gesamter ${CLR_BOLD}~/.config${CLR_RESET}-Ordner ist nun das Git-Repository."
printf "%b\n" " - Vorherige Konfigurationen wurden gesichert in: ${CLR_BOLD}${backup_dir}${CLR_RESET}"
# Hyprland-Konfiguration neu laden falls aktiv
if command -v hyprctl >/dev/null 2>&1; then
printf "\n"
printf "%b\n" "${CLR_CYAN}[3/3] Lade Hyprland-Konfiguration neu...${CLR_RESET}"
hyprctl reload 2>/dev/null || true
printf "%b\n" "${CLR_GREEN}[✓] Hyprland erfolgreich aktualisiert.${CLR_RESET}"
fi
printf "\n"
printf "%b\n" "${CLR_GREEN}Dotfiles-Einrichtung abgeschlossen!${CLR_RESET}"
return 0
else
printf "\n"
printf "%b\n" "${CLR_RED}${CLR_BOLD}[FEHLER] Git Clone ist fehlgeschlagen (Authentifizierungsfehler, falsche URL oder Netzwerkproblem).${CLR_RESET}"
printf "%b\n" "${CLR_YELLOW}[ROLLBACK] Stelle vorheriges ~/.config-Verzeichnis aus dem Backup wieder her...${CLR_RESET}"
rm -rf "$HOME/.config" 2>/dev/null || true
if [[ -d "$backup_dir" ]]; then
mv "$backup_dir" "$HOME/.config"
printf "%b\n" "${CLR_GREEN}[✓] Ursprüngliches ~/.config-Verzeichnis wurde vollständig wiederhergestellt.${CLR_RESET}"
fi
printf "\n"
read -r -p "Möchten Sie es erneut versuchen? [J/n]: " retry || retry="n"
if [[ ! "$retry" =~ ^[JjYy] && -n "$retry" ]]; then
return 0
fi
fi
done
}
clear 2>/dev/null || true
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}==============================================================================${CLR_RESET}"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN} Hyprland Desktop - Dotfiles Setup & Konfiguration ${CLR_RESET}"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}==============================================================================${CLR_RESET}"
printf "\n"
printf "%b\n" "Willkommen auf Ihrem neuen Debian Unstable Hyprland-System!"
printf "\n"
printf "%b\n" "Aktuell ist eine rudimentäre Basiskonfiguration aktiv:"
printf "%b\n" " - ${CLR_BOLD}SUPER + ENTER${CLR_RESET} : Terminal (Kitty) öffnen"
printf "%b\n" " - ${CLR_BOLD}SUPER + Q${CLR_RESET} : Aktives Fenster schließen"
printf "\n"
printf "%b\n" "Wie möchten Sie Ihre Desktop-Konfiguration (Dotfiles) einrichten?"
printf "\n"
printf "%b\n" " ${CLR_BOLD}[1]${CLR_RESET} ${CLR_GREEN}LinuxBeginnings Hyprland-Dotfiles installieren${CLR_RESET}"
printf "%b\n" " Vollständig vorkonfiguriertes Setup (Waybar, Rofi, Animationen, Themes)"
printf "%b\n" " URL: https://github.com/LinuxBeginnings/Hyprland-Dots"
printf "\n"
printf "%b\n" " ${CLR_BOLD}[2]${CLR_RESET} ${CLR_MAGENTA}Eigenes Git-Repository als ~/.config klonen${CLR_RESET}"
printf "%b\n" " Klont Ihr eigenes Git-Repository direkt als vollständigen ~/.config-Ordner."
printf "%b\n" " (Unterstützt HTTPS & SSH inkl. Authentifizierung)"
printf "\n"
printf "%b\n" " ${CLR_BOLD}[3]${CLR_RESET} ${CLR_YELLOW}Überspringen / Basiskonfiguration beibehalten${CLR_RESET}"
printf "%b\n" " Keine Änderungen vornehmen (kann später manuell eingerichtet werden)."
printf "\n"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}------------------------------------------------------------------------------${CLR_RESET}"
while true; do
read -r -p "Bitte wählen Sie eine Option [1-3] (Standard: 1): " choice || choice="1"
choice="$(echo "$choice" | xargs)"
[[ -z "$choice" ]] && choice="1"
case "$choice" in
1)
install_linuxbeginnings
break
;;
2)
clone_custom_repo
break
;;
3)
printf "\n"
printf "%b\n" "${CLR_YELLOW}[INFO] Dotfiles-Einrichtung übersprungen. Die Basiskonfiguration bleibt aktiv.${CLR_RESET}"
break
;;
*)
printf "%b\n" "${CLR_RED}Ungültige Eingabe. Bitte 1, 2 oder 3 wählen.${CLR_RESET}"
;;
esac
done
# Oh-My-Zsh & Plugins einrichten
install_oh_my_zsh
# JetBrains Junie CLI & AwesomeJunieSkills installieren
install_junie_cli_and_skills
# Marker-Datei setzen
mark_as_prompted
printf "\n"
printf "%b\n" "${CLR_GREEN}${CLR_BOLD}[✓] Ersteinrichtung vollständig abgeschlossen!${CLR_RESET}"
printf "%b\n" "Sie können dieses Menü jederzeit erneut aufrufen mit:"
printf "%b\n" " ${CLR_BOLD}~/.config/hypr/scripts/install-dots-prompt.sh --force${CLR_RESET}"
+18
View File
@@ -0,0 +1,18 @@
### vkBasalt default post-processing configuration
### Deployed to ~/.config/vkBasalt/vkBasalt.conf via stages/07-skel-and-user.sh
### Full reference: https://github.com/DadSchoorse/vkBasalt
###
### Only active for games launched with ENABLE_VKBASALT=1, e.g. as a Steam
### launch option: ENABLE_VKBASALT=1 %command%
# Effect chain to apply, in order
effects = cas
# Contrast Adaptive Sharpening strength (0.0 - 1.0)
casSharpness = 0.4
# Apply the effect chain immediately without needing to press toggleKey first
enableOnLaunch = True
# Key to toggle all effects on/off at runtime
toggleKey = Home
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# ==============================================================================
# /usr/local/lib/first-login-setup/_lib.sh
# Gemeinsame Farbcodes & Ausgabe-Helfer für die first-login-setup.sh Skripte.
# Nicht zur direkten Ausführung gedacht - wird von den anderen Skripten source't.
# ==============================================================================
CLR_RESET='\033[0m'
CLR_BOLD='\033[1m'
CLR_CYAN='\033[0;36m'
CLR_GREEN='\033[0;32m'
CLR_YELLOW='\033[0;33m'
CLR_RED='\033[0;31m'
CLR_BLUE='\033[0;34m'
CLR_MAGENTA='\033[0;35m'
print_header() {
printf "\n"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}==============================================================================${CLR_RESET}"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}$1${CLR_RESET}"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}==============================================================================${CLR_RESET}"
printf "\n"
}
print_info() {
printf "%b\n" "${CLR_CYAN}[INFO] $*${CLR_RESET}"
}
print_success() {
printf "%b\n" "${CLR_GREEN}[✓] $*${CLR_RESET}"
}
print_warn() {
printf "%b\n" "${CLR_YELLOW}[WARNUNG] $*${CLR_RESET}"
}
print_error() {
printf "%b\n" "${CLR_RED}[FEHLER] $*${CLR_RESET}"
}
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
# ==============================================================================
# /usr/local/lib/first-login-setup/first-login-setup.sh
# Ersteinrichtung nach der ersten Anmeldung. Führt der Reihe nach aus:
# 1) setup-dotfiles.sh - Desktop-Konfiguration (LinuxBeginnings / eigenes Repo)
# 2) setup-shell.sh - Oh-My-Zsh & Plugins
# 3) setup-junie-cli.sh - JetBrains Junie CLI
# 4) setup-junie-skills.sh - AwesomeJunieSkills
# 5) setup-claude-cli.sh - Claude Code (Anthropic)
# 6) setup-claude-skills.sh - Awesome LLM Skills
#
# Wird automatisch beim Login über den systemweiten systemd --user Service
# 'first-login-setup.service' (/etc/systemd/user/) gestartet - bewusst NICHT
# über hyprland.conf, damit die Ersteinrichtung unabhängig vom gewählten
# Compositor funktioniert. Kann jederzeit auch manuell aufgerufen werden.
# Jedes der obigen Skripte kann auch einzeln & unabhängig ausgeführt werden.
#
# Liegt system- statt benutzerweit unter /usr/local/lib (eine gemeinsame
# Kopie für alle Benutzer, statt in jedes Home dupliziert zu werden), und
# NICHT unter ~/.config: setup-dotfiles.sh kann ~/.config komplett durch ein
# eigenes Dotfiles-Repository ersetzen - das darf weder dieses Skript noch
# den Fortschritt (siehe MARKER_FILE) mit sich reißen.
#
# Läuft wirklich nur bei der allerersten Anmeldung eines Benutzers, oder
# erneut, wenn zuvor explizit "Später" gewählt wurde (siehe unten) - danach
# nie wieder, sobald MARKER_FILE gesetzt ist.
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib.sh
source "$SCRIPT_DIR/_lib.sh"
MARKER_FILE="$HOME/.local/state/first-login-setup/completed"
FORCE=0
for arg in "$@"; do
case "$arg" in
-f|--force)
FORCE=1
;;
esac
done
# Wenn bereits abgeschlossen und kein --force übergeben wurde, beenden.
# (Ein zuvor gewähltes "Später" setzt MARKER_FILE bewusst NICHT, siehe unten -
# das Skript fragt in diesem Fall beim nächsten Login erneut.)
if [[ "$FORCE" -eq 0 && -f "$MARKER_FILE" ]]; then
exit 0
fi
# Wenn nicht in einem interaktiven Terminal ausgeführt (z. B. via systemd
# --user Service oder Keybind ohne Terminal), starte das Skript in einem
# Kitty-Fenster:
if [[ ! -t 0 || ! -t 1 ]]; then
# Beim Start über systemd --user ist WAYLAND_DISPLAY/DISPLAY meist nicht
# gesetzt, da der Dienst unabhängig vom Compositor läuft. Das
# Wayland-Socket direkt suchen, statt uns auf eine importierte Umgebung
# zu verlassen.
if [[ -z "${WAYLAND_DISPLAY:-}" && -z "${DISPLAY:-}" && -n "${XDG_RUNTIME_DIR:-}" ]]; then
wl_socket="$(find "$XDG_RUNTIME_DIR" -maxdepth 1 -name 'wayland-*' ! -name '*.lock' 2>/dev/null | head -n1)"
if [[ -n "$wl_socket" ]]; then
export WAYLAND_DISPLAY
WAYLAND_DISPLAY="$(basename "$wl_socket")"
fi
fi
if [[ -z "${WAYLAND_DISPLAY:-}" && -z "${DISPLAY:-}" ]]; then
# Grafische Sitzung ist noch nicht bereit (z. B. Compositor startet
# gerade erst). Mit Exit-Code 1 beenden, damit systemd den Dienst
# gemäß 'Restart=on-failure' erneut versucht.
exit 1
fi
if command -v kitty >/dev/null 2>&1; then
exec kitty --title "System-Ersteinrichtung & Dotfiles" bash -c "$0 --in-terminal; echo; read -r -p 'Drücke Enter zum Schließen...' || true"
else
exit 0
fi
fi
mark_as_prompted() {
mkdir -p "$(dirname "$MARKER_FILE")" 2>/dev/null || true
touch "$MARKER_FILE" 2>/dev/null || true
}
clear 2>/dev/null || true
print_header " Hyprland Desktop - Ersteinrichtung "
printf "%b\n" "Willkommen auf Ihrem neuen Debian Unstable Hyprland-System!"
printf "\n"
printf "%b\n" "Aktuell ist eine rudimentäre Basiskonfiguration aktiv:"
printf "%b\n" " - ${CLR_BOLD}SUPER + ENTER${CLR_RESET} : Terminal (Kitty) öffnen"
printf "%b\n" " - ${CLR_BOLD}SUPER + Q${CLR_RESET} : Aktives Fenster schließen"
printf "\n"
printf "%b\n" "Folgende Schritte können nacheinander ausgeführt werden:"
printf "%b\n" " 1) Desktop-Konfiguration (Dotfiles)"
printf "%b\n" " 2) Oh-My-Zsh & Shell-Plugins"
printf "%b\n" " 3) JetBrains Junie CLI"
printf "%b\n" " 4) AwesomeJunieSkills"
printf "%b\n" " 5) Claude Code"
printf "%b\n" " 6) Awesome LLM Skills"
printf "\n"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}------------------------------------------------------------------------------${CLR_RESET}"
# Explizite Ja/Später-Abfrage: Nur bei "Jetzt" wird MARKER_FILE gesetzt.
# Bei "Später" bricht das Skript ohne Marker ab, sodass es beim nächsten
# Login erneut über first-login-setup.service gestartet wird und wieder fragt.
answer=""
read -r -p "Ersteinrichtung jetzt durchführen? [J]etzt / [S]päter (beim nächsten Login erneut fragen): " answer || answer="j"
answer="$(echo "$answer" | xargs)"
if [[ "$answer" =~ ^[Ss] ]]; then
printf "\n"
print_warn "Ersteinrichtung verschoben. Sie werden beim nächsten Login erneut gefragt."
printf "%b\n" "Manueller Aufruf jederzeit möglich mit: ${CLR_BOLD}/usr/local/lib/first-login-setup/first-login-setup.sh${CLR_RESET}"
exit 0
fi
bash "$SCRIPT_DIR/setup-dotfiles.sh"
bash "$SCRIPT_DIR/setup-shell.sh"
bash "$SCRIPT_DIR/setup-junie-cli.sh"
bash "$SCRIPT_DIR/setup-junie-skills.sh"
bash "$SCRIPT_DIR/setup-claude-cli.sh"
bash "$SCRIPT_DIR/setup-claude-skills.sh"
mark_as_prompted
printf "\n"
print_success "Ersteinrichtung vollständig abgeschlossen!"
printf "%b\n" "Sie können dieses Menü jederzeit erneut aufrufen mit:"
printf "%b\n" " ${CLR_BOLD}/usr/local/lib/first-login-setup/first-login-setup.sh --force${CLR_RESET}"
printf "%b\n" "Oder einzelne Schritte direkt erneut ausführen, z. B.:"
printf "%b\n" " ${CLR_BOLD}/usr/local/lib/first-login-setup/setup-junie-skills.sh${CLR_RESET}"
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# ==============================================================================
# /usr/local/lib/first-login-setup/setup-claude-cli.sh
# Installiert Claude Code (Anthropic) über den offiziellen nativen Installer.
#
# Kann eigenständig ausgeführt werden, unabhängig von first-login-setup.sh.
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib.sh
source "$SCRIPT_DIR/_lib.sh"
install_claude_cli() {
print_header " Claude Code Installation "
if command -v curl >/dev/null 2>&1; then
if curl -fsSL https://claude.ai/install.sh | bash; then
print_success "Claude Code erfolgreich installiert."
else
print_warn "Installation von Claude Code fehlgeschlagen oder unterbrochen."
fi
else
print_error "'curl' ist nicht installiert. Claude Code konnte nicht geladen werden."
fi
}
# Nur ausführen, wenn das Skript direkt gestartet wird (nicht bei source)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
install_claude_cli
fi
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# ==============================================================================
# /usr/local/lib/first-login-setup/setup-claude-skills.sh
# Klont/aktualisiert Awesome LLM Skills (https://github.com/Prat011/awesome-llm-skills)
# in ~/.claude/skills und verknüpft alle gefundenen Skills, damit Claude Code
# sie automatisch erkennt.
#
# Kann eigenständig ausgeführt werden, unabhängig von first-login-setup.sh.
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib.sh
source "$SCRIPT_DIR/_lib.sh"
install_claude_skills() {
print_header " Awesome LLM Skills Installation "
local skills_dir="$HOME/.claude/skills"
local repo_dir="$skills_dir/awesome-llm-skills"
mkdir -p "$skills_dir"
if ! command -v git >/dev/null 2>&1; then
print_error "'git' ist nicht installiert. Awesome LLM Skills konnte nicht eingerichtet werden."
return 1
fi
if [[ -d "$repo_dir/.git" ]]; then
print_info "Repository existiert bereits in $repo_dir. Führe 'git pull' aus..."
if git -C "$repo_dir" pull --ff-only 2>/dev/null; then
print_success "Awesome LLM Skills erfolgreich aktualisiert."
else
print_warn "'git pull' in $repo_dir fehlgeschlagen."
fi
else
print_info "Klone Awesome LLM Skills nach $repo_dir..."
rm -rf "$repo_dir" 2>/dev/null || true
if git clone https://github.com/Prat011/awesome-llm-skills.git "$repo_dir"; then
print_success "Awesome LLM Skills erfolgreich nach $repo_dir geklont."
else
print_warn "Klonen von Awesome LLM Skills fehlgeschlagen."
fi
fi
# Symlinks für alle Skills im Skill-Hauptordner erstellen
if [[ -d "$repo_dir" ]]; then
local count=0
for skill_path in "$repo_dir"/*; do
if [[ -d "$skill_path" && -f "$skill_path/SKILL.md" ]]; then
local s_name
s_name="$(basename "$skill_path")"
ln -sfn "$skill_path" "$skills_dir/$s_name"
((count++)) || true
fi
done
print_success "$count Skills verknüpft in '$skills_dir' (kann via 'git pull' in '$repo_dir' aktualisiert werden)."
fi
}
# Nur ausführen, wenn das Skript direkt gestartet wird (nicht bei source)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
install_claude_skills
fi
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
# ==============================================================================
# /usr/local/lib/first-login-setup/setup-dotfiles.sh
# Interaktive Auswahl & Einrichtung der Desktop-Konfiguration (Dotfiles):
# LinuxBeginnings Hyprland-Dots, eigenes Git-Repository, oder überspringen.
#
# Kann eigenständig ausgeführt werden, unabhängig von first-login-setup.sh.
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib.sh
source "$SCRIPT_DIR/_lib.sh"
install_linuxbeginnings() {
printf "\n"
printf "%b\n" "${CLR_GREEN}${CLR_BOLD}[INFO] Starte Installation der LinuxBeginnings Hyprland-Dotfiles...${CLR_RESET}"
printf "\n"
if command -v curl >/dev/null 2>&1; then
sh <(curl -fsSL https://raw.githubusercontent.com/LinuxBeginnings/Hyprland-Dots/main/Distro-Hyprland.sh)
else
print_error "'curl' ist nicht installiert. Bitte installieren Sie curl und wiederholen Sie den Vorgang."
fi
}
clone_custom_repo() {
if ! command -v git >/dev/null 2>&1; then
printf "\n"
print_error "'git' ist nicht installiert. Bitte installieren Sie git zuerst."
return 1
fi
while true; do
printf "\n"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}--- Eigenes Git-Repository für ~/.config klonen ---${CLR_RESET}"
printf "%b\n" "Geben Sie die Git-URL Ihres Dotfiles-/Config-Repositories an."
printf "%b\n" "Beispiele:"
printf "%b\n" " HTTPS : ${CLR_BLUE}https://github.com/Benutzername/mein-config-repo.git${CLR_RESET}"
printf "%b\n" " SSH : ${CLR_BLUE}git@github.com:Benutzername/mein-config-repo.git${CLR_RESET}"
printf "\n"
printf "%b\n" "${CLR_YELLOW}Hinweise zur Authentifizierung:${CLR_RESET}"
printf "%b\n" " - ${CLR_BOLD}HTTPS:${CLR_RESET} Bei privaten Repositories fragt Git nach Benutzername & Personal Access Token (PAT)."
printf "%b\n" " - ${CLR_BOLD}SSH:${CLR_RESET} Erfordert einen hinterlegten SSH-Schlüssel (z. B. in ~/.ssh/ oder via ssh-agent)."
printf "\n"
read -r -p "Git-Repository-URL (oder 'q' zum Abbrechen): " repo_url
repo_url="$(echo "$repo_url" | xargs)"
if [[ "$repo_url" == "q" || "$repo_url" == "Q" || -z "$repo_url" ]]; then
print_warn "Vorgang abgebrochen. Kehre zum Hauptmenü zurück."
return 0
fi
# SSH-Prüfung und Hilfestellung
if [[ "$repo_url" =~ ^git@ || "$repo_url" =~ ^ssh:// ]]; then
local ssh_keys_found=0
if [[ -d "$HOME/.ssh" ]]; then
for k in "$HOME/.ssh"/id_*; do
if [[ -f "$k" && ! "$k" =~ \.pub$ ]]; then
ssh_keys_found=1
break
fi
done
fi
if [[ "$ssh_keys_found" -eq 0 ]]; then
printf "\n"
print_warn "Es wurde kein lokaler SSH-Privatschlüssel in ~/.ssh/ gefunden."
read -r -p "Möchten Sie vor dem Klonen ein neues SSH-Schlüsselpaar (ed25519) generieren? [j/N]: " gen_ssh || gen_ssh="n"
if [[ "$gen_ssh" =~ ^[JjYy] ]]; then
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
ssh-keygen -t ed25519 -f "$HOME/.ssh/id_ed25519" -C "$USER@$(hostname)"
printf "\n%b\n" "${CLR_GREEN}Neuer öffentlicher SSH-Schlüssel (~/.ssh/id_ed25519.pub):${CLR_RESET}"
cat "$HOME/.ssh/id_ed25519.pub"
printf "\n%b\n" "Bitte fügen Sie diesen Schlüssel bei GitHub/GitLab zu Ihrem Account hinzu."
if command -v wl-copy >/dev/null 2>&1; then
if wl-copy < "$HOME/.ssh/id_ed25519.pub" 2>/dev/null; then
print_success "(In die Zwischenablage kopiert!)"
fi
fi
read -r -p "Drücken Sie Enter, sobald der SSH-Key hinterlegt wurde..."
fi
fi
fi
# Backup und Klon-Vorgang vorbereiten
local timestamp
timestamp="$(date +%Y%m%d_%H%M%S)"
local backup_dir="$HOME/.config.backup_$timestamp"
printf "\n"
printf "%b\n" "${CLR_CYAN}[1/3] Sichere bestehendes ~/.config-Verzeichnis nach ${backup_dir}...${CLR_RESET}"
if [[ -d "$HOME/.config" ]]; then
mv "$HOME/.config" "$backup_dir"
fi
printf "%b\n" "${CLR_CYAN}[2/3] Klone Repository direkt nach ~/.config...${CLR_RESET}"
# Git ausführen mit aktivierter Terminal-Eingabeaufforderung für Authentifizierung
if env GIT_TERMINAL_PROMPT=1 git clone --recurse-submodules "$repo_url" "$HOME/.config"; then
printf "\n"
printf "%b\n" "${CLR_GREEN}${CLR_BOLD}[ERFOLG] Repository wurde erfolgreich nach ~/.config geklont!${CLR_RESET}"
printf "%b\n" " - Ihr gesamter ${CLR_BOLD}~/.config${CLR_RESET}-Ordner ist nun das Git-Repository."
printf "%b\n" " - Vorherige Konfigurationen wurden gesichert in: ${CLR_BOLD}${backup_dir}${CLR_RESET}"
# Hyprland-Konfiguration neu laden falls aktiv
if command -v hyprctl >/dev/null 2>&1; then
printf "\n"
printf "%b\n" "${CLR_CYAN}[3/3] Lade Hyprland-Konfiguration neu...${CLR_RESET}"
hyprctl reload 2>/dev/null || true
print_success "Hyprland erfolgreich aktualisiert."
fi
printf "\n"
print_success "Dotfiles-Einrichtung abgeschlossen!"
return 0
else
printf "\n"
print_error "Git Clone ist fehlgeschlagen (Authentifizierungsfehler, falsche URL oder Netzwerkproblem)."
print_warn "[ROLLBACK] Stelle vorheriges ~/.config-Verzeichnis aus dem Backup wieder her..."
rm -rf "$HOME/.config" 2>/dev/null || true
if [[ -d "$backup_dir" ]]; then
mv "$backup_dir" "$HOME/.config"
print_success "Ursprüngliches ~/.config-Verzeichnis wurde vollständig wiederhergestellt."
fi
printf "\n"
read -r -p "Möchten Sie es erneut versuchen? [J/n]: " retry || retry="n"
if [[ ! "$retry" =~ ^[JjYy] && -n "$retry" ]]; then
return 0
fi
fi
done
}
run_dotfiles_menu() {
print_header " Hyprland Desktop - Dotfiles Setup & Konfiguration "
printf "%b\n" "Wie möchten Sie Ihre Desktop-Konfiguration (Dotfiles) einrichten?"
printf "\n"
printf "%b\n" " ${CLR_BOLD}[1]${CLR_RESET} ${CLR_GREEN}LinuxBeginnings Hyprland-Dotfiles installieren${CLR_RESET}"
printf "%b\n" " Vollständig vorkonfiguriertes Setup (Waybar, Rofi, Animationen, Themes)"
printf "%b\n" " URL: https://github.com/LinuxBeginnings/Hyprland-Dots"
printf "\n"
printf "%b\n" " ${CLR_BOLD}[2]${CLR_RESET} ${CLR_MAGENTA}Eigenes Git-Repository als ~/.config klonen${CLR_RESET}"
printf "%b\n" " Klont Ihr eigenes Git-Repository direkt als vollständigen ~/.config-Ordner."
printf "%b\n" " (Unterstützt HTTPS & SSH inkl. Authentifizierung)"
printf "\n"
printf "%b\n" " ${CLR_BOLD}[3]${CLR_RESET} ${CLR_YELLOW}Überspringen / Basiskonfiguration beibehalten${CLR_RESET}"
printf "%b\n" " Keine Änderungen vornehmen (kann später manuell eingerichtet werden)."
printf "\n"
printf "%b\n" "${CLR_BOLD}${CLR_CYAN}------------------------------------------------------------------------------${CLR_RESET}"
while true; do
read -r -p "Bitte wählen Sie eine Option [1-3] (Standard: 1): " choice || choice="1"
choice="$(echo "$choice" | xargs)"
[[ -z "$choice" ]] && choice="1"
case "$choice" in
1)
install_linuxbeginnings
break
;;
2)
clone_custom_repo
break
;;
3)
printf "\n"
print_warn "Dotfiles-Einrichtung übersprungen. Die Basiskonfiguration bleibt aktiv."
break
;;
*)
print_error "Ungültige Eingabe. Bitte 1, 2 oder 3 wählen."
;;
esac
done
}
# Nur ausführen, wenn das Skript direkt gestartet wird (nicht bei source)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
run_dotfiles_menu
fi
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# ==============================================================================
# /usr/local/lib/first-login-setup/setup-junie-cli.sh
# Installiert die JetBrains Junie CLI.
#
# Kann eigenständig ausgeführt werden, unabhängig von first-login-setup.sh.
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib.sh
source "$SCRIPT_DIR/_lib.sh"
install_junie_cli() {
print_header " JetBrains Junie CLI Installation "
if command -v curl >/dev/null 2>&1; then
if curl -fsSL https://junie.jetbrains.com/install.sh | bash; then
print_success "JetBrains Junie CLI erfolgreich installiert."
else
print_warn "Installation von JetBrains Junie CLI fehlgeschlagen oder unterbrochen."
fi
else
print_error "'curl' ist nicht installiert. Junie CLI konnte nicht geladen werden."
fi
}
# Nur ausführen, wenn das Skript direkt gestartet wird (nicht bei source)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
install_junie_cli
fi
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# ==============================================================================
# /usr/local/lib/first-login-setup/setup-junie-skills.sh
# Klont/aktualisiert AwesomeJunieSkills in ~/.junie/skills, verknüpft alle
# gefundenen Skills und trägt das Repository in ~/.junie/config.json ein.
#
# Kann eigenständig ausgeführt werden, unabhängig von first-login-setup.sh.
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib.sh
source "$SCRIPT_DIR/_lib.sh"
install_junie_skills() {
print_header " AwesomeJunieSkills Installation "
local skills_dir="$HOME/.junie/skills"
local repo_dir="$skills_dir/awesome-junie-skills"
mkdir -p "$skills_dir"
if ! command -v git >/dev/null 2>&1; then
print_error "'git' ist nicht installiert. AwesomeJunieSkills konnte nicht eingerichtet werden."
return 1
fi
if [[ -d "$repo_dir/.git" ]]; then
print_info "Repository existiert bereits in $repo_dir. Führe 'git pull' aus..."
if git -C "$repo_dir" pull --ff-only 2>/dev/null; then
print_success "AwesomeJunieSkills erfolgreich aktualisiert."
else
print_warn "'git pull' in $repo_dir fehlgeschlagen."
fi
else
print_info "Klone AwesomeJunieSkills nach $repo_dir..."
rm -rf "$repo_dir" 2>/dev/null || true
if git clone https://github.com/alebaffa/awesome-junie-skills.git "$repo_dir"; then
print_success "AwesomeJunieSkills erfolgreich nach $repo_dir geklont."
else
print_warn "Klonen von AwesomeJunieSkills fehlgeschlagen."
fi
fi
# Symlinks für alle Skills im Skill-Hauptordner erstellen
if [[ -d "$repo_dir" ]]; then
local count=0
for skill_path in "$repo_dir"/*; do
if [[ -d "$skill_path" && -f "$skill_path/SKILL.md" ]]; then
local s_name
s_name="$(basename "$skill_path")"
ln -sfn "$skill_path" "$skills_dir/$s_name"
((count++)) || true
fi
done
print_success "$count Skills verknüpft in '$skills_dir' (kann via 'git pull' in '$repo_dir' aktualisiert werden)."
fi
# Junie config.json konfigurieren (skill-locations)
mkdir -p "$HOME/.junie"
local config_file="$HOME/.junie/config.json"
if command -v python3 >/dev/null 2>&1; then
python3 -c "
import json, os
cfg_path = os.path.expanduser('$config_file')
repo_path = os.path.expanduser('$repo_dir')
data = {}
if os.path.exists(cfg_path):
try:
with open(cfg_path, 'r', encoding='utf-8') as f:
data = json.load(f)
except Exception:
data = {}
locs = data.get('skill-locations', [])
if repo_path not in locs:
locs.append(repo_path)
data['skill-locations'] = locs
with open(cfg_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
" 2>/dev/null || true
fi
}
# Nur ausführen, wenn das Skript direkt gestartet wird (nicht bei source)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
install_junie_skills
fi
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# ==============================================================================
# /usr/local/lib/first-login-setup/setup-shell.sh
# Richtet Oh-My-Zsh samt nützlicher Plugins (autosuggestions, syntax-highlighting)
# für den aktuellen Benutzer ein.
#
# Kann eigenständig ausgeführt werden, unabhängig von first-login-setup.sh.
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib.sh
source "$SCRIPT_DIR/_lib.sh"
install_oh_my_zsh() {
local zsh_dir="$HOME/.oh-my-zsh"
print_header " Oh-My-Zsh & Shell-Konfiguration "
if [[ ! -d "$zsh_dir" ]]; then
print_info "Richte Oh-My-Zsh für $USER ein..."
if command -v git >/dev/null 2>&1; then
if git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "$zsh_dir"; then
mkdir -p "$zsh_dir/custom/plugins"
git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "$zsh_dir/custom/plugins/zsh-autosuggestions" 2>/dev/null || true
git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting "$zsh_dir/custom/plugins/zsh-syntax-highlighting" 2>/dev/null || true
print_success "Oh-My-Zsh erfolgreich eingerichtet."
else
print_warn "Oh-My-Zsh konnte nicht geklont werden."
fi
else
print_error "'git' ist nicht installiert. Oh-My-Zsh konnte nicht eingerichtet werden."
fi
else
# Plugins sicherstellen
if [[ ! -d "$zsh_dir/custom/plugins/zsh-autosuggestions" ]] && command -v git >/dev/null 2>&1; then
git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "$zsh_dir/custom/plugins/zsh-autosuggestions" 2>/dev/null || true
fi
if [[ ! -d "$zsh_dir/custom/plugins/zsh-syntax-highlighting" ]] && command -v git >/dev/null 2>&1; then
git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting "$zsh_dir/custom/plugins/zsh-syntax-highlighting" 2>/dev/null || true
fi
print_success "Oh-My-Zsh ist bereits vorhanden und konfiguriert."
fi
}
# Nur ausführen, wenn das Skript direkt gestartet wird (nicht bei source)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
install_oh_my_zsh
fi
+49 -42
View File
@@ -47,18 +47,22 @@ log_step() {
printf "\n${CLR_BOLD}${CLR_CYAN}==>${CLR_RESET} ${CLR_BOLD}%s${CLR_RESET}\n" "$*" printf "\n${CLR_BOLD}${CLR_CYAN}==>${CLR_RESET} ${CLR_BOLD}%s${CLR_RESET}\n" "$*"
} }
# If stdin is a pipe (e.g. curl ... | bash), reconnect to /dev/tty if available for interactive prompts & TUI TMP_DIR=""
if [[ ! -t 0 ]]; then
if (exec < /dev/tty) 2>/dev/null; then
exec < /dev/tty
fi
fi
REPO_URL="${REPO_URL:-https://gitea.creative-dragonslayer.de/Scripts/Setup.git}" cleanup() {
REPO_BRANCH="${REPO_BRANCH:-}" if [[ -n "${TMP_DIR:-}" && -d "$TMP_DIR" ]]; then
log_info "Cleaning up temporary installation files..."
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT INT TERM
main() {
local repo_url="${REPO_URL:-https://gitea.creative-dragonslayer.de/Scripts/Setup.git}"
local repo_branch="${REPO_BRANCH:-dev}"
log_step "Debian Unstable & Hyprland Setup Framework Installer" log_step "Debian Unstable & Hyprland Setup Framework Installer"
log_info "Repository: $REPO_URL ${REPO_BRANCH:+(Branch: $REPO_BRANCH)}" log_info "Repository: $repo_url ${repo_branch:+(Branch: $repo_branch)}"
# Ensure git is available # Ensure git is available
if ! command -v git >/dev/null 2>&1; then if ! command -v git >/dev/null 2>&1; then
@@ -78,33 +82,26 @@ fi
# Create temporary directory # Create temporary directory
TMP_DIR="$(mktemp -d /tmp/debian-setup-XXXXXX)" TMP_DIR="$(mktemp -d /tmp/debian-setup-XXXXXX)"
cleanup() {
if [[ -d "$TMP_DIR" ]]; then
log_info "Cleaning up temporary installation files..."
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT INT TERM
log_info "Cloning repository into temporary directory: $TMP_DIR..." log_info "Cloning repository into temporary directory: $TMP_DIR..."
CLONE_SUCCESS=0 local clone_success=0
if [[ -n "$REPO_BRANCH" ]]; then if [[ -n "$repo_branch" ]]; then
if git clone --depth 1 -b "$REPO_BRANCH" "$REPO_URL" "$TMP_DIR" 2>/dev/null; then if git clone --depth 1 -b "$repo_branch" "$repo_url" "$TMP_DIR" 2>/dev/null; then
CLONE_SUCCESS=1 clone_success=1
else else
log_warn "Failed to clone branch '$REPO_BRANCH', falling back to default branch..." log_warn "Failed to clone branch '$repo_branch', falling back to default branch..."
rm -rf "${TMP_DIR:?}"/* "${TMP_DIR:?}"/.* 2>/dev/null || true rm -rf "${TMP_DIR:?}"/* "${TMP_DIR:?}"/.* 2>/dev/null || true
fi fi
fi fi
if [[ "$CLONE_SUCCESS" -eq 0 ]]; then if [[ "$clone_success" -eq 0 ]]; then
if git clone --depth 1 "$REPO_URL" "$TMP_DIR"; then if git clone --depth 1 "$repo_url" "$TMP_DIR"; then
CLONE_SUCCESS=1 clone_success=1
fi fi
fi fi
if [[ "$CLONE_SUCCESS" -eq 0 ]]; then if [[ "$clone_success" -eq 0 ]]; then
log_error "Failed to clone repository from $REPO_URL" log_error "Failed to clone repository from $repo_url"
exit 1 exit 1
fi fi
@@ -113,22 +110,32 @@ chmod +x setup.sh
log_success "Repository cloned successfully. Starting setup orchestrator..." log_success "Repository cloned successfully. Starting setup orchestrator..."
# Check if dry-run flag is requested # setup.sh itself elevates to root via sudo once it needs it - after all
is_dry_run=0 # interactive TUI/prompts have already run as the current user. Escalating
for arg in "$@"; do # here instead would wrap the whiptail dialogs in sudo, which (when sudo is
if [[ "$arg" == "-d" || "$arg" == "--dry-run" ]]; then # invoked from this non-interactive piped shell) leaves them unable to
is_dry_run=1 # receive keystrokes. See setup.sh for details.
break local -a setup_cmd=(./setup.sh "$@")
fi
done
# Execute setup.sh with root privileges (or directly if dry-run) while preserving arguments # Reconnect stdin/stdout to /dev/tty before executing setup.sh if piped
if [[ "$(id -u)" -eq 0 ]] || [[ "$is_dry_run" -eq 1 ]]; then # (e.g. curl ... | bash), otherwise the interactive TUI receives no keystrokes
./setup.sh "$@" if [[ -c /dev/tty ]] && ( : < /dev/tty ) 2>/dev/null; then
else if [[ ! -t 0 ]]; then
if ! command -v sudo >/dev/null 2>&1; then exec < /dev/tty
log_error "sudo is required to run setup.sh. Please install sudo or run as root."
exit 1
fi fi
sudo ./setup.sh "$@" if [[ ! -t 1 ]]; then
exec > /dev/tty
fi fi
fi
# Ensure a terminal type whiptail/newt is able to render
case "${TERM:-}" in
""|dumb|unknown)
export TERM="xterm-256color"
;;
esac
"${setup_cmd[@]}"
}
main "$@"
+273
View File
@@ -0,0 +1,273 @@
#!/usr/bin/env bash
# ==============================================================================
# lib/network.sh - Network interface configuration (Debian Installer style & Loopback)
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=lib/utils.sh
source "$SCRIPT_DIR/lib/utils.sh"
# Get a list of all physical LAN / Ethernet network interfaces on the system
get_lan_interfaces() {
local -a lan_ifaces=()
if [[ -d /sys/class/net ]]; then
for iface_path in /sys/class/net/*; do
[[ -e "$iface_path" ]] || continue
local iface
iface="$(basename "$iface_path")"
# Skip loopback interface
[[ "$iface" == "lo" ]] && continue
# Skip known virtual, bridge, container, tunnel and VPN interfaces
if [[ "$iface" =~ ^(docker[0-9]*|veth.*|virbr[0-9]*|br.*|bridge.*|tun[0-9]*|tap[0-9]*|wg[0-9]*|dummy[0-9]*|waydroid.*|tailscale.*|zt.*|bond.*|sit[0-9]*|ip6tnl.*|gre.*|erspan.*|vboxnet.*|vmnet.*)$ ]]; then
continue
fi
# Skip wireless / Wi-Fi interfaces
if [[ -e "$iface_path/wireless" || -e "$iface_path/phy80211" || "$iface" =~ ^(wl.*|wlan.*|wifi.*|ath.*)$ ]]; then
continue
fi
# Check if it is a physical device (has device link) or standard Ethernet type (type == 1)
local is_lan=0
local dev_type=""
if [[ -f "$iface_path/type" ]]; then
dev_type="$(cat "$iface_path/type" 2>/dev/null || echo "")"
fi
if [[ -e "$iface_path/device" && "$dev_type" == "1" ]]; then
is_lan=1
elif [[ "$iface" =~ ^(eth[0-9]+|en[a-zA-Z0-9]+)$ ]] && [[ "$dev_type" == "1" || -z "$dev_type" ]]; then
is_lan=1
elif [[ -e "$iface_path/device" ]]; then
is_lan=1
fi
if [[ "$is_lan" -eq 1 ]]; then
lan_ifaces+=("$iface")
fi
done
elif command_exists ip; then
# Fallback to ip link parsing if /sys/class/net is not directly readable
while IFS= read -r line; do
local iface
iface="$(echo "$line" | awk -F': ' '{print $2}' | cut -d'@' -f1)"
[[ -z "$iface" || "$iface" == "lo" ]] && continue
if [[ "$iface" =~ ^(eth[0-9]+|en[a-zA-Z0-9]+)$ ]]; then
lan_ifaces+=("$iface")
fi
done < <(ip -o link show 2>/dev/null || true)
fi
echo "${lan_ifaces[@]:-}"
}
# Check if a specific interface is already configured in /etc/network/interfaces or interfaces.d
is_interface_configured() {
local iface="$1"
local interfaces_file="/etc/network/interfaces"
local interfaces_dir="/etc/network/interfaces.d"
if [[ -f "$interfaces_file" ]]; then
if grep -E -q "^[[:space:]]*(iface|auto|allow-hotplug)[[:space:]]+${iface}([[:space:]]|$)" "$interfaces_file" 2>/dev/null; then
return 0
fi
fi
if [[ -d "$interfaces_dir" ]]; then
if grep -E -q -r "^[[:space:]]*(iface|auto|allow-hotplug)[[:space:]]+${iface}([[:space:]]|$)" "$interfaces_dir" 2>/dev/null; then
return 0
fi
fi
return 1
}
# Configure loopback interface in /etc/network/interfaces and /etc/hosts
configure_loopback_interface() {
local interfaces_file="/etc/network/interfaces"
local interfaces_dir="/etc/network/interfaces.d"
mkdir -p "/etc/network" "$interfaces_dir"
log_substep "Configuring loopback network interface (lo)..."
if [[ ! -f "$interfaces_file" ]]; then
cat <<EOF > "$interfaces_file"
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
EOF
chmod 0644 "$interfaces_file"
log_success "Created $interfaces_file with loopback configuration."
else
backup_file_or_dir "$interfaces_file"
# Ensure source /etc/network/interfaces.d/* is present
if ! grep -E -q "^[[:space:]]*source[[:space:]]+/etc/network/interfaces\.d/\*" "$interfaces_file"; then
sed -i '1i # The loopback network interface\nsource /etc/network/interfaces.d/*\n' "$interfaces_file"
fi
# Ensure auto lo and iface lo inet loopback are configured
if ! grep -E -q "^[[:space:]]*iface[[:space:]]+lo[[:space:]]+inet[[:space:]]+loopback" "$interfaces_file"; then
cat <<EOF >> "$interfaces_file"
# The loopback network interface
auto lo
iface lo inet loopback
EOF
log_success "Added loopback configuration to $interfaces_file."
fi
fi
# Ensure /etc/hosts has standard loopback mappings
if [[ -f /etc/hosts ]]; then
local host_name
host_name="$(hostname 2>/dev/null || cat /etc/hostname 2>/dev/null || echo "debian")"
if ! grep -q "127.0.0.1[[:space:]]\+localhost" /etc/hosts; then
echo "127.0.0.1 localhost" >> /etc/hosts
fi
if ! grep -q "::1[[:space:]]\+localhost" /etc/hosts; then
echo "::1 localhost ip6-localhost ip6-loopback" >> /etc/hosts
fi
if [[ -n "$host_name" ]] && ! grep -E -q "(127\.0\.1\.1|127\.0\.0\.1)[[:space:]]+.*$host_name" /etc/hosts; then
echo "127.0.1.1 $host_name" >> /etc/hosts
fi
fi
# Bring up loopback interface immediately
if command_exists ip; then
ip link set lo up 2>/dev/null || true
fi
if command_exists ifup; then
ifup lo 2>/dev/null || true
fi
log_success "Loopback interface (lo) configured and activated."
}
# Configure all detected physical LAN interfaces (Debian installer standard)
configure_lan_interfaces() {
local interfaces_file="/etc/network/interfaces"
local interfaces_dir="/etc/network/interfaces.d"
mkdir -p "/etc/network" "$interfaces_dir"
local -a lan_ifaces
read -r -a lan_ifaces <<< "$(get_lan_interfaces)"
if [[ ${#lan_ifaces[@]} -eq 0 ]]; then
log_info "No physical LAN interfaces detected at this time."
return 0
fi
log_substep "Found physical LAN interface(s): ${lan_ifaces[*]}"
# Backup interfaces file before modifications
if [[ -f "$interfaces_file" ]]; then
backup_file_or_dir "$interfaces_file"
fi
for iface in "${lan_ifaces[@]}"; do
[[ -z "$iface" ]] && continue
if is_interface_configured "$iface"; then
log_info "LAN interface '$iface' is already configured."
else
log_info "Configuring LAN interface '$iface' (Debian Installer style: DHCP & IPv6 Auto)..."
cat <<EOF >> "$interfaces_file"
# LAN network interface: $iface
allow-hotplug $iface
iface $iface inet dhcp
iface $iface inet6 auto
EOF
log_success "Added LAN interface '$iface' to $interfaces_file."
fi
# Ensure the interface is brought UP and usable
if command_exists ip; then
ip link set "$iface" up 2>/dev/null || true
fi
if command_exists ifup; then
ifup "$iface" 2>/dev/null || true
fi
done
log_success "All LAN network interfaces configured and activated."
}
# Configure NetworkManager to manage interfaces and work seamlessly with ifupdown
configure_network_manager() {
local nm_conf="/etc/NetworkManager/NetworkManager.conf"
local nm_dir="/etc/NetworkManager"
if [[ -d "$nm_dir" ]] || command_exists NetworkManager || command_exists nmcli; then
log_substep "Configuring NetworkManager integration (managed=true)..."
mkdir -p "$nm_dir"
if [[ ! -f "$nm_conf" ]]; then
cat <<EOF > "$nm_conf"
[main]
plugins=ifupdown,keyfile
[ifupdown]
managed=true
EOF
chmod 0644 "$nm_conf"
log_success "Created $nm_conf with managed=true."
else
backup_file_or_dir "$nm_conf"
# Set managed=true in [ifupdown] section
if grep -q "\[ifupdown\]" "$nm_conf"; then
sed -i '/\[ifupdown\]/,/^\[/{s/managed=false/managed=true/}' "$nm_conf"
if ! grep -A2 "\[ifupdown\]" "$nm_conf" | grep -q "managed="; then
sed -i '/\[ifupdown\]/a managed=true' "$nm_conf"
fi
else
cat <<EOF >> "$nm_conf"
[ifupdown]
managed=true
EOF
fi
log_success "Updated $nm_conf to managed=true."
fi
fi
}
# Enable networking and NetworkManager services
enable_network_services() {
if command_exists systemctl; then
log_substep "Enabling and activating network services..."
# Enable networking service (ifupdown)
systemctl enable networking.service 2>/dev/null || true
# Enable & start NetworkManager if available
if systemctl list-unit-files NetworkManager.service >/dev/null 2>&1 || [[ -f /lib/systemd/system/NetworkManager.service ]]; then
systemctl enable NetworkManager.service 2>/dev/null || true
systemctl start NetworkManager.service 2>/dev/null || true
log_success "NetworkManager service enabled and started."
fi
fi
}
# Central orchestration function for network configuration
configure_network_all() {
log_substep "Configuring complete network subsystem (Loopback, all LAN interfaces, NetworkManager)..."
configure_loopback_interface
configure_lan_interfaces
configure_network_manager
enable_network_services
log_success "Network configuration completed successfully."
}
+120 -30
View File
@@ -5,28 +5,91 @@
set -euo pipefail set -euo pipefail
# All whiptail dialogs are executed via 'run_whiptail' (lib/utils.sh), which
# binds stdin and stdout to the controlling terminal (/dev/tty). Without this,
# keyboard input (ENTER, SPACE, arrow keys) is consumed by the shell instead of
# the dialog whenever the setup is started through a pipe (curl ... | bash).
# Check if whiptail is available # Check if whiptail is available
tui_check_whiptail() { tui_check_whiptail() {
if ! command_exists whiptail; then if ! command_exists whiptail; then
log_warn "whiptail is not installed. Attempting to install whiptail..." log_warn "whiptail is not installed. Attempting to install whiptail..."
DEBIAN_FRONTEND=noninteractive apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y whiptail DEBIAN_FRONTEND=noninteractive apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y whiptail
fi fi
ensure_term
}
# Determine whether an interactive TUI can be displayed at all
tui_is_available() {
command_exists whiptail || return 1
has_tty || return 1
return 0
}
# Compute dialog geometry into TUI_HEIGHT / TUI_WIDTH / TUI_LIST_HEIGHT
# Usage: tui_calc_geometry <desired_height> <desired_width> [desired_list_height]
tui_calc_geometry() {
local want_height="$1"
local want_width="$2"
local want_list="${3:-0}"
local term_size term_lines term_cols
term_size="$(get_term_size)"
term_lines="${term_size%% *}"
term_cols="${term_size##* }"
local height="$want_height"
local width="$want_width"
if (( term_lines - 2 < height )); then
height=$(( term_lines - 2 ))
fi
if (( height < 8 )); then
height=8
fi
if (( term_cols - 4 < width )); then
width=$(( term_cols - 4 ))
fi
if (( width < 40 )); then
width=40
fi
local list_height="$want_list"
if (( want_list > 0 )); then
if (( height - 8 < list_height )); then
list_height=$(( height - 8 ))
fi
if (( list_height < 3 )); then
list_height=3
fi
fi
TUI_HEIGHT="$height"
TUI_WIDTH="$width"
TUI_LIST_HEIGHT="$list_height"
} }
# Display welcome message # Display welcome message
tui_welcome() { tui_welcome() {
whiptail --title "Debian Unstable Setup & Hyprland Installer" \ tui_calc_geometry 14 70
run_whiptail --title "Debian Unstable Setup & Hyprland Installer" \
--msgbox "Willkommen beim Debian Unstable & Desktop Setup!\n\nDieses Skript führt dich durch die modulare Einrichtung deines Debian Sid Systems mit XanMod-Kernel, Hyprland und Optimierungen.\n\nDrücke ENTER um zur Modulauswahl zu gelangen." \ --msgbox "Willkommen beim Debian Unstable & Desktop Setup!\n\nDieses Skript führt dich durch die modulare Einrichtung deines Debian Sid Systems mit XanMod-Kernel, Hyprland und Optimierungen.\n\nDrücke ENTER um zur Modulauswahl zu gelangen." \
14 70 "$TUI_HEIGHT" "$TUI_WIDTH" || true
} }
# Present stage checklist to user # Present stage checklist to user
# Returns space-separated list of selected stage numbers (e.g., "01 02 03 04 05 06 07 08 09") # Sets TUI_SELECTION and returns 0 on success, 1 on cancel
tui_select_stages() { tui_select_stages() {
local choices local tmp_file
choices=$(whiptail --title "Modulauswahl / Stages" \ tmp_file="$(mktemp)"
--checklist "Wähle die gewünschten Installations-Phasen mit der LEERTASTE aus:" \
23 78 10 \ tui_calc_geometry 20 74 10
if run_whiptail --output-fd 3 --title "Modulauswahl / Stages" \
--checklist "Wähle die gewünschten Installations-Phasen mit der LEERTASTE aus (ENTER = OK):" \
"$TUI_HEIGHT" "$TUI_WIDTH" "$TUI_LIST_HEIGHT" \
"01" "Debian Unstable (Deb822, Pinning, Upgrade)" ON \ "01" "Debian Unstable (Deb822, Pinning, Upgrade)" ON \
"02" "Kernel & Hardware (XanMod, P-State, Microcode)" ON \ "02" "Kernel & Hardware (XanMod, P-State, Microcode)" ON \
"03" "Bootloader & Theme (GRUB, Vimix, Plymouth)" ON \ "03" "Bootloader & Theme (GRUB, Vimix, Plymouth)" ON \
@@ -37,15 +100,19 @@ tui_select_stages() {
"08" "Flatpaks (Flathub Repository & Flatpaks)" ON \ "08" "Flatpaks (Flathub Repository & Flatpaks)" ON \
"09" "Zusatz-Apps & MIME (Spotify, Waydroid, OpenDeck, MIME)" ON \ "09" "Zusatz-Apps & MIME (Spotify, Waydroid, OpenDeck, MIME)" ON \
"10" "Hyprland Desktop (LinuxBeginnings Debian-Hyprland Installer)" ON \ "10" "Hyprland Desktop (LinuxBeginnings Debian-Hyprland Installer)" ON \
3>&1 1>&2 2>&3) || { "11" "Linutil (Chris Titus Tech System Toolbox)" ON \
log_info "Setup durch Benutzer abgebrochen." 3> "$tmp_file"; then
exit 0
}
# Clean quotes from whiptail output (e.g. '"01" "02"' -> '01 02') TUI_SELECTION="$(tr -d '"' < "$tmp_file")"
local cleaned_choices export TUI_SELECTION
cleaned_choices=$(echo "$choices" | tr -d '"') rm -f "$tmp_file"
echo "$cleaned_choices" return 0
else
rm -f "$tmp_file"
TUI_SELECTION=""
export TUI_SELECTION
return 1
fi
} }
# Prompt user for target user via Whiptail dialog # Prompt user for target user via Whiptail dialog
@@ -53,39 +120,60 @@ tui_prompt_target_user() {
local default_user="${1:-}" local default_user="${1:-}"
local chosen="" local chosen=""
# Detect candidate user if not provided or root
if [[ -z "$default_user" || "$default_user" == "root" ]]; then
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then
default_user="$SUDO_USER"
elif [[ -n "${PKEXEC_UID:-}" ]]; then
default_user="$(id -nu "$PKEXEC_UID" 2>/dev/null || true)"
elif [[ "$(id -u)" -ne 0 ]]; then
default_user="$(id -un)"
else
default_user="$(awk -F: '$3 >= 1000 && $3 < 60000 && $1 != "nobody" {print $1; exit}' /etc/passwd 2>/dev/null || true)"
fi
fi
if [[ -n "$default_user" && "$default_user" != "root" ]]; then if [[ -n "$default_user" && "$default_user" != "root" ]]; then
if whiptail --title "Ziel-Benutzer bestätigen" \ tui_calc_geometry 10 65
if run_whiptail --title "Ziel-Benutzer bestätigen" \
--yes-button "Verwenden" \ --yes-button "Verwenden" \
--no-button "Anderer Benutzer" \ --no-button "Anderer Benutzer" \
--yesno "Erkannter Ziel-Benutzer: $default_user\n\nMöchtest du diesen Benutzer für die Konfiguration (Dotfiles, Gruppen, Hyprland) verwenden?" \ --yesno "Erkannter Ziel-Benutzer: $default_user\n\nMöchtest du diesen Benutzer für die Konfiguration (Dotfiles, Gruppen, Hyprland) verwenden?" \
10 65; then "$TUI_HEIGHT" "$TUI_WIDTH"; then
chosen="$default_user" chosen="$default_user"
fi fi
fi fi
local tmp_file
tmp_file="$(mktemp)"
while [[ -z "$chosen" ]]; do while [[ -z "$chosen" ]]; do
local input_user tui_calc_geometry 10 60
input_user=$(whiptail --title "Ziel-Benutzer eingeben" \ if run_whiptail --output-fd 3 --title "Ziel-Benutzer eingeben" \
--inputbox "Bitte gib den Benutzernamen des Desktop-Benutzers ein:" \ --inputbox "Bitte gib den Benutzernamen des Desktop-Benutzers ein:" \
10 60 "$default_user" \ "$TUI_HEIGHT" "$TUI_WIDTH" "$default_user" \
3>&1 1>&2 2>&3) || { 3> "$tmp_file"; then
log_info "Setup durch Benutzer abgebrochen."
exit 0 local input_user
} input_user="$(tr -d '[:space:]' < "$tmp_file")"
input_user="$(echo "$input_user" | tr -d '[:space:]')"
if [[ -z "$input_user" || "$input_user" == "root" ]]; then if [[ -z "$input_user" || "$input_user" == "root" ]]; then
whiptail --title "Ungültiger Benutzer" --msgbox "Ungültiger Benutzername (darf nicht leer oder root sein)." 8 50 run_whiptail --title "Ungültiger Benutzer" --msgbox "Ungültiger Benutzername (darf nicht leer oder root sein)." 8 55 || true
continue continue
fi fi
if ! id "$input_user" >/dev/null 2>&1; then if ! id "$input_user" >/dev/null 2>&1; then
whiptail --title "Benutzer nicht gefunden" --msgbox "Benutzer '$input_user' existiert nicht im System." 8 50 run_whiptail --title "Benutzer nicht gefunden" --msgbox "Benutzer '$input_user' existiert nicht im System." 8 55 || true
continue continue
fi fi
chosen="$input_user" chosen="$input_user"
else
rm -f "$tmp_file"
log_info "Setup durch Benutzer abgebrochen."
exit 0
fi
done done
rm -f "$tmp_file"
export TARGET_USER="$chosen" export TARGET_USER="$chosen"
echo "$chosen"
} }
# Confirmation dialog before running # Confirmation dialog before running
@@ -93,7 +181,9 @@ tui_confirm_execution() {
local selected_stages="$1" local selected_stages="$1"
local target_user="$2" local target_user="$2"
whiptail --title "Installation starten" \ tui_calc_geometry 14 70
run_whiptail --title "Installation starten" \
--yesno "Folgende Konfiguration wird ausgeführt:\n\n- Ziel-Benutzer: $target_user\n- Ausgewählte Stages: $selected_stages\n\nMöchtest du die Installation jetzt starten?" \ --yesno "Folgende Konfiguration wird ausgeführt:\n\n- Ziel-Benutzer: $target_user\n- Ausgewählte Stages: $selected_stages\n\nMöchtest du die Installation jetzt starten?" \
14 70 "$TUI_HEIGHT" "$TUI_WIDTH"
} }
+202 -24
View File
@@ -58,15 +58,101 @@ log_substep() {
# Error trap handler # Error trap handler
setup_err_trap() { setup_err_trap() {
# Required so the ERR trap also fires for failures inside sourced
# lib/*.sh functions (errtrace) and nested function calls (functrace),
# not just for top-level commands in the stage script itself.
set -o errtrace
set -o functrace
trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR
} }
# Re-arms 'set -e' for the *next* command only, via a self-removing DEBUG
# trap. Bash normally terminates the script right after an ERR trap when
# errexit is set, no matter what the trap does - the only way to make the
# script actually resume past the failed line is to have errexit disabled
# at the moment the trap returns. Must be the very last statement executed
# on the "resume" path (no trailing 'return'/'break'/...): any further
# command run inside error_handler itself would consume the DEBUG trap's
# one-shot reset before control ever gets back to the failed line.
resume_after_error() {
set +e
trap 'set -e; trap - DEBUG' DEBUG
}
# Ask the user what to do about a failed command.
# Echoes one of: r (retry), i (ignore), a (abort). Defaults to "a" when
# not running interactively (piped install, CI, DEBIAN_FRONTEND=noninteractive,
# AUTO_CONFIRM=1) or when the prompt can't be read, so unattended installs
# keep failing hard exactly like before this feature existed.
ask_retry_ignore_abort() {
local is_interactive=0
if { [[ -t 0 ]] || has_tty; } && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then
is_interactive=1
fi
if [[ "$is_interactive" -ne 1 ]]; then
echo "a"
return 0
fi
local tty_in="/dev/tty"
local tty_out="/dev/tty"
if ! has_tty; then
tty_in="/dev/stdin"
tty_out="/dev/stderr"
fi
local prompt answer=""
prompt="$(printf "%b[W]iederholen%b / %b[i]gnorieren%b / %b[a]bbrechen%b (Standard: a): " \
"$CLR_BOLD" "$CLR_RESET" "$CLR_BOLD" "$CLR_RESET" "$CLR_BOLD" "$CLR_RESET")"
read -r -p "$prompt" answer < "$tty_in" > "$tty_out" 2>&1 || answer="a"
answer="$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$answer")"
case "$answer" in
[Ww]*) echo "r" ;;
[Ii]*) echo "i" ;;
*) echo "a" ;;
esac
}
error_handler() { error_handler() {
local exit_code="$1" local exit_code="$1"
local line_no="$2" local line_no="$2"
local bash_command="$3" local bash_command="$3"
log_error "Command failed with exit code $exit_code at line $line_no: '$bash_command'" log_error "Command failed with exit code $exit_code at line $line_no: '$bash_command'"
local choice
choice="$(ask_retry_ignore_abort)"
case "$choice" in
r)
# Bare shell keywords (return/exit/break/continue) can't be
# meaningfully re-run via eval - they'd just unwind the current
# function instead of redoing any actual work. Treat these like
# "ignore" instead of silently aborting on a nonsensical retry.
if [[ "$bash_command" =~ ^(return|exit|break|continue)([[:space:]]|$) ]]; then
log_warn "'$bash_command' kann nicht wiederholt werden - Fehler wird ignoriert."
resume_after_error
else
log_info "Wiederhole: $bash_command"
if eval "$bash_command"; then
log_success "Wiederholung erfolgreich, Installation wird fortgesetzt."
resume_after_error
else
error_handler "$?" "$line_no" "$bash_command"
fi
fi
;;
i)
log_warn "Fehler wird ignoriert, Installation wird fortgesetzt: $bash_command"
resume_after_error
;;
*)
log_error "Installation abgebrochen."
exit "$exit_code" exit "$exit_code"
;;
esac
} }
# Root permission check # Root permission check
@@ -82,6 +168,76 @@ command_exists() {
command -v "$1" >/dev/null 2>&1 command -v "$1" >/dev/null 2>&1
} }
# ------------------------------------------------------------------------------
# Terminal / TTY helpers (required for interactive whiptail dialogs)
# ------------------------------------------------------------------------------
# Check whether a usable controlling terminal is available
has_tty() {
[[ -c /dev/tty ]] || return 1
{ : < /dev/tty; } 2>/dev/null || return 1
return 0
}
# Ensure TERM is set to a value newt/whiptail is able to render
ensure_term() {
case "${TERM:-}" in
""|dumb|unknown)
if [[ -f /usr/share/terminfo/x/xterm-256color || -f /lib/terminfo/x/xterm-256color ]]; then
export TERM="xterm-256color"
else
export TERM="linux"
fi
;;
esac
}
# Report the current terminal size as "<lines> <columns>"
get_term_size() {
local size=""
if has_tty; then
size="$(stty size < /dev/tty 2>/dev/null || true)"
fi
if [[ -z "$size" ]]; then
size="$(stty size 2>/dev/null || true)"
fi
if [[ ! "$size" =~ ^[0-9]+[[:space:]]+[0-9]+$ ]]; then
size="${LINES:-24} ${COLUMNS:-80}"
fi
echo "$size"
}
# Run whiptail with stdin and stdout bound to the controlling terminal.
# This is required because the setup may be started through a pipe
# (e.g. 'curl ... | bash'), where stdin is not the terminal and all
# keystrokes would otherwise be swallowed by the shell instead of the dialog.
# Dialog results must be requested via '--output-fd 3'.
run_whiptail() {
ensure_term
if has_tty; then
whiptail "$@" < /dev/tty > /dev/tty
else
whiptail "$@"
fi
}
# Ask a yes/no question on the CLI. Returns 0 for yes, 1 for no.
# On read failure (e.g. closed stdin), falls back to default_answer.
# Usage: confirm_yes_no "<prompt>" "<y|n default answer>" "$tty_in" "$tty_out"
confirm_yes_no() {
local prompt="$1"
local default_answer="$2"
local tty_in="$3"
local tty_out="$4"
local answer=""
read -r -p "$prompt" answer < "$tty_in" > "$tty_out" 2>&1 || answer="$default_answer"
answer="$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$answer")"
if [[ -z "$answer" ]]; then
answer="$default_answer"
fi
[[ "$answer" =~ ^[YyJj] ]]
}
# Determine and prompt for the non-root target user # Determine and prompt for the non-root target user
prompt_target_user() { prompt_target_user() {
if [[ -n "${TARGET_USER:-}" ]]; then if [[ -n "${TARGET_USER:-}" ]]; then
@@ -105,14 +261,14 @@ prompt_target_user() {
local chosen="" local chosen=""
local is_interactive=0 local is_interactive=0
if [[ -t 0 || -c /dev/tty ]] && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then if { [[ -t 0 ]] || has_tty; } && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then
is_interactive=1 is_interactive=1
fi fi
if [[ "$is_interactive" -eq 1 ]]; then if [[ "$is_interactive" -eq 1 ]]; then
local tty_in="/dev/tty" local tty_in="/dev/tty"
local tty_out="/dev/tty" local tty_out="/dev/tty"
if [[ ! -c /dev/tty ]]; then if ! has_tty; then
tty_in="/dev/stdin" tty_in="/dev/stdin"
tty_out="/dev/stderr" tty_out="/dev/stderr"
fi fi
@@ -120,9 +276,7 @@ prompt_target_user() {
if [[ -n "$candidate" && "$candidate" != "root" ]]; then if [[ -n "$candidate" && "$candidate" != "root" ]]; then
printf "\n%bTarget User Detection:%b\n" "${CLR_CYAN}" "${CLR_RESET}" > "$tty_out" printf "\n%bTarget User Detection:%b\n" "${CLR_CYAN}" "${CLR_RESET}" > "$tty_out"
printf "Detected target user: %b%s%b\n" "${CLR_BOLD}" "$candidate" "${CLR_RESET}" > "$tty_out" printf "Detected target user: %b%s%b\n" "${CLR_BOLD}" "$candidate" "${CLR_RESET}" > "$tty_out"
local answer="" if confirm_yes_no "Use target user '$candidate'? [Y/n]: " "y" "$tty_in" "$tty_out"; then
read -r -p "Use target user '$candidate'? [Y/n]: " answer < "$tty_in" > "$tty_out" 2>&1 || answer="y"
if [[ -z "$answer" || "$answer" =~ ^[YyJj] ]]; then
chosen="$candidate" chosen="$candidate"
fi fi
fi fi
@@ -187,52 +341,76 @@ prompt_ollama_models_path() {
return 0 return 0
fi fi
local default_path="${OLLAMA_MODELS_DEFAULT:-/mnt/Data/Software/ollama/models}" # Ollama's own default storage location (used by the official install
# script / systemd service, which runs as the "ollama" user).
local ollama_default_path="/usr/share/ollama/.ollama/models"
# Suggested custom path, only ever pre-filled once the user has
# explicitly declined the Ollama default above.
local custom_path_suggestion="${OLLAMA_MODELS_DEFAULT:-/mnt/Data/Software/ollama/models}"
local chosen="" local chosen=""
local is_interactive=0 local is_interactive=0
if [[ -t 0 || -c /dev/tty ]] && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then if { [[ -t 0 ]] || has_tty; } && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then
is_interactive=1 is_interactive=1
fi fi
if [[ "$is_interactive" -eq 1 ]]; then if [[ "$is_interactive" -eq 1 ]]; then
local tty_in="/dev/tty" local tty_in="/dev/tty"
local tty_out="/dev/tty" local tty_out="/dev/tty"
if [[ ! -c /dev/tty ]]; then if ! has_tty; then
tty_in="/dev/stdin" tty_in="/dev/stdin"
tty_out="/dev/stderr" tty_out="/dev/stderr"
fi fi
# If whiptail is available and running on an active terminal, use whiptail dialog # If whiptail is available and running on an active terminal, first ask
if command_exists whiptail && [[ -t 1 || -c /dev/tty ]]; then # whether Ollama's own default path should be used, and only show the
local tui_input # free-text path dialog (pre-filled with the custom suggestion) if not.
if tui_input=$(whiptail --title "Ollama KI-Modelle Speicherort" \ if command_exists whiptail && has_tty; then
--inputbox "Bitte gib den Speicherort für die Ollama KI-Modelle an:" \ local yesno_status=0
10 70 "$default_path" \ run_whiptail --title "Ollama KI-Modelle Speicherort" \
3>&1 1>&2 2>&3); then --yes-button "Standard verwenden" --no-button "Eigenen Pfad angeben" \
tui_input="$(echo "$tui_input" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" --yesno "Standard-Speicherort für Ollama-Modelle:\n\n$ollama_default_path\n\nMöchtest du diesen Speicherort verwenden?" \
if [[ -n "$tui_input" ]]; then 12 70 || yesno_status=$?
chosen="$tui_input" if [[ "$yesno_status" -eq 0 || "$yesno_status" -eq 255 ]]; then
else # Yes, or ESC/Cancel (255) - use the Ollama default,
chosen="$default_path" # consistent with the inputbox fallback below where an
# empty or cancelled dialog also keeps its own default
# instead of asking again.
chosen="$ollama_default_path"
fi fi
if [[ -z "$chosen" ]]; then
local tui_tmp
tui_tmp="$(mktemp)"
if run_whiptail --output-fd 3 --title "Ollama KI-Modelle Speicherort" \
--inputbox "Bitte gib den Speicherort für die Ollama KI-Modelle an:" \
10 70 "$custom_path_suggestion" \
3> "$tui_tmp"; then
chosen="$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' < "$tui_tmp")"
fi
rm -f "$tui_tmp"
[[ -z "$chosen" ]] && chosen="$custom_path_suggestion"
fi fi
fi fi
# CLI fallback if whiptail was not used or failed # CLI fallback if whiptail was not used or failed
if [[ -z "$chosen" ]]; then if [[ -z "$chosen" ]]; then
printf "\n%bOllama KI-Modelle Speicherort:%b\n" "${CLR_CYAN}" "${CLR_RESET}" > "$tty_out" printf "\n%bOllama KI-Modelle Speicherort:%b\n" "${CLR_CYAN}" "${CLR_RESET}" > "$tty_out"
printf "Standard-Pfad: %b%s%b\n" "${CLR_BOLD}" "$default_path" "${CLR_RESET}" > "$tty_out" printf "Standard-Pfad: %b%s%b\n" "${CLR_BOLD}" "$ollama_default_path" "${CLR_RESET}" > "$tty_out"
if confirm_yes_no "Standard-Speicherort verwenden? [J/n]: " "j" "$tty_in" "$tty_out"; then
chosen="$ollama_default_path"
else
local input_path="" local input_path=""
read -r -p "Speicherort für Ollama-Modelle eingeben [$default_path]: " input_path < "$tty_in" > "$tty_out" 2>&1 || true read -r -p "Speicherort für Ollama-Modelle eingeben [$custom_path_suggestion]: " input_path < "$tty_in" > "$tty_out" 2>&1 || true
input_path="$(echo "$input_path" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" input_path="$(echo "$input_path" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
if [[ -n "$input_path" ]]; then if [[ -n "$input_path" ]]; then
chosen="$input_path" chosen="$input_path"
else else
chosen="$default_path" chosen="$custom_path_suggestion"
fi
fi fi
fi fi
else else
chosen="$default_path" chosen="$ollama_default_path"
fi fi
export OLLAMA_MODELS="$chosen" export OLLAMA_MODELS="$chosen"
+82 -19
View File
@@ -11,6 +11,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/utils.sh" source "$SCRIPT_DIR/lib/utils.sh"
source "$SCRIPT_DIR/lib/apt.sh" source "$SCRIPT_DIR/lib/apt.sh"
source "$SCRIPT_DIR/lib/btrfs.sh" source "$SCRIPT_DIR/lib/btrfs.sh"
source "$SCRIPT_DIR/lib/network.sh"
source "$SCRIPT_DIR/lib/tui.sh" source "$SCRIPT_DIR/lib/tui.sh"
if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then
@@ -20,15 +21,32 @@ fi
setup_err_trap setup_err_trap
# Reconnect stdin/stdout to the controlling terminal when the script was
# started through a pipe (e.g. 'curl ... | bash'). Otherwise keystrokes are
# consumed by the shell and never reach the interactive whiptail dialogs.
if has_tty; then
if [[ ! -t 0 ]]; then
exec < /dev/tty
fi
if [[ ! -t 1 ]]; then
exec > /dev/tty
fi
fi
ensure_term
# Show help text # Show help text
show_help() { show_help() {
cat <<EOF cat <<EOF
Usage: sudo ./setup.sh [OPTIONS] Usage: ./setup.sh [OPTIONS]
Debian Unstable Setup & Desktop Customization Framework Debian Unstable Setup & Desktop Customization Framework
Note: setup.sh elevates itself via sudo once needed, after any interactive
prompts have been answered. Run it as your normal desktop user - no need to
prefix it with 'sudo' yourself (though doing so still works).
Options: Options:
-a, --all Run all setup stages sequentially (00 through 10) -a, --all Run all setup stages sequentially (00 through 11)
-s, --stages <list> Comma-separated list of stages to run (e.g. --stages 01,02,04) -s, --stages <list> Comma-separated list of stages to run (e.g. --stages 01,02,04)
-u, --user <username> Specify target desktop user (defaults to \$SUDO_USER) -u, --user <username> Specify target desktop user (defaults to \$SUDO_USER)
-d, --dry-run Simulate stage execution without modifying the system -d, --dry-run Simulate stage execution without modifying the system
@@ -46,12 +64,13 @@ Available Stages:
08: Flatpak Applications (Flathub, Flatpak package list) 08: Flatpak Applications (Flathub, Flatpak package list)
09: Standalone Applications & MIME (Spotify, Waydroid, OpenDeck, MIME types) 09: Standalone Applications & MIME (Spotify, Waydroid, OpenDeck, MIME types)
10: Hyprland Desktop (LinuxBeginnings Auto-Installer) 10: Hyprland Desktop (LinuxBeginnings Auto-Installer)
11: Linutil (Chris Titus Tech System Toolbox)
Examples: Examples:
sudo ./setup.sh # Interactive TUI mode ./setup.sh # Interactive TUI mode
sudo ./setup.sh --all # Full automated install ./setup.sh --all # Full automated install
sudo ./setup.sh --stages 04,05 # Install only packages and fonts ./setup.sh --stages 04,05 # Install only packages and fonts
sudo ./setup.sh --stages 01 --dry-run # Dry-run stage 01 ./setup.sh --stages 01 --dry-run # Dry-run stage 01
EOF EOF
} }
@@ -107,16 +126,15 @@ if [[ -n "$CLI_USER" ]]; then
export TARGET_USER="$CLI_USER" export TARGET_USER="$CLI_USER"
fi fi
# Ensure root privileges unless dry-run # NOTE: Root privileges are intentionally NOT required here. All interactive
if [[ "$FLAG_DRY_RUN" -eq 0 ]]; then # prompts (whiptail TUI, target user, Ollama path) must run BEFORE escalating
require_root # to sudo - see the privilege escalation block below for why.
fi
# Determine stages to run # Determine stages to run
SELECTED_STAGES=() SELECTED_STAGES=()
if [[ "$FLAG_ALL" -eq 1 ]]; then if [[ "$FLAG_ALL" -eq 1 ]]; then
SELECTED_STAGES=(01 02 03 04 05 06 07 08 09 10) SELECTED_STAGES=(01 02 03 04 05 06 07 08 09 10 11)
elif [[ -n "$FLAG_STAGES" ]]; then elif [[ -n "$FLAG_STAGES" ]]; then
IFS=',' read -r -a raw_stages <<< "$FLAG_STAGES" IFS=',' read -r -a raw_stages <<< "$FLAG_STAGES"
for st in "${raw_stages[@]}"; do for st in "${raw_stages[@]}"; do
@@ -129,29 +147,35 @@ elif [[ -n "$FLAG_STAGES" ]]; then
done done
else else
# Interactive TUI Mode # Interactive TUI Mode
if [[ -t 0 && -t 1 ]]; then if has_tty; then
tui_check_whiptail tui_check_whiptail
tui_welcome tui_welcome
TUI_SELECTION=$(tui_select_stages) if ! tui_select_stages; then
log_info "Setup aborted by user."
exit 0
fi
if [[ -z "$TUI_SELECTION" ]]; then if [[ -z "${TUI_SELECTION:-}" ]]; then
log_warn "No stages selected. Exiting." log_warn "No stages selected. Exiting."
exit 0 exit 0
fi fi
read -r -a SELECTED_STAGES <<< "$TUI_SELECTION" read -r -a SELECTED_STAGES <<< "$TUI_SELECTION"
DETECTED_USER="${TARGET_USER:-$(get_target_user 2>/dev/null || true)}" DETECTED_USER="${TARGET_USER:-${SUDO_USER:-$(logname 2>/dev/null || id -un 2>/dev/null || true)}}"
TARGET_USER_NAME="$(tui_prompt_target_user "$DETECTED_USER")" if [[ "$DETECTED_USER" == "root" ]]; then
export TARGET_USER="$TARGET_USER_NAME" DETECTED_USER=""
fi
tui_prompt_target_user "$DETECTED_USER"
if ! tui_confirm_execution "${SELECTED_STAGES[*]}" "$TARGET_USER_NAME"; then if ! tui_confirm_execution "${SELECTED_STAGES[*]}" "$TARGET_USER"; then
log_info "Installation aborted by user." log_info "Installation aborted by user."
exit 0 exit 0
fi fi
else else
log_warn "No controlling terminal available - interactive TUI cannot be displayed."
log_info "Non-interactive shell and no parameters provided. Defaulting to --all." log_info "Non-interactive shell and no parameters provided. Defaulting to --all."
SELECTED_STAGES=(01 02 03 04 05 06 07 08 09 10) SELECTED_STAGES=(01 02 03 04 05 06 07 08 09 10 11)
fi fi
fi fi
@@ -161,6 +185,45 @@ if [[ -z "${TARGET_USER:-}" ]]; then
export TARGET_USER export TARGET_USER
fi fi
# Pre-resolve the Ollama models path here (while still unprivileged and
# directly attached to the terminal) if stage 06 will run, so stage
# 06-services.sh never has to prompt again after privilege escalation.
if [[ "$FLAG_DRY_RUN" -eq 0 ]] && [[ -z "${OLLAMA_MODELS:-}" ]]; then
for st in "${SELECTED_STAGES[@]}"; do
if [[ "$st" == "06" ]]; then
OLLAMA_MODELS="$(prompt_ollama_models_path)"
export OLLAMA_MODELS
break
fi
done
fi
# Escalate to root via sudo now that every interactive prompt (whiptail TUI,
# target user, Ollama path) has already been answered while directly attached
# to the terminal. sudo run from a non-interactive shell (e.g. 'curl | bash')
# does not reliably hand the controlling terminal's foreground process group
# to the command it execs, which leaves whiptail/read unable to receive any
# keystrokes. Running all interactive dialogs before this point, and passing
# the already-resolved choices through as flags/env vars, avoids ever needing
# terminal input again after the sudo boundary.
if [[ "$FLAG_DRY_RUN" -eq 0 ]] && [[ "$(id -u)" -ne 0 ]]; then
if ! command_exists sudo; then
log_error "sudo is required to run setup.sh. Please install sudo or run as root."
exit 1
fi
log_info "Elevating privileges via sudo to continue installation..."
joined_stages="$(IFS=,; echo "${SELECTED_STAGES[*]}")"
sudo_env_args=("TARGET_USER=$TARGET_USER")
if [[ -n "${OLLAMA_MODELS:-}" ]]; then
sudo_env_args+=("OLLAMA_MODELS=$OLLAMA_MODELS")
fi
exec sudo "${sudo_env_args[@]}" bash "$SCRIPT_DIR/setup.sh" --stages "$joined_stages" --user "$TARGET_USER"
fi
if [[ "$FLAG_DRY_RUN" -eq 0 ]]; then
require_root
fi
# Map stage identifier to script file # Map stage identifier to script file
get_stage_script() { get_stage_script() {
local stage_id="$1" local stage_id="$1"
+11 -5
View File
@@ -8,6 +8,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$SCRIPT_DIR/lib/utils.sh" source "$SCRIPT_DIR/lib/utils.sh"
source "$SCRIPT_DIR/lib/btrfs.sh" source "$SCRIPT_DIR/lib/btrfs.sh"
source "$SCRIPT_DIR/lib/network.sh"
if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then
# shellcheck source=/dev/null # shellcheck source=/dev/null
@@ -35,7 +36,12 @@ fi
log_success "Target user identified: $TARGET_USER (Home: $TARGET_HOME)" log_success "Target user identified: $TARGET_USER (Home: $TARGET_HOME)"
# 3. Check Internet Connectivity # 3. Configure and Activate Network Interfaces (Loopback & LAN)
log_substep "Initializing and activating network interfaces (Loopback & LAN)..."
configure_loopback_interface
configure_lan_interfaces
# 4. Check Internet Connectivity
log_substep "Checking internet connectivity..." log_substep "Checking internet connectivity..."
INTERNET_OK=0 INTERNET_OK=0
for host in "deb.debian.org" "1.1.1.1" "8.8.8.8"; do for host in "deb.debian.org" "1.1.1.1" "8.8.8.8"; do
@@ -51,7 +57,7 @@ if [[ "$INTERNET_OK" -ne 1 ]]; then
fi fi
log_success "Internet connection verified." log_success "Internet connection verified."
# 4. Check Operating System # 5. Check Operating System
log_substep "Verifying operating system..." log_substep "Verifying operating system..."
if [[ -f /etc/os-release ]]; then if [[ -f /etc/os-release ]]; then
# shellcheck source=/dev/null # shellcheck source=/dev/null
@@ -64,7 +70,7 @@ else
log_warn "/etc/os-release not found. Proceeding with caution." log_warn "/etc/os-release not found. Proceeding with caution."
fi fi
# 5. Check Disk Space # 6. Check Disk Space
log_substep "Checking available disk space..." log_substep "Checking available disk space..."
AVAILABLE_KB=$(df --output=avail / | tail -n1) AVAILABLE_KB=$(df --output=avail / | tail -n1)
if [[ "$AVAILABLE_KB" -lt 5242880 ]]; then # Less than 5 GB if [[ "$AVAILABLE_KB" -lt 5242880 ]]; then # Less than 5 GB
@@ -73,11 +79,11 @@ else
log_success "Sufficient disk space available ($(( AVAILABLE_KB / 1024 / 1024 )) GB free)." log_success "Sufficient disk space available ($(( AVAILABLE_KB / 1024 / 1024 )) GB free)."
fi fi
# 6. Check & Setup Btrfs Subvolumes (@, @home) # 7. Check & Setup Btrfs Subvolumes (@, @home)
log_substep "Checking Btrfs root volume & subvolume layout..." log_substep "Checking Btrfs root volume & subvolume layout..."
check_and_setup_btrfs_subvolumes check_and_setup_btrfs_subvolumes
# 7. Create Timeshift Btrfs Snapshot after preflight & subvolume setup # 8. Create Timeshift Btrfs Snapshot after preflight & subvolume setup
create_timeshift_snapshot "Stage 00: Pre-flight checks & Btrfs baseline" "B" create_timeshift_snapshot "Stage 00: Pre-flight checks & Btrfs baseline" "B"
log_success "Stage 00: Pre-flight checks passed successfully." log_success "Stage 00: Pre-flight checks passed successfully."
+18
View File
@@ -120,6 +120,24 @@ if compgen -G "$SCRIPT_DIR/data/etc/environment.d/*.conf" > /dev/null; then
done done
fi fi
# 6. Persistent Shader Cache Location (Mesa & DXVK) - optional relocation
if [[ -n "${SHADER_CACHE_DIR:-}" ]]; then
log_substep "Configuring custom Mesa/DXVK shader cache location..."
TARGET_USER="${TARGET_USER:-$(prompt_target_user)}"
mkdir -p "$SHADER_CACHE_DIR"
chown -R "$TARGET_USER:$TARGET_USER" "$SHADER_CACHE_DIR"
cat <<EOF > /etc/environment.d/99-shader-cache.conf
# Persistent Mesa (RADV/OpenGL) & DXVK (Wine/Proton) shader/pipeline cache,
# relocated off the system disk to avoid re-compilation stutter after reboots.
MESA_SHADER_CACHE_DIR=$SHADER_CACHE_DIR
DXVK_STATE_CACHE_PATH=$SHADER_CACHE_DIR
EOF
chmod 0644 /etc/environment.d/99-shader-cache.conf
log_success "Shader cache directory set to $SHADER_CACHE_DIR"
else
log_info "SHADER_CACHE_DIR not set - keeping Mesa's and DXVK's own default shader cache locations."
fi
create_timeshift_snapshot "Stage 02: Post-kernel and hardware tuning" "O" create_timeshift_snapshot "Stage 02: Post-kernel and hardware tuning" "O"
log_success "Stage 02: Kernel and Hardware Tuning completed successfully!" log_success "Stage 02: Kernel and Hardware Tuning completed successfully!"
+69 -3
View File
@@ -9,6 +9,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$SCRIPT_DIR/lib/utils.sh" source "$SCRIPT_DIR/lib/utils.sh"
source "$SCRIPT_DIR/lib/apt.sh" source "$SCRIPT_DIR/lib/apt.sh"
source "$SCRIPT_DIR/lib/btrfs.sh" source "$SCRIPT_DIR/lib/btrfs.sh"
source "$SCRIPT_DIR/lib/network.sh"
if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then
# shellcheck source=/dev/null # shellcheck source=/dev/null
@@ -147,15 +148,80 @@ elif is_root_btrfs; then
log_info "Timeshift will be configured when installed; grub-btrfs configured." log_info "Timeshift will be configured when installed; grub-btrfs configured."
fi fi
# 7. User Group Memberships # 7. Network Configuration & Services (Loopback, LAN, NetworkManager)
log_substep "Configuring and activating network interfaces (Loopback, all LAN interfaces, NetworkManager)..."
configure_network_all
# 8. User Group Memberships
log_substep "Updating user group memberships for $TARGET_USER..." log_substep "Updating user group memberships for $TARGET_USER..."
groupadd -f sudo groupadd -f sudo
groupadd -f docker groupadd -f docker
groupadd -f netdev
groupadd -f ollama 2>/dev/null || true groupadd -f ollama 2>/dev/null || true
usermod -aG sudo,docker "$TARGET_USER" usermod -aG sudo,docker,netdev "$TARGET_USER"
if getent group ollama >/dev/null 2>&1; then if getent group ollama >/dev/null 2>&1; then
usermod -aG ollama "$TARGET_USER" 2>/dev/null || true usermod -aG ollama "$TARGET_USER" 2>/dev/null || true
fi fi
log_success "Target user $TARGET_USER added to sudo, docker and ollama groups." # "render" (GPU compute/Vulkan access) is created by Debian's udev sysusers
# config and is always present; "gamemode" only exists once the gamemode
# package is installed (stage 04), so it is added conditionally.
if getent group render >/dev/null 2>&1; then
usermod -aG render "$TARGET_USER" 2>/dev/null || true
fi
if getent group gamemode >/dev/null 2>&1; then
usermod -aG gamemode "$TARGET_USER" 2>/dev/null || true
fi
log_success "Target user $TARGET_USER added to sudo, docker, netdev, ollama, render and gamemode groups."
# 9. GameMode Performance Daemon & CoreCtrl Configuration
log_substep "Deploying GameMode performance configuration..."
if [[ -f "$SCRIPT_DIR/data/etc/gamemode.ini" ]]; then
backup_file_or_dir "/etc/gamemode.ini"
cp "$SCRIPT_DIR/data/etc/gamemode.ini" /etc/gamemode.ini
chmod 0644 /etc/gamemode.ini
log_success "GameMode configuration deployed to /etc/gamemode.ini."
fi
log_substep "Configuring passwordless CoreCtrl access for administrative users..."
if [[ -f "$SCRIPT_DIR/data/etc/polkit-1/rules.d/90-corectrl.rules" ]]; then
mkdir -p /etc/polkit-1/rules.d
cp "$SCRIPT_DIR/data/etc/polkit-1/rules.d/90-corectrl.rules" /etc/polkit-1/rules.d/90-corectrl.rules
chmod 0644 /etc/polkit-1/rules.d/90-corectrl.rules
log_success "CoreCtrl PolicyKit rule installed for the 'sudo' group."
fi
# 10. systemd-oomd Out-of-Memory Protection
log_substep "Enabling systemd-oomd out-of-memory protection..."
if command_exists systemctl; then
systemctl enable --now systemd-oomd.service 2>/dev/null || log_warn "Could not enable systemd-oomd.service."
log_success "systemd-oomd enabled (protects against freezes under heavy memory/swap pressure)."
fi
# 11. Sched-ext (scx) Gaming Scheduler
log_substep "Configuring sched-ext (scx) gaming scheduler..."
apt_ensure_keyrings_dir
apt_add_keyring_from_url "https://dl.xanmod.org/archive.key" "xanmod-archive-keyring.gpg" || true
if [[ -f "$SCRIPT_DIR/data/apt/sources.list.d/xanmod.sources" ]]; then
apt_install_sources_file "$SCRIPT_DIR/data/apt/sources.list.d/xanmod.sources" "xanmod.sources"
fi
apt_update
if DEBIAN_FRONTEND=noninteractive apt-get install -y scx-scheds 2>/dev/null; then
if [[ -f /etc/default/scx ]]; then
# scx_lavd (Latency-criticality Aware Virtual Deadline) is purpose-built
# for gaming/interactive desktop workloads; --autopilot lets it balance
# performance vs. powersave automatically based on system load.
sed -i 's|^SCX_SCHEDULER=.*|SCX_SCHEDULER=scx_lavd|' /etc/default/scx
grep -q '^SCX_SCHEDULER=' /etc/default/scx || echo "SCX_SCHEDULER=scx_lavd" >> /etc/default/scx
sed -i "s|^#\?SCX_FLAGS=.*|SCX_FLAGS='--autopilot'|" /etc/default/scx
grep -q '^SCX_FLAGS=' /etc/default/scx || echo "SCX_FLAGS='--autopilot'" >> /etc/default/scx
fi
if command_exists systemctl; then
systemctl daemon-reload 2>/dev/null || true
systemctl enable --now scx.service 2>/dev/null || log_warn "scx.service could not be started - a reboot into a sched_ext-capable kernel (e.g. XanMod) may be required."
fi
log_success "sched-ext gaming scheduler (scx_lavd) configured and enabled."
else
log_warn "Could not install scx-scheds. Skipping sched-ext scheduler configuration."
fi
log_success "Stage 06: System services configuration completed successfully!" log_success "Stage 06: System services configuration completed successfully!"
-6
View File
@@ -47,9 +47,6 @@ fi
if [[ -d "$SCRIPT_DIR/data/skel/.config" ]]; then if [[ -d "$SCRIPT_DIR/data/skel/.config" ]]; then
mkdir -p /etc/skel/.config mkdir -p /etc/skel/.config
cp -r "$SCRIPT_DIR"/data/skel/.config/* /etc/skel/.config/ 2>/dev/null || true cp -r "$SCRIPT_DIR"/data/skel/.config/* /etc/skel/.config/ 2>/dev/null || true
if [[ -d /etc/skel/.config/hypr/scripts ]]; then
chmod 0755 /etc/skel/.config/hypr/scripts/*.sh 2>/dev/null || true
fi
fi fi
# Provision Oh-My-Zsh template in /etc/skel so newly created users receive it automatically # Provision Oh-My-Zsh template in /etc/skel so newly created users receive it automatically
@@ -92,9 +89,6 @@ fi
if [[ -d "$SCRIPT_DIR/data/skel/.config" ]]; then if [[ -d "$SCRIPT_DIR/data/skel/.config" ]]; then
cp -r "$SCRIPT_DIR"/data/skel/.config/* "$TARGET_HOME/.config/" 2>/dev/null || true cp -r "$SCRIPT_DIR"/data/skel/.config/* "$TARGET_HOME/.config/" 2>/dev/null || true
if [[ -d "$TARGET_HOME/.config/hypr/scripts" ]]; then
chmod 0755 "$TARGET_HOME"/.config/hypr/scripts/*.sh 2>/dev/null || true
fi
fi fi
# 4. Install Oh-My-Zsh for Target User (if not already installed) # 4. Install Oh-My-Zsh for Target User (if not already installed)
+32 -13
View File
@@ -41,21 +41,40 @@ else
exit 1 exit 1
fi fi
# 2. Ensure rudimentary Hyprland config is deployed to /etc/skel # 2. Install first-login-setup system-wide (shared by all users, not copied
# into individual home directories) and enable its systemd --user service
# globally, exactly as 'systemctl --global enable' would apply to every user.
log_substep "Installing first-login-setup system-wide to /usr/local/lib/first-login-setup..."
if [[ -d "$SCRIPT_DIR/data/usr/local/lib/first-login-setup" ]]; then
mkdir -p /usr/local/lib/first-login-setup
cp "$SCRIPT_DIR"/data/usr/local/lib/first-login-setup/*.sh /usr/local/lib/first-login-setup/
chmod 0755 /usr/local/lib/first-login-setup/*.sh
chown -R root:root /usr/local/lib/first-login-setup
fi
if [[ -f "$SCRIPT_DIR/data/etc/systemd/user/first-login-setup.service" ]]; then
mkdir -p /etc/systemd/user
cp "$SCRIPT_DIR/data/etc/systemd/user/first-login-setup.service" /etc/systemd/user/first-login-setup.service
if command_exists systemctl; then
systemctl --global enable first-login-setup.service 2>/dev/null || log_warn "Could not globally enable first-login-setup.service via systemctl."
else
# Fallback: replicate what 'systemctl --global enable' would do.
mkdir -p /etc/systemd/user/default.target.wants
ln -sfn "../first-login-setup.service" /etc/systemd/user/default.target.wants/first-login-setup.service
fi
fi
# 3. Ensure rudimentary Hyprland config is deployed to /etc/skel
log_substep "Deploying rudimentary Hyprland configuration to /etc/skel..." log_substep "Deploying rudimentary Hyprland configuration to /etc/skel..."
mkdir -p /etc/skel/.config/hypr/scripts mkdir -p /etc/skel/.config/hypr
if [[ -f "$SCRIPT_DIR/data/skel/.config/hypr/hyprland.conf" ]]; then if [[ -f "$SCRIPT_DIR/data/skel/.config/hypr/hyprland.conf" ]]; then
cp "$SCRIPT_DIR/data/skel/.config/hypr/hyprland.conf" /etc/skel/.config/hypr/hyprland.conf cp "$SCRIPT_DIR/data/skel/.config/hypr/hyprland.conf" /etc/skel/.config/hypr/hyprland.conf
fi fi
if [[ -f "$SCRIPT_DIR/data/skel/.config/hypr/scripts/install-dots-prompt.sh" ]]; then
cp "$SCRIPT_DIR/data/skel/.config/hypr/scripts/install-dots-prompt.sh" /etc/skel/.config/hypr/scripts/install-dots-prompt.sh
chmod 0755 /etc/skel/.config/hypr/scripts/install-dots-prompt.sh
fi
chown -R root:root /etc/skel/.config chown -R root:root /etc/skel/.config
# 3. Deploy rudimentary Hyprland config to target user # 4. Deploy rudimentary Hyprland config to target user
log_substep "Deploying rudimentary Hyprland configuration to $TARGET_HOME/.config/hypr..." log_substep "Deploying rudimentary Hyprland configuration to $TARGET_HOME/.config/hypr..."
mkdir -p "$TARGET_HOME/.config/hypr/scripts" mkdir -p "$TARGET_HOME/.config/hypr"
if [[ -f "$TARGET_HOME/.config/hypr/hyprland.conf" ]]; then if [[ -f "$TARGET_HOME/.config/hypr/hyprland.conf" ]]; then
backup_file_or_dir "$TARGET_HOME/.config/hypr/hyprland.conf" backup_file_or_dir "$TARGET_HOME/.config/hypr/hyprland.conf"
@@ -64,17 +83,17 @@ fi
if [[ -f "$SCRIPT_DIR/data/skel/.config/hypr/hyprland.conf" ]]; then if [[ -f "$SCRIPT_DIR/data/skel/.config/hypr/hyprland.conf" ]]; then
cp "$SCRIPT_DIR/data/skel/.config/hypr/hyprland.conf" "$TARGET_HOME/.config/hypr/hyprland.conf" cp "$SCRIPT_DIR/data/skel/.config/hypr/hyprland.conf" "$TARGET_HOME/.config/hypr/hyprland.conf"
fi fi
if [[ -f "$SCRIPT_DIR/data/skel/.config/hypr/scripts/install-dots-prompt.sh" ]]; then
cp "$SCRIPT_DIR/data/skel/.config/hypr/scripts/install-dots-prompt.sh" "$TARGET_HOME/.config/hypr/scripts/install-dots-prompt.sh"
chmod 0755 "$TARGET_HOME/.config/hypr/scripts/install-dots-prompt.sh"
fi
chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME/.config/hypr" chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME/.config/hypr"
log_success "Rudimentary Hyprland configuration deployed:" log_success "Rudimentary Hyprland configuration deployed:"
log_info " - SUPER+ENTER: Kitty Terminal" log_info " - SUPER+ENTER: Kitty Terminal"
log_info " - SUPER+Q: Close Active Window" log_info " - SUPER+Q: Close Active Window"
log_info " - Login Prompt: Asks user after login to choose Dotfiles setup & installs JetBrains Junie CLI + AwesomeJunieSkills" log_info " - Ersteinrichtung (Dotfiles, Oh-My-Zsh, JetBrains Junie, Claude Code & Skills) startet automatisch beim"
log_info " nächsten Login jedes Benutzers über den systemweiten systemd --user Service"
log_info " 'first-login-setup.service' (unabhängig vom gewählten Compositor, NICHT über"
log_info " hyprland.conf). Läuft nur beim ersten Login bzw. nach 'Später' erneut."
log_info " Manueller Aufruf jederzeit möglich mit: /usr/local/lib/first-login-setup/first-login-setup.sh --force"
create_timeshift_snapshot "Stage 10: Post-Hyprland desktop setup" "O" create_timeshift_snapshot "Stage 10: Post-Hyprland desktop setup" "O"
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# ==============================================================================
# stages/11-linutil.sh - Linutil (Chris Titus Tech System Toolbox), final install step
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$SCRIPT_DIR/lib/utils.sh"
source "$SCRIPT_DIR/lib/btrfs.sh"
if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then
# shellcheck source=/dev/null
source "$SCRIPT_DIR/config/setup.conf"
fi
setup_err_trap
log_step "Running Stage 11: Linutil (Chris Titus Tech System Toolbox)"
require_root
TARGET_USER="$(get_target_user)"
log_info "Launching Linutil as target user '$TARGET_USER'..."
create_timeshift_snapshot "Stage 11: Pre-Linutil" "B"
if command_exists curl; then
if has_tty; then
# Linutil is an interactive TUI - run it as the target user (it
# elevates via sudo itself where needed), attached to the controlling
# terminal so its menu can actually receive keystrokes.
if run_as_target_user bash -c 'sh <(curl -fsSL https://christitus.com/linux)' < /dev/tty > /dev/tty; then
log_success "Linutil finished."
else
log_warn "Linutil exited with a non-zero status. Check logs if needed."
fi
else
log_warn "No controlling terminal available - Linutil's interactive menu cannot be displayed. Skipping."
log_info "Run it manually later with: curl -fsSL https://christitus.com/linux | sh"
fi
else
log_error "curl is required to run Linutil."
exit 1
fi
create_timeshift_snapshot "Stage 11: Post-Linutil" "O"
log_success "Stage 11: Linutil completed successfully!"