clusev/app/Support/Ssh/Sftp.php

79 lines
2.2 KiB
PHP

<?php
namespace App\Support\Ssh;
use App\Models\Server;
use phpseclib3\Net\SFTP as SftpProtocol; // aliased: class names are case-insensitive, "Sftp" == "SFTP"
use RuntimeException;
/**
* Thin wrapper around phpseclib SFTP for the file manager.
*/
class Sftp
{
private ?SftpProtocol $sftp = null;
public function __construct(private readonly CredentialVault $vault) {}
public function connect(Server $server): self
{
$sftp = new SftpProtocol($server->ip, $server->ssh_port ?: 22);
$login = $this->vault->resolve($server);
if (! $sftp->login($login['username'], $login['secret'])) {
throw new RuntimeException("SFTP-Login auf {$server->name} fehlgeschlagen.");
}
$this->sftp = $sftp;
return $this;
}
/**
* @return array<int, array{name: string, type: string, size: int, permissions: string, mtime: int}>
*/
public function list(string $path = '.'): array
{
$raw = $this->client()->rawlist($path) ?: [];
$entries = [];
foreach ($raw as $name => $info) {
if ($name === '.' || $name === '..') {
continue;
}
$entries[] = [
'name' => (string) $name,
'type' => ($info['type'] ?? null) === SftpProtocol::TYPE_DIRECTORY ? 'dir' : 'file',
'size' => (int) ($info['size'] ?? 0),
'permissions' => isset($info['permissions'])
? substr(sprintf('%o', $info['permissions']), -4)
: '',
'mtime' => (int) ($info['mtime'] ?? 0),
];
}
return $entries;
}
public function get(string $remote): string
{
return (string) $this->client()->get($remote);
}
public function put(string $remote, string $contents): bool
{
return $this->client()->put($remote, $contents);
}
public function delete(string $remote): bool
{
return $this->client()->delete($remote);
}
private function client(): SftpProtocol
{
return $this->sftp ?? throw new RuntimeException('Nicht verbunden — connect() zuerst aufrufen.');
}
}