mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 00:49:03 +00:00
macOS packaging works with launchd
This commit is contained in:
parent
b0ad96b228
commit
be3d70537b
5 changed files with 232 additions and 81 deletions
|
|
@ -55,6 +55,12 @@ func main() {
|
|||
Usage: "Disable TLS certificate verification",
|
||||
Destination: &opt.Insecure,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "service",
|
||||
Usage: "Install orbit/osquery with a persistence service (launchd, systemd, etc.)",
|
||||
Value: true,
|
||||
Destination: &opt.StartService,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "Enable debug logging",
|
||||
|
|
@ -68,8 +74,6 @@ func main() {
|
|||
}
|
||||
app.Action = func(c *cli.Context) error {
|
||||
if opt.FleetURL != "" || opt.EnrollSecret != "" {
|
||||
opt.StartService = true
|
||||
|
||||
if opt.FleetURL == "" || opt.EnrollSecret == "" {
|
||||
return errors.New("--enroll-secret and --fleet-url must be provided together")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,21 +36,6 @@ func BuildDeb(opt Options) error {
|
|||
return errors.Wrap(err, "create orbit dir")
|
||||
}
|
||||
|
||||
// Write files
|
||||
|
||||
if err := writeSystemdUnit(opt, filesystemRoot); err != nil {
|
||||
return errors.Wrap(err, "write systemd unit")
|
||||
}
|
||||
|
||||
if err := writeEnvFile(opt, filesystemRoot); err != nil {
|
||||
return errors.Wrap(err, "write env file")
|
||||
}
|
||||
|
||||
postInstallPath := filepath.Join(tmpDir, "postinstall.sh")
|
||||
if err := writePostInstall(opt, postInstallPath); err != nil {
|
||||
return errors.Wrap(err, "write postinstall script")
|
||||
}
|
||||
|
||||
// Initialize autoupdate metadata
|
||||
|
||||
localStore, err := filestore.New(filepath.Join(orbitRoot, "tuf-metadata.json"))
|
||||
|
|
@ -76,6 +61,21 @@ func BuildDeb(opt Options) error {
|
|||
}
|
||||
log.Debug().Str("path", osquerydPath).Msg("got osqueryd")
|
||||
|
||||
// Write files
|
||||
|
||||
if err := writeSystemdUnit(opt, filesystemRoot); err != nil {
|
||||
return errors.Wrap(err, "write systemd unit")
|
||||
}
|
||||
|
||||
if err := writeEnvFile(opt, filesystemRoot); err != nil {
|
||||
return errors.Wrap(err, "write env file")
|
||||
}
|
||||
|
||||
postInstallPath := filepath.Join(tmpDir, "postinstall.sh")
|
||||
if err := writePostInstall(opt, postInstallPath); err != nil {
|
||||
return errors.Wrap(err, "write postinstall script")
|
||||
}
|
||||
|
||||
// Pick up all file contents
|
||||
|
||||
contents := files.Contents{
|
||||
|
|
|
|||
|
|
@ -27,34 +27,18 @@ func BuildPkg(opt Options) error {
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
// TODO reenable
|
||||
//defer os.RemoveAll(tmpDir)
|
||||
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"))
|
||||
|
|
@ -80,6 +64,30 @@ func BuildPkg(opt Options) error {
|
|||
}
|
||||
log.Debug().Str("path", osquerydPath).Msg("got osqueryd")
|
||||
|
||||
// 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")
|
||||
}
|
||||
if err := writeScripts(opt, tmpDir); err != nil {
|
||||
return errors.Wrap(err, "write postinstall")
|
||||
}
|
||||
if opt.StartService {
|
||||
if err := writeLaunchd(opt, filesystemRoot); err != nil {
|
||||
return errors.Wrap(err, "write launchd")
|
||||
}
|
||||
}
|
||||
if err := copyFile(
|
||||
"./orbit",
|
||||
filepath.Join(filesystemRoot, "var", "lib", "fleet", "orbit", "orbit"),
|
||||
0755,
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "write orbit")
|
||||
}
|
||||
|
||||
// Build package
|
||||
if err := xarBom(opt, tmpDir); err != nil {
|
||||
return errors.Wrap(err, "build pkg")
|
||||
|
|
@ -95,6 +103,7 @@ func BuildPkg(opt Options) error {
|
|||
}
|
||||
|
||||
func writePackageInfo(opt Options, rootPath string) error {
|
||||
// PackageInfo is metadata for the pkg
|
||||
path := filepath.Join(rootPath, "flat", "base.pkg", "PackageInfo")
|
||||
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "mkdir")
|
||||
|
|
@ -112,7 +121,46 @@ func writePackageInfo(opt Options, rootPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func writeScripts(opt Options, rootPath string) error {
|
||||
// Postinstall script
|
||||
path := filepath.Join(rootPath, "scripts", "postinstall")
|
||||
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "mkdir")
|
||||
}
|
||||
|
||||
var contents bytes.Buffer
|
||||
if err := macosPostinstallTemplate.Execute(&contents, opt); err != nil {
|
||||
return errors.Wrap(err, "execute template")
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, contents.Bytes(), 0744); err != nil {
|
||||
return errors.Wrap(err, "write file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeLaunchd(opt Options, rootPath string) error {
|
||||
// launchd is the service mechanism on macOS
|
||||
path := filepath.Join(rootPath, "Library", "LaunchDaemons", "com.fleetdm.orbit.plist")
|
||||
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "mkdir")
|
||||
}
|
||||
|
||||
var contents bytes.Buffer
|
||||
if err := macosLaunchdTemplate.Execute(&contents, opt); err != nil {
|
||||
return errors.Wrap(err, "execute template")
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, contents.Bytes(), 0644); err != nil {
|
||||
return errors.Wrap(err, "write file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeDistribution(opt Options, rootPath string) error {
|
||||
// Distribution file is metadata for the pkg
|
||||
path := filepath.Join(rootPath, "flat", "Distribution")
|
||||
if err := os.MkdirAll(filepath.Dir(path), constant.DefaultDirMode); err != nil {
|
||||
return errors.Wrap(err, "mkdir")
|
||||
|
|
@ -136,52 +184,18 @@ 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")
|
||||
// Copy payload/scripts
|
||||
if err := cpio(
|
||||
filepath.Join(rootPath, "root"),
|
||||
filepath.Join(rootPath, "flat", "base.pkg", "Payload"),
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "cpio 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")
|
||||
if err := cpio(
|
||||
filepath.Join(rootPath, "scripts"),
|
||||
filepath.Join(rootPath, "flat", "base.pkg", "Scripts"),
|
||||
); err != nil {
|
||||
return errors.Wrap(err, "cpio Scripts")
|
||||
}
|
||||
|
||||
// Make bom
|
||||
|
|
@ -224,3 +238,56 @@ func xarBom(opt Options, rootPath string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cpio(srcPath, dstPath string) error {
|
||||
// This is the compression routine that is expected for pkg files.
|
||||
dst, err := os.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open dst")
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
cmdFind := exec.Command("find", ".")
|
||||
cmdFind.Dir = srcPath
|
||||
cmdCpio := exec.Command("cpio", "-o", "--format", "odc", "-R", "0:80")
|
||||
cmdCpio.Dir = srcPath
|
||||
cmdGzip := exec.Command("gzip", "-c")
|
||||
|
||||
// Pipes like this: find | cpio | gzip > dstPath
|
||||
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 = dst
|
||||
|
||||
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 = dst.Close()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "close dst")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@ import "text/template"
|
|||
// 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>
|
||||
<scripts>
|
||||
<postinstall file="./postinstall"/>
|
||||
</scripts>
|
||||
<bundle-version>
|
||||
</bundle-version>
|
||||
</pkg-info>
|
||||
`))
|
||||
|
||||
|
|
@ -26,3 +29,48 @@ var macosDistributionTemplate = template.Must(template.New("").Option("missingke
|
|||
<pkg-ref id="{{.Identifier}}.base.pkg" version="{{.Version}}" auth="root">#base.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
`))
|
||||
|
||||
var macosPostinstallTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
|
||||
`#!/bin/bash
|
||||
|
||||
ln -sf /var/lib/fleet/orbit/orbit /usr/local/bin/orbit
|
||||
|
||||
{{ if .StartService -}}
|
||||
launchctl stop com.fleetdm.orbit
|
||||
|
||||
sleep 3
|
||||
|
||||
launchctl unload /Library/LaunchDaemons/com.fleetdm.orbit.plist
|
||||
launchctl load /Library/LaunchDaemons/com.fleetdm.orbit.plist
|
||||
{{- end }}
|
||||
`))
|
||||
|
||||
// TODO set Nice?
|
||||
var macosLaunchdTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.fleetdm.orbit</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/var/lib/fleet/orbit/orbit</string>
|
||||
</array>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/orbit/orbit.stdout.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/orbit/orbit.stderr.log</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
{{ if .Insecure }}<key>ORBIT_INSECURE</key><string>true</string>{{ end }}
|
||||
{{ if .FleetURL }}<key>ORBIT_FLEET_URL</key><string>{{.FleetURL}}</string>{{ end }}
|
||||
{{ if .EnrollSecret }}<key>ORBIT_ENROLL_SECRET</key><string>{{.EnrollSecret}}</string>{{ end }}
|
||||
</dict>
|
||||
<key>KeepAlive</key><true/>
|
||||
<key>RunAtLoad</key><true/>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
`))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
// package packaging provides tools for buildin Orbit installation packages.
|
||||
package packaging
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Options are the configurable options provided for the package.
|
||||
type Options struct {
|
||||
// FleetURL is the URL to the Fleet server.
|
||||
|
|
@ -18,3 +26,27 @@ type Options struct {
|
|||
// Insecure enables insecure TLS connections for the generated package.
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
func copyFile(srcPath, dstPath string, perm os.FileMode) error {
|
||||
src, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open src for copy")
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
|
||||
return errors.Wrap(err, "create dst dir for copy")
|
||||
}
|
||||
|
||||
dst, err := os.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, perm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open dst for copy")
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
return errors.Wrap(err, "copy src to dst")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue