# Medii administrate (/ro/docs/environments/managed)

VM-uri pe care codai le provizionează pentru tine pe OpenStack sau Hetzner — dimensiuni, regiuni, pornire și oprire, oprire automată și snapshot-uri.

Un mediu **administrat** este un VM pe care codai îl creează, îl înrolează și îl întreține. Alegi un furnizor și o dimensiune; un worker care rulează în fiecare minut provizionează mașina, instalează `codaid` prin cloud-init, o oprește când e inactivă și o facturează pe secundă de rulare.

<Callout type="info">
Furnizorii administrați sunt **în curs de lansare** și se activează per cont. Până când contul tău e activat, `POST /v1/environments` cu un furnizor administrat răspunde `403` — *Provider 'hetzner' is not available yet; use 'byo' (bring your own machine)* — iar hub-ul afișează opțiunile administrate ca *în curând*. [Propriul server](./byo-install.mdx) funcționează pentru toată lumea.
</Callout>

## Furnizori și regiuni [#furnizori-și-regiuni]

| `provider` | Unde | `region` implicită |
| --- | --- | --- |
| `openstack` | codai cloud pe Cloudify (UE, România), Ubuntu 24.04 | `eu-east-1` |
| `hetzner` | Hetzner Cloud | `nbg1` (tipurile de mai jos există în `fsn1`, `hel1` și `nbg1`) |

Valoarea de furnizor `local` există în schemă, dar este rezervată; nu se provizionează.

## Dimensiuni [#dimensiuni]

Fiecare furnizor facturează memoria RAM pe care o oferă efectiv.

| `size` | vCPU | OpenStack (Cloudify) | Tarif | Hetzner | Tarif |
| --- | --- | --- | --- | --- | --- |
| `small` (implicit) | 2 | `b2i.2c-2g`, 2 GB | 0,03 € / h | `cx23`, 4 GB | 0,04 € / h |
| `medium` | 4 | `b2i.4c-4g`, 4 GB | 0,06 € / h | `cx33`, 8 GB | 0,08 € / h |
| `large` | 8 | `b2i.8c-8g`, 8 GB | 0,12 € / h | `cx43`, 16 GB | 0,16 € / h |

Tarif = vCPU × 0,01 €/h + GB RAM × 0,005 €/h, salvat pe mediu la creare (`hourlyRateMicroEur`). Tipul de server este o preferință: dacă furnizorul îl retrage, codai alege cel mai ieftin tip disponibil cu cel puțin aceleași vCPU și RAM și facturează în continuare tariful dimensiunii. Discul este cel inclus în flavor sau în tipul de server (OpenStack 20 / 40 / 80 GB, Hetzner 40 / 80 / 160 GB); cât timp mașina este oprită sau arhivată, discul păstrat costă 0,05 € pe GB-lună — vezi [stocarea](./billing.mdx#stocare-pentru-mediile-oprite-și-arhivate). Mediile cu GPU vin în curând.

Crearea sau pornirea unui mediu administrat cere un sold în portofel de cel puțin o oră la tariful lui, altfel răspunsul este `402 insufficient_balance` ([detalii](./billing.mdx#verificarea-soldului-la-creare-și-pornire)).

### Rezervă de calcul și perioadă de grație [#rezervă-de-calcul-și-perioadă-de-grație]

Setează o **rezervă de calcul** în Hub → Facturare: utilizarea LLM nu coboară niciodată portofelul sub această sumă, așa că o sesiune de chat intensă nu îți poate opri mașina virtuală. Dacă un interval de 10 minute tot nu poate fi plătit, mediul intră într-o **perioadă de grație** de 30 de minute (afișată în Hub și în aplicația desktop, cu ora opririi); o alimentare în acest timp plătește intervalul restant și anulează grația. După 30 de minute mediul este oprit cu snapshot — nu se pierde nimic.

## Creează unul [#creează-unul]

<Tabs items={['Hub', 'CLI', 'API']}>
<Tab value="Hub">

În [hub.codai.ro/projects](https://hub.codai.ro/projects) deschide un proiect → **Mediu nou**, alege **codai cloud (OpenStack)** sau **Hetzner**, o dimensiune, timpul de inactivitate (5–1440 de minute) și, opțional, un plafon lunar de buget în euro.

</Tab>
<Tab value="CLI">

```bash
codaid env create --project my-app --name ci-box --provider hetzner --size medium
codaid env status ci-box
```

</Tab>
<Tab value="API">

```bash
curl https://ai.codai.ro/v1/environments \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d '{ "project_id": "2b6d1c0e-4f3a-4b9c-8d7e-1a2b3c4d5e6f", "name": "ci-box",
        "provider": "hetzner", "size": "medium", "idle_timeout_minutes": 30, "budget_micro_eur": 20000000 }'
```

</Tab>
</Tabs>

Mediul pornește în starea `creating`. Worker-ul creează serverul (`creating` → `enrolling` când acesta are un IP public), cloud-init instalează `codaid` ca serviciu de sistem și îl înrolează cu un token generat de worker, iar mediul devine `running`. Pe ambii furnizori, în producție, asta a durat cam două-trei minute. La înrolare, daemonul unei mașini administrate primește propria cheie API, emisă pe proprietarul mediului; distrugerea mediului o revocă.

Această cheie de daemon este **limitată la propriul mediu**: poate apela inferența, poate conduce sesiunea partajată a unei sarcini pe care o rulează, își poate citi mediul (și proiectul lui) și poate lista numele secretelor propriului mediu — nimic altceva. Orice altă rută răspunde cu `403 key_scope_forbidden`, deci o mașină compromisă nu poate ajunge la celelalte medii, chei sau la portofelul tău.

| Pas | Limită de timp | La depășire |
| --- | --- | --- |
| Crearea serverului | 10 min | se reîncearcă; după 3 eșecuri consecutive ale furnizorului, mediul este parcat în `error` |
| Înrolarea daemonului | 15 min | `error`, motiv `enroll_timeout` — o înrolare întârziată îl readuce totuși la `running` |
| Pornirea din `stopped` | 10 min | `error` |

Worker-ul nu distruge niciodată o mașină pe o cale de eroare; decizia îți aparține.

Un mediu în `error` al cărui server încă există se taxează ca unul care rulează; după 30 de minute în `error`, worker-ul îl oprește cu snapshot, ca o mașină defectă să nu te taxeze în continuare.

## Ciclul de viață [#ciclul-de-viață]

| Acțiune | Permisă din | Trece în | Rol |
| --- | --- | --- | --- |
| `stop` | `running` | `stopping` → `stopped` | maintainer |
| `start` | `stopped` | `starting` → `running` | maintainer |
| `archive` | `stopped` | `archiving` → `archived` | maintainer |
| `destroy` | orice stare în afară de `destroying`/`destroyed` | `destroying` → `destroyed` | owner |

Orice altceva răspunde `409 invalid_transition`. Butoanele din hub, `codaid env start|stop|destroy` și `POST /v1/environments/{id}/{action}` trec toate prin același tabel de tranziții.

Ce fac oprirea și pornirea la fiecare furnizor:

| | Oprire | Pornire |
| --- | --- | --- |
| **OpenStack** | Shelve, apoi shelve-offload: id-ul serverului și IP-ul public se păstrează, resursele de calcul se eliberează. | Unshelve pe același server. |
| **Hetzner** | Se face un snapshot, apoi serverul este șters. | Se creează un server nou din snapshot. Snapshot-ul anterior se șterge după următoarea oprire. |

`archive` parcurge aceeași cale de oprire și parchează mașina în `archived`; singura ieșire din `archived` este `destroy`. `destroy` șterge serverul (iar pe Hetzner și snapshot-ul lui) și revocă cheia daemonului.

## Oprire automată la inactivitate [#oprire-automată-la-inactivitate]

Fiecare mediu are `idle_timeout_minutes` (implicit **30**, 5–1440; îl schimbi cu `PATCH /v1/environments/{id}` sau din hub). Worker-ul oprește un mediu administrat care rulează atunci când

```text
now − max(last_activity_at, started_at) > idle_timeout_minutes
```

cu motivul `idle`. Se consideră **activitate**:

- orice exec, terminal sau octet de flux de port de la un client atașat, prin relay;
- heartbeat-urile daemonului care raportează activitate `human` sau `agent` — un heartbeat `idle` nu contează niciodată;
- cererile pe care daemonul însuși le face către gateway cu cheia lui (de exemplu o buclă de agent care rulează în mașină);
- propriile tale cereri de start/stop/archive/destroy.

Traficul de preview venit de la terți contează doar ca octeți de flux de port, deci un preview public cu vizitatori ține mașina trează. Activitatea se înregistrează cel mult o dată pe minut.

Mașina poate fi oprită și din motive de facturare — `insufficient_funds`, `billing_error` sau `budget_exceeded`; vezi [facturarea](./billing.mdx). Motivul apare pe pagina mediului și este returnat ca `stateReason`.

## Prebuild-uri [#prebuild-uri]

Un proiect poate păstra un pool de până la **5** mașini pregătite, astfel încât un mediu nou pornește dintr-un snapshot deja încălzit, nu dintr-o instalare de la zero:

```bash
curl -X PATCH https://ai.codai.ro/v1/projects/$PROJECT_ID \
  -H "Authorization: Bearer $CODAI_API_KEY" -H 'content-type: application/json' \
  -d '{"prebuild_pool_size": 2, "prebuild_provider": "openstack", "prebuild_size": "small"}'
```

- Doar owner-ul proiectului sau un owner/admin al organizației îl poate seta. Worker-ul completează pool-ul în fiecare minut, rulează doar `onCreateCommand` și `updateContentCommand` din devcontainer, apoi **oprește** fiecare mașină.
- **Owner-ul proiectului plătește** compute-ul de încălzire și stocarea mașinilor oprite din pool ([tariful normal de stocare](./billing.mdx)). Pool-ul nu se completează cât timp portofelul owner-ului are mai puțin decât o oră dintr-o mașină de pool.
- `POST /v1/environments` cu același furnizor și aceeași dimensiune (și fără `slug`) revendică o mașină gata: proprietatea trece la tine, pornește imediat, iar răspunsul conține `prebuild_claimed: true`. De atunci plătești tu. Verificarea soldului pentru o oră se aplică în continuare.
- Mașinile nerevendicate din pool sunt vizibile doar owner-ului proiectului. Scăderea `prebuild_pool_size` sau schimbarea furnizorului/dimensiunii distruge mașinile nerevendicate în plus.

## În curând [#în-curând]

Mediile cu GPU și alegerea regiunii din hub nu sunt încă disponibile.
