7.3 KiB
Adding New Linux Distributions
This guide explains how to add support for new Linux distributions to the dankdots installer using the new consolidated architecture.
Architecture Overview
The codebase uses a simple, consolidated approach where each distribution is completely self-contained:
- All-in-One (
internal/distros/{distro}.go) - Complete distribution implementation - Auto-Registration - Distributions register themselves via
init()functions - Shared Base - Common functionality inherited from
BaseDistribution
Adding Support
Method 1: Use Existing Implementation (Derivatives)
For distros that are derivatives (like CachyOS being Arch-based), you can register them to use an existing implementation.
Example: Adding CachyOS (Arch-based)
// internal/distros/arch.go - add to the init function
func init() {
Register("arch", "#1793D1", func(config DistroConfig, logChan chan<- string) Distribution {
return NewArchDistribution(config, logChan)
})
Register("cachyos", "#318CE7", func(config DistroConfig, logChan chan<- string) Distribution {
return NewArchDistribution(config, logChan) // CachyOS uses Arch implementation but different color
})
}
That's it! CachyOS now uses Arch's detection and installation logic.
Example: Adding Ubuntu derivatives
// internal/distros/ubuntu.go (after you create it)
func init() {
Register("ubuntu", "#E95420", func(config DistroConfig, logChan chan<- string) Distribution {
return NewUbuntuDistribution(config, logChan)
})
Register("kubuntu", "#0079C1", func(config DistroConfig, logChan chan<- string) Distribution {
return NewUbuntuDistribution(config, logChan) // Kubuntu uses Ubuntu implementation but different color
})
Register("xubuntu", "#2F5BEA", func(config DistroConfig, logChan chan<- string) Distribution {
return NewUbuntuDistribution(config, logChan) // Xubuntu uses Ubuntu implementation but different color
})
Register("pop", "#48B9C7", func(config DistroConfig, logChan chan<- string) Distribution {
return NewUbuntuDistribution(config, logChan) // Pop!_OS uses Ubuntu implementation but different color
})
}
Method 2: Create New Implementation
For entirely new distribution families, create a complete implementation:
Example: Adding openSUSE
Create internal/distros/opensuse.go:
package distros
import (
"context"
"os/exec"
"strings"
"github.com/AvengeMedia/danklinux/internal/deps"
"github.com/AvengeMedia/danklinux/internal/installer"
)
func init() {
Register("opensuse-leap", "#73BA25", func(config DistroConfig, logChan chan<- string) Distribution {
return NewOpenSUSEDistribution(config, logChan)
})
Register("opensuse-tumbleweed", "#73BA25", func(config DistroConfig, logChan chan<- string) Distribution {
return NewOpenSUSEDistribution(config, logChan)
})
}
type OpenSUSEDistribution struct {
*BaseDistribution
*ManualPackageInstaller
config DistroConfig
}
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) GetPackageManager() PackageManagerType {
return PackageManagerZypper
}
func (o *OpenSUSEDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
return map[string]PackageMapping{
"git": {Name: "git", Repository: RepoTypeSystem},
"ghostty": {Name: "ghostty", Repository: RepoTypeManual}, // Build from source
"kitty": {Name: "kitty", Repository: RepoTypeSystem},
// ... map all required packages to openSUSE equivalents
}
}
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
// Use base methods for common functionality
dependencies = append(dependencies, o.detectDMS())
dependencies = append(dependencies, o.detectSpecificTerminal(terminal))
dependencies = append(dependencies, o.detectGit())
// ... add openSUSE-specific detection
return dependencies, nil
}
func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, progressChan chan<- installer.InstallProgressMsg) error {
// Implement installation logic using zypper
// Use o.InstallManualPackages() for source builds
return nil
}
func (o *OpenSUSEDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- installer.InstallProgressMsg) error {
// Install build tools, enable repositories, etc.
return nil
}
func (o *OpenSUSEDistribution) packageInstalled(pkg string) bool {
cmd := exec.Command("rpm", "-q", pkg)
err := cmd.Run()
return err == nil
}
Repository Types
The system supports these repository types:
RepoTypeSystem- Main system repository (zypper, apt, dnf, pacman)RepoTypeAUR- Arch User RepositoryRepoTypeCOPR- Fedora COPRRepoTypePPA- Ubuntu PPARepoTypeManual- Build from source
Package Manager Support
To add a new package manager, add it to internal/distros/interface.go:
const (
PackageManagerPacman PackageManagerType = "pacman"
PackageManagerDNF PackageManagerType = "dnf"
PackageManagerAPT PackageManagerType = "apt"
PackageManagerZypper PackageManagerType = "zypper"
PackageManagerPortage PackageManagerType = "portage" // Add new ones here
)
Testing Your Implementation
- Build:
go build -o dankdots ./cmd/main.go - Test on target distribution
- Verify all packages detect and install correctly
- Test both window managers (Hyprland, Niri)
- Test both terminals (Ghostty, Kitty)
Detection Process
The system automatically detects supported distributions by:
- Reading
/etc/os-releasefor theIDfield - Looking up the ID in the distribution registry
- Creating an instance using the registered constructor function
No hardcoded lists to maintain - everything is driven by the registry!
Benefits of New Architecture
- ✅ Single file per distro - All logic in one place
- ✅ Auto-registration - No factory methods to update
- ✅ Shared functionality - Inherit common features
- ✅ No duplication - Manual builds and fonts are shared
- ✅ Easy derivatives - One line to support a new derivative
Contributing
- Fork the repository
- Create your distribution file in
internal/distros/ - Test thoroughly on your target distribution
- Submit a pull request with example output
The maintainers will review and provide feedback. Thank you for expanding dankdots support!