diff options
| author | Nippy <nippy@rp1.hu> | 2026-05-09 13:33:09 +0200 |
|---|---|---|
| committer | Nippy <nippy@rp1.hu> | 2026-05-09 13:33:09 +0200 |
| commit | a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 (patch) | |
| tree | 1a8769217f84bfe9d6216fafaadaf8cdd876d457 /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros | |
| parent | 34b9c34f982b2596cfa9e368a2d7f74e9a17477c (diff) | |
| download | RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.tar.gz RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.zip | |
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros')
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 -} |