From 96e9a03809edf6cadd10f082f32444d489a71c23 Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 10:45:32 +1100 Subject: [PATCH 01/13] updated some help text --- outset/Outset.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/outset/Outset.swift b/outset/Outset.swift index ed52dec..d9fed9b 100644 --- a/outset/Outset.swift +++ b/outset/Outset.swift @@ -10,7 +10,7 @@ import Foundation import ArgumentParser let author = "Bart Reardon - Adapted from outset by Joseph Chilcote (chilcote@gmail.com) https://github.com/chilcote/outset" -let outsetVersion = "4.0 alpha" +let outsetVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String // Set some Constants TODO: leave these as defaults but maybe make them configurable from a plist @@ -53,7 +53,7 @@ var hashes_available = !file_hashes.isEmpty struct Outset: ParsableCommand { static let configuration = CommandConfiguration( commandName: "outset", - abstract: "This script automatically processes packages, profiles, and/or scripts at boot, on demand, and/or login.") + abstract: "Outset is a utility that automatically processes scripts and/or packages at boot, on demand, or login.") @Flag(help: .hidden) var debug = false @@ -91,7 +91,7 @@ struct Outset: ParsableCommand { @Option(help: ArgumentHelp("Remove one or more scripts from override list", valueName: "script"), completion: .file()) var removeOveride : [String] = [] - @Option(help: ArgumentHelp("Compute the SHA1 hash of the given file", valueName: "file"), completion: .file()) + @Option(help: ArgumentHelp("Compute the SHA1 hash of the given file. Use the keyword 'all' to compute all SHA values and generate a formatted configuration plist", valueName: "file"), completion: .file()) var computeSHA : [String] = [] @Flag(help: .hidden) From d065b6d987349c6a942dfcddaaabade2c6d7d78b Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 10:49:41 +1100 Subject: [PATCH 02/13] removing xcworkspace stuff that doesn't need to be tracked --- .../UserInterfaceState.xcuserstate | Bin 111609 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 outset.xcodeproj/project.xcworkspace/xcuserdata/rea094.xcuserdatad/UserInterfaceState.xcuserstate diff --git a/outset.xcodeproj/project.xcworkspace/xcuserdata/rea094.xcuserdatad/UserInterfaceState.xcuserstate b/outset.xcodeproj/project.xcworkspace/xcuserdata/rea094.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 9a57476a174c2e77537487ba2cb513b6dc827770..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111609 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 Date: Wed, 1 Mar 2023 10:50:18 +1100 Subject: [PATCH 03/13] updated .gitignore --- .gitignore | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3fb0d8a..4589172 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ sample* *.dmg *.pyc outset.wiki -swift/outset/outset.xcodeproj/project.xcworkspace/* -swift/outset/outset.xcodeproj/xcshareddata/* -swift/outset/outset.xcodeproj/xcuserdata/* -swift/outset/outset/Preview Content/* +project.xcworkspace/ +Preview Content/ +xcuserdata/ +xcsharedata/ From 90975146dfb9aae4307bc4a609bf92292bcd65d7 Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 10:59:10 +1100 Subject: [PATCH 04/13] Added outset "alias" to the package so /usr/local/outset/outset references are preserved. --- Package/generatePackage.zsh | 3 ++- Package/outset | 3 +++ outset.xcodeproj/project.pbxproj | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100755 Package/outset diff --git a/Package/generatePackage.zsh b/Package/generatePackage.zsh index a332c36..29de229 100644 --- a/Package/generatePackage.zsh +++ b/Package/generatePackage.zsh @@ -8,6 +8,7 @@ STAGING_DIRECTORY="${TMPDIR}/staging" INSTALL_LOCATION="/usr/local/outset/" INSTALL_ASSETS=${SCRIPT_INPUT_FILE_1} INSTALL_SCRIPTS=${SCRIPT_INPUT_FILE_2} +OUTSET_ALIAS=${SCRIPT_INPUT_FILE_3} APP_NAME=${PROJECT_NAME} IDENTIFIER="${PRODUCT_BUNDLE_IDENTIFIER}" VERSION="${MARKETING_VERSION}" @@ -17,7 +18,7 @@ rm -r "${STAGING_DIRECTORY}" # Set up a staging directory with the contents to install. mkdir -p "${STAGING_DIRECTORY}/${INSTALL_LOCATION}" -cp "outset" "${STAGING_DIRECTORY}/${INSTALL_LOCATION}" +cp "${OUTSET_ALIAS}" "${STAGING_DIRECTORY}/${INSTALL_LOCATION}" cp -r "Outset.app" "${STAGING_DIRECTORY}/${INSTALL_LOCATION}" cp -r "${INSTALL_ASSETS}" "${STAGING_DIRECTORY}" diff --git a/Package/outset b/Package/outset new file mode 100755 index 0000000..a4e9720 --- /dev/null +++ b/Package/outset @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/local/outset/Outset.app/Contents/MacOS/Outset ${@} diff --git a/outset.xcodeproj/project.pbxproj b/outset.xcodeproj/project.pbxproj index 85bcb9a..212b9b6 100644 --- a/outset.xcodeproj/project.pbxproj +++ b/outset.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 41A67A04296B9711000BFFCE /* postinstall */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = postinstall; sourceTree = ""; }; 41A67A0C296BADFE000BFFCE /* Outset Install Package.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Outset Install Package.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 41A67A13296BF35F000BFFCE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 41ADC47C29AECB8B00C5B94C /* outset */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = outset; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -128,6 +129,7 @@ children = ( 41A67A03296B93D1000BFFCE /* Scripts */, 41A679FE2966479E000BFFCE /* generatePackage.zsh */, + 41ADC47C29AECB8B00C5B94C /* outset */, 4124EFCD29446FDE003B00F4 /* Library */, ); path = Package; @@ -242,6 +244,7 @@ "$(SRCROOT)/Package/generatePackage.zsh", "$(SRCROOT)/Package/Library", "$(SRCROOT)/Package/Scripts", + "$(SRCROOT)/Package/outset", ); name = "Generate Package"; outputFileListPaths = ( From ce61346c7b6aa18ab0526c5aea75242286e4de9e Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 11:02:58 +1100 Subject: [PATCH 05/13] updated image references in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 14b5575..985c3f8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Outset ====== -![Outset Icon](https://github.com/bartreardon/outset/blob/master/swift/outset/outset/Assets.xcassets/AppIcon.appiconset/outset.png_128x128.png?raw=true) +![Outset Icon](https://github.com/bartreardon/outset/blob/master/outset/Assets.xcassets/AppIcon.appiconset/Outset.png_128x128.png?raw=true) Outset is a utility application which automatically processes scripts and packages during the boot sequence, user logins, or on demand. @@ -62,7 +62,7 @@ Updated with the `AssociatedBundleIdentifiers` key so under macOS 13, Login Item #### Outset.app -![Outset Icon](https://github.com/bartreardon/outset/blob/master/swift/outset/outset/Assets.xcassets/AppIcon.appiconset/outset.png_32x32@2x.png?raw=true) +![Outset Icon](https://github.com/bartreardon/outset/blob/master/outset/Assets.xcassets/AppIcon.appiconset/Outset.png_32x32@2x.png?raw=true) `/usr/local/outset/Outset.app` From bbd99f1a2b7262f3c634fd9724d486ac051cce7a Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 11:07:20 +1100 Subject: [PATCH 06/13] Create LICENSE.md Added LICENSE.md - TODO: discuss and review copyright owner --- LICENSE.md | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 1181a51cbaf9b809d6620eaad2025e5f131014b7 Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 12:30:52 +1100 Subject: [PATCH 07/13] refactor variable and function names to be consistent --- outset/Functions/FileUtils.swift | 82 +++++++-------- outset/Functions/Processing.swift | 40 ++++---- outset/Functions/SystemUtils.swift | 61 +++++------- outset/Outset.swift | 154 ++++++++++++++--------------- outset/main.swift | 2 + 5 files changed, 163 insertions(+), 176 deletions(-) diff --git a/outset/Functions/FileUtils.swift b/outset/Functions/FileUtils.swift index 04dfcf5..7f65e99 100644 --- a/outset/Functions/FileUtils.swift +++ b/outset/Functions/FileUtils.swift @@ -43,20 +43,20 @@ func runShellCommand(_ command: String, verbose : Bool = false) -> (output: Stri return (output, error, status) } -func ensure_working_folders() { +func ensureWorkingFolders() { // Ensures working folders are all present and creates them if necessary let working_directories = [ - boot_every_dir, - boot_once_dir, - login_every_dir, - login_once_dir, - login_privileged_every_dir, - login_privileged_once_dir, - on_demand_dir + bootEveryDir, + bootOnceDir, + loginEveryDir, + loginOnceDir, + loginEveryPrivilegedDir, + loginOncePrivilegedDir, + onDemandDir ] for directory in working_directories { - if !check_file_exists(path: directory, isDir: true) { + if !checkFileExists(path: directory, isDir: true) { writeLog("\(directory) does not exist, creating now.", status: .debug) do { try FileManager.default.createDirectory(atPath: directory, withIntermediateDirectories: true) @@ -67,13 +67,13 @@ func ensure_working_folders() { } } -func migrate_legacy_preferences() { +func migrateLegacyPreferences() { // shared folder should not contain any executable content, iterate and update as required // TODO: could probably be optimised as there is duplication with ensure_working_folders() - if check_file_exists(path: share_dir) { - writeLog("\(share_dir) exists. Migrating prefrences to user defaults", status: .debug) + if checkFileExists(path: shareDirectory) { + writeLog("\(shareDirectory) exists. Migrating prefrences to user defaults", status: .debug) - let legacyOutsetPreferencesFile = "\(share_dir)com.chilcote.outset.plist" + let legacyOutsetPreferencesFile = "\(shareDirectory)com.chilcote.outset.plist" let legacyRootRunOncePlistFile = "com.github.outset.once.\(getConsoleUserInfo().userID).plist" let userHomePath = FileManager.default.homeDirectoryForCurrentUser.relativeString.replacingOccurrences(of: "file://", with: "") let legacyUserRunOncePlistFile = userHomePath+"Library/Preferences/com.github.outset.once.plist" @@ -84,7 +84,7 @@ func migrate_legacy_preferences() { share_files.append(legacyUserRunOncePlistFile) for filename in share_files { - if check_file_exists(path: filename) { + if checkFileExists(path: filename) { let url = URL(fileURLWithPath: filename) do { let data = try Data(contentsOf: url) @@ -93,9 +93,9 @@ func migrate_legacy_preferences() { case legacyOutsetPreferencesFile: do { let legacyPreferences = try PropertyListDecoder().decode(OutsetPreferences.self, from: data) - write_outset_preferences(prefs: legacyPreferences) + writePreferences(prefs: legacyPreferences) writeLog("Migrated Legacy Outset Preferences", status: .debug) - delete_file(legacyOutsetPreferencesFile) + deleteFile(legacyOutsetPreferencesFile) } catch { writeLog("legacy Preferences migration failed", status: .error) } @@ -103,12 +103,12 @@ func migrate_legacy_preferences() { case legacyRootRunOncePlistFile, legacyUserRunOncePlistFile: do { let legacyRunOncePlistData = try PropertyListDecoder().decode([String:Date].self, from: data) - write_runonce(runOnceData: legacyRunOncePlistData) + writeRunOnce(runOnceData: legacyRunOncePlistData) writeLog("Migrated Legacy Runonce Data", status: .debug) - if is_root() { - delete_file(legacyRootRunOncePlistFile) + if isRoot() { + deleteFile(legacyRootRunOncePlistFile) } else { - delete_file(legacyUserRunOncePlistFile) + deleteFile(legacyUserRunOncePlistFile) } } catch { writeLog("legacy Run Once Plist migration failed", status: .error) @@ -124,25 +124,25 @@ func migrate_legacy_preferences() { } - if list_folder(path: share_dir).isEmpty { + if folderContents(path: shareDirectory).isEmpty { do { - try FileManager.default.removeItem(atPath: share_dir) - writeLog("removed \(share_dir)", status: .debug) + try FileManager.default.removeItem(atPath: shareDirectory) + writeLog("removed \(shareDirectory)", status: .debug) } catch { - writeLog("could not remove \(share_dir)", status: .error) + writeLog("could not remove \(shareDirectory)", status: .error) } } } } -func check_file_exists(path: String, isDir: ObjCBool = false) -> Bool { +func checkFileExists(path: String, isDir: ObjCBool = false) -> Bool { // What is says on the tin var checkIsDir :ObjCBool = isDir return FileManager.default.fileExists(atPath: path, isDirectory: &checkIsDir) } -func list_folder(path: String) -> [String] { +func folderContents(path: String) -> [String] { // Returns a array of strings containing the folder contents // Does not perform a recursive list var filelist : [String] = [] @@ -157,26 +157,26 @@ func list_folder(path: String) -> [String] { return filelist } -func check_permissions(pathname :String) -> Bool { +func verifyPermissions(pathname :String) -> Bool { // Files should be owned by root // Files that are not scripts should have permissions 644 (-rw-r--r--) // Files that are scripts should have permissions 755 (-rwxr-xr-x) // If the permission for the request file is not correct then return fals to indicate it should not be processed - let (ownerID, mode) = get_file_owner_and_permissions(pathname: pathname) //fileAttributes[.ownerAccountID] as! Int + let (ownerID, mode) = getFileProperties(pathname: pathname) //fileAttributes[.ownerAccountID] as! Int let posixPermissions = String(mode.intValue, radix: 8, uppercase: false) writeLog("ownerID for \(pathname) : \(String(describing: ownerID))", status: .debug) writeLog("posixPermissions for \(pathname) : \(String(describing: posixPermissions))", status: .debug) if ["pkg", "mpkg", "dmg", "mobileconfig"].contains(pathname.lowercased().split(separator: ".").last) { - if ownerID == 0 && mode == filePermissions { + if ownerID == 0 && mode == requiredFilePermissions { return true } else { writeLog("Permissions for \(pathname) are incorrect. Should be owned by root and with mode x644", status: .error) } } else { - if ownerID == 0 && mode == executablePermissions { + if ownerID == 0 && mode == requiredExecutablePermissions { return true } else { writeLog("Permissions for \(pathname) are incorrect. Should be owned by root and with mode x755", status: .error) @@ -185,7 +185,7 @@ func check_permissions(pathname :String) -> Bool { return false } -func get_file_owner_and_permissions(pathname: String) -> (ownerID : Int, permissions : NSNumber) { +func getFileProperties(pathname: String) -> (ownerID : Int, permissions : NSNumber) { // returns the ID and permissions of the specified file var fileAttributes : [FileAttributeKey:Any] var ownerID : Int = 0 @@ -200,22 +200,22 @@ func get_file_owner_and_permissions(pathname: String) -> (ownerID : Int, permiss return (ownerID,mode) } -func path_cleanup(pathname: String) { +func pathCleanup(pathname: String) { // check if folder and clean all files in that folder // Deletes given script or cleans folder writeLog("Cleaning up \(pathname)", status: .debug) - if check_file_exists(path: pathname, isDir: true) { - for fileItem in list_folder(path: pathname) { - delete_file(fileItem) + if checkFileExists(path: pathname, isDir: true) { + for fileItem in folderContents(path: pathname) { + deleteFile(fileItem) } - } else if check_file_exists(path: pathname) { - delete_file(pathname) + } else if checkFileExists(path: pathname) { + deleteFile(pathname) } else { writeLog("\(pathname) doesn't seem to exist", status: .error) } } -func delete_file(_ path: String) { +func deleteFile(_ path: String) { // Deletes the specified file writeLog("Deleting \(path)", status: .debug) do { @@ -226,7 +226,7 @@ func delete_file(_ path: String) { } } -func mount_dmg(dmg: String) -> String { +func mountDmg(dmg: String) -> String { // Attaches dmg and returns the path let cmd = "/usr/bin/hdiutil attach -nobrowse -noverify -noautoopen \(dmg)" writeLog("Attaching \(dmg)", status: .debug) @@ -238,7 +238,7 @@ func mount_dmg(dmg: String) -> String { return output.trimmingCharacters(in: .whitespacesAndNewlines) } -func detach_dmg(dmgMount: String) -> String { +func detachDmg(dmgMount: String) -> String { // Detaches dmg writeLog("Detaching \(dmgMount)", status: .debug) let cmd = "/usr/bin/hdiutil detach -force \(dmgMount)" @@ -268,7 +268,7 @@ func shaAllFiles() { // plaintext // as plist format ready for import into an MDM or converting to a .mobileconfig - let url = URL(fileURLWithPath: outset_dir) + let url = URL(fileURLWithPath: outsetDirectory) writeLog("SHASUM", status: .info) var shasum_plist = FileHashes() if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) { diff --git a/outset/Functions/Processing.swift b/outset/Functions/Processing.swift index bc9d5d2..1e07377 100644 --- a/outset/Functions/Processing.swift +++ b/outset/Functions/Processing.swift @@ -7,10 +7,10 @@ import Foundation -func process_items(_ path: String, delete_items : Bool=false, once : Bool=false, override : [String:Date] = [:]) { +func processItems(_ path: String, delete_items : Bool=false, once : Bool=false, override : [String:Date] = [:]) { // Main processing logic // TODO: should be able to break this into seperate functions if it helps readability or if seperate components are needed elsewhere individually - if !check_file_exists(path: path) { + if !checkFileExists(path: path) { writeLog("\(path) does not exist. Exiting") exit(1) } @@ -23,14 +23,14 @@ func process_items(_ path: String, delete_items : Bool=false, once : Bool=false, var runOnceDict : [String:Date] = [:] // See if there's any old stuff to migrate - migrate_legacy_preferences() + migrateLegacyPreferences() // Get a list of all the files to process - items_to_process = list_folder(path: path) + items_to_process = folderContents(path: path) // iterate over the list and check the for pathname in items_to_process { - if check_permissions(pathname: pathname) { + if verifyPermissions(pathname: pathname) { switch pathname.split(separator: ".").last { case "pkg", "mpkg", "dmg": packages.append(pathname) @@ -45,14 +45,14 @@ func process_items(_ path: String, delete_items : Bool=false, once : Bool=false, } // load runonce data - runOnceDict = load_runonce() + runOnceDict = loadRunOnce() // loop through the packages list and process installs. // TODO: add in hash comparison for processing packages presuming package installs as a feature is maintained. for package in packages { if once { if !runOnceDict.contains(where: {$0.key == package}) { - if install_package(pkg: package) { + if installPackage(pkg: package) { runOnceDict.updateValue(Date(), forKey: package) } } else { @@ -60,17 +60,17 @@ func process_items(_ path: String, delete_items : Bool=false, once : Bool=false, writeLog("override for \(package) dated \(override[package]!)", status: .debug) if override[package]! > runOnceDict[package]! { writeLog("Actioning package override", status: .debug) - if install_package(pkg: package) { + if installPackage(pkg: package) { runOnceDict.updateValue(Date(), forKey: package) } } } } } else { - _ = install_package(pkg: package) + _ = installPackage(pkg: package) } if delete_items { - path_cleanup(pathname: package) + pathCleanup(pathname: package) } } @@ -82,14 +82,14 @@ func process_items(_ path: String, delete_items : Bool=false, once : Bool=false, // loop through the scripts list and process. for script in scripts { - if hashes_available { + if shasumsAvailable { // check user defaults for a list of sha256 hashes. // This block will run if there are _any_ hashes available so it's all or nothing (by design) // If there is no hash or it doesn't match then we skip to the next file var proceed = false writeLog("checking hash for \(script)", status: .debug) - if let storedHash = getValueForKey(script, inArray: file_hashes) { + if let storedHash = getValueForKey(script, inArray: shasumFileList) { writeLog("stored hash : \(storedHash)", status: .debug) let url = URL(fileURLWithPath: script) if let fileHash = sha256(for: url) { @@ -141,26 +141,26 @@ func process_items(_ path: String, delete_items : Bool=false, once : Bool=false, } } if delete_items { - path_cleanup(pathname: script) + pathCleanup(pathname: script) } } if !runOnceDict.isEmpty { - write_runonce(runOnceData: runOnceDict) + writeRunOnce(runOnceData: runOnceDict) } } -func install_package(pkg : String) -> Bool { +func installPackage(pkg : String) -> Bool { // Installs pkg onto boot drive - if is_root() { + if isRoot() { var pkg_to_install : String = "" var dmg_mount : String = "" if pkg.lowercased().hasSuffix("dmg") { - dmg_mount = mount_dmg(dmg: pkg) - for files in list_folder(path: dmg_mount) { + dmg_mount = mountDmg(dmg: pkg) + for files in folderContents(path: dmg_mount) { if ["pkg", "mpkg"].contains(files.lowercased().suffix(3)) { pkg_to_install = dmg_mount } @@ -179,7 +179,7 @@ func install_package(pkg : String) -> Bool { DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) { if !dmg_mount.isEmpty { - writeLog(detach_dmg(dmgMount: dmg_mount)) + writeLog(detachDmg(dmgMount: dmg_mount)) } } return true @@ -190,7 +190,7 @@ func install_package(pkg : String) -> Bool { return false } -func install_profile(pathname : String) -> Bool { +func installProfile(pathname : String) -> Bool { return false } diff --git a/outset/Functions/SystemUtils.swift b/outset/Functions/SystemUtils.swift index c55c5e4..dcc4d62 100644 --- a/outset/Functions/SystemUtils.swift +++ b/outset/Functions/SystemUtils.swift @@ -21,13 +21,13 @@ struct FileHashes: Codable { } func ensure_root(_ reason : String) { - if !is_root() { + if !isRoot() { writeLog("Must be root to \(reason)", status: .error) exit(1) } } -func is_root() -> Bool { +func isRoot() -> Bool { return NSUserName() == "root" } @@ -65,7 +65,7 @@ func getConsoleUserInfo() -> (username: String, userID: String) { return (consoleUserName.trimmingCharacters(in: .whitespacesAndNewlines), consoleUserID.trimmingCharacters(in: .whitespacesAndNewlines)) } -func write_outset_preferences(prefs: OutsetPreferences) { +func writePreferences(prefs: OutsetPreferences) { let defaults = UserDefaults.standard let path = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true) @@ -83,7 +83,7 @@ func write_outset_preferences(prefs: OutsetPreferences) { } } -func load_outset_preferences() -> OutsetPreferences { +func loadPreferences() -> OutsetPreferences { let defaults = UserDefaults.standard var outsetPrefs = OutsetPreferences() @@ -95,27 +95,27 @@ func load_outset_preferences() -> OutsetPreferences { return outsetPrefs } -func load_runonce() -> [String:Date] { +func loadRunOnce() -> [String:Date] { let defaults = UserDefaults.standard var runOnceKey = "run_once" - if is_root() { + if isRoot() { runOnceKey = runOnceKey+"-"+getConsoleUserInfo().username } return defaults.object(forKey: runOnceKey) as? [String:Date] ?? [:] } -func write_runonce(runOnceData: [String:Date]) { +func writeRunOnce(runOnceData: [String:Date]) { let defaults = UserDefaults.standard var runOnceKey = "run_once" - if is_root() { + if isRoot() { runOnceKey = runOnceKey+"-"+getConsoleUserInfo().username } defaults.set(runOnceData, forKey: runOnceKey) } -func load_hashes() -> [String:String] { +func shasumLoadApprovedFileHashList() -> [String:String] { // imports the list of file hashes that are approved to run var outset_file_hash_list = FileHashes() @@ -131,7 +131,7 @@ func load_hashes() -> [String:String] { return outset_file_hash_list.sha256sum } -func network_up() -> Bool { +func isNetworkUp() -> Bool { // https://stackoverflow.com/a/39782859/17584669 // perform a check to see if the network is available. @@ -157,28 +157,13 @@ func network_up() -> Bool { return ret } -func wait_for_network_old(timeout : Double) -> Bool { - var networkUp : Bool = false - var networkCheck : DispatchWorkItem? - for _ in 0.. Bool { +func waitForNetworkUp(timeout: Double) -> Bool { // used during --boot if "wait_for_network" prefrence is true var networkUp = false let deadline = DispatchTime.now() + timeout while !networkUp && DispatchTime.now() < deadline { writeLog("Waiting for network: \(timeout) seconds", status: .debug) - networkUp = network_up() + networkUp = isNetworkUp() if !networkUp { writeLog("Waiting...", status: .debug) Thread.sleep(forTimeInterval: 1) @@ -190,21 +175,21 @@ func wait_for_network(timeout: Double) -> Bool { return networkUp } -func disable_loginwindow() { +func loginWindowDisable() { // Disables the loginwindow process writeLog("Disabling loginwindow process", status: .debug) let cmd = "/bin/launchctl unload /System/Library/LaunchDaemons/com.apple.loginwindow.plist" _ = runShellCommand(cmd) } -func enable_loginwindow() { +func loginWindowEnable() { // Enables the loginwindow process writeLog("Enabling loginwindow process", status: .debug) let cmd = "/bin/launchctl load /System/Library/LaunchDaemons/com.apple.loginwindow.plist" _ = runShellCommand(cmd) } -func get_hardwaremodel() -> String { +func getDeviceHardwareModel() -> String { // Returns the current devices hardware model from sysctl var size = 0 sysctlbyname("hw.model", nil, &size, nil, 0) @@ -213,7 +198,7 @@ func get_hardwaremodel() -> String { return String(cString: model) } -func get_serialnumber() -> String { +func getDeviceSerialNumber() -> String { // Returns the current devices serial number // TODO: fix warning 'kIOMasterPortDefault' was deprecated in macOS 12.0: renamed to 'kIOMainPortDefault' let platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice") ) @@ -227,7 +212,7 @@ func get_serialnumber() -> String { return serialNumber } -func get_buildversion() -> String { +func getOSBuildVersion() -> String { // Returns the current OS build from sysctl var size = 0 sysctlbyname("kern.osversion", nil, &size, nil, 0) @@ -237,18 +222,18 @@ func get_buildversion() -> String { } -func get_osversion() -> String { +func getOSVersion() -> String { // Returns the OS version let osVersion = ProcessInfo().operatingSystemVersion let version = "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)" return version } -func sys_report() { +func writeSysReport() { // Logs system information to log file writeLog("User: \(getConsoleUserInfo())", status: .debug) - writeLog("Model: \(get_hardwaremodel())", status: .debug) - writeLog("Serial: \(get_serialnumber())", status: .debug) - writeLog("OS: \(get_osversion())", status: .debug) - writeLog("Build: \(get_buildversion())", status: .debug) + writeLog("Model: \(getDeviceHardwareModel())", status: .debug) + writeLog("Serial: \(getDeviceSerialNumber())", status: .debug) + writeLog("OS: \(getOSVersion())", status: .debug) + writeLog("Build: \(getOSBuildVersion())", status: .debug) } diff --git a/outset/Outset.swift b/outset/Outset.swift index d9fed9b..7eb5999 100644 --- a/outset/Outset.swift +++ b/outset/Outset.swift @@ -15,37 +15,37 @@ let outsetVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as // Set some Constants TODO: leave these as defaults but maybe make them configurable from a plist // Outset specific directories -let outset_dir = "/usr/local/outset/" -let boot_every_dir = outset_dir+"boot-every" -let boot_once_dir = outset_dir+"boot-once" -let login_every_dir = outset_dir+"login-every" -let login_once_dir = outset_dir+"login-once" -let login_privileged_every_dir = outset_dir+"login-privileged-every" -let login_privileged_once_dir = outset_dir+"login-privileged-once" -let on_demand_dir = outset_dir+"on-demand" -let share_dir = outset_dir+"share/" +let outsetDirectory = "/usr/local/outset/" +let bootEveryDir = outsetDirectory+"boot-every" +let bootOnceDir = outsetDirectory+"boot-once" +let loginEveryDir = outsetDirectory+"login-every" +let loginOnceDir = outsetDirectory+"login-once" +let loginEveryPrivilegedDir = outsetDirectory+"login-privileged-every" +let loginOncePrivilegedDir = outsetDirectory+"login-privileged-once" +let onDemandDir = outsetDirectory+"on-demand" +let shareDirectory = outsetDirectory+"share/" -let on_demand_trigger = "/private/tmp/.io.macadmins.outset.ondemand.launchd" -let login_privileged_trigger = "/private/tmp/.io.macadmins.outset.login-privileged.launchd" -let cleanup_trigger = "/private/tmp/.io.macadmins.outset.cleanup.launchd" +let onDemandTrigger = "/private/tmp/.io.macadmins.outset.ondemand.launchd" +let loginPrivilegedTrigger = "/private/tmp/.io.macadmins.outset.login-privileged.launchd" +let cleanupTrigger = "/private/tmp/.io.macadmins.outset.cleanup.launchd" // File permission defaults -let filePermissions: NSNumber = 0o644 -let executablePermissions: NSNumber = 0o755 +let requiredFilePermissions: NSNumber = 0o644 +let requiredExecutablePermissions: NSNumber = 0o755 // Set some variables var debugMode : Bool = false var loginwindow : Bool = true -var console_user : String = getConsoleUserInfo().username -var network_wait : Bool = true -var network_timeout : Int = 180 -var ignored_users : [String] = [] -var override_login_once : [String: Date] = [String: Date]() -var continue_firstboot : Bool = true -var prefs = load_outset_preferences() -var file_hashes = load_hashes() -var hashes_available = !file_hashes.isEmpty +var consoleUser : String = getConsoleUserInfo().username +var networkWait : Bool = true +var networkTimeout : Int = 180 +var ignoredUsers : [String] = [] +var loginOnceOverride : [String: Date] = [String: Date]() +var continueFirstBoot : Bool = true +var prefs = loadPreferences() +var shasumFileList = shasumLoadApprovedFileHashList() +var shasumsAvailable = !shasumFileList.isEmpty // Logic insertion point @@ -105,40 +105,40 @@ struct Outset: ParsableCommand { if debug { debugMode = true writeLog("Outset version \(outsetVersion)", status: .debug) - sys_report() + writeSysReport() } if shasumReport { writeLog("sha256sum report", status: .info) - for (filename, shasum) in load_hashes() { + for (filename, shasum) in shasumLoadApprovedFileHashList() { writeLog("\(filename) : \(shasum)", status: .info) } } if boot { writeLog("Processing scheduled runs for boot", status: .debug) - ensure_working_folders() - write_outset_preferences(prefs: prefs) + ensureWorkingFolders() + writePreferences(prefs: prefs) - if !list_folder(path: boot_once_dir).isEmpty { - if network_wait { + if !folderContents(path: bootOnceDir).isEmpty { + if networkWait { loginwindow = false - disable_loginwindow() - continue_firstboot = wait_for_network(timeout: floor(Double(network_timeout) / 10)) + loginWindowDisable() + continueFirstBoot = waitForNetworkUp(timeout: floor(Double(networkTimeout) / 10)) } - if continue_firstboot { - sys_report() - process_items(boot_once_dir, delete_items: true) + if continueFirstBoot { + writeSysReport() + processItems(bootOnceDir, delete_items: true) } else { writeLog("Unable to connect to network. Skipping boot-once scripts...", status: .error) } if !loginwindow { - enable_loginwindow() + loginWindowEnable() } } - if !list_folder(path: boot_every_dir).isEmpty { - process_items(boot_every_dir) + if !folderContents(path: bootEveryDir).isEmpty { + processItems(bootEveryDir) } writeLog("Boot processing complete") @@ -146,15 +146,15 @@ struct Outset: ParsableCommand { if login { writeLog("Processing scheduled runs for login", status: .debug) - if !ignored_users.contains(console_user) { - if !list_folder(path: login_once_dir).isEmpty { - process_items(login_once_dir, once: true, override: prefs.override_login_once) + if !ignoredUsers.contains(consoleUser) { + if !folderContents(path: loginOnceDir).isEmpty { + processItems(loginOnceDir, once: true, override: prefs.override_login_once) } - if !list_folder(path: login_every_dir).isEmpty { - process_items(login_every_dir) + if !folderContents(path: loginEveryDir).isEmpty { + processItems(loginEveryDir) } - if !list_folder(path: login_privileged_once_dir).isEmpty || !list_folder(path: login_privileged_every_dir).isEmpty { - FileManager.default.createFile(atPath: login_privileged_trigger, contents: nil) + if !folderContents(path: loginOncePrivilegedDir).isEmpty || !folderContents(path: loginEveryPrivilegedDir).isEmpty { + FileManager.default.createFile(atPath: loginPrivilegedTrigger, contents: nil) } } @@ -162,38 +162,38 @@ struct Outset: ParsableCommand { if loginPrivileged { writeLog("Processing scheduled runs for privileged login", status: .debug) - if check_file_exists(path: login_privileged_trigger) { - path_cleanup(pathname: login_privileged_trigger) + if checkFileExists(path: loginPrivilegedTrigger) { + pathCleanup(pathname: loginPrivilegedTrigger) } - if !ignored_users.contains(console_user) { - if !list_folder(path: login_privileged_once_dir).isEmpty { - process_items(login_privileged_once_dir, once: true, override: prefs.override_login_once) + if !ignoredUsers.contains(consoleUser) { + if !folderContents(path: loginOncePrivilegedDir).isEmpty { + processItems(loginOncePrivilegedDir, once: true, override: prefs.override_login_once) } - if !list_folder(path: login_privileged_every_dir).isEmpty { - process_items(login_privileged_every_dir) + if !folderContents(path: loginEveryPrivilegedDir).isEmpty { + processItems(loginEveryPrivilegedDir) } } else { - writeLog("Skipping login scripts for user \(console_user)") + writeLog("Skipping login scripts for user \(consoleUser)") } } if onDemand { writeLog("Processing on-demand", status: .debug) - if !list_folder(path: on_demand_dir).isEmpty { - if !["root", "loginwindow"].contains(console_user) { + if !folderContents(path: onDemandDir).isEmpty { + if !["root", "loginwindow"].contains(consoleUser) { let current_user = NSUserName() - if console_user == current_user { - process_items(on_demand_dir) + if consoleUser == current_user { + processItems(onDemandDir) } else { writeLog("User \(current_user) is not the current console user. Skipping on-demand run.") } } else { writeLog("No current user session. Skipping on-demand run.") } - FileManager.default.createFile(atPath: cleanup_trigger, contents: nil) + FileManager.default.createFile(atPath: cleanupTrigger, contents: nil) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - if check_file_exists(path: cleanup_trigger) { - path_cleanup(pathname: cleanup_trigger) + if checkFileExists(path: cleanupTrigger) { + pathCleanup(pathname: cleanupTrigger) } } } @@ -201,29 +201,29 @@ struct Outset: ParsableCommand { if loginEvery { writeLog("Processing scripts in login-every", status: .debug) - if !ignored_users.contains(console_user) { - if !list_folder(path: login_every_dir).isEmpty { - process_items(login_every_dir) + if !ignoredUsers.contains(consoleUser) { + if !folderContents(path: loginEveryDir).isEmpty { + processItems(loginEveryDir) } } } if loginOnce { writeLog("Processing scripts in login-once", status: .debug) - if !ignored_users.contains(console_user) { - if !list_folder(path: login_once_dir).isEmpty { - process_items(login_once_dir, once: true) + if !ignoredUsers.contains(consoleUser) { + if !folderContents(path: loginOnceDir).isEmpty { + processItems(loginOnceDir, once: true) } } } if cleanup { writeLog("Cleaning up on-demand directory.", status: .debug) - if check_file_exists(path: on_demand_trigger) { - path_cleanup(pathname: on_demand_trigger) + if checkFileExists(path: onDemandTrigger) { + pathCleanup(pathname: onDemandTrigger) } - if !list_folder(path: on_demand_dir).isEmpty { - path_cleanup(pathname: on_demand_dir) + if !folderContents(path: onDemandDir).isEmpty { + pathCleanup(pathname: onDemandDir) } } @@ -237,7 +237,7 @@ struct Outset: ParsableCommand { prefs.ignored_users.append(username) } } - write_outset_preferences(prefs: prefs) + writePreferences(prefs: prefs) } if !removeIgnoredUser.isEmpty { @@ -247,32 +247,32 @@ struct Outset: ParsableCommand { prefs.ignored_users.remove(at: index) } } - write_outset_preferences(prefs: prefs) + writePreferences(prefs: prefs) } if !addOveride.isEmpty { ensure_root("add scripts to override list") for var overide in addOveride { - if !overide.contains(login_once_dir) { - overide = "\(login_once_dir)/\(overide)" + if !overide.contains(loginOnceDir) { + overide = "\(loginOnceDir)/\(overide)" } writeLog("Adding \(overide) to overide list", status: .debug) prefs.override_login_once[overide] = Date() } - write_outset_preferences(prefs: prefs) + writePreferences(prefs: prefs) } if !removeOveride.isEmpty { ensure_root("remove scripts to override list") for var overide in removeOveride { - if !overide.contains(login_once_dir) { - overide = "\(login_once_dir)/\(overide)" + if !overide.contains(loginOnceDir) { + overide = "\(loginOnceDir)/\(overide)" } writeLog("Removing \(overide) from overide list", status: .debug) prefs.override_login_once.removeValue(forKey: overide) } - write_outset_preferences(prefs: prefs) + writePreferences(prefs: prefs) } if !computeSHA.isEmpty { diff --git a/outset/main.swift b/outset/main.swift index 1cace5f..6ee1e2d 100644 --- a/outset/main.swift +++ b/outset/main.swift @@ -16,6 +16,8 @@ func main() { func init_daemons() { // The identifier must match the CFBundleIdentifier string in Info.plist. + + //TODO: This code is probably incorrect. This functionality should be re-written from first principles if #available(macOS 13.0, *) { // LaunchDaemon path: $APP.app/Contents/Library/LaunchDaemons/com.example.daemon.plist From 605be825be2fa3e767b91026f8fa4f59a992e663 Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 16:53:41 +1100 Subject: [PATCH 08/13] re-arranging content to be less confusing (don't need a main.swift that isn't the entry point of the app) --- outset.xcodeproj/project.pbxproj | 18 ++++++------------ .../ServiceManagement.swift} | 5 ----- 2 files changed, 6 insertions(+), 17 deletions(-) rename outset/{main.swift => Functions/ServiceManagement.swift} (97%) diff --git a/outset.xcodeproj/project.pbxproj b/outset.xcodeproj/project.pbxproj index 212b9b6..3ddf608 100644 --- a/outset.xcodeproj/project.pbxproj +++ b/outset.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 4124EFB429414A5E003B00F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4124EFB329414A5E003B00F4 /* Assets.xcassets */; }; 4124EFB729414A5E003B00F4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4124EFB629414A5E003B00F4 /* Preview Assets.xcassets */; }; + 41ADC47D29AF1F4500C5B94C /* ServiceManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4124EFCA29446C2C003B00F4 /* ServiceManagement.swift */; }; 41E28EA229ACDCD6002ADBE5 /* Outset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4124EF90293822F4003B00F4 /* Outset.swift */; }; 41E28EA329ACDCE3002ADBE5 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4124EFA2293B2F9B003B00F4 /* FileUtils.swift */; }; 41E28EA429ACDCE3002ADBE5 /* Processing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4124EFA4293B304B003B00F4 /* Processing.swift */; }; @@ -36,7 +37,7 @@ 4124EFB629414A5E003B00F4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 4124EFB829414A5E003B00F4 /* Outset.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Outset.entitlements; sourceTree = ""; }; 4124EFC329414DA4003B00F4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - 4124EFCA29446C2C003B00F4 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = main.swift; path = outset/main.swift; sourceTree = SOURCE_ROOT; }; + 4124EFCA29446C2C003B00F4 /* ServiceManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ServiceManagement.swift; path = Outset/Functions/ServiceManagement.swift; sourceTree = SOURCE_ROOT; }; 4124EFCD29446FDE003B00F4 /* Library */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Library; sourceTree = ""; }; 41A679FE2966479E000BFFCE /* generatePackage.zsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = generatePackage.zsh; sourceTree = ""; }; 41A67A04296B9711000BFFCE /* postinstall */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = postinstall; sourceTree = ""; }; @@ -62,7 +63,6 @@ children = ( 41A67A13296BF35F000BFFCE /* README.md */, 41A679EA29659F4E000BFFCE /* Package */, - 4124EF8F293822F4003B00F4 /* outset */, 4124EFAE29414A5D003B00F4 /* Outset */, 4124EF8E293822F4003B00F4 /* Products */, 4124EFC029414AD1003B00F4 /* Frameworks */, @@ -78,18 +78,10 @@ name = Products; sourceTree = ""; }; - 4124EF8F293822F4003B00F4 /* outset */ = { - isa = PBXGroup; - children = ( - 4124EF90293822F4003B00F4 /* Outset.swift */, - 4124EFA6293B30D6003B00F4 /* Functions */, - ); - path = outset; - sourceTree = ""; - }; 4124EFA6293B30D6003B00F4 /* Functions */ = { isa = PBXGroup; children = ( + 4124EFCA29446C2C003B00F4 /* ServiceManagement.swift */, 4124EFA2293B2F9B003B00F4 /* FileUtils.swift */, 4124EFA4293B304B003B00F4 /* Processing.swift */, 4124EF99293824C8003B00F4 /* SystemUtils.swift */, @@ -100,11 +92,12 @@ 4124EFAE29414A5D003B00F4 /* Outset */ = { isa = PBXGroup; children = ( + 4124EF90293822F4003B00F4 /* Outset.swift */, + 4124EFA6293B30D6003B00F4 /* Functions */, 4124EFC329414DA4003B00F4 /* Info.plist */, 4124EFB329414A5E003B00F4 /* Assets.xcassets */, 4124EFB829414A5E003B00F4 /* Outset.entitlements */, 4124EFB529414A5E003B00F4 /* Preview Content */, - 4124EFCA29446C2C003B00F4 /* main.swift */, ); path = Outset; sourceTree = ""; @@ -265,6 +258,7 @@ files = ( 41E28EA329ACDCE3002ADBE5 /* FileUtils.swift in Sources */, 41E28EA429ACDCE3002ADBE5 /* Processing.swift in Sources */, + 41ADC47D29AF1F4500C5B94C /* ServiceManagement.swift in Sources */, 41E28EA529ACDCE3002ADBE5 /* SystemUtils.swift in Sources */, 41E28EA229ACDCD6002ADBE5 /* Outset.swift in Sources */, ); diff --git a/outset/main.swift b/outset/Functions/ServiceManagement.swift similarity index 97% rename from outset/main.swift rename to outset/Functions/ServiceManagement.swift index 6ee1e2d..b32eb19 100644 --- a/outset/main.swift +++ b/outset/Functions/ServiceManagement.swift @@ -9,11 +9,6 @@ import Foundation import ServiceManagement -func main() { - init_daemons() - //∫exit(0) -} - func init_daemons() { // The identifier must match the CFBundleIdentifier string in Info.plist. From f1cb51606595ba874d5c6d6960d847d66f8bb6a3 Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 18:02:12 +1100 Subject: [PATCH 09/13] added a chmod to ensure the outset script is built with appropriate mode also playing around with automatic signing in the generatePackage script. works (N=1) but commented out for now. committing for posterity. --- Package/generatePackage.zsh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Package/generatePackage.zsh b/Package/generatePackage.zsh index 29de229..4ca40fc 100644 --- a/Package/generatePackage.zsh +++ b/Package/generatePackage.zsh @@ -18,6 +18,7 @@ rm -r "${STAGING_DIRECTORY}" # Set up a staging directory with the contents to install. mkdir -p "${STAGING_DIRECTORY}/${INSTALL_LOCATION}" +chmod 755 "${OUTSET_ALIAS}" cp "${OUTSET_ALIAS}" "${STAGING_DIRECTORY}/${INSTALL_LOCATION}" cp -r "Outset.app" "${STAGING_DIRECTORY}/${INSTALL_LOCATION}" cp -r "${INSTALL_ASSETS}" "${STAGING_DIRECTORY}" @@ -37,3 +38,13 @@ productbuild --synthesize --package tmp-package.pkg --identifier "${IDENTIFIER}" # Synthesize the final package from the distribution. productbuild --distribution Distribution --package-path "${BUILT_PRODUCTS_DIR}" "${SCRIPT_OUTPUT_FILE_0}" + +# Get the developer installer identity of possible for signing +# IDENTITY=$(security find-certificate -p -c "Developer ID Installer" 2>/dev/null) && IDENTITY=$(echo "${IDENTITY}" | openssl x509 -noout -subject | sed -n 's/.*CN=\([^/]*\).*/\1/p') +# +# if [[ -z ${IDENTITY} ]]; then +# productbuild --distribution Distribution --package-path "${BUILT_PRODUCTS_DIR}" "${SCRIPT_OUTPUT_FILE_0}" +# else +# productbuild --distribution Distribution --package-path "${BUILT_PRODUCTS_DIR}" --sign "${IDENTITY}" "${SCRIPT_OUTPUT_FILE_0}" +# fi + From f9597b0d168c62f8676ea8ad9490375dbdf9080d Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 21:25:06 +1100 Subject: [PATCH 10/13] updated installer bundle ID to match the app --- outset.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/outset.xcodeproj/project.pbxproj b/outset.xcodeproj/project.pbxproj index 3ddf608..73b1b10 100644 --- a/outset.xcodeproj/project.pbxproj +++ b/outset.xcodeproj/project.pbxproj @@ -491,7 +491,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 4.0; - PRODUCT_BUNDLE_IDENTIFIER = io.macadmins.OutsetInstaller; + PRODUCT_BUNDLE_IDENTIFIER = io.macadmins.Outset; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = auto; @@ -533,7 +533,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 4.0; - PRODUCT_BUNDLE_IDENTIFIER = io.macadmins.OutsetInstaller; + PRODUCT_BUNDLE_IDENTIFIER = io.macadmins.Outset; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = auto; From 7644b9b5f5bc29c99d83d0964f03329c1420b2ca Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 21:42:57 +1100 Subject: [PATCH 11/13] added shasum to packages processing --- outset/Functions/FileUtils.swift | 20 ++++++++++++++++++++ outset/Functions/Processing.swift | 30 +++++++++--------------------- outset/Outset.swift | 3 --- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/outset/Functions/FileUtils.swift b/outset/Functions/FileUtils.swift index 7f65e99..0fa92e8 100644 --- a/outset/Functions/FileUtils.swift +++ b/outset/Functions/FileUtils.swift @@ -250,6 +250,26 @@ func detachDmg(dmgMount: String) -> String { return output.trimmingCharacters(in: .whitespacesAndNewlines) } +func verifySHASUMForFile(filename: String, shasumArray: [String:String]) -> Bool { + // Verify that the file + var proceed = false + writeLog("checking hash for \(filename)", status: .debug) + if let storedHash = getValueForKey(filename, inArray: shasumArray) { + writeLog("stored hash : \(storedHash)", status: .debug) + let url = URL(fileURLWithPath: filename) + if let fileHash = sha256(for: url) { + writeLog("file hash : \(fileHash)", status: .debug) + if storedHash == fileHash { + proceed = true + } + } + } + if !proceed { + writeLog("file hash mismatch for: \(filename). Skipping", status: .error) + } + + return proceed +} func sha256(for url: URL) -> String? { // computes a sha256sum for the specified file path and returns a string diff --git a/outset/Functions/Processing.swift b/outset/Functions/Processing.swift index 1e07377..c92205f 100644 --- a/outset/Functions/Processing.swift +++ b/outset/Functions/Processing.swift @@ -22,6 +22,9 @@ func processItems(_ path: String, delete_items : Bool=false, once : Bool=false, var profiles : [String] = [] // profiles aren't supported anyway so we could delete this var runOnceDict : [String:Date] = [:] + let shasumFileList = shasumLoadApprovedFileHashList() + let shasumsAvailable = !shasumFileList.isEmpty + // See if there's any old stuff to migrate migrateLegacyPreferences() @@ -50,6 +53,10 @@ func processItems(_ path: String, delete_items : Bool=false, once : Bool=false, // loop through the packages list and process installs. // TODO: add in hash comparison for processing packages presuming package installs as a feature is maintained. for package in packages { + if shasumsAvailable && !verifySHASUMForFile(filename: package, shasumArray: shasumFileList) { + continue + } + if once { if !runOnceDict.contains(where: {$0.key == package}) { if installPackage(pkg: package) { @@ -82,27 +89,8 @@ func processItems(_ path: String, delete_items : Bool=false, once : Bool=false, // loop through the scripts list and process. for script in scripts { - if shasumsAvailable { - // check user defaults for a list of sha256 hashes. - // This block will run if there are _any_ hashes available so it's all or nothing (by design) - // If there is no hash or it doesn't match then we skip to the next file - - var proceed = false - writeLog("checking hash for \(script)", status: .debug) - if let storedHash = getValueForKey(script, inArray: shasumFileList) { - writeLog("stored hash : \(storedHash)", status: .debug) - let url = URL(fileURLWithPath: script) - if let fileHash = sha256(for: url) { - writeLog("file hash : \(fileHash)", status: .debug) - if storedHash == fileHash { - proceed = true - } - } - } - if !proceed { - writeLog("file hash mismatch for: \(script). Skipping", status: .error) - continue - } + if shasumsAvailable && !verifySHASUMForFile(filename: script, shasumArray: shasumFileList) { + continue } if once { diff --git a/outset/Outset.swift b/outset/Outset.swift index 7eb5999..a21618e 100644 --- a/outset/Outset.swift +++ b/outset/Outset.swift @@ -44,9 +44,6 @@ var ignoredUsers : [String] = [] var loginOnceOverride : [String: Date] = [String: Date]() var continueFirstBoot : Bool = true var prefs = loadPreferences() -var shasumFileList = shasumLoadApprovedFileHashList() -var shasumsAvailable = !shasumFileList.isEmpty - // Logic insertion point @main From 89e3172fcfe7391375ccce1d0424af75d919cc8d Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 22:19:55 +1100 Subject: [PATCH 12/13] Update License --- LICENSE.md | 25 ------------------------- README.md | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 261eeb9..d9a10c0 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -174,28 +174,3 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md index 985c3f8..af36b42 100644 --- a/README.md +++ b/README.md @@ -75,16 +75,16 @@ Add your developer certificate in the signing and capabilities of the "Outset Ap License ------- - Copyright Joseph Chilcote + Copyright 2023 Bart Reardon - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 8515228ad95805e453df9d35d46bb29a063892c9 Mon Sep 17 00:00:00 2001 From: Bart Reardon Date: Wed, 1 Mar 2023 22:21:14 +1100 Subject: [PATCH 13/13] add LICENSE.md to project --- outset.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/outset.xcodeproj/project.pbxproj b/outset.xcodeproj/project.pbxproj index 73b1b10..1cde4fb 100644 --- a/outset.xcodeproj/project.pbxproj +++ b/outset.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 41A67A0C296BADFE000BFFCE /* Outset Install Package.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Outset Install Package.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 41A67A13296BF35F000BFFCE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 41ADC47C29AECB8B00C5B94C /* outset */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = outset; sourceTree = ""; }; + 41ADC47E29AF649C00C5B94C /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,6 +63,7 @@ isa = PBXGroup; children = ( 41A67A13296BF35F000BFFCE /* README.md */, + 41ADC47E29AF649C00C5B94C /* LICENSE.md */, 41A679EA29659F4E000BFFCE /* Package */, 4124EFAE29414A5D003B00F4 /* Outset */, 4124EF8E293822F4003B00F4 /* Products */,