summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
committerNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
commita2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 (patch)
tree1a8769217f84bfe9d6216fafaadaf8cdd876d457 /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros
parent34b9c34f982b2596cfa9e368a2d7f74e9a17477c (diff)
downloadRaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.tar.gz
RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/arch.go807
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go721
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base_test.go216
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/debian.go706
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/factory.go19
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/fedora.go550
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go700
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/interface.go158
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/manual_packages.go663
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/manual_packages_test.go122
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/minimal_install.go44
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/opensuse.go856
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/osinfo.go122
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/ubuntu.go702
14 files changed, 0 insertions, 6386 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/arch.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/arch.go
deleted file mode 100644
index b412f88..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/arch.go
+++ /dev/null
@@ -1,807 +0,0 @@
-package distros
-
-import (
- "context"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "slices"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-func init() {
- Register("arch", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("archarm", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("archcraft", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("cachyos", "#08A283", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("catos", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("endeavouros", "#7F3FBF", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("manjaro", "#35BF5C", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("obarun", "#2494be", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("garuda", "#cba6f7", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("artix", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
- Register("XeroLinux", "#888fe2", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewArchDistribution(config, logChan)
- })
-}
-
-type ArchDistribution struct {
- *BaseDistribution
- *ManualPackageInstaller
- config DistroConfig
-}
-
-func NewArchDistribution(config DistroConfig, logChan chan<- string) *ArchDistribution {
- base := NewBaseDistribution(logChan)
- return &ArchDistribution{
- BaseDistribution: base,
- ManualPackageInstaller: &ManualPackageInstaller{BaseDistribution: base},
- config: config,
- }
-}
-
-func (a *ArchDistribution) GetID() string {
- return a.config.ID
-}
-
-func (a *ArchDistribution) GetColorHex() string {
- return a.config.ColorHex
-}
-
-func (a *ArchDistribution) GetFamily() DistroFamily {
- return a.config.Family
-}
-
-func (a *ArchDistribution) GetPackageManager() PackageManagerType {
- return PackageManagerPacman
-}
-
-func (a *ArchDistribution) DetectDependencies(ctx context.Context, wm deps.WindowManager) ([]deps.Dependency, error) {
- return a.DetectDependenciesWithTerminal(ctx, wm, deps.TerminalGhostty)
-}
-
-func (a *ArchDistribution) DetectDependenciesWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]deps.Dependency, error) {
- var dependencies []deps.Dependency
-
- // DMS at the top (shell is prominent)
- dependencies = append(dependencies, a.detectDMS())
-
- // Terminal with choice support
- dependencies = append(dependencies, a.detectSpecificTerminal(terminal))
-
- // Common detections using base methods
- dependencies = append(dependencies, a.detectGit())
- dependencies = append(dependencies, a.detectWindowManager(wm))
- dependencies = append(dependencies, a.detectQuickshell())
- dependencies = append(dependencies, a.detectDMSGreeter())
- dependencies = append(dependencies, a.detectXDGPortal())
- dependencies = append(dependencies, a.detectAccountsService())
-
- // Hyprland-specific tools
- if wm == deps.WindowManagerHyprland {
- dependencies = append(dependencies, a.detectHyprlandTools()...)
- }
-
- // Niri-specific tools
- if wm == deps.WindowManagerNiri {
- dependencies = append(dependencies, a.detectXwaylandSatellite())
- }
-
- dependencies = append(dependencies, a.detectMatugen())
- dependencies = append(dependencies, a.detectDgop())
-
- return dependencies, nil
-}
-
-func (a *ArchDistribution) detectXDGPortal() deps.Dependency {
- return a.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", a.packageInstalled("xdg-desktop-portal-gtk"))
-}
-
-func (a *ArchDistribution) detectAccountsService() deps.Dependency {
- return a.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", a.packageInstalled("accountsservice"))
-}
-
-func (a *ArchDistribution) detectDMSGreeter() deps.Dependency {
- return a.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", a.packageInstalled("greetd-dms-greeter-git"))
-}
-
-func (a *ArchDistribution) packageInstalled(pkg string) bool {
- cmd := exec.Command("pacman", "-Q", pkg)
- err := cmd.Run()
- return err == nil
-}
-
-// parseSRCINFODeps reads a .SRCINFO file and returns runtime dep and makedep package
-func parseSRCINFODeps(srcinfoPath string) (deps []string, makedeps []string, err error) {
- data, err := os.ReadFile(srcinfoPath)
- if err != nil {
- return nil, nil, err
- }
- for _, line := range strings.Split(string(data), "\n") {
- line = strings.TrimSpace(line)
- var pkg string
- var target *[]string
- switch {
- case strings.HasPrefix(line, "makedepends = "):
- pkg = strings.TrimPrefix(line, "makedepends = ")
- target = &makedeps
- case strings.HasPrefix(line, "depends = "):
- pkg = strings.TrimPrefix(line, "depends = ")
- target = &deps
- default:
- continue
- }
- // Strip version constraint (>=, <=, >, <, =) and colon-descriptions
- if idx := strings.IndexAny(pkg, "><:="); idx >= 0 {
- pkg = pkg[:idx]
- }
- pkg = strings.TrimSpace(pkg)
- if pkg != "" {
- *target = append(*target, pkg)
- }
- }
- return deps, makedeps, nil
-}
-
-func (a *ArchDistribution) isInSystemRepo(pkg string) bool {
- return exec.Command("pacman", "-Si", pkg).Run() == nil
-}
-
-func (a *ArchDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
- return a.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
-}
-
-func (a *ArchDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping {
- packages := map[string]PackageMapping{
- "dms (DankMaterialShell)": a.getDMSMapping(variants["dms (DankMaterialShell)"]),
- "git": {Name: "git", Repository: RepoTypeSystem},
- "quickshell": a.getQuickshellMapping(variants["quickshell"]),
- "dms-greeter": {Name: "greetd-dms-greeter-git", Repository: RepoTypeAUR},
- "matugen": a.getMatugenMapping(variants["matugen"]),
- "dgop": {Name: "dgop", Repository: RepoTypeSystem},
- "ghostty": {Name: "ghostty", Repository: RepoTypeSystem},
- "kitty": {Name: "kitty", Repository: RepoTypeSystem},
- "alacritty": {Name: "alacritty", Repository: RepoTypeSystem},
- "xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
- "accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
- }
-
- switch wm {
- case deps.WindowManagerHyprland:
- packages["hyprland"] = a.getHyprlandMapping(variants["hyprland"])
- packages["hyprctl"] = a.getHyprlandMapping(variants["hyprland"])
- packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
- case deps.WindowManagerNiri:
- packages["niri"] = a.getNiriMapping(variants["niri"])
- packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeSystem}
- }
-
- return packages
-}
-
-func (a *ArchDistribution) getQuickshellMapping(variant deps.PackageVariant) PackageMapping {
- if forceQuickshellGit || variant == deps.VariantGit {
- return PackageMapping{Name: "quickshell-git", Repository: RepoTypeAUR}
- }
- // ! TODO - for now we're only forcing quickshell-git on ARCH, as other distros use DL repos which pin a newer quickshell
- return PackageMapping{Name: "quickshell-git", Repository: RepoTypeAUR}
-}
-
-func (a *ArchDistribution) getHyprlandMapping(_ deps.PackageVariant) PackageMapping {
- return PackageMapping{Name: "hyprland", Repository: RepoTypeSystem}
-}
-
-func (a *ArchDistribution) getNiriMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "niri-git", Repository: RepoTypeAUR}
- }
- return PackageMapping{Name: "niri", Repository: RepoTypeSystem}
-}
-
-func (a *ArchDistribution) getMatugenMapping(variant deps.PackageVariant) PackageMapping {
- if runtime.GOARCH == "arm64" {
- return PackageMapping{Name: "matugen-git", Repository: RepoTypeAUR}
- }
-
- if variant == deps.VariantGit {
- return PackageMapping{Name: "matugen-git", Repository: RepoTypeAUR}
- }
- return PackageMapping{Name: "matugen", Repository: RepoTypeSystem}
-}
-
-func (a *ArchDistribution) getDMSMapping(variant deps.PackageVariant) PackageMapping {
- if forceDMSGit || variant == deps.VariantGit {
- return PackageMapping{Name: "dms-shell-git", Repository: RepoTypeAUR}
- }
-
- if a.packageInstalled("dms-shell-git") {
- return PackageMapping{Name: "dms-shell-git", Repository: RepoTypeAUR}
- }
-
- return PackageMapping{Name: "dms-shell", Repository: RepoTypeSystem}
-}
-
-func (a *ArchDistribution) detectXwaylandSatellite() deps.Dependency {
- status := deps.StatusMissing
- if a.commandExists("xwayland-satellite") {
- status = deps.StatusInstalled
- }
-
- return deps.Dependency{
- Name: "xwayland-satellite",
- Status: status,
- Description: "Xwayland support",
- Required: true,
- }
-}
-
-func (a *ArchDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.06,
- Step: "Checking base-devel...",
- IsComplete: false,
- LogOutput: "Checking if base-devel is installed",
- }
-
- checkCmd := exec.CommandContext(ctx, "pacman", "-Qq", "base-devel")
- if err := checkCmd.Run(); err == nil {
- a.log("base-devel already installed")
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.10,
- Step: "base-devel already installed",
- IsComplete: false,
- LogOutput: "base-devel is already installed on the system",
- }
- return nil
- }
-
- a.log("Installing base-devel...")
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.08,
- Step: "Installing base-devel...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo pacman -S --needed --noconfirm base-devel",
- LogOutput: "Installing base-devel development tools",
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword, "pacman -S --needed --noconfirm base-devel")
- if err := a.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.10); err != nil {
- return fmt.Errorf("failed to install base-devel: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.12,
- Step: "base-devel installation complete",
- IsComplete: false,
- LogOutput: "base-devel successfully installed",
- }
-
- return nil
-}
-
-func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, disabledFlags map[string]bool, skipGlobalUseFlags bool, progressChan chan<- InstallProgressMsg) error {
- // Phase 1: Check Prerequisites
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.05,
- Step: "Checking system prerequisites...",
- IsComplete: false,
- LogOutput: "Starting prerequisite check...",
- }
-
- if err := a.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install prerequisites: %w", err)
- }
-
- systemPkgs, aurPkgs, manualPkgs, variantMap := a.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
-
- if slices.Contains(aurPkgs, "quickshell-git") && slices.Contains(systemPkgs, "dms-shell") {
- if err := a.preinstallQuickshellGit(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to preinstall quickshell-git: %w", err)
- }
- aurPkgs = slices.DeleteFunc(aurPkgs, func(p string) bool { return p == "quickshell-git" })
- }
-
- // Phase 3: System Packages
- if len(systemPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.35,
- Step: fmt.Sprintf("Installing %d system packages...", len(systemPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(systemPkgs, ", ")),
- }
- if err := a.installSystemPackages(ctx, systemPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install system packages: %w", err)
- }
- }
-
- // Phase 4: AUR Packages
- if len(aurPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.65,
- Step: fmt.Sprintf("Installing %d AUR packages...", len(aurPkgs)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Installing AUR packages: %s", strings.Join(aurPkgs, ", ")),
- }
- if err := a.installAURPackages(ctx, aurPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install AUR packages: %w", err)
- }
- }
-
- // Phase 5: Manual Builds
- if len(manualPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.85,
- Step: fmt.Sprintf("Building %d packages from source...", len(manualPkgs)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
- }
- if err := a.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install manual packages: %w", err)
- }
- }
-
- // Phase 6: Configuration
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.90,
- Step: "Configuring system...",
- IsComplete: false,
- LogOutput: "Starting post-installation configuration...",
- }
-
- terminal := a.DetectTerminalFromDeps(dependencies)
- if err := a.WriteEnvironmentConfig(terminal); err != nil {
- a.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
- }
-
- if err := a.WriteWindowManagerConfig(wm); err != nil {
- a.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
- }
-
- if err := a.EnableDMSService(ctx, wm); err != nil {
- a.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
- }
-
- // Phase 7: Complete
- progressChan <- InstallProgressMsg{
- Phase: PhaseComplete,
- Progress: 1.0,
- Step: "Installation complete!",
- IsComplete: true,
- LogOutput: "All packages installed and configured successfully",
- }
-
- return nil
-}
-
-func (a *ArchDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, []string, map[string]deps.PackageVariant) {
- systemPkgs := []string{}
- aurPkgs := []string{}
- manualPkgs := []string{}
-
- variantMap := make(map[string]deps.PackageVariant)
- for _, dep := range dependencies {
- variantMap[dep.Name] = dep.Variant
- }
-
- packageMap := a.GetPackageMappingWithVariants(wm, variantMap)
-
- for _, dep := range dependencies {
- if disabledFlags[dep.Name] {
- continue
- }
-
- if dep.Status == deps.StatusInstalled && !reinstallFlags[dep.Name] {
- continue
- }
-
- pkgInfo, exists := packageMap[dep.Name]
- if !exists {
- manualPkgs = append(manualPkgs, dep.Name)
- continue
- }
-
- switch pkgInfo.Repository {
- case RepoTypeAUR:
- aurPkgs = append(aurPkgs, pkgInfo.Name)
- case RepoTypeSystem:
- systemPkgs = append(systemPkgs, pkgInfo.Name)
- case RepoTypeManual:
- manualPkgs = append(manualPkgs, dep.Name)
- }
- }
-
- return systemPkgs, aurPkgs, manualPkgs, variantMap
-}
-
-func (a *ArchDistribution) preinstallQuickshellGit(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if a.packageInstalled("quickshell-git") {
- return nil
- }
-
- if a.packageInstalled("quickshell") {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.15,
- Step: "Removing stable quickshell...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo pacman -Rdd --noconfirm quickshell",
- LogOutput: "Removing stable quickshell so quickshell-git can be installed",
- }
- cmd := ExecSudoCommand(ctx, sudoPassword, "pacman -Rdd --noconfirm quickshell")
- if err := a.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.15, 0.18); err != nil {
- return fmt.Errorf("failed to remove stable quickshell: %w", err)
- }
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.18,
- Step: "Building quickshell-git before system packages...",
- IsComplete: false,
- CommandInfo: "Installing quickshell-git ahead of dms-shell to avoid conflict",
- }
- return a.installSingleAURPackage(ctx, "quickshell-git", sudoPassword, progressChan, 0.18, 0.32)
-}
-
-func (a *ArchDistribution) installSystemPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- a.log(fmt.Sprintf("Installing system packages: %s", strings.Join(packages, ", ")))
-
- args := []string{"pacman", "-S", "--needed", "--noconfirm"}
- if slices.Contains(packages, "dms-shell") {
- args = append(args, "--assume-installed", "dms-shell-compositor=1")
- }
- args = append(args, packages...)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.40,
- Step: "Installing system packages...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return a.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
-}
-
-func (a *ArchDistribution) installAURPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- a.log(fmt.Sprintf("Installing AUR packages manually: %s", strings.Join(packages, ", ")))
-
- hasNiri := false
- for _, pkg := range packages {
- if pkg == "niri-git" {
- hasNiri = true
- }
- }
-
- // If niri is in the list, install makepkg-git-lfs-proto first if not already installed
- if hasNiri {
- if !a.packageInstalled("makepkg-git-lfs-proto") {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.65,
- Step: "Installing makepkg-git-lfs-proto for niri...",
- IsComplete: false,
- CommandInfo: "Installing prerequisite for niri-git",
- }
-
- if err := a.installSingleAURPackage(ctx, "makepkg-git-lfs-proto", sudoPassword, progressChan, 0.65, 0.67); err != nil {
- return fmt.Errorf("failed to install makepkg-git-lfs-proto prerequisite for niri: %w", err)
- }
- }
- }
-
- // Reorder packages to ensure dms-shell-git dependencies are installed first
- orderedPackages := a.reorderAURPackages(packages)
-
- baseProgress := 0.67
- progressStep := 0.13 / float64(len(orderedPackages))
-
- for i, pkg := range orderedPackages {
- currentProgress := baseProgress + (float64(i) * progressStep)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: currentProgress,
- Step: fmt.Sprintf("Installing AUR package %s (%d/%d)...", pkg, i+1, len(packages)),
- IsComplete: false,
- CommandInfo: fmt.Sprintf("Building and installing %s", pkg),
- }
-
- if err := a.installSingleAURPackage(ctx, pkg, sudoPassword, progressChan, currentProgress, currentProgress+progressStep); err != nil {
- return fmt.Errorf("failed to install AUR package %s: %w", pkg, err)
- }
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.80,
- Step: "All AUR packages installed successfully",
- IsComplete: false,
- LogOutput: fmt.Sprintf("Successfully installed AUR packages: %s", strings.Join(packages, ", ")),
- }
-
- return nil
-}
-
-func (a *ArchDistribution) reorderAURPackages(packages []string) []string {
- dmsDepencies := []string{"quickshell", "quickshell-git", "dgop"}
-
- var deps []string
- var others []string
- var dmsShell []string
-
- for _, pkg := range packages {
- if pkg == "dms-shell-git" {
- dmsShell = append(dmsShell, pkg)
- } else {
- isDep := false
- if slices.Contains(dmsDepencies, pkg) {
- deps = append(deps, pkg)
- isDep = true
- }
- if !isDep {
- others = append(others, pkg)
- }
- }
- }
-
- result := append(deps, others...)
- result = append(result, dmsShell...)
- return result
-}
-
-func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sudoPassword string, progressChan chan<- InstallProgressMsg, startProgress, endProgress float64) error {
- return a.installSingleAURPackageInternal(ctx, pkg, sudoPassword, progressChan, startProgress, endProgress, make(map[string]bool))
-}
-
-func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context, pkg, sudoPassword string, progressChan chan<- InstallProgressMsg, startProgress, endProgress float64, visited map[string]bool) error {
- if visited[pkg] {
- a.log(fmt.Sprintf("Skipping %s (already being installed, cycle detected)", pkg))
- return nil
- }
- visited[pkg] = true
-
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get user home directory: %w", err)
- }
-
- buildDir := filepath.Join(homeDir, ".cache", "dankinstall", "aur-builds", pkg)
-
- // Clean up any existing cache first
- if err := os.RemoveAll(buildDir); err != nil {
- a.log(fmt.Sprintf("Warning: failed to clean existing cache for %s: %v", pkg, err))
- }
-
- if err := os.MkdirAll(buildDir, 0o755); err != nil {
- return fmt.Errorf("failed to create build directory: %w", err)
- }
- defer func() {
- if removeErr := os.RemoveAll(buildDir); removeErr != nil {
- a.log(fmt.Sprintf("Warning: failed to cleanup build directory %s: %v", buildDir, removeErr))
- }
- }()
-
- // Clone the AUR package
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: startProgress + 0.1*(endProgress-startProgress),
- Step: fmt.Sprintf("Cloning %s from AUR...", pkg),
- IsComplete: false,
- CommandInfo: fmt.Sprintf("git clone https://aur.archlinux.org/%s.git", pkg),
- }
-
- cloneCmd := exec.CommandContext(ctx, "git", "clone", fmt.Sprintf("https://aur.archlinux.org/%s.git", pkg), filepath.Join(buildDir, pkg))
- if err := a.runWithProgress(cloneCmd, progressChan, PhaseAURPackages, startProgress+0.1*(endProgress-startProgress), startProgress+0.2*(endProgress-startProgress)); err != nil {
- return fmt.Errorf("failed to clone %s: %w", pkg, err)
- }
-
- packageDir := filepath.Join(buildDir, pkg)
-
- if pkg == "niri-git" {
- pkgbuildPath := filepath.Join(packageDir, "PKGBUILD")
- sedCmd := exec.CommandContext(ctx, "sed", "-i", "s/makepkg-git-lfs-proto//g", pkgbuildPath)
- if err := sedCmd.Run(); err != nil {
- return fmt.Errorf("failed to patch PKGBUILD for niri-git: %w", err)
- }
-
- srcinfoPath := filepath.Join(packageDir, ".SRCINFO")
- sedCmd2 := exec.CommandContext(ctx, "sed", "-i", "/makedepends = makepkg-git-lfs-proto/d", srcinfoPath)
- if err := sedCmd2.Run(); err != nil {
- return fmt.Errorf("failed to patch .SRCINFO for niri-git: %w", err)
- }
- }
-
- if pkg == "dms-shell-git" {
- srcinfoPath := filepath.Join(packageDir, ".SRCINFO")
- depsToRemove := []string{
- "depends = quickshell",
- "depends = dgop",
- }
-
- for _, dep := range depsToRemove {
- sedCmd := exec.CommandContext(ctx, "sed", "-i", fmt.Sprintf("/%s/d", dep), srcinfoPath)
- if err := sedCmd.Run(); err != nil {
- return fmt.Errorf("failed to remove dependency %s from .SRCINFO for %s: %w", dep, pkg, err)
- }
- }
- }
-
- // Remove all optdepends from .SRCINFO for all packages
- srcinfoPath := filepath.Join(packageDir, ".SRCINFO")
- optdepsCmd := exec.CommandContext(ctx, "sed", "-i", "/^[[:space:]]*optdepends = /d", srcinfoPath)
- if err := optdepsCmd.Run(); err != nil {
- return fmt.Errorf("failed to remove optdepends from .SRCINFO for %s: %w", pkg, err)
- }
-
- srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
- {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: startProgress + 0.3*(endProgress-startProgress),
- Step: fmt.Sprintf("Resolving dependencies for %s...", pkg),
- IsComplete: false,
- CommandInfo: "Classifying dependencies as system or AUR",
- }
-
- runtimeDeps, makeDeps, err := parseSRCINFODeps(srcinfoPath)
- if err != nil {
- return fmt.Errorf("failed to parse .SRCINFO for %s: %w", pkg, err)
- }
-
- seen := make(map[string]bool)
- var systemPkgs []string
- var aurPkgs []string
-
- for _, dep := range append(runtimeDeps, makeDeps...) {
- if seen[dep] || a.packageInstalled(dep) {
- continue
- }
- seen[dep] = true
- if a.isInSystemRepo(dep) {
- systemPkgs = append(systemPkgs, dep)
- } else {
- aurPkgs = append(aurPkgs, dep)
- }
- }
-
- if len(systemPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: startProgress + 0.32*(endProgress-startProgress),
- Step: fmt.Sprintf("Installing %d system dependencies for %s...", len(systemPkgs), pkg),
- IsComplete: false,
- CommandInfo: fmt.Sprintf("sudo pacman -S --needed --noconfirm %s", strings.Join(systemPkgs, " ")),
- }
- if err := a.installSystemPackages(ctx, systemPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install system dependencies for %s: %w", pkg, err)
- }
- }
-
- for _, aurDep := range aurPkgs {
- a.log(fmt.Sprintf("Dependency %s is AUR-only, building from source...", aurDep))
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: startProgress + 0.35*(endProgress-startProgress),
- Step: fmt.Sprintf("Installing AUR dependency %s for %s...", aurDep, pkg),
- IsComplete: false,
- CommandInfo: fmt.Sprintf("Building AUR dependency: %s", aurDep),
- }
- if err := a.installSingleAURPackageInternal(ctx, aurDep, sudoPassword, progressChan,
- startProgress+0.35*(endProgress-startProgress),
- startProgress+0.39*(endProgress-startProgress),
- visited,
- ); err != nil {
- return fmt.Errorf("failed to install AUR dependency %s for %s: %w", aurDep, pkg, err)
- }
- }
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: startProgress + 0.4*(endProgress-startProgress),
- Step: fmt.Sprintf("Building %s...", pkg),
- IsComplete: false,
- CommandInfo: "makepkg --noconfirm",
- }
-
- buildCmd := exec.CommandContext(ctx, "makepkg", "--noconfirm")
- buildCmd.Dir = packageDir
- buildCmd.Env = append(os.Environ(), "PKGEXT=.pkg.tar")
-
- if err := a.runWithProgress(buildCmd, progressChan, PhaseAURPackages, startProgress+0.4*(endProgress-startProgress), startProgress+0.7*(endProgress-startProgress)); err != nil {
- return fmt.Errorf("failed to build %s: %w", pkg, err)
- }
-
- // Find built package file
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: startProgress + 0.7*(endProgress-startProgress),
- Step: fmt.Sprintf("Installing %s...", pkg),
- IsComplete: false,
- CommandInfo: "sudo pacman -U built-package",
- }
-
- var files []string
- matches, _ := filepath.Glob(filepath.Join(packageDir, "*.pkg.tar*"))
- files = matches
-
- if len(files) == 0 {
- return fmt.Errorf("no package files found after building %s", pkg)
- }
-
- installArgs := []string{"pacman", "-U", "--noconfirm"}
- installArgs = append(installArgs, files...)
-
- installCmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(installArgs, " "))
-
- fileNames := make([]string, len(files))
- for i, f := range files {
- fileNames[i] = filepath.Base(f)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: startProgress + 0.7*(endProgress-startProgress),
- LogOutput: fmt.Sprintf("Installing packages: %s", strings.Join(fileNames, ", ")),
- }
-
- if err := a.runWithProgress(installCmd, progressChan, PhaseAURPackages, startProgress+0.7*(endProgress-startProgress), endProgress); err != nil {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: startProgress,
- LogOutput: fmt.Sprintf("ERROR: pacman -U failed for %s with error: %v", pkg, err),
- Error: err,
- }
- return fmt.Errorf("failed to install built package %s: %w", pkg, err)
- }
-
- a.log(fmt.Sprintf("Successfully installed AUR package: %s", pkg))
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go
deleted file mode 100644
index 63555c8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go
+++ /dev/null
@@ -1,721 +0,0 @@
-package distros
-
-import (
- "bufio"
- "context"
- _ "embed"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "runtime"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/version"
-)
-
-const (
- forceQuickshellGit = false
- forceDMSGit = false
-)
-
-// BaseDistribution provides common functionality for all distributions
-type BaseDistribution struct {
- logChan chan<- string
-}
-
-// NewBaseDistribution creates a new base distribution
-func NewBaseDistribution(logChan chan<- string) *BaseDistribution {
- return &BaseDistribution{
- logChan: logChan,
- }
-}
-
-// Common helper methods
-func (b *BaseDistribution) commandExists(cmd string) bool {
- _, err := exec.LookPath(cmd)
- return err == nil
-}
-
-func (b *BaseDistribution) CommandExists(cmd string) bool {
- return b.commandExists(cmd)
-}
-
-func (b *BaseDistribution) log(message string) {
- if b.logChan != nil {
- b.logChan <- message
- }
-}
-
-func (b *BaseDistribution) logError(message string, err error) {
- errorMsg := fmt.Sprintf("ERROR: %s: %v", message, err)
- b.log(errorMsg)
-}
-
-// escapeSingleQuotes escapes single quotes in a string for safe use in bash single-quoted strings.
-// It replaces each ' with '\” which closes the quote, adds an escaped quote, and reopens the quote.
-// This prevents shell injection and syntax errors when passwords contain single quotes or apostrophes.
-func escapeSingleQuotes(s string) string {
- return strings.ReplaceAll(s, "'", "'\\''")
-}
-
-// MakeSudoCommand creates a command string that safely passes password to sudo.
-// This helper escapes special characters in the password to prevent shell injection
-// and syntax errors when passwords contain single quotes, apostrophes, or other special chars.
-func MakeSudoCommand(sudoPassword string, command string) string {
- return fmt.Sprintf("echo '%s' | sudo -S %s", escapeSingleQuotes(sudoPassword), command)
-}
-
-// ExecSudoCommand creates an exec.Cmd that runs a command with sudo using the provided password.
-// The password is properly escaped to prevent shell injection and syntax errors.
-func ExecSudoCommand(ctx context.Context, sudoPassword string, command string) *exec.Cmd {
- cmdStr := MakeSudoCommand(sudoPassword, command)
- return exec.CommandContext(ctx, "bash", "-c", cmdStr)
-}
-
-func (b *BaseDistribution) detectCommand(name, description string) deps.Dependency {
- status := deps.StatusMissing
- if b.commandExists(name) {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: name,
- Status: status,
- Description: description,
- Required: true,
- }
-}
-
-func (b *BaseDistribution) detectPackage(name, description string, installed bool) deps.Dependency {
- status := deps.StatusMissing
- if installed {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: name,
- Status: status,
- Description: description,
- Required: true,
- }
-}
-
-func (b *BaseDistribution) detectOptionalPackage(name, description string, installed bool) deps.Dependency {
- status := deps.StatusMissing
- if installed {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: name,
- Status: status,
- Description: description,
- Required: false,
- }
-}
-
-func (b *BaseDistribution) detectGit() deps.Dependency {
- return b.detectCommand("git", "Version control system")
-}
-
-func (b *BaseDistribution) detectMatugen() deps.Dependency {
- return b.detectCommand("matugen", "Material Design color generation tool")
-}
-
-func (b *BaseDistribution) detectDgop() deps.Dependency {
- return b.detectCommand("dgop", "Desktop portal management tool")
-}
-
-func (b *BaseDistribution) detectDMS() deps.Dependency {
- dmsPath := filepath.Join(os.Getenv("HOME"), ".config/quickshell/dms")
-
- status := deps.StatusMissing
- currentVersion := ""
-
- if _, err := os.Stat(dmsPath); err == nil {
- status = deps.StatusInstalled
-
- // Only get current version, don't check for updates (lazy loading)
- current, err := version.GetCurrentDMSVersion()
- if err == nil {
- currentVersion = current
- }
- }
-
- dep := deps.Dependency{
- Name: "dms (DankMaterialShell)",
- Status: status,
- Description: "Desktop Management System configuration",
- Required: true,
- CanToggle: true,
- }
-
- if currentVersion != "" {
- dep.Version = currentVersion
- }
-
- return dep
-}
-
-func (b *BaseDistribution) detectSpecificTerminal(terminal deps.Terminal) deps.Dependency {
- switch terminal {
- case deps.TerminalGhostty:
- status := deps.StatusMissing
- if b.commandExists("ghostty") {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: "ghostty",
- Status: status,
- Description: "A fast, native terminal emulator built in Zig.",
- Required: true,
- }
- case deps.TerminalKitty:
- status := deps.StatusMissing
- if b.commandExists("kitty") {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: "kitty",
- Status: status,
- Description: "A feature-rich, customizable terminal emulator.",
- Required: true,
- }
- case deps.TerminalAlacritty:
- status := deps.StatusMissing
- if b.commandExists("alacritty") {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: "alacritty",
- Status: status,
- Description: "A simple terminal emulator. (No dynamic theming)",
- Required: true,
- }
- default:
- return b.detectSpecificTerminal(deps.TerminalGhostty)
- }
-}
-
-func (b *BaseDistribution) detectHyprlandTools() []deps.Dependency {
- var dependencies []deps.Dependency
-
- tools := []struct {
- name string
- description string
- }{
- {"hyprctl", "Hyprland control utility"},
- {"jq", "JSON processor"},
- }
-
- for _, tool := range tools {
- status := deps.StatusMissing
- if b.commandExists(tool.name) {
- status = deps.StatusInstalled
- }
-
- dependencies = append(dependencies, deps.Dependency{
- Name: tool.name,
- Status: status,
- Description: tool.description,
- Required: true,
- })
- }
-
- return dependencies
-}
-
-func (b *BaseDistribution) detectQuickshell() deps.Dependency {
- if !b.commandExists("qs") {
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusMissing,
- Description: "QtQuick based desktop shell toolkit",
- Required: true,
- Variant: deps.VariantStable,
- CanToggle: true,
- }
- }
-
- cmd := exec.Command("qs", "--version")
- output, err := cmd.Output()
- if err != nil {
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusNeedsReinstall,
- Description: "QtQuick based desktop shell toolkit (version check failed)",
- Required: true,
- Variant: deps.VariantStable,
- CanToggle: true,
- }
- }
-
- versionStr := string(output)
- versionRegex := regexp.MustCompile(`quickshell (\d+\.\d+\.\d+)`)
- matches := versionRegex.FindStringSubmatch(versionStr)
-
- if len(matches) < 2 {
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusNeedsReinstall,
- Description: "QtQuick based desktop shell toolkit (unknown version)",
- Required: true,
- Variant: deps.VariantStable,
- CanToggle: true,
- }
- }
-
- version := matches[1]
- variant := deps.VariantStable
- if strings.Contains(versionStr, "git") || strings.Contains(versionStr, "+") {
- variant = deps.VariantGit
- }
-
- if b.versionCompare(version, "0.2.0") >= 0 {
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusInstalled,
- Version: version,
- Description: "QtQuick based desktop shell toolkit",
- Required: true,
- Variant: variant,
- CanToggle: true,
- }
- }
-
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusNeedsUpdate,
- Variant: variant,
- CanToggle: true,
- Version: version,
- Description: "QtQuick based desktop shell toolkit (needs 0.2.0+)",
- Required: true,
- }
-}
-
-func (b *BaseDistribution) detectWindowManager(wm deps.WindowManager) deps.Dependency {
- switch wm {
- case deps.WindowManagerHyprland:
- status := deps.StatusMissing
- variant := deps.VariantStable
- version := ""
-
- if b.commandExists("hyprland") || b.commandExists("Hyprland") {
- status = deps.StatusInstalled
- cmd := exec.Command("hyprctl", "version")
- if output, err := cmd.Output(); err == nil {
- outStr := string(output)
- if strings.Contains(outStr, "git") || strings.Contains(outStr, "dirty") {
- variant = deps.VariantGit
- }
- if versionRegex := regexp.MustCompile(`v(\d+\.\d+\.\d+)`); versionRegex.MatchString(outStr) {
- matches := versionRegex.FindStringSubmatch(outStr)
- if len(matches) > 1 {
- version = matches[1]
- }
- }
- }
- }
- return deps.Dependency{
- Name: "hyprland",
- Status: status,
- Version: version,
- Description: "Dynamic tiling Wayland compositor",
- Required: true,
- Variant: variant,
- CanToggle: true,
- }
- case deps.WindowManagerNiri:
- status := deps.StatusMissing
- variant := deps.VariantStable
- version := ""
-
- if b.commandExists("niri") {
- status = deps.StatusInstalled
- cmd := exec.Command("niri", "--version")
- if output, err := cmd.Output(); err == nil {
- outStr := string(output)
- if strings.Contains(outStr, "git") || strings.Contains(outStr, "+") {
- variant = deps.VariantGit
- }
- if versionRegex := regexp.MustCompile(`niri (\d+\.\d+)`); versionRegex.MatchString(outStr) {
- matches := versionRegex.FindStringSubmatch(outStr)
- if len(matches) > 1 {
- version = matches[1]
- }
- }
- }
- }
- return deps.Dependency{
- Name: "niri",
- Status: status,
- Version: version,
- Description: "Scrollable-tiling Wayland compositor",
- Required: true,
- Variant: variant,
- CanToggle: true,
- }
- default:
- return deps.Dependency{
- Name: "unknown-wm",
- Status: deps.StatusMissing,
- Description: "Unknown window manager",
- Required: true,
- }
- }
-}
-
-// Version comparison helper
-func (b *BaseDistribution) versionCompare(v1, v2 string) int {
- parts1 := strings.Split(v1, ".")
- parts2 := strings.Split(v2, ".")
-
- for i := 0; i < len(parts1) && i < len(parts2); i++ {
- if parts1[i] < parts2[i] {
- return -1
- }
- if parts1[i] > parts2[i] {
- return 1
- }
- }
-
- if len(parts1) < len(parts2) {
- return -1
- }
- if len(parts1) > len(parts2) {
- return 1
- }
-
- return 0
-}
-
-// Common installation helper
-func (b *BaseDistribution) runWithProgress(cmd *exec.Cmd, progressChan chan<- InstallProgressMsg, phase InstallPhase, startProgress, endProgress float64) error {
- return b.runWithProgressTimeout(cmd, progressChan, phase, startProgress, endProgress, 20*time.Minute)
-}
-
-func (b *BaseDistribution) runWithProgressTimeout(cmd *exec.Cmd, progressChan chan<- InstallProgressMsg, phase InstallPhase, startProgress, endProgress float64, timeout time.Duration) error {
- return b.runWithProgressStepTimeout(cmd, progressChan, phase, startProgress, endProgress, "Installing...", timeout)
-}
-
-func (b *BaseDistribution) runWithProgressStep(cmd *exec.Cmd, progressChan chan<- InstallProgressMsg, phase InstallPhase, startProgress, endProgress float64, stepMessage string) error {
- return b.runWithProgressStepTimeout(cmd, progressChan, phase, startProgress, endProgress, stepMessage, 20*time.Minute)
-}
-
-func (b *BaseDistribution) runWithProgressStepTimeout(cmd *exec.Cmd, progressChan chan<- InstallProgressMsg, phase InstallPhase, startProgress, endProgress float64, stepMessage string, timeoutDuration time.Duration) error {
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return fmt.Errorf("failed to create stdout pipe: %w", err)
- }
- stderr, err := cmd.StderrPipe()
- if err != nil {
- return fmt.Errorf("failed to create stderr pipe: %w", err)
- }
-
- if err := cmd.Start(); err != nil {
- return err
- }
-
- outputChan := make(chan string, 100)
- done := make(chan error, 1)
-
- go func() {
- scanner := bufio.NewScanner(stdout)
- for scanner.Scan() {
- line := scanner.Text()
- b.log(line)
- outputChan <- line
- }
- }()
-
- go func() {
- scanner := bufio.NewScanner(stderr)
- for scanner.Scan() {
- line := scanner.Text()
- b.log(line)
- outputChan <- line
- }
- }()
-
- go func() {
- done <- cmd.Wait()
- close(outputChan)
- }()
-
- ticker := time.NewTicker(200 * time.Millisecond)
- defer ticker.Stop()
-
- progress := startProgress
- progressStep := (endProgress - startProgress) / 50
- lastOutput := ""
-
- var timeout *time.Timer
- var timeoutChan <-chan time.Time
- if timeoutDuration > 0 {
- timeout = time.NewTimer(timeoutDuration)
- defer timeout.Stop()
- timeoutChan = timeout.C
- }
-
- for {
- select {
- case err := <-done:
- if err != nil {
- b.logError("Command execution failed", err)
- b.log(fmt.Sprintf("Last output before failure: %s", lastOutput))
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: startProgress,
- Step: "Command failed",
- IsComplete: false,
- LogOutput: lastOutput,
- Error: err,
- }
- return err
- }
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: endProgress,
- Step: "Installation step complete",
- IsComplete: false,
- LogOutput: lastOutput,
- }
- return nil
- case output, ok := <-outputChan:
- if ok {
- lastOutput = output
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: progress,
- Step: stepMessage,
- IsComplete: false,
- LogOutput: output,
- }
- if timeout != nil {
- timeout.Reset(timeoutDuration)
- }
- }
- case <-timeoutChan:
- if cmd.Process != nil {
- cmd.Process.Kill()
- }
- err := fmt.Errorf("installation timed out after %v", timeoutDuration)
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: startProgress,
- Step: "Installation timed out",
- IsComplete: false,
- LogOutput: lastOutput,
- Error: err,
- }
- return err
- case <-ticker.C:
- if progress < endProgress-0.01 {
- progress += progressStep
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: progress,
- Step: "Installing...",
- IsComplete: false,
- LogOutput: lastOutput,
- }
- }
- }
- }
-}
-
-func (b *BaseDistribution) DetectTerminalFromDeps(dependencies []deps.Dependency) deps.Terminal {
- for _, dep := range dependencies {
- switch dep.Name {
- case "ghostty":
- return deps.TerminalGhostty
- case "kitty":
- return deps.TerminalKitty
- case "alacritty":
- return deps.TerminalAlacritty
- }
- }
- return deps.TerminalGhostty
-}
-
-func (b *BaseDistribution) WriteEnvironmentConfig(terminal deps.Terminal) error {
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get home directory: %w", err)
- }
-
- envDir := filepath.Join(homeDir, ".config", "environment.d")
- if err := os.MkdirAll(envDir, 0o755); err != nil {
- return fmt.Errorf("failed to create environment.d directory: %w", err)
- }
-
- var terminalCmd string
- switch terminal {
- case deps.TerminalGhostty:
- terminalCmd = "ghostty"
- case deps.TerminalKitty:
- terminalCmd = "kitty"
- case deps.TerminalAlacritty:
- terminalCmd = "alacritty"
- default:
- terminalCmd = "ghostty"
- }
-
- content := fmt.Sprintf(`ELECTRON_OZONE_PLATFORM_HINT=auto
-TERMINAL=%s
-`, terminalCmd)
-
- envFile := filepath.Join(envDir, "90-dms.conf")
- if err := os.WriteFile(envFile, []byte(content), 0o644); err != nil {
- return fmt.Errorf("failed to write environment config: %w", err)
- }
-
- b.log(fmt.Sprintf("Wrote environment config to %s", envFile))
- return nil
-}
-
-func (b *BaseDistribution) EnableDMSService(ctx context.Context, wm deps.WindowManager) error {
- switch wm {
- case deps.WindowManagerNiri:
- if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "niri.service", "dms").Run(); err != nil {
- b.log("Warning: failed to add dms as a want for niri.service")
- }
- case deps.WindowManagerHyprland:
- if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "hyprland-session.target", "dms").Run(); err != nil {
- b.log("Warning: failed to add dms as a want for hyprland-session.target")
- }
- }
-
- return nil
-}
-
-func (b *BaseDistribution) WriteWindowManagerConfig(wm deps.WindowManager) error {
- if wm == deps.WindowManagerHyprland {
- if err := b.WriteHyprlandSessionTarget(); err != nil {
- return fmt.Errorf("failed to write hyprland session target: %w", err)
- }
- }
- return nil
-}
-
-func (b *BaseDistribution) WriteHyprlandSessionTarget() error {
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get home directory: %w", err)
- }
-
- targetDir := filepath.Join(homeDir, ".config", "systemd", "user")
- if err := os.MkdirAll(targetDir, 0o755); err != nil {
- return fmt.Errorf("failed to create systemd user directory: %w", err)
- }
-
- targetPath := filepath.Join(targetDir, "hyprland-session.target")
- content := `[Unit]
-Description=Hyprland Session Target
-Requires=graphical-session.target
-After=graphical-session.target
-`
-
- if err := os.WriteFile(targetPath, []byte(content), 0o644); err != nil {
- return fmt.Errorf("failed to write hyprland-session.target: %w", err)
- }
-
- b.log(fmt.Sprintf("Wrote hyprland-session.target to %s", targetPath))
- return nil
-}
-
-// installDMSBinary installs the DMS binary from GitHub releases
-func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- b.log("Installing/updating DMS binary...")
-
- // Detect architecture
- arch := runtime.GOARCH
- switch arch {
- case "amd64":
- case "arm64":
- default:
- return fmt.Errorf("unsupported architecture for DMS: %s", arch)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.80,
- Step: "Downloading DMS binary...",
- IsComplete: false,
- CommandInfo: fmt.Sprintf("Downloading dms-%s.gz", arch),
- }
-
- // Get latest release version
- latestVersionCmd := exec.CommandContext(ctx, "bash", "-c",
- `curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'`)
- versionOutput, err := latestVersionCmd.Output()
- if err != nil {
- return fmt.Errorf("failed to get latest DMS version: %w", err)
- }
- version := strings.TrimSpace(string(versionOutput))
- if version == "" {
- return fmt.Errorf("could not determine latest DMS version")
- }
-
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get user home directory: %w", err)
- }
- tmpDir := filepath.Join(homeDir, ".cache", "dankinstall", "manual-builds")
- if err := os.MkdirAll(tmpDir, 0o755); err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer os.RemoveAll(tmpDir)
-
- // Download the gzipped binary
- downloadURL := fmt.Sprintf("https://github.com/AvengeMedia/DankMaterialShell/releases/download/%s/dms-cli-%s.gz", version, arch)
- gzPath := filepath.Join(tmpDir, "dms.gz")
-
- downloadCmd := exec.CommandContext(ctx, "curl", "-L", downloadURL, "-o", gzPath)
- if err := downloadCmd.Run(); err != nil {
- return fmt.Errorf("failed to download DMS binary: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.85,
- Step: "Extracting DMS binary...",
- IsComplete: false,
- CommandInfo: "gunzip dms.gz",
- }
-
- // Extract the binary
- extractCmd := exec.CommandContext(ctx, "gunzip", gzPath)
- if err := extractCmd.Run(); err != nil {
- return fmt.Errorf("failed to extract DMS binary: %w", err)
- }
-
- binaryPath := filepath.Join(tmpDir, "dms")
-
- // Make it executable
- chmodCmd := exec.CommandContext(ctx, "chmod", "+x", binaryPath)
- if err := chmodCmd.Run(); err != nil {
- return fmt.Errorf("failed to make DMS binary executable: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.88,
- Step: "Installing DMS to /usr/local/bin...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo cp dms /usr/local/bin/",
- }
-
- // Install to /usr/local/bin
- installCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("cp %s /usr/local/bin/dms", binaryPath))
- if err := installCmd.Run(); err != nil {
- return fmt.Errorf("failed to install DMS binary: %w", err)
- }
-
- b.log("DMS binary installed successfully")
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base_test.go
deleted file mode 100644
index 51e86d7..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base_test.go
+++ /dev/null
@@ -1,216 +0,0 @@
-package distros
-
-import (
- "os"
- "os/exec"
- "path/filepath"
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-func TestBaseDistribution_detectDMS_NotInstalled(t *testing.T) {
- originalHome := os.Getenv("HOME")
- defer os.Setenv("HOME", originalHome)
-
- tempDir := t.TempDir()
- os.Setenv("HOME", tempDir)
-
- logChan := make(chan string, 10)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
- dep := base.detectDMS()
-
- if dep.Status != deps.StatusMissing {
- t.Errorf("Expected StatusMissing, got %d", dep.Status)
- }
-
- if dep.Name != "dms (DankMaterialShell)" {
- t.Errorf("Expected name 'dms (DankMaterialShell)', got %s", dep.Name)
- }
-
- if !dep.Required {
- t.Error("Expected Required to be true")
- }
-}
-
-func TestBaseDistribution_detectDMS_Installed(t *testing.T) {
- if !utils.CommandExists("git") {
- t.Skip("git not available")
- }
-
- tempDir := t.TempDir()
- dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
- os.MkdirAll(dmsPath, 0o755)
-
- originalHome := os.Getenv("HOME")
- defer os.Setenv("HOME", originalHome)
- os.Setenv("HOME", tempDir)
-
- exec.Command("git", "init", dmsPath).Run()
- exec.Command("git", "-C", dmsPath, "config", "user.email", "test@test.com").Run()
- exec.Command("git", "-C", dmsPath, "config", "user.name", "Test User").Run()
- exec.Command("git", "-C", dmsPath, "checkout", "-b", "master").Run()
-
- testFile := filepath.Join(dmsPath, "test.txt")
- os.WriteFile(testFile, []byte("test"), 0o644)
- exec.Command("git", "-C", dmsPath, "add", ".").Run()
- exec.Command("git", "-C", dmsPath, "commit", "-m", "initial").Run()
-
- logChan := make(chan string, 10)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
- dep := base.detectDMS()
-
- if dep.Status == deps.StatusMissing {
- t.Error("Expected DMS to be detected as installed")
- }
-
- if dep.Name != "dms (DankMaterialShell)" {
- t.Errorf("Expected name 'dms (DankMaterialShell)', got %s", dep.Name)
- }
-
- if !dep.Required {
- t.Error("Expected Required to be true")
- }
-
- t.Logf("Status: %d, Version: %s", dep.Status, dep.Version)
-}
-
-func TestBaseDistribution_detectDMS_NeedsUpdate(t *testing.T) {
- if !utils.CommandExists("git") {
- t.Skip("git not available")
- }
-
- tempDir := t.TempDir()
- dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
- os.MkdirAll(dmsPath, 0o755)
-
- originalHome := os.Getenv("HOME")
- defer os.Setenv("HOME", originalHome)
- os.Setenv("HOME", tempDir)
-
- exec.Command("git", "init", dmsPath).Run()
- exec.Command("git", "-C", dmsPath, "config", "user.email", "test@test.com").Run()
- exec.Command("git", "-C", dmsPath, "config", "user.name", "Test User").Run()
- exec.Command("git", "-C", dmsPath, "remote", "add", "origin", "https://github.com/AvengeMedia/DankMaterialShell.git").Run()
-
- testFile := filepath.Join(dmsPath, "test.txt")
- os.WriteFile(testFile, []byte("test"), 0o644)
- exec.Command("git", "-C", dmsPath, "add", ".").Run()
- exec.Command("git", "-C", dmsPath, "commit", "-m", "initial").Run()
- exec.Command("git", "-C", dmsPath, "tag", "v0.0.1").Run()
- exec.Command("git", "-C", dmsPath, "checkout", "v0.0.1").Run()
-
- logChan := make(chan string, 10)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
- dep := base.detectDMS()
-
- if dep.Name != "dms (DankMaterialShell)" {
- t.Errorf("Expected name 'dms (DankMaterialShell)', got %s", dep.Name)
- }
-
- if !dep.Required {
- t.Error("Expected Required to be true")
- }
-
- t.Logf("Status: %d, Version: %s", dep.Status, dep.Version)
-}
-
-func TestBaseDistribution_detectDMS_DirectoryWithoutGit(t *testing.T) {
- tempDir := t.TempDir()
- dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
- os.MkdirAll(dmsPath, 0o755)
-
- originalHome := os.Getenv("HOME")
- defer os.Setenv("HOME", originalHome)
- os.Setenv("HOME", tempDir)
-
- logChan := make(chan string, 10)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
- dep := base.detectDMS()
-
- if dep.Status == deps.StatusMissing {
- t.Error("Expected DMS to be detected as present")
- }
-
- if dep.Name != "dms (DankMaterialShell)" {
- t.Errorf("Expected name 'dms (DankMaterialShell)', got %s", dep.Name)
- }
-
- if !dep.Required {
- t.Error("Expected Required to be true")
- }
-}
-
-func TestBaseDistribution_NewBaseDistribution(t *testing.T) {
- logChan := make(chan string, 10)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
-
- if base == nil {
- t.Fatal("NewBaseDistribution returned nil")
- }
-
- if base.logChan == nil {
- t.Error("logChan was not set")
- }
-}
-
-func TestBaseDistribution_versionCompare(t *testing.T) {
- logChan := make(chan string, 10)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
-
- tests := []struct {
- v1 string
- v2 string
- expected int
- }{
- {"0.1.0", "0.1.0", 0},
- {"0.1.0", "0.1.1", -1},
- {"0.1.1", "0.1.0", 1},
- {"0.2.0", "0.1.9", 1},
- {"1.0.0", "0.9.9", 1},
- }
-
- for _, tt := range tests {
- result := base.versionCompare(tt.v1, tt.v2)
- if result != tt.expected {
- t.Errorf("versionCompare(%q, %q) = %d; want %d", tt.v1, tt.v2, result, tt.expected)
- }
- }
-}
-
-func TestBaseDistribution_versionCompare_WithPrefix(t *testing.T) {
- logChan := make(chan string, 10)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
-
- tests := []struct {
- v1 string
- v2 string
- expected int
- }{
- {"v0.1.0", "v0.1.0", 0},
- {"v0.1.0", "v0.1.1", -1},
- {"v0.1.1", "v0.1.0", 1},
- }
-
- for _, tt := range tests {
- result := base.versionCompare(tt.v1, tt.v2)
- if result != tt.expected {
- t.Errorf("versionCompare(%q, %q) = %d; want %d", tt.v1, tt.v2, result, tt.expected)
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/debian.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/debian.go
deleted file mode 100644
index a3537ff..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/debian.go
+++ /dev/null
@@ -1,706 +0,0 @@
-package distros
-
-import (
- "context"
- "fmt"
- "os/exec"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-func init() {
- Register("debian", "#A80030", FamilyDebian, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewDebianDistribution(config, logChan)
- })
-}
-
-type DebianDistribution struct {
- *BaseDistribution
- *ManualPackageInstaller
- config DistroConfig
-}
-
-func NewDebianDistribution(config DistroConfig, logChan chan<- string) *DebianDistribution {
- base := NewBaseDistribution(logChan)
- return &DebianDistribution{
- BaseDistribution: base,
- ManualPackageInstaller: &ManualPackageInstaller{BaseDistribution: base},
- config: config,
- }
-}
-
-func (d *DebianDistribution) GetID() string {
- return d.config.ID
-}
-
-func (d *DebianDistribution) GetColorHex() string {
- return d.config.ColorHex
-}
-
-func (d *DebianDistribution) GetFamily() DistroFamily {
- return d.config.Family
-}
-
-func (d *DebianDistribution) GetPackageManager() PackageManagerType {
- return PackageManagerAPT
-}
-
-func (d *DebianDistribution) DetectDependencies(ctx context.Context, wm deps.WindowManager) ([]deps.Dependency, error) {
- return d.DetectDependenciesWithTerminal(ctx, wm, deps.TerminalGhostty)
-}
-
-func (d *DebianDistribution) DetectDependenciesWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]deps.Dependency, error) {
- var dependencies []deps.Dependency
-
- dependencies = append(dependencies, d.detectDMS())
-
- dependencies = append(dependencies, d.detectSpecificTerminal(terminal))
-
- dependencies = append(dependencies, d.detectGit())
- dependencies = append(dependencies, d.detectWindowManager(wm))
- dependencies = append(dependencies, d.detectQuickshell())
- dependencies = append(dependencies, d.detectDMSGreeter())
- dependencies = append(dependencies, d.detectXDGPortal())
- dependencies = append(dependencies, d.detectAccountsService())
-
- if wm == deps.WindowManagerNiri {
- dependencies = append(dependencies, d.detectXwaylandSatellite())
- }
-
- dependencies = append(dependencies, d.detectMatugen())
- dependencies = append(dependencies, d.detectDgop())
-
- return dependencies, nil
-}
-
-func (d *DebianDistribution) detectXDGPortal() deps.Dependency {
- return d.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", d.packageInstalled("xdg-desktop-portal-gtk"))
-}
-
-func (d *DebianDistribution) detectXwaylandSatellite() deps.Dependency {
- return d.detectCommand("xwayland-satellite", "Xwayland support")
-}
-
-func (d *DebianDistribution) detectAccountsService() deps.Dependency {
- return d.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", d.packageInstalled("accountsservice"))
-}
-
-func (d *DebianDistribution) detectDMSGreeter() deps.Dependency {
- return d.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", d.packageInstalled("dms-greeter"))
-}
-
-func (d *DebianDistribution) packageInstalled(pkg string) bool {
- return debianPackageInstalledPrecisely(pkg)
-}
-
-func debianPackageInstalledPrecisely(pkg string) bool {
- cmd := exec.Command("dpkg-query", "-W", "-f=${db:Status-Status}", pkg)
- output, err := cmd.Output()
- if err != nil {
- return false
- }
- return strings.TrimSpace(string(output)) == "installed"
-}
-
-func debianRepoArchitecture(arch string) string {
- switch arch {
- case "amd64", "x86_64":
- return "amd64"
- case "arm64", "aarch64":
- return "arm64"
- default:
- return arch
- }
-}
-
-func (d *DebianDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
- return d.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
-}
-
-func (d *DebianDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping {
- packages := map[string]PackageMapping{
- // Standard APT packages
- "git": {Name: "git", Repository: RepoTypeSystem},
- "kitty": {Name: "kitty", Repository: RepoTypeSystem},
- "alacritty": {Name: "alacritty", Repository: RepoTypeSystem},
- "xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
- "accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
-
- // DMS packages from OBS with variant support
- "dms (DankMaterialShell)": d.getDmsMapping(variants["dms (DankMaterialShell)"]),
- "quickshell": d.getQuickshellMapping(variants["quickshell"]),
- "dms-greeter": {Name: "dms-greeter", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
- "matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
- "dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
- "ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
- }
-
- if wm == deps.WindowManagerNiri {
- niriVariant := variants["niri"]
- packages["niri"] = d.getNiriMapping(niriVariant)
- packages["xwayland-satellite"] = d.getXwaylandSatelliteMapping(niriVariant)
- }
-
- return packages
-}
-
-func (d *DebianDistribution) getDmsMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "dms-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:dms-git"}
- }
- return PackageMapping{Name: "dms", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:dms"}
-}
-
-func (d *DebianDistribution) getQuickshellMapping(variant deps.PackageVariant) PackageMapping {
- if forceQuickshellGit || variant == deps.VariantGit {
- return PackageMapping{Name: "quickshell-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
- }
- return PackageMapping{Name: "quickshell", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
-}
-
-func (d *DebianDistribution) getNiriMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "niri-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
- }
- return PackageMapping{Name: "niri", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
-}
-
-func (d *DebianDistribution) getXwaylandSatelliteMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "xwayland-satellite-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
- }
- return PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
-}
-
-func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.06,
- Step: "Updating package lists...",
- IsComplete: false,
- LogOutput: "Updating APT package lists",
- }
-
- updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
- if err := d.runWithProgress(updateCmd, progressChan, PhasePrerequisites, 0.06, 0.07); err != nil {
- return fmt.Errorf("failed to update package lists: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.08,
- Step: "Installing build-essential...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get install -y build-essential",
- LogOutput: "Installing build tools",
- }
-
- checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
- if err := checkCmd.Run(); err != nil {
- cmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential")
- if err := d.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
- return fmt.Errorf("failed to install build-essential: %w", err)
- }
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.10,
- Step: "Installing development dependencies...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev",
- LogOutput: "Installing additional development tools",
- }
-
- devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
- "DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
- if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
- return fmt.Errorf("failed to install development tools: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.12,
- Step: "Prerequisites installation complete",
- IsComplete: false,
- LogOutput: "Prerequisites successfully installed",
- }
-
- return nil
-}
-
-func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, disabledFlags map[string]bool, skipGlobalUseFlags bool, progressChan chan<- InstallProgressMsg) error {
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.05,
- Step: "Checking system prerequisites...",
- IsComplete: false,
- LogOutput: "Starting prerequisite check...",
- }
-
- if err := d.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install prerequisites: %w", err)
- }
-
- systemPkgs, obsPkgs, manualPkgs, variantMap := d.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
-
- // Enable OBS repositories
- if len(obsPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.15,
- Step: "Enabling OBS repositories...",
- IsComplete: false,
- LogOutput: "Setting up OBS repositories for additional packages",
- }
- if err := d.enableOBSRepos(ctx, obsPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to enable OBS repositories: %w", err)
- }
- }
-
- // System Packages
- if len(systemPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.35,
- Step: fmt.Sprintf("Installing %d system packages...", len(systemPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(systemPkgs, ", ")),
- }
- if err := d.installAPTPackages(ctx, systemPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install APT packages: %w", err)
- }
- }
-
- // OBS Packages
- obsPkgNames := d.extractPackageNames(obsPkgs)
- if len(obsPkgNames) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.65,
- Step: fmt.Sprintf("Installing %d OBS packages...", len(obsPkgNames)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Installing OBS packages: %s", strings.Join(obsPkgNames, ", ")),
- }
- if err := d.installAPTPackages(ctx, obsPkgNames, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install OBS packages: %w", err)
- }
- }
-
- // Manual Builds
- if len(manualPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.80,
- Step: "Installing build dependencies...",
- IsComplete: false,
- LogOutput: "Installing build tools for manual compilation",
- }
- if err := d.installBuildDependencies(ctx, manualPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install build dependencies: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.85,
- Step: fmt.Sprintf("Building %d packages from source...", len(manualPkgs)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
- }
- if err := d.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install manual packages: %w", err)
- }
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.90,
- Step: "Configuring system...",
- IsComplete: false,
- LogOutput: "Starting post-installation configuration...",
- }
-
- terminal := d.DetectTerminalFromDeps(dependencies)
- if err := d.WriteEnvironmentConfig(terminal); err != nil {
- d.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
- }
-
- if err := d.WriteWindowManagerConfig(wm); err != nil {
- d.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
- }
-
- if err := d.EnableDMSService(ctx, wm); err != nil {
- d.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseComplete,
- Progress: 1.0,
- Step: "Installation complete!",
- IsComplete: true,
- LogOutput: "All packages installed and configured successfully",
- }
-
- return nil
-}
-
-func (d *DebianDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string, map[string]deps.PackageVariant) {
- systemPkgs := []string{}
- obsPkgs := []PackageMapping{}
- manualPkgs := []string{}
-
- variantMap := make(map[string]deps.PackageVariant)
- for _, dep := range dependencies {
- variantMap[dep.Name] = dep.Variant
- }
-
- packageMap := d.GetPackageMappingWithVariants(wm, variantMap)
-
- for _, dep := range dependencies {
- if disabledFlags[dep.Name] {
- continue
- }
-
- if dep.Status == deps.StatusInstalled && !reinstallFlags[dep.Name] {
- continue
- }
-
- pkgInfo, exists := packageMap[dep.Name]
- if !exists {
- d.log(fmt.Sprintf("Warning: No package mapping for %s", dep.Name))
- continue
- }
-
- switch pkgInfo.Repository {
- case RepoTypeSystem:
- systemPkgs = append(systemPkgs, pkgInfo.Name)
- case RepoTypeOBS:
- obsPkgs = append(obsPkgs, pkgInfo)
- case RepoTypeManual:
- manualPkgs = append(manualPkgs, dep.Name)
- }
- }
-
- return systemPkgs, obsPkgs, manualPkgs, variantMap
-}
-
-func (d *DebianDistribution) extractPackageNames(packages []PackageMapping) []string {
- names := make([]string, len(packages))
- for i, pkg := range packages {
- names[i] = pkg.Name
- }
- return names
-}
-
-func (d *DebianDistribution) aptInstallArgs(packages []string, minimal bool) []string {
- args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
- if minimal {
- args = append(args, "--no-install-recommends")
- }
- return append(args, packages...)
-}
-
-func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- enabledRepos := make(map[string]bool)
-
- osInfo, err := GetOSInfo()
- if err != nil {
- return fmt.Errorf("failed to get OS info: %w", err)
- }
-
- // Determine Debian version for OBS repository URL
- debianVersion := "Debian_13"
- if osInfo.VersionID == "testing" {
- debianVersion = "Debian_Testing"
- } else if osInfo.VersionCodename == "sid" || osInfo.VersionID == "sid" || strings.Contains(strings.ToLower(osInfo.PrettyName), "sid") || strings.Contains(strings.ToLower(osInfo.PrettyName), "unstable") {
- debianVersion = "Debian_Unstable"
- }
-
- for _, pkg := range obsPkgs {
- if pkg.RepoURL != "" && !enabledRepos[pkg.RepoURL] {
- d.log(fmt.Sprintf("Enabling OBS repository: %s", pkg.RepoURL))
-
- // RepoURL format: "home:AvengeMedia:danklinux"
- repoPath := strings.ReplaceAll(pkg.RepoURL, ":", ":/")
- repoName := strings.ReplaceAll(pkg.RepoURL, ":", "-")
- baseURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/%s", repoPath, debianVersion)
-
- // Check if repository already exists
- listFile := fmt.Sprintf("/etc/apt/sources.list.d/%s.list", repoName)
- checkCmd := exec.CommandContext(ctx, "test", "-f", listFile)
- if checkCmd.Run() == nil {
- d.log(fmt.Sprintf("OBS repo %s already exists, skipping", pkg.RepoURL))
- enabledRepos[pkg.RepoURL] = true
- continue
- }
-
- keyringPath := fmt.Sprintf("/etc/apt/keyrings/%s.gpg", repoName)
-
- // Create keyrings directory if it doesn't exist
- mkdirCmd := ExecSudoCommand(ctx, sudoPassword, "mkdir -p /etc/apt/keyrings")
- if err := mkdirCmd.Run(); err != nil {
- d.log(fmt.Sprintf("Warning: failed to create keyrings directory: %v", err))
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.18,
- Step: fmt.Sprintf("Adding OBS GPG key for %s...", pkg.RepoURL),
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("curl & gpg to add key for %s", pkg.RepoURL),
- }
-
- keyCmd := fmt.Sprintf("bash -c 'rm -f %s && curl -fsSL %s/Release.key | gpg --batch --dearmor -o %s'", keyringPath, baseURL, keyringPath)
- cmd := ExecSudoCommand(ctx, sudoPassword, keyCmd)
- if err := d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20); err != nil {
- return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
- }
-
- // Add repository
- repoLine := fmt.Sprintf("deb [signed-by=%s arch=%s] %s/ /", keyringPath, debianRepoArchitecture(osInfo.Architecture), baseURL)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.20,
- Step: fmt.Sprintf("Adding OBS repository %s...", pkg.RepoURL),
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("echo '%s' | sudo tee %s", repoLine, listFile),
- }
-
- addRepoCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("bash -c \"echo '%s' | tee %s\"", repoLine, listFile))
- if err := d.runWithProgress(addRepoCmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
- return fmt.Errorf("failed to add OBS repo %s: %w", pkg.RepoURL, err)
- }
-
- enabledRepos[pkg.RepoURL] = true
- d.log(fmt.Sprintf("OBS repo %s enabled successfully", pkg.RepoURL))
- }
- }
-
- if len(enabledRepos) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.25,
- Step: "Updating package lists...",
- NeedsSudo: true,
- CommandInfo: "sudo apt-get update",
- }
-
- updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
- if err := d.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
- return fmt.Errorf("failed to update package lists after adding OBS repos: %w", err)
- }
- }
-
- return nil
-}
-
-func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- d.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
-
- groups := orderedMinimalInstallGroups(packages)
- totalGroups := len(groups)
-
- groupIndex := 0
- installGroup := func(groupPackages []string, minimal bool) error {
- if len(groupPackages) == 0 {
- return nil
- }
-
- groupIndex++
- startProgress := 0.40
- endProgress := 0.60
- if totalGroups > 1 {
- if groupIndex == 1 {
- endProgress = 0.50
- } else {
- startProgress = 0.50
- }
- }
-
- args := d.aptInstallArgs(groupPackages, minimal)
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: startProgress,
- Step: "Installing system packages...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, startProgress, endProgress)
- }
-
- for _, group := range groups {
- if err := installGroup(group.packages, group.minimal); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (d *DebianDistribution) installBuildDependencies(ctx context.Context, manualPkgs []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- buildDeps := make(map[string]bool)
-
- for _, pkg := range manualPkgs {
- switch pkg {
- case "niri":
- buildDeps["curl"] = true
- buildDeps["libxkbcommon-dev"] = true
- buildDeps["libwayland-dev"] = true
- buildDeps["libudev-dev"] = true
- buildDeps["libinput-dev"] = true
- buildDeps["libdisplay-info-dev"] = true
- buildDeps["libpango1.0-dev"] = true
- buildDeps["libcairo-dev"] = true
- buildDeps["libpipewire-0.3-dev"] = true
- buildDeps["libc6-dev"] = true
- buildDeps["clang"] = true
- buildDeps["libseat-dev"] = true
- buildDeps["libgbm-dev"] = true
- buildDeps["alacritty"] = true
- buildDeps["fuzzel"] = true
- case "quickshell":
- buildDeps["qt6-base-dev"] = true
- buildDeps["qt6-base-private-dev"] = true
- buildDeps["qt6-declarative-dev"] = true
- buildDeps["qt6-declarative-private-dev"] = true
- buildDeps["qt6-wayland-dev"] = true
- buildDeps["qt6-wayland-private-dev"] = true
- buildDeps["qt6-tools-dev"] = true
- buildDeps["libqt6svg6-dev"] = true
- buildDeps["qt6-shadertools-dev"] = true
- buildDeps["spirv-tools"] = true
- buildDeps["libcli11-dev"] = true
- buildDeps["libjemalloc-dev"] = true
- buildDeps["libwayland-dev"] = true
- buildDeps["wayland-protocols"] = true
- buildDeps["libdrm-dev"] = true
- buildDeps["libgbm-dev"] = true
- buildDeps["libegl-dev"] = true
- buildDeps["libgles2-mesa-dev"] = true
- buildDeps["libgl1-mesa-dev"] = true
- buildDeps["libxcb1-dev"] = true
- buildDeps["libpipewire-0.3-dev"] = true
- buildDeps["libpam0g-dev"] = true
- case "ghostty":
- buildDeps["curl"] = true
- case "matugen":
- buildDeps["curl"] = true
- }
- }
-
- for _, pkg := range manualPkgs {
- switch pkg {
- case "niri", "matugen":
- if err := d.installRust(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install Rust: %w", err)
- }
- case "dgop":
- if err := d.installGo(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install Go: %w", err)
- }
- }
- }
-
- if len(buildDeps) == 0 {
- return nil
- }
-
- depList := make([]string, 0, len(buildDeps))
- for dep := range buildDeps {
- depList = append(depList, dep)
- }
-
- args := []string{"apt-get", "install", "-y"}
- args = append(args, depList...)
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.80, 0.82)
-}
-
-func (d *DebianDistribution) installRust(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if d.commandExists("cargo") {
- return nil
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.82,
- Step: "Installing rustup...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get install rustup",
- }
-
- rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y rustup")
- if err := d.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
- return fmt.Errorf("failed to install rustup: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.83,
- Step: "Installing stable Rust toolchain...",
- IsComplete: false,
- CommandInfo: "rustup install stable",
- }
-
- rustInstallCmd := exec.CommandContext(ctx, "bash", "-c", "rustup install stable && rustup default stable")
- if err := d.runWithProgress(rustInstallCmd, progressChan, PhaseSystemPackages, 0.83, 0.84); err != nil {
- return fmt.Errorf("failed to install Rust toolchain: %w", err)
- }
-
- if !d.commandExists("cargo") {
- d.log("Warning: cargo not found in PATH after Rust installation, trying to source environment")
- }
-
- return nil
-}
-
-func (d *DebianDistribution) installGo(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if d.commandExists("go") {
- return nil
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.87,
- Step: "Installing Go...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get install golang-go",
- }
-
- installCmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y golang-go")
- return d.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.87, 0.90)
-}
-
-func (d *DebianDistribution) InstallManualPackages(ctx context.Context, packages []string, variantMap map[string]deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- d.log(fmt.Sprintf("Installing manual packages: %s", strings.Join(packages, ", ")))
-
- for _, pkg := range packages {
- switch pkg {
- default:
- if err := d.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install %s: %w", pkg, err)
- }
- }
- }
-
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/factory.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/factory.go
deleted file mode 100644
index 9c3826a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/factory.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package distros
-
-import (
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-// NewDependencyDetector creates a DependencyDetector for the specified distribution
-func NewDependencyDetector(distribution string, logChan chan<- string) (deps.DependencyDetector, error) {
- distro, err := NewDistribution(distribution, logChan)
- if err != nil {
- return nil, err
- }
- return distro, nil
-}
-
-// NewPackageInstaller creates a Distribution for package installation
-func NewPackageInstaller(distribution string, logChan chan<- string) (Distribution, error) {
- return NewDistribution(distribution, logChan)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/fedora.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/fedora.go
deleted file mode 100644
index 4c16d4e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/fedora.go
+++ /dev/null
@@ -1,550 +0,0 @@
-package distros
-
-import (
- "context"
- "fmt"
- "os/exec"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-func init() {
- Register("fedora", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewFedoraDistribution(config, logChan)
- })
- Register("evernight", "#72B8DC", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewFedoraDistribution(config, logChan)
- })
- Register("nobara", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewFedoraDistribution(config, logChan)
- })
- Register("fedora-asahi-remix", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewFedoraDistribution(config, logChan)
- })
- Register("bluefin", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewFedoraDistribution(config, logChan)
- })
- Register("ultramarine", "#00078b", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewFedoraDistribution(config, logChan)
- })
-}
-
-type FedoraDistribution struct {
- *BaseDistribution
- *ManualPackageInstaller
- config DistroConfig
-}
-
-func NewFedoraDistribution(config DistroConfig, logChan chan<- string) *FedoraDistribution {
- base := NewBaseDistribution(logChan)
- return &FedoraDistribution{
- BaseDistribution: base,
- ManualPackageInstaller: &ManualPackageInstaller{BaseDistribution: base},
- config: config,
- }
-}
-
-func (f *FedoraDistribution) GetID() string {
- return f.config.ID
-}
-
-func (f *FedoraDistribution) GetColorHex() string {
- return f.config.ColorHex
-}
-
-func (f *FedoraDistribution) GetFamily() DistroFamily {
- return f.config.Family
-}
-
-func (f *FedoraDistribution) GetPackageManager() PackageManagerType {
- return PackageManagerDNF
-}
-
-func (f *FedoraDistribution) DetectDependencies(ctx context.Context, wm deps.WindowManager) ([]deps.Dependency, error) {
- return f.DetectDependenciesWithTerminal(ctx, wm, deps.TerminalGhostty)
-}
-
-func (f *FedoraDistribution) DetectDependenciesWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]deps.Dependency, error) {
- var dependencies []deps.Dependency
-
- // DMS at the top (shell is prominent)
- dependencies = append(dependencies, f.detectDMS())
-
- // Terminal with choice support
- dependencies = append(dependencies, f.detectSpecificTerminal(terminal))
-
- // Common detections using base methods
- dependencies = append(dependencies, f.detectGit())
- dependencies = append(dependencies, f.detectWindowManager(wm))
- dependencies = append(dependencies, f.detectQuickshell())
- dependencies = append(dependencies, f.detectDMSGreeter())
- dependencies = append(dependencies, f.detectXDGPortal())
- dependencies = append(dependencies, f.detectAccountsService())
-
- // Hyprland-specific tools
- if wm == deps.WindowManagerHyprland {
- dependencies = append(dependencies, f.detectHyprlandTools()...)
- }
-
- // Niri-specific tools
- if wm == deps.WindowManagerNiri {
- dependencies = append(dependencies, f.detectXwaylandSatellite())
- }
-
- dependencies = append(dependencies, f.detectMatugen())
- dependencies = append(dependencies, f.detectDgop())
-
- return dependencies, nil
-}
-
-func (f *FedoraDistribution) detectXDGPortal() deps.Dependency {
- return f.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", f.packageInstalled("xdg-desktop-portal-gtk"))
-}
-
-func (f *FedoraDistribution) packageInstalled(pkg string) bool {
- cmd := exec.Command("rpm", "-q", pkg)
- err := cmd.Run()
- return err == nil
-}
-
-func (f *FedoraDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
- return f.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
-}
-
-func (f *FedoraDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping {
- packages := map[string]PackageMapping{
- // Standard DNF packages
- "git": {Name: "git", Repository: RepoTypeSystem},
- "ghostty": {Name: "ghostty", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
- "kitty": {Name: "kitty", Repository: RepoTypeSystem},
- "alacritty": {Name: "alacritty", Repository: RepoTypeSystem},
- "xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
- "accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
-
- // COPR packages
- "quickshell": f.getQuickshellMapping(variants["quickshell"]),
- "dms-greeter": {Name: "dms-greeter", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
- "matugen": {Name: "matugen", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
- "dms (DankMaterialShell)": f.getDmsMapping(variants["dms (DankMaterialShell)"]),
- "dgop": {Name: "dgop", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
- }
-
- switch wm {
- case deps.WindowManagerHyprland:
- packages["hyprland"] = f.getHyprlandMapping(variants["hyprland"])
- packages["hyprctl"] = f.getHyprlandMapping(variants["hyprland"])
- packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
- case deps.WindowManagerNiri:
- packages["niri"] = f.getNiriMapping(variants["niri"])
- packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeSystem}
- }
-
- return packages
-}
-
-func (f *FedoraDistribution) getQuickshellMapping(variant deps.PackageVariant) PackageMapping {
- if forceQuickshellGit || variant == deps.VariantGit {
- return PackageMapping{Name: "quickshell-git", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}
- }
- return PackageMapping{Name: "quickshell", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}
-}
-
-func (f *FedoraDistribution) getDmsMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "dms", Repository: RepoTypeCOPR, RepoURL: "avengemedia/dms-git"}
- }
- return PackageMapping{Name: "dms", Repository: RepoTypeCOPR, RepoURL: "avengemedia/dms"}
-}
-
-func (f *FedoraDistribution) getHyprlandMapping(_ deps.PackageVariant) PackageMapping {
- return PackageMapping{Name: "hyprland", Repository: RepoTypeCOPR, RepoURL: "sdegler/hyprland"}
-}
-
-func (f *FedoraDistribution) getNiriMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "niri", Repository: RepoTypeCOPR, RepoURL: "yalter/niri-git"}
- }
- return PackageMapping{Name: "niri", Repository: RepoTypeCOPR, RepoURL: "yalter/niri"}
-}
-
-func (f *FedoraDistribution) detectXwaylandSatellite() deps.Dependency {
- status := deps.StatusMissing
- if f.commandExists("xwayland-satellite") {
- status = deps.StatusInstalled
- }
-
- return deps.Dependency{
- Name: "xwayland-satellite",
- Status: status,
- Description: "Xwayland support",
- Required: true,
- }
-}
-
-func (f *FedoraDistribution) detectAccountsService() deps.Dependency {
- status := deps.StatusMissing
- if f.packageInstalled("accountsservice") {
- status = deps.StatusInstalled
- }
-
- return deps.Dependency{
- Name: "accountsservice",
- Status: status,
- Description: "D-Bus interface for user account query and manipulation",
- Required: true,
- }
-}
-
-func (f *FedoraDistribution) detectDMSGreeter() deps.Dependency {
- return f.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", f.packageInstalled("dms-greeter"))
-}
-
-func (f *FedoraDistribution) getPrerequisites() []string {
- return []string{
- "dnf-plugins-core",
- "make",
- "unzip",
- "libwayland-server",
- }
-}
-
-func (f *FedoraDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- prerequisites := f.getPrerequisites()
- var missingPkgs []string
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.06,
- Step: "Checking prerequisites...",
- IsComplete: false,
- LogOutput: "Checking prerequisite packages",
- }
-
- for _, pkg := range prerequisites {
- checkCmd := exec.CommandContext(ctx, "rpm", "-q", pkg)
- if err := checkCmd.Run(); err != nil {
- missingPkgs = append(missingPkgs, pkg)
- }
- }
-
- _, err := exec.LookPath("go")
- if err != nil {
- f.log("go not found in PATH, will install golang-bin")
- missingPkgs = append(missingPkgs, "golang-bin")
- } else {
- f.log("go already available in PATH")
- }
-
- if len(missingPkgs) == 0 {
- f.log("All prerequisites already installed")
- return nil
- }
-
- f.log(fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")))
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.08,
- Step: fmt.Sprintf("Installing %d prerequisites...", len(missingPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo dnf install -y %s", strings.Join(missingPkgs, " ")),
- LogOutput: fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")),
- }
-
- args := []string{"dnf", "install", "-y"}
- args = append(args, missingPkgs...)
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- output, err := cmd.CombinedOutput()
- if err != nil {
- f.logError("failed to install prerequisites", err)
- f.log(fmt.Sprintf("Prerequisites command output: %s", string(output)))
- return fmt.Errorf("failed to install prerequisites: %w", err)
- }
- f.log(fmt.Sprintf("Prerequisites install output: %s", string(output)))
-
- return nil
-}
-
-func (f *FedoraDistribution) InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, disabledFlags map[string]bool, skipGlobalUseFlags bool, progressChan chan<- InstallProgressMsg) error {
- // Phase 1: Check Prerequisites
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.05,
- Step: "Checking system prerequisites...",
- IsComplete: false,
- LogOutput: "Starting prerequisite check...",
- }
-
- if err := f.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install prerequisites: %w", err)
- }
-
- dnfPkgs, coprPkgs, manualPkgs, variantMap := f.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
-
- // Phase 2: Enable COPR repositories
- if len(coprPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.15,
- Step: "Enabling COPR repositories...",
- IsComplete: false,
- LogOutput: "Setting up COPR repositories for additional packages",
- }
- if err := f.enableCOPRRepos(ctx, coprPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to enable COPR repositories: %w", err)
- }
- }
-
- // Phase 3: System Packages (DNF)
- if len(dnfPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.35,
- Step: fmt.Sprintf("Installing %d system packages...", len(dnfPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(dnfPkgs, ", ")),
- }
- if err := f.installDNFPackages(ctx, dnfPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install DNF packages: %w", err)
- }
- }
-
- // Phase 4: COPR Packages
- coprPkgNames := f.extractPackageNames(coprPkgs)
- if len(coprPkgNames) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages, // Reusing AUR phase for COPR
- Progress: 0.65,
- Step: fmt.Sprintf("Installing %d COPR packages...", len(coprPkgNames)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Installing COPR packages: %s", strings.Join(coprPkgNames, ", ")),
- }
- if err := f.installCOPRPackages(ctx, coprPkgNames, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install COPR packages: %w", err)
- }
- }
-
- // Phase 5: Manual Builds
- if len(manualPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.85,
- Step: fmt.Sprintf("Building %d packages from source...", len(manualPkgs)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
- }
- if err := f.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install manual packages: %w", err)
- }
- }
-
- // Phase 6: Configuration
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.90,
- Step: "Configuring system...",
- IsComplete: false,
- LogOutput: "Starting post-installation configuration...",
- }
-
- terminal := f.DetectTerminalFromDeps(dependencies)
- if err := f.WriteEnvironmentConfig(terminal); err != nil {
- f.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
- }
-
- if err := f.WriteWindowManagerConfig(wm); err != nil {
- f.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
- }
-
- if err := f.EnableDMSService(ctx, wm); err != nil {
- f.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
- }
-
- // Phase 7: Complete
- progressChan <- InstallProgressMsg{
- Phase: PhaseComplete,
- Progress: 1.0,
- Step: "Installation complete!",
- IsComplete: true,
- LogOutput: "All packages installed and configured successfully",
- }
-
- return nil
-}
-
-func (f *FedoraDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string, map[string]deps.PackageVariant) {
- dnfPkgs := []string{}
- coprPkgs := []PackageMapping{}
- manualPkgs := []string{}
-
- variantMap := make(map[string]deps.PackageVariant)
- for _, dep := range dependencies {
- variantMap[dep.Name] = dep.Variant
- }
-
- packageMap := f.GetPackageMappingWithVariants(wm, variantMap)
-
- for _, dep := range dependencies {
- if disabledFlags[dep.Name] {
- continue
- }
-
- if dep.Status == deps.StatusInstalled && !reinstallFlags[dep.Name] {
- continue
- }
-
- pkgInfo, exists := packageMap[dep.Name]
- if !exists {
- f.log(fmt.Sprintf("Warning: No package mapping for %s", dep.Name))
- continue
- }
-
- switch pkgInfo.Repository {
- case RepoTypeSystem:
- dnfPkgs = append(dnfPkgs, pkgInfo.Name)
- case RepoTypeCOPR:
- coprPkgs = append(coprPkgs, pkgInfo)
- case RepoTypeManual:
- manualPkgs = append(manualPkgs, dep.Name)
- }
- }
-
- return dnfPkgs, coprPkgs, manualPkgs, variantMap
-}
-
-func (f *FedoraDistribution) extractPackageNames(packages []PackageMapping) []string {
- names := make([]string, len(packages))
- for i, pkg := range packages {
- names[i] = pkg.Name
- }
- return names
-}
-
-func (f *FedoraDistribution) enableCOPRRepos(ctx context.Context, coprPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- enabledRepos := make(map[string]bool)
-
- for _, pkg := range coprPkgs {
- if pkg.RepoURL != "" && !enabledRepos[pkg.RepoURL] {
- f.log(fmt.Sprintf("Enabling COPR repository: %s", pkg.RepoURL))
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.20,
- Step: fmt.Sprintf("Enabling COPR repo %s...", pkg.RepoURL),
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo dnf copr enable -y %s", pkg.RepoURL),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("dnf copr enable -y %s 2>&1", pkg.RepoURL))
- output, err := cmd.CombinedOutput()
- if err != nil {
- f.logError(fmt.Sprintf("failed to enable COPR repo %s", pkg.RepoURL), err)
- f.log(fmt.Sprintf("COPR enable command output: %s", string(output)))
- return fmt.Errorf("failed to enable COPR repo %s: %w", pkg.RepoURL, err)
- }
- f.log(fmt.Sprintf("COPR repo %s enabled successfully: %s", pkg.RepoURL, string(output)))
- enabledRepos[pkg.RepoURL] = true
-
- // Special handling for niri COPR repo - set priority=1
- if pkg.RepoURL == "yalter/niri-git" {
- f.log("Setting priority=1 for niri-git COPR repo...")
- repoFile := "/etc/yum.repos.d/_copr:copr.fedorainfracloud.org:yalter:niri-git.repo"
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.22,
- Step: "Setting niri COPR repo priority...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("echo \"priority=1\" | sudo tee -a %s", repoFile),
- }
-
- priorityCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("bash -c 'echo \"priority=1\" | tee -a %s'", repoFile))
- priorityOutput, err := priorityCmd.CombinedOutput()
- if err != nil {
- f.logError("failed to set niri COPR repo priority", err)
- f.log(fmt.Sprintf("Priority command output: %s", string(priorityOutput)))
- return fmt.Errorf("failed to set niri COPR repo priority: %w", err)
- }
- f.log(fmt.Sprintf("niri COPR repo priority set successfully: %s", string(priorityOutput)))
- }
- }
- }
-
- return nil
-}
-
-func (f *FedoraDistribution) installDNFPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
-
- return f.installDNFGroups(ctx, packages, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60)
-}
-
-func (f *FedoraDistribution) installCOPRPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- f.log(fmt.Sprintf("Installing COPR packages: %s", strings.Join(packages, ", ")))
-
- return f.installDNFGroups(ctx, packages, sudoPassword, progressChan, PhaseAURPackages, "Installing COPR packages...", 0.70, 0.85)
-}
-
-func (f *FedoraDistribution) dnfInstallArgs(packages []string, minimal bool) []string {
- args := []string{"dnf", "install", "-y"}
- if minimal {
- args = append(args, "--setopt=install_weak_deps=False")
- }
- return append(args, packages...)
-}
-
-func (f *FedoraDistribution) installDNFGroups(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
- groups := orderedMinimalInstallGroups(packages)
- totalGroups := len(groups)
-
- groupIndex := 0
- installGroup := func(groupPackages []string, minimal bool) error {
- if len(groupPackages) == 0 {
- return nil
- }
-
- groupIndex++
- groupStart := startProgress
- groupEnd := endProgress
- if totalGroups > 1 {
- midpoint := startProgress + ((endProgress - startProgress) / 2)
- if groupIndex == 1 {
- groupEnd = midpoint
- } else {
- groupStart = midpoint
- }
- }
-
- args := f.dnfInstallArgs(groupPackages, minimal)
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: groupStart,
- Step: step,
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return f.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
- }
-
- for _, group := range groups {
- if err := installGroup(group.packages, group.minimal); err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go
deleted file mode 100644
index e0a2a9c..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go
+++ /dev/null
@@ -1,700 +0,0 @@
-package distros
-
-import (
- "context"
- "fmt"
- "os/exec"
- "runtime"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-var GentooGlobalUseFlags = []string{
- "dbus",
- "udev",
- "alsa",
- "policykit",
- "jpeg",
- "png",
- "webp",
- "gif",
- "tiff",
- "svg",
- "brotli",
- "gdbm",
- "accessibility",
- "gtk",
- "qt6",
- "egl",
- "gbm",
-}
-
-func init() {
- Register("gentoo", "#54487A", FamilyGentoo, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewGentooDistribution(config, logChan)
- })
-}
-
-type GentooDistribution struct {
- *BaseDistribution
- *ManualPackageInstaller
- config DistroConfig
- skipGlobalUseFlags bool
-}
-
-func NewGentooDistribution(config DistroConfig, logChan chan<- string) *GentooDistribution {
- base := NewBaseDistribution(logChan)
- return &GentooDistribution{
- BaseDistribution: base,
- ManualPackageInstaller: &ManualPackageInstaller{BaseDistribution: base},
- config: config,
- }
-}
-
-func (g *GentooDistribution) getArchKeyword() string {
- arch := runtime.GOARCH
- switch arch {
- case "amd64":
- return "~amd64"
- case "arm64":
- return "~arm64"
- default:
- return "~amd64"
- }
-}
-
-func (g *GentooDistribution) GetID() string {
- return g.config.ID
-}
-
-func (g *GentooDistribution) GetColorHex() string {
- return g.config.ColorHex
-}
-
-func (g *GentooDistribution) GetFamily() DistroFamily {
- return g.config.Family
-}
-
-func (g *GentooDistribution) GetPackageManager() PackageManagerType {
- return PackageManagerPortage
-}
-
-func (g *GentooDistribution) DetectDependencies(ctx context.Context, wm deps.WindowManager) ([]deps.Dependency, error) {
- return g.DetectDependenciesWithTerminal(ctx, wm, deps.TerminalGhostty)
-}
-
-func (g *GentooDistribution) DetectDependenciesWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]deps.Dependency, error) {
- var dependencies []deps.Dependency
-
- dependencies = append(dependencies, g.detectDMS())
-
- dependencies = append(dependencies, g.detectSpecificTerminal(terminal))
-
- dependencies = append(dependencies, g.detectGit())
- dependencies = append(dependencies, g.detectWindowManager(wm))
- dependencies = append(dependencies, g.detectQuickshell())
- dependencies = append(dependencies, g.detectXDGPortal())
- dependencies = append(dependencies, g.detectAccountsService())
-
- if wm == deps.WindowManagerHyprland {
- dependencies = append(dependencies, g.detectHyprlandTools()...)
- }
-
- if wm == deps.WindowManagerNiri {
- dependencies = append(dependencies, g.detectXwaylandSatellite())
- }
-
- dependencies = append(dependencies, g.detectMatugen())
- dependencies = append(dependencies, g.detectDgop())
-
- return dependencies, nil
-}
-
-func (g *GentooDistribution) detectXDGPortal() deps.Dependency {
- return g.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", g.packageInstalled("sys-apps/xdg-desktop-portal-gtk"))
-}
-
-func (g *GentooDistribution) detectXwaylandSatellite() deps.Dependency {
- return g.detectPackage("xwayland-satellite", "Xwayland support", g.packageInstalled("gui-apps/xwayland-satellite"))
-}
-
-func (g *GentooDistribution) detectAccountsService() deps.Dependency {
- return g.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", g.packageInstalled("sys-apps/accountsservice"))
-}
-
-func (g *GentooDistribution) packageInstalled(pkg string) bool {
- cmd := exec.Command("qlist", "-I", pkg)
- err := cmd.Run()
- return err == nil
-}
-
-func (g *GentooDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
- return g.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
-}
-
-func (g *GentooDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping {
- archKeyword := g.getArchKeyword()
- packages := map[string]PackageMapping{
- "git": {Name: "dev-vcs/git", Repository: RepoTypeSystem},
- "kitty": {Name: "x11-terms/kitty", Repository: RepoTypeSystem, UseFlags: "X wayland"},
- "alacritty": {Name: "x11-terms/alacritty", Repository: RepoTypeSystem, UseFlags: "X wayland"},
- "xdg-desktop-portal-gtk": {Name: "sys-apps/xdg-desktop-portal-gtk", Repository: RepoTypeSystem, UseFlags: "wayland X"},
- "accountsservice": {Name: "sys-apps/accountsservice", Repository: RepoTypeSystem},
-
- "qtbase": {Name: "dev-qt/qtbase", Repository: RepoTypeSystem, UseFlags: "wayland opengl vulkan widgets"},
- "qtdeclarative": {Name: "dev-qt/qtdeclarative", Repository: RepoTypeSystem, UseFlags: "opengl vulkan"},
- "qtwayland": {Name: "dev-qt/qtwayland", Repository: RepoTypeSystem},
- "mesa": {Name: "media-libs/mesa", Repository: RepoTypeSystem, UseFlags: "opengl vulkan"},
-
- "quickshell": g.getQuickshellMapping(variants["quickshell"]),
- "matugen": {Name: "x11-misc/matugen", Repository: RepoTypeGURU, AcceptKeywords: archKeyword},
- "dms (DankMaterialShell)": g.getDmsMapping(variants["dms (DankMaterialShell)"]),
- "dgop": {Name: "dgop", Repository: RepoTypeManual, BuildFunc: "installDgop"},
- }
-
- switch wm {
- case deps.WindowManagerHyprland:
- packages["hyprland"] = g.getHyprlandMapping(variants["hyprland"])
- packages["hyprctl"] = g.getHyprlandMapping(variants["hyprland"])
- packages["jq"] = PackageMapping{Name: "app-misc/jq", Repository: RepoTypeSystem}
- case deps.WindowManagerNiri:
- packages["niri"] = g.getNiriMapping(variants["niri"])
- packages["xwayland-satellite"] = PackageMapping{Name: "gui-apps/xwayland-satellite", Repository: RepoTypeGURU, AcceptKeywords: archKeyword}
- }
-
- return packages
-}
-
-func (g *GentooDistribution) getQuickshellMapping(_ deps.PackageVariant) PackageMapping {
- return PackageMapping{Name: "gui-apps/quickshell", Repository: RepoTypeGURU, UseFlags: "breakpad jemalloc sockets wayland layer-shell session-lock toplevel-management screencopy X pipewire tray mpris pam hyprland hyprland-global-shortcuts hyprland-focus-grab i3 i3-ipc bluetooth", AcceptKeywords: "**"}
-}
-
-func (g *GentooDistribution) getDmsMapping(_ deps.PackageVariant) PackageMapping {
- return PackageMapping{Name: "dms", Repository: RepoTypeManual, BuildFunc: "installDankMaterialShell"}
-}
-
-func (g *GentooDistribution) getHyprlandMapping(_ deps.PackageVariant) PackageMapping {
- return PackageMapping{Name: "gui-wm/hyprland", Repository: RepoTypeSystem, UseFlags: "X", AcceptKeywords: g.getArchKeyword()}
-}
-
-func (g *GentooDistribution) getNiriMapping(_ deps.PackageVariant) PackageMapping {
- return PackageMapping{Name: "gui-wm/niri", Repository: RepoTypeGURU, UseFlags: "dbus screencast", AcceptKeywords: g.getArchKeyword()}
-}
-
-func (g *GentooDistribution) getPrerequisites() []string {
- return []string{
- "app-eselect/eselect-repository",
- "dev-vcs/git",
- "dev-build/make",
- "app-arch/unzip",
- "dev-util/pkgconf",
- "dev-qt/qtdeclarative",
- }
-}
-
-func (g *GentooDistribution) setGlobalUseFlags(ctx context.Context, sudoPassword string) error {
- useFlags := strings.Join(GentooGlobalUseFlags, " ")
-
- checkCmd := exec.CommandContext(ctx, "grep", "-q", "^USE=", "/etc/portage/make.conf")
- hasUse := checkCmd.Run() == nil
-
- var cmd *exec.Cmd
- if hasUse {
- cmd = ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("sed -i 's/^USE=\"\\(.*\\)\"/USE=\"\\1 %s\"/' /etc/portage/make.conf", useFlags))
- } else {
- cmd = ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("bash -c \"echo 'USE=\\\"%s\\\"' >> /etc/portage/make.conf\"", useFlags))
- }
-
- output, err := cmd.CombinedOutput()
- if err != nil {
- g.log(fmt.Sprintf("Failed to set global USE flags: %s", string(output)))
- return err
- }
-
- g.log(fmt.Sprintf("Set global USE flags: %s", useFlags))
- return nil
-}
-
-func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- prerequisites := g.getPrerequisites()
- var missingPkgs []string
-
- if !g.skipGlobalUseFlags {
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.05,
- Step: "Setting global USE flags...",
- IsComplete: false,
- LogOutput: "Configuring global USE flags in /etc/portage/make.conf",
- }
-
- if err := g.setGlobalUseFlags(ctx, sudoPassword); err != nil {
- g.logError("failed to set global USE flags", err)
- return fmt.Errorf("failed to set global USE flags: %w", err)
- }
- } else {
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.05,
- Step: "Skipping global USE flags...",
- IsComplete: false,
- LogOutput: "Skipping global USE flags configuration (using existing configuration)",
- }
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.06,
- Step: "Checking prerequisites...",
- IsComplete: false,
- LogOutput: "Checking prerequisite packages",
- }
-
- for _, pkg := range prerequisites {
- checkCmd := exec.CommandContext(ctx, "qlist", "-I", pkg)
- if err := checkCmd.Run(); err != nil {
- missingPkgs = append(missingPkgs, pkg)
- }
- }
-
- _, err := exec.LookPath("go")
- if err != nil {
- g.log("go not found in PATH, will install dev-lang/go")
- missingPkgs = append(missingPkgs, "dev-lang/go")
- } else {
- g.log("go already available in PATH")
- }
-
- if len(missingPkgs) == 0 {
- g.log("All prerequisites already installed")
- return nil
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.07,
- Step: "Syncing Portage tree...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo emerge --sync",
- LogOutput: "Syncing Portage tree with emerge --sync",
- }
-
- syncCmd := ExecSudoCommand(ctx, sudoPassword, "emerge --sync --quiet")
- syncOutput, syncErr := syncCmd.CombinedOutput()
- if syncErr != nil {
- g.log(fmt.Sprintf("emerge --sync output: %s", string(syncOutput)))
- return fmt.Errorf("failed to sync Portage tree: %w\nOutput: %s", syncErr, string(syncOutput))
- }
- g.log("Portage tree synced successfully")
-
- g.log(fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")))
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.08,
- Step: fmt.Sprintf("Installing %d prerequisites...", len(missingPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo emerge --ask=n %s", strings.Join(missingPkgs, " ")),
- LogOutput: fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")),
- }
-
- args := []string{"emerge", "--ask=n", "--quiet"}
- args = append(args, missingPkgs...)
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- output, err := cmd.CombinedOutput()
- if err != nil {
- g.logError("failed to install prerequisites", err)
- g.log(fmt.Sprintf("Prerequisites command output: %s", string(output)))
- return fmt.Errorf("failed to install prerequisites: %w\nOutput: %s", err, string(output))
- }
- g.log(fmt.Sprintf("Prerequisites install output: %s", string(output)))
-
- return nil
-}
-
-func (g *GentooDistribution) InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, disabledFlags map[string]bool, skipGlobalUseFlags bool, progressChan chan<- InstallProgressMsg) error {
- g.skipGlobalUseFlags = skipGlobalUseFlags
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.05,
- Step: "Checking system prerequisites...",
- IsComplete: false,
- LogOutput: "Starting prerequisite check...",
- }
-
- if err := g.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install prerequisites: %w", err)
- }
-
- systemPkgs, guruPkgs, manualPkgs, variantMap := g.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
-
- g.log(fmt.Sprintf("CATEGORIZED PACKAGES: system=%d, guru=%d, manual=%d", len(systemPkgs), len(guruPkgs), len(manualPkgs)))
-
- if len(systemPkgs) > 0 {
- systemPkgNames := g.extractPackageNames(systemPkgs)
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.35,
- Step: fmt.Sprintf("Installing %d system packages...", len(systemPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(systemPkgNames, ", ")),
- }
- if err := g.installPortagePackages(ctx, systemPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install Portage packages: %w", err)
- }
- }
-
- if len(guruPkgs) > 0 {
- g.log(fmt.Sprintf("FOUND %d GURU PACKAGES - WILL SYNC GURU REPO", len(guruPkgs)))
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.60,
- Step: "Syncing GURU repository...",
- IsComplete: false,
- LogOutput: "Syncing GURU repository to fetch latest ebuilds",
- }
- g.log("ABOUT TO CALL syncGURURepo")
- if err := g.syncGURURepo(ctx, sudoPassword, progressChan); err != nil {
- g.log(fmt.Sprintf("syncGURURepo RETURNED ERROR: %v", err))
- return fmt.Errorf("failed to sync GURU repository: %w", err)
- }
- g.log("syncGURURepo COMPLETED SUCCESSFULLY")
-
- guruPkgNames := g.extractPackageNames(guruPkgs)
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.65,
- Step: fmt.Sprintf("Installing %d GURU packages...", len(guruPkgs)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Installing GURU packages: %s", strings.Join(guruPkgNames, ", ")),
- }
- if err := g.installGURUPackages(ctx, guruPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install GURU packages: %w", err)
- }
- }
-
- if len(manualPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.85,
- Step: fmt.Sprintf("Building %d packages from source...", len(manualPkgs)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
- }
- if err := g.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install manual packages: %w", err)
- }
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.90,
- Step: "Configuring system...",
- IsComplete: false,
- LogOutput: "Starting post-installation configuration...",
- }
-
- terminal := g.DetectTerminalFromDeps(dependencies)
- if err := g.WriteEnvironmentConfig(terminal); err != nil {
- g.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
- }
-
- if err := g.WriteWindowManagerConfig(wm); err != nil {
- g.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
- }
-
- if err := g.EnableDMSService(ctx, wm); err != nil {
- g.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseComplete,
- Progress: 1.0,
- Step: "Installation complete!",
- IsComplete: true,
- LogOutput: "All packages installed and configured successfully",
- }
-
- return nil
-}
-
-func (g *GentooDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]PackageMapping, []PackageMapping, []string, map[string]deps.PackageVariant) {
- systemPkgs := []PackageMapping{}
- guruPkgs := []PackageMapping{}
- manualPkgs := []string{}
-
- variantMap := make(map[string]deps.PackageVariant)
- for _, dep := range dependencies {
- variantMap[dep.Name] = dep.Variant
- }
-
- packageMap := g.GetPackageMappingWithVariants(wm, variantMap)
-
- for _, dep := range dependencies {
- if disabledFlags[dep.Name] {
- continue
- }
-
- if dep.Status == deps.StatusInstalled && !reinstallFlags[dep.Name] {
- continue
- }
-
- pkgInfo, exists := packageMap[dep.Name]
- if !exists {
- g.log(fmt.Sprintf("Warning: No package mapping for %s", dep.Name))
- continue
- }
-
- switch pkgInfo.Repository {
- case RepoTypeSystem:
- systemPkgs = append(systemPkgs, pkgInfo)
- case RepoTypeGURU:
- guruPkgs = append(guruPkgs, pkgInfo)
- case RepoTypeManual:
- manualPkgs = append(manualPkgs, dep.Name)
- }
- }
-
- return systemPkgs, guruPkgs, manualPkgs, variantMap
-}
-
-func (g *GentooDistribution) extractPackageNames(packages []PackageMapping) []string {
- names := make([]string, len(packages))
- for i, pkg := range packages {
- names[i] = pkg.Name
- }
- return names
-}
-
-func (g *GentooDistribution) installPortagePackages(ctx context.Context, packages []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- packageNames := g.extractPackageNames(packages)
- g.log(fmt.Sprintf("Installing Portage packages: %s", strings.Join(packageNames, ", ")))
-
- for _, pkg := range packages {
- if pkg.AcceptKeywords != "" {
- if err := g.setPackageAcceptKeywords(ctx, pkg.Name, pkg.AcceptKeywords, sudoPassword); err != nil {
- return fmt.Errorf("failed to set accept keywords for %s: %w", pkg.Name, err)
- }
- }
- if pkg.UseFlags != "" {
- if err := g.setPackageUseFlags(ctx, pkg.Name, pkg.UseFlags, sudoPassword); err != nil {
- return fmt.Errorf("failed to set USE flags for %s: %w", pkg.Name, err)
- }
- }
- }
-
- args := []string{"emerge", "--ask=n", "--quiet"}
- args = append(args, packageNames...)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.40,
- Step: "Installing system packages...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return g.runWithProgressTimeout(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60, 0)
-}
-
-func (g *GentooDistribution) setPackageUseFlags(ctx context.Context, packageName, useFlags, sudoPassword string) error {
- packageUseDir := "/etc/portage/package.use"
-
- mkdirCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("mkdir -p %s", packageUseDir))
- if output, err := mkdirCmd.CombinedOutput(); err != nil {
- g.log(fmt.Sprintf("mkdir output: %s", string(output)))
- return fmt.Errorf("failed to create package.use directory: %w", err)
- }
-
- useFlagLine := fmt.Sprintf("%s %s", packageName, useFlags)
-
- checkExistingCmd := exec.CommandContext(ctx, "bash", "-c",
- fmt.Sprintf("grep -q '^%s ' %s/danklinux 2>/dev/null", packageName, packageUseDir))
- if checkExistingCmd.Run() == nil {
- g.log(fmt.Sprintf("Updating USE flags for %s from existing entry", packageName))
- escapedPkg := strings.ReplaceAll(packageName, "/", "\\/")
- replaceCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("sed -i '/^%s /d' %s/danklinux; exit_code=$?; exit $exit_code", escapedPkg, packageUseDir))
- if output, err := replaceCmd.CombinedOutput(); err != nil {
- g.log(fmt.Sprintf("sed delete output: %s", string(output)))
- return fmt.Errorf("failed to remove old USE flags: %w", err)
- }
- }
-
- appendCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("bash -c \"echo '%s' >> %s/danklinux\"", useFlagLine, packageUseDir))
-
- output, err := appendCmd.CombinedOutput()
- if err != nil {
- g.log(fmt.Sprintf("append output: %s", string(output)))
- return fmt.Errorf("failed to write USE flags to package.use: %w", err)
- }
-
- g.log(fmt.Sprintf("Set USE flags for %s: %s", packageName, useFlags))
- return nil
-}
-
-func (g *GentooDistribution) syncGURURepo(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.55,
- Step: "Enabling GURU repository...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo eselect repository enable guru",
- LogOutput: "Enabling GURU repository with eselect",
- }
-
- // Enable GURU repository
- enableCmd := ExecSudoCommand(ctx, sudoPassword,
- "eselect repository enable guru 2>&1; exit_code=$?; exit $exit_code")
- output, err := enableCmd.CombinedOutput()
-
- g.log(fmt.Sprintf("eselect repository enable guru output:\n%s", string(output)))
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.55,
- LogOutput: "GURU repository enabled",
- }
-
- if err != nil {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.55,
- LogOutput: fmt.Sprintf("ERROR enabling GURU: %v", err),
- Error: err,
- }
- return fmt.Errorf("failed to enable GURU repository: %w\nOutput: %s", err, string(output))
- }
-
- // Sync GURU repository
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.57,
- Step: "Syncing GURU repository...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo emaint sync --repo guru",
- LogOutput: "Syncing GURU repository",
- }
-
- syncCmd := ExecSudoCommand(ctx, sudoPassword,
- "emaint sync --repo guru 2>&1; exit_code=$?; exit $exit_code")
- syncOutput, syncErr := syncCmd.CombinedOutput()
-
- g.log(fmt.Sprintf("emaint sync --repo guru output:\n%s", string(syncOutput)))
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.57,
- LogOutput: "GURU repository synced",
- }
-
- if syncErr != nil {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.57,
- LogOutput: fmt.Sprintf("ERROR syncing GURU: %v", syncErr),
- Error: syncErr,
- }
- return fmt.Errorf("failed to sync GURU repository: %w\nOutput: %s", syncErr, string(syncOutput))
- }
-
- return nil
-}
-
-func (g *GentooDistribution) setPackageAcceptKeywords(ctx context.Context, packageName, keywords, sudoPassword string) error {
- checkCmd := exec.CommandContext(ctx, "portageq", "match", "/", packageName)
- if output, err := checkCmd.CombinedOutput(); err == nil && len(output) > 0 {
- g.log(fmt.Sprintf("Package %s is already available (may already be unmasked)", packageName))
- return nil
- }
-
- acceptKeywordsDir := "/etc/portage/package.accept_keywords"
-
- mkdirCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("mkdir -p %s", acceptKeywordsDir))
- if output, err := mkdirCmd.CombinedOutput(); err != nil {
- g.log(fmt.Sprintf("mkdir output: %s", string(output)))
- return fmt.Errorf("failed to create package.accept_keywords directory: %w", err)
- }
-
- keywordLine := fmt.Sprintf("%s %s", packageName, keywords)
-
- checkExistingCmd := exec.CommandContext(ctx, "bash", "-c",
- fmt.Sprintf("grep -q '^%s ' %s/danklinux 2>/dev/null", packageName, acceptKeywordsDir))
- if checkExistingCmd.Run() == nil {
- g.log(fmt.Sprintf("Updating accept keywords for %s from existing entry", packageName))
- escapedPkg := strings.ReplaceAll(packageName, "/", "\\/")
- replaceCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("sed -i '/^%s /d' %s/danklinux; exit_code=$?; exit $exit_code", escapedPkg, acceptKeywordsDir))
- if output, err := replaceCmd.CombinedOutput(); err != nil {
- g.log(fmt.Sprintf("sed delete output: %s", string(output)))
- return fmt.Errorf("failed to remove old accept keywords: %w", err)
- }
- }
-
- appendCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("bash -c \"echo '%s' >> %s/danklinux\"", keywordLine, acceptKeywordsDir))
-
- output, err := appendCmd.CombinedOutput()
- if err != nil {
- g.log(fmt.Sprintf("append output: %s", string(output)))
- return fmt.Errorf("failed to write accept keywords: %w", err)
- }
-
- g.log(fmt.Sprintf("Set accept keywords for %s: %s", packageName, keywords))
- return nil
-}
-
-func (g *GentooDistribution) installGURUPackages(ctx context.Context, packages []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- packageNames := g.extractPackageNames(packages)
- g.log(fmt.Sprintf("Installing GURU packages: %s", strings.Join(packageNames, ", ")))
-
- for _, pkg := range packages {
- if pkg.AcceptKeywords != "" {
- if err := g.setPackageAcceptKeywords(ctx, pkg.Name, pkg.AcceptKeywords, sudoPassword); err != nil {
- return fmt.Errorf("failed to set accept keywords for %s: %w", pkg.Name, err)
- }
- }
- if pkg.UseFlags != "" {
- if err := g.setPackageUseFlags(ctx, pkg.Name, pkg.UseFlags, sudoPassword); err != nil {
- return fmt.Errorf("failed to set USE flags for %s: %w", pkg.Name, err)
- }
- }
- }
-
- guruPackages := make([]string, len(packageNames))
- for i, pkg := range packageNames {
- guruPackages[i] = pkg + "::guru"
- }
-
- args := []string{"emerge", "--ask=n", "--quiet"}
- args = append(args, guruPackages...)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.70,
- Step: "Installing GURU packages...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return g.runWithProgressTimeout(cmd, progressChan, PhaseAURPackages, 0.70, 0.85, 0)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/interface.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/interface.go
deleted file mode 100644
index f719f1b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/interface.go
+++ /dev/null
@@ -1,158 +0,0 @@
-package distros
-
-import (
- "context"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-// DistroFamily represents a family of related distributions
-type DistroFamily string
-
-const (
- FamilyArch DistroFamily = "arch"
- FamilyFedora DistroFamily = "fedora"
- FamilySUSE DistroFamily = "suse"
- FamilyUbuntu DistroFamily = "ubuntu"
- FamilyDebian DistroFamily = "debian"
- FamilyNix DistroFamily = "nix"
- FamilyGentoo DistroFamily = "gentoo"
-)
-
-// PackageManagerType defines the package manager a distro uses
-type PackageManagerType string
-
-const (
- PackageManagerPacman PackageManagerType = "pacman"
- PackageManagerDNF PackageManagerType = "dnf"
- PackageManagerAPT PackageManagerType = "apt"
- PackageManagerZypper PackageManagerType = "zypper"
- PackageManagerNix PackageManagerType = "nix"
- PackageManagerPortage PackageManagerType = "portage"
-)
-
-// RepositoryType defines the type of repository for a package
-type RepositoryType string
-
-const (
- RepoTypeSystem RepositoryType = "system" // Standard system repo (pacman, dnf, apt)
- RepoTypeAUR RepositoryType = "aur" // Arch User Repository
- RepoTypeCOPR RepositoryType = "copr" // Fedora COPR
- RepoTypePPA RepositoryType = "ppa" // Ubuntu PPA
- RepoTypeOBS RepositoryType = "obs" // OpenBuild Service (Debian/OpenSUSE)
- RepoTypeFlake RepositoryType = "flake" // Nix flake
- RepoTypeGURU RepositoryType = "guru" // Gentoo GURU
- RepoTypeManual RepositoryType = "manual" // Manual build from source
-)
-
-// InstallPhase represents the current phase of installation
-type InstallPhase int
-
-const (
- PhasePrerequisites InstallPhase = iota
- PhaseAURHelper
- PhaseSystemPackages
- PhaseAURPackages
- PhaseCursorTheme
- PhaseConfiguration
- PhaseGreeterSetup
- PhaseComplete
-)
-
-// InstallProgressMsg represents progress during package installation
-type InstallProgressMsg struct {
- Phase InstallPhase
- Progress float64
- Step string
- IsComplete bool
- NeedsSudo bool
- CommandInfo string
- LogOutput string
- Error error
-}
-
-// PackageMapping defines how to install a package on a specific distro
-type PackageMapping struct {
- Name string // Package name to install
- Repository RepositoryType // Repository type
- RepoURL string // Repository URL if needed (e.g., COPR repo, PPA)
- BuildFunc string // Name of manual build function if RepoTypeManual
- UseFlags string // USE flags for Gentoo packages
- AcceptKeywords string // Accept keywords for Gentoo packages (e.g., "~amd64")
-}
-
-// Distribution defines a Linux distribution with all its specific configurations
-type Distribution interface {
- // Metadata
- GetID() string
- GetColorHex() string
- GetFamily() DistroFamily
- GetPackageManager() PackageManagerType
-
- // Dependency Detection
- DetectDependencies(ctx context.Context, wm deps.WindowManager) ([]deps.Dependency, error)
- DetectDependenciesWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]deps.Dependency, error)
-
- // Package Installation
- InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, disabledFlags map[string]bool, skipGlobalUseFlags bool, progressChan chan<- InstallProgressMsg) error
-
- // Package Mapping
- GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping
-
- // Prerequisites
- InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error
-}
-
-// DistroConfig holds configuration for a distribution
-type DistroConfig struct {
- ID string
- ColorHex string
- Family DistroFamily
- Constructor func(config DistroConfig, logChan chan<- string) Distribution
-}
-
-// Registry holds all supported distributions
-var Registry = make(map[string]DistroConfig)
-
-// Register adds a distribution to the registry
-func Register(id, colorHex string, family DistroFamily, constructor func(config DistroConfig, logChan chan<- string) Distribution) {
- Registry[id] = DistroConfig{
- ID: id,
- ColorHex: colorHex,
- Family: family,
- Constructor: constructor,
- }
-}
-
-// GetSupportedDistros returns a list of all supported distribution IDs
-func GetSupportedDistros() []string {
- ids := make([]string, 0, len(Registry))
- for id := range Registry {
- ids = append(ids, id)
- }
- return ids
-}
-
-// IsDistroSupported checks if a distribution ID is supported
-func IsDistroSupported(id string) bool {
- _, exists := Registry[id]
- return exists
-}
-
-// NewDistribution creates a distribution instance by ID
-func NewDistribution(id string, logChan chan<- string) (Distribution, error) {
- config, exists := Registry[id]
- if !exists {
- return nil, &UnsupportedDistributionError{ID: id}
- }
- return config.Constructor(config, logChan), nil
-}
-
-// UnsupportedDistributionError is returned when a distribution is not supported
-type UnsupportedDistributionError struct {
- ID string
-}
-
-func (e *UnsupportedDistributionError) Error() string {
- return "unsupported distribution: " + e.ID
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/manual_packages.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/manual_packages.go
deleted file mode 100644
index 2f38161..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/manual_packages.go
+++ /dev/null
@@ -1,663 +0,0 @@
-package distros
-
-import (
- "context"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-// ManualPackageInstaller provides methods for installing packages from source
-type ManualPackageInstaller struct {
- *BaseDistribution
-}
-
-// parseLatestTagFromGitOutput parses git ls-remote output and returns the latest tag
-func (m *ManualPackageInstaller) parseLatestTagFromGitOutput(output string) string {
- lines := strings.SplitSeq(output, "\n")
- for line := range lines {
- if strings.Contains(line, "refs/tags/") && !strings.Contains(line, "^{}") {
- parts := strings.Split(line, "refs/tags/")
- if len(parts) > 1 {
- latestTag := strings.TrimSpace(parts[1])
- return latestTag
- }
- }
- }
- return ""
-}
-
-// getLatestQuickshellTag fetches the latest tag from the quickshell repository
-func (m *ManualPackageInstaller) getLatestQuickshellTag(ctx context.Context) string {
- tagCmd := exec.CommandContext(ctx, "git", "ls-remote", "--tags", "--sort=-v:refname",
- "https://github.com/quickshell-mirror/quickshell.git")
- tagOutput, err := tagCmd.Output()
- if err != nil {
- m.log(fmt.Sprintf("Warning: failed to fetch quickshell tags: %v", err))
- return ""
- }
-
- return m.parseLatestTagFromGitOutput(string(tagOutput))
-}
-
-func (m *ManualPackageInstaller) InstallManualPackages(ctx context.Context, packages []string, variantMap map[string]deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- m.log(fmt.Sprintf("Installing manual packages: %s", strings.Join(packages, ", ")))
-
- for _, pkg := range packages {
- variant := variantMap[pkg]
- switch pkg {
- case "dms (DankMaterialShell)", "dms":
- if err := m.installDankMaterialShell(ctx, variant, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install DankMaterialShell: %w", err)
- }
- case "dgop":
- if err := m.installDgop(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install dgop: %w", err)
- }
- case "niri":
- if err := m.installNiri(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install niri: %w", err)
- }
- case "quickshell":
- if err := m.installQuickshell(ctx, variant, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install quickshell: %w", err)
- }
- case "hyprland":
- if err := m.installHyprland(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install hyprland: %w", err)
- }
- case "ghostty":
- if err := m.installGhostty(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install ghostty: %w", err)
- }
- case "matugen":
- if err := m.installMatugen(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install matugen: %w", err)
- }
- case "xwayland-satellite":
- if err := m.installXwaylandSatellite(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install xwayland-satellite: %w", err)
- }
- default:
- m.log(fmt.Sprintf("Warning: No manual build method for %s", pkg))
- }
- }
-
- return nil
-}
-
-func (m *ManualPackageInstaller) installDgop(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- m.log("Installing dgop from source...")
-
- homeDir := os.Getenv("HOME")
- if homeDir == "" {
- return fmt.Errorf("HOME environment variable not set")
- }
-
- cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
- if err := os.MkdirAll(cacheDir, 0o755); err != nil {
- return fmt.Errorf("failed to create cache directory: %w", err)
- }
-
- tmpDir := filepath.Join(cacheDir, "dgop-build")
- if err := os.MkdirAll(tmpDir, 0o755); err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer os.RemoveAll(tmpDir)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.1,
- Step: "Cloning dgop repository...",
- IsComplete: false,
- CommandInfo: "git clone https://github.com/AvengeMedia/dgop.git",
- }
-
- cloneCmd := exec.CommandContext(ctx, "git", "clone", "https://github.com/AvengeMedia/dgop.git", tmpDir)
- if err := cloneCmd.Run(); err != nil {
- m.logError("failed to clone dgop repository", err)
- return fmt.Errorf("failed to clone dgop repository: %w", err)
- }
-
- buildCmd := exec.CommandContext(ctx, "make")
- buildCmd.Dir = tmpDir
- buildCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
- if err := m.runWithProgressStep(buildCmd, progressChan, PhaseSystemPackages, 0.4, 0.7, "Building dgop..."); err != nil {
- return fmt.Errorf("failed to build dgop: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.7,
- Step: "Installing dgop...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo make install",
- }
-
- installCmd := ExecSudoCommand(ctx, sudoPassword, "make install")
- installCmd.Dir = tmpDir
- if err := installCmd.Run(); err != nil {
- m.logError("failed to install dgop", err)
- return fmt.Errorf("failed to install dgop: %w", err)
- }
-
- m.log("dgop installed successfully from source")
- return nil
-}
-
-func (m *ManualPackageInstaller) installNiri(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- m.log("Installing niri from source...")
-
- homeDir, _ := os.UserHomeDir()
- buildDir := filepath.Join(homeDir, ".cache", "dankinstall", "niri-build")
- tmpDir := filepath.Join(homeDir, ".cache", "dankinstall", "tmp")
- if err := os.MkdirAll(buildDir, 0o755); err != nil {
- return fmt.Errorf("failed to create build directory: %w", err)
- }
- if err := os.MkdirAll(tmpDir, 0o755); err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer func() {
- os.RemoveAll(buildDir)
- os.RemoveAll(tmpDir)
- }()
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.2,
- Step: "Cloning niri repository...",
- IsComplete: false,
- CommandInfo: "git clone https://github.com/YaLTeR/niri.git",
- }
-
- cloneCmd := exec.CommandContext(ctx, "git", "clone", "https://github.com/YaLTeR/niri.git", buildDir)
- if err := cloneCmd.Run(); err != nil {
- return fmt.Errorf("failed to clone niri: %w", err)
- }
-
- checkoutCmd := exec.CommandContext(ctx, "git", "-C", buildDir, "checkout", "v25.08")
- if err := checkoutCmd.Run(); err != nil {
- m.log(fmt.Sprintf("Warning: failed to checkout v25.08, using main: %v", err))
- }
-
- if !m.commandExists("cargo-deb") {
- cargoDebInstallCmd := exec.CommandContext(ctx, "cargo", "install", "cargo-deb")
- cargoDebInstallCmd.Env = append(os.Environ(), "TMPDIR="+tmpDir)
- if err := m.runWithProgressStep(cargoDebInstallCmd, progressChan, PhaseSystemPackages, 0.3, 0.35, "Installing cargo-deb..."); err != nil {
- return fmt.Errorf("failed to install cargo-deb: %w", err)
- }
- }
-
- buildDebCmd := exec.CommandContext(ctx, "cargo", "deb")
- buildDebCmd.Dir = buildDir
- buildDebCmd.Env = append(os.Environ(), "TMPDIR="+tmpDir)
- if err := m.runWithProgressStep(buildDebCmd, progressChan, PhaseSystemPackages, 0.35, 0.95, "Building niri deb package..."); err != nil {
- return fmt.Errorf("failed to build niri deb: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.95,
- Step: "Installing niri deb package...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "dpkg -i niri.deb",
- }
-
- installDebCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("dpkg -i %s/target/debian/niri_*.deb", buildDir))
-
- output, err := installDebCmd.CombinedOutput()
- if err != nil {
- m.log(fmt.Sprintf("dpkg install failed. Output:\n%s", string(output)))
- return fmt.Errorf("failed to install niri deb package: %w\nOutput:\n%s", err, string(output))
- }
-
- m.log(fmt.Sprintf("dpkg install successful. Output:\n%s", string(output)))
-
- m.log("niri installed successfully from source")
- return nil
-}
-
-func (m *ManualPackageInstaller) installQuickshell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- m.log("Installing quickshell from source...")
-
- homeDir := os.Getenv("HOME")
- if homeDir == "" {
- return fmt.Errorf("HOME environment variable not set")
- }
-
- cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
- if err := os.MkdirAll(cacheDir, 0o755); err != nil {
- return fmt.Errorf("failed to create cache directory: %w", err)
- }
-
- tmpDir := filepath.Join(cacheDir, "quickshell-build")
- if err := os.MkdirAll(tmpDir, 0o755); err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer os.RemoveAll(tmpDir)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.1,
- Step: "Cloning quickshell repository...",
- IsComplete: false,
- CommandInfo: "git clone https://github.com/quickshell-mirror/quickshell.git",
- }
-
- var cloneCmd *exec.Cmd
- if forceQuickshellGit || variant == deps.VariantGit {
- cloneCmd = exec.CommandContext(ctx, "git", "clone", "https://github.com/quickshell-mirror/quickshell.git", tmpDir)
- } else {
- latestTag := m.getLatestQuickshellTag(ctx)
- if latestTag != "" {
- m.log(fmt.Sprintf("Using latest quickshell tag: %s", latestTag))
- cloneCmd = exec.CommandContext(ctx, "git", "clone", "--branch", latestTag, "https://github.com/quickshell-mirror/quickshell.git", tmpDir)
- } else {
- m.log("Warning: failed to fetch latest tag, using default branch")
- cloneCmd = exec.CommandContext(ctx, "git", "clone", "https://github.com/quickshell-mirror/quickshell.git", tmpDir)
- }
- }
- if err := cloneCmd.Run(); err != nil {
- return fmt.Errorf("failed to clone quickshell: %w", err)
- }
-
- buildDir := tmpDir + "/build"
- if err := os.MkdirAll(buildDir, 0o755); err != nil {
- return fmt.Errorf("failed to create build directory: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.3,
- Step: "Configuring quickshell build...",
- IsComplete: false,
- CommandInfo: "cmake -B build -S . -G Ninja",
- }
-
- configureCmd := exec.CommandContext(ctx, "cmake", "-GNinja", "-B", "build",
- "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
- "-DCRASH_REPORTER=off",
- "-DCMAKE_CXX_STANDARD=20")
- configureCmd.Dir = tmpDir
- configureCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
-
- output, err := configureCmd.CombinedOutput()
- if err != nil {
- m.log(fmt.Sprintf("cmake configure failed. Output:\n%s", string(output)))
- return fmt.Errorf("failed to configure quickshell: %w\nCMake output:\n%s", err, string(output))
- }
-
- m.log(fmt.Sprintf("cmake configure successful. Output:\n%s", string(output)))
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.4,
- Step: "Building quickshell (this may take a while)...",
- IsComplete: false,
- CommandInfo: "cmake --build build",
- }
-
- buildCmd := exec.CommandContext(ctx, "cmake", "--build", "build")
- buildCmd.Dir = tmpDir
- buildCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
- if err := m.runWithProgressStep(buildCmd, progressChan, PhaseSystemPackages, 0.4, 0.8, "Building quickshell..."); err != nil {
- return fmt.Errorf("failed to build quickshell: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.8,
- Step: "Installing quickshell...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo cmake --install build",
- }
-
- installCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install build")
- installCmd.Dir = tmpDir
- if err := installCmd.Run(); err != nil {
- return fmt.Errorf("failed to install quickshell: %w", err)
- }
-
- m.log("quickshell installed successfully from source")
- return nil
-}
-
-func (m *ManualPackageInstaller) installHyprland(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- m.log("Installing Hyprland from source...")
-
- homeDir := os.Getenv("HOME")
- if homeDir == "" {
- return fmt.Errorf("HOME environment variable not set")
- }
-
- cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
- if err := os.MkdirAll(cacheDir, 0o755); err != nil {
- return fmt.Errorf("failed to create cache directory: %w", err)
- }
-
- tmpDir := filepath.Join(cacheDir, "hyprland-build")
- if err := os.MkdirAll(tmpDir, 0o755); err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer os.RemoveAll(tmpDir)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.1,
- Step: "Cloning Hyprland repository...",
- IsComplete: false,
- CommandInfo: "git clone --recursive https://github.com/hyprwm/Hyprland.git",
- }
-
- cloneCmd := exec.CommandContext(ctx, "git", "clone", "--recursive", "https://github.com/hyprwm/Hyprland.git", tmpDir)
- if err := cloneCmd.Run(); err != nil {
- return fmt.Errorf("failed to clone Hyprland: %w", err)
- }
-
- checkoutCmd := exec.CommandContext(ctx, "git", "-C", tmpDir, "checkout", "v0.50.1")
- if err := checkoutCmd.Run(); err != nil {
- m.log(fmt.Sprintf("Warning: failed to checkout v0.50.1, using main: %v", err))
- }
-
- buildCmd := exec.CommandContext(ctx, "make", "all")
- buildCmd.Dir = tmpDir
- buildCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
- if err := m.runWithProgressStep(buildCmd, progressChan, PhaseSystemPackages, 0.2, 0.8, "Building Hyprland..."); err != nil {
- return fmt.Errorf("failed to build Hyprland: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.8,
- Step: "Installing Hyprland...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo make install",
- }
-
- installCmd := ExecSudoCommand(ctx, sudoPassword, "make install")
- installCmd.Dir = tmpDir
- if err := installCmd.Run(); err != nil {
- return fmt.Errorf("failed to install Hyprland: %w", err)
- }
-
- m.log("Hyprland installed successfully from source")
- return nil
-}
-
-func (m *ManualPackageInstaller) installGhostty(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- m.log("Installing Ghostty from source...")
-
- homeDir := os.Getenv("HOME")
- if homeDir == "" {
- return fmt.Errorf("HOME environment variable not set")
- }
-
- cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
- if err := os.MkdirAll(cacheDir, 0o755); err != nil {
- return fmt.Errorf("failed to create cache directory: %w", err)
- }
-
- tmpDir := filepath.Join(cacheDir, "ghostty-build")
- if err := os.MkdirAll(tmpDir, 0o755); err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer os.RemoveAll(tmpDir)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.1,
- Step: "Cloning Ghostty repository...",
- IsComplete: false,
- CommandInfo: "git clone https://github.com/ghostty-org/ghostty.git",
- }
-
- cloneCmd := exec.CommandContext(ctx, "git", "clone", "https://github.com/ghostty-org/ghostty.git", tmpDir)
- if err := cloneCmd.Run(); err != nil {
- return fmt.Errorf("failed to clone Ghostty: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.2,
- Step: "Building Ghostty (this may take a while)...",
- IsComplete: false,
- CommandInfo: "zig build -Doptimize=ReleaseFast",
- }
-
- buildCmd := exec.CommandContext(ctx, "zig", "build", "-Doptimize=ReleaseFast")
- buildCmd.Dir = tmpDir
- buildCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
- if err := buildCmd.Run(); err != nil {
- return fmt.Errorf("failed to build Ghostty: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.8,
- Step: "Installing Ghostty...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo cp zig-out/bin/ghostty /usr/local/bin/",
- }
-
- installCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("cp %s/zig-out/bin/ghostty /usr/local/bin/", tmpDir))
- if err := installCmd.Run(); err != nil {
- return fmt.Errorf("failed to install Ghostty: %w", err)
- }
-
- m.log("Ghostty installed successfully from source")
- return nil
-}
-
-func (m *ManualPackageInstaller) installMatugen(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- m.log("Installing matugen from source...")
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.1,
- Step: "Installing matugen via cargo...",
- IsComplete: false,
- CommandInfo: "cargo install matugen",
- }
-
- installCmd := exec.CommandContext(ctx, "cargo", "install", "matugen")
- if err := m.runWithProgressStep(installCmd, progressChan, PhaseSystemPackages, 0.1, 0.7, "Building matugen..."); err != nil {
- return fmt.Errorf("failed to install matugen: %w", err)
- }
-
- homeDir := os.Getenv("HOME")
- sourcePath := filepath.Join(homeDir, ".cargo", "bin", "matugen")
- targetPath := "/usr/local/bin/matugen"
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.7,
- Step: "Installing matugen binary to system...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo cp %s %s", sourcePath, targetPath),
- }
-
- copyCmd := exec.CommandContext(ctx, "sudo", "-S", "cp", sourcePath, targetPath)
- copyCmd.Stdin = strings.NewReader(sudoPassword + "\n")
- if err := copyCmd.Run(); err != nil {
- return fmt.Errorf("failed to copy matugen to /usr/local/bin: %w", err)
- }
-
- // Make it executable
- chmodCmd := exec.CommandContext(ctx, "sudo", "-S", "chmod", "+x", targetPath)
- chmodCmd.Stdin = strings.NewReader(sudoPassword + "\n")
- if err := chmodCmd.Run(); err != nil {
- return fmt.Errorf("failed to make matugen executable: %w", err)
- }
-
- m.log("matugen installed successfully from source")
- return nil
-}
-
-func (m *ManualPackageInstaller) installDankMaterialShell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- m.log("Installing DankMaterialShell (DMS)...")
-
- if err := m.installDMSBinary(ctx, sudoPassword, progressChan); err != nil {
- m.logError("Failed to install DMS binary", err)
- }
-
- dmsPath := filepath.Join(os.Getenv("HOME"), ".config/quickshell/dms")
-
- if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.90,
- Step: "Cloning DankMaterialShell...",
- IsComplete: false,
- CommandInfo: "git clone https://github.com/AvengeMedia/DankMaterialShell.git",
- }
-
- configDir := filepath.Dir(dmsPath)
- if err := os.MkdirAll(configDir, 0o755); err != nil {
- return fmt.Errorf("failed to create quickshell config directory: %w", err)
- }
-
- cloneCmd := exec.CommandContext(ctx, "git", "clone",
- "https://github.com/AvengeMedia/DankMaterialShell.git", dmsPath)
- if err := cloneCmd.Run(); err != nil {
- return fmt.Errorf("failed to clone DankMaterialShell: %w", err)
- }
-
- if forceDMSGit || variant == deps.VariantGit {
- m.log("Using git variant (master branch)")
- return nil
- }
-
- tagCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "describe", "--tags", "--abbrev=0", "origin/master")
- tagOutput, err := tagCmd.Output()
- if err != nil {
- m.log("Using default branch (no tags found)")
- return nil
- }
-
- latestTag := strings.TrimSpace(string(tagOutput))
- checkoutCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "checkout", latestTag)
- if err := checkoutCmd.Run(); err != nil {
- m.logError(fmt.Sprintf("Failed to checkout tag %s", latestTag), err)
- return nil
- }
-
- m.log(fmt.Sprintf("Checked out latest tag: %s", latestTag))
- m.log("DankMaterialShell cloned successfully")
- return nil
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.90,
- Step: "Updating DankMaterialShell...",
- IsComplete: false,
- CommandInfo: "Updating ~/.config/quickshell/dms",
- }
-
- fetchCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "fetch", "origin", "--tags", "--force")
- if err := fetchCmd.Run(); err != nil {
- m.logError("Failed to fetch updates", err)
- return nil
- }
-
- if forceDMSGit || variant == deps.VariantGit {
- branchCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "rev-parse", "--abbrev-ref", "HEAD")
- branchOutput, err := branchCmd.Output()
- if err != nil {
- m.logError("Failed to get current branch", err)
- return nil
- }
-
- branch := strings.TrimSpace(string(branchOutput))
- if branch == "" {
- branch = "master"
- }
-
- pullCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "pull", "origin", branch)
- if err := pullCmd.Run(); err != nil {
- m.logError("Failed to pull updates", err)
- return nil
- }
-
- m.log("DankMaterialShell updated successfully (git variant)")
- return nil
- }
-
- latestTagCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "describe", "--tags", "--abbrev=0", "origin/master")
- tagOutput, err := latestTagCmd.Output()
- if err != nil {
- m.logError("Failed to get latest tag", err)
- return nil
- }
-
- latestTag := strings.TrimSpace(string(tagOutput))
- checkoutCmd := exec.CommandContext(ctx, "git", "-C", dmsPath, "checkout", latestTag)
- if err := checkoutCmd.Run(); err != nil {
- m.logError(fmt.Sprintf("Failed to checkout tag %s", latestTag), err)
- return nil
- }
-
- m.log(fmt.Sprintf("Updated to tag: %s", latestTag))
- return nil
-}
-
-func (m *ManualPackageInstaller) installXwaylandSatellite(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- m.log("Installing xwayland-satellite from source...")
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.1,
- Step: "Installing xwayland-satellite via cargo...",
- IsComplete: false,
- CommandInfo: "cargo install --git https://github.com/Supreeeme/xwayland-satellite --tag v0.7",
- }
-
- installCmd := exec.CommandContext(ctx, "cargo", "install", "--git", "https://github.com/Supreeeme/xwayland-satellite", "--tag", "v0.7")
- if err := m.runWithProgressStep(installCmd, progressChan, PhaseSystemPackages, 0.1, 0.7, "Building xwayland-satellite..."); err != nil {
- return fmt.Errorf("failed to install xwayland-satellite: %w", err)
- }
-
- homeDir := os.Getenv("HOME")
- sourcePath := filepath.Join(homeDir, ".cargo", "bin", "xwayland-satellite")
- targetPath := "/usr/local/bin/xwayland-satellite"
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.7,
- Step: "Installing xwayland-satellite binary to system...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo cp %s %s", sourcePath, targetPath),
- }
-
- copyCmd := exec.CommandContext(ctx, "sudo", "-S", "cp", sourcePath, targetPath)
- copyCmd.Stdin = strings.NewReader(sudoPassword + "\n")
- if err := copyCmd.Run(); err != nil {
- return fmt.Errorf("failed to copy xwayland-satellite to /usr/local/bin: %w", err)
- }
-
- chmodCmd := exec.CommandContext(ctx, "sudo", "-S", "chmod", "+x", targetPath)
- chmodCmd.Stdin = strings.NewReader(sudoPassword + "\n")
- if err := chmodCmd.Run(); err != nil {
- return fmt.Errorf("failed to make xwayland-satellite executable: %w", err)
- }
-
- m.log("xwayland-satellite installed successfully from source")
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/manual_packages_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/manual_packages_test.go
deleted file mode 100644
index 4d9fc62..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/manual_packages_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package distros
-
-import (
- "testing"
-)
-
-func TestManualPackageInstaller_parseLatestTagFromGitOutput(t *testing.T) {
- tests := []struct {
- name string
- input string
- expected string
- }{
- {
- name: "normal tag output",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v0.2.1
-a5431dd02dc23d9ef1680e67777fed00fe5f7cda refs/tags/v0.2.0
-703a3789083d2f990c4e99cd25c97c2a4cccbd81 refs/tags/v0.1.0`,
- expected: "v0.2.1",
- },
- {
- name: "annotated tags with ^{}",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v0.2.1
-b1b150fab00a93ea983aaca5df55304bc837f51c refs/tags/v0.2.1^{}
-a5431dd02dc23d9ef1680e67777fed00fe5f7cda refs/tags/v0.2.0`,
- expected: "v0.2.1",
- },
- {
- name: "mixed tags",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v0.3.0
-b1b150fab00a93ea983aaca5df55304bc837f51c refs/tags/v0.3.0^{}
-a5431dd02dc23d9ef1680e67777fed00fe5f7cda refs/tags/v0.2.0
-c1c150fab00a93ea983aaca5df55304bc837f51d refs/tags/beta-1`,
- expected: "v0.3.0",
- },
- {
- name: "empty output",
- input: "",
- expected: "",
- },
- {
- name: "no tags",
- input: "some other output\nwithout tags",
- expected: "",
- },
- {
- name: "only annotated tags",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v0.2.1^{}
-a5431dd02dc23d9ef1680e67777fed00fe5f7cda refs/tags/v0.2.0^{}`,
- expected: "",
- },
- {
- name: "single tag",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v1.0.0`,
- expected: "v1.0.0",
- },
- {
- name: "tag with extra whitespace",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v0.2.1
-a5431dd02dc23d9ef1680e67777fed00fe5f7cda refs/tags/v0.2.0`,
- expected: "v0.2.1",
- },
- {
- name: "beta and rc tags",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v0.3.0-beta.1
-a5431dd02dc23d9ef1680e67777fed00fe5f7cda refs/tags/v0.2.0`,
- expected: "v0.3.0-beta.1",
- },
- {
- name: "tags without v prefix",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/0.2.1
-a5431dd02dc23d9ef1680e67777fed00fe5f7cda refs/tags/0.2.0`,
- expected: "0.2.1",
- },
- {
- name: "multiple lines with spaces",
- input: `
-a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v1.2.3
-a5431dd02dc23d9ef1680e67777fed00fe5f7cda refs/tags/v1.2.2
-`,
- expected: "v1.2.3",
- },
- {
- name: "tag at end of line",
- input: `a1a150fab00a93ea983aaca5df55304bc837f51b refs/tags/v0.2.1`,
- expected: "v0.2.1",
- },
- }
-
- logChan := make(chan string, 100)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
- installer := &ManualPackageInstaller{BaseDistribution: base}
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := installer.parseLatestTagFromGitOutput(tt.input)
-
- if result != tt.expected {
- t.Errorf("parseLatestTagFromGitOutput() = %q, expected %q", result, tt.expected)
- }
- })
- }
-}
-
-func TestManualPackageInstaller_parseLatestTagFromGitOutput_EmptyInstaller(t *testing.T) {
- // Test that parsing works even with a minimal installer setup
- logChan := make(chan string, 10)
- defer close(logChan)
-
- base := NewBaseDistribution(logChan)
- installer := &ManualPackageInstaller{BaseDistribution: base}
-
- input := `abc123 refs/tags/v1.0.0
-def456 refs/tags/v0.9.0`
-
- result := installer.parseLatestTagFromGitOutput(input)
-
- if result != "v1.0.0" {
- t.Errorf("Expected v1.0.0, got %s", result)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/minimal_install.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/minimal_install.go
deleted file mode 100644
index 029f1ba..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/minimal_install.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package distros
-
-type minimalInstallGroup struct {
- packages []string
- minimal bool
-}
-
-func shouldPreferMinimalInstall(pkg string) bool {
- switch pkg {
- case "niri", "niri-git":
- return true
- default:
- return false
- }
-}
-
-func splitMinimalInstallPackages(packages []string) (normal []string, minimal []string) {
- for _, pkg := range packages {
- if shouldPreferMinimalInstall(pkg) {
- minimal = append(minimal, pkg)
- continue
- }
- normal = append(normal, pkg)
- }
- return normal, minimal
-}
-
-func orderedMinimalInstallGroups(packages []string) []minimalInstallGroup {
- normal, minimal := splitMinimalInstallPackages(packages)
- groups := make([]minimalInstallGroup, 0, 2)
- if len(minimal) > 0 {
- groups = append(groups, minimalInstallGroup{
- packages: minimal,
- minimal: true,
- })
- }
- if len(normal) > 0 {
- groups = append(groups, minimalInstallGroup{
- packages: normal,
- minimal: false,
- })
- }
- return groups
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/opensuse.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/opensuse.go
deleted file mode 100644
index 62f40cd..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/opensuse.go
+++ /dev/null
@@ -1,856 +0,0 @@
-package distros
-
-import (
- "context"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "slices"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-func init() {
- Register("opensuse-tumbleweed", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewOpenSUSEDistribution(config, logChan)
- })
- Register("opensuse-leap", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewOpenSUSEDistribution(config, logChan)
- })
- Register("opensuse-slowroll", "#73BA25", FamilySUSE, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewOpenSUSEDistribution(config, logChan)
- })
-}
-
-type OpenSUSEDistribution struct {
- *BaseDistribution
- *ManualPackageInstaller
- config DistroConfig
-}
-
-const openSUSENiriWaylandServerPackage = "libwayland-server0"
-
-func NewOpenSUSEDistribution(config DistroConfig, logChan chan<- string) *OpenSUSEDistribution {
- base := NewBaseDistribution(logChan)
- return &OpenSUSEDistribution{
- BaseDistribution: base,
- ManualPackageInstaller: &ManualPackageInstaller{BaseDistribution: base},
- config: config,
- }
-}
-
-func (o *OpenSUSEDistribution) GetID() string {
- return o.config.ID
-}
-
-func (o *OpenSUSEDistribution) GetColorHex() string {
- return o.config.ColorHex
-}
-
-func (o *OpenSUSEDistribution) GetFamily() DistroFamily {
- return o.config.Family
-}
-
-func (o *OpenSUSEDistribution) GetPackageManager() PackageManagerType {
- return PackageManagerZypper
-}
-
-func (o *OpenSUSEDistribution) DetectDependencies(ctx context.Context, wm deps.WindowManager) ([]deps.Dependency, error) {
- return o.DetectDependenciesWithTerminal(ctx, wm, deps.TerminalGhostty)
-}
-
-func (o *OpenSUSEDistribution) DetectDependenciesWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]deps.Dependency, error) {
- var dependencies []deps.Dependency
-
- // DMS at the top (shell is prominent)
- dependencies = append(dependencies, o.detectDMS())
-
- // Terminal with choice support
- dependencies = append(dependencies, o.detectSpecificTerminal(terminal))
-
- // Common detections using base methods
- dependencies = append(dependencies, o.detectGit())
- dependencies = append(dependencies, o.detectWindowManager(wm))
- dependencies = append(dependencies, o.detectQuickshell())
- dependencies = append(dependencies, o.detectDMSGreeter())
- dependencies = append(dependencies, o.detectXDGPortal())
- dependencies = append(dependencies, o.detectAccountsService())
-
- // Hyprland-specific tools
- if wm == deps.WindowManagerHyprland {
- dependencies = append(dependencies, o.detectHyprlandTools()...)
- }
-
- // Niri-specific tools
- if wm == deps.WindowManagerNiri {
- dependencies = append(dependencies, o.detectXwaylandSatellite())
- }
-
- dependencies = append(dependencies, o.detectMatugen())
- dependencies = append(dependencies, o.detectDgop())
-
- return dependencies, nil
-}
-
-func (o *OpenSUSEDistribution) detectXDGPortal() deps.Dependency {
- return o.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", o.packageInstalled("xdg-desktop-portal-gtk"))
-}
-
-func (o *OpenSUSEDistribution) packageInstalled(pkg string) bool {
- cmd := exec.Command("rpm", "-q", pkg)
- err := cmd.Run()
- return err == nil
-}
-
-func (o *OpenSUSEDistribution) detectDMSGreeter() deps.Dependency {
- return o.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", o.packageInstalled("dms-greeter"))
-}
-
-func (o *OpenSUSEDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
- return o.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
-}
-
-func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping {
- packages := map[string]PackageMapping{
- // Standard zypper packages
- "git": {Name: "git", Repository: RepoTypeSystem},
- "kitty": {Name: "kitty", Repository: RepoTypeSystem},
- "alacritty": {Name: "alacritty", Repository: RepoTypeSystem},
- "xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
- "accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
-
- // DMS packages from OBS
- "dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]),
- "quickshell": o.getQuickshellMapping(variants["quickshell"]),
- "dms-greeter": {Name: "dms-greeter", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
- "ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
- "matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
- "dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
- }
-
- switch wm {
- case deps.WindowManagerHyprland:
- packages["hyprland"] = PackageMapping{Name: "hyprland", Repository: RepoTypeSystem}
- packages["hyprctl"] = PackageMapping{Name: "hyprland", Repository: RepoTypeSystem}
- packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
- case deps.WindowManagerNiri:
- // Niri stable has native package support on openSUSE
- niriVariant := variants["niri"]
- packages["niri"] = o.getNiriMapping(niriVariant)
- packages["xwayland-satellite"] = o.getXwaylandSatelliteMapping(niriVariant)
- }
-
- return packages
-}
-
-func (o *OpenSUSEDistribution) getDmsMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "dms-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:dms-git"}
- }
- return PackageMapping{Name: "dms", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:dms"}
-}
-
-func (o *OpenSUSEDistribution) getQuickshellMapping(variant deps.PackageVariant) PackageMapping {
- if forceQuickshellGit || variant == deps.VariantGit {
- return PackageMapping{Name: "quickshell-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
- }
- return PackageMapping{Name: "quickshell", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
-}
-
-func (o *OpenSUSEDistribution) getNiriMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "niri-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
- }
- return PackageMapping{Name: "niri", Repository: RepoTypeSystem}
-}
-
-func (o *OpenSUSEDistribution) getXwaylandSatelliteMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "xwayland-satellite-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
- }
- return PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeSystem}
-}
-
-func (o *OpenSUSEDistribution) detectXwaylandSatellite() deps.Dependency {
- status := deps.StatusMissing
- if o.commandExists("xwayland-satellite") {
- status = deps.StatusInstalled
- }
-
- return deps.Dependency{
- Name: "xwayland-satellite",
- Status: status,
- Description: "Xwayland support",
- Required: true,
- }
-}
-
-func (o *OpenSUSEDistribution) detectAccountsService() deps.Dependency {
- status := deps.StatusMissing
- if o.packageInstalled("accountsservice") {
- status = deps.StatusInstalled
- }
-
- return deps.Dependency{
- Name: "accountsservice",
- Status: status,
- Description: "D-Bus interface for user account query and manipulation",
- Required: true,
- }
-}
-
-func (o *OpenSUSEDistribution) getPrerequisites() []string {
- return []string{}
-}
-
-func (o *OpenSUSEDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- prerequisites := o.getPrerequisites()
- var missingPkgs []string
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.06,
- Step: "Checking prerequisites...",
- IsComplete: false,
- LogOutput: "Checking prerequisite packages",
- }
-
- for _, pkg := range prerequisites {
- checkCmd := exec.CommandContext(ctx, "rpm", "-q", pkg)
- if err := checkCmd.Run(); err != nil {
- missingPkgs = append(missingPkgs, pkg)
- }
- }
-
- _, err := exec.LookPath("go")
- if err != nil {
- o.log("go not found in PATH, will install go")
- missingPkgs = append(missingPkgs, "go")
- } else {
- o.log("go already available in PATH")
- }
-
- if len(missingPkgs) == 0 {
- o.log("All prerequisites already installed")
- return nil
- }
-
- o.log(fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")))
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.08,
- Step: fmt.Sprintf("Installing %d prerequisites...", len(missingPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo zypper install -y %s", strings.Join(missingPkgs, " ")),
- LogOutput: fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")),
- }
-
- args := []string{"zypper", "install", "-y"}
- args = append(args, missingPkgs...)
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- output, err := cmd.CombinedOutput()
- if err != nil {
- o.logError("failed to install prerequisites", err)
- o.log(fmt.Sprintf("Prerequisites command output: %s", string(output)))
- return fmt.Errorf("failed to install prerequisites: %w", err)
- }
- o.log(fmt.Sprintf("Prerequisites install output: %s", string(output)))
-
- return nil
-}
-
-func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, disabledFlags map[string]bool, skipGlobalUseFlags bool, progressChan chan<- InstallProgressMsg) error {
- // Phase 1: Check Prerequisites
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.05,
- Step: "Checking system prerequisites...",
- IsComplete: false,
- LogOutput: "Starting prerequisite check...",
- }
-
- if err := o.disableInstallMediaRepos(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to disable install media repositories: %w", err)
- }
-
- if err := o.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install prerequisites: %w", err)
- }
-
- systemPkgs, obsPkgs, manualPkgs, variantMap := o.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
-
- // Enable OBS repositories
- if len(obsPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.15,
- Step: "Enabling OBS repositories...",
- IsComplete: false,
- LogOutput: "Setting up OBS repositories for additional packages",
- }
- if err := o.enableOBSRepos(ctx, obsPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to enable OBS repositories: %w", err)
- }
- }
-
- // Phase 3: System Packages (Zypper)
- if len(systemPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.35,
- Step: fmt.Sprintf("Installing %d system packages...", len(systemPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(systemPkgs, ", ")),
- }
- if err := o.installZypperPackages(ctx, systemPkgs, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60); err != nil {
- return fmt.Errorf("failed to install zypper packages: %w", err)
- }
- }
-
- // OBS Packages
- obsPkgNames := o.extractPackageNames(obsPkgs)
- if len(obsPkgNames) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages,
- Progress: 0.65,
- Step: fmt.Sprintf("Installing %d OBS packages...", len(obsPkgNames)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Installing OBS packages: %s", strings.Join(obsPkgNames, ", ")),
- }
- if err := o.installZypperPackages(ctx, obsPkgNames, sudoPassword, progressChan, PhaseAURPackages, "Installing OBS packages...", 0.70, 0.85); err != nil {
- return fmt.Errorf("failed to install OBS packages: %w", err)
- }
- }
-
- // Manual Builds
- if len(manualPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.85,
- Step: fmt.Sprintf("Building %d packages from source...", len(manualPkgs)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
- }
- if err := o.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install manual packages: %w", err)
- }
- }
-
- // Configuration
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.90,
- Step: "Configuring system...",
- IsComplete: false,
- LogOutput: "Starting post-installation configuration...",
- }
-
- terminal := o.DetectTerminalFromDeps(dependencies)
- if err := o.WriteEnvironmentConfig(terminal); err != nil {
- o.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
- }
-
- if err := o.WriteWindowManagerConfig(wm); err != nil {
- o.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
- }
-
- if err := o.EnableDMSService(ctx, wm); err != nil {
- o.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
- }
-
- // Complete
- progressChan <- InstallProgressMsg{
- Phase: PhaseComplete,
- Progress: 1.0,
- Step: "Installation complete!",
- IsComplete: true,
- LogOutput: "All packages installed and configured successfully",
- }
-
- return nil
-}
-
-func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string, map[string]deps.PackageVariant) {
- systemPkgs := []string{}
- obsPkgs := []PackageMapping{}
- manualPkgs := []string{}
-
- variantMap := make(map[string]deps.PackageVariant)
- for _, dep := range dependencies {
- variantMap[dep.Name] = dep.Variant
- }
-
- packageMap := o.GetPackageMappingWithVariants(wm, variantMap)
-
- for _, dep := range dependencies {
- if disabledFlags[dep.Name] {
- continue
- }
-
- if dep.Status == deps.StatusInstalled && !reinstallFlags[dep.Name] {
- continue
- }
-
- pkgInfo, exists := packageMap[dep.Name]
- if !exists {
- o.log(fmt.Sprintf("Warning: No package mapping for %s", dep.Name))
- continue
- }
-
- switch pkgInfo.Repository {
- case RepoTypeSystem:
- systemPkgs = append(systemPkgs, pkgInfo.Name)
- case RepoTypeOBS:
- obsPkgs = append(obsPkgs, pkgInfo)
- case RepoTypeManual:
- manualPkgs = append(manualPkgs, dep.Name)
- }
- }
-
- systemPkgs = o.appendMissingSystemPackages(systemPkgs, openSUSENiriRuntimePackages(wm, disabledFlags))
-
- return systemPkgs, obsPkgs, manualPkgs, variantMap
-}
-
-func openSUSENiriRuntimePackages(wm deps.WindowManager, disabledFlags map[string]bool) []string {
- if wm != deps.WindowManagerNiri || disabledFlags["niri"] {
- return nil
- }
-
- return []string{openSUSENiriWaylandServerPackage}
-}
-
-func (o *OpenSUSEDistribution) appendMissingSystemPackages(systemPkgs []string, extraPkgs []string) []string {
- for _, pkg := range extraPkgs {
- if slices.Contains(systemPkgs, pkg) || o.packageInstalled(pkg) {
- continue
- }
-
- o.log(fmt.Sprintf("Adding openSUSE runtime package: %s", pkg))
- systemPkgs = append(systemPkgs, pkg)
- }
-
- return systemPkgs
-}
-
-func (o *OpenSUSEDistribution) extractPackageNames(packages []PackageMapping) []string {
- names := make([]string, len(packages))
- for i, pkg := range packages {
- names[i] = pkg.Name
- }
- return names
-}
-
-func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- enabledRepos := make(map[string]bool)
-
- osInfo, err := GetOSInfo()
- if err != nil {
- return fmt.Errorf("failed to get OS info: %w", err)
- }
-
- obsDistroVersion := "openSUSE_Tumbleweed"
- switch osInfo.Distribution.ID {
- case "opensuse-leap":
- obsDistroVersion = fmt.Sprintf("openSUSE_Leap_%s", osInfo.VersionID)
- case "opensuse-slowroll":
- obsDistroVersion = "openSUSE_Slowroll"
- }
-
- for _, pkg := range obsPkgs {
- if pkg.RepoURL != "" && !enabledRepos[pkg.RepoURL] {
- o.log(fmt.Sprintf("Enabling OBS repository: %s", pkg.RepoURL))
-
- // RepoURL format: "home:AvengeMedia:danklinux"
- repoPath := strings.ReplaceAll(pkg.RepoURL, ":", ":/")
- repoName := strings.ReplaceAll(pkg.RepoURL, ":", "-")
- repoURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/%s/%s.repo",
- repoPath, obsDistroVersion, pkg.RepoURL)
-
- checkCmd := exec.CommandContext(ctx, "zypper", "repos", repoName)
- if checkCmd.Run() == nil {
- o.log(fmt.Sprintf("OBS repo %s already exists, skipping", pkg.RepoURL))
- enabledRepos[pkg.RepoURL] = true
- continue
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.20,
- Step: fmt.Sprintf("Enabling OBS repo %s...", pkg.RepoURL),
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo zypper addrepo %s", repoURL),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("zypper addrepo -f %s", repoURL))
- if err := o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
- o.log(fmt.Sprintf("OBS repo %s add failed (may already exist): %v", pkg.RepoURL, err))
- }
-
- enabledRepos[pkg.RepoURL] = true
- o.log(fmt.Sprintf("OBS repo %s enabled successfully", pkg.RepoURL))
- }
- }
-
- // Refresh repositories with GPG auto-import
- if len(enabledRepos) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.25,
- Step: "Refreshing repositories...",
- NeedsSudo: true,
- CommandInfo: "sudo zypper --gpg-auto-import-keys refresh",
- }
-
- refreshCmd := ExecSudoCommand(ctx, sudoPassword, "zypper --gpg-auto-import-keys refresh")
- if err := o.runWithProgress(refreshCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
- return fmt.Errorf("failed to refresh repositories: %w", err)
- }
- }
-
- return nil
-}
-
-func isOpenSUSEInstallMediaURI(uri string) bool {
- normalizedURI := strings.ToLower(strings.TrimSpace(uri))
-
- return strings.HasPrefix(normalizedURI, "cd:/") ||
- strings.HasPrefix(normalizedURI, "dvd:/") ||
- strings.HasPrefix(normalizedURI, "hd:/") ||
- strings.HasPrefix(normalizedURI, "iso:/")
-}
-
-func parseZypperInstallMediaAliases(output string) []string {
- var aliases []string
-
- for _, line := range strings.Split(output, "\n") {
- line = strings.TrimSpace(line)
- if line == "" || !strings.Contains(line, "|") {
- continue
- }
-
- parts := strings.Split(line, "|")
- if len(parts) < 7 {
- continue
- }
-
- for i := range parts {
- parts[i] = strings.TrimSpace(parts[i])
- }
-
- alias := parts[1]
- enabled := strings.ToLower(parts[3])
- uri := parts[len(parts)-1]
-
- if alias == "" || strings.EqualFold(alias, "alias") {
- continue
- }
- if enabled != "" && enabled != "yes" {
- continue
- }
- if !isOpenSUSEInstallMediaURI(uri) {
- continue
- }
-
- aliases = append(aliases, alias)
- }
-
- return aliases
-}
-
-func (o *OpenSUSEDistribution) disableInstallMediaRepos(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- listCmd := exec.CommandContext(ctx, "zypper", "repos", "-u")
- output, err := listCmd.CombinedOutput()
- if err != nil {
- o.log(fmt.Sprintf("Warning: failed to list zypper repositories: %s", strings.TrimSpace(string(output))))
- return fmt.Errorf("failed to list zypper repositories: %w", err)
- }
-
- aliases := parseZypperInstallMediaAliases(string(output))
- if len(aliases) == 0 {
- return nil
- }
-
- o.log(fmt.Sprintf("Disabling install media repositories: %s", strings.Join(aliases, ", ")))
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.055,
- Step: "Disabling install media repositories...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo zypper modifyrepo -d %s", strings.Join(aliases, " ")),
- LogOutput: fmt.Sprintf("Disabling install media repositories: %s", strings.Join(aliases, ", ")),
- }
-
- for _, alias := range aliases {
- cmd := ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("zypper modifyrepo -d '%s'", escapeSingleQuotes(alias)))
- repoOutput, err := cmd.CombinedOutput()
- if err != nil {
- o.log(fmt.Sprintf("Failed to disable install media repo %s: %s", alias, strings.TrimSpace(string(repoOutput))))
- return fmt.Errorf("failed to disable install media repo %s: %w", alias, err)
- }
- o.log(fmt.Sprintf("Disabled install media repo %s: %s", alias, strings.TrimSpace(string(repoOutput))))
- }
-
- return nil
-}
-
-func (o *OpenSUSEDistribution) zypperInstallArgs(packages []string, minimal bool) []string {
- args := []string{"zypper", "install", "-y"}
- if minimal {
- args = append(args, "--no-recommends")
- }
- return append(args, packages...)
-}
-
-func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
- if len(packages) == 0 {
- return nil
- }
-
- o.log(fmt.Sprintf("Installing zypper packages: %s", strings.Join(packages, ", ")))
-
- groups := orderedMinimalInstallGroups(packages)
- totalGroups := len(groups)
-
- groupIndex := 0
- installGroup := func(groupPackages []string, minimal bool) error {
- if len(groupPackages) == 0 {
- return nil
- }
-
- groupIndex++
- groupStart := startProgress
- groupEnd := endProgress
- if totalGroups > 1 {
- midpoint := startProgress + ((endProgress - startProgress) / 2)
- if groupIndex == 1 {
- groupEnd = midpoint
- } else {
- groupStart = midpoint
- }
- }
-
- args := o.zypperInstallArgs(groupPackages, minimal)
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: groupStart,
- Step: step,
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return o.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
- }
-
- for _, group := range groups {
- if err := installGroup(group.packages, group.minimal); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- o.log("Installing quickshell from source (with openSUSE-specific build flags)...")
-
- homeDir := os.Getenv("HOME")
- if homeDir == "" {
- return fmt.Errorf("HOME environment variable not set")
- }
-
- cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
- if err := os.MkdirAll(cacheDir, 0o755); err != nil {
- return fmt.Errorf("failed to create cache directory: %w", err)
- }
-
- tmpDir := filepath.Join(cacheDir, "quickshell-build")
- if err := os.MkdirAll(tmpDir, 0o755); err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer os.RemoveAll(tmpDir)
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.1,
- Step: "Cloning quickshell repository...",
- IsComplete: false,
- CommandInfo: "git clone https://github.com/quickshell-mirror/quickshell.git",
- }
-
- var cloneCmd *exec.Cmd
- if forceQuickshellGit || variant == deps.VariantGit {
- cloneCmd = exec.CommandContext(ctx, "git", "clone", "https://github.com/quickshell-mirror/quickshell.git", tmpDir)
- } else {
- latestTag := o.getLatestQuickshellTag(ctx)
- if latestTag != "" {
- o.log(fmt.Sprintf("Using latest quickshell tag: %s", latestTag))
- cloneCmd = exec.CommandContext(ctx, "git", "clone", "--branch", latestTag, "https://github.com/quickshell-mirror/quickshell.git", tmpDir)
- } else {
- o.log("Warning: failed to fetch latest tag, using default branch")
- cloneCmd = exec.CommandContext(ctx, "git", "clone", "https://github.com/quickshell-mirror/quickshell.git", tmpDir)
- }
- }
- if err := cloneCmd.Run(); err != nil {
- return fmt.Errorf("failed to clone quickshell: %w", err)
- }
-
- buildDir := tmpDir + "/build"
- if err := os.MkdirAll(buildDir, 0o755); err != nil {
- return fmt.Errorf("failed to create build directory: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.3,
- Step: "Configuring quickshell build (with openSUSE flags)...",
- IsComplete: false,
- CommandInfo: "cmake -B build -S . -G Ninja",
- }
-
- // Get optflags from rpm
- optflagsCmd := exec.CommandContext(ctx, "rpm", "--eval", "%{optflags}")
- optflagsOutput, err := optflagsCmd.Output()
- optflags := strings.TrimSpace(string(optflagsOutput))
- if err != nil || optflags == "" {
- o.log("Warning: Could not get optflags from rpm, using default -O2 -g")
- optflags = "-O2 -g"
- }
-
- // Set openSUSE-specific CFLAGS
- customCFLAGS := fmt.Sprintf("%s -I/usr/include/wayland", optflags)
-
- configureCmd := exec.CommandContext(ctx, "cmake", "-GNinja", "-B", "build",
- "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
- "-DCRASH_REPORTER=off",
- "-DCMAKE_CXX_STANDARD=20")
- configureCmd.Dir = tmpDir
- configureCmd.Env = append(os.Environ(),
- "TMPDIR="+cacheDir,
- "CFLAGS="+customCFLAGS,
- "CXXFLAGS="+customCFLAGS)
-
- o.log(fmt.Sprintf("Using CFLAGS: %s", customCFLAGS))
-
- output, err := configureCmd.CombinedOutput()
- if err != nil {
- o.log(fmt.Sprintf("cmake configure failed. Output:\n%s", string(output)))
- return fmt.Errorf("failed to configure quickshell: %w\nCMake output:\n%s", err, string(output))
- }
-
- o.log(fmt.Sprintf("cmake configure successful. Output:\n%s", string(output)))
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.4,
- Step: "Building quickshell (this may take a while)...",
- IsComplete: false,
- CommandInfo: "cmake --build build",
- }
-
- buildCmd := exec.CommandContext(ctx, "cmake", "--build", "build")
- buildCmd.Dir = tmpDir
- buildCmd.Env = append(os.Environ(),
- "TMPDIR="+cacheDir,
- "CFLAGS="+customCFLAGS,
- "CXXFLAGS="+customCFLAGS)
- if err := o.runWithProgressStep(buildCmd, progressChan, PhaseSystemPackages, 0.4, 0.8, "Building quickshell..."); err != nil {
- return fmt.Errorf("failed to build quickshell: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.8,
- Step: "Installing quickshell...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo cmake --install build",
- }
-
- installCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install build")
- installCmd.Dir = tmpDir
- if err := installCmd.Run(); err != nil {
- return fmt.Errorf("failed to install quickshell: %w", err)
- }
-
- o.log("quickshell installed successfully from source")
- return nil
-}
-
-func (o *OpenSUSEDistribution) installRust(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if o.commandExists("cargo") {
- return nil
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.82,
- Step: "Installing rustup...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo zypper install rustup",
- }
-
- rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "zypper install -y rustup")
- if err := o.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
- return fmt.Errorf("failed to install rustup: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.83,
- Step: "Installing stable Rust toolchain...",
- IsComplete: false,
- CommandInfo: "rustup install stable",
- }
-
- rustInstallCmd := exec.CommandContext(ctx, "bash", "-c", "rustup install stable && rustup default stable")
- if err := o.runWithProgress(rustInstallCmd, progressChan, PhaseSystemPackages, 0.83, 0.84); err != nil {
- return fmt.Errorf("failed to install Rust toolchain: %w", err)
- }
-
- if !o.commandExists("cargo") {
- o.log("Warning: cargo not found in PATH after Rust installation, trying to source environment")
- }
-
- return nil
-}
-
-func (o *OpenSUSEDistribution) InstallManualPackages(ctx context.Context, packages []string, variantMap map[string]deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- o.log(fmt.Sprintf("Installing manual packages: %s", strings.Join(packages, ", ")))
-
- for _, pkg := range packages {
- if pkg == "matugen" {
- if err := o.installRust(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install Rust: %w", err)
- }
- break
- }
- }
-
- for _, pkg := range packages {
- variant := variantMap[pkg]
- if pkg == "quickshell" {
- if err := o.installQuickshell(ctx, variant, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install quickshell: %w", err)
- }
- } else {
- if err := o.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install %s: %w", pkg, err)
- }
- }
- }
-
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/osinfo.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/osinfo.go
deleted file mode 100644
index 8fc4b25..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/osinfo.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package distros
-
-import (
- "bufio"
- "fmt"
- "os"
- "runtime"
- "strconv"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
-)
-
-// DistroInfo contains basic information about a distribution
-type DistroInfo struct {
- ID string
- HexColorCode string
-}
-
-// OSInfo contains complete OS information
-type OSInfo struct {
- Distribution DistroInfo
- Version string
- VersionID string
- VersionCodename string
- PrettyName string
- Architecture string
-}
-
-// GetOSInfo detects the current OS and returns information about it
-func GetOSInfo() (*OSInfo, error) {
- if runtime.GOOS != "linux" {
- return nil, errdefs.NewCustomError(errdefs.ErrTypeNotLinux, fmt.Sprintf("Only linux is supported, but I found %s", runtime.GOOS))
- }
-
- if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
- return nil, errdefs.NewCustomError(errdefs.ErrTypeInvalidArchitecture, fmt.Sprintf("Only amd64 and arm64 are supported, but I found %s", runtime.GOARCH))
- }
-
- info := &OSInfo{
- Architecture: runtime.GOARCH,
- }
-
- file, err := os.Open("/etc/os-release")
- if err != nil {
- return nil, err
- }
- defer file.Close()
-
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- line := scanner.Text()
- parts := strings.SplitN(line, "=", 2)
- if len(parts) != 2 {
- continue
- }
-
- key := parts[0]
- value := strings.Trim(parts[1], "\"")
-
- switch key {
- case "ID":
- config, exists := Registry[value]
- if !exists {
- return nil, errdefs.NewCustomError(errdefs.ErrTypeUnsupportedDistribution, fmt.Sprintf("Unsupported distribution: %s", value))
- }
-
- info.Distribution = DistroInfo{
- ID: value, // Use the actual ID from os-release
- HexColorCode: config.ColorHex,
- }
- case "VERSION_ID", "BUILD_ID":
- info.VersionID = value
- case "VERSION":
- info.Version = value
- case "VERSION_CODENAME":
- info.VersionCodename = value
- case "PRETTY_NAME":
- info.PrettyName = value
- }
- }
-
- return info, scanner.Err()
-}
-
-// IsUnsupportedDistro checks if a distribution/version combination is supported
-func IsUnsupportedDistro(distroID, versionID string) bool {
- if !IsDistroSupported(distroID) {
- return true
- }
-
- if distroID == "ubuntu" {
- parts := strings.Split(versionID, ".")
- if len(parts) >= 2 {
- major, err1 := strconv.Atoi(parts[0])
- minor, err2 := strconv.Atoi(parts[1])
-
- if err1 == nil && err2 == nil {
- return major < 25 || (major == 25 && minor < 4)
- }
- }
- return true
- }
-
- if distroID == "debian" {
- // unstable/sid support
- if versionID == "sid" {
- return false
- }
- if versionID == "" {
- // debian testing/sid have no version ID
- return false
- }
- versionNum, err := strconv.Atoi(versionID)
- if err == nil {
- return versionNum < 12
- }
- return true
- }
-
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/ubuntu.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/ubuntu.go
deleted file mode 100644
index 37911f0..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/ubuntu.go
+++ /dev/null
@@ -1,702 +0,0 @@
-package distros
-
-import (
- "context"
- "fmt"
- "os/exec"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
-)
-
-func init() {
- Register("ubuntu", "#E95420", FamilyUbuntu, func(config DistroConfig, logChan chan<- string) Distribution {
- return NewUbuntuDistribution(config, logChan)
- })
-}
-
-type UbuntuDistribution struct {
- *BaseDistribution
- *ManualPackageInstaller
- config DistroConfig
-}
-
-func NewUbuntuDistribution(config DistroConfig, logChan chan<- string) *UbuntuDistribution {
- base := NewBaseDistribution(logChan)
- return &UbuntuDistribution{
- BaseDistribution: base,
- ManualPackageInstaller: &ManualPackageInstaller{BaseDistribution: base},
- config: config,
- }
-}
-
-func (u *UbuntuDistribution) GetID() string {
- return u.config.ID
-}
-
-func (u *UbuntuDistribution) GetColorHex() string {
- return u.config.ColorHex
-}
-
-func (u *UbuntuDistribution) GetFamily() DistroFamily {
- return u.config.Family
-}
-
-func (u *UbuntuDistribution) GetPackageManager() PackageManagerType {
- return PackageManagerAPT
-}
-
-func (u *UbuntuDistribution) DetectDependencies(ctx context.Context, wm deps.WindowManager) ([]deps.Dependency, error) {
- return u.DetectDependenciesWithTerminal(ctx, wm, deps.TerminalGhostty)
-}
-
-func (u *UbuntuDistribution) DetectDependenciesWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]deps.Dependency, error) {
- var dependencies []deps.Dependency
-
- // DMS at the top (shell is prominent)
- dependencies = append(dependencies, u.detectDMS())
-
- // Terminal with choice support
- dependencies = append(dependencies, u.detectSpecificTerminal(terminal))
-
- // Common detections using base methods
- dependencies = append(dependencies, u.detectGit())
- dependencies = append(dependencies, u.detectWindowManager(wm))
- dependencies = append(dependencies, u.detectQuickshell())
- dependencies = append(dependencies, u.detectDMSGreeter())
- dependencies = append(dependencies, u.detectXDGPortal())
- dependencies = append(dependencies, u.detectAccountsService())
-
- // Hyprland-specific tools
- if wm == deps.WindowManagerHyprland {
- dependencies = append(dependencies, u.detectHyprlandTools()...)
- }
-
- // Niri-specific tools
- if wm == deps.WindowManagerNiri {
- dependencies = append(dependencies, u.detectXwaylandSatellite())
- }
-
- dependencies = append(dependencies, u.detectMatugen())
- dependencies = append(dependencies, u.detectDgop())
-
- return dependencies, nil
-}
-
-func (u *UbuntuDistribution) detectXDGPortal() deps.Dependency {
- return u.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", u.packageInstalled("xdg-desktop-portal-gtk"))
-}
-
-func (u *UbuntuDistribution) detectXwaylandSatellite() deps.Dependency {
- return u.detectCommand("xwayland-satellite", "Xwayland support")
-}
-
-func (u *UbuntuDistribution) detectAccountsService() deps.Dependency {
- return u.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", u.packageInstalled("accountsservice"))
-}
-
-func (u *UbuntuDistribution) detectDMSGreeter() deps.Dependency {
- return u.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", u.packageInstalled("dms-greeter"))
-}
-
-func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
- return debianPackageInstalledPrecisely(pkg)
-}
-
-func (u *UbuntuDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
- return u.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
-}
-
-func (u *UbuntuDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping {
- packages := map[string]PackageMapping{
- // Standard APT packages
- "git": {Name: "git", Repository: RepoTypeSystem},
- "kitty": {Name: "kitty", Repository: RepoTypeSystem},
- "alacritty": {Name: "alacritty", Repository: RepoTypeSystem},
- "xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
- "accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
-
- // DMS packages from PPAs
- "dms (DankMaterialShell)": u.getDmsMapping(variants["dms (DankMaterialShell)"]),
- "quickshell": u.getQuickshellMapping(variants["quickshell"]),
- "dms-greeter": {Name: "dms-greeter", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
- "matugen": {Name: "matugen", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
- "dgop": {Name: "dgop", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
- "ghostty": {Name: "ghostty", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
- }
-
- switch wm {
- case deps.WindowManagerHyprland:
- // Use the cppiber PPA for Hyprland
- packages["hyprland"] = PackageMapping{Name: "hyprland", Repository: RepoTypePPA, RepoURL: "ppa:cppiber/hyprland"}
- packages["hyprctl"] = PackageMapping{Name: "hyprland", Repository: RepoTypePPA, RepoURL: "ppa:cppiber/hyprland"}
- packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
- case deps.WindowManagerNiri:
- niriVariant := variants["niri"]
- packages["niri"] = u.getNiriMapping(niriVariant)
- packages["xwayland-satellite"] = u.getXwaylandSatelliteMapping(niriVariant)
- }
-
- return packages
-}
-
-func (u *UbuntuDistribution) getDmsMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "dms-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/dms-git"}
- }
- return PackageMapping{Name: "dms", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/dms"}
-}
-
-func (u *UbuntuDistribution) getQuickshellMapping(variant deps.PackageVariant) PackageMapping {
- if forceQuickshellGit || variant == deps.VariantGit {
- return PackageMapping{Name: "quickshell-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
- }
- return PackageMapping{Name: "quickshell", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
-}
-
-func (u *UbuntuDistribution) getNiriMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "niri-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
- }
- return PackageMapping{Name: "niri", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
-}
-
-func (u *UbuntuDistribution) getXwaylandSatelliteMapping(variant deps.PackageVariant) PackageMapping {
- if variant == deps.VariantGit {
- return PackageMapping{Name: "xwayland-satellite-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
- }
- return PackageMapping{Name: "xwayland-satellite", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
-}
-
-func (u *UbuntuDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.06,
- Step: "Updating package lists...",
- IsComplete: false,
- LogOutput: "Updating APT package lists",
- }
-
- updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
- if err := u.runWithProgress(updateCmd, progressChan, PhasePrerequisites, 0.06, 0.07); err != nil {
- return fmt.Errorf("failed to update package lists: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.08,
- Step: "Installing build-essential...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get install -y build-essential",
- LogOutput: "Installing build tools",
- }
-
- checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
- if err := checkCmd.Run(); err != nil {
- // Not installed, install it
- cmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y build-essential")
- if err := u.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
- return fmt.Errorf("failed to install build-essential: %w", err)
- }
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.10,
- Step: "Installing development dependencies...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get install -y curl wget git cmake ninja-build pkg-config libglib2.0-dev libpolkit-agent-1-dev",
- LogOutput: "Installing additional development tools",
- }
-
- devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
- "apt-get install -y curl wget git cmake ninja-build pkg-config libglib2.0-dev libpolkit-agent-1-dev")
- if err := u.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
- return fmt.Errorf("failed to install development tools: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.12,
- Step: "Prerequisites installation complete",
- IsComplete: false,
- LogOutput: "Prerequisites successfully installed",
- }
-
- return nil
-}
-
-func (u *UbuntuDistribution) InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, disabledFlags map[string]bool, skipGlobalUseFlags bool, progressChan chan<- InstallProgressMsg) error {
- // Phase 1: Check Prerequisites
- progressChan <- InstallProgressMsg{
- Phase: PhasePrerequisites,
- Progress: 0.05,
- Step: "Checking system prerequisites...",
- IsComplete: false,
- LogOutput: "Starting prerequisite check...",
- }
-
- if err := u.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install prerequisites: %w", err)
- }
-
- systemPkgs, ppaPkgs, manualPkgs, variantMap := u.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
-
- // Phase 2: Enable PPA repositories
- if len(ppaPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.15,
- Step: "Enabling PPA repositories...",
- IsComplete: false,
- LogOutput: "Setting up PPA repositories for additional packages",
- }
- if err := u.enablePPARepos(ctx, ppaPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to enable PPA repositories: %w", err)
- }
- }
-
- // Phase 3: System Packages (APT)
- if len(systemPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.35,
- Step: fmt.Sprintf("Installing %d system packages...", len(systemPkgs)),
- IsComplete: false,
- NeedsSudo: true,
- LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(systemPkgs, ", ")),
- }
- if err := u.installAPTPackages(ctx, systemPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install APT packages: %w", err)
- }
- }
-
- // Phase 4: PPA Packages
- ppaPkgNames := u.extractPackageNames(ppaPkgs)
- if len(ppaPkgNames) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseAURPackages, // Reusing AUR phase for PPA
- Progress: 0.65,
- Step: fmt.Sprintf("Installing %d PPA packages...", len(ppaPkgNames)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Installing PPA packages: %s", strings.Join(ppaPkgNames, ", ")),
- }
- if err := u.installPPAPackages(ctx, ppaPkgNames, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install PPA packages: %w", err)
- }
- }
-
- // Phase 5: Manual Builds
- if len(manualPkgs) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.80,
- Step: "Installing build dependencies...",
- IsComplete: false,
- LogOutput: "Installing build tools for manual compilation",
- }
- if err := u.installBuildDependencies(ctx, manualPkgs, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install build dependencies: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.85,
- Step: fmt.Sprintf("Building %d packages from source...", len(manualPkgs)),
- IsComplete: false,
- LogOutput: fmt.Sprintf("Building from source: %s", strings.Join(manualPkgs, ", ")),
- }
- if err := u.InstallManualPackages(ctx, manualPkgs, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install manual packages: %w", err)
- }
- }
-
- // Phase 6: Configuration
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.90,
- Step: "Configuring system...",
- IsComplete: false,
- LogOutput: "Starting post-installation configuration...",
- }
-
- terminal := u.DetectTerminalFromDeps(dependencies)
- if err := u.WriteEnvironmentConfig(terminal); err != nil {
- u.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
- }
-
- if err := u.WriteWindowManagerConfig(wm); err != nil {
- u.log(fmt.Sprintf("Warning: failed to write window manager config: %v", err))
- }
-
- if err := u.EnableDMSService(ctx, wm); err != nil {
- u.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
- }
-
- // Phase 7: Complete
- progressChan <- InstallProgressMsg{
- Phase: PhaseComplete,
- Progress: 1.0,
- Step: "Installation complete!",
- IsComplete: true,
- LogOutput: "All packages installed and configured successfully",
- }
-
- return nil
-}
-
-func (u *UbuntuDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string, map[string]deps.PackageVariant) {
- systemPkgs := []string{}
- ppaPkgs := []PackageMapping{}
- manualPkgs := []string{}
-
- variantMap := make(map[string]deps.PackageVariant)
- for _, dep := range dependencies {
- variantMap[dep.Name] = dep.Variant
- }
-
- packageMap := u.GetPackageMappingWithVariants(wm, variantMap)
-
- for _, dep := range dependencies {
- if disabledFlags[dep.Name] {
- continue
- }
-
- if dep.Status == deps.StatusInstalled && !reinstallFlags[dep.Name] {
- continue
- }
-
- pkgInfo, exists := packageMap[dep.Name]
- if !exists {
- u.log(fmt.Sprintf("Warning: No package mapping for %s", dep.Name))
- continue
- }
-
- switch pkgInfo.Repository {
- case RepoTypeSystem:
- systemPkgs = append(systemPkgs, pkgInfo.Name)
- case RepoTypePPA:
- ppaPkgs = append(ppaPkgs, pkgInfo)
- case RepoTypeManual:
- manualPkgs = append(manualPkgs, dep.Name)
- }
- }
-
- return systemPkgs, ppaPkgs, manualPkgs, variantMap
-}
-
-func (u *UbuntuDistribution) extractPackageNames(packages []PackageMapping) []string {
- names := make([]string, len(packages))
- for i, pkg := range packages {
- names[i] = pkg.Name
- }
- return names
-}
-
-func (u *UbuntuDistribution) enablePPARepos(ctx context.Context, ppaPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- enabledRepos := make(map[string]bool)
-
- installPPACmd := ExecSudoCommand(ctx, sudoPassword,
- "apt-get install -y software-properties-common")
- if err := u.runWithProgress(installPPACmd, progressChan, PhaseSystemPackages, 0.15, 0.17); err != nil {
- return fmt.Errorf("failed to install software-properties-common: %w", err)
- }
-
- for _, pkg := range ppaPkgs {
- if pkg.RepoURL != "" && !enabledRepos[pkg.RepoURL] {
- u.log(fmt.Sprintf("Enabling PPA repository: %s", pkg.RepoURL))
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.20,
- Step: fmt.Sprintf("Enabling PPA repo %s...", pkg.RepoURL),
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo add-apt-repository -y %s", pkg.RepoURL),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("add-apt-repository -y %s", pkg.RepoURL))
- if err := u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
- u.logError(fmt.Sprintf("failed to enable PPA repo %s", pkg.RepoURL), err)
- return fmt.Errorf("failed to enable PPA repo %s: %w", pkg.RepoURL, err)
- }
- u.log(fmt.Sprintf("PPA repo %s enabled successfully", pkg.RepoURL))
- enabledRepos[pkg.RepoURL] = true
- }
- }
-
- if len(enabledRepos) > 0 {
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.25,
- Step: "Updating package lists...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get update",
- }
-
- updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
- if err := u.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
- return fmt.Errorf("failed to update package lists after adding PPAs: %w", err)
- }
- }
-
- return nil
-}
-
-func (u *UbuntuDistribution) installAPTPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- u.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
- return u.installAPTGroups(ctx, packages, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60)
-}
-
-func (u *UbuntuDistribution) installPPAPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- u.log(fmt.Sprintf("Installing PPA packages: %s", strings.Join(packages, ", ")))
- return u.installAPTGroups(ctx, packages, sudoPassword, progressChan, PhaseAURPackages, "Installing PPA packages...", 0.70, 0.85)
-}
-
-func (u *UbuntuDistribution) aptInstallArgs(packages []string, minimal bool) []string {
- args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
- if minimal {
- args = append(args, "--no-install-recommends")
- }
- return append(args, packages...)
-}
-
-func (u *UbuntuDistribution) installAPTGroups(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
- groups := orderedMinimalInstallGroups(packages)
- totalGroups := len(groups)
-
- groupIndex := 0
- installGroup := func(groupPackages []string, minimal bool) error {
- if len(groupPackages) == 0 {
- return nil
- }
-
- groupIndex++
- groupStart := startProgress
- groupEnd := endProgress
- if totalGroups > 1 {
- midpoint := startProgress + ((endProgress - startProgress) / 2)
- if groupIndex == 1 {
- groupEnd = midpoint
- } else {
- groupStart = midpoint
- }
- }
-
- args := u.aptInstallArgs(groupPackages, minimal)
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: groupStart,
- Step: step,
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
- }
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return u.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
- }
-
- for _, group := range groups {
- if err := installGroup(group.packages, group.minimal); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (u *UbuntuDistribution) installBuildDependencies(ctx context.Context, manualPkgs []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- buildDeps := make(map[string]bool)
-
- for _, pkg := range manualPkgs {
- switch pkg {
- case "niri":
- buildDeps["curl"] = true
- buildDeps["libxkbcommon-dev"] = true
- buildDeps["libwayland-dev"] = true
- buildDeps["libudev-dev"] = true
- buildDeps["libinput-dev"] = true
- buildDeps["libdisplay-info-dev"] = true
- buildDeps["libpango1.0-dev"] = true
- buildDeps["libcairo-dev"] = true
- buildDeps["libpipewire-0.3-dev"] = true
- buildDeps["libc6-dev"] = true
- buildDeps["clang"] = true
- buildDeps["libseat-dev"] = true
- buildDeps["libgbm-dev"] = true
- buildDeps["alacritty"] = true
- buildDeps["fuzzel"] = true
- buildDeps["libxcb-cursor-dev"] = true
- case "quickshell":
- buildDeps["qt6-base-dev"] = true
- buildDeps["qt6-base-private-dev"] = true
- buildDeps["qt6-declarative-dev"] = true
- buildDeps["qt6-declarative-private-dev"] = true
- buildDeps["qt6-wayland-dev"] = true
- buildDeps["qt6-wayland-private-dev"] = true
- buildDeps["qt6-tools-dev"] = true
- buildDeps["libqt6svg6-dev"] = true
- buildDeps["qt6-shadertools-dev"] = true
- buildDeps["spirv-tools"] = true
- buildDeps["libcli11-dev"] = true
- buildDeps["libjemalloc-dev"] = true
- buildDeps["libwayland-dev"] = true
- buildDeps["wayland-protocols"] = true
- buildDeps["libdrm-dev"] = true
- buildDeps["libgbm-dev"] = true
- buildDeps["libegl-dev"] = true
- buildDeps["libgles2-mesa-dev"] = true
- buildDeps["libgl1-mesa-dev"] = true
- buildDeps["libxcb1-dev"] = true
- buildDeps["libpipewire-0.3-dev"] = true
- buildDeps["libpam0g-dev"] = true
- case "matugen":
- buildDeps["curl"] = true
- }
- }
-
- for _, pkg := range manualPkgs {
- switch pkg {
- case "niri", "matugen":
- if err := u.installRust(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install Rust: %w", err)
- }
- case "dgop":
- if err := u.installGo(ctx, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install Go: %w", err)
- }
- }
- }
-
- if len(buildDeps) == 0 {
- return nil
- }
-
- depList := make([]string, 0, len(buildDeps))
- for dep := range buildDeps {
- depList = append(depList, dep)
- }
-
- args := []string{"apt-get", "install", "-y"}
- args = append(args, depList...)
-
- cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
- return u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.80, 0.82)
-}
-
-func (u *UbuntuDistribution) installRust(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if u.commandExists("cargo") {
- return nil
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.82,
- Step: "Installing rustup...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get install rustup",
- }
-
- rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y rustup")
- if err := u.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
- return fmt.Errorf("failed to install rustup: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.83,
- Step: "Installing stable Rust toolchain...",
- IsComplete: false,
- CommandInfo: "rustup install stable",
- }
-
- rustInstallCmd := exec.CommandContext(ctx, "bash", "-c", "rustup install stable && rustup default stable")
- if err := u.runWithProgress(rustInstallCmd, progressChan, PhaseSystemPackages, 0.83, 0.84); err != nil {
- return fmt.Errorf("failed to install Rust toolchain: %w", err)
- }
-
- // Verify cargo is now available
- if !u.commandExists("cargo") {
- u.log("Warning: cargo not found in PATH after Rust installation, trying to source environment")
- }
-
- return nil
-}
-
-func (u *UbuntuDistribution) installGo(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if u.commandExists("go") {
- return nil
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.87,
- Step: "Adding Go PPA repository...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo add-apt-repository ppa:longsleep/golang-backports",
- }
-
- addPPACmd := ExecSudoCommand(ctx, sudoPassword,
- "add-apt-repository -y ppa:longsleep/golang-backports")
- if err := u.runWithProgress(addPPACmd, progressChan, PhaseSystemPackages, 0.87, 0.88); err != nil {
- return fmt.Errorf("failed to add Go PPA: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.88,
- Step: "Updating package lists...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get update",
- }
-
- updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
- if err := u.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.88, 0.89); err != nil {
- return fmt.Errorf("failed to update package lists after adding Go PPA: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseSystemPackages,
- Progress: 0.89,
- Step: "Installing Go...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo apt-get install golang-go",
- }
-
- installCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y golang-go")
- return u.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.89, 0.90)
-}
-
-func (u *UbuntuDistribution) InstallManualPackages(ctx context.Context, packages []string, variantMap map[string]deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- if len(packages) == 0 {
- return nil
- }
-
- u.log(fmt.Sprintf("Installing manual packages: %s", strings.Join(packages, ", ")))
-
- for _, pkg := range packages {
- switch pkg {
- default:
- if err := u.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, variantMap, sudoPassword, progressChan); err != nil {
- return fmt.Errorf("failed to install %s: %w", pkg, err)
- }
- }
- }
-
- return nil
-}