mirror of
https://github.com/wearepixel/pulsewise-collector.git
synced 2026-11-14 22:12:01 +00:00
fix: update re-execs with sudo; version output with color and formatting
- 'update' now re-execs with sudo when not root (same pattern as status) - 'version' output: name and version on separate lines, cyan version number, green for up-to-date, yellow for available update - 'update' output: matching color treatment, red on failure - Deduplicate color constants — single package-level set used everywhere
This commit is contained in:
@@ -29,6 +29,16 @@ import (
|
|||||||
// version is set at build time via -ldflags "-X main.version=x.y.z"
|
// version is set at build time via -ldflags "-X main.version=x.y.z"
|
||||||
var version = "dev"
|
var version = "dev"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ansiReset = "\033[0m"
|
||||||
|
ansiBold = "\033[1m"
|
||||||
|
ansiDim = "\033[2m"
|
||||||
|
ansiCyan = "\033[0;36m"
|
||||||
|
ansiGreen = "\033[0;32m"
|
||||||
|
ansiYellow = "\033[1;33m"
|
||||||
|
ansiRed = "\033[0;31m"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultURL = "https://pulsewise.app/api/collect"
|
defaultURL = "https://pulsewise.app/api/collect"
|
||||||
versionCheckURL = "https://pulsewise.app/collector/release/latest"
|
versionCheckURL = "https://pulsewise.app/collector/release/latest"
|
||||||
@@ -216,47 +226,55 @@ func main() {
|
|||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func runVersionCommand() {
|
func runVersionCommand() {
|
||||||
fmt.Printf("pulsewise-collector v%s\n\n", version)
|
fmt.Printf("\n %spulsewise-collector%s\n", ansiBold, ansiReset)
|
||||||
|
fmt.Printf(" %s%sv%s%s\n\n", ansiBold, ansiCyan, version, ansiReset)
|
||||||
|
|
||||||
fmt.Print("Checking for updates... ")
|
fmt.Printf(" %sChecking for updates...%s ", ansiDim, ansiReset)
|
||||||
latest, err := fetchLatestVersion()
|
latest, err := fetchLatestVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("(update check failed: %v)\n", err)
|
fmt.Printf("%s(update check failed: %v)%s\n\n", ansiDim, err, ansiReset)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if latest == version {
|
if latest == version {
|
||||||
fmt.Println("You are on the latest version.")
|
fmt.Printf("%sYou are on the latest version.%s\n\n", ansiGreen, ansiReset)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("v%s is available.\n", latest)
|
fmt.Printf("%sv%s is available%s\n", ansiYellow, latest, ansiReset)
|
||||||
fmt.Println("Run 'pulsewise-collector update' to upgrade.")
|
fmt.Printf(" %sRun 'pulsewise-collector update' to upgrade.%s\n\n", ansiDim, ansiReset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdateCommand() {
|
func runUpdateCommand() {
|
||||||
fmt.Printf("pulsewise-collector v%s\n\n", version)
|
if os.Getuid() != 0 {
|
||||||
|
reexecWithSudo()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Print("Checking for updates... ")
|
fmt.Printf("\n %spulsewise-collector%s\n", ansiBold, ansiReset)
|
||||||
|
fmt.Printf(" %s%sv%s%s\n\n", ansiBold, ansiCyan, version, ansiReset)
|
||||||
|
|
||||||
|
fmt.Printf(" %sChecking for updates...%s ", ansiDim, ansiReset)
|
||||||
latest, err := fetchLatestVersion()
|
latest, err := fetchLatestVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "update check failed: %v\n", err)
|
fmt.Fprintf(os.Stderr, "%supdate check failed: %v%s\n\n", ansiRed, err, ansiReset)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if latest == version {
|
if latest == version {
|
||||||
fmt.Printf("Already on the latest version (v%s).\n", version)
|
fmt.Printf("%sAlready on the latest version.%s\n\n", ansiGreen, ansiReset)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("v%s is available.\n\n", latest)
|
fmt.Printf("%sv%s is available%s\n\n", ansiYellow, latest, ansiReset)
|
||||||
fmt.Print("Downloading... ")
|
fmt.Printf(" %sDownloading...%s ", ansiDim, ansiReset)
|
||||||
|
|
||||||
if err := downloadAndReplace(); err != nil {
|
if err := downloadAndReplace(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "\nUpdate failed: %v\n", err)
|
fmt.Fprintf(os.Stderr, "\n %sUpdate failed: %v%s\n\n", ansiRed, err, ansiReset)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Done.\nSuccessfully updated to v%s.\n", latest)
|
fmt.Printf("%sDone.%s\n", ansiGreen, ansiReset)
|
||||||
|
fmt.Printf(" Successfully updated to %sv%s%s.\n\n", ansiCyan, latest, ansiReset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reexecWithSudo() {
|
func reexecWithSudo() {
|
||||||
@@ -280,22 +298,14 @@ func runStatusCommand() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
label := func(s string) string { return fmt.Sprintf(" %s%-16s%s", ansiDim, s, ansiReset) }
|
||||||
bold = "\033[1m"
|
|
||||||
dim = "\033[2m"
|
|
||||||
green = "\033[0;32m"
|
|
||||||
red = "\033[0;31m"
|
|
||||||
nc = "\033[0m"
|
|
||||||
)
|
|
||||||
|
|
||||||
label := func(s string) string { return fmt.Sprintf(" %s%-16s%s", dim, s, nc) }
|
fmt.Printf("\n%spulsewise-collector%s v%s\n\n", ansiBold, ansiReset, version)
|
||||||
|
|
||||||
fmt.Printf("\n%spulsewise-collector%s v%s\n\n", bold, nc, version)
|
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
token, hostname, url, _, autoUpdate, err := loadConfigFromFile()
|
token, hostname, url, _, autoUpdate, err := loadConfigFromFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s Not configured — %v\n\n", red, nc)
|
fmt.Printf("%s Not configured — %v\n\n", ansiRed, ansiReset)
|
||||||
} else {
|
} else {
|
||||||
maskedToken := token
|
maskedToken := token
|
||||||
if len(token) >= 8 {
|
if len(token) >= 8 {
|
||||||
@@ -303,11 +313,11 @@ func runStatusCommand() {
|
|||||||
} else {
|
} else {
|
||||||
maskedToken = "****"
|
maskedToken = "****"
|
||||||
}
|
}
|
||||||
autoUpdateStr := green + "enabled" + nc
|
autoUpdateStr := ansiGreen + "enabled" + ansiReset
|
||||||
if !autoUpdate {
|
if !autoUpdate {
|
||||||
autoUpdateStr = dim + "disabled" + nc
|
autoUpdateStr = ansiDim + "disabled" + ansiReset
|
||||||
}
|
}
|
||||||
fmt.Printf("%sCollector%s\n", bold, nc)
|
fmt.Printf("%sCollector%s\n", ansiBold, ansiReset)
|
||||||
fmt.Printf("%s %s\n", label("Hostname"), hostname)
|
fmt.Printf("%s %s\n", label("Hostname"), hostname)
|
||||||
fmt.Printf("%s %s\n", label("Token"), maskedToken)
|
fmt.Printf("%s %s\n", label("Token"), maskedToken)
|
||||||
fmt.Printf("%s %s\n", label("API endpoint"), url)
|
fmt.Printf("%s %s\n", label("API endpoint"), url)
|
||||||
@@ -317,22 +327,22 @@ func runStatusCommand() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Service status
|
// Service status
|
||||||
fmt.Printf("%sService%s\n", bold, nc)
|
fmt.Printf("%sService%s\n", ansiBold, ansiReset)
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
out, err := exec.Command("systemctl", "is-active", systemdUnit).Output()
|
out, err := exec.Command("systemctl", "is-active", systemdUnit).Output()
|
||||||
state := strings.TrimSpace(string(out))
|
state := strings.TrimSpace(string(out))
|
||||||
if err == nil && state == "active" {
|
if err == nil && state == "active" {
|
||||||
fmt.Printf("%s %srunning%s (systemd)\n", label("Status"), green, nc)
|
fmt.Printf("%s %srunning%s (systemd)\n", label("Status"), ansiGreen, ansiReset)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s %sstopped%s (systemd)\n", label("Status"), red, nc)
|
fmt.Printf("%s %sstopped%s (systemd)\n", label("Status"), ansiRed, ansiReset)
|
||||||
}
|
}
|
||||||
case "darwin":
|
case "darwin":
|
||||||
err := exec.Command("launchctl", "list", launchdLabel).Run()
|
err := exec.Command("launchctl", "list", launchdLabel).Run()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("%s %srunning%s (launchd)\n", label("Status"), green, nc)
|
fmt.Printf("%s %srunning%s (launchd)\n", label("Status"), ansiGreen, ansiReset)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s %sstopped%s (launchd)\n", label("Status"), red, nc)
|
fmt.Printf("%s %sstopped%s (launchd)\n", label("Status"), ansiRed, ansiReset)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Printf("%s unknown platform\n", label("Status"))
|
fmt.Printf("%s unknown platform\n", label("Status"))
|
||||||
@@ -341,7 +351,7 @@ func runStatusCommand() {
|
|||||||
// Last pulse
|
// Last pulse
|
||||||
if t, err := readLastPulse(); err == nil {
|
if t, err := readLastPulse(); err == nil {
|
||||||
ago := time.Since(t).Round(time.Second)
|
ago := time.Since(t).Round(time.Second)
|
||||||
fmt.Printf("%s %s %s(%s ago)%s\n", label("Last pulse"), t.Format("2006-01-02 15:04:05"), dim, ago, nc)
|
fmt.Printf("%s %s %s(%s ago)%s\n", label("Last pulse"), t.Format("2006-01-02 15:04:05"), ansiDim, ago, ansiReset)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s never\n", label("Last pulse"))
|
fmt.Printf("%s never\n", label("Last pulse"))
|
||||||
}
|
}
|
||||||
@@ -349,25 +359,16 @@ func runStatusCommand() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runUninstallCommand() {
|
func runUninstallCommand() {
|
||||||
const (
|
|
||||||
bold = "\033[1m"
|
|
||||||
dim = "\033[2m"
|
|
||||||
green = "\033[0;32m"
|
|
||||||
red = "\033[0;31m"
|
|
||||||
yellow = "\033[1;33m"
|
|
||||||
nc = "\033[0m"
|
|
||||||
)
|
|
||||||
|
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
fmt.Fprintf(os.Stderr, "%sError:%s uninstall must be run as root (try sudo pulsewise-collector uninstall)\n", red, nc)
|
fmt.Fprintf(os.Stderr, "%sError:%s uninstall must be run as root (try sudo pulsewise-collector uninstall)\n", ansiRed, ansiReset)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
selfPath, _ := os.Executable()
|
selfPath, _ := os.Executable()
|
||||||
selfPath, _ = filepath.EvalSymlinks(selfPath)
|
selfPath, _ = filepath.EvalSymlinks(selfPath)
|
||||||
|
|
||||||
fmt.Printf("\n%sUninstall pulsewise-collector%s\n\n", bold, nc)
|
fmt.Printf("\n%sUninstall pulsewise-collector%s\n\n", ansiBold, ansiReset)
|
||||||
fmt.Printf(" %sThe following will be removed:%s\n", dim, nc)
|
fmt.Printf(" %sThe following will be removed:%s\n", ansiDim, ansiReset)
|
||||||
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
@@ -380,7 +381,7 @@ func runUninstallCommand() {
|
|||||||
fmt.Printf(" Config %s/\n", configDir)
|
fmt.Printf(" Config %s/\n", configDir)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
fmt.Printf(" %sAre you sure?%s [y/N]: ", yellow, nc)
|
fmt.Printf(" %sAre you sure?%s [y/N]: ", ansiYellow, ansiReset)
|
||||||
var reply string
|
var reply string
|
||||||
fmt.Scanln(&reply)
|
fmt.Scanln(&reply)
|
||||||
if strings.ToLower(strings.TrimSpace(reply)) != "y" {
|
if strings.ToLower(strings.TrimSpace(reply)) != "y" {
|
||||||
@@ -392,9 +393,9 @@ func runUninstallCommand() {
|
|||||||
step := func(label, path string, fn func() error) {
|
step := func(label, path string, fn func() error) {
|
||||||
fmt.Printf(" %-28s", label+"...")
|
fmt.Printf(" %-28s", label+"...")
|
||||||
if err := fn(); err != nil {
|
if err := fn(); err != nil {
|
||||||
fmt.Printf("%sfailed%s (%v)\n", red, nc, err)
|
fmt.Printf("%sfailed%s (%v)\n", ansiRed, ansiReset, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%sdone%s\n", green, nc)
|
fmt.Printf("%sdone%s\n", ansiGreen, ansiReset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,7 +441,7 @@ func runUninstallCommand() {
|
|||||||
return os.Remove(selfPath)
|
return os.Remove(selfPath)
|
||||||
})
|
})
|
||||||
|
|
||||||
fmt.Printf("\n %sPulsewise Collector has been uninstalled.%s\n\n", green, nc)
|
fmt.Printf("\n %sPulsewise Collector has been uninstalled.%s\n\n", ansiGreen, ansiReset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user