Completed the first interation of the new Bootstrapper experience. Downloads and extracts vendors from Gitea and configures the encryption key. Still need to get upgrades implemented. #146

This commit is contained in:
Andy Heathershaw 2020-04-26 21:53:24 +01:00
parent 365034d611
commit e06b227147
14 changed files with 342 additions and 34 deletions

View File

@ -1,7 +1,7 @@
APP_ENV=local
APP_ENV=production
APP_KEY=
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_DEBUG=false
APP_LOG_LEVEL=warning
APP_URL=http://localhost
DB_CONNECTION=mysql

View File

@ -14,7 +14,9 @@ module.exports = function(grunt)
const sass = require('node-sass');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-curl');
grunt.loadNpmTasks('grunt-dart-sass');
grunt.loadNpmTasks('grunt-exec');
@ -97,6 +99,23 @@ module.exports = function(grunt)
ext: '.css'
}]
},
},
cssmin: {
bt_css: {
files: {
'public/css/blue-twilight.min.css': ['public/css/blue-twilight.css']
}
}
},
uglify: {
bt_js: {
options: {
sourceMap: true
},
files: {
'public/js/blue-twilight.min.js': ['public/js/blue-twilight.js']
}
}
}
});
@ -121,7 +140,18 @@ module.exports = function(grunt)
'concat:bt_js'
]);
grunt.registerTask('build-css-release', [
'build-css-debug',
'cssmin:bt_css'
]);
grunt.registerTask('build-js-release', [
'build-js-debug',
'uglify:bt_js'
]);
// Shortcut tasks for the ones above
grunt.registerTask('clean-all', ['clean:build_css', 'clean:build_js', 'clean:output']);
grunt.registerTask('build-debug', ['clean-all', 'build-css-debug', 'build-js-debug']);
grunt.registerTask('build-release', ['clean-all', 'build-css-release', 'build-js-release']);
};

View File

@ -2,7 +2,7 @@
return [
// Version number of Blue Twilight
'version' => '2.1.2',
'version' => '2.2.0-beta.1',
/*
|--------------------------------------------------------------------------

View File

@ -12,4 +12,4 @@
"grunt-exec": "^3.0.0",
"node-sass": "^4.13.0"
}
}
}

View File

@ -3,6 +3,7 @@
namespace AppBootstrap;
use App\Services\GiteaService;
use Illuminate\Support\Facades\Artisan;
/**
* This class handles the downloading and extracting of the vendors directory.
@ -13,17 +14,54 @@ use App\Services\GiteaService;
*/
class Bootstrapper
{
/**
* Path to /public/bootstrap
* @var string
*/
private $bootstrapDir;
/**
* Path to /app/config
* @var string
*/
private $configDir;
/**
* Path to / - the app's root
* @var string
*/
private $rootDir;
/**
* Path to /public/bootstrap/temp
* @var string
*/
private $tempDir;
/**
* Path to /vendor
* @var string
*/
private $vendorDir;
/**
* Path to /public/bootstrap/views
* @var string
*/
private $viewsDir;
public function __construct()
{
$this->rootDir = dirname(__DIR__);
$this->configDir = sprintf('%s/config', dirname(dirname($this->rootDir)));
$this->tempDir = sprintf('%s/temp', $this->rootDir);
$this->viewsDir = sprintf('%s/views', $this->rootDir);
$this->bootstrapDir = dirname(__DIR__);
$this->rootDir = dirname(dirname($this->bootstrapDir));
$this->configDir = sprintf('%s/config', dirname(dirname($this->bootstrapDir)));
$this->tempDir = sprintf('%s/temp', $this->bootstrapDir);
$this->vendorDir = sprintf('%s/vendor', $this->rootDir);
$this->viewsDir = sprintf('%s/views', $this->bootstrapDir);
chdir($this->rootDir);
putenv('HOME=' . $this->rootDir);
}
public function handleRequest()
@ -40,13 +78,21 @@ class Bootstrapper
$this->download();
return;
case 'extract':
$this->extract();
return;
case 'finalise':
$this->finalise();
return;
default:
throw new \Exception(sprintf('ERROR: Action \'%s\' was not recognised.', $_GET['act']));
throw new \Exception(sprintf('Action \'%s\' was not recognised.', $_GET['act']));
}
}
}
private function download()
protected function download()
{
$appConfig = require_once sprintf('%s/app.php', $this->configDir);
$servicesConfig = require_once sprintf('%s/services.php', $this->configDir);
@ -58,16 +104,17 @@ class Bootstrapper
if (is_null($releaseInfo))
{
throw new \Exception(sprintf('No release info found in Gitea for Blue Twilight version \'%\'', $versionNumber));
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 \'%\'', $versionNumber));
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)
{
/*
@ -87,13 +134,68 @@ class Bootstrapper
if (is_null($selectedAsset))
{
throw new \Exception('No vendors.tar.gz found in Gitea for Blue Twilight version \'%\'', $versionNumber);
throw new \Exception(sprintf('No vendors.tar.gz found in Gitea for Blue Twilight version \'%s\'', $versionNumber));
}
$targetFileName = sprintf('%s/vendors.tar.gz', $this->tempDir);
$targetFileName = $this->getVendorsTempFileName();
$this->downloadFile($selectedAsset->browser_download_url, $targetFileName);
var_dump('done');
$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)
@ -120,6 +222,44 @@ class Bootstrapper
@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);

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="../css/blue-twilight.css">
<link rel="stylesheet" href="../css/blue-twilight.min.css">
<title><?php echo $appName; ?></title>
<style type="text/css">
* {
@ -23,12 +23,12 @@
<p class="lead"><b>Welcome to Blue Twilight - the self-hosted PHP photo gallery.</b></p>
<div v-if="!isRunning">
<p>We need to download and install a few more files before you get started.</p>
<p>Click the &quot;Let's Go&quot; button below to begin.</p>
<p class="mb-0 mt-4"><button class="btn btn-primary btn-lg" @click.prevent="bootstrap">Let's Go</button></p>
<p>We need to download and install a few more files before you get started.</p>
<p>Click the &quot;Let's Go&quot; button below to begin.</p>
<p class="mb-0 mt-4"><button class="btn btn-primary btn-lg" @click.prevent="bootstrap">Let's Go</button></p>
</div>
<div v-else>
<ul>
<ul v-cloak>
<li class="operation mb-3" v-for="operation in operations">
<div class="status mr-1">
<img src="images/waiting.svg" v-if="!operation.isRunning && !operation.isCompleted"></img>
@ -45,7 +45,7 @@
</div>
</div>
<script src="../js/blue-twilight.js"></script>
<script src="../js/blue-twilight.min.js"></script>
<script type="text/javascript">
$(function()
{

View File

@ -10260,6 +10260,29 @@ a.text-dark:hover, a.text-dark:focus {
[v-cloak] {
display: none;
}
#bootstrapper [v-cloak] {
display: none;
}
#bootstrapper .operation .status {
display: inline-block;
height: 32px;
width: 32px;
}
#bootstrapper .operation .status i,
#bootstrapper .operation .status img {
height: 100%;
width: 100%;
}
#bootstrapper .operation .status span {
display: inline-block;
height: 100%;
vertical-align: middle;
}
#bootstrapper ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.activity-grid {
font-size: smaller;
}

6
public/css/blue-twilight.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -35700,6 +35700,12 @@ function AnalyseAlbumViewModel() {
});
item.isSuccessful = false;
item.isPending = false;
var indexToRemove = self.imagesInProgress.indexOf(item);
if (indexToRemove > -1)
{
self.imagesInProgress.splice(indexToRemove, 1);
}
}
}
}
@ -35824,6 +35830,7 @@ function EditPhotosViewModel(album_id, language, urls) {
deletePhoto: function (e) {
var self = this;
this.selectPhotoSingle(e.target);
var parent = $(e.target).closest('.photo');
var photo_id = self.photoIDs[0];
this.photoIDs = [];
@ -35846,7 +35853,7 @@ function EditPhotosViewModel(album_id, language, urls) {
$('.loading', parent).show();
$.post(url, {'_method': 'DELETE'}, function (data) {
window.location.reload();
$(parent).remove();
});
}
}
@ -35875,10 +35882,11 @@ function EditPhotosViewModel(album_id, language, urls) {
}
$('.loading', parent).show();
$.post(url, function () {
$.post(url, function (response) {
var image = $('img.photo-thumbnail', parent);
var originalUrl = image.data('original-src');
image.attr('src', originalUrl + "&_=" + new Date().getTime());
// response from server is the URL to the modified image
image.attr('src', response);
$('.loading', parent).hide();
});
@ -35958,10 +35966,10 @@ function EditPhotosViewModel(album_id, language, urls) {
url = url.replace(/\/0$/, '/' + this.photoIDs[0]);
$('.loading', parent).show();
$.post(url, function () {
$.post(url, function (response) {
var image = $('img.photo-thumbnail', parent);
var originalUrl = image.data('original-src');
image.attr('src', originalUrl + "&_=" + new Date().getTime());
image.attr('src', response.thumbnail_url);
$('.loading', parent).hide();
});
@ -36310,6 +36318,79 @@ function UploadPhotosViewModel(album_id, queue_token, language, urls) {
}
};
}
/**
* This model is used by the system bootstrapper in public/bootstrap.
* @constructor
*/
function BootstrapperViewModel() {
this.el = '#bootstrapper';
this.data = {
isCompleted: false,
isRunning: false,
operations: []
}
this.methods = {
bootstrap: function()
{
this.operations.push({
'isCompleted': false,
'isRunning': false,
'name': 'Removing any previous versions',
'url': '?act=removePrevious'
});
this.operations.push({
'isCompleted': false,
'isRunning': false,
'name': 'Downloading new files',
'url': '?act=download'
});
this.operations.push({
'isCompleted': false,
'isRunning': false,
'name': 'Extracting new files',
'url': '?act=extract'
});
this.operations.push({
'isCompleted': false,
'isRunning': false,
'name': 'Cleaning up',
'url': '?act=finalise'
});
this.isRunning = true;
this.runOperation(this.operations[0], 0);
},
runOperation: function(operation, index)
{
var self = this;
operation.isRunning = true;
$.post(operation.url)
.done(function(result)
{
operation.isRunning = false;
operation.isCompleted = true;
index++;
if (index < self.operations.length)
{
self.runOperation(self.operations[index], index);
}
else
{
//self.isRunning = false;
self.isCompleted = true;
window.location = '../';
}
})
}
}
}
/*!
* Chart.js
* http://chartjs.org/
@ -54804,6 +54885,27 @@ module.exports = function(Chart) {
},{"25":25,"45":45,"6":6}]},{},[7])(7)
});
function ExternalServiceViewModel()
{
this.el = '#external-service-options';
this.data = {
service_type: ''
};
this.computed = {
hasOAuthStandardOptions: function()
{
// This logic must be mirrored in App\ExternalService
return this.service_type === 'facebook' ||
this.service_type === 'google' ||
this.service_type === 'twitter';
},
isDropbox: function()
{
// This logic must be mirrored in App\ExternalService
return this.service_type === 'dropbox';
}
}
}
/**
* This model is used by gallery/explore_users.blade.php, to handle following/unfollowing users profiles.
* @constructor

2
public/js/blue-twilight.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ function BootstrapperViewModel() {
operations: []
}
this.methods = {
bootstrap()
bootstrap: function()
{
this.operations.push({
'isCompleted': false,
@ -37,14 +37,14 @@ function BootstrapperViewModel() {
'isCompleted': false,
'isRunning': false,
'name': 'Cleaning up',
'url': '?act=cleanup'
'url': '?act=finalise'
});
this.isRunning = true;
this.runOperation(this.operations[0], 0);
},
runOperation(operation, index)
runOperation: function(operation, index)
{
var self = this;
operation.isRunning = true;
@ -64,6 +64,8 @@ function BootstrapperViewModel() {
{
//self.isRunning = false;
self.isCompleted = true;
window.location = '../';
}
})
}

View File

@ -5,14 +5,14 @@ function ExternalServiceViewModel()
service_type: ''
};
this.computed = {
hasOAuthStandardOptions()
hasOAuthStandardOptions: function()
{
// This logic must be mirrored in App\ExternalService
return this.service_type === 'facebook' ||
this.service_type === 'google' ||
this.service_type === 'twitter';
},
isDropbox()
isDropbox: function()
{
// This logic must be mirrored in App\ExternalService
return this.service_type === 'dropbox';

View File

@ -1,5 +1,7 @@
#bootstrapper
{
[v-cloak] { display: none }
.operation
{
.status