feat: harden enterprise control plane
This commit is contained in:
311
scripts/package-boss-agent-mac-runtime.sh
Executable file
311
scripts/package-boss-agent-mac-runtime.sh
Executable file
@@ -0,0 +1,311 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
VERSION="${BOSS_AGENT_PACKAGE_VERSION:-$(date +%Y%m%d%H%M%S)}"
|
||||
PACKAGE_NAME="boss-agent-mac-runtime-${VERSION}"
|
||||
DIST_DIR="$ROOT_DIR/dist"
|
||||
STAGE_DIR="$DIST_DIR/$PACKAGE_NAME"
|
||||
RUNTIME_DIR="$STAGE_DIR/runtime"
|
||||
ARCHIVE_PATH="$DIST_DIR/${PACKAGE_NAME}.zip"
|
||||
|
||||
rm -rf "$STAGE_DIR" "$ARCHIVE_PATH"
|
||||
"$ROOT_DIR/scripts/build-boss-agent-mac-app.sh" >/dev/null
|
||||
|
||||
mkdir -p "$RUNTIME_DIR/scripts" "$RUNTIME_DIR/local-agent" "$RUNTIME_DIR/deployment/launchd"
|
||||
cp -R "$ROOT_DIR/dist/boss-agent.app" "$STAGE_DIR/boss-agent.app"
|
||||
rsync -a "$ROOT_DIR/local-agent/" "$RUNTIME_DIR/local-agent/"
|
||||
cp "$ROOT_DIR/scripts/start-local-agent.sh" "$RUNTIME_DIR/scripts/start-local-agent.sh"
|
||||
cp "$ROOT_DIR/scripts/install-local-launchagent.sh" "$RUNTIME_DIR/scripts/install-local-launchagent.sh"
|
||||
cp "$ROOT_DIR/scripts/browser-control-smoke.mjs" "$RUNTIME_DIR/scripts/browser-control-smoke.mjs"
|
||||
cp "$ROOT_DIR/scripts/codex-computer-use-runtime.mjs" "$RUNTIME_DIR/scripts/codex-computer-use-runtime.mjs"
|
||||
cp "$ROOT_DIR/scripts/computer-use-smoke.mjs" "$RUNTIME_DIR/scripts/computer-use-smoke.mjs"
|
||||
cp "$ROOT_DIR/scripts/cua-driver-computer-use-runtime.mjs" "$RUNTIME_DIR/scripts/cua-driver-computer-use-runtime.mjs"
|
||||
cp "$ROOT_DIR/scripts/codex-desktop-refresh-hint.mjs" "$RUNTIME_DIR/scripts/codex-desktop-refresh-hint.mjs"
|
||||
cp "$ROOT_DIR/scripts/codex-desktop-refresh-bridge-daemon.mjs" "$RUNTIME_DIR/scripts/codex-desktop-refresh-bridge-daemon.mjs"
|
||||
cp "$ROOT_DIR/deployment/launchd/com.hyzq.boss.local-agent.plist" "$RUNTIME_DIR/deployment/launchd/com.hyzq.boss.local-agent.plist"
|
||||
cp "$ROOT_DIR/deployment/launchd/com.hyzq.boss.codex-desktop-bridge.plist" "$RUNTIME_DIR/deployment/launchd/com.hyzq.boss.codex-desktop-bridge.plist"
|
||||
|
||||
node - <<'NODE' "$RUNTIME_DIR" "$VERSION"
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const runtimeDir = process.argv[2];
|
||||
const version = process.argv[3];
|
||||
for (const name of ["config.cloud.json", "config.example.json"]) {
|
||||
const configPath = path.join(runtimeDir, "local-agent", name);
|
||||
if (!fs.existsSync(configPath)) continue;
|
||||
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
config.bossAgentOtaEnabled = true;
|
||||
config.bossAgentVersion = version;
|
||||
config.bossAgentOtaAutoInstall = false;
|
||||
config.bossAgentOtaCheckIntervalMs = config.bossAgentOtaCheckIntervalMs || 300000;
|
||||
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
||||
}
|
||||
NODE
|
||||
|
||||
cat > "$RUNTIME_DIR/package.json" <<'JSON'
|
||||
{
|
||||
"name": "boss-agent-runtime",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"qrcode": "^1.5.4"
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
node - <<'NODE' "$ROOT_DIR" "$RUNTIME_DIR"
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const rootDir = process.argv[2];
|
||||
const runtimeDir = process.argv[3];
|
||||
const sourceModules = path.join(rootDir, "node_modules");
|
||||
const targetModules = path.join(runtimeDir, "node_modules");
|
||||
const seen = new Set();
|
||||
|
||||
function readPackage(name) {
|
||||
return JSON.parse(fs.readFileSync(path.join(sourceModules, name, "package.json"), "utf8"));
|
||||
}
|
||||
|
||||
function walk(name) {
|
||||
if (seen.has(name)) return;
|
||||
seen.add(name);
|
||||
const pkg = readPackage(name);
|
||||
for (const dep of Object.keys(pkg.dependencies || {})) {
|
||||
walk(dep);
|
||||
}
|
||||
}
|
||||
|
||||
walk("qrcode");
|
||||
fs.mkdirSync(targetModules, { recursive: true });
|
||||
for (const name of seen) {
|
||||
fs.cpSync(path.join(sourceModules, name), path.join(targetModules, name), {
|
||||
recursive: true,
|
||||
dereference: true,
|
||||
filter: (source) => !/\/(test|tests|example|examples|\.github)\b/.test(source),
|
||||
});
|
||||
}
|
||||
NODE
|
||||
|
||||
cat > "$STAGE_DIR/install.command" <<'SH'
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
INSTALL_ROOT="${BOSS_AGENT_INSTALL_ROOT:-$HOME/boss-agent/current}"
|
||||
APP_TARGET_DIR="${BOSS_AGENT_APP_TARGET_DIR:-$HOME/Applications}"
|
||||
APP_TARGET="$APP_TARGET_DIR/boss-agent.app"
|
||||
CONFIG_BACKUP_DIR=""
|
||||
ACTIVE_CONFIG_BASENAME="config.cloud.json"
|
||||
ACTIVE_PLIST="$HOME/Library/LaunchAgents/com.hyzq.boss.local-agent.plist"
|
||||
NODE_BIN="${BOSS_NODE_BIN:-}"
|
||||
|
||||
if [[ -f "$ACTIVE_PLIST" ]]; then
|
||||
ACTIVE_CONFIG_PATH="$(/usr/libexec/PlistBuddy -c 'Print :ProgramArguments:2' "$ACTIVE_PLIST" 2>/dev/null || true)"
|
||||
if [[ -n "$ACTIVE_CONFIG_PATH" && "$ACTIVE_CONFIG_PATH" == "$INSTALL_ROOT/local-agent/"*.json ]]; then
|
||||
ACTIVE_CONFIG_BASENAME="$(basename "$ACTIVE_CONFIG_PATH")"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$NODE_BIN" ]]; then
|
||||
NODE_BIN="$(command -v node 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$NODE_BIN" ]]; then
|
||||
NODE_CANDIDATES=("$HOME"/.boss-runtime/node-*/bin/node(N) /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node)
|
||||
for candidate in "${NODE_CANDIDATES[@]}"; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
NODE_BIN="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "$NODE_BIN" || ! -x "$NODE_BIN" ]]; then
|
||||
echo "Node.js is required. Install Node.js 22 or newer first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_MAJOR="$("$NODE_BIN" -p 'Number(process.versions.node.split(".")[0])')"
|
||||
if [[ "$NODE_MAJOR" -lt 22 ]]; then
|
||||
echo "Node.js 22 or newer is required. Current: $("$NODE_BIN" -v)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
EXISTING_CONFIGS=("$INSTALL_ROOT"/local-agent/config*.json(N))
|
||||
if [[ "${#EXISTING_CONFIGS[@]}" -gt 0 ]]; then
|
||||
CONFIG_BACKUP_DIR="$(mktemp -d)"
|
||||
cp "${EXISTING_CONFIGS[@]}" "$CONFIG_BACKUP_DIR"/
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$INSTALL_ROOT")" "$APP_TARGET_DIR"
|
||||
rsync -a --delete "$SOURCE_DIR/runtime/" "$INSTALL_ROOT/"
|
||||
|
||||
if [[ -n "$CONFIG_BACKUP_DIR" && -d "$CONFIG_BACKUP_DIR" ]]; then
|
||||
cp "$CONFIG_BACKUP_DIR"/config*.json "$INSTALL_ROOT/local-agent/"
|
||||
rm -rf "$CONFIG_BACKUP_DIR"
|
||||
fi
|
||||
|
||||
PACKAGE_VERSION="__BOSS_AGENT_PACKAGE_VERSION__"
|
||||
"$NODE_BIN" - <<'NODE' "$INSTALL_ROOT/local-agent" "$INSTALL_ROOT" "$PACKAGE_VERSION"
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
const configDir = process.argv[2];
|
||||
const installRoot = process.argv[3];
|
||||
const version = process.argv[4];
|
||||
const home = os.homedir();
|
||||
const configPaths = fs.readdirSync(configDir)
|
||||
.filter((name) => /^config.*\.json$/.test(name))
|
||||
.map((name) => path.join(configDir, name));
|
||||
|
||||
for (const configPath of configPaths) {
|
||||
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
const nodeCommand = config.computerUseCommand && path.isAbsolute(config.computerUseCommand)
|
||||
? config.computerUseCommand
|
||||
: "node";
|
||||
|
||||
config.bossAgentOtaEnabled = true;
|
||||
config.bossAgentVersion = version;
|
||||
config.bossAgentInstallRoot = installRoot;
|
||||
config.bossAgentOtaDownloadDir = path.join(home, "boss-agent", "updates");
|
||||
config.bossAgentOtaCheckIntervalMs = config.bossAgentOtaCheckIntervalMs || 300000;
|
||||
config.bossAgentOtaAutoInstall = false;
|
||||
config.skillsDir = config.skillsDir || path.join(home, ".codex", "skills");
|
||||
|
||||
config.codexAppServerEnabled = config.codexAppServerEnabled !== false;
|
||||
config.codexAppServerCommand = config.codexAppServerCommand || "codex";
|
||||
config.codexAppServerArgs = Array.isArray(config.codexAppServerArgs) ? config.codexAppServerArgs : ["app-server"];
|
||||
config.codexAppServerTimeoutMs = config.codexAppServerTimeoutMs || 120000;
|
||||
config.codexAppServerFallbackToCli = config.codexAppServerFallbackToCli !== false;
|
||||
config.codexComputerUseEnabled = config.codexComputerUseEnabled !== false;
|
||||
config.codexComputerUseCommand = config.codexComputerUseCommand || nodeCommand;
|
||||
config.codexComputerUseArgs = Array.isArray(config.codexComputerUseArgs)
|
||||
? config.codexComputerUseArgs
|
||||
: ["scripts/codex-computer-use-runtime.mjs"];
|
||||
config.codexComputerUseTimeoutMs = config.codexComputerUseTimeoutMs || 120000;
|
||||
config.codexComputerUseFallbackToCua = config.codexComputerUseFallbackToCua !== false;
|
||||
|
||||
if (!Array.isArray(config.computerUseArgs) || config.computerUseArgs.length === 0 || config.computerUseArgs.includes("scripts/computer-use-smoke.mjs")) {
|
||||
config.computerUseArgs = ["scripts/cua-driver-computer-use-runtime.mjs"];
|
||||
}
|
||||
config.cuaDriverCommand = config.cuaDriverCommand || "cua-driver";
|
||||
config.cuaDriverArgs = Array.isArray(config.cuaDriverArgs) ? config.cuaDriverArgs : [];
|
||||
config.cuaDriverTimeoutMs = config.cuaDriverTimeoutMs || 45000;
|
||||
|
||||
for (const key of [
|
||||
"masterAgentWorkdir",
|
||||
"codexAppServerWorkdir",
|
||||
"codexComputerUseWorkdir",
|
||||
"browserControlWorkdir",
|
||||
"computerUseWorkdir",
|
||||
"codexDesktopRefreshWorkdir",
|
||||
"omxWorkdir"
|
||||
]) {
|
||||
config[key] = installRoot;
|
||||
}
|
||||
|
||||
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
||||
}
|
||||
NODE
|
||||
|
||||
if [[ "$ACTIVE_CONFIG_BASENAME" == "config.installed.json" || "$ACTIVE_CONFIG_BASENAME" == "config.cloud.json" || "$ACTIVE_CONFIG_BASENAME" == "config.example.json" ]]; then
|
||||
CUSTOM_CONFIGS=("$INSTALL_ROOT"/local-agent/config*.json(N))
|
||||
for custom_config in "${CUSTOM_CONFIGS[@]}"; do
|
||||
custom_name="$(basename "$custom_config")"
|
||||
case "$custom_name" in
|
||||
config.installed.json|config.cloud.json|config.example.json)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
if "$NODE_BIN" - "$custom_config" <<'NODE'; then
|
||||
const fs = require("fs");
|
||||
const configPath = process.argv[2];
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
process.exit(config.deviceId && config.token ? 0 : 1);
|
||||
} catch {
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
ACTIVE_CONFIG_BASENAME="$custom_name"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ ! -f "$INSTALL_ROOT/local-agent/$ACTIVE_CONFIG_BASENAME" ]]; then
|
||||
ACTIVE_CONFIG_BASENAME="config.cloud.json"
|
||||
fi
|
||||
|
||||
chmod +x "$INSTALL_ROOT/scripts/start-local-agent.sh"
|
||||
chmod +x "$INSTALL_ROOT/scripts/install-local-launchagent.sh"
|
||||
chmod +x "$INSTALL_ROOT/scripts/"*.mjs
|
||||
|
||||
rm -rf "$APP_TARGET"
|
||||
cp -R "$SOURCE_DIR/boss-agent.app" "$APP_TARGET"
|
||||
|
||||
"$INSTALL_ROOT/scripts/install-local-launchagent.sh" "$INSTALL_ROOT/local-agent/$ACTIVE_CONFIG_BASENAME"
|
||||
|
||||
echo "boss-agent installed:"
|
||||
echo " runtime: $INSTALL_ROOT"
|
||||
echo " app: $APP_TARGET"
|
||||
echo "Open $APP_TARGET to view status."
|
||||
SH
|
||||
perl -0pi -e "s/__BOSS_AGENT_PACKAGE_VERSION__/$VERSION/g" "$STAGE_DIR/install.command"
|
||||
chmod +x "$STAGE_DIR/install.command"
|
||||
|
||||
cat > "$STAGE_DIR/README_INSTALL.txt" <<'TXT'
|
||||
Boss Agent macOS runtime package
|
||||
|
||||
Install:
|
||||
1. Unzip this package on the controlled Mac.
|
||||
2. Double click install.command.
|
||||
3. Open ~/Applications/boss-agent.app.
|
||||
|
||||
Notes:
|
||||
- Existing ~/boss-agent/current/local-agent/config.cloud.json is preserved.
|
||||
- The installer updates bossAgentVersion and local runtime paths after preserving binding credentials.
|
||||
- boss-agent OTA downloads future macOS runtime packages from the Boss server, verifies sha256, then launches this installer flow again.
|
||||
- Node.js 22 or newer is required because the local agent uses node:sqlite.
|
||||
- This build includes the Cua Driver desktop control runtime. Install and authorize `cua-driver` on the controlled Mac before enabling full desktop GUI control.
|
||||
TXT
|
||||
|
||||
(
|
||||
cd "$DIST_DIR"
|
||||
ditto -c -k --sequesterRsrc --keepParent "$PACKAGE_NAME" "$ARCHIVE_PATH"
|
||||
)
|
||||
|
||||
node - <<'NODE' "$ARCHIVE_PATH" "$ROOT_DIR" "$VERSION"
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const archivePath = process.argv[2];
|
||||
const rootDir = process.argv[3];
|
||||
const version = process.argv[4];
|
||||
const downloadsDir = path.join(rootDir, "public", "downloads");
|
||||
const latestPath = path.join(downloadsDir, "boss-agent-mac-latest.zip");
|
||||
const latestMetaPath = path.join(downloadsDir, "boss-agent-mac-latest.json");
|
||||
|
||||
fs.mkdirSync(downloadsDir, { recursive: true });
|
||||
fs.copyFileSync(archivePath, latestPath);
|
||||
const content = fs.readFileSync(latestPath);
|
||||
const stat = fs.statSync(latestPath);
|
||||
const meta = {
|
||||
packageType: "boss_agent_macos",
|
||||
version,
|
||||
fileName: "boss-agent-mac-latest.zip",
|
||||
archiveFileName: path.basename(archivePath),
|
||||
sizeBytes: stat.size,
|
||||
sha256: crypto.createHash("sha256").update(content).digest("hex"),
|
||||
updatedAt: stat.mtime.toISOString(),
|
||||
downloadUrl: "/api/v1/boss-agent/ota/package"
|
||||
};
|
||||
fs.writeFileSync(latestMetaPath, `${JSON.stringify(meta, null, 2)}\n`);
|
||||
NODE
|
||||
|
||||
echo "$ARCHIVE_PATH"
|
||||
Reference in New Issue
Block a user