Files
LazyUpdateManager/cmd/lazy-update-manager/main.go
Pepe44DEV bdd51a201a fix: Electron desktop app startup and add install script
- tryElectronApp() now finds the project directory via
  LAZY_UPDATE_MANAGER_DIR env, CWD, binary path, or $HOME
- Launches node_modules/.bin/electron directly instead of npm start
  (avoids recompiling the Go backend on every launch)
- Add install.sh with Electron fallback, desktop launcher, and
  prefer_electron config
- Desktop entry points to electron launcher for wofi support
- Update electron to v41.7.1
2026-05-27 03:16:40 +02:00

372 lines
8.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"lazy-update-manager/internal/config"
"lazy-update-manager/internal/gui"
"lazy-update-manager/internal/notify"
"lazy-update-manager/internal/state"
"lazy-update-manager/internal/updater"
"lazy-update-manager/internal/version"
)
func main() {
os.Exit(run(os.Args[1:]))
}
func run(args []string) int {
if len(args) == 0 {
args = []string{"status"}
}
switch args[0] {
case "status":
return status()
case "check":
return check(args[1:])
case "notify":
return notifyUpdates(args[1:])
case "gui":
return runGUI(args[1:])
case "update":
return update()
case "version":
fmt.Println(version.String())
return 0
case "help", "-h", "--help":
usage()
return 0
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n\n", args[0])
usage()
return 2
}
}
func status() int {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
cfg, err := config.Load(defaultConfigPath())
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
result, err := updater.CheckWithOptions(ctx, updater.Options{
CheckAUR: cfg.CheckAUR,
IgnoredPackages: cfg.IgnoredPackages,
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
printResult(result)
return 0
}
func check(args []string) int {
fs := flag.NewFlagSet("check", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
quiet := fs.Bool("quiet", false, "only print the number of available updates")
if err := fs.Parse(args); err != nil {
return 2
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
cfg, err := config.Load(defaultConfigPath())
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
result, err := updater.CheckWithOptions(ctx, updater.Options{
CheckAUR: cfg.CheckAUR,
IgnoredPackages: cfg.IgnoredPackages,
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
if *quiet {
fmt.Println(result.Total())
return 0
}
printResult(result)
return 0
}
func notifyUpdates(args []string) int {
fs := flag.NewFlagSet("notify", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
force := fs.Bool("force", false, "send a notification even if the weekly reminder was already shown")
if err := fs.Parse(args); err != nil {
return 2
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
cfg, err := config.Load(defaultConfigPath())
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
result, err := updater.CheckWithOptions(ctx, updater.Options{
CheckAUR: cfg.CheckAUR,
IgnoredPackages: cfg.IgnoredPackages,
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
if result.Total() == 0 {
return 0
}
if !cfg.NotificationsEnabled {
return 0
}
store, err := state.Load(defaultStatePath())
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
now := time.Now()
reminderInterval := time.Duration(cfg.ReminderIntervalHours) * time.Hour
if !*force && now.Sub(store.LastReminder) < reminderInterval {
return 0
}
message := result.Summary()
if err := notify.Send("LazyUpdateManager", message); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
store.LastReminder = now
store.LastUpdateCount = result.Total()
store.LastCheck = now
store.LastSuccess = now
store.LastSummary = result.Summary()
if err := state.Save(defaultStatePath(), store); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
return 0
}
func runGUI(args []string) int {
fs := flag.NewFlagSet("gui", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
noOpen := fs.Bool("no-open", false, "start the GUI server without opening a browser")
// Support positional "web" argument: `lazy-update-manager gui web`
forceWeb := false
if len(args) > 0 && args[0] == "web" {
forceWeb = true
args = args[1:]
}
if err := fs.Parse(args); err != nil {
return 2
}
cfg, err := config.Load(defaultConfigPath())
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
// If the user explicitly requested the web interface, skip Electron
if forceWeb {
if err := gui.Run(defaultConfigPath(), defaultStatePath(), !*noOpen); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
return 0
}
// Only try launching the Electron desktop when the caller did not request
// "-no-open" (used by the Electron wrapper to start the backend).
// This avoids the Electron<->backend launch loop.
if !*noOpen && cfg.PreferElectron && runtime.GOOS == "linux" {
if err := tryElectronApp(); err == nil {
return 0
}
// Fall through to browser if Electron fails
}
if err := gui.Run(defaultConfigPath(), defaultStatePath(), !*noOpen); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
return 0
}
func update() int {
cfg, err := config.Load(defaultConfigPath())
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
helper := updater.AURHelper()
if helper != "" && cfg.CheckAUR {
return runInteractive(helper, "-Syu")
}
return runInteractive("sudo", "pacman", "-Syu")
}
func runInteractive(name string, args ...string) int {
cmd := exec.Command(name, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return exitErr.ExitCode()
}
fmt.Fprintln(os.Stderr, err)
return 1
}
return 0
}
func printResult(result updater.Result) {
fmt.Println(result.Summary())
for _, item := range result.Packages {
fmt.Printf("%-8s %s", item.Source, item.Name)
if item.Current != "" || item.Available != "" {
fmt.Printf(" %s -> %s", item.Current, item.Available)
}
fmt.Println()
}
}
func defaultStatePath() string {
stateHome := os.Getenv("XDG_STATE_HOME")
if stateHome == "" {
home, err := os.UserHomeDir()
if err != nil {
return filepath.Join(".", "lazy-update-manager.json")
}
stateHome = filepath.Join(home, ".local", "state")
}
return filepath.Join(stateHome, "lazy-update-manager", "state.json")
}
func defaultConfigPath() string {
configHome := os.Getenv("XDG_CONFIG_HOME")
if configHome == "" {
home, err := os.UserHomeDir()
if err != nil {
return filepath.Join(".", "lazy-update-manager.json")
}
configHome = filepath.Join(home, ".config")
}
return filepath.Join(configHome, "lazy-update-manager", "config.json")
}
func tryElectronApp() error {
projectDir := electronProjectDir()
if projectDir == "" {
return errors.New("LazyUpdateManager project directory not found set LAZY_UPDATE_MANAGER_DIR or run from the project folder")
}
electronBin := filepath.Join(projectDir, "node_modules", ".bin", "electron")
if _, err := os.Stat(electronBin); err != nil {
electronBin = "electron"
}
cmd := exec.Command(electronBin, projectDir)
cmd.Dir = projectDir
cmd.Stdout = nil
cmd.Stderr = nil
cmd.Stdin = nil
return cmd.Run()
}
func electronProjectDir() string {
if d := os.Getenv("LAZY_UPDATE_MANAGER_DIR"); d != "" {
if hasElectronMain(d) {
return d
}
}
if d, err := os.Getwd(); err == nil && hasElectronMain(d) {
return d
}
exe, err := os.Executable()
if err != nil {
return ""
}
exeDir := filepath.Dir(exe)
// binary in ~/Projekte/LazyUpdateManager/bin/
if p := filepath.Dir(exeDir); hasElectronMain(p) {
return p
}
// binary in ~/.local/bin/, project is at configured location
if p := filepath.Join(exeDir, "..", "Projekte", "LazyUpdateManager"); hasElectronMain(p) {
return p
}
// relative to $HOME
if home, err := os.UserHomeDir(); err == nil {
if p := filepath.Join(home, "Projekte", "LazyUpdateManager"); hasElectronMain(p) {
return p
}
}
return ""
}
func hasElectronMain(dir string) bool {
info, err := os.Stat(filepath.Join(dir, "electron", "main.cjs"))
return err == nil && !info.IsDir()
}
func usage() {
fmt.Println(strings.TrimSpace(`
LazyUpdateManager - Update helper for Arch / Hyprland
Usage:
lazy-update-manager status
lazy-update-manager check [-quiet]
lazy-update-manager notify [-force]
lazy-update-manager gui [web] [-no-open]
lazy-update-manager update
lazy-update-manager version
Commands:
status Show available pacman and AUR updates
check Check updates, useful for scripts and status bars
notify Send a weekly desktop notification when updates exist
gui Start the graphical web interface (use 'web' to force opening in the browser)
update Run paru/yay -Syu when available, otherwise sudo pacman -Syu
version Print the installed version and build time
`))
}