fleet/pkg/packaging/macos.go

227 lines
6.4 KiB
Go
Raw Normal View History

2021-02-09 03:23:50 +00:00
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
}