Files
fleet/tools/hangar/services/settings_service.go
T
Andrey KizimenkoandGeorge Karr 0f9af89a93 Add Hangar: Go/Wails desktop control panel for the Fleet dev environment (#46406)
## Summary

Adds `tools/hangar` — a macOS desktop control panel for working on Fleet
locally: branch management, `fleet serve` orchestration, log tail, dev
MySQL backup/restore, `fleetctl`, GitOps, and `osquery-perf`, all in one
window.

Built with **Go + [Wails 3](https://v3alpha.wails.io)** — the backend is
plain Go (`os/exec`, `syscall`, goroutines) so Fleet engineers can
contribute to it; only the desktop shell is Wails. The `internal/`
packages are pure and unit-tested.

### History note
Hangar started as a Rust/Tauri app. It was ported to Go, and **the Go
port is now the canonical `tools/hangar`**. The original Rust/Tauri
implementation has been removed from the monorepo (preserved in a
standalone repo) — so although this branch's earlier commits add and
then replace the Rust app, the net diff is just the Go app at
`tools/hangar`. The bundle identifier is `com.fleetdm.fleet-hangar`,
matching the original app so existing settings carry over.

# Checklist for submitter

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

> No `changes/` file: `tools/` is contributor tooling, not a
user-visible Fleet change. No DB migrations, no Fleet config settings,
no fleetd/orbit changes.

## Testing

- [x] Added/updated automated tests (Go unit tests across `internal/`,
including a path-traversal regression for backup deletion)
- [x] QA'd all new/changed functionality manually

## Test plan
- [x] `cd tools/hangar && task dev` launches the app (live-reload)
- [x] `task build` produces `bin/fleet-hangar`; `go test ./...` is green
- [x] First-run gate discovers a local Fleet clone and runs dep checks
- [x] Server tab can run the build chain and start `fleet serve`
- [x] Git tab branch search finds an older branch (e.g. a stale `qa-*`)
by name


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced Fleet Hangar, a comprehensive desktop application for Fleet
development workflows, providing unified controls for server/database
management, git operations, configuration, logging, and troubleshooting.
  * Added database backup management with metadata tracking.
* Integrated process orchestration for development services (Docker,
ngrok, Python).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: George Karr <georgekarrv@users.noreply.github.com>
2026-06-23 15:45:23 -05:00

73 lines
2.4 KiB
Go

// Package services holds the Wails-bound service structs. Each method is
// callable from the frontend; the structs are thin adapters that resolve
// real paths and delegate to the internal/* packages (where the logic and
// its tests live).
package services
import (
"github.com/fleetdm/fleet/tools/hangar/internal/paths"
"github.com/fleetdm/fleet/tools/hangar/internal/settings"
)
// SettingsService exposes settings, repo probing, ngrok parsing, and the
// sandboxed file helpers. Mirrors the settings/* commands from settings.rs.
type SettingsService struct{}
// GetSettings loads the persisted settings (defaults if none saved yet).
func (s *SettingsService) GetSettings() (settings.Settings, error) {
dir, err := paths.ConfigDir()
if err != nil {
return settings.Settings{}, err
}
return settings.Load(dir)
}
// SaveSettings persists the given settings.
func (s *SettingsService) SaveSettings(in settings.Settings) error {
dir, err := paths.ConfigDir()
if err != nil {
return err
}
return settings.Save(dir, in)
}
// ProbeFleetRepo validates a single path, or (when path is empty) discovers
// Fleet clones under the well-known dev roots.
func (s *SettingsService) ProbeFleetRepo(path string) []settings.RepoProbe {
if path != "" {
return []settings.RepoProbe{settings.ProbeOne(path)}
}
return settings.DiscoverFleetRepos()
}
// DetectFleetConfig returns the relative serve-config name in the repo root
// (fleet.yml/fleet.yaml), or "" if none.
func (s *SettingsService) DetectFleetConfig(repo string) string {
return settings.DetectFleetConfig(repo)
}
// ParseNgrokYml summarizes an ngrok.yml (empty path = ngrok's default).
func (s *SettingsService) ParseNgrokYml(path string) settings.NgrokYamlInfo {
return settings.ParseNgrokYml(path)
}
// ReadTextFile reads a .yml/.yaml file under $HOME.
func (s *SettingsService) ReadTextFile(path string) (string, error) {
return settings.ReadTextFile(path)
}
// WriteTextFile writes a .yml/.yaml file under $HOME.
func (s *SettingsService) WriteTextFile(path, contents string) error {
return settings.WriteTextFile(path, contents)
}
// OpenPath opens a dir or allowed file in the system file manager.
func (s *SettingsService) OpenPath(path string, reveal bool) error {
return settings.OpenPath(path, reveal)
}
// OpenURL opens an http(s) URL in the default browser.
func (s *SettingsService) OpenURL(url string) error {
return settings.OpenURL(url)
}