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/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,176 @@ + 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 diff --git a/Package/generatePackage.zsh b/Package/generatePackage.zsh index a332c36..4ca40fc 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,8 @@ 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}" +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}" @@ -36,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 + 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/README.md b/README.md index 9d3fb02..af36b42 100644 --- a/README.md +++ b/README.md @@ -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` @@ -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. diff --git a/outset.xcodeproj/project.pbxproj b/outset.xcodeproj/project.pbxproj index 85bcb9a..1cde4fb 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,12 +37,14 @@ 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 = ""; }; 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 */ @@ -60,8 +63,8 @@ isa = PBXGroup; children = ( 41A67A13296BF35F000BFFCE /* README.md */, + 41ADC47E29AF649C00C5B94C /* LICENSE.md */, 41A679EA29659F4E000BFFCE /* Package */, - 4124EF8F293822F4003B00F4 /* outset */, 4124EFAE29414A5D003B00F4 /* Outset */, 4124EF8E293822F4003B00F4 /* Products */, 4124EFC029414AD1003B00F4 /* Frameworks */, @@ -77,18 +80,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 */, @@ -99,11 +94,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 = ""; @@ -128,6 +124,7 @@ children = ( 41A67A03296B93D1000BFFCE /* Scripts */, 41A679FE2966479E000BFFCE /* generatePackage.zsh */, + 41ADC47C29AECB8B00C5B94C /* outset */, 4124EFCD29446FDE003B00F4 /* Library */, ); path = Package; @@ -242,6 +239,7 @@ "$(SRCROOT)/Package/generatePackage.zsh", "$(SRCROOT)/Package/Library", "$(SRCROOT)/Package/Scripts", + "$(SRCROOT)/Package/outset", ); name = "Generate Package"; outputFileListPaths = ( @@ -262,6 +260,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 */, ); @@ -494,7 +493,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; @@ -536,7 +535,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; 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 9a57476..0000000 Binary files a/outset.xcodeproj/project.xcworkspace/xcuserdata/rea094.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/outset/Functions/FileUtils.swift b/outset/Functions/FileUtils.swift index 04dfcf5..0fa92e8 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)" @@ -250,6 +250,26 @@ func detach_dmg(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 @@ -268,7 +288,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..c92205f 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) } @@ -22,15 +22,18 @@ func process_items(_ 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 - 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 +48,18 @@ 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 shasumsAvailable && !verifySHASUMForFile(filename: package, shasumArray: shasumFileList) { + continue + } + 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 +67,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,27 +89,8 @@ 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 { - // 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) { - 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 { @@ -141,26 +129,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 +167,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 +178,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/main.swift b/outset/Functions/ServiceManagement.swift similarity index 93% rename from outset/main.swift rename to outset/Functions/ServiceManagement.swift index 1cace5f..b32eb19 100644 --- a/outset/main.swift +++ b/outset/Functions/ServiceManagement.swift @@ -9,13 +9,10 @@ import Foundation import ServiceManagement -func main() { - init_daemons() - //∫exit(0) -} - 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 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 ed52dec..a21618e 100644 --- a/outset/Outset.swift +++ b/outset/Outset.swift @@ -10,50 +10,47 @@ 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 // 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() // Logic insertion point @main 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 +88,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) @@ -105,40 +102,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 +143,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 +159,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 +198,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 +234,7 @@ struct Outset: ParsableCommand { prefs.ignored_users.append(username) } } - write_outset_preferences(prefs: prefs) + writePreferences(prefs: prefs) } if !removeIgnoredUser.isEmpty { @@ -247,32 +244,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 {