notify_push/lib/SetupWizard.php

281 строка
7.1 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\NotifyPush;
use OCA\NotifyPush\Queue\IQueue;
use OCA\NotifyPush\Queue\RedisQueue;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use Symfony\Component\Console\Output\BufferedOutput;
class SetupWizard {
private $queue;
private $test;
private $client;
private $config;
private $httpsCache = [];
private $binaryFinder;
public function __construct(
IQueue $queue,
SelfTest $test,
IClientService $clientService,
IConfig $config,
BinaryFinder $binaryFinder
) {
$this->queue = $queue;
$this->test = $test;
$this->client = $clientService->newClient();
$this->config = $config;
$this->binaryFinder = $binaryFinder;
}
public function getArch(): string {
return $this->binaryFinder->getArch();
}
private function getBinaryPath(): string {
return $this->binaryFinder->getBinaryPath();
}
public function hasBundledBinaries(): bool {
return is_dir(__DIR__ . '/../bin/' . $this->binaryFinder->getArch());
}
public function hasBinary(): bool {
return file_exists($this->getBinaryPath());
}
public function testBinary(): bool {
$path = $this->getBinaryPath();
@chmod($path, 0755);
$output = [];
exec("$path --version", $output);
return count($output) === 1 && strpos($output[0], "notify_push") === 0;
}
public function isPortFree(): bool {
$port = 7867;
return !is_resource(@fsockopen('localhost', $port));
}
public function hasRedis(): bool {
return $this->queue instanceof RedisQueue;
}
public function hasSystemd(): bool {
$result = null;
$output = [];
exec("which systemctl 2>&1", $output, $result);
return $result === 0;
}
public function hasSELinux(): bool {
$result = null;
$output = [];
exec("which getenforce 2>&1", $output, $result);
return $result === 0;
}
private function getConfigPath(): string {
return rtrim(\OC::$configDir, '/') . '/config.php';
}
private function getNextcloudUrl(): string {
$baseUrl = $this->getBaseUrl();
if (parse_url($baseUrl, PHP_URL_SCHEME) === 'https') {
$host = parse_url($baseUrl, PHP_URL_HOST);
// using an ip address and http isn't supported by the push server
if (substr_count($host, '.') === 3) {
// since we run the push server on the same server, use localhost instead
return "http://localhost/" . parse_url($baseUrl, PHP_URL_PATH);
}
}
return $baseUrl;
}
/**
* @param bool $selfSigned
* @return bool|string
*/
public function testAutoConfig(bool $selfSigned) {
$path = $this->getBinaryPath();
$config = $this->getConfigPath();
$descriptorSpec = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["pipe", "w"],
];
$pipes = [];
$proc = proc_open("exec $path $config", $descriptorSpec, $pipes, null, [
'PORT' => 7867,
'ALLOW_SELF_SIGNED' => $selfSigned ? 'true' : 'false',
'LOG' => 'notify_push=info',
'NEXTCLOUD_URL' => $this->getNextcloudUrl(),
]);
// give the server some time to start
for ($i = 0; $i < 20; $i++) {
usleep(100 * 1000);
if ($this->isBinaryRunningAt("localhost:7867")) {
break;
}
}
$status = proc_get_status($proc);
if (!$status['running']) {
proc_terminate($proc);
rewind($pipes[1]);
return stream_get_contents($pipes[1]);
}
$testResult = $this->selfTestNonProxied(true);
if ($testResult !== true) {
proc_terminate($proc);
rewind($pipes[1]);
return stream_get_contents($pipes[1]) . $testResult;
}
proc_terminate($proc);
return true;
}
public function isBinaryRunningAtDefaultPort(): bool {
return $this->isBinaryRunningAt("http://localhost:7867");
}
private function getBaseUrl(): string {
$base = $this->config->getSystemValueString('overwrite.cli.url', '');
if (strpos($base, "https://") !== 0) {
$httpsBase = "https://" . ltrim($base, "http://");
if (isset($this->httpsCache[$httpsBase])) {
return ($this->httpsCache[$httpsBase]) ? $httpsBase : $base;
}
try {
$this->client->get($base, ['nextcloud' => ['allow_local_address' => true], 'verify' => false]);
$this->httpsCache[$httpsBase] = true;
return $base;
} catch (\Exception $e) {
$this->httpsCache[$httpsBase] = false;
return $base;
}
}
return $base;
}
public function isSelfSigned(): bool {
$base = $this->getBaseUrl();
try {
$this->client->get($base, ['nextcloud' => ['allow_local_address' => true]]);
return false;
} catch (\Exception $e) {
return true;
}
}
public function getProxiedBase(): string {
return $this->getBaseUrl() . '/push';
}
private function isBinaryRunningAt(string $address): bool {
try {
$result = $this->client->get($address . '/test/cookie', ['nextcloud' => ['allow_local_address' => true], 'verify' => false]);
return is_numeric($result->getBody());
} catch (\Exception $e) {
return false;
}
}
public function isBinaryRunningBehindProxy(): bool {
return $this->isBinaryRunningAt($this->getProxiedBase());
}
/**
* @param bool $ignoreProxyError
* @return bool|string
*/
public function selfTestNonProxied(bool $ignoreProxyError = false) {
$output = new BufferedOutput();
$result = $this->test->test("http://localhost:7867", $output, $ignoreProxyError);
if ($result === 0) {
return true;
} else {
return $output->fetch();
}
}
/**
* @return bool|string
*/
public function selfTestProxied() {
$output = new BufferedOutput();
if ($this->test->test($this->getProxiedBase(), $output) === 0) {
return true;
} else {
return $output->fetch();
}
}
public function generateSystemdService(bool $selfSigned): string {
$path = $this->getBinaryPath();
$config = $this->getConfigPath();
$user = posix_getpwuid(posix_getuid())['name'];
$selfSigned = $selfSigned ? "Environment=ALLOW_SELF_SIGNED=true\n" : "";
$ncUrl = $this->getNextcloudUrl();
$service = "[Unit]
Description = Push daemon for Nextcloud clients
[Service]
Environment=PORT=7867
Environment=NEXTCLOUD_URL=$ncUrl
{$selfSigned}ExecStart=$path $config
Type=notify
User=$user
[Install]
WantedBy = multi-user.target
";
return $service;
}
/**
* @return false|string
*/
public function guessProxy() {
$base = $this->config->getSystemValueString('overwrite.cli.url', '');
try {
$result = $this->client->get($base, ['nextcloud' => ['allow_local_address' => true], 'verify' => false]);
$server = strtolower($result->getHeader('server'));
if (strpos($server, 'apache') !== false) {
return 'apache';
} elseif (strpos($server, 'nginx') !== false) {
return 'nginx';
}
return false;
} catch (\Exception $e) {
return false;
}
}
public function nginxConfig(): string {
return "location ^~ /push/ {
proxy_pass http://127.0.0.1:7867/;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection \"Upgrade\";
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
";
}
public function apacheConfig(): string {
return "ProxyPass /push/ws ws://127.0.0.1:7867/ws
ProxyPass /push/ http://127.0.0.1:7867/
ProxyPassReverse /push/ http://127.0.0.1:7867/
";
}
}