# Terminal API

6 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## GET /projects/{projectId}/terminal-settings

A project's terminal settings

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `projectId` (path, string, required)

Response 200: OK (TerminalSettingsView)
  - `allowProductionFileWrites` (boolean, required): Let the file browser upload files in production environments of this project (default off: read-only).
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## PUT /projects/{projectId}/terminal-settings

Change a project's terminal settings

Admins and Owners only. `allowProductionFileWrites` lets the file browser upload files in the project's production environments (default off). Audited.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `projectId` (path, string, required)

Request body (application/json, required): TerminalSettingsView
  - `allowProductionFileWrites` (boolean, required): Let the file browser upload files in production environments of this project (default off: read-only).

Response 200: OK (TerminalSettingsView)
  - `allowProductionFileWrites` (boolean, required): Let the file browser upload files in production environments of this project (default off: read-only).
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## GET /servers/{serverId}/host-shell

Host shell availability of a server

Whether the caller can open a shell on the server itself, and why not: the setting is off (the default), the caller's account has no two-factor authentication, or the server is offline. Owners and Admins with a signed-in session only; a token gets 403 `session_required`.

**Shell.** `GET /servers/{serverId}/shell/ws` upgrades to a WebSocket with the protocol of the service terminal (see `getServiceTerminal`): the first message is JSON `{"type":"open","cols":80,"rows":24,"confirmProduction":false}` (no command: it is always the host's login shell). It answers 403 `host_shell_disabled` when the setting is off and 403 `two_factor_required` when the account has no 2FA, before the upgrade. A server that runs any service of a production environment needs `confirmProduction: true`. A person may hold 2 host shells; an idle session (no input for 15 minutes) is closed. Sessions are audited (`server.shell.open`, `server.shell.close`: who, server, duration); their I/O is never recorded.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serverId` (path, string, required)

Response 200: OK (HostShellInfoView)
  - `activeSessions` (integer (int64), required): Host shells the caller has open.
  - `available` (boolean, required): A session can be opened now: enabled, the server online and the caller's account has two-factor authentication.
  - `enabled` (boolean, required): An admin switched the host shell on for this server (default off).
  - `idleTimeoutSeconds` (integer (int64), required)
  - `maxSessions` (integer (int64), required)
  - `production` (boolean, required): A service of a production environment runs on this server: opening a shell needs confirmProduction.
  - `reason` (string): Why Available is false, in a sentence.
  - `serverId` (string, required)
  - `serverName` (string, required)
  - `twoFactorEnabled` (boolean, required): The caller's account has two-factor authentication.
Errors: 401 Unauthorized; 403 Forbidden (`session_required`, `host_shell_disabled`, `two_factor_required`); 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## PUT /servers/{serverId}/host-shell

Switch a server's host shell on or off

Owners and Admins only (signed-in session). Off by default. The host shell is a root shell on the server: switching it on is audited (`server.shell.enable`, `server.shell.disable`), and switching it off ends the sessions that are open.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serverId` (path, string, required)

Request body (application/json, required): HostShellSettingsView
  - `enabled` (boolean, required): Allow Owners and Admins with two-factor authentication to open a root shell on this server.

Response 200: OK (HostShellSettingsView)
  - `enabled` (boolean, required): Allow Owners and Admins with two-factor authentication to open a root shell on this server.
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/files

List a directory inside a replica container

Lists the immediate children of an absolute path (directories first, at most 2000 entries). The listing reads the container's own filesystem through the Docker API; nothing on the host is reachable. Requires the Owner, Admin or Developer role.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serviceId` (path, string, required)
- `replica` (query, integer (int64)): Replica index.
- `path` (query, string): Absolute directory path inside the container.

Response 200: OK (FileListing)
  - `entries` (FileEntry[] | null, required)
  - `path` (string, required): The resolved directory path.
  - `readOnlyReason` (string)
  - `replica` (integer (int64), required)
  - `truncated` (boolean, required): The directory has more entries than are listed.
  - `writable` (boolean, required): Uploads are allowed here.
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/terminal

Terminal and file browser availability

What the caller can do with the service's web terminal and file browser: its replicas, whether a terminal can be opened now, whether production needs a confirmation and whether the file browser may write. Requires the Owner, Admin or Developer role and `deployments:write`; a Viewer gets 403.

**Terminal.** `GET /services/{serviceId}/terminal/ws` upgrades to a WebSocket (browser session cookie, same origin). The first client message is JSON `{"type":"open","replica":0,"command":[…],"cols":80,"rows":24,"confirmProduction":false}`; binary messages are terminal input/output; JSON `{"type":"resize","cols":…,"rows":…}` resizes and `{"type":"close"}` ends the session. The server answers `opened`, `idle_warning`, `exit`, `error` and `closed` JSON messages. Production services need `confirmProduction: true`. A principal may hold 3 sessions; an idle session (no input for 15 minutes) is closed. Sessions are audited (`terminal.open`, `terminal.close`); their I/O is never recorded.

**Files.** `GET /services/{serviceId}/files/content?replica=&path=&preview=` streams a file (`preview=true` returns at most 1 MiB as text/plain); `POST /services/{serviceId}/files/upload?replica=&path=<dir>&name=<file>` stores the request body (Content-Length required, 50 MB at most). Uploads are refused in production unless an admin allowed writes for the project.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `serviceId` (path, string, required)

Response 200: OK (TerminalInfoView)
  - `activeSessions` (integer (int64), required): Terminal sessions the caller has open right now.
  - `available` (boolean, required): A terminal can be opened now.
  - `fileWrites` (FileWritePolicy, required)
  - `idleTimeoutSeconds` (integer (int64), required)
  - `maxSessions` (integer (int64), required)
  - `maxUploadBytes` (integer (int64), required)
  - `previewBytes` (integer (int64), required)
  - `production` (boolean, required): The service runs in a production environment: opening a terminal needs confirmProduction.
  - `reason` (string): Why not, when unavailable.
  - `replicas` (ReplicaView[] | null, required)
  - `serverName` (string)
  - `serviceId` (string, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error
