---
name: deploi-server
description: >
  Provision and manage Deploi VPS servers: authenticate via API,
  create servers with SSH access, and connect. Use when the user mentions Deploi,
  wants a Norwegian cloud VPS, or asks to create/manage a server at deploi.no.
---

# Deploi Server — AI Agent Skill

When this skill is invoked, display this banner to the user:

```
    ◇
   ◇ ◇        ____             _       _
  ◇   ◇      |  _ \  ___ _ __ | | ___ (_)
 ◇     ◇     | | | |/ _ \ '_ \| |/ _ \| |
  ◇   ◇      | |_| |  __/ |_) | | (_) | |
   ◇ ◇       |____/ \___| .__/|_|\___/|_|
    ◇                    |_|
  ◇ ◇        Norwegian Cloud VPS
 ◇   ◇       deploi.no
  ◇ ◇
```

This skill guides an AI agent through provisioning a VPS at Deploi.no:
authentication, server creation, IP retrieval, SSH configuration, and connection.

**API endpoint:** `https://api.deploi.no`
**Method:** POST with `Content-Type: application/json` for all commands.

> **IMPORTANT:** All API calls MUST use Node.js `https` module — NOT `curl`.
> The API blocks `curl` requests for authenticated commands (returns 403 / "Request blocked").

---

## API Call Helper

Use this Node.js pattern for ALL Deploi API calls:

```bash
node -e "
const https = require('https');
const data = JSON.stringify(<PAYLOAD>);
const req = https.request('https://api.deploi.no', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }
}, (res) => {
  let body = '';
  res.on('data', c => body += c);
  res.on('end', () => console.log(body));
});
req.write(data);
req.end();
"
```

Replace `<PAYLOAD>` with the JSON object for each command.

### Token Expiry Handling

If any API call returns `{"success":false,"message":"Not logged in","messageId":41}`, the token has expired. Re-authenticate by repeating Phase 2 before retrying the failed call.

---

## Phase 1: Credentials & Environment Check

Read the project's `.env` file and check for ALL of these:

| Variable | Required | Purpose |
|----------|----------|---------|
| `DEPLOI_USERNAME` | Yes | Login email |
| `DEPLOI_ACCOUNT_PASSWORD` | Yes | Account password |
| `DEPLOI_PASSWORD` | Generated later | Server sudo password (auto-generated, saved before server creation) |

**If `DEPLOI_USERNAME` and `DEPLOI_ACCOUNT_PASSWORD` exist**, proceed to Phase 2.

**If missing**, tell the user:

> Du trenger en Deploi-konto for å fortsette. Registrer deg på **kundepanel.deploi.no** og oppgi e-post og passord her etterpå.
>
> You need a Deploi account to continue. Register at **kundepanel.deploi.no** and provide your email and password here afterwards.

Save credentials to `.env` once provided. **Do not attempt to register accounts via API.**

---

## Phase 2: Authentication

Obtain a fresh token before any authenticated API call. Tokens expire — never store them in `.env`.

**Login payload:**
```json
{
  "command": "Login",
  "username": "<DEPLOI_USERNAME from .env>",
  "password": "<DEPLOI_ACCOUNT_PASSWORD from .env>"
}
```

**Success response:**
```json
{
  "success": true,
  "message": "",
  "messageId": -1,
  "data": {
    "nextstep": "none",
    "token": "uyskpvglnmgpigpahswgsigba",
    "clients": [{ "id": 928, "name": "Friendly Tech AS" }]
  }
}
```

Extract and keep in memory for the session:
- `data.token` → use for all authenticated API calls
- `data.clients[0].id` → use as `clientid`

**If `data.nextstep` is not `"none"`**, the user needs to complete additional steps (e.g., 2FA) — inform them.

**Error handling:**
- `"Email or Password Invalid"` (messageId -1) → credentials are wrong, ask user to verify
- `"Request blocked"` (messageId 138) → you are using curl instead of Node.js, switch to the helper pattern above

---

## Phase 3: SSH Key Preparation (MANDATORY — do this before anything else)

**The SSH key is the primary and preferred access method. Do NOT proceed to server creation without it.**

### Step 1: Check for existing key

Check if `~/.ssh/id_ed25519` exists:
```bash
test -f ~/.ssh/id_ed25519 && echo "EXISTS" || echo "NOT_FOUND"
```

### Step 2: If key does NOT exist

Tell the user and offer to help:

> Jeg fant ingen SSH-nøkkel (`~/.ssh/id_ed25519`). Du trenger en for å koble til serveren sikkert.
> Skal jeg generere en for deg?
>
> No SSH key found at `~/.ssh/id_ed25519`. You need one to connect securely to the server.
> Want me to generate one for you?

**If the user agrees**, generate:
```bash
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
```

**If the user has a key elsewhere**, ask for the path and use that instead.

### Step 3: Read and store the public key

```bash
cat ~/.ssh/id_ed25519.pub
```

Keep the full public key contents in memory — it will be sent with the server creation request.

**STOP HERE if no SSH key is available. Do not proceed without it.**

---

## Phase 4: Needs Assessment & Server Proposal (Mandatory User Approval)

**Never create a server without explicit user approval of the spec and price.**

### Step 1: Understand the user's needs

Before recommending a configuration, ask the user about their use case. Use these questions to guide the conversation — you don't need to ask all of them, just enough to make a good recommendation:

- **What will you use the server for?** (e.g., web app, database, API backend, development/testing, game server, AI/ML, file storage)
- **What tech stack are you planning to run?** (e.g., Node.js, Python/Django, Docker, WordPress, PostgreSQL)
- **How much traffic or how many users do you expect?** (helps size RAM and CPU)
- **Do you need a specific OS?** (default is Ubuntu 24.04 — most users should stick with this)
- **Any specific storage needs?** (e.g., large media files, databases)

If the user already knows exactly what they want (e.g., "give me a Core server"), skip the questions and go straight to the proposal. If they're unsure, help them figure it out.

**Example guidance you can give:**
- **Simple web app / API:** Start package (1 GB RAM) is usually enough to start
- **Docker + database + app:** Core package (4 GB RAM) recommended
- **Multiple services / heavier workloads:** Custom config with 8+ GB RAM
- **Just experimenting / learning:** Start package is perfect

### Step 2: Present packages and pricing

| Package | RAM | vCPU | SSD | Price/month |
|---------|-----|------|-----|-------------|
| Start | 1 GB | 1 | 10 GB | ~131 NOK |
| Core | 4 GB | 2 | 40 GB | ~332 NOK |

**Resource prices** (for custom configurations):
- RAM: **25 NOK/GB/month**
- vCPU (Linux): **96 NOK/vCPU/month**
- SSD: **1 NOK/GB/month**

**Resource limits** (API-enforced):
- RAM: 1–160 GB
- vCPU: 1–80
- SSD: 5–10,000 GB (1 disk only)

### Step 3: Present a clear proposal

Based on the user's needs, recommend a configuration:

> **Foreslått server:** 4 GB RAM, 2 vCPU, 40 GB SSD, Ubuntu 24.04
> **Tilgang:** SSH-nøkkel (passordinnlogging deaktivert)
> **Estimert pris:** 332 NOK/måned (eks. mva)
> **Godkjenner du dette oppsettet og prisen?** Jeg sender bestillingen først etter din bekreftelse.

### Step 4: Wait for explicit approval

If the user wants changes, adjust and re-propose. The user can ask questions at any point — answer them before continuing.

---

## Phase 5: Pre-flight Checks & Server Creation

### Step 1: Verify SSH key is ready

Re-confirm the public key is available. If not, go back to Phase 3. **Do not skip this.**

### Step 2: Check for duplicate server names

**The API allows duplicate server names and will create a billable server regardless.** Always check first:

Call `GetServers` and verify the chosen name does not already exist in the `servers` array. If it does, ask the user for a different name.

### Step 3: Generate and save server password

The API **requires** a password even when using SSH key authentication. This is a sudo password for the server user.

**Check `.env` for `DEPLOI_PASSWORD`.** If it does not exist, generate one and save it **before** creating the server:

```bash
node -e "const c='abcdefghijklmnopqrstuvwxyz',C='ABCDEFGHIJKLMNOPQRSTUVWXYZ',n='0123456789',a=c+C+n;let p=c[Math.random()*26|0]+C[Math.random()*26|0]+n[Math.random()*10|0];for(let i=0;i<13;i++)p+=a[Math.random()*a.length|0];console.log(p.split('').sort(()=>Math.random()-.5).join(''))"
```

**Save to `.env` IMMEDIATELY — before calling CreateVirtualServer:**
```
DEPLOI_PASSWORD=<generated password>
```

This password is the only recovery method if the SSH key is lost. Never skip saving it.

### Step 4: Final checklist before API call

Confirm ALL of the following before proceeding:
- [ ] SSH public key is read and ready
- [ ] `DEPLOI_PASSWORD` is saved in `.env`
- [ ] Server name does not already exist (checked via GetServers)
- [ ] User has explicitly approved the spec and price
- [ ] RAM, CPU, and disk are within API limits

### Step 5: Create the server

**CreateVirtualServer payload:**
```json
{
  "command": "CreateVirtualServer",
  "token": "<from Login>",
  "clientid": <from Login, e.g. 928>,
  "name": "<server-name>",
  "ram": 4,
  "cpu": 2,
  "disks": [
    {
      "idSet": false,
      "id": "",
      "name": "root",
      "size": 40,
      "type": "ssd",
      "backupSpecs": {
        "profileIdSet": false,
        "profileId": 0,
        "spec": [
          { "loc": 1, "daily": 10, "weekly": 0, "monthly": 0, "yearly": 0 }
        ]
      }
    }
  ],
  "OS": "Ubuntu 24.04",
  "licenses": [],
  "username": "cursor",
  "password": "<DEPLOI_PASSWORD from .env>",
  "publickey": "<contents of ~/.ssh/id_ed25519.pub>",
  "allowRemotePasswordLogin": false,
  "publicip": true
}
```

**Allowed OS values** (use exactly):
`"Ubuntu 24.04"`, `"Ubuntu 22.04"`, `"Debian 12"`, `"Debian 13"`, `"Fedora 34"`,
`"Suse 15.1"`, `"CentOS 8.2"`, `"AlmaLinux 9.0"`,
`"Windows Server 2019 Datasenter"`, `"Windows Server 2022 Datasenter"`, `"Windows Server 2025 Datasenter"`

**Success response:** `{ "success": true, "message": "Server created.", "messageId": 80 }`

**Field notes:**
- `clientid`: number (the client ID from Login)
- `username`: always `"cursor"` (Deploi AI-Server convention)
- `publickey`: full contents of the `.pub` file — **must be provided**
- `allowRemotePasswordLogin`: always `false` (SSH key access only, unless user explicitly requests password login)
- `password`: required by API even with SSH key — used as sudo password on the server
- `disks[0].name`: use `"root"`
- `name`: use a descriptive, lowercase, hyphenated name (e.g. `"my-project-server"`)
- Only 1 disk is supported per server via API

---

## Phase 6: Get Server IP

The server takes 30–120 seconds to provision. Poll for the IP.

**Step 1 — GetServers:**
```json
{
  "command": "GetServers",
  "token": "<token>",
  "clientid": <clientid>
}
```

Response: `{ "servers": [{ "id": 1148, "name": "my-server", "cpu": 1, "ram": 1, "disk": 20, "type": "virtual", "status": "active" }], ... }`

Find your server by `name` in the `servers` array.

Note: if multiple servers share the same name (should not happen if Phase 5 pre-flight was followed), use the highest `id` (most recently created).

**Step 2 — GetServerInfo:**
```json
{
  "command": "GetServerInfo",
  "token": "<token>",
  "id": <server id from GetServers>
}
```

Response: `{ "serverInfo": { "type": "virtual", "virtual": { "ram": 1, "cpu": 1, "mainip": "193.69.50.148", "ips": ["193.69.50.148", "10.0.2.20"], ... } }, ... }`

The public IP is at `serverInfo.virtual.mainip`.

**Retry logic:** If `mainip` is empty, `"0.0.0.0"`, or the server `status` is not `"active"`, wait 15 seconds and retry. Up to 8 retries (2 minutes total). If still no IP, tell the user to check kundepanel.deploi.no.

**Note:** Servers with `"status": "off"` will return `success: false` from GetServerInfo — only active/running servers can be queried.

---

## Phase 7: SSH Config

Add the server to `~/.ssh/config` so the user can connect easily.

**Check first:** Read `~/.ssh/config` and verify no `Host <server-name>` block already exists.

**If the file does not exist**, create it. **If it exists**, append to the end.

```
Host <server-name>
  HostName <server-ip>
  User cursor
  IdentityFile ~/.ssh/id_ed25519
```

- `Host`: use the server name (e.g. `my-server`)
- `User`: always `cursor`
- `IdentityFile`: always `~/.ssh/id_ed25519`
- No `Port` line (default 22)

**Do not overwrite** existing entries.

---

## Phase 8: Connect to Server

Once the server is in SSH config, prompt the user to connect.

**Do not run server-side commands from the local context.** Commands execute on the user's machine, not the server.

Tell the user:

> **Serveren er klar!** Koble til for å begynne å jobbe på den.
>
> - **VS Code / Cursor:** Trykk F1 → «Remote-SSH: Connect to Host» → velg **<server-name>**
> - **Claude Code:** `claude --ssh <server-name>`
> - **Terminal:** `ssh <server-name>`
>
> Gå til server-konteksten og si hva du vil gjøre videre.

**Wait for the user to connect.** Do not attempt server-side work in this local context.

---

## Next: Deploy & Manage Your Server

Now that the server is provisioned and SSH is configured, there's more you can do. Deploi has an **Ops skill** that lets your AI agent deploy apps, install databases, configure web servers, set up Docker, and manage everything on your server.

Tell the user:

> **Vil du deploye noe til serveren?** Det finnes en Deploi Ops-skill som lar AI-agenten din installere programvare, deploye apper og konfigurere tjenester automatisk.
>
> Last ned flere skills på **https://deploi.no/ai-skills**

---

## Environment Variables Summary

| Variable | Purpose | When saved |
|----------|---------|------------|
| `DEPLOI_USERNAME` | Login email | Phase 1 (user provides) |
| `DEPLOI_ACCOUNT_PASSWORD` | Account password | Phase 1 (user provides) |
| `DEPLOI_PASSWORD` | Server sudo password | Phase 5 Step 3 (auto-generated, saved BEFORE server creation) |

All `.env` files should be gitignored. Never commit credentials.

---

## API Error Reference

| messageId | Message | Meaning |
|-----------|---------|---------|
| -1 | Email or Password Invalid | Wrong credentials |
| 4 | Password must be at least 8 characters... | Server password too weak |
| 8 | Memory must be more than 0... | RAM out of range (1-160 GB) |
| 9 | CPU must be more than 0... | CPU out of range (1-80) |
| 11 | OS is not one of the supported options | Invalid OS string |
| 13 | The API currently only supports creating one disk | Wrong disk count |
| 14 | Disk size must be between 5 and 10 000 GB | Disk size out of range |
| 23 | Request is not according to the API specifications | Missing required fields |
| 41 | Not logged in | Token expired or invalid — re-authenticate |
| 25 | Creating virtual server failed | Generic creation failure — check account limits or contact Deploi support |
| 80 | Server created | Success |
| 138 | Request blocked | Using curl — switch to Node.js |

## Important Notes

- **SSH key is the primary access method.** Always use `allowRemotePasswordLogin: false` unless the user explicitly asks for password login.
- **Password is required by the API** even with SSH key — it becomes the server's sudo password. Always generate, save to `.env`, then create the server.
- **No server deletion via API.** To cancel/delete a server, the user must use kundepanel.deploi.no.
- **Duplicate names are allowed by the API** — always check with GetServers before creating.
- **Tokens are invalidated on Logout** and expire after inactivity — re-authenticate as needed.
- **Wrong clientid returns "Not logged in"** — if you get messageId 41, verify both token and clientid.
