diff --git a/.gitignore b/.gitignore
index 0059f2561c..daac2ebd16 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,9 @@ macoffice_rel_notes/
# IDE
.vscode
+
+# residual files when running the build-windows tool
+orbit/cmd/desktop/manifest.xml
+orbit/cmd/desktop/resource.syso
+orbit/cmd/orbit/manifest.xml
+orbit/cmd/orbit/resource.syso
diff --git a/Makefile b/Makefile
index df46760cfe..efa2914b1f 100644
--- a/Makefile
+++ b/Makefile
@@ -394,13 +394,17 @@ endif
FLEET_DESKTOP_VERSION ?= unknown
# Build desktop executable for Windows.
+# This generates desktop executable for Windows that includes versioninfo binary properties
+# These properties can be displayed when right-click on the binary in Windows Explorer.
+# See: https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
+# To sign this binary with a certificate, use signtool.exe or osslsigncode tool
#
# Usage:
# FLEET_DESKTOP_VERSION=0.0.1 make desktop-windows
#
# Output: fleet-desktop.exe
desktop-windows:
- GOOS=windows GOARCH=amd64 go build -ldflags "-H=windowsgui -X=main.version=$(FLEET_DESKTOP_VERSION)" -o fleet-desktop.exe ./orbit/cmd/desktop
+ go run ./orbit/tools/build/build-windows.go -version $(FLEET_DESKTOP_VERSION) -input ./orbit/cmd/desktop -output fleet-desktop.exe
# Build desktop executable for Linux.
#
@@ -423,6 +427,19 @@ desktop-linux:
tar czf desktop.tar.gz fleet-desktop && \
rm -r fleet-desktop"
+# Build orbit executable for Windows.
+# This generates orbit executable for Windows that includes versioninfo binary properties
+# These properties can be displayed when right-click on the binary in Windows Explorer.
+# See: https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
+# To sign this binary with a certificate, use signtool.exe or osslsigncode tool
+#
+# Usage:
+# ORBIT_VERSION=0.0.1 make orbit-windows
+#
+# Output: orbit.exe
+orbit-windows:
+ go run ./orbit/tools/build/build-windows.go -version $(ORBIT_VERSION) -input ./orbit/cmd/orbit -output orbit.exe
+
# db-replica-setup setups one main and one read replica MySQL instance for dev/testing.
# - Assumes the docker containers are already running (tools/mysql-replica-testing/docker-compose.yml)
# - MySQL instance listening on 3308 is the main instance.
diff --git a/changes/8085-add-versioninfo-metadata b/changes/8085-add-versioninfo-metadata
new file mode 100644
index 0000000000..4d5e260b3c
--- /dev/null
+++ b/changes/8085-add-versioninfo-metadata
@@ -0,0 +1 @@
+- Added version info metadata file to windows installer.
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 4b1f12712d..bee3fdbeaf 100644
--- a/go.mod
+++ b/go.mod
@@ -149,6 +149,7 @@ require (
github.com/RobotsAndPencils/buford v0.14.0 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
+ github.com/akavel/rsrc v0.10.2 // indirect
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/antchfx/xpath v1.2.2 // indirect
@@ -241,6 +242,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
+ github.com/josephspurrier/goversioninfo v1.4.0 // indirect
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/klauspost/compress v1.15.11 // indirect
github.com/lib/pq v1.10.6 // indirect
diff --git a/go.sum b/go.sum
index a4eca7c822..975bf4c1b6 100644
--- a/go.sum
+++ b/go.sum
@@ -192,6 +192,8 @@ github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
+github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
+github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725 h1:NjwIgLQlD46o79bheVG4SCdRnnOz4XtgUN1WABX5DLA=
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
@@ -839,6 +841,8 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
+github.com/josephspurrier/goversioninfo v1.4.0 h1:Puhl12NSHUSALHSuzYwPYQkqa2E1+7SrtAPJorKK0C8=
+github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
diff --git a/orbit/changes/11012-11013-versioninfo-and-icon-on-orbit-binaries b/orbit/changes/11012-11013-versioninfo-and-icon-on-orbit-binaries
new file mode 100644
index 0000000000..f16338083e
--- /dev/null
+++ b/orbit/changes/11012-11013-versioninfo-and-icon-on-orbit-binaries
@@ -0,0 +1 @@
+* Added version information and icon on orbit and fleet-desktop binaries
diff --git a/orbit/cmd/desktop/windows_app.ico b/orbit/cmd/desktop/windows_app.ico
new file mode 100644
index 0000000000..9928aad30b
Binary files /dev/null and b/orbit/cmd/desktop/windows_app.ico differ
diff --git a/orbit/cmd/orbit/windows_app.ico b/orbit/cmd/orbit/windows_app.ico
new file mode 100644
index 0000000000..9928aad30b
Binary files /dev/null and b/orbit/cmd/orbit/windows_app.ico differ
diff --git a/orbit/goreleaser-windows.yml b/orbit/goreleaser-windows.yml
index ef93d6a27c..9ed8963e55 100644
--- a/orbit/goreleaser-windows.yml
+++ b/orbit/goreleaser-windows.yml
@@ -16,6 +16,8 @@ builds:
- -X github.com/fleetdm/fleet/v4/orbit/pkg/build.Version={{.Version}}
- -X github.com/fleetdm/fleet/v4/orbit/pkg/build.Commit={{.Commit}}
- -X github.com/fleetdm/fleet/v4/orbit/pkg/build.Date={{.Date}}
+ hooks:
+ pre: "go run ./orbit/tools/build/build-windows.go -version {{.Version}} -input ./orbit/cmd/orbit -resource=true"
archives:
- id: orbit
diff --git a/orbit/pkg/packaging/windows.go b/orbit/pkg/packaging/windows.go
index c1d239b208..57151182eb 100644
--- a/orbit/pkg/packaging/windows.go
+++ b/orbit/pkg/packaging/windows.go
@@ -3,18 +3,23 @@ package packaging
import (
"bytes"
"crypto/tls"
+ "errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
+ "strconv"
+ "strings"
+ "time"
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging/wix"
"github.com/fleetdm/fleet/v4/orbit/pkg/update"
"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/pkg/secure"
+ "github.com/josephspurrier/goversioninfo"
"github.com/rs/zerolog/log"
)
@@ -123,6 +128,10 @@ func BuildMSI(opt Options) (string, error) {
return "", fmt.Errorf("write powershell installer utils file: %w", err)
}
+ if err := writeResourceSyso(opt, orbitRoot); err != nil {
+ return "", fmt.Errorf("write VERSIONINFO: %w", err)
+ }
+
if err := writeWixFile(opt, tmpDir); err != nil {
return "", fmt.Errorf("write wix file: %w", err)
}
@@ -225,3 +234,148 @@ func writePowershellInstallerUtilsFile(opt Options, rootPath string) error {
return nil
}
+
+// writeManifestXML creates the manifest.xml file used when generating the 'resource.syso' metadata
+// (see writeResourceSyso). Returns the path of the newly created file.
+func writeManifestXML(vParts []string, orbitPath string) (string, error) {
+ filePath := filepath.Join(orbitPath, "manifest.xml")
+
+ tmplOpts := struct {
+ Version string
+ }{
+ Version: strings.Join(vParts, "."),
+ }
+
+ var contents bytes.Buffer
+ if err := ManifestXMLTemplate.Execute(&contents, tmplOpts); err != nil {
+ return "", fmt.Errorf("parsing manifest.xml template: %w", err)
+ }
+
+ if err := ioutil.WriteFile(filePath, contents.Bytes(), constant.DefaultFileMode); err != nil {
+ return "", fmt.Errorf("writing manifest.xml file: %w", err)
+ }
+
+ return filePath, nil
+}
+
+// createVersionInfo returns a VersionInfo struct pointer to be used to generate the 'resource.syso'
+// metadata file (see writeResourceSyso).
+func createVersionInfo(vParts []string, manifestPath string) (*goversioninfo.VersionInfo, error) {
+ vIntParts := make([]int, 0, len(vParts))
+ for _, p := range vParts {
+ v, err := strconv.Atoi(p)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing version part %s: %w", p, err)
+ }
+ vIntParts = append(vIntParts, v)
+ }
+ version := strings.Join(vParts, ".")
+ copyright := fmt.Sprintf("%d Fleet Device Management Inc.", time.Now().Year())
+
+ // Taken from https://github.com/josephspurrier/goversioninfo/blob/master/testdata/resource/versioninfo.json
+ langID, err := strconv.ParseUint("0409", 16, 16)
+ if err != nil {
+ return nil, errors.New("invalid LangID")
+ }
+ // Taken from https://github.com/josephspurrier/goversioninfo/blob/master/testdata/resource/versioninfo.json
+ charsetID, err := strconv.ParseUint("04B0", 16, 16)
+ if err != nil {
+ return nil, errors.New("invalid charsetID")
+ }
+
+ result := goversioninfo.VersionInfo{
+ FixedFileInfo: goversioninfo.FixedFileInfo{
+ FileVersion: goversioninfo.FileVersion{
+ Major: vIntParts[0],
+ Minor: vIntParts[1],
+ Patch: vIntParts[2],
+ Build: vIntParts[3],
+ },
+ ProductVersion: goversioninfo.FileVersion{
+ Major: vIntParts[0],
+ Minor: vIntParts[1],
+ Patch: vIntParts[2],
+ Build: vIntParts[3],
+ },
+ FileFlagsMask: "3f",
+ FileFlags: "00",
+ FileOS: "040004",
+ FileType: "01",
+ FileSubType: "00",
+ },
+ StringFileInfo: goversioninfo.StringFileInfo{
+ Comments: "Fleet osquery",
+ CompanyName: "Fleet Device Management (fleetdm.com)",
+ FileDescription: "Fleet osquery installer",
+ FileVersion: version,
+ InternalName: "",
+ LegalCopyright: copyright,
+ LegalTrademarks: "",
+ OriginalFilename: "",
+ PrivateBuild: "",
+ ProductName: "Fleet osquery",
+ ProductVersion: version,
+ SpecialBuild: "",
+ },
+ VarFileInfo: goversioninfo.VarFileInfo{
+ Translation: goversioninfo.Translation{
+ LangID: goversioninfo.LangID(langID),
+ CharsetID: goversioninfo.CharsetID(charsetID),
+ },
+ },
+ IconPath: "",
+ ManifestPath: manifestPath,
+ }
+
+ return &result, nil
+}
+
+// SanitizeVersion returns the version parts (Major, Minor, Patch and Build), filling the Build part
+// with '0' if missing. Will error out if the version string is missing the Major, Minor or
+// Patch part(s).
+func SanitizeVersion(version string) ([]string, error) {
+ vParts := strings.Split(version, ".")
+ if len(vParts) < 3 {
+ return nil, errors.New("invalid version string")
+ }
+
+ if len(vParts) < 4 {
+ vParts = append(vParts, "0")
+ }
+
+ return vParts[:4], nil
+}
+
+// writeResourceSyso creates the 'resource.syso' metadata file which contains the required Microsoft
+// Windows Version Information
+func writeResourceSyso(opt Options, orbitPath string) error {
+ if err := secure.MkdirAll(orbitPath, constant.DefaultDirMode); err != nil {
+ return fmt.Errorf("mkdir: %w", err)
+ }
+
+ vParts, err := SanitizeVersion(opt.Version)
+ if err != nil {
+ return fmt.Errorf("invalid version %s: %w", opt.Version, err)
+ }
+
+ manifestPath, err := writeManifestXML(vParts, orbitPath)
+ if err != nil {
+ return fmt.Errorf("creating manifest.xml: %w", err)
+ }
+ defer os.RemoveAll(manifestPath)
+
+ vi, err := createVersionInfo(vParts, manifestPath)
+ if err != nil {
+ return fmt.Errorf("parsing versioninfo: %w", err)
+ }
+
+ vi.Build()
+ vi.Walk()
+
+ outPath := filepath.Join(orbitPath, "resource.syso")
+ if err := vi.WriteSyso(outPath, "amd64"); err != nil {
+ return fmt.Errorf("creating syso file: %w", err)
+ }
+
+ return nil
+}
diff --git a/orbit/pkg/packaging/windows_templates.go b/orbit/pkg/packaging/windows_templates.go
index 45b6f05b22..fcbe8df002 100644
--- a/orbit/pkg/packaging/windows_templates.go
+++ b/orbit/pkg/packaging/windows_templates.go
@@ -2,6 +2,27 @@ package packaging
import "text/template"
+// Adapted from
+// https://github.com/josephspurrier/goversioninfo/blob/master/testdata/resource/goversioninfo.exe.manifest
+var ManifestXMLTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
+ `
+
+
+
+
+
+
+
+
+
+`))
+
// Partially adapted from Launcher's wix XML in
// https://github.com/kolide/launcher/blob/master/pkg/packagekit/internal/assets/main.wxs.
var windowsWixTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
diff --git a/orbit/pkg/packaging/windows_test.go b/orbit/pkg/packaging/windows_test.go
new file mode 100644
index 0000000000..1199be4209
--- /dev/null
+++ b/orbit/pkg/packaging/windows_test.go
@@ -0,0 +1,91 @@
+package packaging
+
+import (
+ "fmt"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestCreateVersionInfo(t *testing.T) {
+ manifestPath := filepath.Join(t.TempDir(), "somefile.json")
+
+ t.Run("invalid version parts", func(t *testing.T) {
+ parts := []string{"1", "a", "3", "c"}
+ result, err := createVersionInfo(parts, manifestPath)
+ require.ErrorContains(t, err, "error parsing version part")
+ require.Nil(t, result)
+ })
+
+ t.Run("creates a VersionInfo struct", func(t *testing.T) {
+ parts := []string{"1", "2", "3", "0"}
+ result, err := createVersionInfo(parts, manifestPath)
+ require.NoError(t, err)
+
+ require.NotNil(t, result)
+
+ require.Equal(t, result.FixedFileInfo.FileVersion.Major, 1)
+ require.Equal(t, result.FixedFileInfo.FileVersion.Minor, 2)
+ require.Equal(t, result.FixedFileInfo.FileVersion.Patch, 3)
+ require.Equal(t, result.FixedFileInfo.FileVersion.Build, 0)
+
+ require.Equal(t, result.FixedFileInfo.ProductVersion.Major, 1)
+ require.Equal(t, result.FixedFileInfo.ProductVersion.Minor, 2)
+ require.Equal(t, result.FixedFileInfo.ProductVersion.Patch, 3)
+ require.Equal(t, result.FixedFileInfo.ProductVersion.Build, 0)
+
+ require.Equal(t, result.FixedFileInfo.FileFlagsMask, "3f")
+ require.Equal(t, result.FixedFileInfo.FileFlags, "00")
+ require.Equal(t, result.FixedFileInfo.FileOS, "040004")
+ require.Equal(t, result.FixedFileInfo.FileType, "01")
+ require.Equal(t, result.FixedFileInfo.FileSubType, "00")
+
+ require.Equal(t, result.StringFileInfo.Comments, "Fleet osquery")
+ require.Equal(t, result.StringFileInfo.CompanyName, "Fleet Device Management (fleetdm.com)")
+ require.Equal(t, result.StringFileInfo.FileDescription, "Fleet osquery installer")
+ require.Equal(t, result.StringFileInfo.FileVersion, "1.2.3.0")
+ require.Equal(t, result.StringFileInfo.LegalCopyright, fmt.Sprintf("%d Fleet Device Management Inc.", time.Now().Year()))
+ require.Equal(t, result.StringFileInfo.ProductName, "Fleet osquery")
+ require.Equal(t, result.StringFileInfo.ProductVersion, "1.2.3.0")
+ require.Equal(t, result.ManifestPath, manifestPath)
+ })
+}
+
+func TestWriteResourceSyso(t *testing.T) {
+ t.Run("removes intermediary manifest.xml file", func(t *testing.T) {
+ path := t.TempDir()
+ opt := Options{Version: "1.2.3"}
+
+ err := writeResourceSyso(opt, path)
+ require.NoError(t, err)
+
+ require.NoFileExists(t, filepath.Join(path, "manifest.xml"))
+ })
+}
+
+func TestSanitizeVersion(t *testing.T) {
+ testCases := []struct {
+ Version string
+ Parts []string
+ ErrorsOut bool
+ }{
+ {Version: "4.13.0", Parts: []string{"4", "13", "0", "0"}},
+ {Version: "4.13.0.1", Parts: []string{"4", "13", "0", "1"}},
+ {Version: "4.13.0.1.2", Parts: []string{"4", "13", "0", "1"}},
+ {Version: "4", ErrorsOut: true},
+ {Version: "4.13", ErrorsOut: true},
+ {Version: "bad bad", ErrorsOut: true},
+ }
+
+ for _, tC := range testCases {
+ result, err := SanitizeVersion(tC.Version)
+
+ if tC.ErrorsOut {
+ require.Error(t, err)
+ }
+
+ require.Equal(t, tC.Parts, result)
+ }
+}
diff --git a/orbit/tools/build/build-windows.go b/orbit/tools/build/build-windows.go
new file mode 100644
index 0000000000..8809e4e2c7
--- /dev/null
+++ b/orbit/tools/build/build-windows.go
@@ -0,0 +1,224 @@
+package main
+
+// This tool builds Orbit binaries with versioninfo information.
+// https://learn.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
+
+import (
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/fleetdm/fleet/v4/orbit/pkg/constant"
+ "github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
+ "github.com/josephspurrier/goversioninfo"
+ zlog "github.com/rs/zerolog/log"
+)
+
+func main() {
+ // Input flags
+ flagVersion := flag.String("version", "0.0.1", "Version string")
+ flagCreateResource := flag.Bool("resource", false, "This is a bool flag to just create the resource.syso file and not build the binary")
+ flagIcon := flag.String("icon", "windows_app.ico", "Path to the icon file to embed on the binary")
+ flagOutputBinary := flag.String("output", "output.exe", "Path to the output binary")
+ flagCmdDir := flag.String("input", "", "Path to the directory containing the utility to build")
+
+ flag.Usage = func() {
+ zlog.Fatal().Msgf("Usage: %s -version -input -output \n", os.Args[0])
+ }
+ flag.Parse()
+
+ // check if flagCmdDir is a valid directory
+ _, err := os.Stat(*flagCmdDir)
+ if err != nil {
+ zlog.Fatal().Err(err).Msg("invalid cmd-dir")
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ // now we need to create the 'resource.syso' metadata file which contains versioninfo data
+
+ // lets start with sanitizing the version data
+ vParts, err := packaging.SanitizeVersion(*flagVersion)
+ if err != nil {
+ zlog.Fatal().Err(err).Msgf("invalid version: %s", *flagVersion)
+ os.Exit(1)
+ }
+
+ // then we need to create the manifest.xml file
+ manifestPath, err := writeManifestXML(vParts, *flagCmdDir)
+ if err != nil {
+ zlog.Fatal().Err(err).Msg("creating manifest.xml")
+ os.Exit(1)
+ }
+ if !*flagCreateResource {
+ defer os.Remove(manifestPath)
+ }
+
+ // now we can create the VersionInfo struct
+ targetIconPath := filepath.Join(*flagCmdDir, *flagIcon)
+ vi, err := createVersionInfo(vParts, targetIconPath, manifestPath)
+ if err != nil {
+ zlog.Fatal().Err(err).Msg("parsing versioninfo")
+ os.Exit(1)
+ }
+
+ // and finally we can write the 'resource.syso' file
+ vi.Build()
+ vi.Walk()
+
+ // resource.syso is the resource file that is going to be picked up by golang compiler
+ outPath := filepath.Join(*flagCmdDir, "resource.syso")
+ if err := vi.WriteSyso(outPath, "amd64"); err != nil {
+ zlog.Fatal().Err(err).Msg("creating syso file")
+ os.Exit(1)
+ }
+
+ // checking if build should be performed or not
+ if *flagCreateResource {
+ zlog.Info().Msgf("Successfully created resource file %s", outPath)
+ } else {
+
+ defer os.Remove(outPath)
+ // now we can build the binary
+ if err := buildTargetBinary(*flagCmdDir, *flagVersion, *flagOutputBinary); err != nil {
+ zlog.Fatal().Err(err).Msg("error building binary")
+ os.Exit(1)
+ }
+
+ zlog.Info().Msgf("Successfully built %s", *flagOutputBinary)
+ }
+}
+
+// createVersionInfo returns a VersionInfo struct pointer to be used to generate the 'resource.syso'
+// metadata file (see writeResourceSyso).
+func createVersionInfo(vParts []string, iconPath string, manifestPath string) (*goversioninfo.VersionInfo, error) {
+ vIntParts := make([]int, 0, len(vParts))
+ for _, p := range vParts {
+ v, err := strconv.Atoi(p)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing version part %s: %w", p, err)
+ }
+ vIntParts = append(vIntParts, v)
+ }
+ version := strings.Join(vParts, ".")
+ copyright := fmt.Sprintf("%d Fleet Device Management Inc.", time.Now().Year())
+
+ // Taken from https://github.com/josephspurrier/goversioninfo/blob/master/testdata/resource/versioninfo.json
+ langID, err := strconv.ParseUint("0409", 16, 16)
+ if err != nil {
+ return nil, errors.New("invalid LangID")
+ }
+ // Taken from https://github.com/josephspurrier/goversioninfo/blob/master/testdata/resource/versioninfo.json
+ charsetID, err := strconv.ParseUint("04B0", 16, 16)
+ if err != nil {
+ return nil, errors.New("invalid charsetID")
+ }
+
+ result := goversioninfo.VersionInfo{
+ FixedFileInfo: goversioninfo.FixedFileInfo{
+ FileVersion: goversioninfo.FileVersion{
+ Major: vIntParts[0],
+ Minor: vIntParts[1],
+ Patch: vIntParts[2],
+ Build: vIntParts[3],
+ },
+ ProductVersion: goversioninfo.FileVersion{
+ Major: vIntParts[0],
+ Minor: vIntParts[1],
+ Patch: vIntParts[2],
+ Build: vIntParts[3],
+ },
+ FileFlagsMask: "3f",
+ FileFlags: "00",
+ FileOS: "040004",
+ FileType: "01",
+ FileSubType: "00",
+ },
+ StringFileInfo: goversioninfo.StringFileInfo{
+ Comments: "Fleet osquery",
+ CompanyName: "Fleet Device Management (fleetdm.com)",
+ FileDescription: "Orbit osquery runtime and autoupdater",
+ FileVersion: version,
+ InternalName: "",
+ LegalCopyright: copyright,
+ LegalTrademarks: "",
+ OriginalFilename: "",
+ PrivateBuild: "",
+ ProductName: "Fleet osquery",
+ ProductVersion: version,
+ SpecialBuild: "",
+ },
+ VarFileInfo: goversioninfo.VarFileInfo{
+ Translation: goversioninfo.Translation{
+ LangID: goversioninfo.LangID(langID),
+ CharsetID: goversioninfo.CharsetID(charsetID),
+ },
+ },
+ IconPath: iconPath,
+ ManifestPath: manifestPath,
+ }
+
+ return &result, nil
+}
+
+// writeManifestXML creates the manifest.xml file used when generating the 'resource.syso' metadata
+// (see writeResourceSyso). Returns the path of the newly created file.
+func writeManifestXML(vParts []string, orbitPath string) (string, error) {
+ filePath := filepath.Join(orbitPath, "manifest.xml")
+
+ tmplOpts := struct {
+ Version string
+ }{
+ Version: strings.Join(vParts, "."),
+ }
+
+ var contents bytes.Buffer
+
+ if err := packaging.ManifestXMLTemplate.Execute(&contents, tmplOpts); err != nil {
+ return "", fmt.Errorf("parsing manifest.xml template: %w", err)
+ }
+
+ if err := ioutil.WriteFile(filePath, contents.Bytes(), constant.DefaultFileMode); err != nil {
+ return "", fmt.Errorf("writing manifest.xml file: %w", err)
+ }
+
+ return filePath, nil
+}
+
+// Build the target binary for Windows
+func buildTargetBinary(cmdDir string, version string, binaryPath string) error {
+ var buildExec *exec.Cmd
+
+ // convert relative to full output path
+ outputBinary, err := filepath.Abs(binaryPath)
+ if err != nil {
+ return fmt.Errorf("error converting binary path to absolute: %w", err)
+ }
+
+ // check if cmdDir contains desktop
+ // if it does, add -ldflags "-H=windowsgui" to exec.Command
+ if strings.Contains(cmdDir, "desktop") {
+ linkFlags := fmt.Sprintf("-H=windowsgui -X=main.version=%s", version)
+ buildExec = exec.Command("go", "build", "-ldflags", linkFlags, "-o", outputBinary)
+ } else {
+ linkFlags := fmt.Sprintf("-X=github.com/fleetdm/fleet/v4/orbit/pkg/build.Version=%s", version)
+ buildExec = exec.Command("go", "build", "-ldflags", linkFlags, "-o", outputBinary)
+ }
+ buildExec.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64")
+ buildExec.Stderr = os.Stderr
+ buildExec.Stdout = os.Stdout
+ buildExec.Dir = cmdDir
+
+ if err := buildExec.Run(); err != nil {
+ return fmt.Errorf("compile orbit: %w", err)
+ }
+ return nil
+}