diff --git a/Launchd Package Creator.xcodeproj/project.xcworkspace/xcuserdata/ryan_ball.xcuserdatad/UserInterfaceState.xcuserstate b/Launchd Package Creator.xcodeproj/project.xcworkspace/xcuserdata/ryan_ball.xcuserdatad/UserInterfaceState.xcuserstate
deleted file mode 100644
index d97fd9f..0000000
Binary files a/Launchd Package Creator.xcodeproj/project.xcworkspace/xcuserdata/ryan_ball.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ
diff --git a/Launchd Package Creator/AppDelegate.swift b/Launchd Package Creator/AppDelegate.swift
index 3f9689f..ec2d625 100644
--- a/Launchd Package Creator/AppDelegate.swift
+++ b/Launchd Package Creator/AppDelegate.swift
@@ -1,6 +1,6 @@
//
// AppDelegate.swift
-// simple-launchdaemon-creator
+// launchd-package-creator
//
// Created by Ryan Ball on 4/11/19.
// Copyright © 2019 Ryan Ball. All rights reserved.
diff --git a/Launchd Package Creator/Base.lproj/Main.storyboard b/Launchd Package Creator/Base.lproj/Main.storyboard
index 000404e..c291861 100644
--- a/Launchd Package Creator/Base.lproj/Main.storyboard
+++ b/Launchd Package Creator/Base.lproj/Main.storyboard
@@ -441,11 +441,11 @@
-
+
-
+
@@ -454,7 +454,7 @@
-
+
@@ -463,7 +463,7 @@
-
+
@@ -472,7 +472,7 @@
-
+
@@ -481,7 +481,7 @@
-
+
@@ -490,7 +490,7 @@
-
+
@@ -499,7 +499,7 @@
-
+
@@ -508,7 +508,7 @@
-
+
-
+
-
+
@@ -555,7 +555,7 @@
-
+
@@ -564,7 +564,7 @@
-
+
@@ -573,7 +573,7 @@
-
+
@@ -582,7 +582,7 @@
-
+
@@ -646,7 +646,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -665,7 +711,10 @@
+
+
+
@@ -677,7 +726,7 @@
-
+
diff --git a/Launchd Package Creator/ViewController.swift b/Launchd Package Creator/ViewController.swift
index ccc6494..e602894 100644
--- a/Launchd Package Creator/ViewController.swift
+++ b/Launchd Package Creator/ViewController.swift
@@ -1,6 +1,6 @@
//
// ViewController.swift
-// simple-launchdaemon-creator
+// launchd-package-creator
//
// Created by Ryan Ball on 4/11/19.
// Copyright © 2019 Ryan Ball. All rights reserved.
@@ -8,6 +8,7 @@
import Cocoa
import Foundation
+import AppKit
var globalIdentifier: String = ""
var globalProgramArgs: String = ""
@@ -24,7 +25,9 @@ var globalDaemonFolderName: String = ""
var globalStandardOutPath: String? = nil
var globalStandardErrorPath: String? = nil
var globalPkgTempLocation: String = ""
-var optionalRequiredFields = [String]()
+var globalSessionType: String? = nil
+var emptyRequiredFields = [String]()
+var usingApp: Bool = false
class ViewController: NSViewController {
@@ -42,16 +45,22 @@ class ViewController: NSViewController {
@IBOutlet weak var standardErrorPathCheck: NSButton!
@IBOutlet weak var standardErrorPathField: NSTextField!
@IBOutlet weak var clearButton: NSButton!
+ @IBOutlet weak var sessionTypeOptions: NSPopUpButton!
+ @IBOutlet weak var sessionTypeCheck: NSButton!
+ @IBOutlet weak var programArgsOptLabel: NSTextField!
// Create arrays of fields/buttons to clear or disable when using the "Clear" button
lazy var fieldsToClear: [NSTextField] = [self.daemonIdentifier, self.daemonVersion, self.programArgs, self.targetPath, self.startIntervalSeconds, self.standardOutPathField, self.standardErrorPathField]
- lazy var buttonsToClear: [NSButton] = [self.runAtLoad, self.startInterval, self.standardOutPathCheck, self.standardErrorPathCheck]
+ lazy var buttonsToClear: [NSButton] = [self.runAtLoad, self.startInterval, self.standardOutPathCheck, self.standardErrorPathCheck, self.sessionTypeCheck, self.sessionTypeOptions]
lazy var fieldsToDisable: [NSTextField] = [self.startIntervalSeconds, self.standardOutPathField, self.standardErrorPathField]
- lazy var requiredFields: [NSTextField] = [self.daemonIdentifier, self.daemonVersion, self.targetPath]
+ lazy var alwaysRequiredFields: [NSTextField] = [self.daemonIdentifier, self.daemonVersion, self.targetPath]
+ var sessionTypes: Array = [""]
+
+ // Function to display a dialog with an OK button
func dialogOK(title: String, message: String) -> Void {
let dialogOK: NSAlert = NSAlert()
dialogOK.messageText = title
@@ -62,38 +71,68 @@ class ViewController: NSViewController {
return
}
+ func fileSaveDialog(title: String, allowedFileTypes: Array, source: String) {
+ // Open the File Save dialog box
+ let fileSaveDialog = NSSavePanel();
+ fileSaveDialog.message = title;
+ fileSaveDialog.showsResizeIndicator = true;
+ fileSaveDialog.showsHiddenFiles = false;
+ fileSaveDialog.showsTagField = false;
+ fileSaveDialog.canCreateDirectories = true;
+ fileSaveDialog.allowedFileTypes = allowedFileTypes;
+ fileSaveDialog.allowsOtherFileTypes = false;
+ fileSaveDialog.beginSheetModal(for: self.view.window!) { (result) in
+ if result == NSApplication.ModalResponse.OK {
+ let result = fileSaveDialog.url // Pathname of the file
+
+ if (result != nil) {
+ let selectedPath = result!.path
+ //let destination = path
+ _ = extras().copyFile(source: source, destination: selectedPath)
+ } else {
+ // User clicked on "Cancel"
+ return
+ }
+ }
+ }
+ }
+
// Function that checks for empty required fields
func checkRequiredFields() -> Bool {
- optionalRequiredFields = []
+ emptyRequiredFields = []
- // Determine our required fields
- for field in requiredFields {
+ // Determine which of our required fields are empty, if so add to our emptyRequiredFields array
+ for field in alwaysRequiredFields {
if field.stringValue.isEmpty {
- optionalRequiredFields.append(field.toolTip!)
+ emptyRequiredFields.append(field.toolTip!)
}
}
- // If there are options selected, add their values as required
+ // If options are selected, make sure their associated values are populated, if not add to our emptyRequiredFields array
if startInterval.state == NSControl.StateValue.on && startIntervalSeconds.stringValue.isEmpty {
- optionalRequiredFields.append(startIntervalSeconds.toolTip!)
+ emptyRequiredFields.append(startIntervalSeconds.toolTip!)
}
if standardOutPathCheck.state == NSControl.StateValue.on && standardOutPathField.stringValue.isEmpty {
- optionalRequiredFields.append(standardOutPathField.toolTip!)
+ emptyRequiredFields.append(standardOutPathField.toolTip!)
}
if standardErrorPathCheck.state == NSControl.StateValue.on && standardErrorPathField.stringValue.isEmpty {
- optionalRequiredFields.append(standardErrorPathField.toolTip!)
+ emptyRequiredFields.append(standardErrorPathField.toolTip!)
+ }
+
+ if programArgs.isEnabled == true && programArgs.stringValue.isEmpty {
+ emptyRequiredFields.append(programArgs.toolTip!)
}
let dialogText = """
Please complete all of the required fields to continue:
- \(optionalRequiredFields.joined(separator: "\n"))
+ \(emptyRequiredFields.joined(separator: "\n"))
"""
- if !optionalRequiredFields.isEmpty {
+ if !emptyRequiredFields.isEmpty {
dialogOK(title: "Fields Empty", message: dialogText)
return true
} else {
@@ -113,28 +152,38 @@ class ViewController: NSViewController {
func determineProgramArgs (userSelectedTarget:String) -> String {
let targetExtension = NSURL(fileURLWithPath: userSelectedTarget).pathExtension
+ // Make sure the program args field is enabled initially
+ programArgs.isEnabled = true
if targetExtension == "" {
+ // We asume the target is a binary (no extension) so disable the program args field
programArgs.isEnabled = false
programArgs.stringValue = ""
+ programArgsOptLabel.isHidden = false
} else if targetExtension == "app" {
- optionalRequiredFields.append(programArgs.toolTip!)
+ // Target has a .app extensin and use set /usr/bin/open as the program args
programArgs.stringValue = String("/usr/bin/open");
+ usingApp = true
+ programArgsOptLabel.isHidden = true
} else {
do {
- optionalRequiredFields.append(programArgs.toolTip!)
+ programArgsOptLabel.isHidden = true
+ // We attempt to look at the shebang of the file to determine program args
let targetContents = try String(contentsOfFile: userSelectedTarget)
let linesOfTarget = targetContents.components(separatedBy: "\n")
let shebang = String(linesOfTarget[0]);
if shebang.contains ("bash") {
+ // shebang contains bash
programArgs.stringValue = String("/bin/bash");
} else if shebang.contains ("sh") {
+ // shebang contains sh
programArgs.stringValue = String("/bin/sh");
} else if shebang.contains ("python") {
+ // shebang contains python
programArgs.stringValue = String("/usr/bin/python");
} else {
- dialogOK(title: "Missing shebang", message: "Your script does not have a proper shebang as the first line. Either add a shebang into the script or manually configure the Program Arguments.")
- targetPath.stringValue = ""
+ // Either no shebang or we don't know how to deal with the shebang in the file
+ dialogOK(title: "Missing shebang", message: "Your script does not have a proper shebang as the first line. Either add a shebang into the script and try again or manually configure the Program Arguments.")
}
} catch {
// bad things happened
@@ -143,11 +192,17 @@ class ViewController: NSViewController {
return programArgs.stringValue
}
-
@IBAction func daemonRadioAction(_ sender: Any) {
if daemonButton.state == NSControl.StateValue.on {
agentButton.state = NSControl.StateValue.off
globalDaemonType = "daemon"
+
+ // Populate LimitLoadToSessionType PopUp Button
+ let sessionTypes: Array = ["System"]
+ sessionTypeOptions.removeAllItems()
+ for sessionType in sessionTypes {
+ sessionTypeOptions.addItem(withTitle: sessionType)
+ }
}
}
@@ -155,6 +210,13 @@ class ViewController: NSViewController {
if agentButton.state == NSControl.StateValue.on {
daemonButton.state = NSControl.StateValue.off
globalDaemonType = "agent"
+
+ // Populate LimitLoadToSessionType PopUp Button
+ let sessionTypes: Array = ["Aqua", "Background", "LoginWindow", "StandardIO"]
+ sessionTypeOptions.removeAllItems()
+ for sessionType in sessionTypes {
+ sessionTypeOptions.addItem(withTitle: sessionType)
+ }
}
}
@@ -214,6 +276,19 @@ class ViewController: NSViewController {
}
}
+ @IBAction func sessionTypeCheckAction(_ sender: Any) {
+ if sessionTypeCheck.state == NSControl.StateValue.on {
+ sessionTypeOptions.isEnabled = true
+ } else {
+ sessionTypeOptions.isEnabled = false
+ sessionTypeOptions.selectItem(at: 0)
+ }
+ }
+
+ @IBAction func viewPlistButtonAction(_ sender: Any) {
+ mostOfTheDaemonStuff(buildType: "Plist")
+ }
+
// Actions to take when the "Clear" button is pressed
@IBAction func clearButtonAction(_ sender: Any) {
@@ -222,42 +297,71 @@ class ViewController: NSViewController {
for field in self.fieldsToDisable { field.isEnabled = false }
daemonButton.state = NSControl.StateValue.on
agentButton.state = NSControl.StateValue.off
+ sessionTypeOptions.isEnabled = false
+ sessionTypeOptions.selectItem(at: 0)
+ programArgsOptLabel.isHidden = true
+
+ // Populate LimitLoadToSessionType PopUp Button
+ let sessionTypes: Array = ["System"]
+ sessionTypeOptions.removeAllItems()
+ for sessionType in sessionTypes {
+ sessionTypeOptions.addItem(withTitle: sessionType)
+ }
+
self.daemonIdentifier.becomeFirstResponder()
}
- @IBAction func createDaemon(_ sender: AnyObject) {
-
+ func mostOfTheDaemonStuff(buildType: String) {
let anyRequredFieldsEmpty = checkRequiredFields()
let startIntervalFieldIsValid = verifyStartInterval(value: startIntervalSeconds.stringValue)
- if anyRequredFieldsEmpty == false || startIntervalFieldIsValid == true {
+ // If no required fields are empty and the start interval field is valid then continue
+ if anyRequredFieldsEmpty == false && startIntervalFieldIsValid == true {
globalIdentifier = daemonIdentifier.stringValue
globalVersion = daemonVersion.stringValue
globalProgramArgs = programArgs.stringValue
globalTargetPath = targetPath.stringValue
+
+ // Determine state of runAtLoad Checkbox, populate value for plist
if runAtLoad.state == NSControl.StateValue.on {
globalRunAtLoad = true
} else {
globalRunAtLoad = nil
}
+
+ // Determine state of startInterval checkbox, populate value for plist
if startInterval.state == NSControl.StateValue.on {
globalStartInterval = startIntervalSeconds.integerValue
} else {
globalStartInterval = nil
}
+
+ // Determine if user has selected LaunchDaemon or LaunchAgent, set a global type so we know which directory to place the plist
if globalDaemonType == "agent" {
globalDaemonFolderName = "LaunchAgents"
} else {
globalDaemonFolderName = "LaunchDaemons"
}
+
+ // Determine state of standardOutPath checkbox, populate value for plist
if standardOutPathCheck.state == NSControl.StateValue.on {
globalStandardOutPath = standardOutPathField.stringValue
}
+
+ // Determine state of standardErrorPath checkbox, populate value for plist
if standardErrorPathCheck.state == NSControl.StateValue.on {
globalStandardErrorPath = standardErrorPathField.stringValue
}
+ // Determine state of sessionType checkbox, populate value for plist
+ if sessionTypeCheck.state == NSControl.StateValue.on {
+ globalSessionType = sessionTypeOptions.titleOfSelectedItem
+ } else {
+ globalSessionType = nil
+ }
+
+ // Determine the filename of the target app/script, build the program args array for plist
globalTargetPathFileName = (globalTargetPath as NSString).lastPathComponent
if programArgs.stringValue != "" {
globalProgramArgsFull = [globalProgramArgs, "/Library/Scripts/\(globalTargetPathFileName)"]
@@ -265,40 +369,76 @@ class ViewController: NSViewController {
globalProgramArgsFull = ["/Library/Scripts/\(globalTargetPathFileName)"]
}
- create_daemon().build()
-
- let fileSaveDialog = NSSavePanel();
-
- fileSaveDialog.message = "Choose a save location for the packaged \(globalDaemonType).";
- fileSaveDialog.showsResizeIndicator = true;
- fileSaveDialog.showsHiddenFiles = false;
- fileSaveDialog.showsTagField = false;
- fileSaveDialog.canCreateDirectories = true;
- fileSaveDialog.allowedFileTypes = ["pkg"];
- fileSaveDialog.allowsOtherFileTypes = false;
- fileSaveDialog.beginSheetModal(for: self.view.window!) { (result) in
- if result == NSApplication.ModalResponse.OK {
- let result = fileSaveDialog.url // Pathname of the file
-
- if (result != nil) {
- let path = result!.path
- globalDestinationPath = path
- _ = extras().copyFile(source: globalPkgTempLocation, destination: globalDestinationPath)
- } else {
- // User clicked on "Cancel"
+ // Let the user know LaunchAgent/LimitLoadToSessionType: Aqua is preferred for .apps
+ if (usingApp == true) && (globalDaemonType == "agent") && (globalSessionType != "Aqua") || (usingApp == true) && (globalDaemonType == "daemon") {
+
+ // Display the alert
+ let dialogCustomButton = NSAlert()
+ dialogCustomButton.messageText = "Are you sure?"
+ dialogCustomButton.informativeText = "When targeting a GUI application it is recommended to use a LaunchAgent with LimitLoadToSessionType: Aqua"
+ dialogCustomButton.alertStyle = .warning
+ dialogCustomButton.addButton(withTitle: "Create \(buildType) Anyway")
+ dialogCustomButton.addButton(withTitle: "Cancel")
+ // Include "Do not show this message again" option in future release
+ // dialogCustomButton.showsSuppressionButton = true
+ dialogCustomButton.beginSheetModal(for: self.view.window!, completionHandler: { (NSModalResponse) -> Void in
+ if (NSModalResponse == NSApplication.ModalResponse.alertSecondButtonReturn) {
+ // User clicked the cancel button
return
+ } else if (NSModalResponse == NSApplication.ModalResponse.alertFirstButtonReturn) {
+ // User clicked the continue button
+ create_daemon().build(buildType: buildType)
+ // If the intention is to build a PKG, then show the save dialog
+ //if buildType == "PKG" { _ = self.fileSaveDialog() }
+ //if buildType == "Plist" { NSWorkspace.shared.openFile(preferencesURL.path, withApplication: "TextEdit") }
+ if buildType == "Plist" {
+ self.fileSaveDialog(title: "Choose a save location for the launchd plist.", allowedFileTypes: ["plist"], source: preferencesURL.path)
+ }
+ if buildType == "PKG" {
+ self.fileSaveDialog(title: "Choose a save location for the packaged \(globalDaemonType).", allowedFileTypes: ["pkg"], source: globalPkgTempLocation)
+ }
}
+ })
+ } else {
+ create_daemon().build(buildType: buildType)
+ // If the intention is to build a PKG, then show the save dialog
+ //if buildType == "PKG" { _ = self.fileSaveDialog() }
+ //if buildType == "Plist" { NSWorkspace.shared.openFile(preferencesURL.path, withApplication: "TextEdit") }
+ if buildType == "Plist" {
+ self.fileSaveDialog(title: "Choose a save location for the launchd plist.", allowedFileTypes: ["plist"], source: preferencesURL.path)
+ }
+ if buildType == "PKG" {
+ self.fileSaveDialog(title: "Choose a save location for the packaged \(globalDaemonType).", allowedFileTypes: ["pkg"], source: globalPkgTempLocation)
}
}
+ } else {
+ return
}
}
+ @IBAction func createDaemon(_ sender: AnyObject) {
+ // This will build the PKG and prompt for a save location
+ mostOfTheDaemonStuff(buildType: "PKG")
+ }
+
override func viewDidLoad() {
super.viewDidLoad()
globalDaemonType = "daemon"
+ programArgsOptLabel.isHidden = true
startIntervalSeconds.isEnabled = false
standardOutPathField.isEnabled = false
standardErrorPathField.isEnabled = false
+ sessionTypeOptions.isEnabled = false
+
+ // Populate LimitLoadToSessionType PopUp Button
+ let sessionTypes: Array = ["System"]
+ sessionTypeOptions.autoenablesItems = false
+ sessionTypeOptions.removeAllItems()
+ for sessionType in sessionTypes {
+ sessionTypeOptions.addItem(withTitle: sessionType)
+ }
+
+ // Set focus to Identifier field
self.daemonIdentifier.becomeFirstResponder()
}
@@ -307,5 +447,4 @@ class ViewController: NSViewController {
// Update the view, if already loaded.
}
}
-
}
diff --git a/Launchd Package Creator/create_daemon.swift b/Launchd Package Creator/create_daemon.swift
index 5e9f13c..0a2e99d 100644
--- a/Launchd Package Creator/create_daemon.swift
+++ b/Launchd Package Creator/create_daemon.swift
@@ -1,14 +1,15 @@
//
// create_daemon.swift
-// simple-launchdaemon-creator
+// launchd-package-creator
//
// Created by Ryan Ball on 4/23/19.
// Copyright © 2019 Ryan Ball. All rights reserved.
//
-// import Cocoa
import Foundation
+var preferencesURL: URL!
+
struct Preferences: Codable {
// Items that are required in the plist
var Label: String
@@ -19,21 +20,36 @@ struct Preferences: Codable {
var StartInterval: Int?
var StandardOutPath: String?
var StandardErrorPath: String?
+ var LimitLoadToSessionType: String?
+}
+
+struct ComponentPlistRoot : Codable {
+ let array : [ComponentPlist]
}
-public class create_daemon {
+struct ComponentPlist: Codable {
+ var BundleIsRelocatable: Bool
+ var BundleIsVersionChecked: Bool
+ var BundleOverwriteAction: String
+ var RootRelativeBundlePath: String
+}
+
+public class create_daemon: NSObject {
var uuid: String = ""
var tempBuildDir: String = ""
var baseTempDir: URL!
var sessionTempDir: URL!
+ var componentPlistURL: URL!
- public func build() {
+ public func build(buildType: String) {
uuid = NSUUID().uuidString
tempBuildDir = "com.github.ryangball.launchd-package-creator/\(uuid)"
baseTempDir = URL(fileURLWithPath: NSTemporaryDirectory())
sessionTempDir = baseTempDir.appendingPathComponent(tempBuildDir, isDirectory: true)
+ preferencesURL = sessionTempDir.appendingPathComponent("/root/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist")
+ componentPlistURL = sessionTempDir.appendingPathComponent("/build/component.plist")
let subPaths = [
"root/Library/\(globalDaemonFolderName)",
@@ -53,21 +69,20 @@ public class create_daemon {
}
// Create the pkg postinstall script
- let postInstallText = """
- #!/bin/bash
-
- # Set permissions on LaunchDaemon and Script
- chown root:wheel "/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist"
- chmod 644 "/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist"
- chown -R root:wheel "/Library/Scripts/\(globalTargetPathFileName)"
- chmod -R 755 "/Library/Scripts/\(globalTargetPathFileName)"
-
- exit 0
- """
-
- do {
- // get the documents folder url
- if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
+ func createPostinstall() {
+ let postInstallText = """
+ #!/bin/bash
+
+ # Set permissions on LaunchDaemon and Script
+ chown root:wheel "/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist"
+ chmod 644 "/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist"
+ chown -R root:wheel "/Library/Scripts/\(globalTargetPathFileName)"
+ chmod -R 755 "/Library/Scripts/\(globalTargetPathFileName)"
+
+ exit 0
+ """
+
+ do {
// create the destination url for the text file to be saved
let fileURL = sessionTempDir.appendingPathComponent("scripts/postinstall")
@@ -79,54 +94,90 @@ public class create_daemon {
attributes[.posixPermissions] = 0o755
do {
try FileManager.default.setAttributes(attributes, ofItemAtPath: fileURL.path)
- }catch let error {
+ } catch let error {
print("Permissions error: ", error)
}
-
+ } catch {
+ print("error:", error)
}
- } catch {
- print("error:", error)
}
- // Copy the target script/app
- let destinationURL = sessionTempDir.appendingPathComponent("root/Library/Scripts/\(globalTargetPathFileName)")
- do {
- try FileManager.default.copyItem(atPath: globalTargetPath, toPath: destinationURL.path)
+// func encodePlist(PlistData: String, Destination: URL) {
+// let preferencesToEncode = ComponentPlist(BundleIsRelocatable: false, BundleIsVersionChecked: false, BundleOverwriteAction: "upgrade", RootRelativeBundlePath: "/Library/Scripts/\(globalTargetPathFileName)")
+// let encoder = PropertyListEncoder()
+// encoder.outputFormat = .xml
+// do {
+// let data = try encoder.encode(preferencesToEncode)
+// try data.write(to: Destination)
+// } catch {
+// // Handle error
+// print(error)
+// }
+// // Code goes here
+// }
+
+ func createComponentPlist() {
+ let preferencesToEncode = ComponentPlist(BundleIsRelocatable: false, BundleIsVersionChecked: false, BundleOverwriteAction: "upgrade", RootRelativeBundlePath: "/Library/Scripts/\(globalTargetPathFileName)")
+ let encoder = PropertyListEncoder()
+ encoder.outputFormat = .xml
+ do {
+ let data = try encoder.encode([preferencesToEncode].self)
+ try data.write(to: componentPlistURL)
+ } catch {
+ // Handle error
+ print(error)
+ }
}
- catch let error as NSError {
- print("Ooops! Something went wrong: \(error)")
+
+ // Copy the target script/app
+ func copyTarget() {
+ let destinationURL = sessionTempDir.appendingPathComponent("root/Library/Scripts/\(globalTargetPathFileName)")
+ _ = extras().copyFile(source: globalTargetPath, destination: destinationURL.path)
}
// Create the plist
- let preferencesURL = sessionTempDir.appendingPathComponent("/root/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist")
- let preferencesToEncode = Preferences(Label: globalIdentifier, ProgramArguments: globalProgramArgsFull, RunAtLoad: globalRunAtLoad, StartInterval: globalStartInterval, StandardOutPath: globalStandardOutPath, StandardErrorPath: globalStandardErrorPath)
- let encoder = PropertyListEncoder()
- encoder.outputFormat = .xml
- do {
- let data = try encoder.encode(preferencesToEncode)
- try data.write(to: preferencesURL)
- } catch {
- // Handle error
- print(error)
+ func createPlist() {
+ let preferencesToEncode = Preferences(Label: globalIdentifier, ProgramArguments: globalProgramArgsFull, RunAtLoad: globalRunAtLoad, StartInterval: globalStartInterval, StandardOutPath: globalStandardOutPath, StandardErrorPath: globalStandardErrorPath, LimitLoadToSessionType: globalSessionType)
+ let encoder = PropertyListEncoder()
+ encoder.outputFormat = .xml
+ do {
+ let data = try encoder.encode(preferencesToEncode)
+ try data.write(to: preferencesURL)
+ } catch {
+ // Handle error
+ print(error)
+ }
}
- // Create the .pkg
- @discardableResult
- func shell(_ args: String...) -> Int32 {
- let task = Process()
- task.launchPath = "/usr/bin/env"
- task.arguments = args
- task.launch()
- task.waitUntilExit()
- return task.terminationStatus
+ // Create the .pkg using shell commands
+ func createPKG() {
+ @discardableResult
+ func shell(_ args: String...) -> Int32 {
+ let task = Process()
+ task.launchPath = "/usr/bin/env"
+ task.arguments = args
+ task.launch()
+ task.waitUntilExit()
+ return task.terminationStatus
+ }
+
+ let pkgRoot = sessionTempDir.appendingPathComponent("/root/")
+ let pkgScripts = sessionTempDir.appendingPathComponent("/scripts/")
+ let pkgBuildDir = sessionTempDir.appendingPathComponent("build/")
+ globalPkgTempLocation = "\(pkgBuildDir.path)/hello.pkg"
+
+ shell("/usr/bin/pkgbuild", "--quiet", "--root", "\(pkgRoot.path)", "--install-location", "/", "--scripts", "\(pkgScripts.path)", "--identifier", "\(globalIdentifier)", "--version", "\(globalVersion)", "--ownership", "recommended", "--component-plist", "\(componentPlistURL.path)", "\(globalPkgTempLocation)")
}
- let pkgRoot = sessionTempDir.appendingPathComponent("/root/")
- let pkgScripts = sessionTempDir.appendingPathComponent("/scripts/")
- let pkgBuildDir = sessionTempDir.appendingPathComponent("build/")
- globalPkgTempLocation = "\(pkgBuildDir.path)/hello.pkg"
-
+ if buildType == "PKG" {
+ createPostinstall()
+ createComponentPlist()
+ copyTarget()
+ createPlist()
+ createPKG()
+ } else if buildType == "Plist" {
+ createPlist()
+ }
- shell("/usr/bin/pkgbuild", "--quiet", "--root", "\(pkgRoot.path)", "--install-location", "/", "--scripts", "\(pkgScripts.path)", "--identifier", "\(globalIdentifier)", "--version", "\(globalVersion)", "--ownership", "recommended", "\(globalPkgTempLocation)")
}
}
diff --git a/Launchd Package Creator/extras.swift b/Launchd Package Creator/extras.swift
index fa467da..e1e56be 100644
--- a/Launchd Package Creator/extras.swift
+++ b/Launchd Package Creator/extras.swift
@@ -1,6 +1,6 @@
//
// dialog.swift
-// testing
+// launchd-package-creator
//
// Created by Ryan Ball on 4/15/19.
// Copyright © 2019 Ryan Ball. All rights reserved.
diff --git a/images/main_window.png b/images/main_window.png
index 8e9cca4..fc96ae3 100644
Binary files a/images/main_window.png and b/images/main_window.png differ