summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go
diff options
context:
space:
mode:
authorAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
committerAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
commit70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch)
treeab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go
parent5d94c0a7d44a2255b81815a52a7056a94a39842d (diff)
downloadRaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz
RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go700
1 files changed, 700 insertions, 0 deletions
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
new file mode 100644
index 0000000..e0a2a9c
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/gentoo.go
@@ -0,0 +1,700 @@
+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)
+}