Agent skills for managing evroc cloud infrastructure: compute, networking, storage, IAM, load balancers, Think Models, and Rancher deployments.
Use when the user wants to create, list, inspect, update, or delete evroc VMs, disks, snapshots, or placement groups. Requires the evroc CLI. Do NOT trigger for networking, IAM, storage buckets, load balancers, or Think model operations.
# evroc-compute
Manage evroc compute resources through the `evroc compute` CLI. Covers VMs, disks,
snapshots, and placement groups.
Read the full CLI reference when needed: [`references/cli-reference.md`](references/cli-reference.md).
## When to Use
- User wants to create, list, inspect, update, or delete a VM on evroc
- User wants to create or manage disks on evroc
- User wants to take or manage disk snapshots
- User wants to create placement groups for VM scheduling
- User mentions "evroc" with "VM", "instance", "disk", "snapshot", or "compute"
**Do NOT use for:** networking (public IPs, security groups, subnets, VPCs), IAM,
storage (buckets), load balancers, or Think models.
## Prerequisites
Ensure the evroc CLI is installed:
```bash
bash scripts/check-cli.sh
```
Then authenticate:
```bash
bash scripts/check-auth.sh
```
If it fails, authenticate:
```bash
evroc login
```
## Virtual Machines
Requires: a pre-existing boot disk, a flavor, a subnet, and a zone.
If the subnet or security group does not exist yet, create them via
evroc-networking first.
Read networking details when needed:
[`references/networking.md`](references/networking.md) or via **evroc-networking** skill.
### List VMs
```bash
# Names only
evroc compute vm list
# YAML output with full details
evroc compute vm list -o yaml
# Filter by label
evroc compute vm list -l "team in (frontend,backend)"
```
### Get VM details
```bash
evroc compute vm get <vm-name> -o yaml
```
### Create a VM
Requires: a pre-existing boot disk, a flavor, a subnet, and a zone.
**Before creating the boot disk, ask the user which OS image they want.**
Available images are listed in [`references/cli-reference.md`](references/cli-reference.md#available-disk-images)
(e.g. `ubuntu-minimal.24-04.1`, `ubuntu.24-04.1`, `rocky.9-6.1`). Do not guess —
present the options and confirm the user's choice.
```bash
# 1. Create boot disk (ask user which image — see references/cli-reference.md)
evroc compute disk create my-boot-disk \
--image=<image-name> \
--size-amount=50 \
--size-unit=GB \
--zone=a
# 2. Create VM
evroc compute vm create my-vm \
--vm-virtual-resources-ref=a1a.m \
--disk=my-boot-disk \
--boot-from=true \
--subnet=default-se-sto-a \
--security-group=my-sg \
--ssh-authorized-key="ssh-ed25519 AAAA... user@example.com" \
--zone=a \
--running=true
```
**Completion criterion:** The VM has a `Ready` condition with `status: "True"` **and**
`virtualMachineStatus: "Running"`. The VM goes through several conditions before Ready
(`SecurityGroupsReady`, `NetworkingReady`, `VMIsRunning`, `Ready`), so checking just
`reason:` or grepping `status:` returns intermediate states like `SecurityGroupsReady`.
### Poll for VM readiness:
- Checking readiness — prefer Python, with jq only as last resort: The status: key appears at both the top level and nested under conditions[] in the YAML/JSON output. Grepping for status: matches the wrong field and returns the condition's status ("True") instead of the condition's type ("Ready"). Prefer a robust parser — try Python first, and fall back to jq only when you know the JSON does not contain unescaped control characters:
#### Preferred — Python using YAML output (avoids unescaped JSON control chars)
evroc compute vm get <vm-name> -o yaml | python3 -c "import sys, yaml; d=yaml.safe_load(sys.stdin); print([c['status'] for c in d['status']['conditions'] if c['type']=='Ready'][0])"
#### Alternative — jq when JSON is clean
evroc compute vm get <vm-name> -o json | jq -r '.status.conditions[] | select(.type=="Ready") | .status'
For VM running state, use .status.virtualMachineStatus.
Why Python first? The evroc output can contain unescaped control characters inside string fields such as cloudInitUserData; these make the JSON technically invalid and break jq with:
jq: parse error: Invalid string: control characters from U+0000 through U+001F must be escaped.
YAML mode plus python3 + pyyaml handles this gracefully.
Do NOT grep for `status:` or `reason:` in YAML — there are multiple condition entries
and grep matches the first one (e.g. `SecurityGroupsReady`), not the overall Ready state.
> **Note on cloud-init timing:** The `Ready` and `Running` conditions indicate the VM
> is up, but cloud-init may still be executing `runcmd` blocks (package installs,
> downloads, service setup). These can take several minutes after the VM becomes
> `Ready`. An application service may not be available even though the VM reports
> `Running`. If the user expects a service to be reachable, wait and probe the
> application port rather than relying solely on the VM's `Ready` condition.
To probe whether an application port is open and accepting connections:
```bash
# Poll the application port — redirect nc output to /dev/null so the
# comparison variable only contains OPEN/CLOSED, not nc's stdout.
for i in $(seq 1 30); do
if nc -z -w3 <ip> <port> >/dev/null 2>&1; then
echo "Port <port> is OPEN (attempt $i)"
break
fi
echo "Port <port> closed (attempt $i), waiting..."
sleep 10
done
```
> **Warning:** Do NOT capture `nc` output into the comparison variable without
> redirecting to `/dev/null`. `nc` prints "Connection to ... succeeded!" to stdout,
> which contaminates the variable and breaks string comparisons — the loop never
> breaks even when the port is open.
### Create a VM with cloud-init
```bash
evroc compute vm create my-vm \
--vm-virtual-resources-ref=a1a.m \
--disk=my-boot-disk \
--boot-from=true \
--subnet=default-se-sto-a \
--zone=a \
--cloud-init-user-data="#cloud-config
packages:
- nginx
runcmd:
- systemctl enable nginx
- systemctl start nginx"
```
**SSH users and cloud-init interaction:**
If you do not provide custom cloudInit userdata, the platform creates an
`evroc-user` for you and attaches the `--ssh-authorized-key` to it.
When you provide custom cloudInit with a `users:` block, `evroc-user`
is **NOT** created automatically, and the `--ssh-authorized-key` passed
via CLI or API is **NOT** automatically attached to your custom users.
To get SSH access with custom users, you must include the SSH key in
that user's `ssh_authorized_keys` field within the cloudInit config
itself.
**Alternative:** omit the `users:` block from your cloudInit, let the
platform create `evroc-user`, and SSH as that user.
**Cloud-init `write_files` ordering:** The `write_files` module runs **before**
`users` and `runcmd`. Do **not** use it to create files in directories that are
created by `runcmd` (e.g., `mkdir -p /opt/app`), and do **not** set `owner` to a
user defined in the `users:` block because that user does not exist yet when
`write_files` executes. To avoid silently failed files, use `runcmd` with `tee`
or `cat` instead, or use `bootcmd` to create directories early and
let `write_files` create files in those directories.
**Runtime version compatibility:** When the user's application has runtime
dependencies (Java, Python, Node.js, Go, etc.), verify that the runtime version. **do NOT** assume
installed in the cloud-init `packages:` or `runcmd:` block is compatible with
the application version before deploying. A version mismatch causes the service
to crash silently — the VM is `Ready`, the port probe shows `CLOSED`, but there's
no obvious error without SSH access.
HALT: Before running ANY evroc compute create/update command, you MUST complete the following 6 steps in order. Do NOT proceed to disk/VM creation until the user has explicitly confirmed the compatibility message in Step 4.
1. Identify the application's runtime requirement (e.g. Java xx for an application,
Python 3.11 for a specific framework, Node 20 for an app) - NOTE: These are only examples.
2. **Web search** the application's official documentation to find the exact **minimum runtime version** required for that application version. Treat
system knowledge as potentially stale — documentation is the source of truth
**You MUST cite the exact URL and the verbatim text/quote from that documentation
that states the runtime requirement. NEVER proceed from just memory.**
3. Once you have the runtime requirement, check the **OS image's package manager**
(e.g. `openjdk-21-jre-headless` for Ubuntu 24.04, `python3.11` for
Ubuntu 22.04, `nodejs` from NodeSource). Present the exact package name
(or alternative installation method if apt does not provide it) to the user.
4. If the OS image's default package manager cannot satisfy the required
version, add a step in `runcmd` to install the correct runtime (e.g.
Adoptium tarball, NodeSource repo, deadsnakes PPA, `pyenv`, `rustup`,
language-specific version managers).
5. Present the compatibility check to the user in this exact format:
"**[INSERT APPLICATION]** requires **[INSERT RUNTIME] [VERSION]**.
**[INSERT OS IMAGE]** provides **[INSERT PACKAGE]** which gives
**[INSERT INSTALLED VERSION]** — confirm this is what you want?"
6. **Only proceed with the cloud-init config once the user confirms.**
Common runtime version pitfalls:
>
> | Runtime | Issue | Fix |
> |---|---|---|
> | **Python** | App needs 3.11+ but image has 3.10 | Install via `deadsnakes` PPA, `pyenv`, or official Python source tarball |
> | **Node.js** | App needs Node 20 but image has Node 18 | Use NodeSource setup script, `nvm`, or official Node.js binaries |
> | **Go** | Binary compiled for wrong arch | Ensure `GOARCH` matches VM arch (amd64 vs arm64) |
> | **Ruby** | App needs Ruby 3.3 but image has 3.0 | Use `rvm` or `rbenv` in `runcmd` |
> | **Rust** | App needs recent cargo/rustc | Install via `rustup` in `runcmd` |
### Update a VM
```bash
# Attach an additional disk
evroc compute vm update my-vm --append --disk=extra-disk --boot-from=false
# Attach a security group
evroc compute vm update my-vm --append --security-group=additional-sg
# Stop / start
evroc compute vm update my-vm --running=false
evroc compute vm update my-vm --running=true
# Assign a public IP
evroc compute vm update my-vm --public-ip=my-public-ip-name
```
## Security Groups
VMs require security groups for network access. Without explicitly assigned
security groups, the VM's firewall will drop all traffic.
### Default security groups
Each project includes four preconfigured security groups:
| Security Group | Allows |
|---|---|
| `default-allow-egress` | All outbound traffic (IPv4 + IPv6). VMs are auto-added unless you specify custom groups. |
| `default-allow-intra-vpc` | All inbound + outbound traffic within the default VPC (inter-VM communication). |
| `default-allow-web-protocols` | HTTP (TCP 80) + HTTPS (TCP 443) from any IP. |
| `default-allow-ssh` | SSH (TCP 22) from any IP. Must be explicitly added. |
### List existing security groups
Before creating a new security group, check what already exists:
```bash
evroc networking securitygroup list
```
### Create a security group
```bash
evroc networking securitygroup create my-sg
```
### Add rules to a security group
```bash
# Allow SSH from anywhere (IPv4)
evroc networking securitygroup addrule my-sg \
--name=allow-ssh-ipv4 \
--direction=Ingress \
--remote-ip-or-cidr=0.0.0.0/0 \
--port=22 \
--protocol=TCP
# Allow HTTPS from anywhere (IPv6)
evroc networking securitygroup addrule my-sg \
--name=allow-https-ipv6 \
--direction=Ingress \
--remote-ip-or-cidr=::/0 \
--port=443 \
--protocol=TCP
```
> **Note:** IPv4 and IPv6 rules are separate — create one rule per protocol if you need both.
> **Application ports:** In addition to SSH (22), the user's application likely needs
> its own ingress rule for the port it listens on. The default security groups only
> cover SSH (22), HTTP (80), and HTTPS (443) — any other port requires a custom rule.
>
> **Port lookup workflow:** When the user mentions an application by name (e.g.
> PostgreSQL, Redis, Minecraft, Grafana), look up its default port(s) and confirm
> with the user before creating security group rules:
>
> | Common application | Default port(s) | Protocol |
> |---|---|---|
> | PostgreSQL | 5432 | TCP |
> | MySQL | 3306 | TCP |
> | Redis | 6379 | TCP |
> | MongoDB | 27017 | TCP |
> | Elasticsearch | 9200 | TCP |
> | Grafana | 3000 | TCP |
> | Prometheus | 9090 | TCP |
> | Node.js / Express | 3000 | TCP |
> | Django / Flask | 8000 | TCP |
> | FastAPI | 8000 | TCP |
> | Nginx (non-default) | 8080 | TCP |
> | RabbitMQ | 5672 (AMQP), 15672 (mgmt) | TCP |
> | Kafka | 9092 | TCP |
> | MinIO | 9000, 9001 (console) | TCP |
> | etcd | 2379 | TCP |
> | Consul | 8500 (HTTP), 8300 (RPC) | TCP |
>
> This table is not exhaustive. If the application isn't listed or the user has
> customized the port, **ask the user to confirm the exact port(s)** rather than
> assuming the default. Present what you found and get explicit confirmation:
> "PostgreSQL typically listens on port 5432 — is that correct for your setup?"
>
> If the application isn't in the table above and the user doesn't know the port,
> use a web search to look up the application's default port(s), then confirm
> the findings with the user before creating the rule.
### Attach a security group to a VM
```bash
# Append (keeps existing groups)
evroc compute vm update my-vm --append --security-group=my-sg
# Replace all security groups (must specify ALL groups the VM should have)
evroc compute vm update my-vm \
--security-group=default-allow-egress \
--security-group=my-sg
```
> **Warning:** Without `--append`, the update replaces all existing security groups. You must specify the complete list.
### Pre-flight checklist before VM creation
Before creating a VM, verify or create these prerequisites:
1. **Boot disk** — create and wait for `Ready` condition (see [Disks](#disks))
2. **Security group** — either use a default (e.g. `default-allow-ssh`) or create one with
`evroc networking securitygroup create` + `addrule`. If the user needs SSH access,
ensure a rule allows TCP 22. If they need web traffic, allow 80/443. If their
application uses a custom port, create an ingress rule for it.
3. **Subnet** — use the default `default-<region>-<zone>` or create one
4. **SSH key** — confirm or ask the user for their SSH public key. Without custom
cloudInit, the platform creates `evroc-user` and attaches the key. With custom
cloudInit containing a `users:` block, `evroc-user` is not created and the key
is not automatically attached — the user must embed the key in the cloudInit
`ssh_authorized_keys` field for the intended user.
### Delete a VM
HALT: Before running ANY `evroc compute vm delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete the Virtual Machine '<vm-name>' and cannot be
undone. Deleting a Virtual Machine also deletes all associated Public IPs
and Disks."
Then:
1. Run a pre-deletion inspection to identify what's running on the VM:
```bash
bash scripts/inspect-vm-services.sh <vm-name> [ssh-user] [ssh-key-path]
```
- If the agent is running ON the VM, the script detects this automatically
and warns: "SELF-DESTRUCTION DETECTED — this session will be terminated."
- If the agent is remote, the script SSHes into the VM and lists running
systemd services and Docker containers.
- If the VM is stopped or SSH fails, the script exits 0 with a note —
non-blocking, proceed with the standard warning.
2. Present the inspection results to the user alongside the resource list
(name, type, project). Include any services/containers that will be lost.
3. If the inspection detected self-destruction, output this additional
warning VERBATIM:
"WARNING: This agent is running on the VM you are about to delete. Deleting
it will terminate this session immediately and any unsaved work will be lost.
Consider migrating the agent to another VM before proceeding."
4. Ask the user to confirm: "Type 'yes' to confirm deletion of '<vm-name>'."
5. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc compute vm delete my-vm --force
```
**Completion criterion:** `evroc compute vm get <vm-name>` returns `not found`.
## Disks
### List disks
```bash
evroc compute disk list
evroc compute disk list -o yaml
evroc compute disk list -l "environment=production"
```
### Get disk details
```bash
evroc compute disk get <disk-name> -o yaml
```
### Create a disk
From an image (ask the user which one — see [`references/cli-reference.md`](references/cli-reference.md#available-disk-images)):
```bash
evroc compute disk create my-disk \
--image=<image-name> \
--size-amount=100 \
--size-unit=GB \
--zone=a
```
**Completion criterion:** Wait for the disk's `Ready` condition to be `True` before
creating a VM from it. Poll with:
```bash
evroc compute disk get <disk-name> -o json | jq -r '.status.conditions[] | select(.type=="Ready") | .status'
# Returns "True" when ready. Do NOT grep for "status:" in YAML output —
# the top-level status: field and the nested condition status: field both
# match, causing false readings.
```
From a snapshot:
```bash
evroc compute disk create my-disk \
--snapshot=my-snapshot \
--zone=a
```
### Delete a disk
HALT: Before running ANY `evroc compute disk delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your Disk '<disk-name>' and cannot be undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<disk-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc compute disk delete <disk-name> --force
```
**Completion criterion:** `evroc compute disk get <disk-name>` returns `not found`.
## Hotswap Disk Attachments
Hotswap disk attachments allow attaching a disk to a running VM without rebooting.
See [`references/cli-reference.md`](references/cli-reference.md#hotswap-disk-attachments) for full flag details.
### Create a hotswap disk attachment
```bash
evroc compute hotswapdiskattachment create my-hda \
--disk-ref=my-disk \
--vm-ref=my-vm
```
### List hotswap disk attachments
```bash
evroc compute hotswapdiskattachment list
```
### Delete a hotswap disk attachment
HALT: Before running ANY `evroc compute hotswapdiskattachment delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete the Disk Attachment '<attachment-name>' and
cannot be undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<attachment-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc compute hotswapdiskattachment delete my-hda --force
```
## Snapshots
### Create a snapshot
```bash
evroc compute snapshot create my-snapshot --disk-ref=my-disk
```
### List snapshots
```bash
evroc compute snapshot list
```
### Get snapshot details
```bash
evroc compute snapshot get <snapshot-name> -o yaml
```
### Delete a snapshot
HALT: Before running ANY `evroc compute snapshot delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your Snapshot '<snapshot-name>' and cannot be
undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<snapshot-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc compute snapshot delete <snapshot-name> --force
```
## Placement Groups
### Create a placement group
```bash
evroc compute placementgroup create my-pg \
--strategy-type=spread \
--zone=a
```
### List placement groups
```bash
evroc compute placementgroup list
```
### Assign a VM to a placement group
```bash
evroc compute vm create my-vm \
--vm-virtual-resources-ref=a1a.m \
--disk=my-boot-disk \
--boot-from=true \
--subnet=default-se-sto-a \
--placement-group=my-pg \
--zone=a
```
### Delete a placement group
HALT: Before running ANY `evroc compute placementgroup delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete the Placement Group '<pg-name>' and cannot be
undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<pg-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc compute placementgroup delete my-pg --force
```
## Pitfalls
- **Disk images — ask, don't guess**: There is no `disk image list` CLI command.
For the live, current list, run:
```bash
bash scripts/get-disk-images.sh
```
This fetches from the API using your current `evroc login` token. The
[`references/cli-reference.md`](references/cli-reference.md#available-disk-images)
table is a cache — use it for offline reference or quick browsing only.
Always have the user confirm their choice from the live list or cache.
Image names use format `<distro>.<version>.1` (dot before version,
hyphen within). e.g. `ubuntu-minimal.24-04.1`, not `ubuntu-minimal-24.04.1` — wrong
separators silently create a disk that never becomes Ready (`DiskImageInvalid`).
- **Checking readiness — use JSON + jq, not YAML grep**: The `status:` key appears at both
the top level and nested under `conditions[]` in the YAML/JSON output. Grepping for
`status:` matches the wrong field and returns the condition's status (`"True"`) instead
of the condition's type (`"Ready"`). Always use:
`evroc compute <resource> get <name> -o json | jq -r '.status.conditions[] | select(.type=="Ready") | .status'`
to check readiness. For VM running state, use `.status.virtualMachineStatus`.
- **Wait for disk Ready before VM create**: A disk that is not yet Ready will cause VM
creation to fail with "disk is not ready". Always poll the disk's Ready condition first.
- **Security groups — check or create before VM creation**: Without explicitly assigned
security groups, the VM's firewall drops all traffic. Before creating a VM, check
existing groups with `evroc networking securitygroup list`. If the user needs SSH access,
ensure `default-allow-ssh` is attached or create a custom group with a TCP 22 ingress
rule. If the user needs web traffic, ensure 80/443 are allowed. The VM's
`SecurityGroupsReady` condition will show `True` once groups are resolved.
- **Application ports need ingress rules**: Default security groups only cover SSH (22),
HTTP (80), and HTTPS (443). If the user's application listens on a different port, you
must create a custom ingress rule for it before the VM can receive traffic on that port.
When the user mentions an application by name, look up its default port(s) (see the
table in [Security Groups](#security-groups)) and confirm with the user before creating
the rule — do not assume the default port is correct.
- **`evroc-user` and custom cloud-init**: When you do not provide custom
cloudInit userdata, the platform creates `evroc-user` and `--ssh-authorized-key`
attaches to it. When you provide custom cloudInit with a `users:` block,
`evroc-user` is NOT created, and the key is NOT automatically attached to your
custom users. Embed the key in each custom user's `ssh_authorized_keys` in the
cloud-init config, or omit the `users:` block to keep the default `evroc-user`.
- **SSH troubleshooting — may be locked out**: If SSH fails, check VM status with
`evroc compute vm get <name> -o json`. If you are locked out, use cloud-init
`write_files` to set up monitoring scripts that run without SSH, or redeploy with a
corrected cloud-init config that attaches the SSH key to the intended user.
- **cloud-init runs after `Ready`**: The VM reports `Ready` and `Running` before
cloud-init finishes. Package installs, downloads, and service setup in `runcmd` can
take several minutes after the VM is `Ready`. If the user expects a service to be
reachable, wait and probe the application port rather than relying on the VM's
`Ready` condition alone.
- **Runtime version compatibility**: If the application has runtime dependencies (Java,
Python, Node.js, etc.), verify the runtime version matches the application's
requirements before deploying. A mismatch causes the service to crash silently — the
VM is `Ready` but the port stays `CLOSED` and there's no obvious error without SSH.
Look up the app's minimum runtime version (web search if needed), confirm the correct
package name in the OS image, and get user confirmation before writing the cloud-init
config (see [Runtime version compatibility](#create-a-vm-with-cloud-init)).
- **Post-deployment cloud-init validation:** If the application port is closed
even though the VM is `Ready`, first check `cloud-init status` via SSH. If it
returns `error`, inspect `/var/log/cloud-init-output.log` for the actual failure
(common causes: missing parent directories for `write_files`, failed package
installs, or missing systemd units started in `runcmd` before they were
created).
- **SSH host key change warning**: Every new evroc VM generates a fresh SSH host
key pair. If the assigned public IP was previously used for a different VM, your
local `~/.ssh/known_hosts` will contain the old key and SSH will refuse to
connect with a `REMOTE HOST IDENTIFICATION HAS CHANGED` error. This is normal,
not a man-in-the-middle attack. Fix it by removing the stale entry before the
next connection:
```bash
ssh-keygen -R <vm-public-ip>
- **Disk before VM**: Always create the boot disk first. The VM references it by name.
- **Boot-from flag**: Every disk attachment must specify `--boot-from` (`true` for boot,
`false` for data).
- **Subnet naming**: Pattern is `default-<region>-<zone>`. The zone in the subnet name must
match `--zone`.
- **Flavor format**: `<series>.<size>` — e.g. `a1a.m`, `c1a.l`. Series: `a1a` (general),
`c1a` (compute-optimized), `m1a` (memory-optimized).
- **Delete order**: Delete VMs before their attached disks. Disks must be detached before
deletion.
- **Output formats**: `-o yaml` (default for `get`), `-o json`, `-o list` (default for `list`,
shows names only). Use `yaml` or `json` when you need full resource details.
- **Label selectors**: Kubernetes-style syntax: `key=value`, `key in (val1,val2)`, `!key`.
Works with user labels and system labels. Add or remove labels with the `label`
subcommand (see [`references/cli-reference.md`](references/cli-reference.md)):
`evroc compute vm label <name> --add key=value`, `evroc compute disk label <name> --remove key`.
Works on VMs, disks, snapshots, and placement groups.
- **Name rules**: Max 63 chars, lowercase alphanumeric with hyphens, starting/ending
alphanumeric.Use when the user wants to create, list, inspect, or delete evroc projects; grant or revoke role bindings; manage service accounts and their credentials (including rs256-jwt and hmac-sigv4 credential types); enable or disable service accounts; or retrieve an access token. Requires the evroc CLI. Do NOT trigger for compute, networking, storage, or Think model tasks.
# evroc-iam
Manage evroc Identity and Access Management through the `evroc iam` CLI.
Covers projects, role bindings, service accounts, and credentials.
evroc IAM uses a principal-role-permission model: predefined roles (collections of
permissions) are granted to principals (users or service accounts) on a scope
(project or organization).
Read the full CLI reference when needed: [`references/cli-reference.md`](references/cli-reference.md).
## When to Use
- User wants to create, list, inspect, update, or delete a project on evroc
- User wants to grant or revoke roles for a user or service account
- User wants to create or manage service accounts and their credentials
- User wants to enable or disable a service account
- User wants to list available roles or check permissions
- User wants to retrieve an access token for API calls
- User mentions "evroc" with "IAM", "project", "role", "permission", "service account", or "access"
**Do NOT use for:** compute VMs, networking (public IPs, security groups, subnets, VPCs), storage buckets, load balancers, or Think models.
## Prerequisites
Ensure the evroc CLI is installed:
```bash
bash scripts/check-cli.sh
```
Then authenticate:
```bash
bash scripts/check-auth.sh
```
If it fails:
```bash
evroc login
```
## Projects
### List projects
```bash
evroc iam project list
```
### Create a project
```bash
evroc iam project create my-project --name "My Project"
```
**Completion criterion:** `evroc iam project get my-project` returns the project.
### Get project details
```bash
evroc iam project get my-project
```
### Update a project
```bash
evroc iam project update my-project --name "New Name"
```
### Delete a project
HALT: Before running ANY `evroc iam project delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete the Project '<project-name>' and cannot be
undone. Deleting a Project also deletes all associated resources within it
(VMs, disks, networks, load balancers, storage, etc.)."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<project-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc iam project delete my-project --force
```
**Completion criterion:** `evroc iam project get my-project` returns `not found`.
### Set current project
```bash
evroc config set-project my-project
```
## Role Bindings
### List available roles
```bash
evroc iam rolebinding roles
```
Use the role `id` (e.g. `/iam/roles/computeOperator`) when granting.
### Grant a project role
```bash
# To a user
evroc iam rb assign \
--principal /iam/users/<user-id> \
--role /iam/roles/projectOwner
# To a service account
evroc iam rb assign \
--principal /iam/projects/<project-id>/serviceAccounts/<sa-id> \
--role /iam/roles/storageOperator
```
### Scope a role to specific resources
Limit to specific resource FQIDs with `--resource` (repeatable):
```bash
evroc iam rb assign \
--principal /iam/users/<user-id> \
--role /iam/roles/computeOperator \
--resource /compute/projects/my-project/regions/se-sto/virtualMachines/my-vm
```
Wildcards for one path segment:
```bash
--resource /compute/projects/my-project/regions/se-sto/virtualMachines/*
```
### Grant an organization role
```bash
evroc iam rb assign \
--principal /iam/users/<user-id> \
--role /iam/roles/organizationOwner \
--organization
```
### List role bindings
```bash
# In the current project
evroc iam rb list
# In the current organization
evroc iam rb list --organization
# Your own bindings across all scopes
evroc iam rb list-me
```
### Get a specific role binding
```bash
evroc iam rb get <binding-name>
```
### Revoke a single role
```bash
evroc iam rb revoke \
--principal /iam/users/<user-id> \
--role /iam/roles/computeOperator
```
### Delete all roles for a principal
HALT: Before running ANY `evroc iam rb delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete the Role Binding '<binding-name>' and cannot
be undone. This revokes every role for that principal on the selected scope."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<binding-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc iam rb delete <binding-name>
```
This revokes every role for that principal on the selected scope.
### Test permissions via API
```bash
TOKEN=$(evroc iam get-access-token)
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"checks": [
{
"permission": "compute.virtualMachines.start",
"resource": "/compute/projects/my-project/regions/se-sto/virtualMachines/my-vm"
}
]
}' \
https://api.evroc.com/iam/v1beta1/testPermissions
```
## Service Accounts
A service account is a non-human identity scoped to a single project. It has no
access of its own until you grant it a role via a role binding (see above).
Service accounts are addressed by fully qualified identifiers (FQIDs):
```
/iam/projects/<project-id>/serviceAccounts/<service-account-id>
```
### Create a service account
```bash
# Enabled by default
evroc iam serviceaccount create my-sa --description "CI/CD automation"
# Create disabled (can't authenticate until enabled)
evroc iam serviceaccount create my-sa --description "CI/CD automation" --enabled=false
```
### List service accounts
```bash
evroc iam serviceaccount list
```
### Get service account details
```bash
evroc iam serviceaccount get my-sa
```
### Enable or disable a service account
A disabled service account cannot authenticate, even if it has credentials and
role bindings.
```bash
# Disable
evroc iam serviceaccount update my-sa --enabled=false
# Re-enable
evroc iam serviceaccount update my-sa --enabled
```
### Credentials
A service account can't authenticate until it has a credential. Credentials come
in two types, each compatible with a different API:
| Type | Key mechanism | Compatible with |
|---|---|---|
| `rs256-jwt` (default) | Asymmetric (RSA + SHA256). Signs a JWT and exchanges it for a short-lived access token via the OAuth 2.0 private key JWT flow. | evroc public API |
| `hmac-sigv4` | Symmetric (HMAC + SHA256). Signs requests with an S3 compatible signature. | S3 compatible API |
Use `rs256-jwt` for managing compute, storage, IAM, or other evroc public API
resources. Use `hmac-sigv4` for reading or writing objects through the S3
compatible API (e.g. with `s3cmd` or `rclone`). If a workload needs both, create
one credential of each type.
#### Create a credential
```bash
# Default type (rs256-jwt), default 1-year expiry
evroc iam serviceaccount credential create cred-1 --service-account my-sa
# 90-day expiry
evroc iam serviceaccount credential create cred-1 --service-account my-sa --expires-in 2160h
# HMAC credential for S3 compatible API
evroc iam serviceaccount credential create cred-1 --service-account my-sa --type hmac-sigv4
```
Credential expiration is mandatory: minimum 24 hours, maximum 2 years. You
can't change the expiration after the credential is created.
For `rs256-jwt` credentials, the access token lifetime is separate from the
credential expiration. The default is 300 seconds (5 minutes), with a minimum of
60 seconds and a maximum of 28800 seconds (8 hours). This is set at credential
creation.
#### List credentials
```bash
evroc iam serviceaccount credential list --service-account my-sa
```
#### Revoke a credential
Revocation invalidates the credential immediately and preserves the audit
record with the reason.
HALT: Before running ANY `evroc iam serviceaccount credential revoke` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently revoke the Credential '<cred-name>' and cannot be
undone. Any services using this credential will immediately lose access."
Then:
1. List the specific resource(s) that will be revoked (credential name, service account, project)
2. Ask the user to confirm: "Type 'yes' to confirm revocation of '<cred-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the revoke command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc iam serviceaccount credential revoke cred-1 --service-account my-sa --reason "Compromised"
```
#### Delete a credential
Delete removes the credential resource entirely. Use revoke if you need an
audit trail.
```bash
evroc iam serviceaccount credential delete cred-1 --service-account my-sa --force
```
### Using a credential
For `rs256-jwt` credentials, the private key (a JSON Web Key) is printed at
creation time. Store it where the workload can read it, e.g. as environment
variables (the evroc Go SDK reads these):
```bash
export EVROC_SERVICE_ACCOUNT_ID="my-sa"
export EVROC_SERVICE_ACCOUNT_SECRET="<private-key>"
```
For `hmac-sigv4` credentials, the access key ID and secret access key are
returned at creation time. Use them to configure your S3 client.
### Delete a service account
HALT: Before running ANY `evroc iam serviceaccount delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete the service account '<sa-name>' and cannot be undone.
Deleting a service account also revokes all associated credentials."
Then:
1. List the specific service account(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<sa-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc iam serviceaccount delete my-sa --force
```
## Access Tokens
```bash
# Get a short-lived bearer token
evroc iam get-access-token
# Get current user info
evroc iam get-user-info
```
## Key Predefined Roles
| Role | Scope | Description |
|---|---|---|
| `/iam/roles/projectOwner` | project | Full control |
| `/iam/roles/projectContributor` | project | Create/manage resources, no access management |
| `/iam/roles/computeOperator` | project | Manage compute resources |
| `/iam/roles/computeViewer` | project | Read-only compute access |
| `/iam/roles/storageOperator` | project | Manage storage buckets |
| `/iam/roles/networkingOperator` | project | Manage networking resources |
| `/iam/roles/thinkOperator` | project | Manage Think instances and API keys |
| `/iam/roles/organizationOwner` | organization | Full org control |
| `/iam/roles/organizationViewer` | organization | Read-only org access |
| `/iam/roles/organizationAccessAdministrator` | organization | Manage role bindings and users |
Run `evroc iam rb roles` for the complete list.
## Pitfalls
- **Private key shown only once**: The private key (JWK for `rs256-jwt`,
access key ID + secret for `hmac-sigv4`) is returned only at credential
creation and can't be viewed again. Capture it immediately and store it
securely.
- **Credential expiry is immutable**: Min 24 hours, max 2 years. You can't
change the expiration after creation — create a new credential instead.
- **Access token lifetime is separate from credential expiry**: For `rs256-jwt`
credentials, the short-lived access token expires (default 300s, range
60s–28800s) and the workload must request a new one. This is independent of
the credential's own expiration.
- **Service accounts are project-scoped**: A service account belongs to exactly
one project and its FQID includes that project
(`/iam/projects/<project-id>/serviceAccounts/<sa-id>`). It can't be shared
across projects.
- **Revoke vs delete credentials**: Revoke invalidates immediately and keeps
the audit record (with `--reason`). Delete removes the resource entirely.
Use revoke for suspected compromise, delete for cleanup.
- **Principals use FQIDs, not emails**: Role bindings reference users by user ID
(e.g. `/iam/users/5a9bb4ea-...`), not email. Service accounts use
`/iam/projects/<project-id>/serviceAccounts/<sa-id>`. Find user IDs in the
evroc Console.
- **Organization roles ≠ project access**: Organization roles govern only org-level
operations. Grant project roles for access to project resources.
- **Role assignment is idempotent**: Re-running with the same principal+role replaces
the resource list. Use `--resource` to scope, or omit it for all resources.
- **PermissionSets are deprecated**: Use role bindings. PermissionSets were a binary
admin model; role bindings provide fine-grained predefined roles.
- **Custom roles not available**: Only predefined roles are supported. Custom roles,
deny rules, and condition-based bindings are not yet available.
- **Resource name limits**: Max 63 chars, lowercase alphanumeric with hyphens,
starting/ending alphanumeric.
- **Labeling service accounts**: Service accounts support the `label` subcommand for
adding/removing labels:
`evroc iam serviceaccount label <name> --add key=value --remove key`.
See [`references/cli-reference.md`](references/cli-reference.md) for details.
- **MFA is OTP-only**: TOTP is the only supported MFA mechanism.
- **Brute force protection**: After 10 failed login attempts, the account is locked for
~10 minutes with no visual indication.
- **CLI aliases**: `rb` for `rolebinding`, `sa` for `serviceaccount`, `ls` for `list`,
`rm`/`del` for `delete`, `new` for `create`.Use when the user wants to create, configure, or delete evroc load balancers, backend pools, backend services, health checks, L4 routes, or listeners. Requires the evroc CLI and a pre-existing public IP (managed via evroc-networking). Do NOT trigger for compute VMs, IAM, storage buckets, networking resources, or Think models.
# evroc-loadbalancer
Manage evroc load balancers through the `evroc loadbalancer` CLI.
Covers backend pools, backend services, health checks, L4 routes, and listeners.
Read the full CLI reference when needed: [`references/cli-reference.md`](references/cli-reference.md).
## When to Use
- User wants to create or manage a load balancer on evroc
- User wants to configure backend pools with VM backends
- User wants to set up health checks (HTTP or TCP) for backend services
- User wants to configure L4 routes and listeners for traffic distribution
- User mentions "evroc" with "load balancer", "LB", "backend pool", or "health check"
**Do NOT use for:** compute VMs, networking (public IPs, security groups, subnets, VPCs), IAM, storage, or Think models.
## Prerequisites
Ensure the evroc CLI is installed:
```bash
bash scripts/check-cli.sh
```
Then authenticate:
```bash
bash scripts/check-auth.sh
```
A public IP must exist before creating a load balancer. Create it via **evroc-networking**:
```bash
evroc networking publicip create my-lb-ip
```
Note the subnet name when attaching the LB (managed via evroc-networking; defaults to
`default-<region>-<zone>`, e.g. `default-se-sto-a`).
## Load Balancer Lifecycle
Hierarchy: load balancer → listeners → L4 routes → backend services → backend pools → backends (VMs).
### 1. Create backend pool
```bash
evroc loadbalancer backendpool create my-pool \
--backend-ref my-vm-1 \
--backend-ref my-vm-2
```
### 2. Create backend service
```bash
evroc loadbalancer backendservice create my-service \
--backend-pool-ref my-pool \
--port 8080 \
--ip-protocol-selection IPv4
```
### 3. Set health check
HTTP:
```bash
evroc loadbalancer backendservice sethealthcheckhttp my-service \
--path /health \
--target-port 8080 \
--interval 10s \
--timeout 5s \
--healthy-threshold 2 \
--unhealthy-threshold 3 \
--expected-statuses 200 \
--method GET
```
TCP:
```bash
evroc loadbalancer backendservice sethealthchecktcp my-service \
--target-port 8080 \
--interval 10s \
--timeout 5s \
--healthy-threshold 2 \
--unhealthy-threshold 3
```
### 4. Create L4 route
```bash
evroc loadbalancer l4route create my-route \
--backend-service-ref my-service
```
### 5. Create load balancer
```bash
evroc loadbalancer loadbalancer create my-lb \
--public-ip-ref my-lb-ip
```
### 6. Add listener
```bash
evroc loadbalancer loadbalancer addlistener my-lb \
--listener-name my-listener \
--listener-port 443 \
--listener-protocol TCP \
--listener-route-ref my-route
```
**Completion criterion:** `evroc loadbalancer loadbalancer get my-lb -o yaml | grep -E '^\s+listeners:'` shows the listener attached.
## List and Inspect
```bash
# List all
evroc loadbalancer loadbalancer list
# With details
evroc loadbalancer loadbalancer list -o yaml
# Get specific
evroc loadbalancer loadbalancer get my-lb -o yaml
# List related resources
evroc loadbalancer backendpool list
evroc loadbalancer backendservice list
evroc loadbalancer l4route list
```
## Remove a Listener
```bash
evroc loadbalancer loadbalancer removelistener my-lb \
--listener-name my-listener
```
## Delete Resources
Delete in reverse order: listener → load balancer → L4 route → backend service → backend pool.
HALT: Before running ANY `evroc loadbalancer ... delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
For load balancers: "This will permanently delete your Load Balancer
'<lb-name>' and cannot be undone."
For L4 routes: "This will permanently delete your L4 Route '<route-name>'
and cannot be undone."
For backend services: "This will permanently delete your Backend Service
'<service-name>' and cannot be undone."
For backend pools: "This will permanently delete your Backend Pool
'<pool-name>' and cannot be undone."
Then:
1. List ALL specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of the listed resources."
3. Wait for the user to explicitly type 'yes'
Do NOT run any delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc loadbalancer loadbalancer delete my-lb --force
evroc loadbalancer l4route delete my-route --force
evroc loadbalancer backendservice delete my-service --force
evroc loadbalancer backendpool delete my-pool --force
```
## Pitfalls
- **Public IP required**: A load balancer requires a pre-existing public IP referenced by `--public-ip-ref`. Create it via **evroc-networking**.
- **Hierarchy order**: Create bottom-up (pool → service → route → load balancer → listener). Delete top-down.
- **Listener protocol**: Currently only `TCP` is supported for listener protocol.
- **Health check target-port**: The `--target-port` on a health check is the port on the backend VM, not the listener port. These can differ.
- **Backend references**: `--backend-ref` on a backend pool refers to VM names. Repeatable for multiple VMs.
- **Beta service**: The load balancer service is in beta. Command syntax and resource semantics may change between releases.
- **Labeling**: All load balancer resources support the `label` subcommand for adding/removing labels:
`evroc loadbalancer <resource> label <name> --add key=value --remove key`.
See [`references/cli-reference.md`](references/cli-reference.md) for details.
- **CLI aliases**: `lb` for `loadbalancer`, `bp` for `backendpool`, `bs` for `backendservice`, `l4r` for `l4route`, `ls` for `list`, `rm`/`del` for `delete`, `new` for `create`.Use when the user wants to create, list, inspect, update, or delete evroc networking resources — public IPv4 and IPv6 addresses, security groups (dual-stack), subnets, or VPCs. Requires the evroc CLI. Do NOT trigger for compute VMs, disks, IAM, storage buckets, load balancers, or Think models.
# evroc-networking
Manage evroc networking resources through the `evroc networking` CLI.
Covers public IPs, security groups, subnets, and VPCs.
These resources are **prerequisites** for compute (VMs need subnets and security
groups), load balancers (need public IPs), and Rancher deployments (need public IPs
and security groups). Create networking resources first, then reference them in
other skills.
Read the full CLI reference when needed: [`references/cli-reference.md`](references/cli-reference.md).
## When to Use
- User wants to create, list, inspect, or delete an evroc public IP
- User wants to create or manage security groups and their rules
- User wants to create or manage subnets
- User wants to create or manage VPCs
- User mentions "evroc" with "public IP", "security group", "subnet", "VPC", or "network"
**Do NOT use for:** compute VMs, IAM, storage (buckets), load balancers,
or Think models.
## Prerequisites
Ensure the evroc CLI is installed:
```bash
bash scripts/check-cli.sh
```
Then authenticate:
```bash
bash scripts/check-auth.sh
```
If it fails:
```bash
evroc login
```
## Public IPs
Public IPv4 addresses are a scarce resource. evroc accounts have a 3-address
quota. Elastic IPv4s are required for load balancers, Rancher server nodes,
and any VM that needs fixed inbound internet access over IPv4.
IPv6 is end-to-end by design: each VM in an IPv6-enabled subnet receives a
globally routable address without NAT, because every IPv6 subnet allocation is
a /64 and individual addresses are globally unique by default (see RFC 4291,
Section 2.5.1 on Global Unicast addresses). This eliminates the need for
elastic IP allocation, NAT gateways, or port forwarding that IPv4 requires.
### List public IPs
```bash
evroc networking publicip list
evroc networking publicip list -o yaml
```
### Get public IP details
```bash
evroc networking publicip get <ip-name> -o yaml
```
### Create a public IP
```bash
evroc networking publicip create my-ip
```
**Completion criterion:** `evroc networking publicip get my-ip` returns the IP
with an assigned address.
### Delete a public IP
HALT: Before running ANY `evroc networking publicip delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your Public IP '<ip-name>' and cannot be undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<ip-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc networking publicip delete <ip-name> --force
```
**Completion criterion:** `evroc networking publicip get <ip-name>` returns
`not found`.
## Security Groups
Security groups are stateful firewalls attached to VM network interfaces. The default
deny model means any traffic not explicitly allowed is blocked.
### List security groups
```bash
evroc networking securitygroup list
evroc networking securitygroup list -o yaml
```
### Get security group details
```bash
evroc networking securitygroup get <sg-name> -o yaml
```
### Create a security group
```bash
evroc networking securitygroup create my-sg \
--vpc=default-se-sto
```
**Flags:**
- `--vpc <vpc-name>`: VPC the security group belongs to (e.g. `default-se-sto`).
**Completion criterion:** `evroc networking securitygroup get my-sg` returns the group.
### Add a rule
**IPv4 / IPv6 side-by-side:**
```bash
# IPv4 rule
evroc networking securitygroup addrule my-sg \
--name=allow-ssh \
--direction=Ingress \
--protocol=TCP \
--port=22 \
--remote-ip-or-cidr=0.0.0.0/0
# IPv6 rule
evroc networking securitygroup addrule my-sg \
--name=allow-ssh-v6 \
--direction=Ingress \
--protocol=TCP \
--port=22 \
--remote-ip-or-cidr=::/0
```
**Flags:**
- `--port <int>`: Single port for the rule (used as both start and end port).
- `--end-port <int>`: End of a port range; use with `--port` (as start) to open a range (e.g. `--port=8000 --end-port=8100`).
- `--security-group <sg-name>`: Target security group by name (alternative to passing it as a positional argument).
- `--subnet <subnet-name>`: Restrict the rule to traffic from a specific subnet instead of a CIDR.
### Remove a rule
```bash
evroc networking securitygroup removerule my-sg \
--name=allow-ssh
```
### Delete a security group
HALT: Before running ANY `evroc networking securitygroup delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your Security Group '<sg-name>' and cannot be
undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<sg-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc networking securitygroup delete <sg-name> --force
```
**Completion criterion:** `evroc networking securitygroup get <sg-name>` returns
`not found`.
## Subnets
Subnets are scoped to a region-zone pair. The default subnet follows the pattern
`default-<region>-<zone>` (e.g. `default-se-sto-a`).
### List subnets
```bash
evroc networking subnet list
evroc networking subnet list -o yaml
```
### Get subnet details
```bash
evroc networking subnet get <subnet-name> -o yaml
```
### Create a subnet
```bash
# Dual-stack subnet (IPv4 + IPv6)
evroc networking subnet create my-subnet-dual \
--zone=a \
--ipv4-cidr-block=10.0.1.0/24 \
--stack-type=dual-stack \
--vpc=default-se-sto
```
**CIDR notes:**
- **IPv4:** `--ipv4-cidr-block=10.0.1.0/24` (RFC1918 private range, 256 addresses)
- **IPv6:** IPv6 addressing is automatic in dual-stack mode; no separate IPv6 CIDR flag is needed.
- **VPC:** `--vpc=default-se-sto` attaches the subnet to the default VPC for the `se-sto` region.
### Delete a subnet
HALT: Before running ANY `evroc networking subnet delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your Subnet '<subnet-name>' and cannot be
undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<subnet-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc networking subnet delete <subnet-name> --force
```
**Completion criterion:** `evroc networking subnet get <subnet-name>` returns
`not found`.
## VPCs
VPCs are isolated network containers. A VPC can span multiple regions. Subnets live
inside VPCs.
### List VPCs
```bash
evroc networking vpc list
evroc networking vpc list -o yaml
```
### Get VPC details
```bash
evroc networking vpc get <vpc-name> -o yaml
```
### Create a VPC
```bash
# Dual-stack VPC (IPv4 + IPv6)
evroc networking vpc create my-vpc-dual \
--ipv4-cidr-block=10.0.0.0/16 \
--stack-type=dual-stack
```
**CIDR notes:**
- **IPv4:** `--ipv4-cidr-block=10.0.0.0/16` (RFC1918, ~65k addresses across all subnets)
- **IPv6:** IPv6 addressing is automatic in dual-stack mode; no separate IPv6 CIDR flag is needed.
### Delete a VPC
HALT: Before running ANY `evroc networking vpc delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your VPC '<vpc-name>' and cannot be undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<vpc-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc networking vpc delete <vpc-name> --force
```
**Completion criterion:** `evroc networking vpc get <vpc-name>` returns `not found`.
## Quota Check Helper (IPv4 Elastic IPs)
Before creating resources that need public IPv4 addresses (load balancers, VMs
with fixed IPv4), verify remaining quota:
```bash
bash scripts/check-public-ip-quota.sh <needed>
```
Returns the number of available elastic IPv4 addresses vs the 3-address
account quota. Exits non-zero if insufficient.
**Not needed for IPv6.** Every VM in an IPv6-enabled subnet receives a
public address automatically without quota tracking.
## Pitfalls
- **Default-deny model**: evroc security groups block all traffic not explicitly
allowed. If a VM can't be reached, check the security group rules (not just the VM).
- **Subnet naming**: Default subnets follow `default-<region>-<zone>` (e.g.
`default-se-sto-a`). Custom subnets follow the same zone-scoping rules.
- **Security group scope**: Security groups are project-scoped. A security group in
one project cannot be attached to a VM in another project.
- **Rule direction**: `Ingress` = inbound to the VM. `Egress` = outbound from the VM.
The default-deny applies to both directions independently.
- **IPv4 quota**: evroc accounts have a 3-address elastic IPv4 quota. IPv6 has no per-address quota. Run
`evroc networking publicip list` to check IPv4 usage. Free unused IPv4 addresses before
creating new ones.
- **IPv6 security groups require ICMP**: In dual-stack or IPv6-only subnets, security group rules
must explicitly allow ICMP traffic. The CLI `--protocol ICMP` value covers both ICMPv4 and
ICMPv6 depending on the CIDR family. IPv6 Neighbor Discovery (N/D), Router Solicitation (RS),
and Router Advertisement (RA) all use ICMPv6. Blocking this prevents address configuration and
basic Layer 3 reachability. Add a rule for `ICMP` on both Ingress and Egress directions.
- **Delete order**: Delete VMs and load balancers before deleting the security
groups, public IPs, and subnets they reference. Resources with active references
cannot be deleted.
- **Name rules**: Max 63 chars, lowercase alphanumeric with hyphens, starting/ending
alphanumeric.
- **Labeling**: All networking resources (public IPs, security groups, subnets, VPCs)
support the `label` subcommand for adding/removing labels:
`evroc networking <resource> label <name> --add key=value --remove key`.
See [`references/cli-reference.md`](references/cli-reference.md) for details.
- **CLI aliases**: `sg` for `securitygroup`, `vpc` for `virtualprivatecloud`, `ls` for `list`,
`rm`/`del` for `delete`, `new` for `create`.Use when the user wants to deploy, inspect, or destroy Rancher Manager on evroc cloud using Terraform. Covers K3s provisioning, cloud-init automation, and full lifecycle management. Do NOT trigger for bare VM creation (see evroc-compute), networking resources (see evroc-networking), IAM tasks (evroc-iam), or Think model deployment (evroc-think).
# evroc-rancher
Deploy Rancher Manager on evroc cloud. Provisions a Rancher server node (K3s +
cert-manager + Rancher) and downstream workload nodes (K3s agents) via the evroc
Terraform provider. Cloud-init automates the full software stack.
For troubleshooting, read: [`references/troubleshooting.md`](references/troubleshooting.md).
## When to Use
- User wants to deploy Rancher on evroc
- User wants to destroy an evroc Rancher deployment
- User wants to check the status of a Rancher deployment on evroc
- User mentions "rancher" and "evroc" together in a deployment context
**Do NOT use for:** bare VM creation without Rancher, networking (public IPs,
security groups, subnets, VPCs), IAM tasks, storage buckets, or Think models.
## Prerequisites
Run the prerequisite checker:
```bash
bash scripts/prerequisites.sh
```
If any prerequisite needs manual action, the script exits non-zero and reports what
the user must do.
**Completion criterion:** Script exits 0 with "✓ All prerequisites met."
**Note:** On first run, `terraform init` downloads the evroc Terraform provider
(~20 MB) from `registry.terraform.io`. This requires network access. Subsequent
runs use the local plugin cache.
## Operations
Three operations, each with a dedicated script. Collect required inputs from the
user before running any script.
### Deploy
**Required inputs** (collect via `clarify` if not provided):
1. **SSH public key** — If `prerequisites.sh` found one, confirm it. Otherwise ask
the user to paste it or generate one with `ssh-keygen -t ed25519`.
2. **Rancher admin password** — Must be at least 14 characters.
3. **Number of workload nodes** — If not specified, ask via `clarify`. Default: 1.
Each needs a public IP (see IP quota pitfall).
**Optional inputs** (offer defaults):
| Parameter | Default |
|-----------|---------|
| evroc region | `se-sto` |
| evroc zone | `a` |
| resource prefix | `rancher` |
**Deploy workflow** (copy this checklist and track progress):
```
[ ] 1. Prerequisites pass (prerequisites.sh exits 0)
[ ] 2. Inputs collected (SSH key, password, node count)
[ ] 3. Run deploy.sh
[ ] 4. terraform apply exits 0
[ ] 5. VMs provisioned (~5–8 min)
[ ] 6. cloud-init completes (~5–10 min)
[ ] 7. Rancher URL returns 200
[ ] 8. Report URL + credentials to user
```
Run:
```bash
bash scripts/deploy.sh \
<workdir> \
"<ssh_public_key>" \
"<rancher_password>" \
[region] [zone] [prefix] [workload_node_count]
```
**Completion criterion:** `terraform apply` exits 0 and outputs the Rancher URL.
Report the URL, node IPs, and admin password to the user.
### Status
```bash
bash scripts/status.sh <rancher_node_ip>
```
Checks: cloud-init status, K3s nodes, Rancher pods, cert-manager pods.
**Completion criterion:** All checks show Ready/Running state. If any fail, read
[`references/troubleshooting.md`](references/troubleshooting.md).
### Destroy
HALT: Before running `bash scripts/destroy.sh`, you MUST output this
message to the user VERBATIM before doing anything else:
"This will permanently destroy the entire Rancher deployment and all
associated resources (VMs, disks, public IPs, security groups, networks)
and cannot be undone."
Then:
1. List ALL specific resources that will be destroyed (VM names, disk names,
public IPs, security groups, networks — everything Terraform manages)
2. Ask the user to confirm: "Type 'yes' to confirm destruction of the entire deployment."
3. Wait for the user to explicitly type 'yes'
Do NOT run the destroy command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
bash scripts/destroy.sh <workdir>
```
**Completion criterion:** `terraform destroy` exits 0 with "Destroy complete."
Confirm all resources are removed.
## Git Init
After a successful deploy:
```bash
cd <workdir>
git init
git add -A
git commit -m "Rancher evroc deployment"
```
The user manages remotes and pushes themselves.
## Quick Reference
| Script | Purpose | Usage |
|---|---|---|
| `scripts/prerequisites.sh` | Check and install tools | `bash scripts/prerequisites.sh` |
| `scripts/deploy.sh` | Provision VMs + Rancher | `bash scripts/deploy.sh <workdir> "<ssh_key>" "<password>" [region] [zone] [prefix] [count]` |
| `scripts/destroy.sh` | Tear down everything | `bash scripts/destroy.sh <workdir>` |
| `scripts/status.sh` | Check deployment health | `bash scripts/status.sh <rancher_ip>` |
## Assets
The `assets/` directory contains the Terraform module files. `deploy.sh` copies them
into the work directory at deploy time:
| File | Role |
|---|---|
| `main.tf` | Provider, security groups, VMs, disks, public IPs, outputs |
| `variables.tf` | Input variable definitions |
| `terraform.tfvars.example` | Example variable file |
| `cloud-init-rancher.yaml` | Cloud-init: K3s server + cert-manager + Rancher Helm |
| `cloud-init-workload.yaml` | Cloud-init: K3s agent joining the server cluster |
## Pitfalls
- **Public IP quota**: evroc accounts have a 3-IP quota. The deployment needs
1 IP for the Rancher server + 1 IP per workload node. Run
`bash scripts/check-public-ip-quota.sh <needed>` before deploying (or use the
**evroc-networking** skill). If the user hits a 403 quota error, they must free
IPs via evroc-networking:
`evroc networking publicip list` + `evroc networking publicip delete <id>`.
- **Bootstrap time**: Rancher takes 5–10 minutes to become ready after VMs are
provisioned. A 404 at the Rancher URL during this window is normal.
- **SSH key mismatch**: The SSH key in `terraform.tfvars` must match the key used
to SSH into the VMs.
- **Password requirements**: The Rancher admin password must be at least 14
characters. Shorter passwords cause the Helm install to fail silently.
- **K3s version**: Pinned to `v1.35.7+k3s1` in `variables.tf` for Rancher chart
compatibility (requires `kubeVersion < 1.36.0`). Do not override to v1.36.x
without also bumping the Rancher chart version.
- **Terraform provider**: The `.terraform/` directory is NOT committed. On first
deploy, `terraform init` downloads the evroc provider from
`registry.terraform.io`. Requires internet access for the first run.Use when the user wants to create, list, inspect, or delete evroc storage resources — S3-compatible buckets, file stores (NFS), or bucket service accounts. Requires the evroc CLI. Do NOT trigger for compute VMs, IAM, networking, load balancers, or Think models.
# evroc-storage
Manage evroc storage resources through the `evroc storage` CLI. Covers S3-compatible
buckets, file stores (NFS), and bucket service accounts.
Read the full CLI reference when needed: [`references/cli-reference.md`](references/cli-reference.md).
## When to Use
- User wants to create, list, inspect, or delete an S3-compatible bucket
- User wants to copy files to or from a bucket
- User wants to get S3 credentials for programmatic access
- User wants to create or manage a file store (NFS)
- User wants to create a bucket service account for bucket access
- User mentions "evroc" with "bucket", "storage", "S3", "file store", or "object storage"
**Do NOT use for:** compute VMs, networking (public IPs, security groups, subnets, VPCs), IAM, load balancers, or Think models.
## Prerequisites
Ensure the evroc CLI is installed:
```bash
bash scripts/check-cli.sh
```
Then authenticate:
```bash
bash scripts/check-auth.sh
```
If it fails:
```bash
evroc login
```
## Object Storage (Buckets)
### List buckets
```bash
evroc storage bucket list
evroc storage bucket list -o yaml
evroc storage bucket list -l "environment=production"
```
### Get bucket details
```bash
evroc storage bucket get <bucket-name> -o yaml
```
### Create a bucket
```bash
# Simple
evroc storage bucket create my-bucket
# With object versioning
evroc storage bucket create my-bucket --object-retention-mode=Versioned
# With object locking
evroc storage bucket create my-bucket \
--object-retention-mode=Locking \
--default-object-lock-mode=Soft \
--default-object-lock-duration-days=30
# With a lifecycle policy
evroc storage bucket create my-bucket \
--lifecycle-policy=file://lifecycle-policy.json
```
### Get S3 credentials
Before copying objects or using S3 clients, retrieve credentials:
```bash
evroc storage bucket get-s3-credentials
# Custom path
evroc storage bucket get-s3-credentials --path /tmp/s3-creds.ini
```
Credentials are written to `~/.evroc/credentials.ini` by default.
### Copy objects
```bash
# Upload
evroc storage bucket copy --from /path/to/local/file --to bucket://my-bucket/object-name
# Download
evroc storage bucket copy --from bucket://my-bucket/object-name --to /path/to/local/file
# Between buckets
evroc storage bucket copy \
--from bucket://source-bucket/object \
--to bucket://dest-bucket/object
```
### List objects
```bash
# Current objects
evroc storage bucket list
# Include deleted versions
evroc storage bucket list --include-deleted
# Include all versions
evroc storage bucket list --include-all
```
### Delete a bucket or objects
HALT: Before running ANY `evroc storage bucket delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your Bucket '<bucket-name>' and cannot be
undone."
If using `--purge`, also output: "All objects within the bucket will also
be deleted."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<bucket-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
# Empty bucket
evroc storage bucket delete my-bucket --force
# Non-empty bucket (purges all contents)
evroc storage bucket delete my-bucket --purge --force
# Specific object
evroc storage bucket delete bucket://my-bucket/example.txt
# All objects, keep bucket
evroc storage bucket delete bucket://my-bucket/* --force
```
**Completion criterion:** `evroc storage bucket get my-bucket` returns `not found`.
## File Stores (NFS)
### Create a file store
```bash
evroc storage filestore create my-store --zone a
```
### List file stores
```bash
evroc storage filestore list
evroc storage filestore list -o yaml
```
### Get file store details
```bash
evroc storage filestore get <filestore-name> -o yaml
```
### Delete a file store
HALT: Before running ANY `evroc storage filestore delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your File Store '<filestore-name>' and cannot
be undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<filestore-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc storage filestore delete my-store --force
```
## Bucket Service Accounts
### Create a bucket service account
```bash
evroc storage bucketserviceaccount create my-sa \
--bucket my-bucket \
--bucket my-other-bucket
```
### List bucket service accounts
```bash
evroc storage bucketserviceaccount list
```
### Get details
```bash
evroc storage bucketserviceaccount get <service-account-name>
```
### Delete
HALT: Before running ANY `evroc storage bucketserviceaccount delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your Bucket Service Account '<sa-name>' and
cannot be undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<sa-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc storage bucketserviceaccount delete my-sa --force
```
## Pitfalls
- **S3 credentials required for copy**: `bucket copy` and `bucket list` (within buckets)
read S3 credentials from `~/.evroc/credentials.ini`. Run
`evroc storage bucket get-s3-credentials` first if you haven't already.
- **Bucket must be empty to delete**: Non-empty buckets require `--purge` to delete.
If object locking is enabled, `--purge` is ignored — delete all versions first.
- **Object retention modes**: `Disabled` (default), `Suspended`, `Versioned`, `Locking`.
Object lock requires `Locking` mode and a default lock mode+duration.
- **File store requires zone**: `--zone` is required for file store creation.
The file store uses NFS v4.1.
- **Bucket service accounts**: Must reference at least one bucket via `--bucket`
(repeatable for multiple buckets).
- **Labeling**: Buckets, file stores, and bucket service accounts support the `label`
subcommand for adding/removing labels:
`evroc storage <resource> label <name> --add key=value --remove key`.
See [`references/cli-reference.md`](references/cli-reference.md) for details.
- **CLI aliases**: `bu` for `bucket`, `fs` for `filestore`, `bsa` for
`bucketserviceaccount`, `ls` for `list`, `rm`/`del` for `delete`, `new` for `create`.Use when the user wants to deploy, start, stop, update, or delete evroc Think dedicated GPU model instances; list or use shared OpenAI-compatible models; create or manage Think API keys; or launch coding agents with Think as backend. Requires the evroc CLI. Do NOT trigger for bare compute VMs (evroc-compute), IAM tasks (evroc-iam), or non-evroc model providers.
# evroc-think
Manage evroc Think Models — a Model-as-a-Service inference platform hosting leading
open-source AI models on NVIDIA H100 and B200 GPUs.
Two modes of operation:
1. **Shared Models** — managed by evroc, ready to use, OpenAI-compatible endpoints.
2. **Dedicated Model Instances** — isolated GPU resources for a single model.
Read the full CLI reference when needed: [`references/cli-reference.md`](references/cli-reference.md).
## When to Use
- User wants to create, list, inspect, start, stop, or delete a model instance
- User wants to list available models or instance sizes
- User wants to list or use shared models
- User wants to create or manage API keys for Think endpoints
- User wants to launch a coding agent (Claude Code, Codex, Hermes, OpenCode, Aider)
with Think as backend
- User mentions "evroc" with "Think", "model", "inference", "GPU", "LLM", or "AI model"
**Do NOT use for:** compute VMs, networking (public IPs, security groups, subnets, VPCs), IAM, storage, load balancers, or non-evroc model providers.
## Prerequisites
Ensure the evroc CLI is installed:
```bash
bash scripts/check-cli.sh
```
Then authenticate:
```bash
bash scripts/check-auth.sh
```
If it fails:
```bash
evroc login
```
## Shared Models
Shared Models are managed by evroc — no deployment needed. All shared models expose
OpenAI API-compatible endpoints.
### List shared models
```bash
evroc think sharedmodel list
```
### Use a shared model (curl example)
```bash
TOKEN=$(evroc think apikey create temp-key)
curl -H "Authorization: Bearer ${TOKEN}" \
https://models.think.evroc.com/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"<shared-model-id>","messages":[{"role":"user","content":"Hello"}]}'
```
## Dedicated Model Instances
For more details, read: [`references/dedicated-instances.md`](references/dedicated-instances.md)
### List available models
```bash
evroc think model list
```
### Get model details
```bash
evroc think model get <model-id> -o yaml
```
### List instance sizes
```bash
evroc think size list
```
Sizes encode GPU type + resources, e.g. `1-b200-27c-240g` (1× B200, 27 vCPUs, 240GB RAM).
### Create a dedicated instance
```bash
evroc think instance create my-instance \
--model openai-gpt-oss-120b \
--size 1-b200-27c-240g
```
- `--model` — Required. Model ID from `evroc think model list` (dashed format).
- `--size` — Required. Instance size ID from `evroc think size list`.
- `--stopped` — Create in stopped state (default: running).
**Completion criterion:** `evroc think instance get my-instance -o yaml | grep -E '^\s+status:'` shows `Running`.
### List instances
```bash
evroc think instance list
```
### Get instance details
```bash
evroc think instance get my-instance -o yaml
```
### Start / stop
```bash
evroc think instance start my-instance
evroc think instance stop my-instance
```
### Update a model
```bash
evroc think instance update my-instance --model <new-model-id>
```
### Delete an instance
HALT: Before running ANY `evroc think instance delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete the Model Instance '<instance-name>' and
cannot be undone."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<instance-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc think instance delete my-instance --force
```
**Completion criterion:** `evroc think instance get my-instance` returns `not found`.
## API Keys
API keys authenticate requests to Think endpoints (both shared and dedicated).
### Create an API key
```bash
# No expiry
evroc think apikey create my-key
# With expiry
evroc think apikey create my-key --expiry-timestamp 2026-12-31T23:59:59Z
```
### List API keys
```bash
evroc think apikey list
```
### Delete an API key
HALT: Before running ANY `evroc think apikey delete` command, you MUST output
this message to the user VERBATIM before doing anything else:
"This will permanently delete your API Key '<key-name>' and cannot be
undone. Any services using this key will immediately lose access."
Then:
1. List the specific resource(s) that will be deleted (name, type, project)
2. Ask the user to confirm: "Type 'yes' to confirm deletion of '<key-name>'."
3. Wait for the user to explicitly type 'yes'
Do NOT run the delete command until the user explicitly confirms.
A silence or ambiguous response is NOT confirmation.
```bash
evroc think apikey delete my-key --force
```
## Coding Agent Integration
Think can serve as the model backend for coding agents.
### One-off session (no config files written)
```bash
# Claude Code
evroc think code claude
# Codex
evroc think code codex
# Hermes Agent
evroc think code hermes
# OpenCode
evroc think code opencode
# Aider
evroc think code aider
```
This sets `OPENAI_API_KEY` and `OPENAI_BASE_URL` for the session and spawns the agent.
An API key is created automatically if you're logged in. A project is created
automatically if you don't have one.
### Permanent project config (committable)
Write agent-specific config files into your project directory:
```bash
# Claude Code → CLAUDE.md + .claude/settings.local.json
evroc think code init claude
# Codex → ~/.codex/config.toml
evroc think code init codex
# Hermes Agent → ~/.hermes/config.yaml
evroc think code init hermes
# OpenCode → opencode.jsonc
evroc think code init opencode
```
## Inference API
Think exposes `https://models.think.evroc.com/v1` with OpenAI-compatible endpoints.
### List models via API
```bash
TOKEN=$(evroc think apikey create temp-key)
curl -s -H "Authorization: Bearer ${TOKEN}" \
https://models.think.evroc.com/v1/models | python3 -c \
"import sys,json; [print(m['id']) for m in json.load(sys.stdin)['data']]"
```
## Pitfalls
- **Dedicated vs shared**: Shared models are zero-deployment. Dedicated instances
require choosing a model and size, then waiting for deployment.
- **Model IDs use dashed format** matching `evroc think model list` output: e.g. `openai-gpt-oss-120b`,
not HuggingFace slash format.
- **Size format**: Encodes GPU + resources, e.g. `1-b200-27c-240g`. Run
`evroc think size list` for the authoritative list.
- **API key expiry**: Keys created without `--expiry-timestamp` have no expiry.
Set an expiry for short-lived access.
- **Code integration auto-creates resources**: `evroc think code` and
`evroc think code init` auto-create an API key and project if missing. Check
`evroc think apikey list` after to see what was created.
- **OpenAI API compatibility**: Endpoints follow OpenAI format.
Any OpenAI-compatible client works.
- **CLI aliases**: `mi` for `modelinstance`/`instance`, `ls` for `list`,
`rm`/`del` for `delete`, `new` for `create`.