> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asymptotelabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# macOS System-Mode Object Storage Forwarding

> Install Beacon on one Mac and forward system runtime and inventory telemetry to AWS S3, Google Cloud Storage, or both.

## Overview

Use this guide for a single Apple Silicon Mac without MDM. Beacon's object
storage forwarders are harness-neutral: they tail the system endpoint logs, so
Claude Code, Codex CLI, Cursor, VS Code, opencode, and other configured
integrations can share the same S3 or GCS pipeline.

The end state is:

* Beacon and bundled Vector are installed under `/opt/beacon`.
* `com.beacon.endpoint.collector` receives local OTLP telemetry.
* Supported hooks and plugins write to the same system runtime log.
* Runtime activity is stored in `/var/log/beacon-agent/runtime.jsonl`.
* Inventory telemetry is stored in
  `/var/log/beacon-agent/inventory_state.jsonl`.
* Vector forwards both streams to S3, GCS, or both:

```text theme={null}
<bucket>/<prefix>/runtime/date=YYYY-MM-DD/<timestamp>-<uuid>.jsonl.gz
<bucket>/<prefix>/inventory/date=YYYY-MM-DD/<timestamp>-<uuid>.jsonl.gz
```

The scripts below use the destination-only `install-forwarder.sh` helpers.
The `repair-hooks-and-forwarder.sh` scripts under the `claude/` package path are
Claude convenience wrappers: they install the same forwarder and then repair
Claude hooks. The forwarding services themselves do not inspect or depend on
the producing harness.

## 1. Install Beacon

Download and install the latest signed Apple Silicon endpoint package from
[GitHub Releases](https://github.com/Asymptote-Labs/agent-beacon/releases):

```bash theme={null}
tmpdir="$(mktemp -d)"
gh release download \
  --repo Asymptote-Labs/agent-beacon \
  --pattern 'BeaconEndpointAgent-*-arm64.pkg' \
  --dir "$tmpdir"

sudo installer -pkg "$tmpdir"/BeaconEndpointAgent-*-arm64.pkg -target /
/opt/beacon/bin/beacon version
```

The package installs Beacon, the collector, bundled Vector, and the S3 and GCS
forwarder helpers. Confirm the system endpoint is healthy:

```bash theme={null}
sudo /opt/beacon/bin/beacon endpoint status --system
sudo launchctl print system/com.beacon.endpoint.collector
```

## 2. Configure Runtime Harnesses

Object storage forwarding begins after events reach Beacon's system logs.
Configure the runtimes you use before troubleshooting the destination.

The package configures the default native OTLP path for Claude Code and Codex
CLI for the active console user. Other hook- or plugin-backed integrations use
their normal endpoint installation commands. For example:

```bash theme={null}
/opt/beacon/bin/beacon endpoint hooks install \
  --harness cursor \
  --level user \
  --log-path /var/log/beacon-agent/runtime.jsonl

/opt/beacon/bin/beacon endpoint hooks install \
  --harness opencode \
  --level user \
  --log-path /var/log/beacon-agent/runtime.jsonl
```

Run hook installers as the interactive user, not root. Review
[Agent Harness Integrations](/runtimes) and
[Endpoint Hooks](/cli/hooks) for the supported setup method for each runtime.
Restart the runtime after changing hooks or OTLP settings.

Confirm recent local events before configuring a cloud destination:

```bash theme={null}
sudo /opt/beacon/bin/beacon endpoint status --system --json
ls -l \
  /var/log/beacon-agent/runtime.jsonl \
  /var/log/beacon-agent/inventory_state.jsonl
```

## 3. Choose A Destination

Use a root prefix such as `beacon-prod`. Do not include `runtime` or
`inventory`; Beacon appends those folders.

### AWS S3

You need an AWS account where your current AWS CLI identity can create an S3
bucket, IAM user, and inline policy. Confirm the identity before making changes:

```bash theme={null}
aws sts get-caller-identity
```

Choose globally unique bucket and writer names:

```bash theme={null}
export AWS_REGION="us-west-2"
export BEACON_S3_BUCKET="<globally-unique-bucket>"
export BEACON_S3_PREFIX="beacon-prod"
export BEACON_S3_STORAGE_CLASS="STANDARD"
export BEACON_S3_WRITER="beacon-endpoint-writer"
```

Create the bucket. AWS requires a different command shape for `us-east-1`:

```bash theme={null}
if [ "$AWS_REGION" = "us-east-1" ]; then
  aws s3api create-bucket \
    --bucket "$BEACON_S3_BUCKET" \
    --region "$AWS_REGION"
else
  aws s3api create-bucket \
    --bucket "$BEACON_S3_BUCKET" \
    --region "$AWS_REGION" \
    --create-bucket-configuration "LocationConstraint=$AWS_REGION"
fi
```

Create a dedicated writer and grant only `s3:PutObject` below the Beacon root
prefix:

```bash theme={null}
aws iam create-user --user-name "$BEACON_S3_WRITER"

aws iam put-user-policy \
  --user-name "$BEACON_S3_WRITER" \
  --policy-name BeaconEndpointWriteOnly \
  --policy-document "$(cat <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:PutObject"],
    "Resource": "arn:aws:s3:::${BEACON_S3_BUCKET}/${BEACON_S3_PREFIX}/*"
  }]
}
EOF
)"
```

Create an access key:

```bash theme={null}
aws iam create-access-key \
  --user-name "$BEACON_S3_WRITER" \
  --output json
```

AWS displays the secret only once. Store both values in your password manager
or endpoint secret tooling, then export them without committing them or placing
them in shell startup files:

```bash theme={null}
export AWS_ACCESS_KEY_ID="<AccessKeyId from the previous command>"
export AWS_SECRET_ACCESS_KEY="<SecretAccessKey from the previous command>"
export AWS_SESSION_TOKEN="" # only set for temporary credentials
```

Then install only the S3 forwarder:

```bash theme={null}
sudo env \
  BEACON_S3_BUCKET="$BEACON_S3_BUCKET" \
  BEACON_S3_PREFIX="$BEACON_S3_PREFIX" \
  BEACON_S3_STORAGE_CLASS="$BEACON_S3_STORAGE_CLASS" \
  AWS_REGION="$AWS_REGION" \
  AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \
  AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \
  AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN:-}" \
  /opt/beacon/jamf/claude/s3/install-forwarder.sh
```

The helper stores the provider values in a root-owned `0600` file for the
launchd service. For production, prefer temporary credentials, a managed
profile, or web identity where your environment can refresh them. Configure
bucket encryption, lifecycle, retention, and access logging in AWS rather than
in Beacon.

### Google Cloud Storage

You need a Google Cloud project where your current `gcloud` identity can enable
APIs, create a bucket, create a service account, and manage bucket IAM. Confirm
the active identity and project:

```bash theme={null}
gcloud auth list --filter=status:ACTIVE
gcloud config get-value project
```

Choose the project, bucket, location, prefix, and writer name:

```bash theme={null}
export GCP_PROJECT="<project-id>"
export GCP_LOCATION="us-west1"
export BEACON_GCS_BUCKET="<globally-unique-bucket>"
export BEACON_GCS_PREFIX="beacon-prod"
export BEACON_GCS_STORAGE_CLASS="STANDARD"
export BEACON_GCS_WRITER="beacon-endpoint-writer"
export BEACON_GCS_WRITER_EMAIL="${BEACON_GCS_WRITER}@${GCP_PROJECT}.iam.gserviceaccount.com"
```

Enable the APIs and create a uniform-access bucket:

```bash theme={null}
gcloud services enable storage.googleapis.com iam.googleapis.com \
  --project "$GCP_PROJECT"

gcloud storage buckets create "gs://${BEACON_GCS_BUCKET}" \
  --project "$GCP_PROJECT" \
  --location "$GCP_LOCATION" \
  --default-storage-class "$BEACON_GCS_STORAGE_CLASS" \
  --uniform-bucket-level-access
```

Create the writer service account:

```bash theme={null}
gcloud iam service-accounts create "$BEACON_GCS_WRITER" \
  --project "$GCP_PROJECT" \
  --display-name "Beacon endpoint GCS writer"
```

Grant it write-only access to the bucket:

```bash theme={null}
gcloud storage buckets add-iam-policy-binding \
  "gs://${BEACON_GCS_BUCKET}" \
  --member "serviceAccount:${BEACON_GCS_WRITER_EMAIL}" \
  --role roles/storage.objectCreator
```

The packaged Vector 0.56 service on macOS needs a service-account JSON file. If
your organization allows service-account keys, create one in a temporary
directory:

```bash theme={null}
credential_dir="$(mktemp -d)"
credential_file="${credential_dir}/beacon-gcs-writer.json"
umask 077

gcloud iam service-accounts keys create "$credential_file" \
  --project "$GCP_PROJECT" \
  --iam-account "$BEACON_GCS_WRITER_EMAIL"
```

Install the JSON outside Beacon-managed directories so upgrades and cleanup do
not remove it:

```bash theme={null}
export GOOGLE_APPLICATION_CREDENTIALS="/Library/Application Support/Your Organization/Secrets/beacon-gcs-writer.json"

sudo install -d -m 0700 -o root -g wheel \
  "$(dirname "$GOOGLE_APPLICATION_CREDENTIALS")"
sudo install -m 0600 -o root -g wheel \
  "$credential_file" \
  "$GOOGLE_APPLICATION_CREDENTIALS"

rm -rf "$credential_dir"
```

Then install only the GCS forwarder:

```bash theme={null}
sudo env \
  BEACON_GCS_BUCKET="$BEACON_GCS_BUCKET" \
  BEACON_GCS_PREFIX="$BEACON_GCS_PREFIX" \
  BEACON_GCS_STORAGE_CLASS="$BEACON_GCS_STORAGE_CLASS" \
  GOOGLE_APPLICATION_CREDENTIALS="$GOOGLE_APPLICATION_CREDENTIALS" \
  /opt/beacon/jamf/claude/gcs/install-forwarder.sh
```

The endpoint writer intentionally cannot list or read objects. Validate with
your current administrator identity or a separate identity with
`roles/storage.objectViewer`. If service-account key creation is blocked by
organization policy, stop here: packaged Vector 0.56 does not support
external-account Workload Identity Federation on macOS. Configure lifecycle,
retention, audit logging, and CMEK controls in Google Cloud rather than in
Beacon.

### Forward To Both

The services have separate labels, configs, credentials, and checkpoint
directories. Install both helpers to forward the same local events to S3 and
GCS. After exporting the provider variables from the two sections above, run:

```bash theme={null}
sudo env \
  BEACON_S3_BUCKET="$BEACON_S3_BUCKET" \
  BEACON_S3_PREFIX="$BEACON_S3_PREFIX" \
  BEACON_S3_STORAGE_CLASS="$BEACON_S3_STORAGE_CLASS" \
  AWS_REGION="$AWS_REGION" \
  AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \
  AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \
  AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN:-}" \
  /opt/beacon/jamf/claude/s3/install-forwarder.sh

sudo env \
  BEACON_GCS_BUCKET="$BEACON_GCS_BUCKET" \
  BEACON_GCS_PREFIX="$BEACON_GCS_PREFIX" \
  BEACON_GCS_STORAGE_CLASS="$BEACON_GCS_STORAGE_CLASS" \
  GOOGLE_APPLICATION_CREDENTIALS="$GOOGLE_APPLICATION_CREDENTIALS" \
  /opt/beacon/jamf/claude/gcs/install-forwarder.sh
```

This creates two independent services:

```text theme={null}
com.beacon.endpoint.s3-forwarder
com.beacon.endpoint.gcs-forwarder
```

## 4. Validate

Check the configured services:

```bash theme={null}
sudo launchctl print system/com.beacon.endpoint.collector
sudo launchctl print system/com.beacon.endpoint.s3-forwarder  # when configured
sudo launchctl print system/com.beacon.endpoint.gcs-forwarder # when configured
```

Write destination and inventory validation events:

```bash theme={null}
sudo /opt/beacon/bin/beacon endpoint s3 validate --system  # when configured
sudo /opt/beacon/bin/beacon endpoint gcs validate --system # when configured

sudo /opt/beacon/bin/beacon endpoint inventory heartbeat \
  --system \
  --force \
  --trigger manual \
  --trigger-harness manual \
  --working-dir /Users/Shared \
  --log-path /var/log/beacon-agent/runtime.jsonl
```

Vector batches for up to five minutes by default. Use an authorized reader to
inspect the destination:

```bash theme={null}
# AWS S3
aws s3 ls \
  "s3://${BEACON_S3_BUCKET}/${BEACON_S3_PREFIX}/runtime/" \
  --recursive \
  --region "$AWS_REGION"
aws s3 ls \
  "s3://${BEACON_S3_BUCKET}/${BEACON_S3_PREFIX}/inventory/" \
  --recursive \
  --region "$AWS_REGION"

# Google Cloud Storage
gcloud storage ls \
  "gs://${BEACON_GCS_BUCKET}/${BEACON_GCS_PREFIX}/runtime/**"
gcloud storage ls \
  "gs://${BEACON_GCS_BUCKET}/${BEACON_GCS_PREFIX}/inventory/**"
```

Inspect a runtime object and look for the destination validation event:

```bash theme={null}
aws s3 cp \
  "s3://${BEACON_S3_BUCKET}/${BEACON_S3_PREFIX}/runtime/date=<YYYY-MM-DD>/<object>.jsonl.gz" \
  - \
  --region "$AWS_REGION" |
  gzip -dc | grep "Beacon endpoint S3 validation event"

gcloud storage cat \
  "gs://${BEACON_GCS_BUCKET}/${BEACON_GCS_PREFIX}/runtime/date=<YYYY-MM-DD>/<object>.jsonl.gz" |
  gzip -dc | grep "Beacon endpoint GCS validation event"
```

The endpoint writer identities are intentionally write-only. Run these
inspection commands with your administrator identity or a separate reader.

Generate a real event from every configured harness and confirm its
`harness.name` appears in a remote runtime object.

## 5. Operate And Troubleshoot

The forwarders persist checkpoints, so restarts and package upgrades resume from
the last observed offsets. Runtime starts at the end on first install to avoid
unexpected historical backfill; inventory starts at the beginning so the first
snapshot is not missed.

Check Vector errors:

```bash theme={null}
sudo tail -n 100 /tmp/com.beacon.endpoint.s3-forwarder.err
sudo tail -n 100 /tmp/com.beacon.endpoint.gcs-forwarder.err
```

Common causes of missing objects are:

* The validation event was written before Vector started. Write another event.
* The configured bucket, region, or root prefix does not match the IAM policy.
* The service credential cannot create objects.
* The GCS credential path is not absolute, root-owned, and mode `0400` or
  `0600`.
* The write-only endpoint identity is being used for a list/read validation
  command.

### Rotate AWS credentials

Create a replacement key, rerun the S3 helper with the new values, confirm a new
object, and only then delete the retired key:

```bash theme={null}
aws iam create-access-key \
  --user-name "$BEACON_S3_WRITER" \
  --output json

export AWS_ACCESS_KEY_ID="<new AccessKeyId>"
export AWS_SECRET_ACCESS_KEY="<new SecretAccessKey>"

sudo env \
  BEACON_S3_BUCKET="$BEACON_S3_BUCKET" \
  BEACON_S3_PREFIX="$BEACON_S3_PREFIX" \
  BEACON_S3_STORAGE_CLASS="$BEACON_S3_STORAGE_CLASS" \
  AWS_REGION="$AWS_REGION" \
  AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \
  AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \
  /opt/beacon/jamf/claude/s3/install-forwarder.sh

aws iam delete-access-key \
  --user-name "$BEACON_S3_WRITER" \
  --access-key-id "<retired-access-key-id>"
```

### Rotate Google credentials

Create a replacement key in a temporary directory, atomically replace the
externally managed file, restart Vector, confirm a new object, and then delete
the retired service-account key:

```bash theme={null}
next_dir="$(mktemp -d)"
next_key="${next_dir}/beacon-gcs-writer.json"
umask 077

gcloud iam service-accounts keys create "$next_key" \
  --project "$GCP_PROJECT" \
  --iam-account "$BEACON_GCS_WRITER_EMAIL"

sudo install -m 0600 -o root -g wheel \
  "$next_key" \
  "${GOOGLE_APPLICATION_CREDENTIALS}.new"
sudo mv -f \
  "${GOOGLE_APPLICATION_CREDENTIALS}.new" \
  "$GOOGLE_APPLICATION_CREDENTIALS"
rm -rf "$next_dir"

sudo launchctl kickstart -k system/com.beacon.endpoint.gcs-forwarder

gcloud iam service-accounts keys list \
  --project "$GCP_PROJECT" \
  --iam-account "$BEACON_GCS_WRITER_EMAIL"
gcloud iam service-accounts keys delete "<retired-key-id>" \
  --project "$GCP_PROJECT" \
  --iam-account "$BEACON_GCS_WRITER_EMAIL"
```

For managed fleets, use the dedicated [Jamf S3 guide](/guides/jamf-s3-mdm) or
[Jamf GCS guide](/guides/jamf-gcs-mdm). Those guides show the optional
Claude-oriented combined repair wrappers as well as policy parameters and
managed credential delivery.
