Hello @3246
With the cloudron cli you can run e.g:
cloudron exec --app $APP -- fallocate -l 4M /app/data/4M
See cloudron exec --help:
Usage: cloudron exec [options] [cmd...]
Exec a command in an application
Options:
-t,--tty Allocate tty
--app <id/location> App id or location
-h, --help display help for command
Examples:
$ cloudron exec --app myapp # run an interactive shell
$ cloudron exec --app myapp ls # run command
$ cloudron exec --app myapp -- ls -l # use -- to indicate end of options
Was not sure if possible via the API but since I can debug the cloudron cli and see what it do I figured something out.
Send the to /api/v1/apps/$APPID/exec
# Command is an array `ls -lah /app/data` would become for each space ["ls", "-lah", "/app/data/"]
curl "https://my.$DOMAIN.$TLD/api/v1/apps/$APPID/exec" \
-H 'Authorization: Bearer $APITOKEN' \
-H 'content-type: application/json' \
--data-raw '["ls", "-lah", "/app/data/"]'
# this returns and id
{
"id": "a90bcfcec1d29f7a595638ea66c8ac0bb53b594047ac74fc80bf97f75fed0c19"
}
But getting the output of the executed command is. . . tricky.
Just look at the source code for the cli for this part:
const searchParams = new URLSearchParams({
rows: stdout.rows || 24,
columns: stdout.columns || 80,
access_token: token,
tty
});
const req = https.request({
hostname: adminFqdn,
path: `/api/v1/apps/${app.id}/exec/${execId}/start?${searchParams.toString()}`,
method: 'GET',
headers: {
'Connection': 'Upgrade',
'Upgrade': 'tcp'
},
rejectUnauthorized
}, function handler(res) {
if (res.statusCode === 403) exit('Unauthorized.'); // only admin or only owner (for docker addon)
exit('Could not upgrade connection to tcp. http status:', res.statusCode);
});
req.on('upgrade', function (resThatShouldNotBeUsed, socket /*, upgradeHead*/) {
// do not use res here! it's all socket from here on
socket.on('error', exit);
socket.setNoDelay(true);
socket.setKeepAlive(true);
if (tty) {
stdin.setRawMode(true);
stdin.pipe(socket, { end: false }); // the remote will close the connection
socket.pipe(stdout); // in tty mode, stdout/stderr is merged
socket.on('end', exitWithCode); // server closed the socket
} else { // create stdin process on demand
if (typeof stdin === 'function') stdin = stdin();
stdin.on('data', function (d) {
var buf = Buffer.alloc(4);
buf.writeUInt32BE(d.length, 0 /* offset */);
socket.write(buf);
socket.write(d);
});
stdin.on('end', function () {
var buf = Buffer.alloc(4);
buf.writeUInt32BE(0, 0 /* offset */);
socket.write(buf);
});
stdout.on('close', exitWithCode); // this is only emitted when stdout is a file and not the terminal
demuxStream(socket, stdout, process.stderr); // can get separate streams in non-tty mode
socket.on('end', function () { // server closed the socket
if (typeof stdin.end === 'function') stdin.end(); // required for this process to 'exit' cleanly. do not call exit() because writes may not have finished . the type check is required for when stdin: 'ignore' in execSync, not sure why
if (stdout !== process.stdout) stdout.end(); // for push stream
socket.end();
// process._getActiveHandles(); process._getActiveRequests();
if (stdout === process.stdout) setImmediate(exitWithCode); // otherwise, we rely on the 'close' event above
});
}
});
req.on('error', exit); // could not make a request
req.end(); // this makes the request
From a little debugging I got the API path:
/api/v1/apps/$APPID/exec/$EXECID/start?rows=21&columns=257&tty=false
But when CURL'ing this:
curl "https://my.$DOMAIN.TLD/api/v1/apps/$APPID/exec/$EXECID/start?rows=21&columns=257&tty=false" \
-H "Authorization: Bearer $APITOKEN" \
-H 'content-type: application/json'
{
"status": "Not Found",
"message": "exec requires TCP upgrade"
}
And I am not sure how to "TCP upgrade" the curl request.
I will ask the team.