mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
756 lines
26 KiB
Go
756 lines
26 KiB
Go
package distros
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/backend/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.detectXDGPortal())
|
|
dependencies = append(dependencies, u.detectPolkitAgent())
|
|
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())
|
|
}
|
|
|
|
// Base detections (common across distros)
|
|
dependencies = append(dependencies, u.detectMatugen())
|
|
dependencies = append(dependencies, u.detectDgop())
|
|
dependencies = append(dependencies, u.detectHyprpicker())
|
|
dependencies = append(dependencies, u.detectClipboardTools()...)
|
|
|
|
return dependencies, nil
|
|
}
|
|
|
|
func (u *UbuntuDistribution) detectXDGPortal() deps.Dependency {
|
|
status := deps.StatusMissing
|
|
if u.packageInstalled("xdg-desktop-portal-gtk") {
|
|
status = deps.StatusInstalled
|
|
}
|
|
|
|
return deps.Dependency{
|
|
Name: "xdg-desktop-portal-gtk",
|
|
Status: status,
|
|
Description: "Desktop integration portal for GTK",
|
|
Required: true,
|
|
}
|
|
}
|
|
|
|
func (u *UbuntuDistribution) detectPolkitAgent() deps.Dependency {
|
|
status := deps.StatusMissing
|
|
if u.packageInstalled("mate-polkit") {
|
|
status = deps.StatusInstalled
|
|
}
|
|
|
|
return deps.Dependency{
|
|
Name: "mate-polkit",
|
|
Status: status,
|
|
Description: "PolicyKit authentication agent",
|
|
Required: true,
|
|
}
|
|
}
|
|
|
|
func (u *UbuntuDistribution) detectXwaylandSatellite() deps.Dependency {
|
|
status := deps.StatusMissing
|
|
if u.commandExists("xwayland-satellite") {
|
|
status = deps.StatusInstalled
|
|
}
|
|
|
|
return deps.Dependency{
|
|
Name: "xwayland-satellite",
|
|
Status: status,
|
|
Description: "Xwayland support",
|
|
Required: true,
|
|
}
|
|
}
|
|
|
|
func (u *UbuntuDistribution) detectAccountsService() deps.Dependency {
|
|
status := deps.StatusMissing
|
|
if u.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 (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
|
cmd := exec.Command("dpkg", "-l", pkg)
|
|
err := cmd.Run()
|
|
return err == nil
|
|
}
|
|
|
|
func (u *UbuntuDistribution) GetPackageMapping(wm deps.WindowManager) 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},
|
|
"wl-clipboard": {Name: "wl-clipboard", Repository: RepoTypeSystem},
|
|
"xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
|
|
"mate-polkit": {Name: "mate-polkit", Repository: RepoTypeSystem},
|
|
"accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
|
|
"hyprpicker": {Name: "hyprpicker", Repository: RepoTypePPA, RepoURL: "ppa:cppiber/hyprland"},
|
|
|
|
// Manual builds (niri and quickshell likely not available in Ubuntu repos or PPAs)
|
|
"dms (DankMaterialShell)": {Name: "dms", Repository: RepoTypeManual, BuildFunc: "installDankMaterialShell"},
|
|
"niri": {Name: "niri", Repository: RepoTypeManual, BuildFunc: "installNiri"},
|
|
"quickshell": {Name: "quickshell", Repository: RepoTypeManual, BuildFunc: "installQuickshell"},
|
|
"ghostty": {Name: "ghostty", Repository: RepoTypeManual, BuildFunc: "installGhostty"},
|
|
"matugen": {Name: "matugen", Repository: RepoTypeManual, BuildFunc: "installMatugen"},
|
|
"dgop": {Name: "dgop", Repository: RepoTypeManual, BuildFunc: "installDgop"},
|
|
"cliphist": {Name: "cliphist", Repository: RepoTypeManual, BuildFunc: "installCliphist"},
|
|
}
|
|
|
|
switch wm {
|
|
case deps.WindowManagerHyprland:
|
|
// Use the cppiber PPA for Hyprland
|
|
packages["hyprland"] = PackageMapping{Name: "hyprland", Repository: RepoTypePPA, RepoURL: "ppa:cppiber/hyprland"}
|
|
packages["grim"] = PackageMapping{Name: "grim", Repository: RepoTypeSystem}
|
|
packages["slurp"] = PackageMapping{Name: "slurp", Repository: RepoTypeSystem}
|
|
packages["hyprctl"] = PackageMapping{Name: "hyprland", Repository: RepoTypePPA, RepoURL: "ppa:cppiber/hyprland"}
|
|
packages["grimblast"] = PackageMapping{Name: "grimblast", Repository: RepoTypeManual, BuildFunc: "installGrimblast"}
|
|
packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
|
|
case deps.WindowManagerNiri:
|
|
packages["niri"] = PackageMapping{Name: "niri", Repository: RepoTypeManual, BuildFunc: "installNiri"}
|
|
packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeManual, BuildFunc: "installXwaylandSatellite"}
|
|
}
|
|
|
|
return packages
|
|
}
|
|
|
|
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 := 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, 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...",
|
|
}
|
|
|
|
// 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) {
|
|
systemPkgs := []string{}
|
|
ppaPkgs := []PackageMapping{}
|
|
manualPkgs := []string{}
|
|
|
|
packageMap := u.GetPackageMapping(wm)
|
|
|
|
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
|
|
}
|
|
|
|
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, ", ")))
|
|
|
|
args := []string{"apt-get", "install", "-y"}
|
|
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 u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 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, ", ")))
|
|
|
|
args := []string{"apt-get", "install", "-y"}
|
|
args = append(args, packages...)
|
|
|
|
progressChan <- InstallProgressMsg{
|
|
Phase: PhaseAURPackages,
|
|
Progress: 0.70,
|
|
Step: "Installing PPA packages...",
|
|
IsComplete: false,
|
|
NeedsSudo: true,
|
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
|
}
|
|
|
|
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
|
return u.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.70, 0.85)
|
|
}
|
|
|
|
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 "ghostty":
|
|
buildDeps["curl"] = true
|
|
buildDeps["libgtk-4-dev"] = true
|
|
buildDeps["libadwaita-1-dev"] = true
|
|
case "matugen":
|
|
buildDeps["curl"] = true
|
|
case "cliphist":
|
|
// Go will be installed separately with PPA
|
|
}
|
|
}
|
|
|
|
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 "ghostty":
|
|
if err := u.installZig(ctx, sudoPassword, progressChan); err != nil {
|
|
return fmt.Errorf("failed to install Zig: %w", err)
|
|
}
|
|
case "cliphist", "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) installZig(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
|
if u.commandExists("zig") {
|
|
return nil
|
|
}
|
|
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get user home directory: %w", err)
|
|
}
|
|
|
|
cacheDir := filepath.Join(homeDir, ".cache", "dankinstall")
|
|
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create cache directory: %w", err)
|
|
}
|
|
|
|
zigUrl := "https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz"
|
|
zigTmp := filepath.Join(cacheDir, "zig.tar.xz")
|
|
|
|
downloadCmd := exec.CommandContext(ctx, "curl", "-L", zigUrl, "-o", zigTmp)
|
|
if err := u.runWithProgress(downloadCmd, progressChan, PhaseSystemPackages, 0.84, 0.85); err != nil {
|
|
return fmt.Errorf("failed to download Zig: %w", err)
|
|
}
|
|
|
|
extractCmd := ExecSudoCommand(ctx, sudoPassword,
|
|
fmt.Sprintf("tar -xf %s -C /opt/", zigTmp))
|
|
if err := u.runWithProgress(extractCmd, progressChan, PhaseSystemPackages, 0.85, 0.86); err != nil {
|
|
return fmt.Errorf("failed to extract Zig: %w", err)
|
|
}
|
|
|
|
linkCmd := ExecSudoCommand(ctx, sudoPassword,
|
|
"ln -sf /opt/zig-linux-x86_64-0.11.0/zig /usr/local/bin/zig")
|
|
return u.runWithProgress(linkCmd, progressChan, PhaseSystemPackages, 0.86, 0.87)
|
|
}
|
|
|
|
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) installGhosttyUbuntu(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
|
u.log("Installing Ghostty using Ubuntu installer script...")
|
|
|
|
progressChan <- InstallProgressMsg{
|
|
Phase: PhaseSystemPackages,
|
|
Progress: 0.1,
|
|
Step: "Running Ghostty Ubuntu installer...",
|
|
IsComplete: false,
|
|
NeedsSudo: true,
|
|
CommandInfo: "curl -fsSL https://raw.githubusercontent.com/mkasberg/ghostty-ubuntu/HEAD/install.sh | sudo bash",
|
|
LogOutput: "Installing Ghostty using pre-built Ubuntu package",
|
|
}
|
|
|
|
installCmd := ExecSudoCommand(ctx, sudoPassword,
|
|
"/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/mkasberg/ghostty-ubuntu/HEAD/install.sh)\"")
|
|
|
|
if err := u.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.1, 0.9); err != nil {
|
|
return fmt.Errorf("failed to install Ghostty: %w", err)
|
|
}
|
|
|
|
u.log("Ghostty installed successfully using Ubuntu installer")
|
|
return nil
|
|
}
|
|
|
|
// Override InstallManualPackages for Ubuntu to handle Ubuntu-specific installations
|
|
func (u *UbuntuDistribution) InstallManualPackages(ctx context.Context, packages []string, 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 {
|
|
case "ghostty":
|
|
if err := u.installGhosttyUbuntu(ctx, sudoPassword, progressChan); err != nil {
|
|
return fmt.Errorf("failed to install ghostty: %w", err)
|
|
}
|
|
default:
|
|
// Use the base ManualPackageInstaller for other packages
|
|
if err := u.ManualPackageInstaller.InstallManualPackages(ctx, []string{pkg}, sudoPassword, progressChan); err != nil {
|
|
return fmt.Errorf("failed to install %s: %w", pkg, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|