# Medii de echipă (/ro/docs/environments/team)

Împarte un mediu cu echipa — roluri, cont Linux și interval de porturi per membru, repository partajat, lock de build și moduri de izolare.

Mai multe persoane (și agenții lor) pot lucra în același mediu. Fiecare membru primește în mașină propria identitate, astfel încât credențialele unei persoane nu pot fi citite de alta, iar daemonul coordonează lucrurile care nu trebuie să ruleze de două ori.

## Roluri [#roluri]

| Rol | Poate |
| --- | --- |
| `viewer` | Să vadă mediul, membrii, porturile și evenimentele; să se atașeze. Nu i se provizionează un cont Linux pentru SSH. |
| `developer` | Tot ce poate un viewer, plus să publice porturi până la `authenticated`, să listeze numele secretelor și să-și înregistreze cheia SSH. |
| `maintainer` | În plus: pornire, oprire, arhivare, redenumire, schimbarea timpului de inactivitate, adăugare/eliminare de membri, publicare de porturi `public`, setare și ștergere de secrete, generare de tokenuri de înrolare (BYO). |
| `owner` | În plus: distrugere, acordarea sau retragerea rolului `owner`. Un mediu păstrează mereu cel puțin un proprietar (`409 last_owner`). |

Cine creează mediul devine proprietarul lui. Proprietarii și administratorii organizației mediului au rol de `owner`; membrii obișnuiți ai organizației îl văd ca `viewer`. Oricine poate părăsi un mediu.

**Adaugă un membru** din hub (pagina mediului → *Membri* → e-mail + rol; persoana are nevoie de un cont codai cu acea adresă) sau cu `POST /v1/environments/{id}/members` și `{ "email": "ana@example.com", "role": "developer" }`.

## Identitate per membru [#identitate-per-membru]

Când daemonul rulează ca serviciu de sistem cu root, în modul `per-user` (`CODAI_SERVICE_SCOPE=system`, implicit pe mașinile administrate), fiecare membru primește:

| Ce | Detalii |
| --- | --- |
| Cont Linux | `u-` urmat de primele 8 caractere hex din id-ul tău de utilizator — același pe fiecare mașină. Creat la prima folosire (`useradd -m`, grupul `codai-dev`, home `0750`), afișat ca *Utilizator Linux* în hub. Comenzile de shell și terminalele rulează sub acest cont. |
| Identitate git | `~/.gitconfig` cu numele și e-mailul membrului. |
| Worktree | `~/work/<slug-proiect>` pe branch-ul implicit sau pe `u/<utilizator>` când acel branch e deja folosit de altcineva. Operațiile tale pe fișiere sunt limitate la worktree și la home. |
| Interval de porturi | Un bloc de 100 de porturi care începe la 30000, 30100, … Exportat comenzilor tale ca `PORT` (primul port) și `CODAI_PORT_RANGE_START`, `CODAI_PORT_RANGE_END`, `CODAI_PORT_RANGE`, ca serverele de dezvoltare a doi membri să nu se ciocnească. |
| Acces SSH | Lipește cheia publică la *Cheia ta SSH* (sau `PUT /v1/environments/{id}/members/me/ssh-key`). Daemonul o scrie în `~/.ssh/authorized_keys`, iar hub-ul afișează *Instalată*. Viewerii nu sunt provizionați. |

### Eliminarea unui membru [#eliminarea-unui-membru]

Când cineva este eliminat din mediu — sau retrogradat la `viewer` — daemonul îi dezactivează contul Linux în ordine, iar fiecare pas apare în evenimentele mediului (`member.offboard.<pas>`):

1. **locked** — contul este blocat și expirat, iar cheia SSH este ștearsă;
2. **killed** — sesiunile, procesele și containerul lui sunt oprite;
3. **archived** — commit-urile nepublicate nicăieri și modificările necomise sunt salvate în repository-ul comun ca `refs/archive/<utilizator>/<moment>/<branch>` (branch-ul lui nu este mutat; ref-urile de arhivă nu se șterg niciodată);
4. **deleted** — după 30 de zile, dacă nu a revenit, contul și home-ul sunt șterse.

Dacă este adăugat din nou în cele 30 de zile, contul este deblocat (**restored**), cu home-ul și worktree-urile intacte.

Dacă daemonul rulează fără root (identitate `shared` — implicită la o instalare BYO în scope de utilizator), toată lumea rulează sub același utilizator Unix, `codaid status` raportează `identity_unsafe: true`, iar agentul este instruit să trateze mașina ca nesigură pentru secrete. Folosește modul shared doar pe o mașină pe care lucrezi singur.

## Repository partajat [#repository-partajat]

Un proiect cu `repo_url` primește un singur repository bare partajat în `/srv/codai/repos/<slug>.git` (grupul `codai-dev`), adus o dată și folosit de worktree-ul fiecărui membru. Pentru a clona un repository privat, salvează un token ca secret de mediu `CODAI_GIT_TOKEN` (`PUT /v1/environments/{id}/secrets/CODAI_GIT_TOKEN`); daemonul îl transmite lui git printr-un helper askpass, niciodată în linia de comandă. Livrarea valorii secretului către daemon este în curs de lansare — până când gateway-ul tău o expune, daemonul clonează fără token (doar repository-uri publice).

## Lock de build [#lock-de-build]

Două build-uri simultane în același repository își strică reciproc rezultatele. O comandă de shell trimisă cu `"lock": "build"` ia un lock exclusiv la nivel de sistem de operare, fără așteptare:

- unul per repository per mediu în modul per-user (worktree-urile tuturor membrilor pentru același proiect împart un singur lock);
- eliberat automat dacă deținătorul se prăbușește;
- al doilea apelant primește imediat `{ "ok": false, "error": "lock_held: <holder>" }` în loc să aștepte.

Lock-urile deținute apar în starea din relay (`locks`), pe cardul din desktop ca pictogramă de lacăt și în `codaid status`. Cine este atașat — oameni și agenți — apare în aceleași locuri.

## Moduri de izolare [#moduri-de-izolare]

Alese per proiect (`isolation`), de cine administrează proiectul:

| Mod | Ce obții | Stare |
| --- | --- | --- |
| `shared_vm` (implicit) | O singură mașină; fiecare membru lucrează ca propriul utilizator Linux, cu propriul worktree și interval de porturi. | Disponibil |
| `container` | Câte un container podman rootless per membru (`codai-<slug>-<utilizator>`), construit din `devcontainer.json`-ul repository-ului (`.devcontainer/devcontainer.json` sau `.devcontainer.json`; altfel `mcr.microsoft.com/devcontainers/base:ubuntu-24.04`), rulând sub uid-ul membrului, cu repository-ul comun și worktree-ul montate la căile lor din host, pe rețeaua host-ului cu intervalul de porturi al membrului. Shell-urile și terminalele rulează în container; operațiile pe fișiere rămân pe calea din host. | Live (daemon per-user + `podman`) |
| `vm_per_dev` | Câte un mediu per dezvoltator, fixat pe el (`for_user_id`). Crearea unui al doilea răspunde `409 already_has_env` cu id-ul de refolosit; astfel de medii au exact un membru. | Disponibil |

Câmpuri `devcontainer.json` suportate în modul `container`: `image`, `build.{dockerfile,context,args,target}`, `forwardPorts`, `containerEnv`, `remoteEnv`, `containerUser`, `remoteUser`, `postCreateCommand` (o singură dată), `postStartCommand`, `mounts`, `runArgs`, `workspaceFolder`, `overrideCommand`. Docker Compose nu este suportat.

**Limite.** Fiecare container de membru poate folosi toate CPU-urile mașinii, dar nu mai mult (`--cpus`), 75 % din RAM fără swap suplimentar și 4096 de procese; rulează cu `no-new-privileges` și cu toate capabilitățile eliminate, cu excepția CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID, KILL și NET_BIND_SERVICE. Dacă mașina nu delegă controlerele cgroup de CPU/memorie, containerul pornește fără limita de CPU/memorie, iar daemonul înregistrează un avertisment.

**Lista permisă pentru `runArgs`.** Sunt acceptate doar `--add-host`, `-e`/`--env`, `--shm-size`, `--init` și `--label`. Orice altceva — de exemplu `--privileged`, `--network`, `--pid`, `--ipc`, `--cap-add`, `--security-opt`, `--device`, `--user` — oprește pornirea containerului cu o eroare care numește flag-ul; elimină-l din `runArgs`. `mounts` și `-v` pot indica doar în worktree-ul sau home-ul tău (sau un volum numit ori tmpfs).

În hub, proiectele `vm_per_dev` grupează mediile pe dezvoltator (*Mașina ta*, *Mașina lui …*), iar fiecare dezvoltator își creează propriul mediu din pagina proiectului.
