Skip to content

scripts/setup.sh

Platforms: macOS, Linux.

Purpose

setup.sh is the first-time and additional-device provisioning entry point for macOS and Linux. It is a thin orchestrator around the Python provisioning scripts: it verifies prerequisites, bootstraps the dedicated Provisioner app, creates or attaches the full Agent Identity chain (Blueprint → Agent Identity → Agent User), generates and registers the Blueprint certificate, writes .env, optionally provisions cloud-hosted operational memory, registers the entrabot MCP server, and runs a post-setup smoke check. State is persisted to .entrabot-state.json, so re-runs are idempotent.

Requirements

  • macOS or Linux with Bash.
  • Azure CLI (az) on PATH, already signed in (az login). Without an active login the script fails at Step 2.
  • Python 3.12 or newer (python3.12, python3.13, or a python3 that reports ≥ 3.12).
  • git.
  • An OS credential store reachable by keyring (Keychain on macOS, Secret Service / Keyring on Linux). The certificate step fails closed if the active keyring backend is not OS-native, rather than writing the private key to disk.
  • A Teams-capable M365 license for the Agent User is required for end-to-end Teams operation; license replication can take 10–15 minutes after assignment.

Install the macOS prerequisites with prereqs-macos.sh. On Linux, install python3.12, git, and the Azure CLI with your package manager. See also macOS / Linux installation.

Usage

# First-time provisioning — create a brand-new identity chain
./scripts/setup.sh --new --with-upn-suffix=workstation

# Create an explicit Agent User UPN
./scripts/setup.sh --new --agent-user-upn=entrabot-agent@yourtenant.onmicrosoft.com

# Attach this machine to an existing Blueprint (multi-device / re-install).
# Adds a certificate for this machine; other machines' certificates are kept.
./scripts/setup.sh --use-blueprint=<BLUEPRINT_APP_ID> --agent-user-upn=<UPN>

# Replace EVERY certificate on the Blueprint with a new one for this machine.
# Other machines stop authenticating until they run setup again.
# This does not delete the Blueprint itself.
./scripts/setup.sh --use-blueprint=<BLUEPRINT_APP_ID> --remove-all-existing-certs

# Opt in to cloud-hosted operational memory (Azure Blob Storage)
./scripts/setup.sh --new --with-upn-suffix=workstation --use-cloud-memory

# Skip the post-setup smoke test (useful in CI with no live tenant)
./scripts/setup.sh --new --with-upn-suffix=ci --skip-smoke

# Re-run diagnostics or the consolidated status view instead of provisioning
./scripts/setup.sh --diagnose
./scripts/setup.sh --status --json

An identity mode is required: pass exactly one of --new or --use-blueprint=<APP_ID>. --new and --use-blueprint are mutually exclusive.

Options

Identity mode (choose one):

  • --new — create a completely fresh chain. Existing state is backed up and the identity keys are cleared (the Provisioner app is preserved). Requires a UPN suffix (--with-upn-suffix) or an explicit --agent-user-upn; if neither is given, the script prompts for a suffix.
  • --use-blueprint=ID — attach to an existing Blueprint by App ID. A new cert is generated for this machine and added to the Blueprint; existing certificates are kept, so other machines keep working, and the existing Agent Identity and Agent User are reused. If local state points at a different Blueprint, the stale identity-derived state is wiped (after a timestamped backup) so discovery re-runs cleanly against the new Blueprint. The old machine's cert on the previous Blueprint is not revoked automatically.
  • --remove-all-existing-certs — used with --use-blueprint. Removes every certificate on the Blueprint, including those of other machines, and replaces them with a new one for this machine, even if this machine's current certificate is still valid. It does not delete the Blueprint, its Agent Identities, or its Agent Users. Every other machine stops authenticating until it runs setup again. It lists what will be removed (name and expiry only) and asks y/N on a terminal; without a terminal the flag itself is the consent, and it says so loudly. Nothing on this machine changes until the replacement is confirmed, so declining, or Graph refusing the update, leaves the active key, the state file, and any pending key as they were. This is the only way setup removes a certificate. Mutually exclusive with --new, which creates a Blueprint with no certificates.

Agent User selection:

  • --with-upn-suffix=NAME — UPN suffix for the Agent User (required with --new unless --agent-user-upn is given). With --use-blueprint, selects an existing suffixed Agent User.
  • --agent-user-upn=UPN — explicit Agent User UPN. With --new, creates exactly that UPN; with --use-blueprint, selects an existing Agent User to reuse.
  • --switch-user — run az login first so a different signed-in user owns and sponsors the agent.
  • --teams-user=EMAIL — set a different user (or comma-separated users) as the Teams recipient(s); the az CLI user remains the admin/provisioner. Guests (B2B) are detected and resolved to their home tenant. Without it, a re-run for the same Agent User keeps the sponsor already in .env (ENTRABOT_HUMAN_*); with it, the sponsor is changed to the user(s) you name.

Operational memory:

  • --use-cloud-memory — opt in to Azure Blob Storage for operational data (interaction log, daily summaries, promises, per-chat delivery cursors, the watched-chat list, and the email-poll cursor). Provisions a resource group, storage account, and container scoped to the Agent User, and unsets ENTRABOT_KEEP_MEMORY_LOCAL. A re-run without this flag keeps cloud memory that is already set in .env for the same Agent User.
  • --keep-memory-local — operational data stays on the local filesystem; sets ENTRABOT_KEEP_MEMORY_LOCAL=true. A first run with neither memory flag does this too. On a re-run for the same Agent User whose .env already has cloud memory, pass this flag to switch back to local; without it the cloud settings are kept.
  • --with-storage-account=NAME — target a named storage account instead of the deterministic per-tenant default (used with --use-cloud-memory). Mutually exclusive with --create-new-storage.
  • --with-container=NAME — target a named blob container instead of the agent-<oid> default.
  • --create-new-storage — force a fresh randomly-suffixed storage account even when the deterministic-name one exists. Mutually exclusive with --with-storage-account.

Agent 365 Work IQ:

  • --with-a365-work-iq — install or update the Microsoft Agent 365 DevTools CLI (a365) via dotnet global tools (requires a .NET SDK on PATH).
  • --configure-a365-work-iq — run the interactive Work IQ Word developer setup against the existing Entrabot Blueprint, materialize the Work IQ OAuth grants, and validate the tooling manifest. Requires pwsh.
  • --a365-agent-name=NAME — deprecated compatibility flag; Work IQ setup now reuses the existing Entrabot Blueprint from state.

Diagnostics and control:

  • --status — skip setup and delegate to status.sh; extra arguments are forwarded to show_agent_status.py (for example --json, --health-only, --strict).
  • --diagnose — skip provisioning and run a full health check (state file, certificate, three-hop token, Graph identity, Teams scope, MCP wiring, and the optional persona-sati entry in .mcp.json). Exits non-zero if any check fails. The persona-sati check is advisory: it can report SKIP or WARN but never a failure, and it connects only to loopback addresses (see MCP connectivity).
  • --skip-smoke — skip the post-setup smoke test (token + Graph identity + Teams scope).
  • --help, -h — print the full option matrix.

Effects

  • Verifies az, Python ≥ 3.12, and required CLI tools; verifies the Azure login.
  • Bootstraps the Provisioner app via entra_provisioning.py and creates or reuses the Blueprint, Agent Identity, and Agent User via create_entra_agent_ids.py (using the Provisioner certificate token, never az CLI tokens).
  • Reuses this machine's certificate only when find_local_blueprint_cert.py shows that the local private key matches a certificate still registered on the Blueprint that hasn't expired. The thumbprint cached in the state file is never trusted on its own: setup uses the thumbprint that check returns, and saves it if it differs from the cached one. verify_blueprint_cert.py is an operator tool that setup does not call.
  • Otherwise, including whenever a new private key is still waiting under the pending keystore entry from an earlier run, register_blueprint_cert.py generates an RSA-2048 self-signed certificate (or reuses the pending key), keeps the private key under a temporary keystore entry, and uploads only the public certificate. Because a Graph PATCH keyCredentials replaces the whole list, it sends every existing certificate back along with the new one, then reads the list back to confirm before making the new key active and recording the thumbprint. A normal run never removes a certificate.
  • Creates .venv, installs the package with dev extras, and writes .env (chmod 600) with the resulting tenant, Blueprint, Agent Identity, Agent User, and human-user identifiers plus the certificate thumbprint.
  • Reads the existing .env before rewriting it. When this run is for the same Agent User (ENTRABOT_AGENT_USER_ID is unchanged), it keeps the sponsor (ENTRABOT_HUMAN_*) unless you pass --teams-user or --switch-user, and keeps the cloud-memory settings (ENTRABOT_BLOB_ENDPOINT / ENTRABOT_BLOB_CONTAINER) unless you pass --keep-memory-local or --use-cloud-memory. Cloud memory counts as set only when both blob settings are present and ENTRABOT_KEEP_MEMORY_LOCAL is not true, 1, or yes. A run for a different Agent User (--new, or another Blueprint or Agent User) carries nothing over. The rest of .env is written fresh, so other lines you added by hand are dropped.
  • With --use-cloud-memory and an Agent User present, provisions Azure Blob Storage via provision_blob_storage.py and appends ENTRABOT_BLOB_ENDPOINT / ENTRABOT_BLOB_CONTAINER to .env; offers an idempotent, source-preserving migration prompt that copies existing local data (interaction log, daily summaries, promises) and Claude Code persona memory as a one-time artifact copy. If provisioning fails, setup shows the provisioner's output and warns. It keeps the cloud settings already in .env if there were any for this Agent User, and otherwise falls back to local-only memory. The migration doesn't copy local poll state: the watched-chat list, the email cursor, the legacy chat_id key, or the per-chat delivery cursors under chat_cursors/. A machine's copies may be stale or another agent's. The cloud cursor starts at "now", discovery rebuilds the list from Graph about two minutes after boot, and a chat with no cursor surfaces its newest message only if it is under 24 hours old.
  • Registers the entrabot MCP server in the project-local .mcp.json (Claude Code) and ~/.copilot/mcp-config.json (Copilot CLI) via mcp_config.py; both entries are byte-identical.
  • Runs a post-setup smoke test (token + Graph identity + Teams scope) unless --skip-smoke is passed.
  • Does not create or watch any Teams chat. There is no default chat; every Teams tool requires an explicit chat id supplied at runtime. See Messaging and delivery.

Exit behavior

  • 0 — setup (or --status / --help) completed successfully; the smoke test either passed or was skipped.
  • 1 — a fatal setup error (for example a missing prerequisite, no Azure login, a failed provisioning step, a certificate check that couldn't complete, a declined --remove-all-existing-certs prompt, --new / --use-blueprint conflict, or a missing required identity mode).
  • 2 — mutually exclusive storage flags, --new with --remove-all-existing-certs, or setup completed provisioning but the cloud-memory migration failed (cloud memory is not in sync with local disk; re-run after fixing the underlying consent error).
  • 3 — provisioning finished but the post-setup smoke test failed (the agent can't yet authenticate end-to-end, most often because a just-assigned Teams license has not replicated). Re-run ./scripts/setup.sh --diagnose after replication completes.
  • --diagnose exits with the diagnostics' own overall exit code (0 unless a check fails; warnings and skips do not change it).

Common failures

  • Not logged in to Azure CLI — run az login (or ./scripts/setup.sh --switch-user) and retry.
  • No identity mode specified — pass --new --with-upn-suffix=NAME or --use-blueprint=APP_ID.
  • Could not check ... — setup couldn't read the Blueprint's certificates or the OS credential store (the pending key entry included), so it doesn't know whether this machine is already registered. It stops rather than generating a certificate, and nothing was changed. Fix the error shown above the message and re-run.
  • Could not confirm the certificate update — the update may have landed but couldn't be read back. The new private key is kept; re-run setup and it picks up where it left off. See register_blueprint_cert.py.
  • Old certificates building up — a normal run only adds, including when a certificate has expired. Remove individual certificates in the Entra admin center, or replace them all with --remove-all-existing-certs.
  • Another machine stopped authenticating after --remove-all-existing-certs — this is what the flag does. Run setup on that machine to add its certificate back.
  • Smoke test failed (exit 3) — usually license replication latency; wait 10–15 minutes and re-run with --diagnose.
  • Migration failed (exit 2) — most often missing storage consent for the Agent Identity; re-run setup.sh (idempotent) or create_entra_agent_ids.py to grant it, then retry.