Various fixes

This commit is contained in:
crschnick 2025-03-31 13:39:15 +00:00
parent de1daa3e02
commit d46228911f
9 changed files with 84 additions and 31 deletions

View file

@ -350,7 +350,7 @@ public class BrowserFileTransferOperation {
}
outputStream = target.getFileSystem().openOutput(targetFile, fileSize);
transferFile(sourceFile, inputStream, outputStream, transferred, totalSize, start);
transferFile(sourceFile, inputStream, outputStream, transferred, totalSize, start, fileSize);
inputStream.transferTo(OutputStream.nullOutputStream());
} catch (Exception ex) {
// Mark progress as finished to reset any progress display
@ -409,7 +409,8 @@ public class BrowserFileTransferOperation {
OutputStream outputStream,
AtomicLong transferred,
AtomicLong total,
Instant start)
Instant start,
long expectedFileSize)
throws Exception {
// Initialize progress immediately prior to reading anything
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
@ -418,6 +419,7 @@ public class BrowserFileTransferOperation {
var exception = new AtomicReference<Exception>();
var thread = ThreadHelper.createPlatformThread("transfer", true, () -> {
try {
long readCount = 0;
var bs = (int) Math.min(DEFAULT_BUFFER_SIZE, sourceFile.getSize());
byte[] buffer = new byte[bs];
int read;
@ -434,11 +436,17 @@ public class BrowserFileTransferOperation {
outputStream.write(buffer, 0, read);
transferred.addAndGet(read);
updateProgress(
new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
readCount += read;
updateProgress(new BrowserTransferProgress(sourceFile.getName(), transferred.get(), total.get(), start));
}
var incomplete = readCount < expectedFileSize;
if (incomplete) {
throw new IOException("Source file " + sourceFile.getPath() + " input did end prematurely");
}
} catch (Exception ex) {
exception.set(ex);
killStreams.set(true);
}
});

View file

@ -9,6 +9,7 @@ import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.LabelGraphic;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
@ -66,19 +67,27 @@ public class AppLayoutModel {
}
public void selectBrowser() {
selected.setValue(entries.get(1));
PlatformThread.runLaterIfNeeded(() -> {
selected.setValue(entries.get(1));
});
}
public void selectSettings() {
selected.setValue(entries.get(2));
PlatformThread.runLaterIfNeeded(() -> {
selected.setValue(entries.get(2));
});
}
public void selectLicense() {
selected.setValue(entries.get(3));
PlatformThread.runLaterIfNeeded(() -> {
selected.setValue(entries.get(3));
});
}
public void selectConnections() {
selected.setValue(entries.getFirst());
PlatformThread.runLaterIfNeeded(() -> {
selected.setValue(entries.getFirst());
});
}
private List<Entry> createEntryList() {

View file

@ -13,6 +13,8 @@ import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.core.process.OsType;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@ -72,13 +74,12 @@ public class TermiusTerminalType implements ExternalTerminalType {
return;
}
var host = "localhost";
var b = SshLocalBridge.get();
var host = b.getHost();
var port = b.getPort();
var user = b.getUser();
var name = b.getIdentityKey().getFileName().toString();
Hyperlinks.open("termius://app/host-sharing#label=" + name + "&ip=" + host + "&port=" + port + "&username="
+ user + "&os=undefined");
var user = URLEncoder.encode(b.getUser(), StandardCharsets.UTF_8);
var name = b.getName();
Hyperlinks.open("termius://app/host-sharing#label=" + name + "&ip=" + host + "&port=" + port + "&username=" + user + "&os=undefined");
}
private boolean showInfo() throws IOException {
@ -91,7 +92,7 @@ public class TermiusTerminalType implements ExternalTerminalType {
var keyContent = Files.readString(b.getIdentityKey());
var activated =
AppI18n.get().getMarkdownDocumentation("app:termiusSetup").formatted(b.getIdentityKey(), keyContent);
var modal = ModalOverlay.of("termiusSetup", new MarkdownComp(activated, s -> s, false).prefWidth(450));
var modal = ModalOverlay.of("termiusSetup", new MarkdownComp(activated, s -> s, false).prefWidth(550));
modal.addButton(ModalButton.ok(() -> {
AppCache.update("termiusSetup", true);
}));

View file

@ -111,7 +111,7 @@ public class SecretRetrievalStrategyHelper {
}
map.put(AppI18n.observable("app.prompt"), new OptionsBuilder());
map.put(AppI18n.observable("app.password"), inPlace(inPlace));
map.put(AppI18n.observable("app.passwordManager"), passwordManager(passwordManager));
map.put(AppI18n.observable("app.externalPasswordManager"), passwordManager(passwordManager));
map.put(AppI18n.observable("app.customCommand"), customCommand(customCommand));
int offset = allowNone ? 0 : -1;

View file

@ -16,7 +16,9 @@ import lombok.Setter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Getter
public class SshLocalBridge {
@ -40,10 +42,14 @@ public class SshLocalBridge {
this.user = user;
}
private String getName() {
public String getName() {
return AppProperties.get().isStaging() ? "xpipe_ptb_bridge" : "xpipe_bridge";
}
public String getHost() {
return "127.0.0.1";
}
public Path getPubHostKey() {
return directory.resolve(getName() + "_host_key.pub");
}
@ -150,7 +156,8 @@ public class SshLocalBridge {
INSTANCE.getPubIdentityKey());
Files.writeString(config, content);
// INSTANCE.updateConfig();
// Write to local SSH client config
INSTANCE.updateConfig();
var exec = getSshd(sc);
var launchCommand = CommandBuilder.of()
@ -178,25 +185,42 @@ public class SshLocalBridge {
}
private void updateConfig() throws IOException {
var hostEntry = """
Host %s
HostName localhost
User "%s"
Port %s
IdentityFile "%s"
"""
.formatted(getName(), user, port, getIdentityKey());
var file = Path.of(System.getProperty("user.home"), ".ssh", "config");
if (!Files.exists(file)) {
Files.writeString(file, hostEntry);
return;
}
var content = Files.readString(file);
if (content.contains(getName())) {
return;
}
var updated = content + "\n\n"
+ """
var content = Files.readString(file).lines().collect(Collectors.joining("\n")) + "\n";
var pattern = Pattern.compile("""
Host %s
HostName localhost
User "%s"
Port %s
IdentityFile "%s"
"""
.formatted(getName(), port, user, getIdentityKey());
{4}HostName localhost
{4}User "(.+)"
{4}Port (\\d+)
{4}IdentityFile "(.+)"
""".formatted(getName()));
var matcher = pattern.matcher(content);
if (matcher.find()) {
var replaced = matcher.replaceFirst(Matcher.quoteReplacement(hostEntry));
Files.writeString(file, replaced);
return;
}
// Probably an invalid entry that did not match
if (content.contains("Host " + getName())) {
return;
}
var updated = content + "\n" + hostEntry + "\n";
Files.writeString(file, updated);
}

View file

@ -217,7 +217,7 @@ public class CommandBuilder {
}
public CommandBuilder addFile(FilePath s) {
return addFile(s.toString());
return addFile(s != null ? s.toString() : null);
}
public CommandBuilder addLiteral(String s) {

View file

@ -26,10 +26,12 @@ The password manager integration has been improved and made more robust:
## Other
- The SSH gateway implementation has been reworked so that you can now use local SSH keys and other identities for connections with gateways
- Generated connection names, e.g. VM names, will now automatically update on refresh when they were changed
- Various speed improvements for shell operations
- Various startup speed improvements
- The scripts context menu now shows the respective scripts icons instead of generic ones
- There is now built-in support to refresh an SSO openpubkey with the opkssh tool when needed
- When the SSH password is set to none, XPipe will no longer prompt for it anyway if the preferred auth failed
- The Windows application will now block the shutdown until save/sync has finished, preventing vault corruption caused by a sudden shutdown
- Add setting to disable HTTPs TLS verification for license activation API calls for cases where TLS traffic is decrypted in your organization
@ -41,3 +43,4 @@ The password manager integration has been improved and made more robust:
- Fix application restart after update not applying current workspace directory
- Fix custom service commands not launching properly with PowerShell as the local shell
- Fix update check being influenced by the local GitHub rate limiting
- Fix repeated file browser errors when remote system did not have the stat command

View file

@ -196,6 +196,13 @@ public interface SshIdentityStrategy {
+ " is in non-standard PuTTY Private Key format (.ppk), which is not supported by OpenSSH. Please export/convert it to a standard format like .pem via PuTTY"));
}
if (resolved.endsWith(".pub")) {
throw ErrorEvent.expected(
new IllegalArgumentException(
"Identity file " + resolved
+ " is marked to be a public key file, SSH authentication requires the private key"));
}
if ((parent.getOsType().equals(OsType.LINUX) || parent.getOsType().equals(OsType.MACOS))) {
// Try to preserve the same permission set
parent.command(CommandBuilder.of()

View file

@ -352,6 +352,7 @@ developerModeDescription=When enabled, you will have access to a variety of addi
editor=Editor
custom=Custom
passwordManager=Password manager
externalPasswordManager=External password manager
passwordManagerDescription=The password manager implementation to execute to fetch passwords.\n\nYou can then set the key to be retrieved whenever you set up a connection which requires a password.
passwordManagerCommandTest=Test password manager
passwordManagerCommandTestDescription=You can test here whether the output looks correct if you have set up a password manager command. The command should only output the password itself to stdout, no other formatting should be included in the output.