Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,10 +358,17 @@ bash scripts/lume/create-base-vm.sh --config config/lume-runners.yaml --env .env
bash scripts/lume/setup-base-vm.sh --config config/lume-runners.yaml --env .env
bash scripts/lume/reconcile-pool.sh --config config/lume-runners.yaml --env .env
bash scripts/lume/status.sh --config config/lume-runners.yaml --env .env
bash scripts/lume/install-runtime.sh
bash scripts/lume/install-launch-agent.sh
sudo bash scripts/lume/install-system-launch-daemons.sh --disable-user-lume-agent
```

Keep the Lume runner env file outside git and locked down with `chmod 600`. The host controller reads that file and copies it into each guest VM just before starting the guest bootstrap. Do not bake GitHub credentials into the base VM image. If you want the macOS/base-image pipeline to stay pinned to a specific GitHub Actions runner build, set `pool.runnerVersion` in `config/lume-runners.yaml`; otherwise it falls back to `RUNNER_VERSION` from the env file.

The launchd installers publish a source-independent controller runtime under `~/Library/Application Support/github-runner-fleet/controller/current` and point the Lume pool job at that path. Runtime `.env` remains beside that controller at `~/Library/Application Support/github-runner-fleet/controller/.env`, is mode `0600`, and is preserved when the source checkout moves or the installer is rerun. Override the runtime root with `GITHUB_RUNNER_FLEET_RUNTIME_ROOT` if this Mac needs a different stable location.

If launchd reports `Bootstrap failed: 5: Input/output error`, check the disabled override first with `launchctl print-disabled system | rg github-runner-fleet` or `launchctl print-disabled gui/$(id -u) | rg github-runner-fleet`. The installers clear their own disabled overrides before bootstrapping; the system installer only disables the per-user Lume jobs after the root services load successfully.

`create-base-vm.sh` now caches the macOS IPSW under `LUME_RUNNER_BASE_DIR/cache/` by default so rebuilding the base image does not re-download the restore image every time. Override that path with `LUME_RUNNER_IPSW_PATH` if you want the cache elsewhere. If unattended setup drifts or gets interrupted, rerun `scripts/lume/setup-base-vm.sh` against the existing base VM instead of deleting and recreating it.

## Security Notes
Expand Down
20 changes: 16 additions & 4 deletions scripts/lume/install-launch-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set -Eeuo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
source "${SCRIPT_DIR}/install-runtime.sh"

LAUNCH_AGENT_LABEL="com.omt.github-runner-fleet.lume-pool"
LAUNCH_AGENTS_DIR="${HOME}/Library/LaunchAgents"
Expand All @@ -12,6 +13,8 @@ STDOUT_PATH="${LOG_DIR}/lume-pool.stdout.log"
STDERR_PATH="${LOG_DIR}/lume-pool.stderr.log"
DOMAIN_TARGET="gui/$(id -u)"

export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${HOME}/.local/bin:${PATH:-}"

usage() {
cat <<EOF
Usage: $(basename "$0")
Expand All @@ -34,6 +37,8 @@ require_command() {

write_plist() {
local rtk_path="$1"
local runtime_repo="$2"
local runtime_env="$3"
local temp_path

temp_path="$(mktemp)"
Expand All @@ -49,10 +54,10 @@ write_plist() {
<string>${rtk_path}</string>
<string>bash</string>
<string>-lc</string>
<string>cd '${REPO_ROOT}' &amp;&amp; exec bash scripts/lume/reconcile-pool.sh --config config/lume-runners.yaml --env .env</string>
<string>cd '${runtime_repo}' &amp;&amp; exec bash scripts/lume/reconcile-pool.sh --config config/lume-runners.yaml --env '${runtime_env}'</string>
</array>
<key>WorkingDirectory</key>
<string>${REPO_ROOT}</string>
<string>${runtime_repo}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
Expand All @@ -75,11 +80,14 @@ write_plist() {
EOF

plutil -lint "${temp_path}" >/dev/null
mv "${temp_path}" "${PLIST_PATH}"
install -m 0644 "${temp_path}" "${PLIST_PATH}"
rm -f "${temp_path}"
}

main() {
local rtk_path
local runtime_repo
local runtime_env

if [[ $# -gt 0 ]]; then
case "$1" in
Expand All @@ -100,11 +108,15 @@ main() {
require_command rtk

rtk_path="$(command -v rtk)"
install_lume_controller_runtime "${HOME}"
runtime_repo="$(lume_controller_runtime_repo "${HOME}")"
runtime_env="$(lume_controller_runtime_env "${HOME}")"

mkdir -p "${LAUNCH_AGENTS_DIR}" "${LOG_DIR}"
write_plist "${rtk_path}"
write_plist "${rtk_path}" "${runtime_repo}" "${runtime_env}"

launchctl bootout "${DOMAIN_TARGET}" "${PLIST_PATH}" >/dev/null 2>&1 || true
launchctl enable "${DOMAIN_TARGET}/${LAUNCH_AGENT_LABEL}"
launchctl bootstrap "${DOMAIN_TARGET}" "${PLIST_PATH}"
launchctl enable "${DOMAIN_TARGET}/${LAUNCH_AGENT_LABEL}"
launchctl kickstart -k "${DOMAIN_TARGET}/${LAUNCH_AGENT_LABEL}"
Expand Down
184 changes: 184 additions & 0 deletions scripts/lume/install-runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env bash
set -Eeuo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"

usage() {
cat <<EOF
Usage: $(basename "$0") [options]

Install a source-independent controller runtime for Lume launchd jobs.

Options:
--target-home PATH Home directory that owns the runtime (default: ${HOME})
--target-user USER User that should own the runtime after install
--target-group GRP Group that should own the runtime after install
-h, --help Show this help text

Environment:
GITHUB_RUNNER_FLEET_RUNTIME_ROOT Override the runtime root path
EOF
}

require_command() {
local command_name="$1"

if ! command -v "${command_name}" >/dev/null 2>&1; then
echo "missing required command: ${command_name}" >&2
exit 1
fi
}

lume_controller_runtime_root() {
local target_home="$1"

if [[ -n "${GITHUB_RUNNER_FLEET_RUNTIME_ROOT:-}" ]]; then
printf '%s\n' "${GITHUB_RUNNER_FLEET_RUNTIME_ROOT}"
return 0
fi

printf '%s/Library/Application Support/github-runner-fleet/controller\n' "${target_home}"
}

lume_controller_runtime_repo() {
local target_home="$1"
printf '%s/current\n' "$(lume_controller_runtime_root "${target_home}")"
}

lume_controller_runtime_env() {
local target_home="$1"
printf '%s/.env\n' "$(lume_controller_runtime_root "${target_home}")"
}

write_default_runtime_env() {
local env_path="$1"
local target_home="$2"
local lume_base_dir="${target_home}/Library/Application Support/github-runner-fleet/lume"
local temp_path

temp_path="$(mktemp)"
cat > "${temp_path}" <<EOF
GITHUB_PAT=
GITHUB_API_URL=https://api.github.com
LUME_RUNNER_BASE_DIR='${lume_base_dir}'
LUME_RUNNER_ENV_FILE='${lume_base_dir}/runner.env'
COMPOSE_PROJECT_NAME=github-runner-fleet
RUNNER_VERSION=2.333.0
EOF
install -m 0600 "${temp_path}" "${env_path}"
rm -f "${temp_path}"
}

repair_default_runtime_env() {
local env_path="$1"
local target_home="$2"
local unquoted_lume_base="${target_home}/Library/Application Support/github-runner-fleet/lume"

if grep -qx 'GITHUB_PAT=' "${env_path}" \
&& grep -qx "LUME_RUNNER_BASE_DIR=${unquoted_lume_base}" "${env_path}"; then
write_default_runtime_env "${env_path}" "${target_home}"
fi
}

install_runtime_dependencies() {
local runtime_repo="$1"
local target_home="$2"
local target_user="$3"
local path_value="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${target_home}/.local/bin"

if [[ -n "${target_user}" && "${EUID}" -eq 0 ]]; then
require_command sudo
sudo -u "${target_user}" \
env HOME="${target_home}" PATH="${path_value}" \
pnpm --dir "${runtime_repo}" install --frozen-lockfile
return 0
fi

HOME="${target_home}" PATH="${path_value}" \
pnpm --dir "${runtime_repo}" install --frozen-lockfile
}

install_lume_controller_runtime() {
local target_home="${1:-${HOME}}"
local target_user="${2:-}"
local target_group="${3:-}"
local runtime_root
local runtime_repo
local runtime_env

require_command install
require_command pnpm
require_command rsync

runtime_root="$(lume_controller_runtime_root "${target_home}")"
runtime_repo="$(lume_controller_runtime_repo "${target_home}")"
runtime_env="$(lume_controller_runtime_env "${target_home}")"

mkdir -p "${runtime_root}" "${runtime_repo}"
rsync -a --delete \
--exclude '.git/' \
--exclude 'node_modules/' \
--exclude '.pnpm-store/' \
--exclude '.env' \
"${REPO_ROOT}/" "${runtime_repo}/"

if [[ -f "${runtime_env}" ]]; then
repair_default_runtime_env "${runtime_env}" "${target_home}"
elif [[ -f "${REPO_ROOT}/.env" ]]; then
install -m 0600 "${REPO_ROOT}/.env" "${runtime_env}"
else
write_default_runtime_env "${runtime_env}" "${target_home}"
fi

if [[ -n "${target_user}" && -n "${target_group}" && "${EUID}" -eq 0 ]]; then
chown -R "${target_user}:${target_group}" "${runtime_root}"
fi

install_runtime_dependencies "${runtime_repo}" "${target_home}" "${target_user}"

if [[ -n "${target_user}" && -n "${target_group}" && "${EUID}" -eq 0 ]]; then
chown -R "${target_user}:${target_group}" "${runtime_root}"
fi

printf 'runtime_root=%s\nruntime_repo=%s\nruntime_env=%s\n' \
"${runtime_root}" "${runtime_repo}" "${runtime_env}"
}

main() {
local target_home="${HOME}"
local target_user=""
local target_group=""

while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
return 0
;;
--target-home)
target_home="$2"
shift 2
;;
--target-user)
target_user="$2"
shift 2
;;
--target-group)
target_group="$2"
shift 2
;;
*)
usage >&2
echo "unknown argument: $1" >&2
return 1
;;
esac
done

install_lume_controller_runtime "${target_home}" "${target_user}" "${target_group}"
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
main "$@"
fi
41 changes: 37 additions & 4 deletions scripts/lume/install-system-launch-daemons.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ set -Eeuo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
source "${SCRIPT_DIR}/install-runtime.sh"

TARGET_USER="${SUDO_USER:-$(stat -f '%Su' /dev/console)}"
TARGET_HOME="$(dscl . -read "/Users/${TARGET_USER}" NFSHomeDirectory | awk '{print $2}')"
TARGET_GROUP="$(id -gn "${TARGET_USER}")"
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${TARGET_HOME}/.local/bin:${PATH:-}"
LUME_LABEL="com.omt.github-runner-fleet.lume-serve"
POOL_LABEL="com.omt.github-runner-fleet.lume-pool.system"
DAEMON_DIR="/Library/LaunchDaemons"
LOG_DIR="${TARGET_HOME}/Library/Logs/github-runner-fleet"
LUME_PLIST_PATH="${DAEMON_DIR}/${LUME_LABEL}.plist"
POOL_PLIST_PATH="${DAEMON_DIR}/${POOL_LABEL}.plist"
USER_LUME_AGENT_PATH="${TARGET_HOME}/Library/LaunchAgents/com.trycua.lume_daemon.plist"
USER_POOL_AGENT_LABEL="com.omt.github-runner-fleet.lume-pool"
USER_POOL_AGENT_PATH="${TARGET_HOME}/Library/LaunchAgents/${USER_POOL_AGENT_LABEL}.plist"
DISABLE_USER_LUME_AGENT="false"
DISABLE_USER_POOL_AGENT="true"

usage() {
cat <<EOF
Expand All @@ -24,6 +29,7 @@ Install root-owned launchd services for Lume itself and the system-wide pool rec

Options:
--disable-user-lume-agent Disable the per-user Lume launch agent if present
--keep-user-pool-agent Keep the per-user pool launch agent loaded
-h, --help Show this help text
EOF
}
Expand All @@ -38,6 +44,10 @@ while [[ $# -gt 0 ]]; do
DISABLE_USER_LUME_AGENT="true"
shift
;;
--keep-user-pool-agent)
DISABLE_USER_POOL_AGENT="false"
shift
;;
*)
usage >&2
echo "unknown argument: $1" >&2
Expand Down Expand Up @@ -119,6 +129,8 @@ EOF

write_pool_plist() {
local rtk_path="$1"
local runtime_repo="$2"
local runtime_env="$3"
local temp_path

temp_path="$(mktemp)"
Expand All @@ -138,10 +150,10 @@ write_pool_plist() {
<string>${rtk_path}</string>
<string>bash</string>
<string>-lc</string>
<string>cd '${REPO_ROOT}' &amp;&amp; exec bash scripts/lume/reconcile-pool.sh --config config/lume-runners.yaml --env .env</string>
<string>cd '${runtime_repo}' &amp;&amp; exec bash scripts/lume/reconcile-pool.sh --config config/lume-runners.yaml --env '${runtime_env}'</string>
</array>
<key>WorkingDirectory</key>
<string>${REPO_ROOT}</string>
<string>${runtime_repo}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
Expand Down Expand Up @@ -177,11 +189,26 @@ disable_user_lume_agent() {
launchctl disable "gui/${uid}/com.trycua.lume_daemon" >/dev/null 2>&1 || true
}

disable_user_pool_agent() {
local uid

uid="$(id -u "${TARGET_USER}")"
if [[ "${DISABLE_USER_POOL_AGENT}" != "true" ]]; then
return 0
fi

if [[ -f "${USER_POOL_AGENT_PATH}" ]]; then
launchctl bootout "gui/${uid}" "${USER_POOL_AGENT_PATH}" >/dev/null 2>&1 || true
fi
launchctl disable "gui/${uid}/${USER_POOL_AGENT_LABEL}" >/dev/null 2>&1 || true
}

bootstrap_daemon() {
local label="$1"
local plist_path="$2"

launchctl bootout system "${plist_path}" >/dev/null 2>&1 || true
launchctl enable "system/${label}"
launchctl bootstrap system "${plist_path}"
launchctl enable "system/${label}"
launchctl kickstart -k "system/${label}"
Expand All @@ -190,6 +217,8 @@ bootstrap_daemon() {
main() {
local lume_path
local rtk_path
local runtime_repo
local runtime_env

require_command dscl
require_command id
Expand All @@ -209,12 +238,16 @@ main() {

mkdir -p "${DAEMON_DIR}" "${LOG_DIR}"
chown "${TARGET_USER}:${TARGET_GROUP}" "${LOG_DIR}"
install_lume_controller_runtime "${TARGET_HOME}" "${TARGET_USER}" "${TARGET_GROUP}"
runtime_repo="$(lume_controller_runtime_repo "${TARGET_HOME}")"
runtime_env="$(lume_controller_runtime_env "${TARGET_HOME}")"

write_lume_plist "${lume_path}"
write_pool_plist "${rtk_path}"
disable_user_lume_agent
write_pool_plist "${rtk_path}" "${runtime_repo}" "${runtime_env}"
bootstrap_daemon "${LUME_LABEL}" "${LUME_PLIST_PATH}"
bootstrap_daemon "${POOL_LABEL}" "${POOL_PLIST_PATH}"
disable_user_lume_agent
disable_user_pool_agent

printf 'installed %s at %s\n' "${LUME_LABEL}" "${LUME_PLIST_PATH}"
printf 'installed %s at %s\n' "${POOL_LABEL}" "${POOL_PLIST_PATH}"
Expand Down
Loading