This commit is contained in:
crschnick 2024-10-21 19:44:15 +00:00
parent 323ca02a43
commit 4b15af2f17
25 changed files with 133 additions and 37 deletions

View file

@ -4,7 +4,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.api.ConnectionTerminalExchange; import io.xpipe.beacon.api.ConnectionTerminalExchange;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;

View file

@ -5,7 +5,7 @@ import io.xpipe.app.beacon.BeaconShellSession;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.api.ShellStartExchange; import io.xpipe.beacon.api.ShellStartExchange;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
import lombok.SneakyThrows; import lombok.SneakyThrows;

View file

@ -12,6 +12,7 @@ import io.xpipe.app.browser.session.BrowserAbstractSessionModel;
import io.xpipe.app.browser.session.BrowserSessionTab; import io.xpipe.app.browser.session.BrowserSessionTab;
import io.xpipe.app.comp.base.ModalOverlayComp; import io.xpipe.app.comp.base.ModalOverlayComp;
import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;

View file

@ -19,7 +19,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.FileReference; import io.xpipe.app.util.FileReference;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.FileSystemStore; import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;

View file

@ -15,7 +15,7 @@ import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;

View file

@ -4,12 +4,13 @@ import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.NetworkTunnelStore; import io.xpipe.core.store.NetworkTunnelStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StatefulDataStore; import io.xpipe.core.store.StatefulDataStore;
import io.xpipe.core.util.JacksonizedValue; import io.xpipe.core.util.JacksonizedValue;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.Optional;
@JsonTypeName("local") @JsonTypeName("local")
public class LocalStore extends JacksonizedValue public class LocalStore extends JacksonizedValue
implements NetworkTunnelStore, ShellStore, StatefulDataStore<ShellStoreState> { implements NetworkTunnelStore, ShellStore, StatefulDataStore<ShellStoreState> {

View file

@ -0,0 +1,66 @@
package io.xpipe.app.ext;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.Session;
import io.xpipe.core.store.SessionListener;
import lombok.Getter;
import java.util.function.Supplier;
@Getter
public class ShellSession extends Session {
private final Supplier<ShellControl> supplier;
private final ShellControl shellControl;
public ShellSession(SessionListener listener, Supplier<ShellControl> supplier) {
super(listener);
this.supplier = supplier;
this.shellControl = createControl();
}
public void start() throws Exception {
if (shellControl.isRunning()) {
return;
} else {
stop();
}
try {
shellControl.start();
} catch (Exception ex) {
stop();
throw ex;
}
}
private ShellControl createControl() {
var pc = supplier.get();
pc.onStartupFail(shellControl -> {
listener.onStateChange(false);
});
pc.onInit(shellControl -> {
listener.onStateChange(true);
});
pc.onKill(() -> {
listener.onStateChange(false);
});
// Listen for parent exit as onExit is called before exit is completed
// In case it is stuck, we would not get the right status otherwise
pc.getParentControl().ifPresent(p -> {
p.onExit(shellControl -> {
listener.onStateChange(false);
});
});
return pc;
}
public boolean isRunning() {
return shellControl.isRunning();
}
public void stop() throws Exception {
shellControl.close();
}
}

View file

@ -1,8 +1,34 @@
package io.xpipe.core.store; package io.xpipe.app.ext;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.*;
public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore<ShellValidationContext> { public interface ShellStore extends DataStore, FileSystemStore, ValidatableStore<ShellValidationContext>, SingletonSessionStore<ShellSession> {
default ShellControl getOrStartSession() throws Exception {
var session = getSession();
if (session != null) {
try {
session.getShellControl().command("echo hi").execute();
return session.getShellControl();
} catch (Exception e) {
stopSessionIfNeeded();
}
}
startSessionIfNeeded();
return getSession().getShellControl();
}
@Override
default ShellSession newSession() {
return new ShellSession(this, () -> control());
}
@Override
default Class<?> getSessionClass() {
return ShellSession.class;
}
@Override @Override
default FileSystem createFileSystem() { default FileSystem createFileSystem() {

View file

@ -12,7 +12,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.DataStoreCategoryChoiceComp; import io.xpipe.app.util.DataStoreCategoryChoiceComp;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.Property; import javafx.beans.property.Property;

View file

@ -14,9 +14,11 @@ public class HostHelper {
return p; return p;
} }
public static int findRandomOpenPortOnAllLocalInterfaces() throws IOException { public static int findRandomOpenPortOnAllLocalInterfaces() {
try (ServerSocket socket = new ServerSocket(0)) { try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort(); return socket.getLocalPort();
} catch (IOException e) {
return randomPort();
} }
} }

View file

@ -7,7 +7,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState; import io.xpipe.core.process.ShellTtyState;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import io.xpipe.core.store.ShellValidationContext; import io.xpipe.core.store.ShellValidationContext;
import io.xpipe.core.store.ValidationContext; import io.xpipe.core.store.ValidationContext;

View file

@ -12,7 +12,7 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import io.xpipe.core.store.ShellValidationContext; import io.xpipe.core.store.ShellValidationContext;
import javafx.application.Platform; import javafx.application.Platform;

View file

@ -1,7 +1,7 @@
package io.xpipe.core.process; package io.xpipe.core.process;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FilePath; import io.xpipe.core.store.FilePath;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StatefulDataStore; import io.xpipe.core.store.StatefulDataStore;
import io.xpipe.core.util.FailableConsumer; import io.xpipe.core.util.FailableConsumer;
import io.xpipe.core.util.FailableFunction; import io.xpipe.core.util.FailableFunction;
@ -18,6 +18,8 @@ import java.util.function.Function;
public interface ShellControl extends ProcessControl { public interface ShellControl extends ProcessControl {
Optional<ShellControl> getParentControl();
ShellTtyState getTtyState(); ShellTtyState getTtyState();
void setNonInteractive(); void setNonInteractive();
@ -32,9 +34,9 @@ public interface ShellControl extends ProcessControl {
void setWorkingDirectory(WorkingDirectoryFunction workingDirectory); void setWorkingDirectory(WorkingDirectoryFunction workingDirectory);
Optional<ShellStore> getSourceStore(); Optional<DataStore> getSourceStore();
ShellControl withSourceStore(ShellStore store); ShellControl withSourceStore(DataStore store);
List<ShellInitCommand> getInitCommands(); List<ShellInitCommand> getInitCommands();

View file

@ -17,7 +17,7 @@ public interface NetworkTunnelStore extends DataStore {
interface TunnelFunction { interface TunnelFunction {
NetworkTunnelSession create(int localPort, int remotePort); NetworkTunnelSession create(int localPort, int remotePort, String address);
} }
DataStore getNetworkParent(); DataStore getNetworkParent();
@ -57,7 +57,7 @@ public interface NetworkTunnelStore extends DataStore {
} }
} }
default NetworkTunnelSession sessionChain(int local, int remotePort) throws Exception { default NetworkTunnelSession sessionChain(int local, int remotePort, String address) throws Exception {
if (!isLocallyTunneable()) { if (!isLocallyTunneable()) {
throw new IllegalStateException( throw new IllegalStateException(
"Unable to create tunnel chain as one intermediate system does not support tunneling"); "Unable to create tunnel chain as one intermediate system does not support tunneling");
@ -75,7 +75,7 @@ public interface NetworkTunnelStore extends DataStore {
var currentLocalPort = isLast(current) ? local : randomPort(); var currentLocalPort = isLast(current) ? local : randomPort();
var currentRemotePort = var currentRemotePort =
sessions.isEmpty() ? remotePort : sessions.getLast().getLocalPort(); sessions.isEmpty() ? remotePort : sessions.getLast().getLocalPort();
var t = func.create(currentLocalPort, currentRemotePort); var t = func.create(currentLocalPort, currentRemotePort, current == this ? address : "localhost");
t.start(); t.start();
sessions.add(t); sessions.add(t);
counter.incrementAndGet(); counter.incrementAndGet();

View file

@ -38,7 +38,6 @@ public interface SingletonSessionStore<T extends Session>
default void startSessionIfNeeded() throws Exception { default void startSessionIfNeeded() throws Exception {
synchronized (this) { synchronized (this) {
var s = getSession(); var s = getSession();
setSessionEnabled(true);
if (s != null) { if (s != null) {
if (s.isRunning()) { if (s.isRunning()) {
return; return;
@ -50,9 +49,14 @@ public interface SingletonSessionStore<T extends Session>
try { try {
s = newSession(); s = newSession();
s.start(); if (s != null) {
setCache("session", s); setSessionEnabled(true);
onStateChange(true); s.start();
setCache("session", s);
onStateChange(true);
} else {
setSessionEnabled(false);
}
} catch (Exception ex) { } catch (Exception ex) {
onStateChange(false); onStateChange(false);
throw ex; throw ex;

View file

@ -1,8 +0,0 @@
package io.xpipe.core.util;
import io.xpipe.core.store.ShellStore;
public interface Proxyable {
ShellStore getProxy();
}

View file

@ -8,7 +8,7 @@ import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileSystemStore; import io.xpipe.core.store.FileSystemStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;

View file

@ -9,7 +9,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState; import io.xpipe.core.process.ShellTtyState;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import io.xpipe.ext.base.script.ScriptHierarchy; import io.xpipe.ext.base.script.ScriptHierarchy;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;

View file

@ -9,7 +9,7 @@ import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.ElevationFunction; import io.xpipe.core.process.ElevationFunction;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;

View file

@ -7,7 +7,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.ScanAlert; import io.xpipe.app.util.ScanAlert;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState; import io.xpipe.core.process.ShellTtyState;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;

View file

@ -45,7 +45,7 @@ public class ChgrpAction implements BranchAction {
.filter(e -> !e.getValue().equals("nohome") .filter(e -> !e.getValue().equals("nohome")
&& !e.getValue().equals("nogroup") && !e.getValue().equals("nogroup")
&& !e.getValue().equals("nobody") && !e.getValue().equals("nobody")
&& (e.getKey().equals(0) || e.getKey() >= 1000)) && (e.getKey().equals(0) || e.getKey() >= 900))
.map(e -> e.getValue()) .map(e -> e.getValue())
.map(s -> (LeafAction) new Chgrp(s)) .map(s -> (LeafAction) new Chgrp(s))
.toList(); .toList();

View file

@ -44,7 +44,7 @@ public class ChownAction implements BranchAction {
return model.getCache().getUsers().entrySet().stream() return model.getCache().getUsers().entrySet().stream()
.filter(e -> !e.getValue().equals("nohome") .filter(e -> !e.getValue().equals("nohome")
&& !e.getValue().equals("nobody") && !e.getValue().equals("nobody")
&& (e.getKey().equals(0) || e.getKey() >= 1000)) && (e.getKey().equals(0) || e.getKey() >= 900))
.map(e -> e.getValue()) .map(e -> e.getValue())
.map(s -> (LeafAction) new Chown(s)) .map(s -> (LeafAction) new Chown(s))
.toList(); .toList();

View file

@ -35,7 +35,7 @@ public abstract class AbstractServiceStore extends JacksonizedValue
public NetworkTunnelSession newSession() throws Exception { public NetworkTunnelSession newSession() throws Exception {
LicenseProvider.get().getFeature("services").throwIfUnsupported(); LicenseProvider.get().getFeature("services").throwIfUnsupported();
var l = localPort != null ? localPort : HostHelper.findRandomOpenPortOnAllLocalInterfaces(); var l = localPort != null ? localPort : HostHelper.findRandomOpenPortOnAllLocalInterfaces();
return getHost().getStore().sessionChain(l, remotePort); return getHost().getStore().sessionChain(l, remotePort, "localhost");
} }
@Override @Override

View file

@ -16,7 +16,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreFormatter; import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.store.ShellStore; import io.xpipe.app.ext.ShellStore;
import io.xpipe.ext.base.script.ScriptStore; import io.xpipe.ext.base.script.ScriptStore;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;

View file

@ -389,3 +389,5 @@ vmIdentity=Guest identity
vmIdentityDescription=The SSH identity authentication method to use for connecting if needed vmIdentityDescription=The SSH identity authentication method to use for connecting if needed
vmPort=Port vmPort=Port
vmPortDescription=The port to connect to via SSH vmPortDescription=The port to connect to via SSH
forwardAgent=Forward agent
forwardAgentDescription=Make SSH agent identities available on the remote system