feat: add Arch update helper and web UI
This commit is contained in:
255
cmd/lazy-update-manager/main.go
Normal file
255
cmd/lazy-update-manager/main.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"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"
|
||||
)
|
||||
|
||||
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 "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})
|
||||
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})
|
||||
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})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
if result.Total() == 0 {
|
||||
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()
|
||||
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")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return 2
|
||||
}
|
||||
|
||||
if err := gui.Run(defaultConfigPath(), !*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 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 [-no-open]
|
||||
lazy-update-manager update
|
||||
|
||||
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
|
||||
update Run paru/yay -Syu when available, otherwise sudo pacman -Syu
|
||||
`))
|
||||
}
|
||||
Reference in New Issue
Block a user