blue-twilight/installer/Installer.php

276 lines
7.2 KiB
PHP

<?php
namespace AppInstaller;
use App\Services\GiteaService;
use Illuminate\Support\Facades\Artisan;
/**
* This class handles the downloading and extracting of the vendors directory.
* Because Laravel and other vendors are not yet available, it uses "raw" PHP and the odd few classes within Blue
* Twilight, such as GiteaService.
*
* @package AppInstaller
*/
class Installer
{
/**
* Path to /app/config
* @var string
*/
private $configDir;
/**
* Path to /installer
* @var string
*/
private $installerDir;
/**
* Path to / - the app's root
* @var string
*/
private $rootDir;
/**
* Path to /installer/temp
* @var string
*/
private $tempDir;
/**
* Path to /vendor
* @var string
*/
private $vendorDir;
/**
* Path to /installer/views
* @var string
*/
private $viewsDir;
public function __construct()
{
$this->installerDir = __DIR__;
$this->rootDir = dirname($this->installerDir);
$this->configDir = sprintf('%s/config', dirname(dirname($this->installerDir)));
$this->tempDir = sprintf('%s/temp', $this->installerDir);
$this->vendorDir = sprintf('%s/vendor', $this->rootDir);
$this->viewsDir = sprintf('%s/views', $this->installerDir);
chdir($this->rootDir);
putenv('HOME=' . $this->rootDir);
}
public function handleRequest()
{
if (!isset($_GET['act']))
{
$this->view('index', ['appName' => 'Blue Twilight Bootstrapper']);
}
else
{
switch (trim($_GET['act']))
{
case 'download':
$this->download();
return;
case 'extract':
$this->extract();
return;
case 'finalise':
$this->finalise();
return;
default:
throw new \Exception(sprintf('Action \'%s\' was not recognised.', $_GET['act']));
}
}
}
protected function download()
{
$appConfig = require_once sprintf('%s/app.php', $this->configDir);
$servicesConfig = require_once sprintf('%s/services.php', $this->configDir);
$versionNumber = sprintf('v%s', $appConfig['version']);
$gitea = new GiteaService($servicesConfig['gitea'], $versionNumber);
$releaseInfo = $gitea->getSpecificRelease($versionNumber);
if (is_null($releaseInfo))
{
throw new \Exception(sprintf('No release info found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
}
else if (!isset($releaseInfo->assets))
{
throw new \Exception(sprintf('No assets found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
}
$vendorsPrefix = 'vendors';
$vendorsSuffix = '.tar.gz';
$selectedAsset = null;
foreach ($releaseInfo->assets as $asset)
{
/*
Ignore anything that is not "vendors<something>.tar.gz" were the <something> is also optional - e.g.
vendors_2.1.2.tar.gz
vendors.tar.gz
but NOT 2.1.2_vendors.zip
*/
if (!starts_with($asset->name, $vendorsPrefix) || !ends_with($asset->name, $vendorsSuffix))
{
continue;
}
$selectedAsset = $asset;
break;
}
if (is_null($selectedAsset))
{
throw new \Exception(sprintf('No vendors.tar.gz found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
}
$targetFileName = $this->getVendorsTempFileName();
$this->downloadFile($selectedAsset->browser_download_url, $targetFileName);
$this->json([
'result' => true,
'fileName' => $targetFileName
]);
}
protected function extract()
{
$targetFileName = $this->getVendorsTempFileName();
if (!file_exists($targetFileName) || !is_readable($targetFileName))
{
throw new \Exception(sprintf('The file \'%s\' does not exist or is not readable', $targetFileName));
}
$phar = new \PharData($targetFileName);
$phar->extractTo($this->rootDir, null, true);
// We should always have a vendor/autoload.php
$vendorsTestFile = $this->getVendorsAutoloadFileName();
if (file_exists($vendorsTestFile))
{
$this->json([
'result' => true,
'testFile' => $vendorsTestFile
]);
}
else
{
throw new \Exception('The extraction failed');
}
}
protected function finalise()
{
$result = [
'envFileCreated' => false
];
$result['envFileCreated'] = $this->createEnvFileIfNotExist();
require sprintf('%s/bootstrap/autoload.php', $this->rootDir);
$app = require_once sprintf('%s/bootstrap/app.php', $this->rootDir);
$kernel = $app->make(\Illuminate\Contracts\Console\Kernel::class);
$kernel->bootstrap();
if ($result['envFileCreated'])
{
Artisan::call('key:generate');
}
$kernel->terminate(null, null);
$this->json($result);
}
private function downloadFile($sourceURL, $targetFilename)
{
$urlHandle = @fopen($sourceURL, 'r');
$tempFilename = @fopen($targetFilename, 'w');
if ($urlHandle === false)
{
throw new \Exception(sprintf('Failed downloading the file from %s', $sourceURL));
}
else if ($tempFilename === false)
{
throw new \Exception(sprintf('Failed opening the file \'%s\' for writing', $targetFilename));
}
while (!feof($urlHandle))
{
$buffer = fread($urlHandle, 8192);
fwrite($tempFilename, $buffer);
}
@fclose($urlHandle);
@fclose($tempFilename);
}
private function createEnvFileIfNotExist()
{
$envFile = $this->getEnvFileName();
if (!file_exists($envFile))
{
copy($this->getEnvExampleFileName(), $envFile);
return true;
}
return false;
}
private function getEnvExampleFileName()
{
return sprintf('%s/.env.example', $this->rootDir);
}
private function getEnvFileName()
{
return sprintf('%s/.env', $this->rootDir);
}
private function getVendorsAutoloadFileName()
{
return sprintf('%s/autoload.php', $this->vendorDir);
}
private function getVendorsTempFileName()
{
return sprintf('%s/vendors.tar.gz', $this->tempDir);
}
private function json($data)
{
echo json_encode($data);
}
private function view($name, array $viewData = [])
{
$viewFile = sprintf('%s/%s.php', $this->viewsDir, $name);
if (!file_exists($viewFile) || !is_readable($viewFile))
{
throw new \Exception(sprintf('ERROR: View file \'%s\' does not exist.', $viewFile));
}
// Provide keys as variables - e.g. $viewData['something'] becomes accessible via $something
extract($viewData);
require_once $viewFile;
}
}