mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
Partial progress on macOS packaging
This commit is contained in:
parent
967d812bc2
commit
b0ad96b228
5 changed files with 286 additions and 4 deletions
|
|
@ -23,6 +23,11 @@ func main() {
|
|||
app.Usage = "A powered-up, (near) drop-in replacement for osquery"
|
||||
app.Commands = []*cli.Command{}
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
Usage: "Type of package to build",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "enroll-secret",
|
||||
Usage: "Enroll secret for authenticating to Fleet server",
|
||||
|
|
@ -33,6 +38,18 @@ func main() {
|
|||
Usage: "URL (host:port) of Fleet server",
|
||||
Destination: &opt.FleetURL,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "identifier",
|
||||
Usage: "Identifier for package product",
|
||||
Value: "com.fleetdm.orbit",
|
||||
Destination: &opt.Identifier,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "version",
|
||||
Usage: "Version for package product",
|
||||
Value: "0.0.1",
|
||||
Destination: &opt.Version,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "insecure",
|
||||
Usage: "Disable TLS certificate verification",
|
||||
|
|
@ -58,7 +75,14 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
return packaging.BuildDeb(opt)
|
||||
switch c.String("type") {
|
||||
case "pkg":
|
||||
return packaging.BuildPkg(opt)
|
||||
case "deb":
|
||||
return packaging.BuildDeb(opt)
|
||||
default:
|
||||
return errors.New("type must be one of ('pkg', 'deb')")
|
||||
}
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ func BuildDeb(opt Options) error {
|
|||
defer os.RemoveAll(tmpDir)
|
||||
log.Debug().Str("path", tmpDir).Msg("created temp dir")
|
||||
|
||||
filesystemRoot := filepath.Join(tmpDir, "filesystem")
|
||||
filesystemRoot := filepath.Join(tmpDir, "root")
|
||||
if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "create filesystem dir")
|
||||
return errors.Wrap(err, "create root dir")
|
||||
}
|
||||
orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "fleet", "orbit")
|
||||
if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil {
|
||||
|
|
@ -113,7 +113,7 @@ func BuildDeb(opt Options) error {
|
|||
|
||||
info := &nfpm.Info{
|
||||
Name: "orbit-osquery",
|
||||
Version: "0.0.1",
|
||||
Version: opt.Version,
|
||||
Description: "Osquery launcher and autoupdater",
|
||||
Arch: "amd64",
|
||||
Maintainer: "FleetDM Engineers <engineering@fleetdm.com>",
|
||||
|
|
|
|||
226
pkg/packaging/macos.go
Normal file
226
pkg/packaging/macos.go
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package packaging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/fleetdm/orbit/pkg/constant"
|
||||
"github.com/fleetdm/orbit/pkg/update"
|
||||
"github.com/fleetdm/orbit/pkg/update/filestore"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// See helful docs in http://bomutils.dyndns.org/tutorial.html
|
||||
|
||||
// BuildPkg builds a macOS .pkg. So far this is tested only on macOS but in theory it works with bomutils on
|
||||
// Linux.
|
||||
func BuildPkg(opt Options) error {
|
||||
// Initialize directories
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "orbit-package")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
// TODO reenable
|
||||
//defer os.RemoveAll(tmpDir)
|
||||
log.Debug().Str("path", tmpDir).Msg("created temp dir")
|
||||
|
||||
filesystemRoot := filepath.Join(tmpDir, "root")
|
||||
if err := os.MkdirAll(filesystemRoot, constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "create root dir")
|
||||
}
|
||||
// if err := os.MkdirAll(
|
||||
// filepath.Join(filesystemRoot, "Resources", "en.lproj"),
|
||||
// constant.DefaultDirMode,
|
||||
// ); err != nil {
|
||||
// return errors.Wrap(err, "create resources dir")
|
||||
// }
|
||||
orbitRoot := filepath.Join(filesystemRoot, "var", "lib", "fleet", "orbit")
|
||||
if err := os.MkdirAll(orbitRoot, constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "create orbit dir")
|
||||
}
|
||||
|
||||
// Write files
|
||||
|
||||
if err := writePackageInfo(opt, tmpDir); err != nil {
|
||||
return errors.Wrap(err, "write PackageInfo")
|
||||
}
|
||||
if err := writeDistribution(opt, tmpDir); err != nil {
|
||||
return errors.Wrap(err, "write Distribution")
|
||||
}
|
||||
|
||||
// Initialize autoupdate metadata
|
||||
|
||||
localStore, err := filestore.New(filepath.Join(orbitRoot, "tuf-metadata.json"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create local metadata store")
|
||||
}
|
||||
updateOpt := update.DefaultOptions
|
||||
updateOpt.RootDirectory = orbitRoot
|
||||
updateOpt.ServerURL = "https://tuf.fleetctl.com"
|
||||
updateOpt.LocalStore = localStore
|
||||
updateOpt.Platform = "macos"
|
||||
|
||||
updater, err := update.New(updateOpt)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to init updater")
|
||||
}
|
||||
if err := updater.UpdateMetadata(); err != nil {
|
||||
return errors.Wrap(err, "failed to update metadata")
|
||||
}
|
||||
osquerydPath, err := updater.Get("osqueryd", "stable")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get osqueryd")
|
||||
}
|
||||
log.Debug().Str("path", osquerydPath).Msg("got osqueryd")
|
||||
|
||||
// Build package
|
||||
if err := xarBom(opt, tmpDir); err != nil {
|
||||
return errors.Wrap(err, "build pkg")
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("orbit-osquery_%s_amd64.pkg", opt.Version)
|
||||
if err := os.Rename(filepath.Join(tmpDir, "orbit.pkg"), filename); err != nil {
|
||||
return errors.Wrap(err, "rename pkg")
|
||||
}
|
||||
log.Info().Str("path", filename).Msg("wrote pkg package")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writePackageInfo(opt Options, rootPath string) error {
|
||||
path := filepath.Join(rootPath, "flat", "base.pkg", "PackageInfo")
|
||||
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "mkdir")
|
||||
}
|
||||
|
||||
var contents bytes.Buffer
|
||||
if err := macosPackageInfoTemplate.Execute(&contents, opt); err != nil {
|
||||
return errors.Wrap(err, "execute template")
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, contents.Bytes(), constant.DefaultFileMode); err != nil {
|
||||
return errors.Wrap(err, "write file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeDistribution(opt Options, rootPath string) error {
|
||||
path := filepath.Join(rootPath, "flat", "Distribution")
|
||||
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "mkdir")
|
||||
}
|
||||
|
||||
var contents bytes.Buffer
|
||||
if err := macosDistributionTemplate.Execute(&contents, opt); err != nil {
|
||||
return errors.Wrap(err, "execute template")
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, contents.Bytes(), constant.DefaultFileMode); err != nil {
|
||||
return errors.Wrap(err, "write file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// xarBom creates the actual .pkg format. It's a xar archive with a BOM (Bill of
|
||||
// materials?). See http://bomutils.dyndns.org/tutorial.html.
|
||||
func xarBom(opt Options, rootPath string) error {
|
||||
// Adapted from BSD licensed
|
||||
// https://github.com/go-flutter-desktop/hover/blob/v0.46.2/cmd/packaging/darwin-pkg.go
|
||||
|
||||
// Copy payload
|
||||
payload, err := os.OpenFile(filepath.Join(rootPath, "flat", "base.pkg", "Payload"), os.O_RDWR|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open payload")
|
||||
}
|
||||
|
||||
cmdFind := exec.Command("find", ".")
|
||||
cmdFind.Dir = filepath.Join(rootPath, "root")
|
||||
cmdCpio := exec.Command("cpio", "-o", "--format", "odc", "-R", "0:80")
|
||||
cmdCpio.Dir = filepath.Join(rootPath, "root")
|
||||
cmdGzip := exec.Command("gzip", "-c")
|
||||
|
||||
// Pipes like this: find | cpio | gzip > Payload
|
||||
cmdCpio.Stdin, err = cmdFind.StdoutPipe()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "pipe cpio")
|
||||
}
|
||||
cmdGzip.Stdin, err = cmdCpio.StdoutPipe()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "pipe gzip")
|
||||
}
|
||||
cmdGzip.Stdout = payload
|
||||
|
||||
err = cmdGzip.Start()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "start gzip")
|
||||
}
|
||||
err = cmdCpio.Start()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "start cpio")
|
||||
}
|
||||
err = cmdFind.Run()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "run find")
|
||||
}
|
||||
err = cmdCpio.Wait()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "wait cpio")
|
||||
}
|
||||
err = cmdGzip.Wait()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "wait gzip")
|
||||
}
|
||||
err = payload.Close()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "close payload")
|
||||
}
|
||||
|
||||
// Make bom
|
||||
var cmdMkbom *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
cmdMkbom = exec.Command("mkbom", filepath.Join(rootPath, "root"), filepath.Join("flat", "base.pkg", "Bom"))
|
||||
case "linux":
|
||||
cmdMkbom = exec.Command("mkbom", "-u", "0", "-g", "80", filepath.Join(rootPath, "flat", "root"), filepath.Join("flat", "base.pkg", "Bom"))
|
||||
}
|
||||
cmdMkbom.Dir = rootPath
|
||||
cmdMkbom.Stdout = os.Stdout
|
||||
cmdMkbom.Stderr = os.Stderr
|
||||
if err := cmdMkbom.Run(); err != nil {
|
||||
return errors.Wrap(err, "mkbom")
|
||||
}
|
||||
|
||||
// List files for xar
|
||||
var files []string
|
||||
if err := filepath.Walk(filepath.Join(rootPath, "flat"), func(path string, info os.FileInfo, err error) error {
|
||||
relativePath, err := filepath.Rel(filepath.Join(rootPath, "flat"), path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files = append(files, relativePath)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "iterate files")
|
||||
}
|
||||
|
||||
// Make xar
|
||||
cmdXar := exec.Command("xar", append([]string{"--compression", "none", "-cf", filepath.Join("..", "orbit.pkg")}, files...)...)
|
||||
cmdXar.Dir = filepath.Join(rootPath, "flat")
|
||||
cmdXar.Stdout = os.Stdout
|
||||
cmdXar.Stderr = os.Stderr
|
||||
|
||||
if err := cmdXar.Run(); err != nil {
|
||||
return errors.Wrap(err, "run xar")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
28
pkg/packaging/macos_templates.go
Normal file
28
pkg/packaging/macos_templates.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package packaging
|
||||
|
||||
import "text/template"
|
||||
|
||||
// Best reference I could find:
|
||||
// http://s.sudre.free.fr/Stuff/Ivanhoe/FLAT.html
|
||||
var macosPackageInfoTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
|
||||
`<pkg-info format-version="2" identifier="{{.Identifier}}.base.pkg" version="{{.Version}}" install-location="/" auth="root">
|
||||
<bundle-version>
|
||||
</bundle-version>
|
||||
</pkg-info>
|
||||
`))
|
||||
|
||||
// Reference:
|
||||
// https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html
|
||||
var macosDistributionTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
|
||||
`<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-gui-script minSpecVersion="2">
|
||||
<title>Orbit</title>
|
||||
<choices-outline>
|
||||
<line choice="choiceBase"/>
|
||||
</choices-outline>
|
||||
<choice id="choiceBase" title="base">
|
||||
<pkg-ref id="{{.Identifier}}.base.pkg"/>
|
||||
</choice>
|
||||
<pkg-ref id="{{.Identifier}}.base.pkg" version="{{.Version}}" auth="root">#base.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
`))
|
||||
|
|
@ -8,6 +8,10 @@ type Options struct {
|
|||
// EnrollSecret is the enroll secret used to authenticate to the Fleet
|
||||
// server.
|
||||
EnrollSecret string
|
||||
// Version is the version number for this package.
|
||||
Version string
|
||||
// Identifier is the identifier (eg. com.fleetdm.orbit) for the package product.
|
||||
Identifier string
|
||||
// StartService is a boolean indicating whether to start a system-specific
|
||||
// background service.
|
||||
StartService bool
|
||||
|
|
|
|||
Loading…
Reference in a new issue