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 +}