# Commands and files

Source: https://heaven.dev/docs/exec

Run a command inside a Computer, move files in and out, or download its whole disk, without SSH.

All four act inside the Computer as its login user, with the same access check as SSH.

- **Running a command and moving files** need `root` access or ownership. An API key needs `exec`, or `exec NAME` for one Computer.
- **Exporting** needs ownership.

Over SSH, the same things are `ssh NAME@heaven.dev CMD`, `scp` and `ssh export+NAME@heaven.dev`.

## Run a command

`POST /v1/computer/exec`

Runs one command inside a Computer as its login user, and returns its output and exit code. The command has 30 seconds and 1 MiB of output, or an hour when streamed.

The request returns `conflict` before anything runs if the Computer is paused or stopped, held for an unpaid pool, or its image couldn't be prepared. For a paused Computer, the error names `resume`.

If the request ends before the command does, the command is stopped. To keep a command running after the request ends, start it in the background: `nohup ./job >job.log 2>&1 &`.

### Stream the output

With `Accept: application/x-ndjson`, the output arrives as it's written, one line per chunk: `{"stdout":BASE64}`, `{"stderr":BASE64}`, then `{"exit_code":N}`. A streamed command can run for an hour, with no output limit. If the connection drops after output began, the last line is `{"error":MESSAGE}`, and the outcome is unknown.

### Send large input

Put the fields in the query and the input in the body, with a type such as `application/octet-stream`. Any body that isn't JSON is the command's input. The query must include `computer`, or the request returns `invalid`. The input streams, with no size limit.

> **A command is never run twice.** If the response is lost, the outcome is `unknown`. Check inside the Computer before running it again.

#### Body

- `computer` string *required*: A Computer: its name or ID.
- `command` string *required*: The command line, as you'd type it after `ssh NAME`.
- `stdin` string: Its standard input in base64, at most 1 MiB. Send more as the request body, with the fields in the query.

#### Response

- `output` base64: Standard output and error together, at most 1 MiB.
- `exit_code` integer: The command's exit status.

- An API key needs `exec` in its commands.

Request:

```sh
curl https://heaven.dev/v1/computer/exec \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  --json '{"computer": "my-site", "command": "uname -a"}'
```

200:

```json
{
  "version": 1,
  "data": {
    "output": "TGludXggbXktc2l0ZSA2Ljgu…",
    "exit_code": 0
  }
}
```

Stream:

```sh
curl https://heaven.dev/v1/computer/exec \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  -H "Accept: application/x-ndjson" \
  --json '{"computer": "my-site", "command": "apt-get update"}'
```

200 application/x-ndjson:

```json
{"stdout":"SGl0OjEgaHR0cDovL2FyY2hpdmUu…"}
{"stdout":"UmVhZGluZyBwYWNrYWdlIGxpc3Rz…"}
{"exit_code":0}
```

Unpack a large archive:

```sh
curl --data-binary @site.tar \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  'https://heaven.dev/v1/computer/exec?computer=my-site&command=tar%20xf%20-%20-C%20/srv'
```

## Download a file

`GET /v1/computer/exec?computer=NAME&download=PATH`

Returns the file as `application/octet-stream`, streamed, with no size limit. A missing file, a directory, or a file the login user can't read returns an error before any data is sent. A Computer that can't [run a command](https://heaven.dev/docs/exec#run) is refused the same way. An API key needs `exec` in its commands.

#### Query

- `computer` string *required*: The Computer's name or ID.
- `download` string *required*: The file's path. A relative path starts in the login user's home directory.

Request:

```sh
curl -f -o app.log \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  'https://heaven.dev/v1/computer/exec?computer=my-site&download=/var/log/app.log'
```

## Upload a file

`POST /v1/computer/exec?computer=NAME&upload=PATH`

Writes the request body to the path. Heaven writes it to a new file and moves it into place once it's complete, so an interrupted upload leaves the old file as it was. A Computer that can't [run a command](https://heaven.dev/docs/exec#run) is refused the same way. An API key needs `exec` in its commands.

#### Query

- `computer` string *required*: The Computer's name or ID.
- `upload` string *required*: The path to write the file to.
- `mode` string: Octal permissions, such as `0644`. Defaults to the replaced file's permissions, or `0644` for a new file.

Request:

```sh
curl -f --data-binary @app.tar \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  'https://heaven.dev/v1/computer/exec?computer=my-site&upload=/srv/app.tar&mode=0644'
```

200:

```json
{"version": 1, "data": {"path": "/srv/app.tar", "bytes": 1048576}}
```

## Export a disk

`GET /v1/computer/export`

Downloads a Computer's whole disk, running or stopped, as a raw ext4 image compressed with zstd.

It works even when the Computer is held for an unpaid pool, and it doesn't start the Computer.

- **Who can export:** the Computer's owner, the admins of the team that owns it, and the organization's billing owner and admins. Root access, a shared `admin` role and support access aren't enough. An API key needs `export` named in its commands. An SSH key limited to a tag or one Computer can't export.
- **Check that it finished.** A download is complete only if the response ends normally. If it was cut short and curl reports an error, delete the file and export again.
- **One at a time.** Only one export of a Computer runs at a time.

`unzstd` unpacks it to a sparse file about the size of the Computer's data, which you can mount or boot elsewhere.

#### Query

- `computer` string *required*: A Computer: its name or ID.

- An API key needs `export` in its commands.

Request:

```sh
curl -f -o my-site.img.zst \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  'https://heaven.dev/v1/computer/export?computer=my-site'
unzstd my-site.img.zst
```
