fleet/orbit/cmd/desktop/desktop.go
Zach Wasserman 5d35b467d8
Remove errors tooltips from Fleet Desktop menu (#7678)
Now that these errors are available in log files, we think this adds
more confusion for end users.
2022-09-12 08:35:26 -07:00

236 lines
5.7 KiB
Go

package main
import (
_ "embed"
"errors"
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"time"
"github.com/fleetdm/fleet/v4/pkg/open"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/getlantern/systray"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/natefinch/lumberjack.v2"
)
// version is set at compile time via -ldflags
var version = "unknown"
func main() {
setupLogs()
// Our TUF provided targets must support launching with "--help".
if len(os.Args) > 1 && os.Args[1] == "--help" {
fmt.Println("Fleet Desktop application executable")
return
}
log.Info().Msgf("fleet-desktop version=%s", version)
devURL := os.Getenv("FLEET_DESKTOP_DEVICE_URL")
if devURL == "" {
log.Fatal().Msg("missing URL environment FLEET_DESKTOP_DEVICE_URL")
}
deviceURL, err := url.Parse(devURL)
if err != nil {
log.Fatal().Err(err).Msg("invalid URL argument")
}
basePath := deviceURL.Scheme + "://" + deviceURL.Host
deviceToken := path.Base(deviceURL.Path)
transparencyURL := basePath + "/api/latest/fleet/device/" + deviceToken + "/transparency"
onReady := func() {
log.Info().Msg("ready")
systray.SetTemplateIcon(icoBytes, icoBytes)
systray.SetTooltip("Fleet Desktop")
// Add a disabled menu item with the current version
versionItem := systray.AddMenuItem(fmt.Sprintf("Fleet Desktop v%s", version), "")
versionItem.Disable()
systray.AddSeparator()
myDeviceItem := systray.AddMenuItem("Initializing...", "")
myDeviceItem.Disable()
transparencyItem := systray.AddMenuItem("Transparency", "")
transparencyItem.Disable()
var insecureSkipVerify bool
if os.Getenv("FLEET_DESKTOP_INSECURE") != "" {
insecureSkipVerify = true
}
rootCA := os.Getenv("FLEET_DESKTOP_FLEET_ROOT_CA")
client, err := service.NewDeviceClient(basePath, deviceToken, insecureSkipVerify, rootCA)
if err != nil {
log.Fatal().Err(err).Msg("unable to initialize request client")
}
// Perform API test call to enable the "My device" item as soon
// as the device auth token is registered by Fleet.
deviceEnabledChan := func() <-chan interface{} {
done := make(chan interface{})
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
defer close(done)
for {
_, err := client.ListDevicePolicies()
if err == nil || errors.Is(err, service.ErrMissingLicense) {
myDeviceItem.SetTitle("My device")
myDeviceItem.Enable()
transparencyItem.Enable()
return
}
log.Error().Err(err).Msg("get device URL")
<-ticker.C
}
}()
return done
}()
go func() {
<-deviceEnabledChan
tic := time.NewTicker(5 * time.Minute)
defer tic.Stop()
for {
<-tic.C
policies, err := client.ListDevicePolicies()
switch {
case err == nil:
// OK
case errors.Is(err, service.ErrMissingLicense):
myDeviceItem.SetTitle("My device")
continue
default:
log.Error().Err(err).Msg("get device URL")
continue
}
failedPolicyCount := 0
for _, policy := range policies {
if policy.Response != "pass" {
failedPolicyCount++
}
}
if failedPolicyCount > 0 {
myDeviceItem.SetTitle(fmt.Sprintf("🔴 My device (%d)", failedPolicyCount))
} else {
myDeviceItem.SetTitle("🟢 My device")
}
myDeviceItem.Enable()
}
}()
go func() {
for {
select {
case <-myDeviceItem.ClickedCh:
if err := open.Browser(deviceURL.String()); err != nil {
log.Error().Err(err).Msg("open browser my device")
}
case <-transparencyItem.ClickedCh:
if err := open.Browser(transparencyURL); err != nil {
log.Error().Err(err).Msg("open browser transparency")
}
}
}
}()
}
onExit := func() {
log.Info().Msg("exit")
}
systray.Run(onReady, onExit)
}
// setupLogs configures our logging system to write logs to rolling files and
// stderr, if for some reason we can't write a log file the logs are still
// printed to stderr.
func setupLogs() {
stderrOut := zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano, NoColor: true}
dir, err := logDir()
if err != nil {
log.Logger = log.Output(stderrOut)
log.Error().Err(err).Msg("find directory for logs")
return
}
dir = filepath.Join(dir, "Fleet")
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Logger = log.Output(stderrOut)
log.Error().Err(err).Msg("make directories for log files")
return
}
logFile := &lumberjack.Logger{
Filename: filepath.Join(dir, "fleet-desktop.log"),
MaxSize: 25, // megabytes
MaxBackups: 3,
MaxAge: 28, // days
}
log.Logger = log.Output(zerolog.MultiLevelWriter(
zerolog.ConsoleWriter{Out: logFile, TimeFormat: time.RFC3339Nano, NoColor: true},
stderrOut,
))
}
// logDir returns the default root directory to use for application-level logs.
//
// On Unix systems, it returns $XDG_STATE_HOME as specified by
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if
// non-empty, else $HOME/.local/state.
// On Darwin, it returns $HOME/Library/Logs.
// On Windows, it returns %LocalAppData%
//
// If the location cannot be determined (for example, $HOME is not defined),
// then it will return an error.
func logDir() (string, error) {
var dir string
switch runtime.GOOS {
case "windows":
dir = os.Getenv("LocalAppData")
if dir == "" {
return "", errors.New("%LocalAppData% is not defined")
}
case "darwin":
dir = os.Getenv("HOME")
if dir == "" {
return "", errors.New("$HOME is not defined")
}
dir += "/Library/Logs"
default: // Unix
dir = os.Getenv("XDG_STATE_HOME")
if dir == "" {
dir = os.Getenv("HOME")
if dir == "" {
return "", errors.New("neither $XDG_STATE_HOME nor $HOME are defined")
}
dir += "/.local/state"
}
}
return dir, nil
}