Add probo-agent CLI and deviceagent library

Introduce the standalone device agent binary and shared library
for enrollment, posture checks, self-update, and OS service
integration. Include build targets, module deps, and release
workflow so the agent can ship independently of server changes.

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-05-26 09:01:08 -07:00
parent e643a3259c
commit 22e50b3f11
56 changed files with 7410 additions and 127 deletions

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Productbuild distribution definition for the Probo device posture
agent.
Placeholders are substituted by build.sh:
@@VERSION@@ agent version, e.g. 0.1.0
@@PKG_ARCH@@ payload pkgbuild architecture (x86_64 or arm64)
@@HOST_ARCHS@@ host arch filter used by Installer.app
-->
<installer-gui-script minSpecVersion="2">
<title>Probo Device Posture Agent @@VERSION@@</title>
<!--
Single-pane install. Customization is disabled because the agent
is a single component and we do not want users to opt out of the
postinstall script.
-->
<options customize="never"
require-scripts="true"
rootVolumeOnly="true"
hostArchitectures="@@HOST_ARCHS@@"/>
<!--
Refuse installation on macOS older than 11 (Big Sur). The agent
relies on launchd bootstrap/bootout semantics and the modern
`system_profiler` output that earlier releases do not provide.
-->
<volume-check>
<allowed-os-versions>
<os-version min="11.0"/>
</allowed-os-versions>
</volume-check>
<welcome file="welcome.html" mime-type="text/html"/>
<license file="license.txt" mime-type="text/plain"/>
<conclusion file="conclusion.html" mime-type="text/html"/>
<pkg-ref id="com.getprobo.agent"
version="@@VERSION@@"
onConclusion="none">probo-agent-component.pkg</pkg-ref>
<choices-outline>
<line choice="default">
<line choice="com.getprobo.agent"/>
</line>
</choices-outline>
<choice id="default"/>
<choice id="com.getprobo.agent"
title="Probo Device Posture Agent"
description="Installs probo-agent to /usr/local/bin and the launchd unit to /Library/LaunchDaemons.">
<pkg-ref id="com.getprobo.agent"/>
</choice>
</installer-gui-script>

View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Installation complete</title>
<style>
body {
font-family: -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
color: #1d1d1f;
margin: 24px;
line-height: 1.45;
}
h1 { font-size: 18px; margin-bottom: 6px; }
h2 { font-size: 14px; margin-top: 18px; margin-bottom: 4px; }
code, pre {
font-family: Menlo, Consolas, monospace;
background: #f5f5f7;
border-radius: 4px;
}
code { padding: 2px 4px; }
pre {
padding: 8px 10px;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}
</style>
</head>
<body>
<h1>Installation complete</h1>
<p>
The <code>probo-agent</code> binary is installed and the launchd
unit is loaded. If the installer found a pre-staged
configuration file at <code>/tmp/probo-agent.conf</code>, the
device is already enrolled and the agent is running.
</p>
<h2>Enroll this device manually</h2>
<p>
If you installed without a pre-staged configuration, finish the
setup from a Terminal:
</p>
<pre>sudo probo-agent install \
--server https://app.getprobo.com \
--enrollment-token &lt;TOKEN&gt;</pre>
<h2>Inspect the agent</h2>
<pre>sudo probo-agent status
sudo probo-agent collect</pre>
<h2>Uninstall</h2>
<pre>sudo probo-agent uninstall</pre>
<p>
Logs are written to <code>/var/log/probo-agent.log</code>. The
installer's own log lives at
<code>/var/log/probo-agent-install.log</code>.
</p>
</body>
</html>

View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Probo Device Posture Agent</title>
<style>
body {
font-family: -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
color: #1d1d1f;
margin: 24px;
line-height: 1.45;
}
h1 { font-size: 18px; margin-bottom: 6px; }
h2 { font-size: 14px; margin-top: 18px; margin-bottom: 4px; }
code, pre {
font-family: Menlo, Consolas, monospace;
background: #f5f5f7;
border-radius: 4px;
padding: 2px 4px;
}
pre { padding: 8px 10px; overflow-x: auto; }
ul { margin: 4px 0 8px 18px; padding: 0; }
li { margin: 2px 0; }
</style>
</head>
<body>
<h1>Welcome to the Probo Device Posture Agent</h1>
<p>
This installer adds <code>probo-agent</code> to your Mac and starts it
as a system service. The agent reports device posture &mdash; disk
encryption, screen lock, firewall, OS version, and similar
signals &mdash; back to your Probo workspace over HTTPS.
</p>
<h2>What the installer does</h2>
<ul>
<li>Installs the <code>probo-agent</code> binary to
<code>/usr/local/bin/probo-agent</code>.</li>
<li>Registers the launchd unit
<code>com.getprobo.agent</code> in
<code>/Library/LaunchDaemons</code>.</li>
<li>Creates the persistent state directory
<code>/var/lib/probo-agent</code> (root-owned, mode 0700).</li>
<li>Enrolls the device automatically when an admin has pre-staged
<code>/tmp/probo-agent.conf</code> (typically via an MDM).</li>
</ul>
<h2>What you will need</h2>
<ul>
<li>Administrator privileges on this Mac.</li>
<li>The Probo server URL (e.g.
<code>https://app.getprobo.com</code>).</li>
<li>A device enrollment token issued by a workspace administrator.</li>
</ul>
<p>
Click <strong>Continue</strong> to review the license, then
<strong>Install</strong> to proceed.
</p>
</body>
</html>

View File

@@ -0,0 +1,112 @@
#!/bin/bash
#
# Build a Probo device posture agent macOS installer (.pkg) from a
# pre-built `probo-agent` binary.
#
# Required arguments:
# --binary PATH Path to a compiled probo-agent binary.
# --arch ARCH Target architecture: amd64 or arm64.
# --version VER Agent version, e.g. 0.1.0. Defaults to the
# content of cmd/probo-agent/VERSION.
# --output PATH Output .pkg path. Defaults to
# dist/probo-agent_${VER}_${OS}.pkg.
#
# The resulting flat distribution package is unsigned. Apple
# Developer ID signing + notarization are out of scope for this
# script; consumers can chain `productsign` and `xcrun notarytool`
# afterwards.
#
# Must run on macOS: pkgbuild and productbuild are Apple-only tools.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../.." && pwd)"
BINARY=""
ARCH=""
VERSION=""
OUTPUT=""
IDENTIFIER="com.getprobo.agent"
usage() {
sed -ne '/^#/!q; s/^# \{0,1\}//; 2,$ p' < "$0"
}
while [ $# -gt 0 ]; do
case "$1" in
--binary) BINARY="$2"; shift 2 ;;
--arch) ARCH="$2"; shift 2 ;;
--version) VERSION="$2"; shift 2 ;;
--output) OUTPUT="$2"; shift 2 ;;
--identifier) IDENTIFIER="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown flag: $1" >&2; usage >&2; exit 2 ;;
esac
done
if [ -z "${BINARY}" ] || [ ! -x "${BINARY}" ]; then
echo "error: --binary <path-to-probo-agent> is required and must be executable" >&2
exit 2
fi
case "${ARCH}" in
amd64) PKG_ARCH="x86_64" ;;
arm64) PKG_ARCH="arm64" ;;
"") echo "error: --arch (amd64|arm64) is required" >&2; exit 2 ;;
*) echo "error: unsupported --arch '${ARCH}' (want amd64 or arm64)" >&2; exit 2 ;;
esac
if [ -z "${VERSION}" ]; then
VERSION="$(cat "${REPO_ROOT}/cmd/probo-agent/VERSION")"
fi
if [ -z "${OUTPUT}" ]; then
mkdir -p "${REPO_ROOT}/dist"
OUTPUT="${REPO_ROOT}/dist/probo-agent_${VERSION}_darwin_${PKG_ARCH}.pkg"
fi
if ! command -v pkgbuild >/dev/null 2>&1 || ! command -v productbuild >/dev/null 2>&1; then
echo "error: pkgbuild and productbuild are required (run on macOS)" >&2
exit 1
fi
STAGE="$(mktemp -d -t probo-agent-pkg)"
trap 'rm -rf "${STAGE}"' EXIT
PAYLOAD="${STAGE}/payload"
SCRIPTS="${STAGE}/scripts"
RESOURCES="${STAGE}/Resources"
mkdir -p "${PAYLOAD}/usr/local/bin" "${SCRIPTS}" "${RESOURCES}"
install -m 0755 "${BINARY}" "${PAYLOAD}/usr/local/bin/probo-agent"
install -m 0755 "${SCRIPT_DIR}/scripts/postinstall" "${SCRIPTS}/postinstall"
cp "${SCRIPT_DIR}/Resources/welcome.html" "${RESOURCES}/welcome.html"
cp "${SCRIPT_DIR}/Resources/conclusion.html" "${RESOURCES}/conclusion.html"
cp "${REPO_ROOT}/LICENSE" "${RESOURCES}/license.txt"
# Component package: payload + scripts only.
COMPONENT_PKG="${STAGE}/probo-agent-component.pkg"
pkgbuild \
--root "${PAYLOAD}" \
--scripts "${SCRIPTS}" \
--identifier "${IDENTIFIER}" \
--version "${VERSION}" \
--install-location "/" \
"${COMPONENT_PKG}"
# Render Distribution.xml from its template.
DISTRIBUTION="${STAGE}/Distribution.xml"
sed \
-e "s|@@VERSION@@|${VERSION}|g" \
-e "s|@@PKG_ARCH@@|${PKG_ARCH}|g" \
-e "s|@@HOST_ARCHS@@|${PKG_ARCH}|g" \
"${SCRIPT_DIR}/Distribution.xml.tmpl" > "${DISTRIBUTION}"
mkdir -p "$(dirname "${OUTPUT}")"
productbuild \
--distribution "${DISTRIBUTION}" \
--package-path "${STAGE}" \
--resources "${RESOURCES}" \
"${OUTPUT}"
echo "Built ${OUTPUT}"

View File

@@ -0,0 +1,92 @@
#!/bin/bash
#
# probo-agent macOS PKG postinstall script.
#
# Runs as root inside the macOS Installer.app sandbox after the
# payload has been laid down. Standard pkgbuild positional args:
#
# $1 = full path to the component package
# $2 = full path to the install location (selected target)
# $3 = mountpoint of the destination volume
# $4 = root directory ("/" for the target volume)
#
# We intentionally do not abort the install if enrollment fails:
# the binary is laid down regardless, and the operator can finish
# enrollment with `sudo probo-agent install ...` from Terminal.
set -u
LOG_FILE="/var/log/probo-agent-install.log"
BINARY="/usr/local/bin/probo-agent"
STATE_DIR="/var/lib/probo-agent"
CONF_FILE="/tmp/probo-agent.conf"
# Mirror everything to the install log. We keep stdout/stderr open
# too so failures still surface in macOS Installer.app's log pane.
mkdir -p "$(dirname "${LOG_FILE}")"
exec > >(tee -a "${LOG_FILE}") 2>&1
echo
echo "=== probo-agent postinstall $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
echo "pkg=$1 target=$2 mount=$3 root=$4"
if [ ! -x "${BINARY}" ]; then
echo "error: expected binary not found at ${BINARY}"
exit 1
fi
mkdir -p "${STATE_DIR}"
chown root:wheel "${STATE_DIR}"
chmod 0700 "${STATE_DIR}"
# An admin (or MDM) may stage /tmp/probo-agent.conf to drive an
# unattended enrollment. Recognized keys (shell-style):
#
# PROBO_SERVER_URL=https://app.getprobo.com
# PROBO_ENROLLMENT_TOKEN=<token>
# PROBO_NO_AUTO_UPDATE=true
#
# We source the file in a subshell so a malformed line can never
# leak variables into our env, then validate the values we care
# about.
if [ -f "${CONF_FILE}" ]; then
echo "Found ${CONF_FILE}, attempting unattended enrollment."
eval "$(
set -e
# shellcheck source=/dev/null
. "${CONF_FILE}"
printf 'CONF_SERVER=%q\n' "${PROBO_SERVER_URL:-}"
printf 'CONF_TOKEN=%q\n' "${PROBO_ENROLLMENT_TOKEN:-}"
printf 'CONF_NOUPDATE=%q\n' "${PROBO_NO_AUTO_UPDATE:-}"
)"
if [ -z "${CONF_SERVER}" ] || [ -z "${CONF_TOKEN}" ]; then
echo "warning: ${CONF_FILE} is missing PROBO_SERVER_URL or PROBO_ENROLLMENT_TOKEN; skipping enrollment."
else
EXTRA_FLAGS=()
case "${CONF_NOUPDATE}" in
1|true|TRUE|yes|YES) EXTRA_FLAGS+=("--no-auto-update") ;;
esac
if "${BINARY}" install \
--server "${CONF_SERVER}" \
--enrollment-token "${CONF_TOKEN}" \
"${EXTRA_FLAGS[@]}"; then
echo "Device enrolled and service installed."
else
echo "warning: probo-agent install failed; the binary is in place and can be re-run by an admin."
fi
fi
# The token in the conf file is sensitive; clear it whatever
# the outcome so a successful install does not leave secrets
# in /tmp.
rm -f "${CONF_FILE}"
else
echo "No ${CONF_FILE} found; skipping automatic enrollment."
echo "Finish setup with: sudo ${BINARY} install --server <URL> --enrollment-token <TOKEN>"
fi
echo "=== postinstall done ==="
exit 0