From 18846c62b21004d330c9780151acb263bccbe459 Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 27 Apr 2026 09:47:16 +0000 Subject: [PATCH] Rework container shells --- .../io/xpipe/app/ext/ContainerStoreState.java | 42 +++++++++++++++++++ .../service/ServiceRefreshHubProvider.java | 3 +- .../ext/system/podman/PodmanCommandView.java | 17 +++++--- .../system/podman/PodmanContainerStore.java | 10 ++++- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/ext/ContainerStoreState.java b/app/src/main/java/io/xpipe/app/ext/ContainerStoreState.java index 8dcbb8f1b..d1d553f25 100644 --- a/app/src/main/java/io/xpipe/app/ext/ContainerStoreState.java +++ b/app/src/main/java/io/xpipe/app/ext/ContainerStoreState.java @@ -1,7 +1,11 @@ package io.xpipe.app.ext; +import io.xpipe.app.process.ShellControl; +import io.xpipe.app.process.ShellDialect; +import io.xpipe.app.process.ShellDialects; import io.xpipe.app.process.ShellStoreState; +import io.xpipe.core.OsType; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -16,10 +20,47 @@ import lombok.extern.jackson.Jacksonized; @Jacksonized public class ContainerStoreState extends ShellStoreState { + public static ShellDialect findSuitableDialect(ShellControl sc) throws Exception { + if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) { + return sc.getOsType() == OsType.WINDOWS ? ShellDialects.CMD : ShellDialects.SH; + } + + if (sc.getOsType() != OsType.WINDOWS) { + if (sc.view().findProgram("bash").isPresent()) { + return ShellDialects.BASH; + } + + if (sc.view().findProgram("zsh").isPresent()) { + return ShellDialects.ZSH; + } + + return ShellDialects.SH; + } else { + if (sc.view().findProgram("pwsh").isPresent()) { + return ShellDialects.POWERSHELL_CORE; + } + + if (sc.view().findProgram("powershell").isPresent()) { + return ShellDialects.POWERSHELL; + } + + return ShellDialects.CMD; + } + } + String imageName; String containerState; + ShellDialect availableShellDialect; Boolean shellMissing; + public ShellDialect getEffectiveDialect(ShellControl sc) { + if (availableShellDialect != null) { + return availableShellDialect; + } + + return sc.getOsType() != OsType.WINDOWS ? ShellDialects.SH : ShellDialects.CMD; + } + @Override public DataStoreState mergeCopy(DataStoreState newer) { var n = (ContainerStoreState) newer; @@ -32,6 +73,7 @@ public class ContainerStoreState extends ShellStoreState { super.mergeBuilder(css, b); b.containerState(useNewer(containerState, css.getContainerState())); b.imageName(useNewer(imageName, css.getImageName())); + b.availableShellDialect(useNewer(availableShellDialect, css.getAvailableShellDialect())); b.shellMissing(useNewer(shellMissing, css.getShellMissing())); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceRefreshHubProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceRefreshHubProvider.java index 3a67e9ee2..c438a942c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceRefreshHubProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceRefreshHubProvider.java @@ -34,7 +34,8 @@ public class ServiceRefreshHubProvider @Override public boolean isApplicable(DataStoreEntryRef o) { - return o.getStore().allowManualServicesRefresh(); + return o.getStore().allowManualServicesRefresh() && DataStorage.get().getStoreChildren(o.get()).stream() + .noneMatch(e -> e.getStore() instanceof AbstractServiceGroupStore); } @Override diff --git a/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanCommandView.java b/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanCommandView.java index d0b926a6f..cc9350685 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanCommandView.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanCommandView.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Consumer; +import java.util.function.Function; public class PodmanCommandView extends CommandViewBase { @@ -23,6 +24,12 @@ public class PodmanCommandView extends CommandViewBase { } private static T convertException(T s) { + if (s instanceof ProcessOutputException pex) { + if (pex.getOutput().contains("OCI runtime exec failed")) { + ErrorEventFactory.preconfigure(ErrorEventFactory.fromThrowable(s).description("Container does not contain a usable shell")); + } + } + return ErrorEventFactory.expectedIfContains( s, "Error: unable to connect to Podman.", @@ -124,20 +131,20 @@ public class PodmanCommandView extends CommandViewBase { } } - public ShellControl exec(String container) { + public ShellControl exec(String container, Function dialect) { var sub = shellControl.subShell(); - sub.setDumbOpen(createOpenFunction(container, false)); - sub.setTerminalOpen(createOpenFunction(container, true)); + sub.setDumbOpen(createOpenFunction(container, dialect, false)); + sub.setTerminalOpen(createOpenFunction(container, dialect, true)); return sub.withExceptionConverter(PodmanCommandView::convertException); } - private ShellOpenFunction createOpenFunction(String containerName, boolean terminal) { + private ShellOpenFunction createOpenFunction(String containerName, Function dialect, boolean terminal) { return new ShellOpenFunction() { @Override public CommandBuilder prepareWithoutInitCommand() { return execCommand(terminal) .addQuoted(containerName) - .add(ShellDialects.SH.getLaunchCommand().loginCommand()); + .add(sc -> dialect.apply(sc).getLaunchCommand().loginCommand()); } @Override diff --git a/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerStore.java b/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerStore.java index 252c25524..67926f4f2 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerStore.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerStore.java @@ -136,9 +136,17 @@ public class PodmanContainerStore @Override public ShellControl control(ShellControl parent) throws Exception { refreshContainerState(getCmd().getStore().getHost().getStore().getOrStartSession()); - var pc = new PodmanCommandView(parent).container().exec(containerName); + var pc = new PodmanCommandView(parent).container().exec(containerName, getState()::getEffectiveDialect); pc.withSourceStore(PodmanContainerStore.this); pc.withShellStateInit(PodmanContainerStore.this); + pc + .onInit(sc -> { + var s = getState(); + if (s.getAvailableShellDialect() == null) { + setState(s.toBuilder().availableShellDialect(ContainerStoreState.findSuitableDialect(sc)).build()); + } + sc.setOriginalShellDialect(s.getEffectiveDialect(sc)); + }); pc.onStartupFail(throwable -> { if (throwable instanceof LicenseRequiredException) { return;