Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e3ccaf4
feat: add macOS (darwin) support to the worker agent installer
andychoquette Jul 16, 2026
c123104
fix: gate immediate launchd start on --start, keep start-on-boot always
andychoquette Jul 17, 2026
06c03e8
test: add install_darwin.sh integration tests, run them on macOS in CI
andychoquette Jul 17, 2026
d1ef839
fix: restore a previously-loaded service on re-install and tolerate t…
andychoquette Jul 20, 2026
f27cace
fix: stop mypy from following imports into numpy type stubs
andychoquette Jul 20, 2026
19c7f03
Merge remote-tracking branch 'origin/mainline' into macos-installer
andychoquette Jul 27, 2026
a1ac630
fix: emit ProgramArguments without word-splitting and XML-escape the …
andychoquette Jul 27, 2026
82e3945
test: plumb OPERATING_SYSTEM=macos through the e2e suite
andychoquette Jul 27, 2026
f6e5f84
test: add macOS GPU (Metal) compute e2e test
andychoquette Jul 28, 2026
c4e717e
Merge branch 'mainline' into macos-installer
andychoquette Aug 3, 2026
4e16600
fix(test): use platform-correct commands in e2e tests on macOS
andychoquette Aug 4, 2026
ba38cbd
fix(installer): stop restarting a healthy agent on macOS re-install
andychoquette Aug 4, 2026
0ccac4a
fix(installer): grant the agent passwordless sudo to job users on macOS
andychoquette Aug 4, 2026
3ef966f
Merge branch 'mainline' into macos-installer
andychoquette Aug 5, 2026
6cd5ccc
test: fix jobRunAsUser sudoers assertion contradicting its own fixture
andychoquette Aug 5, 2026
0accccf
docs: correct the sudoers rationale and state which queue modes it af…
andychoquette Aug 5, 2026
4bb1dd4
fix(installer): validate the shutdown sudoers file before installing it
andychoquette Aug 6, 2026
4137c8d
Merge remote-tracking branch 'upstream/mainline' into macos-installer
andychoquette Aug 7, 2026
933275b
test: verify the installed LaunchDaemon starts the agent from cold
andychoquette Aug 7, 2026
f4084fb
Merge branch 'mainline' into macos-installer
andychoquette Aug 7, 2026
433f15a
fix(installer): do not configure jobRunAsUser sudo on macOS
andychoquette Aug 7, 2026
c44d368
refactor(installer): rename to install_macos.sh and clarify id lookup
andychoquette Aug 10, 2026
3acd20a
Merge branch 'mainline' into macos-installer
andychoquette Aug 11, 2026
69e1052
chore(deps): require openjd-sessions 0.10.14 for the macOS setsid shim
andychoquette Aug 11, 2026
433ed0f
fix(installer): refuse a shared primary group, adopt the account's ho…
andychoquette Aug 11, 2026
cc4409e
fix(installer): do not take over a shared home directory; fix dscl pa…
andychoquette Aug 11, 2026
3580cb6
fix(installer): close the broad-group bypasses, parse dscl values str…
andychoquette Aug 11, 2026
6a96749
fix(test): make the e2e suite type-check
andychoquette Aug 12, 2026
a8906ef
fix(installer): resolve the primary group exactly, and fail early whe…
andychoquette Aug 12, 2026
2ed6823
Merge branch 'mainline' into macos-installer
andychoquette Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/macos_installer_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: macOS Installer Test

# Runs the install_macos.sh integration tests (test/integ/macos/test_installer.py)
# on a macOS runner. The tests execute the installer for real (as root) and assert
# the security invariants it must establish: user/group isolation, file modes, the
# LaunchDaemon plist, and the --allow-shutdown sudoers rule. They mutate host state, so they are
# gated behind RUN_INSTALLER_TESTS=true and belong on throwaway CI runners only.
# No AWS access is needed: the farm/fleet ids are fakes and the agent never
# successfully starts.

# Runs on every PR rather than behind a paths filter: the installer's behaviour depends
# on more than the installer directory (config defaults, the settings model, the launchd
# label used by the e2e suite), and a filtered job that misses one of those reads as a
# pass.
on:
workflow_dispatch:
pull_request:
branches: [ mainline, release ]

jobs:
macos-installer:
name: Python ${{ matrix.python-version }}
runs-on: macos-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
# Matches code_quality.yml and requires-python >=3.9.
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: ${{ matrix.python-version }}

- name: Install the worker agent from this checkout
run: |
set -euxo pipefail
sudo mkdir -p /opt/wa-venv
sudo chown "$(id -un)" /opt/wa-venv
python -m venv /opt/wa-venv
/opt/wa-venv/bin/pip install --quiet .
# The installer resolves deadline-worker-agent in --scripts-path and
# validates --python-interpreter-path exists.
test -x /opt/wa-venv/bin/deadline-worker-agent

- name: Install Hatch
# virtualenv 21 removed an API that the hatch version resolvable on 3.9 still calls,
# so `hatch run` fails there with "Environment `default` is incompatible". Same
# constraint the other workflows in this repo use.
run: pip install --upgrade hatch 'virtualenv<21; python_version < "3.10"'

- name: Run integration tests
env:
RUN_INSTALLER_TESTS: "true"
WA_VENV_BIN: /opt/wa-venv/bin
# Runs the whole integ suite (the hatch script targets test/integ); the
# installer tests activate via RUN_INSTALLER_TESTS and rely on their
# in-file order (VFS rejection asserts a pristine system, install
# fixtures build on it), which is pytest's default ordering.
run: hatch run integ-test -v
14 changes: 13 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies = [
"boto3 >= 1.34.75",
"deadline-job-attachments == 0.1.3",
# Pinned to patch version due to Host Config Script runner usage of private OpenJD Sessions API.
"openjd-sessions == 0.10.13",
"openjd-sessions == 0.10.14",
"openjd-model >= 0.11.1, < 0.12",
# tomli became tomllib in standard library in Python 3.11
"tomli == 2.0.* ; python_version<'3.11'",
Expand Down Expand Up @@ -124,6 +124,17 @@ module = [
"botocore.*"
]

# numpy is not used by this package; it arrives transitively (via
# deadline-cloud-test-fixtures >= 0.18.16) and mypy reaches its stubs through
# pytest's `approx` implementation. numpy 2.5+ type stubs use PEP 695 `type`
# statements, which mypy rejects while python_version is pinned below 3.12, so
# do not follow imports into it. follow_imports_for_stubs is required for
# follow_imports to apply to .pyi files like numpy/__init__.pyi.
[[tool.mypy.overrides]]
module = [ "numpy", "numpy.*" ]
follow_imports = "skip"
follow_imports_for_stubs = true

[tool.ruff]
line-length = 100

Expand All @@ -140,6 +151,7 @@ ignore = [
# This causes imports to come after regular Python statements causing flake8 rule E402 to be flagged
"src/deadline_worker_agent/**/*win*.py" = ["E402"]
"test/**/*windows*.py" = ["E402"]
"test/integ/macos/*.py" = ["E402"]

[tool.ruff.lint.isort]
known-first-party = [
Expand Down
12 changes: 11 additions & 1 deletion src/deadline_worker_agent/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .config_file import ConfigFile

import os
import sys


# Default path for the worker's logs.
Expand All @@ -28,6 +29,9 @@
)

DEFAULT_POSIX_SESSION_ROOT_DIR = Path("/sessions")
# macOS seals the root volume read-only (macOS 10.15+), so a top-level directory like
# /sessions cannot be created. Use a path under /var (writable) instead.
DEFAULT_MACOS_SESSION_ROOT_DIR = Path("/var/lib/deadline/sessions")
DEFAULT_WINDOWS_SESSION_ROOT_DIR: Path = (
Path(os.getenv("PROGRAMDATA", "C:\\ProgramData")) / "Amazon" / "OpenJD"
)
Expand Down Expand Up @@ -129,7 +133,13 @@ class WorkerSettings(BaseSettings):
session_runtime: SessionRuntimeKind = SessionRuntimeKind.PYTHON
telemetry_opt_out: bool = False
session_root_dir: Path = (
DEFAULT_WINDOWS_SESSION_ROOT_DIR if os.name == "nt" else DEFAULT_POSIX_SESSION_ROOT_DIR
DEFAULT_WINDOWS_SESSION_ROOT_DIR
if os.name == "nt"
else (
DEFAULT_MACOS_SESSION_ROOT_DIR
if sys.platform == "darwin"
else DEFAULT_POSIX_SESSION_ROOT_DIR
)
)

class Config:
Expand Down
16 changes: 14 additions & 2 deletions src/deadline_worker_agent/installer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import sysconfig

from deadline_worker_agent.config.settings import (
DEFAULT_MACOS_SESSION_ROOT_DIR,
DEFAULT_POSIX_SESSION_ROOT_DIR,
DEFAULT_WINDOWS_SESSION_ROOT_DIR,
)
Expand All @@ -25,6 +26,7 @@

INSTALLER_PATH = {
"linux": Path(__file__).parent / "install.sh",
"darwin": Path(__file__).parent / "install_macos.sh",
}


Expand Down Expand Up @@ -70,14 +72,20 @@ def _get_ec2_region() -> Optional[str]:
def install() -> None:
"""Installer entrypoint for the AWS Deadline Cloud Worker Agent"""

if sys.platform not in ["linux", "win32"]:
if sys.platform not in ["linux", "darwin", "win32"]:
print(f"ERROR: Unsupported platform {sys.platform}")
sys.exit(1)

arg_parser = get_argument_parser()
args = arg_parser.parse_args(namespace=ParsedCommandLineArguments())
scripts_path = Path(sysconfig.get_path("scripts"))

# The Deadline Virtual File System (VFS) is not supported on macOS. Reject the option here
# so the error surfaces before we shell out to install_macos.sh (which also rejects it).
if sys.platform == "darwin" and args.vfs_install_path:
print("ERROR: --vfs-install-path is not supported on macOS.")
sys.exit(1)

if args.region is None:
args.region = _get_ec2_region()
if args.region is None:
Expand Down Expand Up @@ -275,7 +283,11 @@ def get_argument_parser() -> ArgumentParser: # pragma: no cover
default=(
str(DEFAULT_WINDOWS_SESSION_ROOT_DIR)
if sys.platform == "win32"
else str(DEFAULT_POSIX_SESSION_ROOT_DIR)
else (
str(DEFAULT_MACOS_SESSION_ROOT_DIR)
if sys.platform == "darwin"
else str(DEFAULT_POSIX_SESSION_ROOT_DIR)
)
), # pragma: nocover
)

Expand Down
Loading
Loading