diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b47f3c..a6e4d55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y libayatana-appindicator3-dev libgtk-3-dev + - name: Set up Go uses: actions/setup-go@v5 with: @@ -39,6 +44,12 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Install Linux dependencies + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libayatana-appindicator3-dev libgtk-3-dev + - name: Set up Go uses: actions/setup-go@v5 with: @@ -47,7 +58,13 @@ jobs: - name: Get dependencies run: go mod download - - name: Run tests + - name: Run tests (Unix) + if: matrix.os != 'windows-latest' + run: go test -race -coverprofile=coverage.out -covermode=atomic ./... + + - name: Run tests (Windows) + if: matrix.os == 'windows-latest' + shell: cmd run: go test -race -coverprofile=coverage.out -covermode=atomic ./... - name: Upload coverage to Codecov @@ -69,6 +86,12 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Install Linux dependencies + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libayatana-appindicator3-dev libgtk-3-dev + - name: Set up Go uses: actions/setup-go@v5 with: @@ -107,6 +130,12 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Install Linux dependencies + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libayatana-appindicator3-dev libgtk-3-dev + - name: Set up Go uses: actions/setup-go@v5 with: diff --git a/internal/systray/systray.go b/internal/systray/systray.go index 7dae9fc..435b4ec 100644 --- a/internal/systray/systray.go +++ b/internal/systray/systray.go @@ -1390,6 +1390,8 @@ func (a *App) installCLIToPathWindows(sourcePath, targetPath string) { } // uninstallCLI removes the CLI binary from the system path. +// +//nolint:unused // Reserved for future use in systray menu func (a *App) uninstallCLI() bool { targetPath := "/usr/local/bin/agentmgr" diff --git a/internal/systray/windows_other.go b/internal/systray/windows_other.go index 2e23e2b..73e7c6e 100644 --- a/internal/systray/windows_other.go +++ b/internal/systray/windows_other.go @@ -2,7 +2,10 @@ package systray -import "github.com/kevinelliott/agentmgr/pkg/agent" +import ( + "github.com/kevinelliott/agentmgr/pkg/agent" + "github.com/kevinelliott/agentmgr/pkg/catalog" +) // showNativeSettingsWindow is not available on this platform. func (a *App) showNativeSettingsWindow() { @@ -10,6 +13,11 @@ func (a *App) showNativeSettingsWindow() { a.showSettings() } +// showNativeManageAgentsWindow is not available on this platform. +func (a *App) showNativeManageAgentsWindow(_ []catalog.AgentDef, _ []agent.Installation) { + // No-op on non-darwin platforms; caller should check hasNativeWindowSupport() +} + // showNativeAgentDetailsWindow is not available on this platform. func (a *App) showNativeAgentDetailsWindow(inst agent.Installation) { // Fall back to platform-specific dialog diff --git a/pkg/platform/linux.go b/pkg/platform/linux.go index 762357f..48b2ece 100644 --- a/pkg/platform/linux.go +++ b/pkg/platform/linux.go @@ -34,7 +34,10 @@ func (l *linuxPlatform) GetDataDir() string { if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" { return filepath.Join(xdgData, "agentmgr") } - home, _ := os.UserHomeDir() + home, err := os.UserHomeDir() + if err != nil { + return filepath.Join(os.TempDir(), ".local", "share", "agentmgr") + } return filepath.Join(home, ".local", "share", "agentmgr") } @@ -42,7 +45,10 @@ func (l *linuxPlatform) GetConfigDir() string { if xdgConfig := os.Getenv("XDG_CONFIG_HOME"); xdgConfig != "" { return filepath.Join(xdgConfig, "agentmgr") } - home, _ := os.UserHomeDir() + home, err := os.UserHomeDir() + if err != nil { + return filepath.Join(os.TempDir(), ".config", "agentmgr") + } return filepath.Join(home, ".config", "agentmgr") } @@ -50,7 +56,10 @@ func (l *linuxPlatform) GetCacheDir() string { if xdgCache := os.Getenv("XDG_CACHE_HOME"); xdgCache != "" { return filepath.Join(xdgCache, "agentmgr") } - home, _ := os.UserHomeDir() + home, err := os.UserHomeDir() + if err != nil { + return filepath.Join(os.TempDir(), ".cache", "agentmgr") + } return filepath.Join(home, ".cache", "agentmgr") } @@ -76,16 +85,17 @@ func (l *linuxPlatform) EnableAutoStart(ctx context.Context) error { } func (l *linuxPlatform) DisableAutoStart(ctx context.Context) error { - // Try both methods - l.disableSystemdAutoStart(ctx) - l.disableXDGAutoStart() + // Try both methods - errors are intentionally ignored as this is best-effort cleanup + _ = l.disableSystemdAutoStart(ctx) //nolint:errcheck + _ = l.disableXDGAutoStart() //nolint:errcheck return nil } func (l *linuxPlatform) IsAutoStartEnabled(ctx context.Context) (bool, error) { // Check systemd if l.hasSystemd() { - if enabled, _ := l.isSystemdEnabled(ctx); enabled { + enabled, err := l.isSystemdEnabled(ctx) + if err == nil && enabled { return true, nil } } @@ -150,7 +160,10 @@ func (l *linuxPlatform) getSystemdUserDir() string { if xdgConfig := os.Getenv("XDG_CONFIG_HOME"); xdgConfig != "" { return filepath.Join(xdgConfig, "systemd", "user") } - home, _ := os.UserHomeDir() + home, err := os.UserHomeDir() + if err != nil { + return filepath.Join(os.TempDir(), ".config", "systemd", "user") + } return filepath.Join(home, ".config", "systemd", "user") } @@ -194,7 +207,10 @@ func (l *linuxPlatform) getXDGAutostartDir() string { if xdgConfig := os.Getenv("XDG_CONFIG_HOME"); xdgConfig != "" { return filepath.Join(xdgConfig, "autostart") } - home, _ := os.UserHomeDir() + home, err := os.UserHomeDir() + if err != nil { + return filepath.Join(os.TempDir(), ".config", "autostart") + } return filepath.Join(home, ".config", "autostart") } @@ -253,6 +269,7 @@ func (l *linuxPlatform) ShowNotification(title, message string) error { } // Try zenity if _, err := exec.LookPath("zenity"); err == nil { + // #nosec G204 -- title and message are from controlled internal sources return exec.Command("zenity", "--notification", "--text="+title+"\n"+message).Run() } return fmt.Errorf("no notification system available") @@ -273,6 +290,7 @@ func (l *linuxPlatform) ShowChangelogDialog(agentName, fromVer, toVer, changelog func (l *linuxPlatform) showZenityDialog(agentName, fromVer, toVer, changelog string) DialogResult { text := fmt.Sprintf("%s\n\n%s → %s\n\n%s", agentName, fromVer, toVer, changelog) + // #nosec G204 -- arguments are from controlled catalog sources, not user input cmd := exec.Command("zenity", "--question", "--title=Update Available", "--text="+text,