mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-10-13 02:19:16 +00:00
Rework
This commit is contained in:
11 files changed
+338
-6
No files matched your search
@@ -13,6 +13,10 @@ public interface LauncherUrlProvider extends ActionProvider {
|
||||
.map(lup -> (LauncherUrlProvider) lup);
|
||||
}
|
||||
|
||||
default boolean executeInPlace() {
|
||||
return false;
|
||||
}
|
||||
|
||||
String getScheme();
|
||||
|
||||
AbstractAction createAction(URI uri) throws Exception;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.xpipe.app.beacon.api;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.beacon.BeaconClientException;
|
||||
import io.xpipe.app.beacon.BeaconInterface;
|
||||
import io.xpipe.app.util.ElevatedExec;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ElevatedExecCallbackExchange extends BeaconInterface<ElevatedExecCallbackExchange.Request> {
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/exec/elevated-callback";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresCompletedStartup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) throws Exception {
|
||||
var found = ElevatedExec.getElevatedExec(msg.getId());
|
||||
if (found.isEmpty()) {
|
||||
throw new BeaconClientException("No elevated exec found for " + msg.getId());
|
||||
} else {
|
||||
ElevatedExec.completeElevatedExec(msg.getId(), msg.getStdout(), msg.getStderr(), msg.getExitCode());
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresEnabledApi() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request {
|
||||
|
||||
@NonNull
|
||||
UUID id;
|
||||
|
||||
@NonNull
|
||||
String stdout;
|
||||
|
||||
@NonNull
|
||||
String stderr;
|
||||
|
||||
long exitCode;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response {}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.xpipe.app.beacon.api;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import io.xpipe.app.beacon.BeaconInterface;
|
||||
import io.xpipe.app.process.ShellDialect;
|
||||
import io.xpipe.app.process.ShellScript;
|
||||
import io.xpipe.app.util.ElevatedExec;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ElevatedExecQueryExchange extends BeaconInterface<ElevatedExecQueryExchange.Request> {
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/exec/elevated-query";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresCompletedStartup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) throws Exception {
|
||||
var found = ElevatedExec.getNextElevatedExec();
|
||||
if (found.isEmpty()) {
|
||||
return Response.builder().build();
|
||||
} else {
|
||||
return Response.builder().id(found.get().getId()).dialect(found.get().getDialect()).command(found.get().getCommand()).build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresEnabledApi() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Request {}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response {
|
||||
|
||||
UUID id;
|
||||
|
||||
ShellDialect dialect;
|
||||
|
||||
ShellScript command;
|
||||
}
|
||||
}
|
||||
@@ -56,12 +56,6 @@ public class AppOpenArguments {
|
||||
}
|
||||
});
|
||||
|
||||
// var requiresPlatform = all.stream().anyMatch(launcherInput -> launcherInput.requiresJavaFXPlatform());
|
||||
// if (requiresPlatform) {
|
||||
// OperationMode.switchToSyncIfPossible(OperationMode.GUI);
|
||||
// }
|
||||
// var hasGui = OperationMode.get() == OperationMode.GUI;
|
||||
|
||||
all.forEach(launcherInput -> {
|
||||
launcherInput.executeAsync();
|
||||
});
|
||||
@@ -72,6 +66,10 @@ public class AppOpenArguments {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
if (input.startsWith("'") && input.endsWith("'")) {
|
||||
input = input.substring(1, input.length() - 1);
|
||||
}
|
||||
|
||||
if (input.startsWith("\"") && input.endsWith("\"")) {
|
||||
input = input.substring(1, input.length() - 1);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ public class AppProperties {
|
||||
boolean isDaemon;
|
||||
boolean isCli;
|
||||
boolean printBeaconMessages;
|
||||
boolean elevatedExecMode;
|
||||
|
||||
@NonFinal
|
||||
@Getter(AccessLevel.PRIVATE)
|
||||
@@ -131,6 +132,9 @@ public class AppProperties {
|
||||
printBeaconMessages = Optional.ofNullable(System.getProperty(AppNames.propertyName("printBeaconMessages")))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
elevatedExecMode = Optional.ofNullable(System.getProperty(AppNames.propertyName("elevatedExecMode")))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
debugThreads = Optional.ofNullable(System.getProperty(AppNames.propertyName("debugThreads")))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
|
||||
@@ -196,6 +196,13 @@ public abstract class AppOperationMode {
|
||||
AppExtensionManager.init();
|
||||
AppI18n.init();
|
||||
AppPrefs.initLocal();
|
||||
|
||||
if (AppProperties.get().isElevatedExecMode()) {
|
||||
LocalShell.init();
|
||||
ElevatedExecClient.runLoop();
|
||||
return;
|
||||
}
|
||||
|
||||
AppInstance.init();
|
||||
// Initialize early to load in parallel
|
||||
PlatformInit.init(false);
|
||||
|
||||
@@ -115,4 +115,6 @@ public abstract class ProcModuleProvider {
|
||||
public abstract void importRdpFile(Path file) throws Exception;
|
||||
|
||||
public abstract BaseRegionBuilder<?, ?> createCustomSshAgentTest();
|
||||
|
||||
public abstract void runElevatedCommand(CommandBuilder b) throws Exception;
|
||||
}
|
||||
@@ -55,6 +55,22 @@ public class LocalShell {
|
||||
return local != null;
|
||||
}
|
||||
|
||||
public static ShellControl getLocalShell(ShellDialect sc) {
|
||||
if (sc == getDialect()) {
|
||||
return local;
|
||||
}
|
||||
|
||||
if (ShellDialects.isPowershell(sc)) {
|
||||
var pwsh = getLocalPowershell();
|
||||
if (pwsh.isEmpty()) {
|
||||
throw new IllegalStateException("Unable to run " + sc + " commands on this system");
|
||||
}
|
||||
return pwsh.get();
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to run " + sc + " commands on this system");
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized ShellControl init() throws Exception {
|
||||
if (local == null) {
|
||||
local = ProcModuleProvider.get().createLocalProcessControl(false).start();
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppInstallation;
|
||||
import io.xpipe.app.core.AppOpenArguments;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.mode.AppOperationMode;
|
||||
import io.xpipe.app.ext.ProcModuleProvider;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.process.*;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter
|
||||
public class ElevatedExec {
|
||||
|
||||
private static final List<ElevatedExec> execs = new ArrayList<>();
|
||||
private static boolean daemonRunning = false;
|
||||
|
||||
private static synchronized void startElevatedDaemonIfNeeded() throws Exception {
|
||||
if (daemonRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cmd = AppInstallation.ofCurrent().getDaemonExecutablePath();
|
||||
ProcModuleProvider.get().runElevatedCommand(CommandBuilder.of().addFile(cmd)
|
||||
.add("-Dio.xpipe.app.dataDir=\"" + AppProperties.get().getDataDir() + "\"")
|
||||
.add("-Dio.xpipe.app.elevatedExecMode=true"));
|
||||
|
||||
var exec = scheduleElevated(CommandBuilder.of().add(LocalShell.getDialect().getPrintWorkingDirectoryCommand()));
|
||||
var success = exec.getLatch().await(30, TimeUnit.SECONDS);
|
||||
if (!success) {
|
||||
throw ErrorEventFactory.expected(new IllegalStateException("Elevated daemon did not start up in time"));
|
||||
}
|
||||
daemonRunning = true;
|
||||
}
|
||||
|
||||
public static ElevatedExec runElevated(CommandBuilder b) throws Exception {
|
||||
startElevatedDaemonIfNeeded();
|
||||
|
||||
var exec = scheduleElevated(b);
|
||||
for (int i = 0; i < 30; i++) {
|
||||
if (exec.isFinished()) {
|
||||
return exec;
|
||||
}
|
||||
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
throw ErrorEventFactory.expected(new IllegalStateException("Elevated command did not complete in time"));
|
||||
}
|
||||
|
||||
private static ElevatedExec scheduleElevated(CommandBuilder b) throws Exception {
|
||||
var full = b.buildFull(LocalShell.getShell());
|
||||
var id = UUID.randomUUID();
|
||||
var exec = new ElevatedExec(id, LocalShell.getDialect(), ShellScript.of(full));
|
||||
synchronized (execs) {
|
||||
execs.add(exec);
|
||||
}
|
||||
return exec;
|
||||
}
|
||||
|
||||
public static void completeElevatedExec(UUID id, String stdout, String stderr, long exitCode) {
|
||||
var e = getElevatedExec(id);
|
||||
if (e.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.get().stdout = stdout;
|
||||
e.get().stderr = stderr;
|
||||
e.get().exitCode = exitCode;
|
||||
e.get().latch.countDown();
|
||||
}
|
||||
|
||||
public static Optional<ElevatedExec> getNextElevatedExec() {
|
||||
while (!AppOperationMode.isInShutdown()) {
|
||||
Optional<ElevatedExec> e;
|
||||
synchronized (execs) {
|
||||
e = execs.stream().filter(x -> x.isScheduled()).findFirst();
|
||||
}
|
||||
if (e.isPresent()) {
|
||||
e.get().getLatch().countDown();
|
||||
return e;
|
||||
} else {
|
||||
ThreadHelper.sleep(100);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static Optional<ElevatedExec> getElevatedExec(UUID id) {
|
||||
synchronized (execs) {
|
||||
var e = execs.stream().filter(x -> x.getId().equals(id)).findFirst();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
public ElevatedExec(UUID id, ShellDialect dialect, ShellScript command) {
|
||||
this.id = id;
|
||||
this.dialect = dialect;
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
private final UUID id;
|
||||
private final ShellDialect dialect;
|
||||
private final ShellScript command;
|
||||
private final CountDownLatch latch = new CountDownLatch(2);
|
||||
|
||||
private String stdout;
|
||||
private String stderr;
|
||||
private long exitCode;
|
||||
|
||||
public void throwIfFailed() throws ProcessOutputException {
|
||||
if (exitCode != 0) {
|
||||
throw ProcessOutputException.of(exitCode, stdout, stderr);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isScheduled() {
|
||||
return latch.getCount() == 2;
|
||||
}
|
||||
|
||||
private boolean isActive() {
|
||||
return latch.getCount() == 1;
|
||||
}
|
||||
|
||||
private boolean isFinished() {
|
||||
return latch.getCount() == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.beacon.BeaconClient;
|
||||
import io.xpipe.app.beacon.BeaconClientInformation;
|
||||
import io.xpipe.app.beacon.api.ElevatedExecCallbackExchange;
|
||||
import io.xpipe.app.beacon.api.ElevatedExecQueryExchange;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.process.LocalShell;
|
||||
|
||||
public class ElevatedExecClient {
|
||||
|
||||
public static void runLoop() {
|
||||
if (OsType.ofLocal() != OsType.WINDOWS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppBeaconServer.get() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var port = AppProperties.get().queryEffectiveBeaconPort(true).orElse(AppProperties.get().getDefaultBeaconPort());
|
||||
var client = BeaconClient.establishConnection(port, BeaconClientInformation.Daemon.builder().build(),
|
||||
AppProperties.get().getBeaconAuthFile());
|
||||
|
||||
while (true) {
|
||||
ElevatedExecQueryExchange.Response response = client.performRequest(ElevatedExecQueryExchange.Request.builder().build());
|
||||
if (response.getId() == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
var sc = LocalShell.getLocalShell(response.getDialect());
|
||||
var command = sc.command(response.getCommand());
|
||||
|
||||
var out = command.readStdoutAndStderr();
|
||||
client.performRequest(ElevatedExecCallbackExchange.Request.builder()
|
||||
.id(response.getId())
|
||||
.stdout(out[0])
|
||||
.stderr(out[1])
|
||||
.exitCode(command.getExitCode())
|
||||
.build());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ErrorEventFactory.fromThrowable(e).expected().omit().term().handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,6 +257,8 @@ open module io.xpipe.app {
|
||||
TerminalLaunchExchange,
|
||||
TerminalExternalLaunchExchange,
|
||||
SshLaunchExchange,
|
||||
ElevatedExecQueryExchange,
|
||||
ElevatedExecCallbackExchange,
|
||||
FsReadExchange,
|
||||
FsBlobExchange,
|
||||
FsWriteExchange,
|
||||
|
||||
Reference in new issue
Block a user