Orbit: Add Fleet Desktop support to Windows (#4873)

* Orbit: Add Fleet Desktop support to Windows

* Rename workflow, fix linux build

* Do not compile systray on linux

* nolint on unused

* Fix lint properly

* nolint both checkers

* Fix monitor logic in desktopRunner

* Fix interrupt and execute order
This commit is contained in:
Lucas Manuel Rodriguez 2022-04-01 17:28:51 -03:00 committed by GitHub
parent fc68e41514
commit c82c580716
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 578 additions and 141 deletions

View File

@ -1,4 +1,4 @@
name: Generate desktop.app.tar.gz for Orbit
name: Generate Fleet Desktop targets for Orbit
on:
push:
@ -6,7 +6,7 @@ on:
- main
paths:
# The workflow can be triggered by modifying FLEET_DESKTOP_VERSION env.
- '.github/workflows/generate-desktop-app-tar-gz.yml'
- '.github/workflows/generate-desktop-targets.yml'
workflow_dispatch:
env:
@ -16,9 +16,10 @@ permissions:
contents: read
jobs:
release:
desktop-macos:
runs-on: macos-latest
steps:
- name: Install Go
uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2
with:
@ -41,6 +42,7 @@ jobs:
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
security find-identity -vv
rm certificate.p12
- name: Generate desktop.app.tar.gz
env:
AC_USERNAME: ${{ secrets.APPLE_USERNAME }}
@ -53,8 +55,32 @@ jobs:
FLEET_DESKTOP_NOTARIZE=true \
FLEET_DESKTOP_VERSION=$FLEET_DESKTOP_VERSION \
make desktop-app-tar-gz
- name: Upload desktop.app.tar.gz
uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2
with:
name: desktop.app.tar.gz
path: desktop.app.tar.gz
desktop-windows:
runs-on: macos-latest
steps:
- name: Install Go
uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2
with:
go-version: '^1.17.0'
- name: Checkout
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
- name: Generate fleet-desktop.exe
run: |
FLEET_DESKTOP_VERSION=$FLEET_DESKTOP_VERSION \
make desktop-windows
- name: Upload fleet-desktop.exe
uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2
with:
name: fleet-desktop.exe
path: fleet-desktop.exe

View File

@ -1,5 +1,5 @@
FROM alpine
MAINTAINER Fleet Developers <hello@fleetdm.com>
LABEL maintainer="Fleet Developers <hello@fleetdm.com>"
RUN apk --update add ca-certificates

View File

@ -311,3 +311,10 @@ ifneq ($(shell uname), Darwin)
@exit 1
endif
go run ./tools/desktop macos
# Build desktop executable for Windows.
#
# Usage:
# FLEET_DESKTOP_VERSION=0.0.1 make desktop-windows
desktop-windows:
GOOS=windows GOARCH=amd64 go build -ldflags "-H=windowsgui" -o fleet-desktop.exe ./orbit/cmd/desktop

View File

@ -0,0 +1 @@
* Add beta support for Fleet Desktop on Windows.

View File

@ -16,6 +16,7 @@ import (
"syscall"
"time"
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/pkg/secure"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
@ -247,6 +248,9 @@ func updatesAddFunc(c *cli.Context) error {
// - an ".exe" file for target=osqueryd is expected to be called "osqueryd.exe".
dstPath := filepath.Join(name, platform, tag, name)
switch {
case name == "desktop" && platform == "windows":
// This is a special case for the desktop target on Windows.
dstPath = filepath.Join(filepath.Dir(dstPath), constant.DesktopAppExecName+".exe")
case strings.HasSuffix(target, ".exe"):
dstPath += ".exe"
case strings.HasSuffix(target, ".app.tar.gz"):
@ -480,7 +484,7 @@ func startRotatePseudoTx(repoPath string) (commit, rollback func() error, err er
func createBackups(dirPath string) error {
// Only *.json files need to be backed up (other files are not modified)
backupPath := filepath.Join(dirPath, backupDirectory)
if err := os.Mkdir(backupPath, os.ModeDir|0744); err != nil {
if err := os.Mkdir(backupPath, os.ModeDir|0o744); err != nil {
if errors.Is(err, fs.ErrExist) {
return fmt.Errorf("backup directory already exists: %w", err)
}
@ -577,11 +581,11 @@ func copyTarget(srcPath, dstPath string) error {
}
defer src.Close()
if err := secure.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
if err := secure.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
return fmt.Errorf("create dst dir for copy: %w", err)
}
dst, err := secure.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0644)
dst, err := secure.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0o644)
if err != nil {
return fmt.Errorf("open dst for copy: %w", err)
}

10
go.mod
View File

@ -29,11 +29,15 @@ require (
github.com/facebookincubator/nvdtools v0.1.4
github.com/fatih/color v1.12.0
github.com/fleetdm/goose v0.0.0-20220214194029-91b5e5eb8e77
github.com/getlantern/systray v1.2.0
github.com/getlantern/golog v0.0.0-20211223150227-d4d95a44d873 // indirect
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 // indirect
github.com/getlantern/systray v1.2.2-0.20220329111105-6065fda28be8
github.com/getsentry/sentry-go v0.12.0
github.com/ghodss/yaml v1.0.0
github.com/go-kit/kit v0.9.0
github.com/go-sql-driver/mysql v1.6.0
github.com/go-stack/stack v1.8.1 // indirect
github.com/gocarina/gocsv v0.0.0-20220310154401-d4df709ca055
github.com/gocolly/colly v1.2.0
github.com/golang-jwt/jwt/v4 v4.0.0
@ -99,9 +103,11 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0
go.opentelemetry.io/otel/sdk v1.3.0
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8
golang.org/x/sys v0.0.0-20220329152356-43be30ef3008
google.golang.org/grpc v1.42.0
gopkg.in/guregu/null.v3 v3.4.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0

36
go.sum
View File

@ -261,6 +261,7 @@ github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -419,18 +420,23 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
github.com/getlantern/errors v1.0.1 h1:XukU2whlh7OdpxnkXhNH9VTLVz0EVPGKDV5K0oWhvzw=
github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
github.com/getlantern/golog v0.0.0-20211223150227-d4d95a44d873 h1:nnod94N4hMKb7pyJmnXDk+HR23o1S2CbZ4oMKzHbp9A=
github.com/getlantern/golog v0.0.0-20211223150227-d4d95a44d873/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc h1:sue+aeVx7JF5v36H1HfvcGFImLpSD5goj8d+MitovDU=
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc/go.mod h1:D9RWpXy/EFPYxiKUURo2TB8UBosbqkiLhttRrZYtvqM=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2yaur9Qk3rHYD414j3Q1rl7+L0AylxrE=
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/systray v1.2.0 h1:MsAdOcmOnm4V+r3HFONDszdZeoj7E3q2dEvsPdsxXtI=
github.com/getlantern/systray v1.2.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 h1:QthAQCekS1YOeYWSvoHI6ZatlG4B+GBDLxV/2ZkBsTA=
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/systray v1.2.2-0.20220329111105-6065fda28be8 h1:ois/A+ZnsBYCw8ezd8Zibsk4wKOQzhbPVlR1fZ/Yg9w=
github.com/getlantern/systray v1.2.2-0.20220329111105-6065fda28be8/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -491,8 +497,9 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.7.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
@ -1252,7 +1259,6 @@ github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c h1:TWQ2UvXPkhPxI2KmApKBOCaV6yD2N4mlvqFQ/DlPtpQ=
github.com/zwass/kit v0.0.0-20210625184505-ec5b5c5cce9c/go.mod h1:OYYulo9tUqRadRLwB0+LE914sa1ui2yL7OrcU3Q/1XY=
@ -1301,20 +1307,28 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
gocloud.dev v0.24.0 h1:cNtHD07zQQiv02OiwwDyVMuHmR7iQt2RLkzoAgz7wBs=
gocloud.dev v0.24.0/go.mod h1:uA+als++iBX5ShuG4upQo/3Zoz49iIPlYUWHV5mM8w8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -1586,8 +1600,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220329152356-43be30ef3008 h1:pq9pwoi2rjLWvmiVser/lIOgiyA3fli4M+RfGVMA7nE=
golang.org/x/sys v0.0.0-20220329152356-43be30ef3008/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=

View File

@ -1,3 +1,6 @@
//go:build darwin || windows
// +build darwin windows
package main
import (
@ -14,9 +17,6 @@ import (
"github.com/getlantern/systray"
)
//go:embed icon_white.png
var icoBytes []byte
func main() {
// Our TUF provided targets must support launching with "--help".
if len(os.Args) > 1 && os.Args[1] == "--help" {

View File

@ -0,0 +1,11 @@
//go:build darwin
// +build darwin
// TODO(lucas): Once we support Linux, amend the above build tags.
package main
import _ "embed"
//go:embed icon_white.png
var icoBytes []byte

View File

@ -0,0 +1,12 @@
//go:build windows
// +build windows
package main
import _ "embed"
// For Windows we must use ico format for the icon,
// see https://github.com/getlantern/systray/blob/6065fda28be8c8d91aeb5e20de25e1600b8664a3/systray_windows.go#L850-L856.
//go:embed icon_white.ico
var icoBytes []byte

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -16,6 +16,7 @@ import (
"time"
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
"github.com/fleetdm/fleet/v4/orbit/pkg/execuser"
"github.com/fleetdm/fleet/v4/orbit/pkg/insecure"
"github.com/fleetdm/fleet/v4/orbit/pkg/osquery"
"github.com/fleetdm/fleet/v4/orbit/pkg/table"
@ -23,7 +24,6 @@ import (
"github.com/fleetdm/fleet/v4/orbit/pkg/update/filestore"
"github.com/fleetdm/fleet/v4/pkg/certificate"
"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/pkg/open"
"github.com/fleetdm/fleet/v4/pkg/secure"
"github.com/google/uuid"
"github.com/oklog/run"
@ -216,8 +216,15 @@ func main() {
opt.Targets = update.DarwinLegacyTargets
}
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
opt.Targets["desktop"] = update.DesktopMacOSTarget
if c.Bool("fleet-desktop") {
switch runtime.GOOS {
case "darwin":
opt.Targets["desktop"] = update.DesktopMacOSTarget
case "windows":
opt.Targets["desktop"] = update.DesktopWindowsTarget
default:
log.Fatal().Str("GOOS", runtime.GOOS).Msg("unsupported GOOS for desktop target")
}
// Override default channel with the provided value.
opt.Targets.SetTargetChannel("desktop", c.String("desktop-channel"))
}
@ -232,9 +239,9 @@ func main() {
opt.InsecureTransport = c.Bool("insecure")
var (
updater *update.Updater
osquerydPath string
desktopAppPath string
updater *update.Updater
osquerydPath string
desktopPath string
)
// NOTE: When running in dev-mode, even if `disable-updates` is set,
@ -252,12 +259,16 @@ func main() {
return fmt.Errorf("get osqueryd target: %w", err)
}
osquerydPath = osquerydLocalTarget.ExecPath
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
if c.Bool("fleet-desktop") {
fleetDesktopLocalTarget, err := updater.Get("desktop")
if err != nil {
return fmt.Errorf("get desktop target: %w", err)
}
desktopAppPath = fleetDesktopLocalTarget.DirPath
if runtime.GOOS == "darwin" {
desktopPath = fleetDesktopLocalTarget.DirPath
} else {
desktopPath = fleetDesktopLocalTarget.ExecPath
}
}
} else {
log.Info().Msg("running with auto updates disabled")
@ -266,10 +277,17 @@ func main() {
if err != nil {
log.Fatal().Err(err).Msg("locate osqueryd")
}
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
desktopAppPath, err = updater.DirLocalPath("desktop")
if err != nil {
return fmt.Errorf("get desktop target: %w", err)
if c.Bool("fleet-desktop") {
if runtime.GOOS == "darwin" {
desktopPath, err = updater.DirLocalPath("desktop")
if err != nil {
return fmt.Errorf("get desktop target: %w", err)
}
} else {
desktopPath, err = updater.ExecutableLocalPath("desktop")
if err != nil {
return fmt.Errorf("get desktop target: %w", err)
}
}
}
}
@ -296,7 +314,7 @@ func main() {
if !c.Bool("disable-updates") {
targets := []string{"orbit", "osqueryd"}
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
if c.Bool("fleet-desktop") && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
targets = append(targets, "desktop")
}
updateRunner, err := update.NewRunner(updater, update.RunnerOptions{
@ -458,8 +476,8 @@ func main() {
}))
g.Add(ext.Execute, ext.Interrupt)
if runtime.GOOS == "darwin" && c.Bool("fleet-desktop") {
desktopRunner := newDesktopRunner(desktopAppPath, fleetURL, deviceAuthToken, c.Bool("insecure"))
if c.Bool("fleet-desktop") && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
desktopRunner := newDesktopRunner(desktopPath, fleetURL, deviceAuthToken, c.Bool("insecure"))
g.Add(desktopRunner.actor())
}
@ -481,20 +499,22 @@ func main() {
}
type desktopRunner struct {
appPath string
desktopPath string
fleetURL string
deviceAuthToken string
insecure bool
done chan struct{}
interruptCh chan struct{} // closed when interrupt is triggered
executeDoneCh chan struct{} // closed when execute returns
}
func newDesktopRunner(appPath, fleetURL, deviceAuthToken string, insecure bool) *desktopRunner {
func newDesktopRunner(desktopPath, fleetURL, deviceAuthToken string, insecure bool) *desktopRunner {
return &desktopRunner{
appPath: appPath,
desktopPath: desktopPath,
fleetURL: fleetURL,
deviceAuthToken: deviceAuthToken,
insecure: insecure,
done: make(chan struct{}),
interruptCh: make(chan struct{}),
executeDoneCh: make(chan struct{}),
}
}
@ -502,43 +522,88 @@ func (d *desktopRunner) actor() (func() error, func(error)) {
return d.execute, d.interrupt
}
// execute makes sure the fleet-desktop application is running.
//
// We have to support the scenario where the user closes its sessions (log out).
// To support this, we add retries to execuser.Run. Basically retry execuser.Run until it succeds,
// which will happen when the user logs in.
// Once fleet-desktop is started, the process is monitored (user processes get killed when the user
// closes all its sessions).
//
// NOTE(lucas): This logic could be improved to detect if there's a valid session or not first.
func (d *desktopRunner) execute() error {
log.Info().Str("path", d.appPath).Msg("opening")
defer close(d.executeDoneCh)
log.Info().Str("path", d.desktopPath).Msg("opening")
url, err := url.Parse(d.fleetURL)
if err != nil {
return fmt.Errorf("invalid fleet-url: %w", err)
}
url.Path = path.Join(url.Path, "device", d.deviceAuthToken)
opts := []open.AppOption{
open.AppWithEnv("FLEET_DESKTOP_DEVICE_URL", url.String()),
open.AppWithEnv("FLEET_DESKTOP_DEVICE_API_TEST_PATH", path.Join("api", "latest", "fleet", "device", d.deviceAuthToken)),
opts := []execuser.Option{
execuser.WithEnv("FLEET_DESKTOP_DEVICE_URL", url.String()),
execuser.WithEnv("FLEET_DESKTOP_DEVICE_API_TEST_PATH", path.Join("api", "latest", "fleet", "device", d.deviceAuthToken)),
}
if d.insecure {
opts = append(opts, open.AppWithEnv("FLEET_DESKTOP_INSECURE", "1"))
}
if err := open.App(d.appPath, opts...); err != nil {
return fmt.Errorf("open desktop app: %w", err)
opts = append(opts, execuser.WithEnv("FLEET_DESKTOP_INSECURE", "1"))
}
// Monitor the fleet-desktop application.
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-d.done:
return nil
case <-ticker.C:
if _, err := getProcessByName(constant.DesktopAppExecName); err != nil {
log.Err(err).Msg("desktopRunner.execute exit")
return fmt.Errorf("get desktop process: %w", err)
// First retry logic to start fleet-desktop.
if done := retry(30*time.Second, d.interruptCh, func() bool {
// Orbit runs as root user on Unix and as SYSTEM (Windows Service) user on Windows.
// To be able to run the desktop application (mostly to register the icon in the system tray)
// we need to run the application as the login user.
// Package execuser provides multi-platform support for this.
if err := execuser.Run(d.desktopPath, opts...); err != nil {
log.Debug().Err(err).Msg("execuser.Run")
return true
}
return false
}); done {
return nil
}
// Second retry logic to monitor fleet-desktop.
if done := retry(30*time.Second, d.interruptCh, func() bool {
switch _, err := getProcessByName(constant.DesktopAppExecName); {
case err == nil:
return true // all good, process is running, retry.
case errors.Is(err, errProcessNotFound):
log.Debug().Msgf("%s process not running", constant.DesktopAppExecName)
return false // process is not running, do not retry.
default:
log.Debug().Err(err).Msg("getProcessByName")
return true // failed to get process by name, retry.
}
}); done {
return nil
}
}
}
func retry(d time.Duration, done chan struct{}, fn func() bool) bool {
ticker := time.NewTicker(d)
defer ticker.Stop()
for {
if retry := fn(); !retry {
return false
}
select {
case <-done:
return true
case <-ticker.C:
// OK
}
}
}
func (d *desktopRunner) interrupt(err error) {
log.Debug().Err(err).Msg("interrupt desktopRunner")
defer close(d.done)
close(d.interruptCh) // Signal execute to return.
<-d.executeDoneCh // Wait for execute to return.
if err := killProcessByName(constant.DesktopAppExecName); err != nil {
log.Error().Err(err).Msg("killProcess")
@ -590,7 +655,7 @@ func getProcessByName(name string) (*gopsutil_process.Process, error) {
log.Debug().Err(err).Int32("pid", process.Pid).Msg("get process name")
continue
}
if processName == name {
if strings.HasPrefix(processName, name) {
foundProcess = process
break
}

View File

@ -6,5 +6,8 @@ const (
// DefaultFileMode is the default file mode to apply to created files.
DefaultFileMode = 0o600
// DesktopAppExecName is the name of Fleet's Desktop executable.
//
// We use fleet-desktop as name to properly identify the process when listing
// running processes/tasks.
DesktopAppExecName = "fleet-desktop"
)

View File

@ -0,0 +1,30 @@
// Package execuser is used to run applications from a high privilege user (root on Unix,
// SYSTEM service on Windows) as the current login user.
package execuser
type eopts struct {
env [][2]string
stderrPath string //nolint:structcheck,unused
}
// Option allows configuring the application.
type Option func(*eopts)
// WithEnv sets environment variables for the application.
func WithEnv(name, value string) Option {
return func(a *eopts) {
a.env = append(a.env, [2]string{name, value})
}
}
// Run runs an application as the current login user.
// It assumes the caller is running with high privileges (root on Unix, SYSTEM on Windows).
//
// It returns after starting the child process.
func Run(path string, opts ...Option) error {
var o eopts
for _, fn := range opts {
fn(&o)
}
return run(path, o)
}

View File

@ -0,0 +1,34 @@
package execuser
import (
"fmt"
"os"
"os/exec"
)
// run uses macOS open command to start application as the current login user.
func run(path string, opts eopts) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("stat path %q: %w", path, err)
}
if !info.IsDir() {
return fmt.Errorf("path is not an .app directory: %s", path)
}
var arg []string
if opts.stderrPath != "" {
arg = append(arg, "--stderr", opts.stderrPath)
}
for _, nv := range opts.env {
arg = append(arg, "--env", fmt.Sprintf("%s=%s", nv[0], nv[1]))
}
arg = append(arg, path)
cmd := exec.Command("open", arg...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return fmt.Errorf("open path %q: %w", path, err)
}
return nil
}

View File

@ -0,0 +1,5 @@
package execuser
func run(path string, opts eopts) error {
panic("unimplemented")
}

View File

@ -0,0 +1,232 @@
package execuser
// NOTE: The following was copied from
// https://gist.github.com/LiamHaworth/1ac37f7fb6018293fc43f86993db24fc
//
// To view what was modified/added, you can use the execuser_windows_diff.sh script.
import (
"fmt"
"os"
"unsafe"
"golang.org/x/sys/windows"
)
var (
modwtsapi32 *windows.LazyDLL = windows.NewLazySystemDLL("wtsapi32.dll")
modkernel32 *windows.LazyDLL = windows.NewLazySystemDLL("kernel32.dll")
modadvapi32 *windows.LazyDLL = windows.NewLazySystemDLL("advapi32.dll")
moduserenv *windows.LazyDLL = windows.NewLazySystemDLL("userenv.dll")
procWTSEnumerateSessionsW *windows.LazyProc = modwtsapi32.NewProc("WTSEnumerateSessionsW")
procWTSGetActiveConsoleSessionId *windows.LazyProc = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
procWTSQueryUserToken *windows.LazyProc = modwtsapi32.NewProc("WTSQueryUserToken")
procDuplicateTokenEx *windows.LazyProc = modadvapi32.NewProc("DuplicateTokenEx")
procCreateEnvironmentBlock *windows.LazyProc = moduserenv.NewProc("CreateEnvironmentBlock")
procCreateProcessAsUser *windows.LazyProc = modadvapi32.NewProc("CreateProcessAsUserW")
)
const (
WTS_CURRENT_SERVER_HANDLE uintptr = 0
)
type WTS_CONNECTSTATE_CLASS int
const (
WTSActive WTS_CONNECTSTATE_CLASS = iota
WTSConnected
WTSConnectQuery
WTSShadow
WTSDisconnected
WTSIdle
WTSListen
WTSReset
WTSDown
WTSInit
)
type SECURITY_IMPERSONATION_LEVEL int
const (
SecurityAnonymous SECURITY_IMPERSONATION_LEVEL = iota
SecurityIdentification
SecurityImpersonation
SecurityDelegation
)
type TOKEN_TYPE int
const (
TokenPrimary TOKEN_TYPE = iota + 1
TokenImpersonazion
)
type SW int
const (
SW_HIDE SW = 0
SW_SHOWNORMAL = 1
SW_NORMAL = 1
SW_SHOWMINIMIZED = 2
SW_SHOWMAXIMIZED = 3
SW_MAXIMIZE = 3
SW_SHOWNOACTIVATE = 4
SW_SHOW = 5
SW_MINIMIZE = 6
SW_SHOWMINNOACTIVE = 7
SW_SHOWNA = 8
SW_RESTORE = 9
SW_SHOWDEFAULT = 10
SW_MAX = 1
)
type WTS_SESSION_INFO struct {
SessionID windows.Handle
WinStationName *uint16
State WTS_CONNECTSTATE_CLASS
}
const (
CREATE_UNICODE_ENVIRONMENT uint16 = 0x00000400
CREATE_NO_WINDOW = 0x08000000
CREATE_NEW_CONSOLE = 0x00000010
)
// run uses the Windows API to run a child process as the current login user.
// It assumes the caller is running as a SYSTEM Windows service.
//
// It sets the environment of the current process so that it gets inherited by
// the child process (see call to CreateEnvironmentBlock).
// From https://docs.microsoft.com/en-us/windows/win32/procthread/changing-environment-variables:
// "If you want the child process to inherit most of the parent's environment with
// only a few changes, retrieve the current values using GetEnvironmentVariable, save these values,
// create an updated block for the child process to inherit, create the child process, and then
// restore the saved values using SetEnvironmentVariable, as shown in the following example."
//
func run(path string, opts eopts) error {
for _, nv := range opts.env {
os.Setenv(nv[0], nv[1])
}
return startProcessAsCurrentUser(path, "", "")
}
// getCurrentUserSessionId will attempt to resolve
// the session ID of the user currently active on
// the system.
func getCurrentUserSessionId() (windows.Handle, error) {
sessionList, err := wtsEnumerateSessions()
if err != nil {
return 0xFFFFFFFF, fmt.Errorf("get current user session token: %s", err)
}
for i := range sessionList {
if sessionList[i].State == WTSActive {
return sessionList[i].SessionID, nil
}
}
// TODO(lucas): Check which sessions is assigned to current log in user.
if sessionId, _, err := procWTSGetActiveConsoleSessionId.Call(); sessionId == 0xFFFFFFFF {
return 0xFFFFFFFF, fmt.Errorf("get current user session token: call native WTSGetActiveConsoleSessionId: %s", err)
} else {
return windows.Handle(sessionId), nil
}
}
// wtsEnumerateSession will call the native
// version for Windows and parse the result
// to a Golang friendly version
func wtsEnumerateSessions() ([]*WTS_SESSION_INFO, error) {
var (
sessionInformation windows.Handle = windows.Handle(0)
sessionCount int = 0
sessionList []*WTS_SESSION_INFO = make([]*WTS_SESSION_INFO, 0)
)
if returnCode, _, err := procWTSEnumerateSessionsW.Call(WTS_CURRENT_SERVER_HANDLE, 0, 1, uintptr(unsafe.Pointer(&sessionInformation)), uintptr(unsafe.Pointer(&sessionCount))); returnCode == 0 {
return nil, fmt.Errorf("call native WTSEnumerateSessionsW: %s", err)
}
structSize := unsafe.Sizeof(WTS_SESSION_INFO{})
current := uintptr(sessionInformation)
for i := 0; i < sessionCount; i++ {
sessionList = append(sessionList, (*WTS_SESSION_INFO)(unsafe.Pointer(current)))
current += structSize
}
return sessionList, nil
}
// duplicateUserTokenFromSessionID will attempt
// to duplicate the user token for the user logged
// into the provided session ID
func duplicateUserTokenFromSessionID(sessionId windows.Handle) (windows.Token, error) {
var (
impersonationToken windows.Handle = 0
userToken windows.Token = 0
)
if returnCode, _, err := procWTSQueryUserToken.Call(uintptr(sessionId), uintptr(unsafe.Pointer(&impersonationToken))); returnCode == 0 {
return 0xFFFFFFFF, fmt.Errorf("call native WTSQueryUserToken: %s", err)
}
if returnCode, _, err := procDuplicateTokenEx.Call(uintptr(impersonationToken), 0, 0, uintptr(SecurityImpersonation), uintptr(TokenPrimary), uintptr(unsafe.Pointer(&userToken))); returnCode == 0 {
return 0xFFFFFFFF, fmt.Errorf("call native DuplicateTokenEx: %s", err)
}
if err := windows.CloseHandle(impersonationToken); err != nil {
return 0xFFFFFFFF, fmt.Errorf("close windows handle used for token duplication: %s", err)
}
return userToken, nil
}
func startProcessAsCurrentUser(appPath, cmdLine, workDir string) error {
var (
sessionId windows.Handle
userToken windows.Token
envInfo windows.Handle
startupInfo windows.StartupInfo
processInfo windows.ProcessInformation
commandLine uintptr = 0
workingDir uintptr = 0
err error
)
if sessionId, err = getCurrentUserSessionId(); err != nil {
return err
}
if userToken, err = duplicateUserTokenFromSessionID(sessionId); err != nil {
return fmt.Errorf("get duplicate user token for current user session: %s", err)
}
if returnCode, _, err := procCreateEnvironmentBlock.Call(uintptr(unsafe.Pointer(&envInfo)), uintptr(userToken), 1); returnCode == 0 {
return fmt.Errorf("create environment details for process: %s", err)
}
// TODO(lucas): Test out creation flags and startup info values.
creationFlags := CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE
startupInfo.ShowWindow = SW_SHOW
startupInfo.Desktop = windows.StringToUTF16Ptr("winsta0\\default")
if len(cmdLine) > 0 {
commandLine = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(cmdLine)))
}
if len(workDir) > 0 {
workingDir = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(workDir)))
}
if returnCode, _, err := procCreateProcessAsUser.Call(
uintptr(userToken), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(appPath))), commandLine, 0, 0, 0,
uintptr(creationFlags), uintptr(envInfo), workingDir, uintptr(unsafe.Pointer(&startupInfo)), uintptr(unsafe.Pointer(&processInfo)),
); returnCode == 0 {
return fmt.Errorf("create process as user: %s", err)
}
return nil
}

View File

@ -0,0 +1,3 @@
#!/bin/bash
diff execuser_windows.go <(curl https://gist.githubusercontent.com/LiamHaworth/1ac37f7fb6018293fc43f86993db24fc/raw/2cfaf36cd326ace04c000fb988a5ef6b1f02a116/native.go)

View File

@ -41,6 +41,12 @@ func BuildMSI(opt Options) (string, error) {
updateOpt.RootDirectory = orbitRoot
updateOpt.Targets = update.WindowsTargets
if opt.Desktop {
updateOpt.Targets["desktop"] = update.DesktopWindowsTarget
// Override default channel with the provided value.
updateOpt.Targets.SetTargetChannel("desktop", opt.DesktopChannel)
}
// Override default channels with the provided values.
updateOpt.Targets.SetTargetChannel("orbit", opt.OrbitChannel)
updateOpt.Targets.SetTargetChannel("osqueryd", opt.OsquerydChannel)

View File

@ -53,7 +53,7 @@ var windowsWixTemplate = template.Must(template.New("").Option("missingkey=error
ErrorControl="ignore"
Start="auto"
Type="ownProcess"
Arguments='--root-dir "[ORBITROOT]." --log-file "[System64Folder]config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log"{{ if .FleetURL }} --fleet-url "{{ .FleetURL }}"{{ end }}{{ if .FleetCertificate }} --fleet-certificate "[ORBITROOT]fleet.pem"{{ end }}{{ if .EnrollSecret }} --enroll-secret-path "[ORBITROOT]secret.txt"{{ end }}{{if .Insecure }} --insecure{{ end }}{{ if .Debug }} --debug{{ end }}{{ if .UpdateURL }} --update-url "{{ .UpdateURL }}"{{ end }}{{ if .DisableUpdates }} --disable-updates{{ end }} --orbit-channel "{{ .OrbitChannel }}" --osqueryd-channel "{{ .OsquerydChannel }}"'
Arguments='--root-dir "[ORBITROOT]." --log-file "[System64Folder]config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log"{{ if .FleetURL }} --fleet-url "{{ .FleetURL }}"{{ end }}{{ if .FleetCertificate }} --fleet-certificate "[ORBITROOT]fleet.pem"{{ end }}{{ if .EnrollSecret }} --enroll-secret-path "[ORBITROOT]secret.txt"{{ end }}{{if .Insecure }} --insecure{{ end }}{{ if .Debug }} --debug{{ end }}{{ if .UpdateURL }} --update-url "{{ .UpdateURL }}"{{ end }}{{ if .DisableUpdates }} --disable-updates{{ end }}{{ if .Desktop }} --fleet-desktop --desktop-channel {{ .DesktopChannel }}{{ end }} --orbit-channel "{{ .OrbitChannel }}" --osqueryd-channel "{{ .OsquerydChannel }}"'
>
<util:ServiceConfig
FirstFailureActionType="restart"

View File

@ -66,4 +66,10 @@ var (
TargetFile: "desktop.app.tar.gz",
ExtractedExecSubPath: []string{"Fleet Desktop.app", "Contents", "MacOS", constant.DesktopAppExecName},
}
DesktopWindowsTarget = TargetInfo{
Platform: "windows",
Channel: "stable",
TargetFile: constant.DesktopAppExecName + ".exe",
}
)

View File

@ -1,84 +1,11 @@
package open
import (
"fmt"
"os"
"os/exec"
"runtime"
)
import "fmt"
// Browser opens the default browser at the given url and returns.
func Browser(url string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "start", url)
case "darwin":
cmd = exec.Command("open", url)
default: // xdg-open is available on most Linux-y systems
cmd = exec.Command("xdg-open", url)
}
if err := cmd.Run(); err != nil {
if err := browser(url); err != nil {
return fmt.Errorf("open in browser: %w", err)
}
return nil
}
type appOpts struct {
env [][2]string
stderrPath string
}
// AppOption are options to use when opening the application with App.
type AppOption func(*appOpts)
// AppWithEnv sets the environment for opening an application.
func AppWithEnv(name, value string) AppOption {
return func(a *appOpts) {
a.env = append(a.env, [2]string{name, value})
}
}
// AppWithStderr sets the stderr destination for the application.
func AppWithStderr(path string) AppOption {
return func(a *appOpts) {
a.stderrPath = path
}
}
// App opens an application at path with the default application.
func App(path string, opts ...AppOption) error {
var o appOpts
for _, fn := range opts {
fn(&o)
}
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("stat path %q: %w", path, err)
}
switch runtime.GOOS {
case "darwin":
if !info.IsDir() {
return fmt.Errorf("path is not an .app directory: %s", path)
}
var arg []string
if o.stderrPath != "" {
arg = append(arg, "--stderr", o.stderrPath)
}
for _, nv := range o.env {
arg = append(arg, "--env", fmt.Sprintf("%s=%s", nv[0], nv[1]))
}
arg = append(arg, path)
cmd := exec.Command("open", arg...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return fmt.Errorf("open path: %w", err)
}
return nil
default:
return fmt.Errorf("platform unsupported: %s", runtime.GOOS)
}
}

7
pkg/open/open_darwin.go Normal file
View File

@ -0,0 +1,7 @@
package open
import "os/exec"
func browser(url string) error {
return exec.Command("open", url).Run()
}

8
pkg/open/open_linux.go Normal file
View File

@ -0,0 +1,8 @@
package open
import "os/exec"
func browser(url string) error {
// xdg-open is available on most Linux-y systems
return exec.Command("xdg-open", url).Run()
}

16
pkg/open/open_windows.go Normal file
View File

@ -0,0 +1,16 @@
package open
import (
"os/exec"
"syscall"
)
func browser(url string) error {
cmd := exec.Command("cmd", "/c", "start", url)
cmd.SysProcAttr = &syscall.SysProcAttr{
// HideWindow avoids a brief cmd console from opening
// before the browser opens the URL.
HideWindow: true,
}
return cmd.Run()
}

View File

@ -1,5 +1,5 @@
FROM alpine
MAINTAINER Fleet Developers <hello@fleetdm.com>
LABEL maintainer="Fleet Developers <hello@fleetdm.com>"
RUN apk --update add ca-certificates

View File

@ -1,5 +1,5 @@
FROM alpine
MAINTAINER Fleet Developers <hello@fleetdm.com>
LABEL maintainer="Fleet Developers <hello@fleetdm.com>"
RUN apk --update add ca-certificates

View File

@ -76,6 +76,19 @@ function create_repository() {
rm desktop.app.tar.gz
fi
# Add Fleet Desktop application on (if enabled).
if [[ $system == "windows" && -n "$FLEET_DESKTOP" ]]; then
FLEET_DESKTOP_VERSION=42.0.0 \
make desktop-windows
./build/fleetctl updates add \
--path $TUF_PATH \
--target fleet-desktop.exe \
--platform windows \
--name desktop \
--version 42.0.0 -t 42.0 -t 42 -t stable
rm fleet-desktop.exe
fi
done
# Generate and add osqueryd .app bundle for macos-app.
@ -151,6 +164,7 @@ if [ -n "$GENERATE_PKGS" ]; then
echo "Generating msi..."
./build/fleetctl package \
--type=msi \
${FLEET_DESKTOP:+--fleet-desktop} \
--fleet-url=https://$MSI_HOSTNAME:8080 \
--enroll-secret=$ENROLL_SECRET \
--insecure \