1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

switch hto monorepo structure

This commit is contained in:
bbedward
2025-11-12 17:18:45 -05:00
parent 6013c994a6
commit 24e800501a
768 changed files with 76284 additions and 221 deletions

View File

@@ -0,0 +1,488 @@
//go:build !distro_binary
package main
import (
"bufio"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/distros"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/errdefs"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/log"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/version"
"github.com/spf13/cobra"
)
var updateCmd = &cobra.Command{
Use: "update",
Short: "Update DankMaterialShell to the latest version",
Long: "Update DankMaterialShell to the latest version using the appropriate package manager for your distribution",
PreRunE: findConfig,
Run: func(cmd *cobra.Command, args []string) {
runUpdate()
},
}
var updateCheckCmd = &cobra.Command{
Use: "check",
Short: "Check if updates are available for DankMaterialShell",
Long: "Check for available updates without performing the actual update",
Run: func(cmd *cobra.Command, args []string) {
runUpdateCheck()
},
}
func runUpdateCheck() {
fmt.Println("Checking for DankMaterialShell updates...")
fmt.Println()
versionInfo, err := version.GetDMSVersionInfo()
if err != nil {
log.Fatalf("Error checking for updates: %v", err)
}
fmt.Printf("Current version: %s\n", versionInfo.Current)
fmt.Printf("Latest version: %s\n", versionInfo.Latest)
fmt.Println()
if versionInfo.HasUpdate {
fmt.Println("✓ Update available!")
fmt.Println()
fmt.Println("Run 'dms update' to install the latest version.")
os.Exit(0)
} else {
fmt.Println("✓ You are running the latest version.")
os.Exit(0)
}
}
func runUpdate() {
osInfo, err := distros.GetOSInfo()
if err != nil {
log.Fatalf("Error detecting OS: %v", err)
}
config, exists := distros.Registry[osInfo.Distribution.ID]
if !exists {
log.Fatalf("Unsupported distribution: %s", osInfo.Distribution.ID)
}
var updateErr error
switch config.Family {
case distros.FamilyArch:
updateErr = updateArchLinux()
case distros.FamilyNix:
updateErr = updateNixOS()
case distros.FamilySUSE:
updateErr = updateOtherDistros()
default:
updateErr = updateOtherDistros()
}
if updateErr != nil {
if errors.Is(updateErr, errdefs.ErrUpdateCancelled) {
log.Info("Update cancelled.")
return
}
if errors.Is(updateErr, errdefs.ErrNoUpdateNeeded) {
return
}
log.Fatalf("Error updating DMS: %v", updateErr)
}
log.Info("Update complete! Restarting DMS...")
restartShell()
}
func updateArchLinux() error {
homeDir, err := os.UserHomeDir()
if err == nil {
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); err == nil {
return updateOtherDistros()
}
}
var packageName string
if isArchPackageInstalled("dms-shell-bin") {
packageName = "dms-shell-bin"
} else if isArchPackageInstalled("dms-shell-git") {
packageName = "dms-shell-git"
} else {
fmt.Println("Info: Neither dms-shell-bin nor dms-shell-git package found.")
fmt.Println("Info: Falling back to git-based update method...")
return updateOtherDistros()
}
var helper string
var updateCmd *exec.Cmd
if commandExists("yay") {
helper = "yay"
updateCmd = exec.Command("yay", "-S", packageName)
} else if commandExists("paru") {
helper = "paru"
updateCmd = exec.Command("paru", "-S", packageName)
} else {
fmt.Println("Error: Neither yay nor paru found - please install an AUR helper")
fmt.Println("Info: Falling back to git-based update method...")
return updateOtherDistros()
}
fmt.Printf("This will update DankMaterialShell using %s.\n", helper)
if !confirmUpdate() {
return errdefs.ErrUpdateCancelled
}
fmt.Printf("\nRunning: %s -S %s\n", helper, packageName)
updateCmd.Stdout = os.Stdout
updateCmd.Stderr = os.Stderr
err = updateCmd.Run()
if err != nil {
fmt.Printf("Error: Failed to update using %s: %v\n", helper, err)
}
fmt.Println("dms successfully updated")
return nil
}
func updateNixOS() error {
fmt.Println("This will update DankMaterialShell using nix profile.")
if !confirmUpdate() {
return errdefs.ErrUpdateCancelled
}
fmt.Println("\nRunning: nix profile upgrade github:AvengeMedia/DankMaterialShell")
updateCmd := exec.Command("nix", "profile", "upgrade", "github:AvengeMedia/DankMaterialShell")
updateCmd.Stdout = os.Stdout
updateCmd.Stderr = os.Stderr
err := updateCmd.Run()
if err != nil {
fmt.Printf("Error: Failed to update using nix profile: %v\n", err)
fmt.Println("Falling back to git-based update method...")
return updateOtherDistros()
}
fmt.Println("dms successfully updated")
return nil
}
func updateOtherDistros() error {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get user home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
return fmt.Errorf("DMS configuration directory not found at %s", dmsPath)
}
fmt.Printf("Found DMS configuration at %s\n", dmsPath)
versionInfo, err := version.GetDMSVersionInfo()
if err == nil && !versionInfo.HasUpdate {
fmt.Println()
fmt.Printf("Current version: %s\n", versionInfo.Current)
fmt.Printf("Latest version: %s\n", versionInfo.Latest)
fmt.Println()
fmt.Println("✓ You are already running the latest version.")
return errdefs.ErrNoUpdateNeeded
}
fmt.Println("\nThis will update:")
fmt.Println(" 1. The dms binary from GitHub releases")
fmt.Println(" 2. DankMaterialShell configuration using git")
if !confirmUpdate() {
return errdefs.ErrUpdateCancelled
}
fmt.Println("\n=== Updating dms binary ===")
if err := updateDMSBinary(); err != nil {
fmt.Printf("Warning: Failed to update dms binary: %v\n", err)
fmt.Println("Continuing with shell configuration update...")
} else {
fmt.Println("dms binary successfully updated")
}
fmt.Println("\n=== Updating DMS shell configuration ===")
if err := os.Chdir(dmsPath); err != nil {
return fmt.Errorf("failed to change to DMS directory: %w", err)
}
statusCmd := exec.Command("git", "status", "--porcelain")
statusOutput, _ := statusCmd.Output()
hasLocalChanges := len(strings.TrimSpace(string(statusOutput))) > 0
currentRefCmd := exec.Command("git", "symbolic-ref", "-q", "HEAD")
currentRefOutput, _ := currentRefCmd.Output()
onBranch := len(currentRefOutput) > 0
var currentTag string
var currentBranch string
if !onBranch {
tagCmd := exec.Command("git", "describe", "--exact-match", "--tags", "HEAD")
if tagOutput, err := tagCmd.Output(); err == nil {
currentTag = strings.TrimSpace(string(tagOutput))
}
} else {
branchCmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
if branchOutput, err := branchCmd.Output(); err == nil {
currentBranch = strings.TrimSpace(string(branchOutput))
}
}
fmt.Println("Fetching latest changes...")
fetchCmd := exec.Command("git", "fetch", "origin", "--tags", "--force")
fetchCmd.Stdout = os.Stdout
fetchCmd.Stderr = os.Stderr
if err := fetchCmd.Run(); err != nil {
return fmt.Errorf("failed to fetch changes: %w", err)
}
if currentTag != "" {
latestTagCmd := exec.Command("git", "tag", "-l", "v*", "--sort=-version:refname")
latestTagOutput, err := latestTagCmd.Output()
if err != nil {
return fmt.Errorf("failed to get latest tag: %w", err)
}
tags := strings.Split(strings.TrimSpace(string(latestTagOutput)), "\n")
if len(tags) == 0 || tags[0] == "" {
return fmt.Errorf("no version tags found")
}
latestTag := tags[0]
if latestTag == currentTag {
fmt.Printf("Already on latest tag: %s\n", currentTag)
return nil
}
fmt.Printf("Current tag: %s\n", currentTag)
fmt.Printf("Latest tag: %s\n", latestTag)
if hasLocalChanges {
fmt.Println("\nWarning: You have local changes in your DMS configuration.")
if offerReclone(dmsPath) {
return nil
}
return errdefs.ErrUpdateCancelled
}
fmt.Printf("Updating to %s...\n", latestTag)
checkoutCmd := exec.Command("git", "checkout", latestTag)
checkoutCmd.Stdout = os.Stdout
checkoutCmd.Stderr = os.Stderr
if err := checkoutCmd.Run(); err != nil {
fmt.Printf("Error: Failed to checkout %s: %v\n", latestTag, err)
if offerReclone(dmsPath) {
return nil
}
return fmt.Errorf("update cancelled")
}
fmt.Printf("\nUpdate complete! Updated from %s to %s\n", currentTag, latestTag)
return nil
}
if currentBranch == "" {
currentBranch = "master"
}
fmt.Printf("Current branch: %s\n", currentBranch)
if hasLocalChanges {
fmt.Println("\nWarning: You have local changes in your DMS configuration.")
if offerReclone(dmsPath) {
return nil
}
return errdefs.ErrUpdateCancelled
}
pullCmd := exec.Command("git", "pull", "origin", currentBranch)
pullCmd.Stdout = os.Stdout
pullCmd.Stderr = os.Stderr
if err := pullCmd.Run(); err != nil {
fmt.Printf("Error: Failed to pull latest changes: %v\n", err)
if offerReclone(dmsPath) {
return nil
}
return fmt.Errorf("update cancelled")
}
fmt.Println("\nUpdate complete!")
return nil
}
func offerReclone(dmsPath string) bool {
fmt.Println("\nWould you like to backup and re-clone the repository? (y/N): ")
reader := bufio.NewReader(os.Stdin)
response, err := reader.ReadString('\n')
if err != nil || !strings.HasPrefix(strings.ToLower(strings.TrimSpace(response)), "y") {
return false
}
timestamp := time.Now().Unix()
backupPath := fmt.Sprintf("%s.backup-%d", dmsPath, timestamp)
fmt.Printf("Backing up current directory to %s...\n", backupPath)
if err := os.Rename(dmsPath, backupPath); err != nil {
fmt.Printf("Error: Failed to backup directory: %v\n", err)
return false
}
fmt.Println("Cloning fresh copy...")
cloneCmd := exec.Command("git", "clone", "https://github.com/AvengeMedia/DankMaterialShell.git", dmsPath)
cloneCmd.Stdout = os.Stdout
cloneCmd.Stderr = os.Stderr
if err := cloneCmd.Run(); err != nil {
fmt.Printf("Error: Failed to clone repository: %v\n", err)
fmt.Printf("Restoring backup...\n")
os.Rename(backupPath, dmsPath)
return false
}
fmt.Printf("Successfully re-cloned repository (backup at %s)\n", backupPath)
return true
}
func confirmUpdate() bool {
fmt.Print("Do you want to proceed with the update? (y/N): ")
reader := bufio.NewReader(os.Stdin)
response, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("Error reading input: %v\n", err)
return false
}
response = strings.TrimSpace(strings.ToLower(response))
return response == "y" || response == "yes"
}
func updateDMSBinary() error {
arch := ""
switch strings.ToLower(os.Getenv("HOSTTYPE")) {
case "x86_64", "amd64":
arch = "amd64"
case "aarch64", "arm64":
arch = "arm64"
default:
cmd := exec.Command("uname", "-m")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to detect architecture: %w", err)
}
archStr := strings.TrimSpace(string(output))
switch archStr {
case "x86_64":
arch = "amd64"
case "aarch64":
arch = "arm64"
default:
return fmt.Errorf("unsupported architecture: %s", archStr)
}
}
fmt.Println("Fetching latest release version...")
cmd := exec.Command("curl", "-s", "https://api.github.com/repos/AvengeMedia/danklinux/releases/latest")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to fetch latest release: %w", err)
}
version := ""
for _, line := range strings.Split(string(output), "\n") {
if strings.Contains(line, "\"tag_name\"") {
parts := strings.Split(line, "\"")
if len(parts) >= 4 {
version = parts[3]
break
}
}
}
if version == "" {
return fmt.Errorf("could not determine latest version")
}
fmt.Printf("Latest version: %s\n", version)
tempDir, err := os.MkdirTemp("", "dms-update-*")
if err != nil {
return fmt.Errorf("failed to create temp directory: %w", err)
}
defer os.RemoveAll(tempDir)
binaryURL := fmt.Sprintf("https://github.com/AvengeMedia/DankMaterialShell/backend/releases/download/%s/dms-%s.gz", version, arch)
checksumURL := fmt.Sprintf("https://github.com/AvengeMedia/DankMaterialShell/backend/releases/download/%s/dms-%s.gz.sha256", version, arch)
binaryPath := filepath.Join(tempDir, "dms.gz")
checksumPath := filepath.Join(tempDir, "dms.gz.sha256")
fmt.Println("Downloading dms binary...")
downloadCmd := exec.Command("curl", "-L", binaryURL, "-o", binaryPath)
if err := downloadCmd.Run(); err != nil {
return fmt.Errorf("failed to download binary: %w", err)
}
fmt.Println("Downloading checksum...")
downloadCmd = exec.Command("curl", "-L", checksumURL, "-o", checksumPath)
if err := downloadCmd.Run(); err != nil {
return fmt.Errorf("failed to download checksum: %w", err)
}
fmt.Println("Verifying checksum...")
checksumData, err := os.ReadFile(checksumPath)
if err != nil {
return fmt.Errorf("failed to read checksum file: %w", err)
}
expectedChecksum := strings.Fields(string(checksumData))[0]
actualCmd := exec.Command("sha256sum", binaryPath)
actualOutput, err := actualCmd.Output()
if err != nil {
return fmt.Errorf("failed to calculate checksum: %w", err)
}
actualChecksum := strings.Fields(string(actualOutput))[0]
if expectedChecksum != actualChecksum {
return fmt.Errorf("checksum verification failed\nExpected: %s\nGot: %s", expectedChecksum, actualChecksum)
}
fmt.Println("Decompressing binary...")
decompressCmd := exec.Command("gunzip", binaryPath)
if err := decompressCmd.Run(); err != nil {
return fmt.Errorf("failed to decompress binary: %w", err)
}
decompressedPath := filepath.Join(tempDir, "dms")
if err := os.Chmod(decompressedPath, 0755); err != nil {
return fmt.Errorf("failed to make binary executable: %w", err)
}
currentPath, err := exec.LookPath("dms")
if err != nil {
return fmt.Errorf("could not find current dms binary: %w", err)
}
fmt.Printf("Installing to %s...\n", currentPath)
replaceCmd := exec.Command("sudo", "install", "-m", "0755", decompressedPath, currentPath)
replaceCmd.Stdin = os.Stdin
replaceCmd.Stdout = os.Stdout
replaceCmd.Stderr = os.Stderr
if err := replaceCmd.Run(); err != nil {
return fmt.Errorf("failed to replace binary: %w", err)
}
return nil
}