Files
portal-api/app/Services/VsphereHostSshService.php
T

246 lines
8.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services;
use Illuminate\Support\Facades\Log;
/**
* VsphereHostSshService
*
* Executes pre-deployed PowerShell scripts on the vSphere Windows host
* via native SSH (sshpass). No phpseclib dependency.
*
* Scripts in C:/CLISave/ accept 5 positional params:
* VM_ID NEW_PASSWORD VCENTER_HOST VCENTER_USER VCENTER_PASSWORD
*
* Scripts only output "SUCCESS" or "FAIL".
*/
final class VsphereHostSshService
{
private const HOST = '10.10.10.173';
private const PORT = 22;
private const USER = 'administrator';
private const PASS = 'Thegioiso@123**$$8899TgTk2';
private const SCRIPT_DIR = 'C:\\CLISave'; // backslash required for Windows cmd.exe
private const SCRIPT_LINUX = 'changePasswordLinux.ps1';
private const SCRIPT_WINDOWS = 'changePasswordWindows.ps1';
private const ASSIGN_DIR = 'C:\\CLISave\\AssignIP';
private const SCRIPT_ASSIGN_LINUX = 'assignIPLinux.ps1';
private const SCRIPT_ASSIGN_WINDOWS = 'assignIPWindows.ps1';
private const SCRIPT_VERIFY_LINUX = 'verifyIPLinux.ps1';
private const SCRIPT_VERIFY_WINDOWS = 'verifyIPWindows.ps1';
private const CONNECT_TIMEOUT = 20;
private const MAX_RETRIES = 5;
private const RETRY_DELAY = 5;
// ─────────────────────────────────────────────────────────────────────────
/**
* Run the OS-appropriate PS1 script on the vSphere host.
*
* @throws \RuntimeException on SSH failure
*/
public function changePassword(
string $vmId,
string $newPassword,
string $osFamily,
string $vcenterHost,
string $vcenterUser,
string $vcenterPassword,
): string {
$script = strtoupper($osFamily) === VmProvisionService::OS_WINDOWS
? self::SCRIPT_WINDOWS
: self::SCRIPT_LINUX;
// Remote Windows cmd.exe command — all args double-quoted
$remoteCmd = sprintf(
'cd %s && powershell -ExecutionPolicy Bypass -NonInteractive -File %s %s %s %s %s %s',
self::SCRIPT_DIR,
$script,
$this->warg($vmId),
$this->warg($newPassword),
$this->warg($vcenterHost),
$this->warg($vcenterUser),
$this->warg($vcenterPassword),
);
Log::info('VsphereHostSshService: executing script', [
'vm_id' => $vmId,
'script' => $script,
'vcenter_host' => $vcenterHost,
]);
$output = $this->sshExec($remoteCmd);
Log::info('VsphereHostSshService: script output', [
'vm_id' => $vmId,
'output' => trim($output),
]);
return $output;
}
// ─────────────────────────────────────────────────────────────────────────
/**
* Assign a static IP to the VM guest OS via PS1 script.
* Calls assignIPLinux.ps1 or assignIPWindows.ps1 in C:\CLISave\AssignIP\.
*
* Script params: vm_id ip gateway cidr vcenter_host vcenter_user vcenter_password guest_password
* Script output: SUCCESS | FAIL
*/
public function assignIp(
string $vmId,
string $ip,
string $gateway,
string $cidr,
string $vcenterHost,
string $vcenterUser,
string $vcenterPassword,
string $guestPassword,
string $osFamily,
): string {
$script = strtoupper($osFamily) === VmProvisionService::OS_WINDOWS
? self::SCRIPT_ASSIGN_WINDOWS
: self::SCRIPT_ASSIGN_LINUX;
$remoteCmd = sprintf(
'cd %s && powershell -ExecutionPolicy Bypass -NonInteractive -File %s %s %s %s %s %s %s %s %s',
self::ASSIGN_DIR,
$script,
$this->warg($vmId),
$this->warg($ip),
$this->warg($gateway),
$this->warg($cidr),
$this->warg($vcenterHost),
$this->warg($vcenterUser),
$this->warg($vcenterPassword),
$this->warg($guestPassword),
);
Log::info('VsphereHostSshService: executing assignIp', [
'vm_id' => $vmId,
'ip' => $ip,
'script' => $script,
'os' => $osFamily,
]);
$output = $this->sshExec($remoteCmd);
Log::info('VsphereHostSshService: assignIp output', [
'vm_id' => $vmId,
'ip' => $ip,
'output' => trim($output),
]);
return $output;
}
/**
* Verify that an IP is active inside the VM guest OS.
* Calls verifyIPLinux.ps1 or verifyIPWindows.ps1 in C:\CLISave\AssignIP\.
*
* Script params: vm_id ip vcenter_host vcenter_user vcenter_password guest_password
* Script output: SUCCESS | FAIL
*/
public function verifyIpInGuest(
string $vmId,
string $ip,
string $vcenterHost,
string $vcenterUser,
string $vcenterPassword,
string $guestPassword,
string $osFamily,
): string {
$script = strtoupper($osFamily) === VmProvisionService::OS_WINDOWS
? self::SCRIPT_VERIFY_WINDOWS
: self::SCRIPT_VERIFY_LINUX;
$remoteCmd = sprintf(
'cd %s && powershell -ExecutionPolicy Bypass -NonInteractive -File %s %s %s %s %s %s %s',
self::ASSIGN_DIR,
$script,
$this->warg($vmId),
$this->warg($ip),
$this->warg($vcenterHost),
$this->warg($vcenterUser),
$this->warg($vcenterPassword),
$this->warg($guestPassword),
);
Log::info('VsphereHostSshService: executing verifyIpInGuest', [
'vm_id' => $vmId,
'ip' => $ip,
'script' => $script,
'os' => $osFamily,
]);
$output = $this->sshExec($remoteCmd);
Log::info('VsphereHostSshService: verifyIpInGuest output', [
'vm_id' => $vmId,
'ip' => $ip,
'output' => trim($output),
]);
return $output;
}
// ─────────────────────────────────────────────────────────────────────────
/**
* Run a command on the vSphere SSH host via sshpass.
* Retries up to MAX_RETRIES times on connection failure.
*
* @throws \RuntimeException
*/
private function sshExec(string $remoteCmd): string
{
// Full native SSH command executed on local shell
$sshCmd = sprintf(
'sshpass -p %s ssh -o StrictHostKeyChecking=no -o ConnectTimeout=%d -p %d %s@%s %s 2>&1',
escapeshellarg(self::PASS),
self::CONNECT_TIMEOUT,
self::PORT,
escapeshellarg(self::USER),
escapeshellarg(self::HOST),
escapeshellarg($remoteCmd),
);
for ($attempt = 1; $attempt <= self::MAX_RETRIES; $attempt++) {
$output = shell_exec($sshCmd);
if ($output === null) {
Log::warning('VsphereHostSshService: shell_exec returned null', [
'attempt' => $attempt,
]);
} else {
Log::info('VsphereHostSshService: SSH executed', ['attempt' => $attempt]);
return $output;
}
if ($attempt < self::MAX_RETRIES) {
sleep(self::RETRY_DELAY);
}
}
throw new \RuntimeException(
'SSH exec to ' . self::HOST . ' failed after ' . self::MAX_RETRIES . ' attempts',
);
}
/**
* Windows cmd.exe safe argument: wrap in double quotes, escape inner double quotes.
* Password charset excludes " and % so wrapping is always safe.
*/
private function warg(string $value): string
{
return '"' . str_replace('"', '\\"', $value) . '"';
}
}