package com.litoralregas.vpnprovisioner.vps; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import com.litoralregas.vpnprovisioner.config.VpsSshProperties; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.Properties; @Service public class SshService { private final VpsSshProperties properties; public SshService(VpsSshProperties properties) { this.properties = properties; } public SshCommandResult executeOnConfiguredVps(String command) { validateConfiguredVps(); return execute( properties.getHost(), properties.getPort(), properties.getUsername(), properties.getPassword(), properties.getPrivateKeyPath(), command, properties.getConnectTimeoutMs(), properties.getCommandTimeoutMs() ); } public SshCommandResult execute( String host, int port, String username, String password, String privateKeyPath, String command, int connectTimeoutMs, int commandTimeoutMs ) { Session session = null; ChannelExec channel = null; try { JSch jsch = new JSch(); if (StringUtils.hasText(privateKeyPath)) { jsch.addIdentity(privateKeyPath); } session = jsch.getSession(username, host, port); if (StringUtils.hasText(password)) { session.setPassword(password); } Properties config = new Properties(); config.put("StrictHostKeyChecking", "no"); session.setConfig(config); session.connect(connectTimeoutMs); channel = (ChannelExec) session.openChannel("exec"); channel.setCommand(command); channel.setInputStream(null); ByteArrayOutputStream stdout = new ByteArrayOutputStream(); ByteArrayOutputStream stderr = new ByteArrayOutputStream(); channel.setOutputStream(stdout); channel.setErrStream(stderr); channel.connect(connectTimeoutMs); long deadline = System.currentTimeMillis() + commandTimeoutMs; while (!channel.isClosed()) { if (System.currentTimeMillis() > deadline) { throw new SshCommandException("SSH command timed out: " + command); } Thread.sleep(100); } return new SshCommandResult( channel.getExitStatus(), stdout.toString(StandardCharsets.UTF_8), stderr.toString(StandardCharsets.UTF_8) ); } catch (SshCommandException exception) { throw exception; } catch (Exception exception) { throw new SshCommandException("SSH command failed: " + command, exception); } finally { if (channel != null) { channel.disconnect(); } if (session != null) { session.disconnect(); } } } private void validateConfiguredVps() { if (!StringUtils.hasText(properties.getHost())) { throw new SshCommandException("VPS SSH host is not configured"); } if (!StringUtils.hasText(properties.getUsername())) { throw new SshCommandException("VPS SSH username is not configured"); } if (!StringUtils.hasText(properties.getPassword()) && !StringUtils.hasText(properties.getPrivateKeyPath())) { throw new SshCommandException("Either VPS SSH password or private key path must be configured"); } } }