mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 15:10:23 +00:00
api rework [stage]
This commit is contained in:
parent
33577ca7c1
commit
c5608bd23c
23 changed files with 1010 additions and 136 deletions
33
app/src/main/java/io/xpipe/app/beacon/AppBeaconCache.java
Normal file
33
app/src/main/java/io/xpipe/app/beacon/AppBeaconCache.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
package io.xpipe.app.beacon;
|
||||
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Value
|
||||
public class AppBeaconCache {
|
||||
|
||||
Set<BeaconShellSession> shellSessions = new HashSet<>();
|
||||
Map<UUID, byte[]> savedBlobs = new ConcurrentHashMap<>();
|
||||
|
||||
public BeaconShellSession getShellSession(UUID uuid) throws BeaconClientException {
|
||||
var found = shellSessions.stream().filter(beaconShellSession -> beaconShellSession.getEntry().getUuid().equals(uuid)).findFirst();
|
||||
if (found.isEmpty()) {
|
||||
throw new BeaconClientException("No active shell session known for id " + uuid);
|
||||
}
|
||||
return found.get();
|
||||
}
|
||||
|
||||
public byte[] getBlob(UUID uuid) throws BeaconClientException {
|
||||
var found = savedBlobs.get(uuid);
|
||||
if (found == null) {
|
||||
throw new BeaconClientException("No saved data known for id " + uuid);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ public class AppBeaconServer {
|
|||
private final Set<BeaconSession> sessions = new HashSet<>();
|
||||
|
||||
@Getter
|
||||
private final Set<BeaconShellSession> shellSessions = new HashSet<>();
|
||||
private final AppBeaconCache cache = new AppBeaconCache();
|
||||
|
||||
@Getter
|
||||
private String localAuthSecret;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.app.beacon;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
|
@ -7,15 +9,13 @@ import io.xpipe.app.prefs.AppPrefs;
|
|||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.beacon.*;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class BeaconRequestHandler<T> implements HttpHandler {
|
||||
|
||||
|
@ -63,14 +63,19 @@ public class BeaconRequestHandler<T> implements HttpHandler {
|
|||
Object response;
|
||||
try {
|
||||
try (InputStream is = exchange.getRequestBody()) {
|
||||
var tree = JacksonMapper.getDefault().readTree(is);
|
||||
TrackEvent.trace("Parsed raw request:\n" + tree.toPrettyString());
|
||||
var emptyRequestClass =
|
||||
tree.isEmpty() && beaconInterface.getRequestClass().getDeclaredFields().length == 0;
|
||||
object = emptyRequestClass
|
||||
? createDefaultRequest(beaconInterface)
|
||||
: JacksonMapper.getDefault().treeToValue(tree, beaconInterface.getRequestClass());
|
||||
TrackEvent.trace("Parsed request object:\n" + object);
|
||||
var read = is.readAllBytes();
|
||||
var rawDataRequestClass = beaconInterface.getRequestClass().getDeclaredFields().length == 1 &&
|
||||
beaconInterface.getRequestClass().getDeclaredFields()[0].getType().equals(byte[].class);
|
||||
if (!new String(read, StandardCharsets.US_ASCII).trim().startsWith("{") && rawDataRequestClass) {
|
||||
object = createRawDataRequest(beaconInterface,read);
|
||||
} else {
|
||||
var tree = JacksonMapper.getDefault().readTree(read);
|
||||
TrackEvent.trace("Parsed raw request:\n" + tree.toPrettyString());
|
||||
var emptyRequestClass = tree.isEmpty() && beaconInterface.getRequestClass().getDeclaredFields().length == 0;
|
||||
object = emptyRequestClass ? createDefaultRequest(beaconInterface) : JacksonMapper.getDefault().treeToValue(tree,
|
||||
beaconInterface.getRequestClass());
|
||||
TrackEvent.trace("Parsed request object:\n" + object);
|
||||
}
|
||||
}
|
||||
response = beaconInterface.handle(exchange, object);
|
||||
} catch (BeaconClientException clientException) {
|
||||
|
@ -79,7 +84,7 @@ public class BeaconRequestHandler<T> implements HttpHandler {
|
|||
return;
|
||||
} catch (BeaconServerException serverException) {
|
||||
var cause = serverException.getCause() != null ? serverException.getCause() : serverException;
|
||||
ErrorEvent.fromThrowable(cause).handle();
|
||||
ErrorEvent.fromThrowable(cause).omit().expected().handle();
|
||||
writeError(exchange, new BeaconServerErrorResponse(cause), 500);
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
|
@ -93,7 +98,7 @@ public class BeaconRequestHandler<T> implements HttpHandler {
|
|||
}
|
||||
return;
|
||||
} catch (Throwable other) {
|
||||
ErrorEvent.fromThrowable(other).handle();
|
||||
ErrorEvent.fromThrowable(other).omit().expected().handle();
|
||||
writeError(exchange, new BeaconServerErrorResponse(other), 500);
|
||||
return;
|
||||
}
|
||||
|
@ -143,4 +148,20 @@ public class BeaconRequestHandler<T> implements HttpHandler {
|
|||
m.setAccessible(true);
|
||||
return (REQ) beaconInterface.getRequestClass().cast(m.invoke(b));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
private <REQ> REQ createRawDataRequest(BeaconInterface<?> beaconInterface, byte[] s) {
|
||||
var c = beaconInterface.getRequestClass().getDeclaredMethod("builder");
|
||||
c.setAccessible(true);
|
||||
|
||||
var b = c.invoke(null);
|
||||
var setMethod = Arrays.stream(b.getClass().getDeclaredMethods()).filter(method -> method.getParameterCount() == 1 &&
|
||||
method.getParameters()[0].getType().equals(byte[].class)).findFirst().orElseThrow();
|
||||
setMethod.invoke(b, (Object) s);
|
||||
|
||||
var m = b.getClass().getDeclaredMethod("build");
|
||||
m.setAccessible(true);
|
||||
return (REQ) beaconInterface.getRequestClass().cast(m.invoke(b));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.beacon;
|
||||
|
||||
import io.xpipe.beacon.BeaconClientInformation;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
|
|
|
@ -41,7 +41,7 @@ public class ConnectionQueryExchangeImpl extends ConnectionQueryExchange {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!typeMatcher.matcher(storeEntry.getProvider().getId()).matches()) {
|
||||
if (!typeMatcher.matcher(storeEntry.getProvider().getId().toLowerCase()).matches()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,14 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import io.xpipe.beacon.api.DaemonModeExchange;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.IOException;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.api.DaemonModeExchange;
|
||||
|
||||
public class DaemonModeExchangeImpl extends DaemonModeExchange {
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg)
|
||||
throws BeaconClientException {
|
||||
// Wait for startup
|
||||
while (OperationMode.get() == null) {
|
||||
ThreadHelper.sleep(100);
|
||||
}
|
||||
|
||||
var mode = OperationMode.map(msg.getMode());
|
||||
if (!mode.isSupported()) {
|
||||
throw new BeaconClientException("Unsupported mode: " + msg.getMode().getDisplayName() + ". Supported: "
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.beacon.api.FsBlobExchange;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FsBlobExchangeImpl extends FsBlobExchange {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Object handle(HttpExchange exchange, Request msg) {
|
||||
var id = UUID.randomUUID();
|
||||
AppBeaconServer.get().getCache().getSavedBlobs().put(id, msg.getPayload());
|
||||
return Response.builder().blob(id).build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.beacon.api.FsScriptExchange;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FsScriptExchangeImpl extends FsScriptExchange {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Object handle(HttpExchange exchange, Request msg) {
|
||||
var shell = AppBeaconServer.get().getCache().getShellSession(msg.getConnection());
|
||||
var data = new String(AppBeaconServer.get().getCache().getBlob(msg.getBlob()), StandardCharsets.UTF_8);
|
||||
var file = ScriptHelper.getExecScriptFile(shell.getControl());
|
||||
shell.getControl().getShellDialect().createScriptTextFileWriteCommand(shell.getControl(), data, file.toString());
|
||||
return Response.builder().path(file).build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.beacon.api.FsWriteExchange;
|
||||
import io.xpipe.core.store.ConnectionFileSystem;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
public class FsWriteExchangeImpl extends FsWriteExchange {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Object handle(HttpExchange exchange, Request msg) {
|
||||
var shell = AppBeaconServer.get().getCache().getShellSession(msg.getConnection());
|
||||
var data = AppBeaconServer.get().getCache().getBlob(msg.getBlob());
|
||||
var fs = new ConnectionFileSystem(shell.getControl());
|
||||
try (var os = fs.openOutput(msg.getPath().toString(), data.length)) {
|
||||
os.write(data);
|
||||
}
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
|
@ -1,15 +1,10 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import io.xpipe.beacon.api.ShellExecExchange;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.beacon.api.ShellExecExchange;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class ShellExecExchangeImpl extends ShellExecExchange {
|
||||
|
@ -17,20 +12,11 @@ public class ShellExecExchangeImpl extends ShellExecExchange {
|
|||
@Override
|
||||
@SneakyThrows
|
||||
public Object handle(HttpExchange exchange, Request msg) {
|
||||
var e = DataStorage.get()
|
||||
.getStoreEntryIfPresent(msg.getConnection())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
|
||||
var existing = AppBeaconServer.get().getShellSessions().stream()
|
||||
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
|
||||
.findFirst();
|
||||
if (existing.isEmpty()) {
|
||||
throw new BeaconClientException("No shell session active for connection");
|
||||
}
|
||||
|
||||
var existing = AppBeaconServer.get().getCache().getShellSession(msg.getConnection());
|
||||
AtomicReference<String> out = new AtomicReference<>();
|
||||
AtomicReference<String> err = new AtomicReference<>();
|
||||
long exitCode;
|
||||
try (var command = existing.get().getControl().command(msg.getCommand()).start()) {
|
||||
try (var command = existing.getControl().command(msg.getCommand()).start()) {
|
||||
command.accumulateStdout(s -> out.set(s));
|
||||
command.accumulateStderr(s -> err.set(s));
|
||||
exitCode = command.getExitCode();
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.beacon.BeaconShellSession;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import io.xpipe.beacon.api.ShellStartExchange;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ShellStartExchangeImpl extends ShellStartExchange {
|
||||
|
||||
@Override
|
||||
|
@ -25,7 +21,7 @@ public class ShellStartExchangeImpl extends ShellStartExchange {
|
|||
throw new BeaconClientException("Not a shell connection");
|
||||
}
|
||||
|
||||
var existing = AppBeaconServer.get().getShellSessions().stream()
|
||||
var existing = AppBeaconServer.get().getCache().getShellSessions().stream()
|
||||
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
|
||||
.findFirst();
|
||||
if (existing.isPresent()) {
|
||||
|
@ -33,7 +29,7 @@ public class ShellStartExchangeImpl extends ShellStartExchange {
|
|||
}
|
||||
|
||||
var control = s.control().start();
|
||||
AppBeaconServer.get().getShellSessions().add(new BeaconShellSession(e, control));
|
||||
AppBeaconServer.get().getCache().getShellSessions().add(new BeaconShellSession(e, control));
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,18 @@
|
|||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import io.xpipe.beacon.api.ShellStopExchange;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.beacon.api.ShellStopExchange;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ShellStopExchangeImpl extends ShellStopExchange {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Object handle(HttpExchange exchange, Request msg) {
|
||||
var e = DataStorage.get()
|
||||
.getStoreEntryIfPresent(msg.getConnection())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown connection"));
|
||||
var existing = AppBeaconServer.get().getShellSessions().stream()
|
||||
.filter(beaconShellSession -> beaconShellSession.getEntry().equals(e))
|
||||
.findFirst();
|
||||
if (existing.isPresent()) {
|
||||
existing.get().getControl().close();
|
||||
AppBeaconServer.get().getShellSessions().remove(existing.get());
|
||||
}
|
||||
var e = AppBeaconServer.get().getCache().getShellSession(msg.getConnection());
|
||||
e.getControl().close();
|
||||
AppBeaconServer.get().getCache().getShellSessions().remove(e);
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,9 @@ open module io.xpipe.app {
|
|||
DaemonStatusExchangeImpl,
|
||||
DaemonStopExchangeImpl,
|
||||
HandshakeExchangeImpl,
|
||||
DaemonModeExchangeImpl,
|
||||
DaemonModeExchangeImpl, FsBlobExchangeImpl,
|
||||
FsScriptExchangeImpl,
|
||||
FsWriteExchangeImpl,
|
||||
AskpassExchangeImpl,
|
||||
TerminalWaitExchangeImpl,
|
||||
TerminalLaunchExchangeImpl,
|
||||
|
|
|
@ -26,7 +26,7 @@ headingLevel: 2
|
|||
The XPipe API provides programmatic access to XPipe’s features.
|
||||
You can get started by either using this page as an API reference or alternatively import the OpenAPI definition file into your API client of choice:
|
||||
|
||||
<a href="/openapi.yaml" style="font-size: 20px">OpenAPI .yaml specification</a>
|
||||
<a download href="/openapi.yaml" style="font-size: 20px">OpenAPI .yaml specification</a>
|
||||
|
||||
The XPipe application will start up an HTTP server that can be used to send requests.
|
||||
You can change the port of it in the settings menu.
|
||||
|
@ -279,22 +279,22 @@ All matching is case insensitive.
|
|||
{
|
||||
"found": [
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"category": [
|
||||
"default"
|
||||
],
|
||||
"connection": [
|
||||
"name": [
|
||||
"local machine"
|
||||
],
|
||||
"type": "local"
|
||||
},
|
||||
{
|
||||
"uuid": "e1462ddc-9beb-484c-bd91-bb666027e300",
|
||||
"connection": "e1462ddc-9beb-484c-bd91-bb666027e300",
|
||||
"category": [
|
||||
"default",
|
||||
"category 1"
|
||||
],
|
||||
"connection": [
|
||||
"name": [
|
||||
"ssh system",
|
||||
"shell environments",
|
||||
"bash"
|
||||
|
@ -453,7 +453,7 @@ These errors will be returned with the HTTP return code 500.
|
|||
|
||||
```json
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -485,7 +485,7 @@ bearerAuth
|
|||
|
||||
```javascript
|
||||
const inputBody = '{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}';
|
||||
const headers = {
|
||||
'Content-Type':'application/json',
|
||||
|
@ -515,7 +515,7 @@ headers = {
|
|||
|
||||
data = """
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}
|
||||
"""
|
||||
r = requests.post('http://localhost:21723/shell/start', headers = headers, data = data)
|
||||
|
@ -534,7 +534,7 @@ var request = HttpRequest
|
|||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}
|
||||
"""))
|
||||
.build();
|
||||
|
@ -576,7 +576,7 @@ curl -X POST http://localhost:21723/shell/start \
|
|||
-H 'Content-Type: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}
|
||||
'
|
||||
|
||||
|
@ -599,7 +599,7 @@ If the shell is busy or stuck, you might have to work with timeouts to account f
|
|||
|
||||
```json
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -631,7 +631,7 @@ bearerAuth
|
|||
|
||||
```javascript
|
||||
const inputBody = '{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}';
|
||||
const headers = {
|
||||
'Content-Type':'application/json',
|
||||
|
@ -661,7 +661,7 @@ headers = {
|
|||
|
||||
data = """
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}
|
||||
"""
|
||||
r = requests.post('http://localhost:21723/shell/stop', headers = headers, data = data)
|
||||
|
@ -680,7 +680,7 @@ var request = HttpRequest
|
|||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}
|
||||
"""))
|
||||
.build();
|
||||
|
@ -722,7 +722,7 @@ curl -X POST http://localhost:21723/shell/stop \
|
|||
-H 'Content-Type: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b"
|
||||
}
|
||||
'
|
||||
|
||||
|
@ -746,7 +746,7 @@ However, if any other error occurs like the shell not responding or exiting unex
|
|||
|
||||
```json
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"command": "echo $USER"
|
||||
}
|
||||
```
|
||||
|
@ -799,7 +799,7 @@ bearerAuth
|
|||
|
||||
```javascript
|
||||
const inputBody = '{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"command": "echo $USER"
|
||||
}';
|
||||
const headers = {
|
||||
|
@ -832,7 +832,7 @@ headers = {
|
|||
|
||||
data = """
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"command": "echo $USER"
|
||||
}
|
||||
"""
|
||||
|
@ -853,7 +853,7 @@ var request = HttpRequest
|
|||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"command": "echo $USER"
|
||||
}
|
||||
"""))
|
||||
|
@ -897,7 +897,7 @@ curl -X POST http://localhost:21723/shell/exec \
|
|||
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
{
|
||||
"uuid": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"command": "echo $USER"
|
||||
}
|
||||
'
|
||||
|
@ -906,6 +906,474 @@ curl -X POST http://localhost:21723/shell/exec \
|
|||
|
||||
</details>
|
||||
|
||||
## Store a raw blob to be used later
|
||||
|
||||
<a id="opIdfsData"></a>
|
||||
|
||||
`POST /fs/blob`
|
||||
|
||||
Stores arbitrary binary data in a blob such that it can be used later on to for example write to a remote file.
|
||||
|
||||
This will return a uuid which can be used as a reference to the blob.
|
||||
You can also store normal text data in blobs if you intend to create text or shell script files with it.
|
||||
|
||||
> Body parameter
|
||||
|
||||
```yaml
|
||||
string
|
||||
|
||||
```
|
||||
|
||||
<h3 id="store-a-raw-blob-to-be-used-later-parameters">Parameters</h3>
|
||||
|
||||
|Name|In|Type|Required|Description|
|
||||
|---|---|---|---|---|
|
||||
|body|body|string(binary)|true|none|
|
||||
|
||||
> Example responses
|
||||
|
||||
> The operation was successful. The data was stored.
|
||||
|
||||
```json
|
||||
{
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="store-a-raw-blob-to-be-used-later-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The data was stored.|[FsBlobResponse](#schemafsblobresponse)|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
bearerAuth
|
||||
</aside>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Code samples</summary>
|
||||
|
||||
```javascript
|
||||
const inputBody = 'string';
|
||||
const headers = {
|
||||
'Content-Type':'application/octet-stream',
|
||||
'Accept':'application/json',
|
||||
'Authorization':'Bearer {access-token}'
|
||||
};
|
||||
|
||||
fetch('http://localhost:21723/fs/blob',
|
||||
{
|
||||
method: 'POST',
|
||||
body: inputBody,
|
||||
headers: headers
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.json();
|
||||
}).then(function(body) {
|
||||
console.log(body);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
import requests
|
||||
headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer {access-token}'
|
||||
}
|
||||
|
||||
data = """
|
||||
string
|
||||
"""
|
||||
r = requests.post('http://localhost:21723/fs/blob', headers = headers, data = data)
|
||||
|
||||
print(r.json())
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
var uri = URI.create("http://localhost:21723/fs/blob");
|
||||
var client = HttpClient.newHttpClient();
|
||||
var request = HttpRequest
|
||||
.newBuilder()
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.header("Accept", "application/json")
|
||||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
string
|
||||
"""))
|
||||
.build();
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println(response.statusCode());
|
||||
System.out.println(response.body());
|
||||
|
||||
```
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": []string{"application/octet-stream"},
|
||||
"Accept": []string{"application/json"},
|
||||
"Authorization": []string{"Bearer {access-token}"},
|
||||
}
|
||||
|
||||
data := bytes.NewBuffer([]byte{jsonReq})
|
||||
req, err := http.NewRequest("POST", "http://localhost:21723/fs/blob", data)
|
||||
req.Header = headers
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
// ...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```shell
|
||||
# You can also use wget
|
||||
curl -X POST http://localhost:21723/fs/blob \
|
||||
-H 'Content-Type: application/octet-stream' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
string
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Write a blob to a remote file
|
||||
|
||||
<a id="opIdfsWrite"></a>
|
||||
|
||||
`POST /fs/write`
|
||||
|
||||
Writes blob data to a file through an active shell session.
|
||||
|
||||
> Body parameter
|
||||
|
||||
```json
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304",
|
||||
"path": "/home/user/myfile.txt"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="write-a-blob-to-a-remote-file-parameters">Parameters</h3>
|
||||
|
||||
|Name|In|Type|Required|Description|
|
||||
|---|---|---|---|---|
|
||||
|body|body|[FsWriteRequest](#schemafswriterequest)|true|none|
|
||||
|
||||
<h3 id="write-a-blob-to-a-remote-file-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The file was written.|None|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
bearerAuth
|
||||
</aside>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Code samples</summary>
|
||||
|
||||
```javascript
|
||||
const inputBody = '{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304",
|
||||
"path": "/home/user/myfile.txt"
|
||||
}';
|
||||
const headers = {
|
||||
'Content-Type':'application/json',
|
||||
'Authorization':'Bearer {access-token}'
|
||||
};
|
||||
|
||||
fetch('http://localhost:21723/fs/write',
|
||||
{
|
||||
method: 'POST',
|
||||
body: inputBody,
|
||||
headers: headers
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.json();
|
||||
}).then(function(body) {
|
||||
console.log(body);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
import requests
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer {access-token}'
|
||||
}
|
||||
|
||||
data = """
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304",
|
||||
"path": "/home/user/myfile.txt"
|
||||
}
|
||||
"""
|
||||
r = requests.post('http://localhost:21723/fs/write', headers = headers, data = data)
|
||||
|
||||
print(r.json())
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
var uri = URI.create("http://localhost:21723/fs/write");
|
||||
var client = HttpClient.newHttpClient();
|
||||
var request = HttpRequest
|
||||
.newBuilder()
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304",
|
||||
"path": "/home/user/myfile.txt"
|
||||
}
|
||||
"""))
|
||||
.build();
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println(response.statusCode());
|
||||
System.out.println(response.body());
|
||||
|
||||
```
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": []string{"application/json"},
|
||||
"Authorization": []string{"Bearer {access-token}"},
|
||||
}
|
||||
|
||||
data := bytes.NewBuffer([]byte{jsonReq})
|
||||
req, err := http.NewRequest("POST", "http://localhost:21723/fs/write", data)
|
||||
req.Header = headers
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
// ...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```shell
|
||||
# You can also use wget
|
||||
curl -X POST http://localhost:21723/fs/write \
|
||||
-H 'Content-Type: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304",
|
||||
"path": "/home/user/myfile.txt"
|
||||
}
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Create a shell script file from a blob
|
||||
|
||||
<a id="opIdfsScript"></a>
|
||||
|
||||
`POST /fs/script`
|
||||
|
||||
Creates a shell script in the temporary directory of the file system that is access through the shell connection.
|
||||
|
||||
This can be used to run more complex commands on remote systems.
|
||||
|
||||
> Body parameter
|
||||
|
||||
```json
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="create-a-shell-script-file-from-a-blob-parameters">Parameters</h3>
|
||||
|
||||
|Name|In|Type|Required|Description|
|
||||
|---|---|---|---|---|
|
||||
|body|body|[FsScriptRequest](#schemafsscriptrequest)|true|none|
|
||||
|
||||
> Example responses
|
||||
|
||||
> The operation was successful. The script file was created.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "/tmp/exec-123.sh"
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="create-a-shell-script-file-from-a-blob-responses">Responses</h3>
|
||||
|
||||
|Status|Meaning|Description|Schema|
|
||||
|---|---|---|---|
|
||||
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|The operation was successful. The script file was created.|[FsScriptResponse](#schemafsscriptresponse)|
|
||||
|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request. Please check error message and your parameters.|None|
|
||||
|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Authorization failed. Please supply a `Bearer` token via the `Authorization` header.|None|
|
||||
|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Authorization failed. Please supply a valid `Bearer` token via the `Authorization` header.|None|
|
||||
|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|The requested resource could not be found.|None|
|
||||
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal error.|None|
|
||||
|
||||
<aside class="warning">
|
||||
To perform this operation, you must be authenticated by means of one of the following methods:
|
||||
bearerAuth
|
||||
</aside>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Code samples</summary>
|
||||
|
||||
```javascript
|
||||
const inputBody = '{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304"
|
||||
}';
|
||||
const headers = {
|
||||
'Content-Type':'application/json',
|
||||
'Accept':'application/json',
|
||||
'Authorization':'Bearer {access-token}'
|
||||
};
|
||||
|
||||
fetch('http://localhost:21723/fs/script',
|
||||
{
|
||||
method: 'POST',
|
||||
body: inputBody,
|
||||
headers: headers
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.json();
|
||||
}).then(function(body) {
|
||||
console.log(body);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
import requests
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer {access-token}'
|
||||
}
|
||||
|
||||
data = """
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304"
|
||||
}
|
||||
"""
|
||||
r = requests.post('http://localhost:21723/fs/script', headers = headers, data = data)
|
||||
|
||||
print(r.json())
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
var uri = URI.create("http://localhost:21723/fs/script");
|
||||
var client = HttpClient.newHttpClient();
|
||||
var request = HttpRequest
|
||||
.newBuilder()
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.header("Authorization", "Bearer {access-token}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("""
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304"
|
||||
}
|
||||
"""))
|
||||
.build();
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println(response.statusCode());
|
||||
System.out.println(response.body());
|
||||
|
||||
```
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
headers := map[string][]string{
|
||||
"Content-Type": []string{"application/json"},
|
||||
"Accept": []string{"application/json"},
|
||||
"Authorization": []string{"Bearer {access-token}"},
|
||||
}
|
||||
|
||||
data := bytes.NewBuffer([]byte{jsonReq})
|
||||
req, err := http.NewRequest("POST", "http://localhost:21723/fs/script", data)
|
||||
req.Header = headers
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
// ...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```shell
|
||||
# You can also use wget
|
||||
curl -X POST http://localhost:21723/fs/script \
|
||||
-H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer {access-token}' \
|
||||
--data '
|
||||
{
|
||||
"connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
|
||||
"blob": "854afc45-eadc-49a0-a45d-9fb76a484304"
|
||||
}
|
||||
'
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
# Schemas
|
||||
|
||||
<h2 id="tocS_ShellStartRequest">ShellStartRequest</h2>
|
||||
|
@ -994,6 +1462,92 @@ curl -X POST http://localhost:21723/shell/exec \
|
|||
|stdout|string|true|none|The stdout output of the command|
|
||||
|stderr|string|true|none|The stderr output of the command|
|
||||
|
||||
<h2 id="tocS_FsBlobResponse">FsBlobResponse</h2>
|
||||
|
||||
<a id="schemafsblobresponse"></a>
|
||||
<a id="schema_FsBlobResponse"></a>
|
||||
<a id="tocSfsblobresponse"></a>
|
||||
<a id="tocsfsblobresponse"></a>
|
||||
|
||||
```json
|
||||
{
|
||||
"blob": "string"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|blob|string|true|none|The data uuid|
|
||||
|
||||
<h2 id="tocS_FsWriteRequest">FsWriteRequest</h2>
|
||||
|
||||
<a id="schemafswriterequest"></a>
|
||||
<a id="schema_FsWriteRequest"></a>
|
||||
<a id="tocSfswriterequest"></a>
|
||||
<a id="tocsfswriterequest"></a>
|
||||
|
||||
```json
|
||||
{
|
||||
"connection": "string",
|
||||
"blob": "string",
|
||||
"path": "string"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|connection|string|true|none|The connection uuid|
|
||||
|blob|string|true|none|The blob uuid|
|
||||
|path|string|true|none|The target filepath|
|
||||
|
||||
<h2 id="tocS_FsScriptRequest">FsScriptRequest</h2>
|
||||
|
||||
<a id="schemafsscriptrequest"></a>
|
||||
<a id="schema_FsScriptRequest"></a>
|
||||
<a id="tocSfsscriptrequest"></a>
|
||||
<a id="tocsfsscriptrequest"></a>
|
||||
|
||||
```json
|
||||
{
|
||||
"connection": "string",
|
||||
"blob": "string"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|connection|string|true|none|The connection uuid|
|
||||
|blob|string|true|none|The blob uuid|
|
||||
|
||||
<h2 id="tocS_FsScriptResponse">FsScriptResponse</h2>
|
||||
|
||||
<a id="schemafsscriptresponse"></a>
|
||||
<a id="schema_FsScriptResponse"></a>
|
||||
<a id="tocSfsscriptresponse"></a>
|
||||
<a id="tocsfsscriptresponse"></a>
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "string"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|path|string|true|none|The generated script file path|
|
||||
|
||||
<h2 id="tocS_ConnectionQueryRequest">ConnectionQueryRequest</h2>
|
||||
|
||||
<a id="schemaconnectionqueryrequest"></a>
|
||||
|
@ -1029,11 +1583,11 @@ curl -X POST http://localhost:21723/shell/exec \
|
|||
{
|
||||
"found": [
|
||||
{
|
||||
"uuid": "string",
|
||||
"connection": "string",
|
||||
"category": [
|
||||
"string"
|
||||
],
|
||||
"connection": [
|
||||
"name": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
|
@ -1048,9 +1602,9 @@ curl -X POST http://localhost:21723/shell/exec \
|
|||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|found|[object]|true|none|The found connections|
|
||||
|» uuid|string|true|none|The unique id of the connection|
|
||||
|» connection|string|true|none|The unique id of the connection|
|
||||
|» category|[string]|true|none|The full category path as an array|
|
||||
|» connection|[string]|true|none|The full connection name path as an array|
|
||||
|» name|[string]|true|none|The full connection name path as an array|
|
||||
|» type|string|true|none|The type identifier of the connection|
|
||||
|
||||
<h2 id="tocS_HandshakeRequest">HandshakeRequest</h2>
|
||||
|
@ -1117,10 +1671,6 @@ curl -X POST http://localhost:21723/shell/exec \
|
|||
|
||||
<h3>Properties</h3>
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|type|string|true|none|none|
|
||||
|
||||
oneOf
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|
@ -1152,18 +1702,10 @@ API key authentication
|
|||
|
||||
<h3>Properties</h3>
|
||||
|
||||
allOf - discriminator: AuthMethod.type
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|*anonymous*|[AuthMethod](#schemaauthmethod)|false|none|none|
|
||||
|
||||
and
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|*anonymous*|object|false|none|none|
|
||||
|» key|string|true|none|The API key|
|
||||
|type|string|true|none|none|
|
||||
|key|string|true|none|The API key|
|
||||
|
||||
<h2 id="tocS_Local">Local</h2>
|
||||
|
||||
|
@ -1175,7 +1717,6 @@ and
|
|||
```json
|
||||
{
|
||||
"type": "string",
|
||||
"key": "string",
|
||||
"authFileContent": "string"
|
||||
}
|
||||
|
||||
|
@ -1185,18 +1726,10 @@ Authentication method for local applications. Uses file system access as proof o
|
|||
|
||||
<h3>Properties</h3>
|
||||
|
||||
allOf - discriminator: AuthMethod.type
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|*anonymous*|[AuthMethod](#schemaauthmethod)|false|none|none|
|
||||
|
||||
and
|
||||
|
||||
|Name|Type|Required|Restrictions|Description|
|
||||
|---|---|---|---|---|
|
||||
|*anonymous*|object|false|none|none|
|
||||
|» authFileContent|string|true|none|The contents of the local file $TEMP/xpipe_auth. This file is automatically generated when XPipe starts.|
|
||||
|type|string|true|none|none|
|
||||
|authFileContent|string|true|none|The contents of the local file $TEMP/xpipe_auth. This file is automatically generated when XPipe starts.|
|
||||
|
||||
<h2 id="tocS_ClientInformation">ClientInformation</h2>
|
||||
|
||||
|
|
32
beacon/src/main/java/io/xpipe/beacon/api/FsBlobExchange.java
Normal file
32
beacon/src/main/java/io/xpipe/beacon/api/FsBlobExchange.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
package io.xpipe.beacon.api;
|
||||
|
||||
import io.xpipe.beacon.BeaconInterface;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FsBlobExchange extends BeaconInterface<FsBlobExchange.Request> {
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/fs/blob";
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request {
|
||||
byte @NonNull [] payload;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response {
|
||||
@NonNull
|
||||
UUID blob;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package io.xpipe.beacon.api;
|
||||
|
||||
import io.xpipe.beacon.BeaconInterface;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FsScriptExchange extends BeaconInterface<FsScriptExchange.Request> {
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/fs/script";
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request {
|
||||
@NonNull
|
||||
UUID connection;
|
||||
@NonNull
|
||||
UUID blob;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response {
|
||||
@NonNull
|
||||
FilePath path;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package io.xpipe.beacon.api;
|
||||
|
||||
import io.xpipe.beacon.BeaconInterface;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FsWriteExchange extends BeaconInterface<FsWriteExchange.Request> {
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/fs/write";
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request {
|
||||
@NonNull
|
||||
UUID connection;
|
||||
@NonNull
|
||||
UUID blob;
|
||||
@NonNull
|
||||
FilePath path;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response {}
|
||||
}
|
|
@ -18,12 +18,8 @@ public class ConnectionFileSystem implements FileSystem {
|
|||
@JsonIgnore
|
||||
protected final ShellControl shellControl;
|
||||
|
||||
@JsonIgnore
|
||||
protected final ShellStore store;
|
||||
|
||||
public ConnectionFileSystem(ShellControl shellControl, ShellStore store) {
|
||||
public ConnectionFileSystem(ShellControl shellControl) {
|
||||
this.shellControl = shellControl;
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -32,11 +28,6 @@ public class ConnectionFileSystem implements FileSystem {
|
|||
shellControl.getShellDialect().queryFileSize(shellControl, file).readStdoutOrThrow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemStore getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ShellControl> getShell() {
|
||||
return Optional.of(shellControl);
|
||||
|
|
|
@ -20,8 +20,6 @@ public interface FileSystem extends Closeable, AutoCloseable {
|
|||
|
||||
long getFileSize(String file) throws Exception;
|
||||
|
||||
FileSystemStore getStore();
|
||||
|
||||
Optional<ShellControl> getShell();
|
||||
|
||||
FileSystem open() throws Exception;
|
||||
|
|
|
@ -11,7 +11,7 @@ public interface ShellStore extends DataStore, LaunchableStore, FileSystemStore,
|
|||
|
||||
@Override
|
||||
default FileSystem createFileSystem() {
|
||||
return new ConnectionFileSystem(control(), this);
|
||||
return new ConnectionFileSystem(control());
|
||||
}
|
||||
|
||||
default ProcessControl prepareLaunchCommand() {
|
||||
|
|
|
@ -16,6 +16,7 @@ import io.xpipe.core.dialog.HeaderElement;
|
|||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellDialect;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.store.StorePath;
|
||||
|
||||
|
@ -44,6 +45,12 @@ public class CoreJacksonModule extends SimpleModule {
|
|||
context.registerSubtypes(new NamedType(t.getClass()));
|
||||
}
|
||||
|
||||
addSerializer(FilePath.class, new FilePathSerializer());
|
||||
addDeserializer(FilePath.class, new FilePathDeserializer());
|
||||
|
||||
addSerializer(StorePath.class, new StorePathSerializer());
|
||||
addDeserializer(StorePath.class, new StorePathDeserializer());
|
||||
|
||||
addSerializer(Charset.class, new CharsetSerializer());
|
||||
addDeserializer(Charset.class, new CharsetDeserializer());
|
||||
|
||||
|
@ -88,6 +95,22 @@ public class CoreJacksonModule extends SimpleModule {
|
|||
}
|
||||
}
|
||||
|
||||
public static class FilePathSerializer extends JsonSerializer<FilePath> {
|
||||
|
||||
@Override
|
||||
public void serialize(FilePath value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
|
||||
jgen.writeString(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static class FilePathDeserializer extends JsonDeserializer<FilePath> {
|
||||
|
||||
@Override
|
||||
public FilePath deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return new FilePath(p.getValueAsString());
|
||||
}
|
||||
}
|
||||
|
||||
public static class CharsetSerializer extends JsonSerializer<Charset> {
|
||||
|
||||
@Override
|
||||
|
|
149
openapi.yaml
149
openapi.yaml
|
@ -228,6 +228,111 @@ paths:
|
|||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
/fs/blob:
|
||||
post:
|
||||
summary: Store a raw blob to be used later
|
||||
description: |
|
||||
Stores arbitrary binary data in a blob such that it can be used later on to for example write to a remote file.
|
||||
|
||||
This will return a uuid which can be used as a reference to the blob.
|
||||
You can also store normal text data in blobs if you intend to create text or shell script files with it.
|
||||
operationId: fsData
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
responses:
|
||||
'200':
|
||||
description: The operation was successful. The data was stored.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FsBlobResponse'
|
||||
examples:
|
||||
success:
|
||||
summary: Success
|
||||
value: { "blob": "854afc45-eadc-49a0-a45d-9fb76a484304" }
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
/fs/write:
|
||||
post:
|
||||
summary: Write a blob to a remote file
|
||||
description: |
|
||||
Writes blob data to a file through an active shell session.
|
||||
operationId: fsWrite
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FsWriteRequest'
|
||||
examples:
|
||||
simple:
|
||||
summary: Write simple file
|
||||
value: { "connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b", "blob": "854afc45-eadc-49a0-a45d-9fb76a484304", "path": "/home/user/myfile.txt" }
|
||||
responses:
|
||||
'200':
|
||||
description: The operation was successful. The file was written.
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
/fs/script:
|
||||
post:
|
||||
summary: Create a shell script file from a blob
|
||||
description: |
|
||||
Creates a shell script in the temporary directory of the file system that is access through the shell connection.
|
||||
|
||||
This can be used to run more complex commands on remote systems.
|
||||
operationId: fsScript
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FsScriptRequest'
|
||||
examples:
|
||||
standard:
|
||||
summary: Standard write
|
||||
value: { "connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b", "blob": "854afc45-eadc-49a0-a45d-9fb76a484304" }
|
||||
responses:
|
||||
'200':
|
||||
description: The operation was successful. The script file was created.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FsScriptResponse'
|
||||
examples:
|
||||
success:
|
||||
summary: Success
|
||||
value: { "path": "/tmp/exec-123.sh" }
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'500':
|
||||
$ref: '#/components/responses/InternalServerError'
|
||||
components:
|
||||
schemas:
|
||||
ShellStartRequest:
|
||||
|
@ -274,6 +379,50 @@ components:
|
|||
- exitCode
|
||||
- stdout
|
||||
- stderr
|
||||
FsBlobResponse:
|
||||
type: object
|
||||
properties:
|
||||
blob:
|
||||
type: string
|
||||
description: The data uuid
|
||||
required:
|
||||
- blob
|
||||
FsWriteRequest:
|
||||
type: object
|
||||
properties:
|
||||
connection:
|
||||
type: string
|
||||
description: The connection uuid
|
||||
blob:
|
||||
type: string
|
||||
description: The blob uuid
|
||||
path:
|
||||
type: string
|
||||
description: The target filepath
|
||||
required:
|
||||
- connection
|
||||
- blob
|
||||
- path
|
||||
FsScriptRequest:
|
||||
type: object
|
||||
properties:
|
||||
connection:
|
||||
type: string
|
||||
description: The connection uuid
|
||||
blob:
|
||||
type: string
|
||||
description: The blob uuid
|
||||
required:
|
||||
- connection
|
||||
- blob
|
||||
FsScriptResponse:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
description: The generated script file path
|
||||
required:
|
||||
- path
|
||||
ConnectionQueryRequest:
|
||||
type: object
|
||||
properties:
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
10.0-7
|
||||
10.0-9
|
||||
|
|
Loading…
Reference in a new issue