Skip to content

Latest commit

 

History

History
71 lines (45 loc) · 9.98 KB

File metadata and controls

71 lines (45 loc) · 9.98 KB

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Fixed

  • Background script timeout documentation (CHANGELOG.md, Preferences.swift, ItemProcessing.swift, closes #79): the 4.3.0 notes and code comments said background_script_timeout had no default and that Outset waits indefinitely when it is unset. The actual default is 30 seconds; set the key to 0 to disable the timeout. Behaviour is unchanged — only the documentation has been corrected.

  • DMG-delivered packages were never installed (DMG.swift, Packages.swift): mountDmg returned the entire multi-line output of hdiutil attach (device nodes and content hints) as if it were the mount point, so the DMG's contents were never found and no packages inside it were installed. This persisted after the 4.3.0 DMG fix. The image is now attached with -plist and the mount point is read from the system-entities array. If an image cannot be mounted, the item is logged and skipped instead of being processed with an invalid path. Image and package paths are now passed as arguments rather than interpolated into the command string, so paths containing spaces work.

  • wait_for_network enabled whenever the key existed (Preferences.swift): in the root context, wait_for_network was evaluated as "key is present" rather than by its value, so an explicit false still enabled the network wait. Because every --boot run (including the one performed by the installer postinstall) writes all preferences back to /Library/Preferences, the key was present on every installation and the network wait was effectively always on. Waiting unloads and reloads com.apple.loginwindow, and a reloaded loginwindow does not perform automatic login, so installing Outset silently broke auto-login. The key's boolean value is now used, falling back to the default (false) when the key is absent or not a boolean.

[4.3.0] - 2026-03-05

Fixed

  • Managed profile payload processing (Login.swift, Payloads.swift, closes #64): three related bugs prevented scripts delivered via MDM managed profiles from running correctly when local Outset directories were empty. (1) The login-privileged trigger was only created when the local login-privileged-once/every directories were non-empty, so privileged scripts delivered exclusively via payload were silently skipped. The condition now also checks whether the payload itself contains privileged scripts. (2) processPayloadScripts() returned true (suppressing local directory fallback) even when the scripts dictionary was present but empty; it now tracks whether any script was actually dispatched. (3) Boot-once run-once tracking was written via a redundant manual writeRunOncePlist call after processScripts(), bypassing the deduplication check inside processScripts; the redundant call has been removed.

  • DMG package installation (installPackage): corrected a variable assignment bug where the DMG mount path was used as the package path instead of the actual .pkg/.mpkg file path. Also fixed a loop that overwrote a single variable on each iteration, meaning only the last package in a DMG was ever installed. Packages found inside a mounted DMG are now collected into an array and each is installed in turn. The DMG is detached synchronously after all packages have been processed (no change in behaviour — runShellCommand has always been synchronous).

  • Network timeout (Boot.swift): the configured network_timeout preference value was being divided by 10 before being passed to waitForNetworkUp, causing the actual wait to be ten times shorter than configured. For example, a configured value of 300 seconds would only wait 30 seconds. The value is now passed directly.

  • Privileged login trigger (Login.swift): the condition that fires the login-privileged launchd trigger used || (OR) instead of && (AND), meaning the trigger would only fire when both the login-privileged-once and login-privileged-every directories were non-empty. The corrected condition fires the trigger when either directory has content.

  • Array bounds crash (computeChecksum): accessing files[0] before checking whether the array was empty could cause an index-out-of-bounds crash when called with an empty argument list. An early return guard has been added.

  • Log file permissions (writeFileLog): newly created log files were given world-writable permissions (0o666). Permissions are now 0o644.

  • Log file path is now context-aware (Globals.swift, writeFileLog): Outset runs in both root context (boot, login-privileged, on-demand-privileged) and user context (login-every, login-once, on-demand). The previous single log path (/usr/local/outset/logs/outset.log) is in a root-owned directory, so user-context runs could not write to it. The log path is now determined at runtime: root context continues to log to /usr/local/outset/logs/outset.log; user-context runs log to ~/Library/Logs/outset.log. Log file permissions are 0o644 in both cases (previously 0o666).

  • Force unwraps (writeFileLog, runShellCommand): force-unwrapped String(data:encoding:) and Data(string.utf8) calls have been replaced with nil-coalescing fallbacks, removing potential crash points if encoding unexpectedly fails.

  • LaunchDaemon Program key (io.macadmins.Outset.on-demand-privileged.plist, closes #72): the Program key was incorrectly typed as an <array> instead of a <string>. All LaunchDaemon plists now use the correct <string> type for the Program key as required by launchd.

Changed

  • Console user is no longer a global variable: consoleUser was previously stored as a mutable global, making it difficult to reason about and impossible to substitute in tests. It is now captured once at startup in Outset.run() and passed explicitly through the call chain to all functions that require it (processLoginTasks, processLoginPrivilegedTasks, processLoginEveryTasks, processLoginOnceTasks, processOnDemandTasks, processOnDemandPrivilegedTasks, processItems, processScripts, processPayloadScripts). Two additional globals that were only ever used inside Boot.swift (loginwindowState, continueFirstBoot) have been moved to local variables in that file.

Added

  • Unit test target (OutsetTests): a new OutsetTests test target has been added to the Xcode project using Swift Testing (@Suite, @Test, #expect). 27 tests cover:

    • runIfNotIgnoredUser — ignored-user skip logic and action execution
    • checkFileExists / checkDirectoryExists / folderContents — core file utilities
    • createTrigger / pathCleanup — trigger file creation and directory cleanup
    • sha256 / verifySHASUMForFile / computeChecksum — checksum utilities including the empty-array guard
    • OutsetPreferences — default values, CodingKeys names, and JSON round-trip
  • Test coverage documentation (UntestableFunctionality.swift): a source file in the test target documents every function not covered by automated tests, with a specific reason for each exclusion (REQUIRES_ROOT, REQUIRES_FILESYSTEM_FIXTURE, REQUIRES_REAL_SHELL, REQUIRES_SYSTEM_STATE, REQUIRES_MDM, SIDE_EFFECTS_ONLY).

  • Background script execution (ItemProcessing.swift, Shell.swift, Preferences.swift): scripts whose filename begins with an underscore (e.g. _my-task.sh) are now dispatched concurrently on background threads while the remaining foreground scripts continue to execute sequentially. Outset waits for all background tasks to finish before it exits, so the overall run is not complete until every script has returned. Key details:

    • Background scripts are dispatched before foreground scripts begin, so they start immediately.
    • All log output from background scripts is tagged [BG:pid=N] and streamed line-by-line in real time, allowing background and foreground log lines to interleave naturally.
    • Run-once semantics are fully supported for background scripts; the run-once record is written only on successful exit, with thread-safe access via a serial dispatch queue.
    • A per-script timeout watchdog terminates a background script that exceeds the configured limit and logs an error.
    • A new optional preference key background_script_timeout (integer, seconds, default 30) sets the per-script timeout. Set it to 0 to wait indefinitely for background scripts to exit.
  • Ed25519 script signing (Checksum.swift, ItemProcessing.swift, Preferences.swift, Outset.swift): scripts can now be signed with an Ed25519 private key, with the signature embedded directly in the script as a # ed25519: <base64sig> comment. When an MDM-delivered public key (manifest_signing_key preference) is present, every script must carry a valid embedded signature — scripts without a valid signature are refused with an error log. Key details:

    • The signed payload is the script content with any existing # ed25519: comment line stripped, so the signature is stable across re-signing.
    • The public key is delivered via MDM only (as a base64-encoded 32-byte raw Ed25519 key in manifest_signing_key). The private key never leaves the admin workstation.
    • Signing is fully self-contained: scripts with embedded signatures are version-control friendly and require no separate manifest file.
    • Applies to all script processing paths including MDM payload scripts (which carry their embedded signature in the base64-encoded script body).
    • New CLI commands: --generate-keypair generates a fresh Ed25519 keypair and prints both keys; --sign-script-file <path> --signing-key <private-key-base64> signs one or more scripts in place; --verify-script <path> --public-key <public-key-base64> verifies embedded signatures without executing the script (exits non-zero if any file fails, suitable for CI).

[4.0] - 2023-03-23

Added

  • Initial automated build of Outset