# EntPEP trial install: instructions for Claude

> **For the person installing.** In a terminal on your computer (macOS or Linux), with the AWS CLI
> signed in (`aws login` works), start Claude Code in an empty folder and say:
>
> *Download https://entpep.com/dl/entpep-trial-install.md with curl, read it, and step me through
> the EntPEP trial install.*
>
> Claude runs the commands on your computer and on the two servers, explains each step, and asks
> before it creates anything that costs money. Our own run took about 15 minutes to a working
> install; allow 30-60 minutes the first time, more if your Microsoft 365 admin sets up SharePoint.
> Downloads, videos and the contact form: https://entpep.com/trial

Everything below is written to **Claude**, the assistant doing the install.

---

## Your job

Install an EntPEP trial in the user's AWS account and get them to a working demo:

- **host**: the agent host (`host.<their domain>`, m8i.2xlarge with nested virtualization). The
  Agent Fleet Manager runs on 443, and each agent runs in its own small VM on ports 8001-8008.
- **data**: the private server (`data.<their domain>`, g6.xlarge with an NVIDIA L4). It holds the
  data lake (ClickHouse with a demo trade blotter, on 8443) and a private model (`qwen3:14b`, on 443).

```
browser ──443──▶ host (Fleet Manager; agents on 8001-8008)
                  │  model ──▶ Anthropic API, Claude on Bedrock, or ──443──▶ data (qwen3:14b)
                  └─ data lake ──8443──▶ data (ClickHouse)
```

Trial builds stop working on **1 January 2027 (UTC)**.

## Rules for you

1. **Run the commands yourself** with your shell tool, on the user's computer and on the servers
   over `ssh`. Before each phase, say in a sentence or two what it does and why. Don't paste long
   command lists at the user to run unless they ask to do it themselves.
2. **Ask before creating anything billable** (instances, Elastic IPs) and before anything
   destructive (stop, terminate, release). Show the cost once: about **$1.25/hour** with both
   running (m8i.2xlarge about $0.42, g6.xlarge about $0.80), plus about $15/month for disks and the
   two addresses while they exist.
3. **Shell state doesn't persist between your commands.** Keep every value in
   `~/entpep-trial/trial.env` and start each command with `source ~/entpep-trial/trial.env && …`.
   Append new values as you learn them (`echo 'HID=i-…' >> ~/entpep-trial/trial.env`).
4. **Don't put an option list in one variable** (`SSHK="-i key.pem"`). zsh, the macOS default
   shell, passes it as a single word and ssh fails. Write `ssh -i "$KEYFILE" …` with the variable
   quoted. Use `ssh -o StrictHostKeyChecking=accept-new` the first time you connect to each server.
5. **Never print a secret**: data lake passwords, model keys, the Fleet Manager password, API keys.
   Move them with pipes (`ssh data "sudo cat …" | ssh host "umask 077; cat > file"`), put them on
   the user's clipboard with `| pbcopy` (macOS), or have the user type them into a file themselves.
   Don't ask the user to paste a secret into the chat. To get a key from them, have them copy it
   and run `! umask 077; pbpaste > ~/entpep-trial/anthropic.key` in this session (on Linux, `! umask
   077; cat > ~/entpep-trial/anthropic.key`, paste, Enter, Ctrl-D).
6. **Long installs:** start them with `nohup` on the server, writing to a log, then check the log
   every minute or so instead of holding one ssh session open (see phases 6 and 8).
7. **When something fails**, read the error, check the Troubleshooting table at the end, fix it and
   carry on. Don't work around a step by turning off a check.
8. Keep the user informed in plain words. They may not know AWS well.

---

## Phase 0: Check the basics and ask the questions

Check that the AWS CLI is signed in. Your tool's safety check may flag a command that reads the
account identity as reconnaissance, so say first what you're checking and why (the install creates
resources in this account), and run only this:

```bash
aws sts get-caller-identity --query Account --output text
```

If it's blocked or fails, ask the user to run `! aws sts get-caller-identity --query Account --output
text` themselves (or `! aws login` first if they aren't signed in) and tell you it printed an
account number. Don't probe for other tools up front: `ssh` and `curl` come with macOS and Linux,
and if `dig` turns out to be missing in phase 3, use `nslookup` or `host`.

Then ask these together (use your question tool if you have one):

1. **Region.** Default `us-east-2`. It must offer g6.xlarge and m8i.2xlarge.
2. **The two DNS names**, e.g. `host.example.com` and `data.example.com`, and **where their DNS is
   hosted** (Cloudflare, Route 53, other). If it's Route 53 in this account, offer to create the
   records yourself.
3. **An email for Let's Encrypt** (certificate expiry notices).
4. **Which model or models**: an Anthropic API key, Claude on Amazon Bedrock in this AWS account,
   the private model on data, or a mix. Two notes for them: the private model is installed either
   way; and in the demo the research sub-agent searches the web with **Claude**, so it needs the
   Anthropic API or Bedrock.
5. **SharePoint.** The demo's documents come from a SharePoint site the host reaches with its own
   Entra app. Will they have a Microsoft 365 admin for about 20 minutes (phase 10)? If not, the
   install still works and the data lake parts of the demo run, but the agent has no documents.
6. **How to create the AWS pieces:**
   - **"Do it all"**: you create the key pair, two security groups, two Elastic IPs and both
     instances with the CLI (phase 2A).
   - **"I'll make them in the AWS console"**: the user creates the key pair, security groups and
     both instances in the web console from your list, then you find them, turn on nested
     virtualization and attach Elastic IPs (phase 2B).

Write what you learn to the state file:

```bash
mkdir -p ~/entpep-trial && chmod 700 ~/entpep-trial && cat > ~/entpep-trial/trial.env <<'EOF'
REGION=us-east-2
H=host.example.com
D=data.example.com
EMAIL=you@example.com
EOF
```

## Phase 1: Account checks (you)

```bash
source ~/entpep-trial/trial.env && aws service-quotas get-service-quota --region $REGION --service-code ec2 --quota-code L-DB2E81BA --query Quota.Value
```

This is the **Running On-Demand G and VT instances** quota, in vCPUs. It must be **4 or more** for
one g6.xlarge. New accounts often have 0. If it's too low, tell the user and offer to request it
(`aws service-quotas request-service-quota-increase --region $REGION --service-code ec2 --quota-code
L-DB2E81BA --desired-value 4`). Approval can take from minutes to a day. Meanwhile you can create the key pair, addresses and
security groups, and launch the host; the data server waits for the quota.

Find a zone that has both instance types, and the default VPC's subnet there:

```bash
source ~/entpep-trial/trial.env && aws ec2 describe-instance-type-offerings --region $REGION --location-type availability-zone --filters Name=instance-type,Values=g6.xlarge,m8i.2xlarge --query 'InstanceTypeOfferings[].[Location,InstanceType]' --output text | sort
```

Pick a zone listed for both. Then:

```bash
source ~/entpep-trial/trial.env && VPC=$(aws ec2 describe-vpcs --region $REGION --filters Name=is-default,Values=true --query 'Vpcs[0].VpcId' --output text) && SUBNET=$(aws ec2 describe-subnets --region $REGION --filters Name=vpc-id,Values=$VPC Name=availability-zone,Values=ZONE --query 'Subnets[0].SubnetId' --output text) && MYIP=$(curl -s https://checkip.amazonaws.com) && printf 'VPC=%s\nSUBNET=%s\nMYIP=%s\n' "$VPC" "$SUBNET" "$MYIP" >> ~/entpep-trial/trial.env && tail -3 ~/entpep-trial/trial.env
```

If there's no default VPC, ask which VPC and public subnet to use.

## Phase 2A: "Do it all" (you create everything)

Confirm with the user first (cost, rule 2). Tag everything `entpep-trial=1` so teardown can find it.

**Key pair:**

```bash
source ~/entpep-trial/trial.env && aws ec2 create-key-pair --region $REGION --key-name entpep-trial --tag-specifications 'ResourceType=key-pair,Tags=[{Key=entpep-trial,Value=1}]' --query KeyMaterial --output text > ~/entpep-trial/entpep-trial.pem && chmod 400 ~/entpep-trial/entpep-trial.pem && printf 'KEY=entpep-trial\nKEYFILE=%s\n' ~/entpep-trial/entpep-trial.pem >> ~/entpep-trial/trial.env
```

If the key name already exists, ask whether to use theirs (they need the `.pem` file) or create
`entpep-trial-2`.

**Two Elastic IPs.** Fixed addresses keep DNS and the data server's allowlist right after a stop and
start:

```bash
source ~/entpep-trial/trial.env && for s in H D; do A=$(aws ec2 allocate-address --region $REGION --domain vpc --tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=entpep-trial,Value=1}]' --query AllocationId --output text); IP=$(aws ec2 describe-addresses --region $REGION --allocation-ids $A --query 'Addresses[0].PublicIp' --output text); printf '%sALLOC=%s\n%sIP=%s\n' $s $A $s $IP >> ~/entpep-trial/trial.env; done; tail -4 ~/entpep-trial/trial.env
```

**Security groups.** host: SSH, 443 and 8000-8999 from the user's address, 80 from anywhere (Let's
Encrypt). data: SSH from the user, 443 (model) and 8443 (data lake) **only from the host's address**,
80 from anywhere.

```bash
source ~/entpep-trial/trial.env && HSG=$(aws ec2 create-security-group --region $REGION --vpc-id $VPC --group-name entpep-trial-host --description "EntPEP trial agent host" --query GroupId --output text) && DSG=$(aws ec2 create-security-group --region $REGION --vpc-id $VPC --group-name entpep-trial-data --description "EntPEP trial private server" --query GroupId --output text) && printf 'HSG=%s\nDSG=%s\n' $HSG $DSG >> ~/entpep-trial/trial.env && for p in 22 443 8000-8999; do aws ec2 authorize-security-group-ingress --region $REGION --group-id $HSG --protocol tcp --port $p --cidr $MYIP/32 >/dev/null; done && aws ec2 authorize-security-group-ingress --region $REGION --group-id $HSG --protocol tcp --port 80 --cidr 0.0.0.0/0 >/dev/null && aws ec2 authorize-security-group-ingress --region $REGION --group-id $DSG --protocol tcp --port 22 --cidr $MYIP/32 >/dev/null && for p in 443 8443; do aws ec2 authorize-security-group-ingress --region $REGION --group-id $DSG --protocol tcp --port $p --cidr $HIP/32 >/dev/null; done && aws ec2 authorize-security-group-ingress --region $REGION --group-id $DSG --protocol tcp --port 80 --cidr 0.0.0.0/0 >/dev/null && echo "host $HSG  data $DSG"
```

**Launch both.** The host **must** have `NestedVirtualization=enabled`. Without it there's no
`/dev/kvm` and the host installer stops.

```bash
source ~/entpep-trial/trial.env && AMI=resolve:ssm:/aws/service/canonical/ubuntu/server/26.04/stable/current/amd64/hvm/ebs-gp3/ami-id && HID=$(aws ec2 run-instances --region $REGION --instance-type m8i.2xlarge --image-id $AMI --cpu-options NestedVirtualization=enabled --key-name $KEY --subnet-id $SUBNET --security-group-ids $HSG --metadata-options HttpTokens=required,HttpEndpoint=enabled --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=30,VolumeType=gp3,Encrypted=true}' --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=EntPEP-Trial-Host},{Key=entpep-trial,Value=1}]' --query 'Instances[0].InstanceId' --output text) && echo "HID=$HID" >> ~/entpep-trial/trial.env && echo $HID
```

```bash
source ~/entpep-trial/trial.env && AMI=resolve:ssm:/aws/service/canonical/ubuntu/server/26.04/stable/current/amd64/hvm/ebs-gp3/ami-id && DID=$(aws ec2 run-instances --region $REGION --instance-type g6.xlarge --image-id $AMI --key-name $KEY --subnet-id $SUBNET --security-group-ids $DSG --metadata-options HttpTokens=required,HttpEndpoint=enabled --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=80,VolumeType=gp3,Encrypted=true}' --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=EntPEP-Trial-Data},{Key=entpep-trial,Value=1}]' --query 'Instances[0].InstanceId' --output text) && echo "DID=$DID" >> ~/entpep-trial/trial.env && echo $DID
```

Then wait, attach the addresses and check nested virtualization (it must print `enabled`):

```bash
source ~/entpep-trial/trial.env && aws ec2 wait instance-running --region $REGION --instance-ids $HID $DID && aws ec2 associate-address --region $REGION --instance-id $HID --allocation-id $HALLOC >/dev/null && aws ec2 associate-address --region $REGION --instance-id $DID --allocation-id $DALLOC >/dev/null && aws ec2 describe-instances --region $REGION --instance-ids $HID --query 'Reservations[0].Instances[0].CpuOptions.NestedVirtualization' --output text
```

Go to phase 3.

## Phase 2B: "I'll make them in the AWS console"

Give the user this list, then wait for them to say it's done. Tell them which **zone** you picked in
phase 1 and their **public IP** (`MYIP`).

1. **EC2 → Key pairs → Create key pair**: name `entpep-trial`, RSA, `.pem`. Move the downloaded file
   to `~/entpep-trial/entpep-trial.pem` (or tell you where it is).
2. **EC2 → Security groups**, two groups in the default VPC:
   - `entpep-trial-host`, inbound: SSH 22, HTTPS 443 and Custom TCP 8000-8999 from **My IP**; HTTP 80
     from **Anywhere-IPv4**.
   - `entpep-trial-data`, inbound: SSH 22 from **My IP**; HTTP 80 from **Anywhere-IPv4**. Leave 443
     and 8443 out for now: you add them from the host's address once it has one.
3. **EC2 → Launch instance**, twice, both **Ubuntu Server 26.04 LTS** (64-bit x86), key pair
   `entpep-trial`, **in the zone you named**, public IP enabled:
   - `EntPEP-Trial-Host`: **m8i.2xlarge**, 30 GB gp3 or more, group `entpep-trial-host`. The console
     can't turn on nested virtualization; you do that next.
   - `EntPEP-Trial-Data`: **g6.xlarge**, **80 GB** gp3 or more, group `entpep-trial-data`.

Then do the rest yourself, telling the user as you go:

```bash
source ~/entpep-trial/trial.env && aws ec2 describe-instances --region $REGION --filters Name=instance-state-name,Values=pending,running,stopped --query 'Reservations[].Instances[].[InstanceId,InstanceType,State.Name,KeyName,Placement.AvailabilityZone,Tags[?Key==`Name`]|[0].Value]' --output table
```

Confirm with the user which instance is which (by type and name), then save `HID`, `DID`, `KEY`,
`KEYFILE` (`chmod 400` it), and the two security group IDs `HSG`, `DSG`
(`aws ec2 describe-instances … --query 'Reservations[0].Instances[0].SecurityGroups'`).

**Turn on nested virtualization on the host** (stop, modify, start). Check you have the **host's**
ID (the m8i.2xlarge); the data server doesn't need this:

```bash
source ~/entpep-trial/trial.env && aws ec2 stop-instances --region $REGION --instance-ids $HID >/dev/null && aws ec2 wait instance-stopped --region $REGION --instance-ids $HID && aws ec2 modify-instance-cpu-options --region $REGION --instance-id $HID --nested-virtualization enabled && aws ec2 start-instances --region $REGION --instance-ids $HID >/dev/null && aws ec2 wait instance-running --region $REGION --instance-ids $HID && aws ec2 describe-instances --region $REGION --instance-ids $HID --query 'Reservations[0].Instances[0].CpuOptions.NestedVirtualization' --output text
```

If the stop hangs for more than 10 minutes, `aws ec2 stop-instances --region $REGION
--instance-ids $HID --force`.

**Attach Elastic IPs** (ask first, rule 2): run the Elastic IP command from phase 2A, then the two
`associate-address` commands. A console-assigned public IP changes on every stop and start; the
Elastic IP doesn't.

**Open data to the host:** add 443 and 8443 on `DSG` from `$HIP/32` (the two lines from phase 2A's
security group command).

## Phase 3: DNS

The records: `H` → `HIP` and `D` → `DIP`, type **A**.

- **Route 53 in this account:** offer to create them (`aws route53 list-hosted-zones`, then
  `change-resource-record-sets` with an UPSERT).
- **Cloudflare:** the user adds them, set to **DNS only** (grey cloud), not proxied. Let's Encrypt
  and the agents' ports don't work through the proxy.
- **Others:** the user adds them.

Show the user the two names and addresses, then check until both match (it can take a few minutes):

```bash
source ~/entpep-trial/trial.env && echo "$H $(dig +short $H) (want $HIP)"; echo "$D $(dig +short $D) (want $DIP)"
```

Don't start phase 6 or 8 until each name resolves: Let's Encrypt needs it.

## Phase 4: First connection to both servers

```bash
source ~/entpep-trial/trial.env && for s in $H $D; do ssh -i "$KEYFILE" -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 ubuntu@$s 'hostname; uname -r'; done
```

On the host, check `/dev/kvm` exists: `ssh -i "$KEYFILE" ubuntu@$H 'ls -l /dev/kvm'`. If it doesn't,
nested virtualization is off (phase 2B's stop, modify, start).

## Phase 5: The order from here

Data first (its pass 2 takes the longest), then copy two passwords from data to host, then the host.
Start the host's install while data's pass 2 is running if you like; it only needs the passwords at
the end. The simple path is one after the other.

## Phase 6: Install the private server (data)

**Pass 1** installs the NVIDIA driver and exits with code **3**, which is expected:

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$D "curl -sSfO https://entpep.com/dl/combined-private-install-v1.tgz && tar xzf combined-private-install-v1.tgz && cd combined-private-install-v1 && sudo ./install.sh --hostname $D --certbot $EMAIL --allow $HIP"; echo "exit $?"
```

Then reboot and wait for SSH to come back (about a minute):

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$D 'sudo reboot'; sleep 60; until ssh -i "$KEYFILE" -o ConnectTimeout=5 ubuntu@$D true 2>/dev/null; do sleep 10; done; echo back
```

(If your shell tool won't run `sleep`, check every 30 seconds with separate commands.)

**Pass 2** (about 5 minutes: it pulls the 9 GB model, installs ClickHouse with the demo data and the
two nginx front doors, then checks them). Run it in the background and follow the log:

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$D "cd combined-private-install-v1 && nohup sudo ./install.sh --hostname $D --certbot $EMAIL --allow $HIP > ~/install-pass2.log 2>&1 < /dev/null & echo started"
```

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$D 'tail -5 ~/install-pass2.log'
```

It's finished when the log ends with `== Done` after four checks: the model answers, the model is on
the GPU, `entpep_host reads trade_blotter (37 rows)`, and the admin login is refused (403). If it ends
`WITH ERRORS` or `install: stopped at line …`, read the lines above it.

## Phase 7: Copy the data lake logins to the host

Two passwords, data → host, never printed:

```bash
source ~/entpep-trial/trial.env && for n in host desk; do ssh -i "$KEYFILE" ubuntu@$D "sudo cat /etc/entpep-datalake/$n.pass" | ssh -i "$KEYFILE" ubuntu@$H "umask 077; cat > ch-$n.pass"; done && ssh -i "$KEYFILE" ubuntu@$H 'ls -l ch-*.pass'
```

Both files must be non-empty and `-rw-------`.

## Phase 8: Install the agent host

**Anthropic key** (if they chose it): have the user put it in `~/entpep-trial/anthropic.key` as in
rule 5, then copy it to the host without printing it:

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$H "umask 077; cat > a.key" < ~/entpep-trial/anthropic.key && echo copied
```

Install. Add `--anthropic-key-file ~/a.key` only if you copied a key. With no terminal attached the
installer doesn't prompt; a key can be added later (phase 9).

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$H "curl -sSfO https://entpep.com/dl/entpep-install-v1.tgz && tar xzf entpep-install-v1.tgz && cd entpep-install-v1 && nohup sudo ./install.sh --hostname $H --certbot $EMAIL --clickhouse-url https://$D:8443/ --clickhouse-user entpep_host --clickhouse-password-file ~/ch-host.pass --clickhouse-desk-password-file ~/ch-desk.pass > ~/install-host.log 2>&1 < /dev/null & echo started"
```

Follow `tail -20 ~/install-host.log` until the summary appears (a few minutes). Look for:

- `data lake: https://<data>:8443/ answers as entpep_host`
- `Trial build: works until 2027-01-01`
- `Fleet Manager: https://<host>/`

A `SharePoint` warning is expected before phase 10. Then remove the copies:

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$H 'shred -u ~/ch-host.pass ~/ch-desk.pass; [ -f ~/a.key ] && shred -u ~/a.key; true'
```

**Sign in.** Put the Fleet Manager password on the clipboard (macOS) and send the user to
`https://<host>/` with any name:

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$H 'sudo cat /opt/entpep/fleetcerts/fleet.pass' | tr -d '\n' | pbcopy && echo "password copied"
```

On Linux, tell the user to run `! ssh -i <keyfile> ubuntu@<host> 'sudo cat /opt/entpep/fleetcerts/fleet.pass'`
themselves (it then shows in their terminal, not in your context).

## Phase 9: Choose the model (Admin → Model)

They can enable more than one and choose per agent at launch.

**Private model on data.** Put its key on the clipboard:

```bash
source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$D 'sudo head -1 /etc/entpep-llm/keys' | tr -d '\n' | pbcopy && echo "model key copied"
```

In **Admin → Model → Private server**: host = the data name, **model = `qwen3:14b`** (the box
starts with an old default, `qwen2.5:1.5b`, which isn't installed and gives `404: model … not
found`), key = paste, **Test & enable**.

**Anthropic API.** Already set if you passed the key in phase 8. To add it later: copy the key file
as in phase 8, then on the host `cd entpep-install-v1 && sudo ./install.sh --anthropic-key-file
~/a.key && shred -u ~/a.key`. Re-running the installer keeps everything else.

**Claude on Amazon Bedrock.** Once per account, the user opens the Bedrock console's **Model access**
page and completes Anthropic's use-case form. Then you create an IAM user that may only call Claude,
and an API key for it (it goes to the clipboard, not the screen):

```bash
ACCT=$(aws sts get-caller-identity --query Account --output text) && aws iam create-user --user-name entpep-bedrock --tags Key=entpep-trial,Value=1 >/dev/null && aws iam put-user-policy --user-name entpep-bedrock --policy-name invoke-claude-only --policy-document "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"bedrock:InvokeModel\",\"bedrock:InvokeModelWithResponseStream\"],\"Resource\":[\"arn:aws:bedrock:*::foundation-model/anthropic.*\",\"arn:aws:bedrock:*:$ACCT:inference-profile/*anthropic.*\"]},{\"Effect\":\"Allow\",\"Action\":\"bedrock:CallWithBearerToken\",\"Resource\":\"*\"}]}" && aws iam create-service-specific-credential --user-name entpep-bedrock --service-name bedrock.amazonaws.com --credential-age-days 90 --query ServiceSpecificCredential.ServiceCredentialSecret --output text | tr -d '\n' | pbcopy && echo "Bedrock key copied"
```

In **Admin → Model → Amazon Bedrock**: region (e.g. `us-east-2`), model ID
`us.anthropic.claude-sonnet-5`, key = paste, **Test & enable**. The key expires in 90 days.

**Keep confidential data internal** (Admin, off by default) shows the policy: agents that can reach
the data lake are labelled *confidential* and may use only Bedrock or the private model.

## Phase 10: SharePoint (needs their Microsoft 365 admin, about 20 minutes)

The host signs in to Microsoft as its own Entra app, using a certificate the installer made
(`/opt/entpep/sharepoint-app.cer`). The app reaches **one SharePoint site and nothing else**
(`Sites.Selected`). Guide the admin step by step; they do steps 2 to 5 in their browser. The full
walkthrough with more detail is https://entpep.com/dl/EntPEP-Trial-Microsoft-Setup.pdf.

1. **You:** download the host's certificate (public half only) and the demo files:
   ```bash
   source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$H 'cat /opt/entpep/sharepoint-app.cer' > ~/entpep-trial/sharepoint-app.cer && curl -sSfo ~/entpep-trial/EntPEP-Trial-Demo-Files.zip https://entpep.com/dl/EntPEP-Trial-Demo-Files.zip && cd ~/entpep-trial && unzip -o -q EntPEP-Trial-Demo-Files.zip && ls ~/entpep-trial/source
   ```
   It should list six files.
2. **Admin: a SharePoint team site**, e.g. `EntPEP Trial`, private. In its **Documents** library,
   a folder `Barclays` with two folders inside it, `source` and `reports`. Upload the six files from
   `~/entpep-trial/source` into `Barclays/source`. Optional, for the demo's "check every file" step: also upload
   `~/entpep-trial/Legal Agreements` (28 files, with Legal's clause library in `standards/`) as a top-level folder, with an empty `reports` folder inside it.
3. **Admin: an Entra app.** Entra admin center → App registrations → New registration, single
   tenant, no redirect URI. Note the **Application (client) ID** and **Directory (tenant) ID** (not
   secrets; they can paste them to you). API permissions: remove `User.Read`, add **Microsoft Graph →
   Application permissions → Sites.Selected**, then **Grant admin consent**.
4. **Admin: upload the certificate.** On the app: **Certificates & secrets → Certificates → Upload
   certificate**, file `~/entpep-trial/sharepoint-app.cer`. **Not** "Certificate authorities" (that's
   tenant-wide trust for user sign-in, and wrong here).
5. **Admin: grant the app the one site**, in Graph Explorer
   (https://developer.microsoft.com/graph/graph-explorer, signed in as the admin, with consent to
   `Sites.FullControl.All` for themselves if it asks):
   - `GET https://graph.microsoft.com/v1.0/sites/<tenant>.sharepoint.com:/sites/<site address>`;
     the top-level `id` is the **site ID** (three parts with commas).
   - `POST https://graph.microsoft.com/v1.0/sites/<site ID>/permissions` with body
     `{"roles":["write"],"grantedToIdentities":[{"application":{"id":"<client ID>","displayName":"EntPEP Trial"}}]}`.
     Expect **201 Created**.
6. **You:** set it on the host. The site ID has commas, so keep it in quotes:
   ```bash
   source ~/entpep-trial/trial.env && ssh -i "$KEYFILE" ubuntu@$H 'cd entpep-install-v1 && sudo ./install.sh --entra-tenant TENANT_ID --entra-client CLIENT_ID --sharepoint-site "SITE_ID" 2>&1 | tail -25'
   ```
   It should show `SharePoint library: EntPEP Trial / Documents`. An `AADSTS700027` error means the
   certificate isn't on the app yet (step 4); give it a minute after uploading and run it again. An
   `HTTP Error 403` means the site grant (step 5) is missing or names another app or site.

Re-running the installer restarts the Fleet Manager. If agents are running, ask the user first.

## Phase 11: Run the demo

The script: https://entpep.com/dl/EntPEP-Trial-Demo-Script.pdf (about 10 minutes; prompts to paste).
The prompts work on any model; the private model gives simpler answers. Setup in the Fleet Manager, under **New session**:

1. **Data Lake card:** hover **Connected**, click **manage**, **Grant 1h** on `trade_blotter` only.
2. **SharePoint card:** on, folder **Barclays**, **Read-write**, RAM workspace **512 MB**.
3. **Data Lake card:** dropdown **All desks**. **Advanced:** Sub-agents 1, Archive **New**.
4. Pick the model, then **Launch Agent**. The agent opens in a new tab on port 8001 and lists the
   six files.

Tips to pass on:

- Rates should come out at **88.66%** of its limit, a breach of rule 4.3.
- A sub-agent takes 1-3 minutes. **Wait for its "done" notice** before the step that uses its
  file. Its files land in the workspace under `from-agents/<name>/`, not `source/`.
- The memo and spreadsheet save to SharePoint under `Barclays/reports/`. The agent can reach only
  the folder it was launched on.
- The **Data Lake** tab in the Fleet Manager is the audit trail.
- If they uploaded `Legal Agreements`, the script's optional last step shows the completeness sweep:
  launch an agent on that folder and type `/sweep Which confirmations and agreements have no governing-law clause?`.

## Phase 12: Wrap up

Tell the user:

- The Fleet Manager URL, and that the password is in `/opt/entpep/fleetcerts/fleet.pass` on the
  host.
- **Stopping** both saves most of the cost (disks and Elastic IPs still cost a little). Offer to do
  it: `aws ec2 stop-instances --region $REGION --instance-ids $HID $DID`. Starting them again keeps
  the same addresses.
- **Colleagues:** their addresses go on the host group's 443 and 8000-8999 (offer to add them).
- **Removing everything** (ask, rule 2): terminate both instances, wait for `terminated`, release
  both Elastic IPs, delete both security groups and the key pair, delete the `entpep-bedrock` IAM
  user (its policy and credential first) if you made one, and remove the DNS records. In Microsoft
  365, the admin deletes the app registration and the site. `aws ec2 describe-instances --filters
  Name=tag:entpep-trial,Values=1` (and `describe-addresses`) find what phase 2A made.
- The trial ends on **1 January 2027**. For a licensed build or questions: the form at
  https://entpep.com/trial. `~/entpep-trial/` on their computer holds the SSH key and the state
  file; keep it until they remove the servers.

---

## Troubleshooting

| Symptom | Cause and fix |
|---|---|
| `run-instances`: `VcpuLimitExceeded` (g6) | G-instance quota under 4 vCPUs: phase 1's quota request |
| `InsufficientInstanceCapacity` or `Unsupported` | That zone has no capacity or doesn't offer the type: pick another zone and subnet (phase 1) |
| ssh: `Identity file … not accessible` or `Permission denied (publickey)` | Wrong key path, an unquoted option variable (rule 4), or the `.pem` isn't `chmod 400` |
| ssh: `UNPROTECTED PRIVATE KEY FILE` | `chmod 400` the `.pem` |
| data: nothing installed after the first run | That was pass 1 (driver, exit 3): reboot, run pass 2 |
| certbot fails | DNS doesn't resolve yet, is proxied (Cloudflare orange cloud), or port 80 is closed |
| host: `install: no /dev/kvm` | Nested virtualization is off: phase 2B's stop, modify, start |
| host: `port 443 is in use` | Another web server on the host: remove it |
| `install.sh` prints nothing and returns | An old package: download it again |
| Data Lake card: `grant failed: HTTP Error 403 … nginx` | data doesn't allow the host's address: on data `sudo entpep-datalake allow <HIP>`, and check `DSG` has 8443 from `HIP` |
| Private model test: `404: model 'qwen2.5:1.5b' not found` | Set the model to `qwen3:14b` (phase 9) |
| Private model test: timeout | `DSG` needs 443 from the host's address |
| SharePoint: `AADSTS700027 … certificate … not registered` | Upload `sharepoint-app.cer` to the app (phase 10.4) |
| SharePoint: `HTTP Error 403` | The site grant (phase 10.5) is missing or for another app or site |
| Agent stuck on "Starting your sealed agent…" | Host group needs 8000-8999 from the user's address (their IP may have changed: compare `curl -s https://checkip.amazonaws.com` with `MYIP`) |
| Fleet Manager won't load after a stop and start | No Elastic IP, so the address changed: update DNS, `DSG`, and `sudo entpep-datalake allow <new IP>` on data |
| The agent says a sub-agent's file is missing | The sub-agent hasn't finished, or it needs Claude (Anthropic or Bedrock) for web search and none is set |
