# CLI- und SDK-Leitfaden

## Übersicht

Clore.ai bietet eine **REST-API** die vollständigen programmgesteuerten Zugriff auf den GPU-Marktplatz ermöglicht — Auflisten von Servern, Erstellen von Bestellungen, Überwachen von Deployments und Stornieren von Vermietungen.

> **Hinweis:** Es gibt derzeit kein offizielles CLI-Binary. Alle Automatisierungen erfolgen direkt über die REST-API mit Tools wie `curl`, Python oder Node.js.

**Basis-URL:** `https://api.clore.ai/v1`

**Antwortformat:** JSON. Jede Antwort enthält ein `code` Feld, das den Status angibt.

***

## Authentifizierung

Generieren Sie Ihren API-Schlüssel aus dem [Clore.ai Dashboard](https://clore.ai):

1. Melden Sie sich bei Ihrem Konto an
2. Navigieren Sie zu **API** Abschnitt in den Einstellungen
3. Generieren und kopieren Sie Ihren API-Schlüssel

**Header-Format:**

```
auth: IHRE_API_KEY
```

> ⚠️ **Wichtig:** Der auth-Header ist `auth`, **nicht** `Authorization: Bearer`. Die Verwendung des falschen Formats gibt den Code `3` (Ungültiges API-Token) zurück.

**Beispiel:**

```bash
curl -H 'auth: IHRE_API_KEY' 'https://api.clore.ai/v1/marketplace'
```

***

## Schnellstart

### Marktplatz-Server auflisten

Durchsuchen Sie alle verfügbaren GPU-Server:

```bash
curl -XGET \
  -H 'auth: IHRE_API_KEY' \
  'https://api.clore.ai/v1/marketplace'
```

**Antwort:**

```json
{
  "servers": [
    {
      "id": 6,
      "owner": 4,
      "mrl": 73,
      "price": {
        "on_demand": { "bitcoin": 0.00001 },
        "spot": { "bitcoin": 0.000001 }
      },
      "rented": false,
      "specs": {
        "cpu": "Intel Core i9-11900",
        "ram": 62.67,
        "gpu": "1x NVIDIA GeForce GTX 1080 Ti",
        "gpuram": 11,
        "net": { "up": 26.38, "down": 118.42, "cc": "CZ" }
      }
    }
  ],
  "my_servers": [1, 2, 4],
  "code": 0
}
```

***

### Ihre Bestellungen abrufen

```bash
curl -XGET \
  -H 'auth: IHRE_API_KEY' \
  'https://api.clore.ai/v1/my_orders'
```

Abgeschlossene/abgelaufene Bestellungen einbeziehen:

```bash
curl -XGET \
  -H 'auth: IHRE_API_KEY' \
  'https://api.clore.ai/v1/my_orders?return_completed=true'
```

***

### Eine Bestellung erstellen (On-Demand)

```bash
curl -XPOST \
  -H 'auth: IHRE_API_KEY' \
  -H 'Content-type: application/json' \
  -d '{
    "currency": "bitcoin",
    "image": "cloreai/ubuntu20.04-jupyter",
    "renting_server": 6,
    "type": "on-demand",
    "ports": {
      "22": "tcp",
      "8888": "http"
    },
    "ssh_password": "YourSSHPassword123",
    "jupyter_token": "YourJupyterToken123"
  }' \
  'https://api.clore.ai/v1/create_order'
```

**Antwort:**

```json
{ "code": 0 }
```

***

### Einen Spot-Order erstellen

Spot-Bestellungen sind günstiger, können aber überboten werden. Sie legen Ihren Preis pro Tag fest:

```bash
curl -XPOST \
  -H 'auth: IHRE_API_KEY' \
  -H 'Content-type: application/json' \
  -d '{
    "currency": "bitcoin",
    "image": "cloreai/ubuntu20.04-jupyter",
    "renting_server": 6,
    "type": "spot",
    "spotprice": 0.000005,
    "ports": {
      "22": "tcp",
      "8888": "http"
    },
    "ssh_password": "YourSSHPassword123"
  }' \
  'https://api.clore.ai/v1/create_order'
```

***

### Bestellstatus prüfen

```bash
curl -XGET \
  -H 'auth: IHRE_API_KEY' \
  'https://api.clore.ai/v1/my_orders'
```

Aktive Bestellungen enthalten `pub_cluster` (Hostnamen) und `tcp_ports` für SSH-Zugriff:

```json
{
  "id": 38,
  "pub_cluster": ["n1.c1.clorecloud.net", "n2.c1.clorecloud.net"],
  "tcp_ports": ["22:10000"],
  "http_port": "8888",
  "expired": false
}
```

SSH auf Ihren gemieteten Server:

```bash
ssh root@n1.c1.clorecloud.net -p 10000
```

***

### Eine Bestellung stornieren

```bash
curl -XPOST \
  -H 'auth: IHRE_API_KEY' \
  -H 'Content-type: application/json' \
  -d '{
    "id": 38
  }' \
  'https://api.clore.ai/v1/cancel_order'
```

Optional können Sie ein Problem mit dem Server melden:

```bash
curl -XPOST \
  -H 'auth: IHRE_API_KEY' \
  -H 'Content-type: application/json' \
  -d '{
    "id": 38,
    "issue": "GPU wurde überhitzt und drosselte die Leistung"
  }' \
  'https://api.clore.ai/v1/cancel_order'
```

***

## Python SDK

Ein leichter Wrapper, der die `requests` Bibliothek verwendet. Installieren Sie sie mit:

```bash
pip install requests
```

### CloreClient-Klasse

```python
import requests
import time

class CloreClient:
    """Ein einfaches Python-SDK für die Clore.ai REST-API."""
    
    BASE_URL = "https://api.clore.ai/v1"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({"auth": api_key})
    
    def _get(self, endpoint: str, params: dict = None) -> dict:
        url = f"{self.BASE_URL}/{endpoint}"
        response = self.session.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        if data.get("code") != 0:
            raise CloreAPIError(data)
        return data
    
    def _post(self, endpoint: str, body: dict) -> dict:
        url = f"{self.BASE_URL}/{endpoint}"
        self.session.headers.update({"Content-type": "application/json"})
        response = self.session.post(url, json=body)
        response.raise_for_status()
        data = response.json()
        if data.get("code") != 0:
            raise CloreAPIError(data)
        return data
    
    def list_servers(self) -> list:
        """Alle verfügbaren Server auf dem Marktplatz abrufen."""
        data = self._get("marketplace")
        return data["servers"]
    
    def get_server_details(self, server_id: int) -> dict:
        """Spot-Marktplatzinformationen für einen bestimmten Server abrufen."""
        data = self._get("spot_marketplace", params={"market": server_id})
        return data["market"]
    
    def create_order(
        self,
        server_id: int,
        image: str,
        order_type: str = "on-demand",
        currency: str = "bitcoin",
        spotprice: float = None,
        ports: dict = None,
        ssh_password: str = None,
        ssh_key: str = None,
        jupyter_token: str = None,
        env: dict = None,
        command: str = None,
    ) -> dict:
        """
        Erstellt eine On-Demand- oder Spot-Bestellung.
        
        Parameter:
            server_id: ID des zu mietenden Servers
            image: Docker-Image (z. B. 'cloreai/ubuntu20.04-jupyter')
            order_type: 'on-demand' oder 'spot'
            currency: 'bitcoin' (Standard)
            spotprice: Erforderlich für Spot-Bestellungen — Preis pro Tag in BTC
            ports: Portweiterleitung, z. B. {"22": "tcp", "8888": "http"}
            ssh_password: SSH-Passwort (nur alphanumerische Zeichen)
            ssh_key: SSH-öffentlicher Schlüssel
            jupyter_token: Jupyter-Notebook-Token
            env: Dictionary mit Umgebungsvariablen
            command: Shell-Befehl, der nach dem Start des Containers ausgeführt wird
        """
        if order_type == "spot" and spotprice is None:
            raise ValueError("spotprice is required for spot orders")
        
        body = {
            "currency": currency,
            "image": image,
            "renting_server": server_id,
            "type": order_type,
        }
        
        if spotprice is not None:
            body["spotprice"] = spotprice
        if ports:
            body["ports"] = ports
        if ssh_password:
            body["ssh_password"] = ssh_password
        if ssh_key:
            body["ssh_key"] = ssh_key
        if jupyter_token:
            body["jupyter_token"] = jupyter_token
        if env:
            body["env"] = env
        if command:
            body["command"] = command
        
        return self._post("create_order", body)
    
    def get_orders(self, include_completed: bool = False) -> list:
        """Ihre aktiven (und optional abgeschlossenen) Bestellungen abrufen."""
        params = {"return_completed": "true"} if include_completed else {}
        data = self._get("my_orders", params=params)
        return data["orders"]
    
    def cancel_order(self, order_id: int, issue: str = None) -> dict:
        """Eine Bestellung stornieren. Optional ein Problem melden."""
        body = {"id": order_id}
        if issue:
            body["issue"] = issue
        return self._post("cancel_order", body)
    
    def get_wallets(self) -> list:
        """Ihre Wallets und Kontostände abrufen."""
        data = self._get("wallets")
        return data["wallets"]
    
    def get_my_servers(self) -> list:
        """Server abrufen, die Sie dem Marktplatz bereitstellen."""
        data = self._get("my_servers")
        return data["servers"]


class CloreAPIError(Exception):
    """Wird ausgelöst, wenn die Clore-API einen von null verschiedenen Code zurückgibt."""
    
    ERROR_CODES = {
        0: "Normal",
        1: "Datenbankfehler",
        2: "Ungültige Eingabedaten",
        3: "Ungültiges API-Token",
        4: "Ungültiger Endpunkt",
        5: "Rate-Limit überschritten (1 Anfrage/Sekunde)",
        6: "Fehler (siehe Fehlerfeld)",
    }
    
    def __init__(self, response: dict):
        self.code = response.get("code")
        self.error = response.get("error", "")
        message = self.ERROR_CODES.get(self.code, f"Unbekannter Code {self.code}")
        if self.error:
            message = f"{message}: {self.error}"
        super().__init__(f"Clore API Fehler {self.code}: {message}")
```

### Vollständiges funktionierendes Beispiel

```python
import os
from clore_client import CloreClient, CloreAPIError

# Client initialisieren
client = CloreClient(api_key=os.environ["CLORE_API_KEY"])

# 1. Marktplatz durchsuchen
servers = client.list_servers()
print(f"Gefunden {len(servers)} Server auf dem Marktplatz")

# 2. Verfügbare RTX 4090-Server filtern
rtx4090_servers = [
    s for s in servers
    if "4090" in s["specs"].get("gpu", "") and not s["rented"]
]

if not rtx4090_servers:
    print("Keine verfügbaren RTX 4090-Server gefunden")
    exit(1)

# 3. Den günstigsten auswählen
cheapest = min(rtx4090_servers, key=lambda s: s["price"]["on_demand"]["bitcoin"])
print(f"Günstigste RTX 4090: Server-ID {cheapest['id']}, "
      f"Preis {cheapest['price']['on_demand']['bitcoin']:.8f} BTC/Tag")

# 4. Eine Bestellung erstellen
try:
    client.create_order(
        server_id=cheapest["id"],
        image="cloreai/ubuntu20.04-jupyter",
        order_type="on-demand",
        currency="bitcoin",
        ports={"22": "tcp", "8888": "http"},
        ssh_password="SecurePass123",
        jupyter_token="MyToken123",
    )
    print("Bestellung erfolgreich erstellt!")
except CloreAPIError as e:
    print(f"Fehler beim Erstellen der Bestellung: {e}")
    exit(1)

# 5. Ihre Bestellungen prüfen
orders = client.get_orders()
for order in orders:
    if not order.get("expired"):
        cluster = order.get("pub_cluster", [])
        tcp = order.get("tcp_ports", [])
        print(f"Bestellung {order['id']}: Server {order['si']}")
        if cluster and tcp:
            ssh_port = tcp[0].split(":")[1]
            print(f"  SSH: ssh root@{cluster[0]} -p {ssh_port}")
```

***

## Node.js-Beispiel

Verwendung der nativen `fetch` API (Node.js 18+):

```javascript
const BASE_URL = 'https://api.clore.ai/v1';

class CloreClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.defaultHeaders = {
      'auth': apiKey,
    };
  }

  async get(endpoint, params = {}) {
    const url = new URL(`${BASE_URL}/${endpoint}`);
    Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
    
    const res = await fetch(url.toString(), {
      headers: this.defaultHeaders,
    });
    
    const data = await res.json();
    if (data.code !== 0) {
      throw new Error(`Clore API error ${data.code}: ${data.error || 'unknown'}`);
    }
    return data;
  }

  async post(endpoint, body) {
    const res = await fetch(`${BASE_URL}/${endpoint}`, {
      method: 'POST',
      headers: {
        ...this.defaultHeaders,
        'Content-type': 'application/json',
      },
      body: JSON.stringify(body),
    });
    
    const data = await res.json();
    if (data.code !== 0) {
      throw new Error(`Clore API error ${data.code}: ${data.error || 'unknown'}`);
    }
    return data;
  }

  async listServers() {
    const data = await this.get('marketplace');
    return data.servers;
  }

  async getOrders(includeCompleted = false) {
    const params = includeCompleted ? { return_completed: 'true' } : {};
    const data = await this.get('my_orders', params);
    return data.orders;
  }

  async createOrder({ serverId, image, type = 'on-demand', currency = 'bitcoin', spotprice, ports, sshPassword, jupyterToken, env, command }) {
    const body = {
      currency,
      image,
      renting_server: serverId,
      type,
      ...(spotprice && { spotprice }),
      ...(ports && { ports }),
      ...(sshPassword && { ssh_password: sshPassword }),
      ...(jupyterToken && { jupyter_token: jupyterToken }),
      ...(env && { env }),
      ...(command && { command }),
    };
    return this.post('create_order', body);
  }

  async cancelOrder(orderId, issue = null) {
    const body = { id: orderId };
    if (issue) body.issue = issue;
    return this.post('cancel_order', body);
  }
}

// Verwendungsbeispiel
const client = new CloreClient(process.env.CLORE_API_KEY);

async function main() {
  // Marktplatz auflisten
  const servers = await client.listServers();
  console.log(`Der Marktplatz hat ${servers.length} Server`);

  // Verfügbare RTX 4090-Server finden
  const available = servers.filter(
    s => s.specs.gpu.includes('4090') && !s.rented
  );
  console.log(`Verfügbare RTX 4090-Server: ${available.length}`);

  // Aktuelle Bestellungen abrufen
  const orders = await client.getOrders();
  const activeOrders = orders.filter(o => !o.expired);
  console.log(`Aktive Bestellungen: ${activeOrders.length}`);

  for (const order of activeOrders) {
    const host = order.pub_cluster?.[0];
    const portMapping = order.tcp_ports?.[0];
    if (host && portMapping) {
      const sshPort = portMapping.split(':')[1];
      console.log(`Bestellung ${order.id} → ssh root@${host} -p ${sshPort}`);
    }
  }
}

main().catch(console.error);
```

***

## Gängige Workflows

### Günstigste RTX 4090 finden und mieten

```python
from clore_client import CloreClient

client = CloreClient(api_key="YOUR_API_KEY")

def rent_cheapest_rtx4090(image="cloreai/ubuntu20.04-jupyter", ssh_password="SecurePass123"):
    servers = client.list_servers()
    
    # Filter: verfügbare RTX 4090
    candidates = [
        s for s in servers
        if "4090" in s["specs"].get("gpu", "")
        and not s["rented"]
    ]
    
    if not candidates:
        raise RuntimeError("Keine verfügbaren RTX 4090-Server gefunden")
    
    # Nach On-Demand-BTC-Preis sortieren
    candidates.sort(key=lambda s: s["price"]["on_demand"]["bitcoin"])
    best = candidates[0]
    
    price_btc = best["price"]["on_demand"]["bitcoin"]
    print(f"Miete Server {best['id']}: {best['specs']['gpu']} @ {price_btc:.8f} BTC/Tag")
    
    client.create_order(
        server_id=best["id"],
        image=image,
        order_type="on-demand",
        currency="bitcoin",
        ports={"22": "tcp"},
        ssh_password=ssh_password,
    )
    
    print("Fertig! Überprüfen Sie Ihre Bestellungen für SSH-Verbindungsdaten.")
    return best["id"]

rent_cheapest_rtx4090()
```

***

### Meine Bestellungen überwachen

```python
import time
from clore_client import CloreClient

client = CloreClient(api_key="YOUR_API_KEY")

def monitor_orders(poll_interval_seconds=60):
    """Bestellungen abfragen und Statusaktualisierungen ausgeben."""
    print(f"Überwache Bestellungen (Abfrage alle {poll_interval_seconds}s). Strg+C zum Beenden.\n")
    
    while True:
        orders = client.get_orders(include_completed=False)
        active = [o for o in orders if not o.get("expired")]
        
        print(f"--- {len(active)} aktive Bestellung(en) ---")
        for order in active:
            cluster = order.get("pub_cluster", [])
            tcp = order.get("tcp_ports", [])
            spend = order.get("spend", 0)
            
            ssh_info = ""
            if cluster and tcp:
                port = tcp[0].split(":")[1]
                ssh_info = f" | SSH: {cluster[0]}:{port}"
            
            print(f"  Bestellung {order['id']}: Server {order['si']}"
                  f" | ausgegeben {spend:.8f} BTC{ssh_info}")
        
        if not active:
            print("  Keine aktiven Bestellungen.")
        
        print()
        time.sleep(poll_interval_seconds)

monitor_orders()
```

***

### Automatisch mieten, wenn der Preis unter X fällt

```python
import time
from clore_client import CloreClient

client = CloreClient(api_key="YOUR_API_KEY")

def auto_rent_on_price_drop(
    gpu_model: str = "RTX 4090",
    max_price_btc: float = 0.00015,
    image: str = "cloreai/ubuntu20.04-jupyter",
    ssh_password: str = "SecurePass123",
    check_interval_seconds: int = 120,
):
    """
    Beobachten Sie den Marktplatz und mieten Sie automatisch eine GPU, wenn der Preis unter die Schwelle fällt.
    
    Parameter:
        gpu_model: GPU-Name, nach dem gesucht wird (case-insensitive)
        max_price_btc: Maximal akzeptabler Preis pro Tag in BTC
        image: Docker-Image, das bereitgestellt werden soll
        ssh_password: SSH-Passwort für den Container
        check_interval_seconds: Wie oft geprüft wird (beachten Sie die Rate-Limits!)
    """
    print(f"Suche nach {gpu_model} bei ≤ {max_price_btc:.8f} BTC/Tag...")
    
    while True:
        servers = client.list_servers()
        
        for server in servers:
            gpu = server["specs"].get("gpu", "")
            if gpu_model.lower() not in gpu.lower():
                continue
            if server["rented"]:
                continue
            
            price = server["price"]["on_demand"]["bitcoin"]
            if price <= max_price_btc:
                print(f"🎯 Treffer gefunden! Server {server['id']}: {gpu} @ {price:.8f} BTC/Tag")
                
                try:
                    client.create_order(
                        server_id=server["id"],
                        image=image,
                        order_type="on-demand",
                        currency="bitcoin",
                        ports={"22": "tcp"},
                        ssh_password=ssh_password,
                        required_price=price,  # Diesen Preis sperren
                    )
                    print(f"✅ Bestellung für Server {server['id']} erstellt!")
                    return server["id"]
                except Exception as e:
                    print(f"Failed to create order: {e}. Will retry...")
        
        print(f"No match yet. Checking again in {check_interval_seconds}s...")
        time.sleep(check_interval_seconds)

auto_rent_on_price_drop(gpu_model="4090", max_price_btc=0.00012)
```

***

## WebSocket

Die Clore.ai REST-API bietet derzeit keinen WebSocket-Endpunkt. Für Echtzeitüberwachung verwenden Sie Polling mit einem angemessenen Intervall (siehe Ratenbegrenzungen unten).

***

## Ratenbegrenzungen

| Endpunkt                        | Limit                       |
| ------------------------------- | --------------------------- |
| Die meisten Endpunkte           | **1 Anfrage/Sekunde**       |
| `create_order`                  | **1 Anfrage/5 Sekunden**    |
| `set_spot_price` (Preissenkung) | Einmal pro **600 Sekunden** |

**Antwort bei Erreichen des Ratenlimits (Code 5):**

```json
{ "code": 5 }
```

**Best Practices:**

* Fügen Sie hinzu `time.sleep(1)` zwischen aufeinanderfolgenden API-Aufrufen
* Für `create_order`, warten Sie mindestens 5 Sekunden zwischen Anfragen
* Verwenden Sie Polling-Intervalle von 60+ Sekunden für Überwachungs-Schleifen
* Cachen Sie Marktplatzdaten lokal, wenn Sie sie häufig abfragen müssen

**Python-Helfer:**

```python
import time

def safe_api_call(fn, *args, delay=1.1, **kwargs):
    """Wickelt einen API-Aufruf mit Ratenlimit-Sicherheit ein."""
    result = fn(*args, **kwargs)
    time.sleep(delay)
    return result
```

***

## Fehlerbehandlung

Jede API-Antwort enthält ein `code` Feld. Ein Wert von `0` bedeutet Erfolg.

### Fehlercodes

| Code | Bedeutung                              | Aktion                                          |
| ---- | -------------------------------------- | ----------------------------------------------- |
| `0`  | Erfolg                                 | —                                               |
| `1`  | Datenbankfehler                        | Nach einer Verzögerung erneut versuchen         |
| `2`  | Ungültige Eingabedaten                 | Überprüfen Sie Ihren Anfragekörper/Parameter    |
| `3`  | Ungültiges API-Token                   | Überprüfen Sie Ihren API-Schlüssel im Dashboard |
| `4`  | Ungültiger Endpunkt                    | Überprüfen Sie die Endpunkt-URL                 |
| `5`  | Ratenlimit überschritten (1 req/sec)   | Fügen Sie Verzögerungen zwischen Anfragen hinzu |
| `6`  | Anwendungsfehler (siehe `Fehler` Feld) | Lesen Sie das `Fehler` Feld für Details         |

### Code 6 Unterfehler

| `Fehler` Wert                 | Bedeutung                                                                                       |
| ----------------------------- | ----------------------------------------------------------------------------------------------- |
| `exceeded_max_step`           | Spot-Preisreduzierung zu groß; überprüfen Sie `max_step` Feld                                   |
| `can_lower_every_600_seconds` | Muss warten, bevor der Spot-Preis erneut gesenkt werden kann; überprüfen Sie `time_to_lowering` |

### Python Fehlerbeispiel zur Fehlerbehandlung

```python
from clore_client import CloreClient, CloreAPIError
import time

client = CloreClient(api_key="YOUR_API_KEY")

def create_order_with_retry(server_id, image, max_retries=3):
    for attempt in range(max_retries):
        try:
            return client.create_order(
                server_id=server_id,
                image=image,
                order_type="on-demand",
                currency="bitcoin",
                ports={"22": "tcp"},
                ssh_password="SecurePass123",
            )
        except CloreAPIError as e:
            if e.code == 5:  # Ratenlimit
                print(f"Rate limited. Waiting 5s... (attempt {attempt+1}/{max_retries})")
                time.sleep(5)
            elif e.code == 3:  # Ungültiger API-Schlüssel
                print("Ungültiger API-Schlüssel! Überprüfen Sie Ihren CLORE_API_KEY.")
                raise
            elif e.code == 2:  # Ungültige Eingabe
                print(f"Invalid request: {e}")
                raise
            else:
                print(f"API error: {e}. Retrying in 3s...")
                time.sleep(3)
    
    raise RuntimeError(f"Failed after {max_retries} attempts")
```

### JavaScript Fehlerbeispiel zur Fehlerbehandlung

```javascript
async function createOrderWithRetry(client, serverConfig, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.createOrder(serverConfig);
    } catch (err) {
      const code = parseInt(err.message.match(/error (\d+)/)?.[1]);
      
      if (code === 5) {
        console.log(`Rate limited. Waiting 5s... (attempt ${attempt + 1}/${maxRetries})`);
        await new Promise(r => setTimeout(r, 5000));
      } else if (code === 3) {
        throw new Error('Invalid API key');
      } else {
        console.log(`API error: ${err.message}. Retrying in 3s...`);
        await new Promise(r => setTimeout(r, 3000));
      }
    }
  }
  throw new Error(`Failed after ${maxRetries} attempts`);
}
```

***

## Verfügbare Docker-Images

Clore.ai stellt vorgefertigte Images bereit, die für GPU-Workloads optimiert sind:

| Image                         | Beschreibung              |
| ----------------------------- | ------------------------- |
| `cloreai/ubuntu20.04-jupyter` | Ubuntu 20.04 + JupyterLab |
| `cloreai/ubuntu22.04-jupyter` | Ubuntu 22.04 + JupyterLab |

Sie können auch jedes öffentliche Docker Hub-Image verwenden. Für GPU-Zugriff verwenden Sie CUDA-fähige Images, z. B.:

```
nvidia/cuda:12.1.0-devel-ubuntu22.04
```

***

## Portweiterleitung

Geben Sie beim Erstellen einer Bestellung Ports an, die expose werden sollen:

```json
{
  "ports": {
    "22": "tcp",
    "8888": "http",
    "6006": "http"
  }
}
```

* **`"tcp"`** — Direkte TCP-Portweiterleitung (für SSH, benutzerdefinierte Server)
* **`"http"`** — HTTPS-Proxy (für Jupyter, Web-UIs). Es ist nur ein HTTP-Port pro `http_port` Feld erlaubt.

Nach Erstellung der Bestellung erscheinen Verbindungsdetails in `my_orders`:

```json
{
  "pub_cluster": ["n1.c1.clorecloud.net", "n2.c1.clorecloud.net"],
  "tcp_ports": ["22:10000"],
  "http_port": "8888"
}
```

Verbinden über SSH:

```bash
ssh root@n1.c1.clorecloud.net -p 10000
```

Zugriff auf Jupyter über den Browser: `https://n1.c1.clorecloud.net` (verwendet das `http_port`)
