$ customer-installation
Jamf Pro MDM で Boost を展開
docs/customer-installation/mdm/jamf-pro にあり、このページはリポジトリのファイルを直接表示するため、次のビルドでサイトが更新されます。テスト済み環境
この展開パスは Jamf Pro でテスト済みです。将来の MDM プロバイダーは docs/customer-installation/mdm/<provider> 配下で同じソースレイアウトに従ってください。
ロールアウトの仕組み
インストールスクリプトを追加
install.sh から Jamf Pro スクリプトを作成します。ログイン中のコンソールユーザーに Boost をインストールし、選択したエージェント統合を初期化し、Jamf インベントリを更新します。
デプロイポリシーを作成
対象 Mac で root としてスクリプトを実行します。Jamf パラメータ 4 で Boost 統合を選び、アクティブなコンソールユーザーがいない Mac には Login トリガーを組み合わせます。
拡張属性を追加
extension-attribute.sh から Jamf Pro Extension Attribute を作成し、インストール状態、バージョン、パス、統合、検出 AI ツール、収集時刻を表示します。
Jamf Pro ポリシー設定
Jamf Pro でインストーラーをスクリプトとして追加し、Boost を受け取る Mac にスコープしたポリシーに添付します。ポリシーはべき等に保ち、再実行でバイナリを更新し選択した統合を再適用します。
- スクリプト
install.shを Jamf Pro スクリプトに貼り付けます。- 実行ユーザー
- Root。
boost initのためにログイン中のユーザーに切り替えます。 - デフォルトインストールパス
- コンソールユーザーの
$HOME/.local/bin/boost - パラメータ 4
- 任意の空白区切り Boost ターゲット(例:
cursor claude)。
ターゲット例
| ロールアウト | Jamf パラメータ 4 |
|---|---|
| デフォルトのローカルエージェント展開 | cursor claude |
| Cursor、Claude、Codex | cursor claude codex |
| CLI ファーストチーム | codex |
#!/bin/bash
# Boost CLI - Jamf install (per-user, macOS).
# Installs to $HOME/.local/bin/boost for the console user, runs `boost init`
# as them, removes the legacy /usr/local/bin/boost if present, and refreshes
# Jamf inventory so the Boost CLI Status EA reflects this run immediately.
#
# Optional:
# BOOST_TARGETS default: "cursor claude"
# Jamf parameter $4 same as BOOST_TARGETS
set -euo pipefail
repo="jfrog/boost"
targets="${4:-${BOOST_TARGETS:-cursor claude}}"
log() { printf '[boost] %s\n' "$*"; }
die() { log "ERROR: $*" >&2; exit 1; }
# curl with retries — handles transient corporate-network resets (curl 56,
# 7, 18, 35, …). --retry-all-errors covers mid-stream resets that plain
# --retry won't catch.
fetch() {
curl -fsSL \
--retry 5 --retry-delay 2 --retry-all-errors \
--connect-timeout 10 --max-time 300 \
"$@"
}
# Repair root-owned leftovers in the dirs `boost init` writes to. Earlier
# POC runs may have created these as root, leaving the user unable to
# overwrite them. Recursive chown is safe: if the dir itself is root-owned,
# the user couldn't have created files inside it anyway.
normalize_boost_paths() {
local user="$1" home="$2" path owner
for path in \
"$home/.local" \
"$home/.boost" \
"$home/.claude" \
"$home/.cursor" \
"$home/.codex"; do
[ -e "$path" ] || continue
owner="$(/usr/bin/stat -f '%Su' "$path" 2>/dev/null || true)"
if [ "$owner" != "$user" ]; then
log "fixing ownership: $path (was $owner)"
/usr/sbin/chown -R "$user" "$path" || log "chown failed for $path (non-fatal)"
fi
done
}
# ---- Console user is required for per-user install ----------------------
user="$(/usr/bin/stat -f "%Su" /dev/console 2>/dev/null || true)"
if [ -z "$user" ] || [ "$user" = "root" ] || [ "$user" = "loginwindow" ]; then
log "no console user; per-user install needs a logged-in account — skipping"
# Exit 0 so Jamf doesn't flag this as a failure and storm retries.
# Pair with a Login trigger on the policy to catch this machine later.
exit 0
fi
uid="$(/usr/bin/id -u "$user")"
home="$(/usr/bin/dscl . -read "/Users/$user" NFSHomeDirectory 2>/dev/null | /usr/bin/awk '{print $2}')"
[ -n "$home" ] || home="/Users/$user"
install_dir="$home/.local/bin"
boost="$install_dir/boost"
# ---- Architecture --------------------------------------------------------
arch="$(uname -m)"
case "$arch" in arm64|aarch64) arch=arm64 ;; x86_64|amd64) arch=amd64 ;; *) die "unsupported arch: $arch" ;; esac
archive="boost-darwin-${arch}.tar.gz"
# ---- Resolve latest tag and download ------------------------------------
tag="$(fetch -I -o /dev/null -w '%{url_effective}' "https://github.com/$repo/releases/latest" | sed 's#.*/tag/##')"
[ -n "$tag" ] || die "could not resolve latest Boost release"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
log "downloading $archive ($tag)"
fetch "https://github.com/$repo/releases/download/$tag/$archive" -o "$tmp/$archive"
tar -xzf "$tmp/$archive" -C "$tmp"
[ -f "$tmp/boost" ] || die "archive missing boost binary"
# ---- Install into the user's ~/.local/bin, owned by them ----------------
/bin/mkdir -p "$install_dir"
/usr/sbin/chown "$user" "$home/.local" "$install_dir" 2>/dev/null || true
/usr/bin/install -o "$user" -g staff -m 0755 "$tmp/boost" "$boost"
log "installed $(/usr/bin/sudo -H -u "$user" "$boost" version 2>/dev/null || echo "$tag") at $boost"
# ---- Remove legacy system install so the EA reports the right path ------
# The EA prefers $HOME/.local/bin/boost but falls back to /usr/local/bin/boost.
# Leaving the old one around means PATH lookups for the user may still hit
# the stale binary depending on shell config. Self-cleanup on every run.
if [ -e /usr/local/bin/boost ]; then
log "removing legacy /usr/local/bin/boost"
/bin/rm -f /usr/local/bin/boost || log "legacy removal failed (non-fatal)"
fi
# ---- Fix any root-owned config leftovers before init --------------------
normalize_boost_paths "$user" "$home"
# ---- Run `boost init` as the console user -------------------------------
flags=(--accept-terms)
for target in $targets; do flags+=(--"$target"); done
log "running '$boost init ${flags[*]}' as $user"
/bin/launchctl asuser "$uid" /usr/bin/sudo -H -u "$user" \
/usr/bin/env HOME="$home" USER="$user" LOGNAME="$user" \
"$boost" init "${flags[@]}"
# ---- Push Jamf inventory so the EA picks up this run --------------------
if [ -x /usr/local/bin/jamf ]; then
log "triggering jamf recon"
/usr/local/bin/jamf recon >/dev/null 2>&1 || log "recon failed (non-fatal)"
fiRaw source: install.sh
#!/bin/bash
# Boost CLI — Jamf Pro Extension Attribute
#
# Looks for boost in this priority order:
# 1. $home/.local/bin/boost (per-user, current layout)
# 2. /usr/local/bin/boost (legacy fallback during migration)
# 3. /Users/*/.local/bin/boost (last resort if no console user)
#
# IMPORTANT: EA values refresh ONLY when `jamf recon` runs. The Boost
# install script must call `/usr/local/bin/jamf recon` at its end (or the
# install policy must include a Maintenance → Update Inventory payload),
# otherwise this attribute will keep showing the previously-recorded value.
#
# Deployment:
# Jamf Pro → Settings → Computer Management → Extension Attributes → New
# Display Name: Boost CLI Status
# Inventory: Applications
# Data Type: String
# Input Type: Script
set -u
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin"
home=""
boost=""
boost_scope=""
emit() { printf '%s=%s\n' "$1" "$2"; }
resolve_home_for() {
local u="$1" h
h="$(/usr/bin/dscl . -read "/Users/$u" NFSHomeDirectory 2>/dev/null | /usr/bin/awk '{print $2}')"
[ -z "$h" ] && h="/Users/$u"
printf '%s' "$h"
}
have_cli() {
local name="$1" p
for p in \
"$home/.local/bin/$name" \
"/usr/local/bin/$name" \
"/opt/homebrew/bin/$name" \
"$home/.npm-global/bin/$name" \
"$home/.bun/bin/$name" \
"$home/.volta/bin/$name"; do
[ -x "$p" ] && return 0
done
return 1
}
echo "<result>"
# ---- EA metadata (most useful field for diagnosing stale inventory) ----
emit ea_collected_at "$(/bin/date '+%Y-%m-%dT%H:%M:%S%z')"
# ---- System --------------------------------------------------------------
emit os "$(/usr/bin/sw_vers -productName 2>/dev/null) $(/usr/bin/sw_vers -productVersion 2>/dev/null)"
emit os_build "$(/usr/bin/sw_vers -buildVersion 2>/dev/null)"
emit arch "$(/usr/bin/uname -m 2>/dev/null)"
# ---- Console user --------------------------------------------------------
console_user="$(/usr/bin/stat -f "%Su" /dev/console 2>/dev/null || echo "")"
if [ -z "$console_user" ] || [ "$console_user" = "root" ] || [ "$console_user" = "loginwindow" ]; then
emit console_user none
console_user=""
else
emit console_user "$console_user"
emit console_uid "$(/usr/bin/id -u "$console_user")"
home="$(resolve_home_for "$console_user")"
emit home "$home"
shell="$(/usr/bin/dscl . -read "/Users/$console_user" UserShell 2>/dev/null | /usr/bin/awk '{print $2}')"
emit shell "${shell:-unknown}"
fi
# ---- Locate boost (priority: console user → system → other users) -------
if [ -n "$home" ] && [ -x "$home/.local/bin/boost" ]; then
boost="$home/.local/bin/boost"
boost_scope="user"
elif [ -x "/usr/local/bin/boost" ]; then
boost="/usr/local/bin/boost"
boost_scope="system"
else
# No console user, or console user has nothing — scan other user homes
# so overnight recon on an unattended Mac still finds boost.
for udir in /Users/*/; do
[ -d "$udir" ] || continue
[ "${udir%/}" = "/Users/Shared" ] && continue
[ "${udir%/}" = "$home" ] && continue
cand="${udir%/}/.local/bin/boost"
if [ -x "$cand" ]; then
boost="$cand"
boost_scope="other_user"
[ -z "$home" ] && home="${udir%/}"
break
fi
done
fi
if [ -z "$boost" ]; then
emit status not_installed
echo "</result>"
exit 0
fi
# ---- Boost binary report -------------------------------------------------
emit status installed
emit boost_path "$boost"
emit boost_scope "$boost_scope"
emit boost_mtime "$(/bin/date -r "$boost" '+%Y-%m-%dT%H:%M:%S' 2>/dev/null)"
emit boost_sha12 "$(/usr/bin/shasum -a 256 "$boost" 2>/dev/null | /usr/bin/awk '{print substr($1,1,12)}')"
# Pass HOME so a per-user binary reads the right config dir, not /var/root.
if [ -n "$home" ] && [ -d "$home" ]; then
raw_version="$(/usr/bin/env HOME="$home" "$boost" version 2>/dev/null | /usr/bin/head -n1 | /usr/bin/tr -d '\r')"
else
raw_version="$("$boost" version 2>/dev/null | /usr/bin/head -n1 | /usr/bin/tr -d '\r')"
fi
parsed_version="$(printf '%s\n' "$raw_version" | /usr/bin/grep -Eo 'v?[0-9]+\.[0-9]+\.[0-9]+([.-][A-Za-z0-9]+)*' | /usr/bin/head -n1)"
emit version "${parsed_version:-unknown}"
emit version_raw "${raw_version:-empty}"
# ---- Integrations + AI tools (need a home dir) --------------------------
if [ -n "$home" ] && [ -d "$home" ]; then
integrations=()
[ -f "$home/.cursor/hooks/boost-rewrite.sh" ] && integrations+=("cursor")
[ -f "$home/.claude/hooks/boost-rewrite.sh" ] && integrations+=("claude")
[ -f "$home/.codex/BOOST.md" ] && integrations+=("codex")
if [ ${#integrations[@]} -eq 0 ]; then
emit boost_integrations none
else
IFS=','; emit boost_integrations "${integrations[*]}"; unset IFS
fi
ai_tools=()
[ -d "/Applications/Cursor.app" ] && ai_tools+=("cursor_app")
[ -d "/Applications/Claude.app" ] && ai_tools+=("claude_app")
have_cli claude && ai_tools+=("claude_cli")
have_cli codex && ai_tools+=("codex_cli")
if [ ${#ai_tools[@]} -eq 0 ]; then
emit ai_tools none
else
IFS=','; emit ai_tools "${ai_tools[*]}"; unset IFS
fi
if [ -f "$home/.boost/config.toml" ]; then
emit config_global present
else
emit config_global absent
fi
last_init=""
for ag in cursor claude codex; do
hook="$home/.$ag/hooks/boost-rewrite.sh"
if [ -f "$hook" ]; then
last_init="$(/bin/date -r "$hook" '+%Y-%m-%dT%H:%M:%S' 2>/dev/null)"
[ -n "$last_init" ] && break
fi
done
[ -n "$last_init" ] && emit last_init "$last_init"
fi
echo "</result>"Raw source: extension-attribute.sh
Jamf Pro インベントリ
拡張属性を追加し、Boost のインストール有無、バージョン、バイナリの場所、アクティブな統合、コンソールユーザーで検出された AI ツールを Jamf インベントリに報告します。
- 表示名
- Boost CLI Status
- インベントリ
- Applications
- データ型
- String
- 入力タイプ
- Script
Smart Group の例
status=not_installedは Boost バイナリのない Mac を検出します。version=v0.6.4は古いリリースのマシンを特定できます。ai_tools contains cursorとboost_integrations does not contain cursorで Boost フックのない Cursor ユーザーを見つけます。
運用上の注意
- Boost は
$HOME/.local/bin配下にユーザー単位でインストールされるため、ログイン中のコンソールユーザーが必要です。 - コンソールユーザーがいない場合、スクリプトはインストールせず正常終了します。後で捕捉するには Login トリガーを使います。
- レガシー
/usr/local/bin/boostを削除し、シェルと拡張属性がユーザーインストールを優先します。 boost init --accept-termsの前に、以前の実行で root 所有になった Boost とエージェント設定ディレクトリを修復します。- Jamf が利用可能なら
jamf reconを実行し、拡張属性が最新のインストールを即反映します。
関連ドキュメント
個別の開発者セットアップは Boost クイックスタート を参照。インストールフローの詳細は インストールフロー仕様 を参照。