로그인
KO
games/xencenter-xenadmin-xcp-jumphp / src / Model / ServerProfile.php · commit fb1b7c9
<?phpnamespace App\Model;/** * How to reach one server (what the Add New Server dialog asks), saved in var/servers.json like XenCenter's saved servers. * Methods: demo (the pool in memory), http (XAPI on port 80: a socket, or in the browser the JumPHP runtime / the server's bridge), * https (port 443, desktop), ssh (an SSH tunnel to the host's own XAPI: `ssh -W 127.0.0.1:80`). */final class ServerProfile{    /** the password as var/servers.json keeps it: sealed by the Keyring ('' when not remembered) */    public string $secret = '';    public const METHODS = ['http' => 'HTTP (port 80)', 'https' => 'HTTPS (port 443, desktop)', 'ssh' => 'SSH tunnel to the host', 'demo' => 'Demo pool (no server)'];    public function __construct(        public string $host,        public string $user = 'root',        public string $password = '',        public string $method = 'http',        public int $port = 0,        public bool $remember = false,        public string $sshUser = 'root',        public int $sshPort = 22,        public string $sshKey = '',        public string $friendlyName = '',        /** @var array<string, string> host uuid => the address the user reaches it at (a pool behind a VPN / NAT): Connection Addresses… */        public array $hostAddresses = [],    ) {    }    public static function demo(): self    {        return new self('xcp-demo.local', \App\Xapi\FakeXapi::USER, \App\Xapi\FakeXapi::PASSWORD, 'demo', 0, true, 'root', 22, '', 'Demo pool');    }    /** Another demo server: a standalone host of its own (FakeXapi builds one for any address but the demo pool's), e.g. 'xcp-demo-2.local' = xcp-ng-03. */    public static function demoServer(string $address = 'xcp-demo-2.local'): self    {        return new self($address, \App\Xapi\FakeXapi::USER, \App\Xapi\FakeXapi::PASSWORD, 'demo', 0, true);    }    public function port(): int    {        return $this->port > 0 ? $this->port : ($this->method === 'https' ? 443 : 80);    }    public function key(): string    {        return $this->method . '://' . strtolower($this->host) . ':' . $this->port();    }    /** @return array<string, mixed> what is saved: the password only when "remember" is ticked, and then sealed (never in clear) */    public function toArray(?Keyring $keyring = null): array    {        if (!$this->remember) {            $this->secret = '';        } elseif ($this->password !== '' && ($sealed = $keyring?->seal($this->password)) !== null) {            $this->secret = $sealed;       // locked by a master password not given yet: the one sealed before is kept        }        return ['host' => $this->host, 'user' => $this->user, 'secret' => $this->secret, 'method' => $this->method, 'port' => $this->port,            'remember' => $this->remember, 'sshUser' => $this->sshUser, 'sshPort' => $this->sshPort, 'sshKey' => $this->sshKey, 'friendlyName' => $this->friendlyName,            'hostAddresses' => $this->hostAddresses];    }    /** @param array<string, mixed> $a */    public static function fromArray(array $a): self    {        // 'password': a clear one of an older version (Keyring::restore() writes it again sealed)        $profile = new self((string)($a['host'] ?? ''), (string)($a['user'] ?? 'root'), (string)($a['password'] ?? ''), (string)($a['method'] ?? 'http'), (int)($a['port'] ?? 0),            (bool)($a['remember'] ?? false), (string)($a['sshUser'] ?? 'root'), (int)($a['sshPort'] ?? 22), (string)($a['sshKey'] ?? ''), (string)($a['friendlyName'] ?? ''),            array_map('strval', array_filter((array)($a['hostAddresses'] ?? []), fn ($v) => is_scalar($v) && trim((string)$v) !== '')));        $profile->secret = (string)($a['secret'] ?? '');        return $profile;    }}