.
This commit is contained in:
commit
bbb491d3d0
5
.env
Executable file
5
.env
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
APP_ENV=dev
|
||||||
|
APP_VERSION='1.0.0'
|
||||||
|
APP_SECRET=d7e71b25a69f640224cfb1df1328454f
|
||||||
|
APP_DEV_GIT_REPOSITORY='https://github.com/swieczorek/tmp-delete-me.git'
|
||||||
|
APP_DEV_DEPLOY_DIRECTORY='/home/moby/htdocs/www.moby.io/'
|
9
.gitignore
vendored
Executable file
9
.gitignore
vendored
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
/.env.local
|
||||||
|
/.env.local.php
|
||||||
|
/.env.*.local
|
||||||
|
/bin/compiled/
|
||||||
|
/data/keys/private.key
|
||||||
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
|
/public/bundles/
|
||||||
|
/var/
|
||||||
|
/vendor/
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright © 2013—2023 Anton Medvedev
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
17
bin/console
Executable file
17
bin/console
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env php8.2
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
|
|
||||||
|
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||||
|
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
return function (array $context) {
|
||||||
|
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
|
|
||||||
|
return new Application($kernel);
|
||||||
|
};
|
26
bin/create-phar
Executable file
26
bin/create-phar
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env php8.2
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$cwd = getcwd();
|
||||||
|
assert(is_string($cwd));
|
||||||
|
|
||||||
|
require __DIR__.'/../src/Compiler/bootstrap.php';
|
||||||
|
|
||||||
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
|
use App\Compiler\Compiler;
|
||||||
|
|
||||||
|
$dotenv = new Dotenv();
|
||||||
|
$dotenv->load(__DIR__.'/../.env');
|
||||||
|
|
||||||
|
error_reporting(-1);
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$privateKey = realpath(__DIR__.'/../data/keys/private.key');
|
||||||
|
$compiler = new Compiler();
|
||||||
|
$compiler->setPrivateKey($privateKey);
|
||||||
|
$compiler->compile($_ENV['APP_VERSION']);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo 'Failed to compile phar: ['.get_class($e).'] '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().PHP_EOL;
|
||||||
|
exit(1);
|
||||||
|
}
|
46
bin/dploy
Executable file
46
bin/dploy
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env php8.2
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
use App\Console\Application;
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
if (false === defined('IS_PHAR')) {
|
||||||
|
define('IS_PHAR', (strlen(Phar::running()) > 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function_exists('ini_set')) {
|
||||||
|
@ini_set('display_errors', '1');
|
||||||
|
if ($memoryLimit = getenv('DPLOY_MEMORY_LIMIT')) {
|
||||||
|
@ini_set('memory_limit', $memoryLimit);
|
||||||
|
} else {
|
||||||
|
$memoryInBytes = function ($value) {
|
||||||
|
$unit = strtolower(substr($value, -1, 1));
|
||||||
|
$value = (int) $value;
|
||||||
|
switch($unit) {
|
||||||
|
case 'g':
|
||||||
|
$value *= 1024;
|
||||||
|
case 'm':
|
||||||
|
$value *= 1024;
|
||||||
|
case 'k':
|
||||||
|
$value *= 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
};
|
||||||
|
$memoryLimit = trim(ini_get('memory_limit'));
|
||||||
|
if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) {
|
||||||
|
@ini_set('memory_limit', '1536M');
|
||||||
|
}
|
||||||
|
unset($memoryInBytes);
|
||||||
|
}
|
||||||
|
unset($memoryLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
return function (array $context) {
|
||||||
|
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
|
return new Application($kernel);
|
||||||
|
};
|
2
changelog
Executable file
2
changelog
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
1.0.0 Stefan Wieczorek <stefan.wieczorek@cloudpanel.io> Thu, 01 Jun 2023 08:08:08
|
||||||
|
- Initial Release
|
69
composer.json
Executable file
69
composer.json
Executable file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"type": "project",
|
||||||
|
"license": "proprietary",
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"composer/pcre": "^2.1 || ^3.1",
|
||||||
|
"symfony/console": "6.2.*",
|
||||||
|
"symfony/dotenv": "6.2.*",
|
||||||
|
"symfony/filesystem": "6.2.*",
|
||||||
|
"symfony/flex": "^2",
|
||||||
|
"symfony/framework-bundle": "6.2.*",
|
||||||
|
"symfony/http-client": "6.2.*",
|
||||||
|
"symfony/process": "6.2.*",
|
||||||
|
"symfony/runtime": "6.2.*",
|
||||||
|
"symfony/yaml": "6.2.*"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"php-http/discovery": true,
|
||||||
|
"symfony/flex": true,
|
||||||
|
"symfony/runtime": true
|
||||||
|
},
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"symfony/polyfill-ctype": "*",
|
||||||
|
"symfony/polyfill-iconv": "*",
|
||||||
|
"symfony/polyfill-php72": "*",
|
||||||
|
"symfony/polyfill-php73": "*",
|
||||||
|
"symfony/polyfill-php74": "*",
|
||||||
|
"symfony/polyfill-php80": "*",
|
||||||
|
"symfony/polyfill-php81": "*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"auto-scripts": {
|
||||||
|
"cache:clear": "symfony-cmd",
|
||||||
|
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||||
|
},
|
||||||
|
"post-install-cmd": [
|
||||||
|
"@auto-scripts"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@auto-scripts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/symfony": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"symfony": {
|
||||||
|
"allow-contrib": false,
|
||||||
|
"require": "6.2.*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2689
composer.lock
generated
Executable file
2689
composer.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
5
config/bundles.php
Executable file
5
config/bundles.php
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||||
|
];
|
19
config/packages/cache.yaml
Executable file
19
config/packages/cache.yaml
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
framework:
|
||||||
|
cache:
|
||||||
|
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||||
|
#prefix_seed: your_vendor_name/app_name
|
||||||
|
|
||||||
|
# The "app" cache stores to the filesystem by default.
|
||||||
|
# The data in this cache should persist between deploys.
|
||||||
|
# Other options include:
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
#app: cache.adapter.redis
|
||||||
|
#default_redis_provider: redis://localhost
|
||||||
|
|
||||||
|
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||||
|
#app: cache.adapter.apcu
|
||||||
|
|
||||||
|
# Namespaced pools use the above "app" backend by default
|
||||||
|
#pools:
|
||||||
|
#my.dedicated.cache: null
|
29
config/packages/framework.yaml
Executable file
29
config/packages/framework.yaml
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||||
|
framework:
|
||||||
|
secret: '%env(APP_SECRET)%'
|
||||||
|
#csrf_protection: true
|
||||||
|
http_method_override: false
|
||||||
|
handle_all_throwables: true
|
||||||
|
|
||||||
|
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||||
|
# Remove or comment this section to explicitly disable session support.
|
||||||
|
session:
|
||||||
|
handler_id: null
|
||||||
|
cookie_secure: auto
|
||||||
|
cookie_samesite: lax
|
||||||
|
storage_factory_id: session.storage.factory.native
|
||||||
|
|
||||||
|
#esi: true
|
||||||
|
#fragments: true
|
||||||
|
php_errors:
|
||||||
|
log: true
|
||||||
|
|
||||||
|
http_client:
|
||||||
|
default_options:
|
||||||
|
http_version: '2.0'
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
framework:
|
||||||
|
test: true
|
||||||
|
session:
|
||||||
|
storage_factory_id: session.storage.factory.mock_file
|
12
config/packages/routing.yaml
Executable file
12
config/packages/routing.yaml
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
framework:
|
||||||
|
router:
|
||||||
|
utf8: true
|
||||||
|
|
||||||
|
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||||
|
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||||
|
#default_uri: http://localhost
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
framework:
|
||||||
|
router:
|
||||||
|
strict_requirements: null
|
5
config/preload.php
Executable file
5
config/preload.php
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||||
|
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||||
|
}
|
5
config/routes.yaml
Executable file
5
config/routes.yaml
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
controllers:
|
||||||
|
resource:
|
||||||
|
path: ../src/Controller/
|
||||||
|
namespace: App\Controller
|
||||||
|
type: attribute
|
4
config/routes/framework.yaml
Executable file
4
config/routes/framework.yaml
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
when@dev:
|
||||||
|
_errors:
|
||||||
|
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||||
|
prefix: /_error
|
40
config/services.yaml
Executable file
40
config/services.yaml
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
# This file is the entry point to configure your own services.
|
||||||
|
# Files in the packages/ subdirectory configure your dependencies.
|
||||||
|
|
||||||
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
services:
|
||||||
|
# default configuration for services in *this* file
|
||||||
|
_defaults:
|
||||||
|
autowire: true # Automatically injects dependencies in your services.
|
||||||
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
|
|
||||||
|
# makes classes in src/ available to be used as services
|
||||||
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
|
App\:
|
||||||
|
resource: '../src/'
|
||||||
|
exclude:
|
||||||
|
- '../src/DependencyInjection/'
|
||||||
|
- '../src/Entity/'
|
||||||
|
- '../src/Kernel.php'
|
||||||
|
|
||||||
|
App\Dploy:
|
||||||
|
arguments: ['@http_client']
|
||||||
|
public: true
|
||||||
|
|
||||||
|
App\Command\SetupCommand:
|
||||||
|
public: true
|
||||||
|
|
||||||
|
App\Command\DeployCommand:
|
||||||
|
public: true
|
||||||
|
|
||||||
|
App\Command\SelfUpdateCommand:
|
||||||
|
public: true
|
||||||
|
|
||||||
|
App\Command\TestCommand:
|
||||||
|
public: true
|
||||||
|
|
||||||
|
# add more service definitions when explicit configuration is needed
|
||||||
|
# please note that last definitions always *replace* previous ones
|
9
data/keys/public.key
Executable file
9
data/keys/public.key
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycvbAEuoQ8Qw8VCQgmIL
|
||||||
|
xccc4P5fKKyrnCXUve9cWbVuXaRgRVC9Mnn3bOy78wXhdQwcptjjTavRngFHO3hM
|
||||||
|
8zZqnUGotmOid0z/CLxLFNvQ7crUODFgiY8o3xydW3uS8IUGvnLlsqFxTHcBmcQR
|
||||||
|
H+hT8ONTgTWX3om/dXClSjwvSXXzUl1ps+Be1UQeiYWo9yW+RmWXMy7eZ/Y7ca8k
|
||||||
|
IObvyGc4bjMtqWH+L9HXPmkoZhl1XQuFJ/m4DdEHlCEbjjwjHC9FeTc1AoXPp47i
|
||||||
|
wyL736NYBs7dU+E6QXDk+tX82OTHVBOSaj2jcpmoyq5zyqyQ2pJp6cbnf8rma3ah
|
||||||
|
0wIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
9
public/index.php
Executable file
9
public/index.php
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
return function (array $context) {
|
||||||
|
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
|
};
|
32
src/Command/Command.php
Executable file
32
src/Command/Command.php
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command as BaseCommand;
|
||||||
|
|
||||||
|
abstract class Command extends BaseCommand
|
||||||
|
{
|
||||||
|
public const TEMPLATES_GITHUB_REPOSITORY = 'https://github.com/cloudpanel-io/dploy-application-templates';
|
||||||
|
|
||||||
|
protected function get(string $id)
|
||||||
|
{
|
||||||
|
return $this->getContainer()->get($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getContainer()
|
||||||
|
{
|
||||||
|
return $this->getApplication()->getContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getKernel()
|
||||||
|
{
|
||||||
|
$container = $this->getContainer();
|
||||||
|
return $container->get('kernel');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getProjectDirectory(): string
|
||||||
|
{
|
||||||
|
$kernel = $this->getKernel();
|
||||||
|
return $kernel->getProjectDir();
|
||||||
|
}
|
||||||
|
}
|
53
src/Command/DeployCommand.php
Executable file
53
src/Command/DeployCommand.php
Executable file
@ -0,0 +1,53 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Deployment\Deployment;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use App\Deployment\Config as DeploymentConfig;
|
||||||
|
|
||||||
|
class DeployCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->setName('deploy');
|
||||||
|
$this->setHidden(false);
|
||||||
|
$this->setDescription('Deploys a branch or tag.');
|
||||||
|
$this->addArgument('version', InputArgument::REQUIRED, 'version');
|
||||||
|
$this->setHelp(
|
||||||
|
<<<EOT
|
||||||
|
<info>Examples:</info>
|
||||||
|
|
||||||
|
Deploying a branch: <comment>dploy deploy main</comment>
|
||||||
|
Deploying a tag: <comment>dploy deploy v1.0.0</comment>
|
||||||
|
EOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$version = trim($input->getArgument('version'));
|
||||||
|
$config = new DeploymentConfig();
|
||||||
|
$systemUserId = $config->getSystemUserId();
|
||||||
|
if (0 == $systemUserId) {
|
||||||
|
throw new \Exception('Not allowed to run the command as root, use the site user.');
|
||||||
|
}
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$configFile = $config->getConfigFile();
|
||||||
|
if (true === $filesystem->exists($configFile)) {
|
||||||
|
$deployment = new Deployment($output, $config, $version);
|
||||||
|
$deployment->deploy();
|
||||||
|
$output->writeln('<info>Deployment has been completed!</info>');
|
||||||
|
} else {
|
||||||
|
throw new \Exception(sprintf('Config file "%s" does not exist. Did you run dploy setup $template?', $configFile));
|
||||||
|
}
|
||||||
|
return Command::SUCCESS;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
src/Command/InitCommand.php
Executable file
107
src/Command/InitCommand.php
Executable file
@ -0,0 +1,107 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Symfony\Component\Console\Question\Question;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use App\Deployment\Setup as DeploymentSetup;
|
||||||
|
use App\Deployment\Config as DeploymentConfig;
|
||||||
|
|
||||||
|
class InitCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->setName('init');
|
||||||
|
$this->setDescription('Setups the project directory structure.');
|
||||||
|
$this->addArgument('application', InputArgument::OPTIONAL, 'Downloads a config for the application.', 'generic');
|
||||||
|
$this->setHelp(
|
||||||
|
<<<EOT
|
||||||
|
The <info>init</info> command downloads a pre-configured <info>config.yml</info> from
|
||||||
|
|
||||||
|
<comment>https://github.com/cloudpanel-io/dploy-application-templates</comment>
|
||||||
|
|
||||||
|
<info>dploy init laravel</info>
|
||||||
|
EOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$applicationName = trim($input->getArgument('application'));
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$config = new DeploymentConfig();
|
||||||
|
$systemUserId = $config->getSystemUserId();
|
||||||
|
if (0 == $systemUserId) {
|
||||||
|
throw new \Exception('Not allowed to run the command as root, use the site user.');
|
||||||
|
}
|
||||||
|
$configFile = $config->getConfigFile();
|
||||||
|
if (false === $filesystem->exists($configFile)) {
|
||||||
|
$templates = $this->getTemplates();
|
||||||
|
if (false === isset($templates[$applicationName])) {
|
||||||
|
throw new \Exception(sprintf('Template %s does not exist, available templates: %s', $applicationName, implode(',', array_keys($templates))));
|
||||||
|
}
|
||||||
|
if (true === function_exists('xdebug_is_debugger_active') && true === xdebug_is_debugger_active()) {
|
||||||
|
$gitRepository = $_ENV['APP_DEV_GIT_REPOSITORY'];
|
||||||
|
$deployDirectory = $_ENV['APP_DEV_DEPLOY_DIRECTORY'];
|
||||||
|
} else {
|
||||||
|
$helper = $this->getHelper('question');
|
||||||
|
$gitRepositoryQuestion = new Question('Git Repository: ');
|
||||||
|
$gitRepository = $helper->ask($input, $output, $gitRepositoryQuestion);
|
||||||
|
$output->writeln(sprintf('<info>Deploy directory like: /home/%s/htdocs/www.domain.com</info>', $config->getSystemUserName()));
|
||||||
|
$deployDirectoryQuestion = new Question('Deploy Directory: ');
|
||||||
|
$deployDirectory = $helper->ask($input, $output, $deployDirectoryQuestion);
|
||||||
|
}
|
||||||
|
$deployDirectory = rtrim($deployDirectory, '/');
|
||||||
|
$template = str_replace(['{git_repository}', '{deploy_directory}'], [$gitRepository, $deployDirectory], $templates[$applicationName]);
|
||||||
|
$tmpFile = tmpfile();
|
||||||
|
$tmpFilePath = stream_get_meta_data($tmpFile)['uri'];
|
||||||
|
file_put_contents($tmpFilePath, $template);
|
||||||
|
$configDirectory = $config->getConfigDirectory();
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$filesystem->mkdir($configDirectory, 0770);
|
||||||
|
$filesystem->copy($tmpFilePath, $configFile);
|
||||||
|
$setup = new DeploymentSetup($config);
|
||||||
|
$setup->create();
|
||||||
|
$output->writeln(sprintf('<comment>%s</comment>', sprintf('The config "%s" has been created.', $configFile)));
|
||||||
|
} else {
|
||||||
|
throw new \Exception(sprintf('Config file %s already exists, nothing to do.', $configFile));
|
||||||
|
}
|
||||||
|
return Command::SUCCESS;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTemplates(): array
|
||||||
|
{
|
||||||
|
$templates = [];
|
||||||
|
try {
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$tmpDirectory = sprintf('%s/%s', rtrim(sys_get_temp_dir(), '/'), uniqid());
|
||||||
|
$gitCloneCommand = sprintf('/usr/bin/git clone %s %s', self::TEMPLATES_GITHUB_REPOSITORY, $tmpDirectory);
|
||||||
|
$process = Process::fromShellCommandline($gitCloneCommand);
|
||||||
|
$process->setTimeout(600);
|
||||||
|
$process->run();
|
||||||
|
$directoryIterator = new \DirectoryIterator($tmpDirectory);
|
||||||
|
foreach ($directoryIterator as $fileInfo) {
|
||||||
|
$name = $fileInfo->getFilename();
|
||||||
|
$filePath = $fileInfo->getPathname();
|
||||||
|
if (false === empty($name) && true === is_file($filePath) && true === file_exists($filePath)) {
|
||||||
|
$templates[$name] = file_get_contents($filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
if (true === isset($tmpDirectory) && true === is_dir($tmpDirectory)) {
|
||||||
|
$filesystem->remove($tmpDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $templates;
|
||||||
|
}
|
||||||
|
}
|
94
src/Command/ListCommand.php
Executable file
94
src/Command/ListCommand.php
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Descriptor\ApplicationDescription;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Helper\DescriptorHelper;
|
||||||
|
use Symfony\Component\Console\Helper\Helper;
|
||||||
|
use App\Command\Command as BaseCommand;
|
||||||
|
|
||||||
|
class ListCommand extends BaseCommand
|
||||||
|
{
|
||||||
|
protected OutputInterface $output;
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('list')
|
||||||
|
->setDefinition([
|
||||||
|
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, function () {
|
||||||
|
return array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces());
|
||||||
|
}),
|
||||||
|
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
|
||||||
|
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
|
||||||
|
return (new DescriptorHelper())->getFormats();
|
||||||
|
}),
|
||||||
|
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
|
||||||
|
])
|
||||||
|
->setDescription('List commands')
|
||||||
|
->setHelp(<<<'EOF'
|
||||||
|
The <info>%command.name%</info> command lists all commands:
|
||||||
|
|
||||||
|
<info>%command.full_name%</info>
|
||||||
|
|
||||||
|
You can also display the commands for a specific namespace:
|
||||||
|
|
||||||
|
<info>%command.full_name% test</info>
|
||||||
|
|
||||||
|
You can also output the information in other formats by using the <comment>--format</comment> option:
|
||||||
|
|
||||||
|
<info>%command.full_name% --format=xml</info>
|
||||||
|
|
||||||
|
It's also possible to get raw list of commands (useful for embedding command runner):
|
||||||
|
|
||||||
|
<info>%command.full_name% --raw</info>
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$application = $this->getApplication();
|
||||||
|
if ('' != $help = $application->getHelp()) {
|
||||||
|
$output->writeln($help.PHP_EOL);
|
||||||
|
}
|
||||||
|
$output->writeln('<comment>Usage:</comment>');
|
||||||
|
$output->writeln(' command [options] [arguments]'.PHP_EOL);
|
||||||
|
$output->writeln('<comment>Available commands:</comment>');
|
||||||
|
$commands = $application->getCommands();
|
||||||
|
$width = $this->getColumnWidth($commands);
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
if (false === ($command instanceof BaseCommand) || (true === $command->isHidden())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $command->getName();
|
||||||
|
$spacingWidth = $width - Helper::width($name);
|
||||||
|
$output->writeln(sprintf(' <info>%s</info>%s %s', $name, str_repeat(' ', $spacingWidth), $command->getDescription()));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getColumnWidth(array $commands): int
|
||||||
|
{
|
||||||
|
$widths = [];
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
if (false === ($command instanceof BaseCommand)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($command instanceof BaseCommand) {
|
||||||
|
$widths[] = Helper::width($command->getName());
|
||||||
|
foreach ($command->getAliases() as $alias) {
|
||||||
|
$widths[] = Helper::width($alias);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$widths[] = Helper::width($command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $widths ? max($widths) + 2 : 0;
|
||||||
|
}
|
||||||
|
}
|
84
src/Command/SelfUpdateCommand.php
Executable file
84
src/Command/SelfUpdateCommand.php
Executable file
@ -0,0 +1,84 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use App\SelfUpdate\Config;
|
||||||
|
use App\Dploy;
|
||||||
|
|
||||||
|
class SelfUpdateCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->setName('self-update');
|
||||||
|
$this->setDescription('Updates dploy to the latest version.');
|
||||||
|
$this->addOption('channel', null, InputOption::VALUE_OPTIONAL, sprintf('Sets the channel to update dploy from, available channels: %s', implode(', ', Dploy::CHANNELS)));
|
||||||
|
$this->addOption('setVersion', null, InputOption::VALUE_OPTIONAL, 'Sets the specific version to update.');
|
||||||
|
$this->setHelp(
|
||||||
|
<<<EOT
|
||||||
|
The <info>self-update</info> command checks dploy.cloudpanel.io for newer
|
||||||
|
versions of dploy and if found, installs the latest.
|
||||||
|
|
||||||
|
<info>dploy self-update</info>
|
||||||
|
EOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$container = $this->getContainer();
|
||||||
|
$dploy = $container->get('App\Dploy');
|
||||||
|
$config = new Config();
|
||||||
|
$channel = (string)$input->getOption('channel');
|
||||||
|
if (true === empty($channel)) {
|
||||||
|
$channel = $config->get('channel');
|
||||||
|
$channel = (false === empty($channel) ? $channel : Dploy::CHANNEL_STABLE);
|
||||||
|
}
|
||||||
|
$channels = $dploy->getChannels();
|
||||||
|
if (false === in_array($channel, $channels)) {
|
||||||
|
throw new \Exception(sprintf('%s is not a valid channel, available channels: %s', $channel, implode(', ', Dploy::CHANNELS)));
|
||||||
|
}
|
||||||
|
$config->set('channel', $channel);
|
||||||
|
$localFilename = realpath($_SERVER['argv'][0]);
|
||||||
|
if (false === $localFilename) {
|
||||||
|
$localFilename = $_SERVER['argv'][0];
|
||||||
|
}
|
||||||
|
if (false === file_exists($localFilename)) {
|
||||||
|
throw new \Exception(sprintf('Dploy update failed: the %s is not accessible.', $localFilename));
|
||||||
|
}
|
||||||
|
$latest = $dploy->getLatest($channel);
|
||||||
|
$latestVersion = $latest['version'] ?? '';
|
||||||
|
$currentVersion = $dploy->getVersion();
|
||||||
|
$updateVersion = $input->getOption('setVersion');
|
||||||
|
$updateVersion = (false === empty($updateVersion) ? $updateVersion : $latestVersion);
|
||||||
|
if ($currentVersion == $updateVersion) {
|
||||||
|
$output->writeln(sprintf('<info>You already use the latest dploy version %s (%s channel).</info>', $updateVersion, $channel));
|
||||||
|
} else {
|
||||||
|
if ($currentVersion < $updateVersion) {
|
||||||
|
$output->writeln(sprintf('Upgrading from <info>%s</info> to <info>%s</info> (%s channel).', $currentVersion, $updateVersion, $channel));
|
||||||
|
$output->writeln('');
|
||||||
|
$downloadedFile = $dploy->downloadVersion($updateVersion, $output);
|
||||||
|
$publicKey = sprintf('%s/data/keys/public.key', $this->getProjectDirectory());
|
||||||
|
$opensslPublicKey = openssl_pkey_get_public(file_get_contents($publicKey));
|
||||||
|
if (false === $opensslPublicKey) {
|
||||||
|
throw new \RuntimeException(sprintf('Failed loading the public key from: %s', $publicKey));
|
||||||
|
}
|
||||||
|
$signature = $dploy->getSignatureForVersion($updateVersion);
|
||||||
|
$verified = 1 === openssl_verify((string) file_get_contents($downloadedFile), $signature, $opensslPublicKey, OPENSSL_ALGO_SHA384);
|
||||||
|
if (false === $verified) {
|
||||||
|
throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified.');
|
||||||
|
}
|
||||||
|
@copy($downloadedFile, $localFilename);
|
||||||
|
$output->writeln(str_repeat(PHP_EOL, 1));
|
||||||
|
exit(Command::SUCCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Command::SUCCESS;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln(PHP_EOL.PHP_EOL.sprintf('<error>%s</error>', $e->getMessage()));
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/Command/TestCommand.php
Executable file
46
src/Command/TestCommand.php
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class TestCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->setName('test');
|
||||||
|
$this->setHidden(false);
|
||||||
|
$this->setDescription('dploy test');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
$process = Process::fromShellCommandline($command, '/tmp/');
|
||||||
|
$process->setTimeout(3600);
|
||||||
|
$process->run(function ($type, $buffer) use ($output) {
|
||||||
|
if (false === empty($buffer)) {
|
||||||
|
$output->write($buffer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (false === $process->isSuccessful()) {
|
||||||
|
throw new \RuntimeException($process->getErrorOutput());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//$output->writeln(sprintf('Muha: %s', rand(1,1000)));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$errorMessage = $e->getMessage();
|
||||||
|
$output->writeln(sprintf('<error>An error has occurred: "%s"</error>', $errorMessage));
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
227
src/Compiler/Compiler.php
Executable file
227
src/Compiler/Compiler.php
Executable file
@ -0,0 +1,227 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Compiler;
|
||||||
|
|
||||||
|
use Composer\Pcre\Preg;
|
||||||
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
class Compiler
|
||||||
|
{
|
||||||
|
private $version;
|
||||||
|
|
||||||
|
private $branchAliasVersion = '';
|
||||||
|
|
||||||
|
private $versionDate;
|
||||||
|
|
||||||
|
private $privateKey;
|
||||||
|
|
||||||
|
public function compile(string $version): void
|
||||||
|
{
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$compiledDirectory = realpath(__DIR__.'/../../bin/compiled');
|
||||||
|
$filesystem->remove($compiledDirectory);
|
||||||
|
$filesystem->mkdir($compiledDirectory);
|
||||||
|
$pharFile = sprintf('%s/dploy.phar', $compiledDirectory);
|
||||||
|
$finalFile = sprintf('%s/dploy', $compiledDirectory);
|
||||||
|
$signatureFile = sprintf('%s/dploy.sig', $compiledDirectory);
|
||||||
|
if (file_exists($pharFile)) {
|
||||||
|
unlink($pharFile);
|
||||||
|
}
|
||||||
|
$phar = new \Phar($pharFile, 0, 'dploy.phar');
|
||||||
|
$phar->setSignatureAlgorithm(\Phar::SHA512);
|
||||||
|
$phar->startBuffering();
|
||||||
|
$finderSort = static function ($a, $b): int {
|
||||||
|
return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/'));
|
||||||
|
};
|
||||||
|
$finder = new Finder();
|
||||||
|
$finder->files()
|
||||||
|
->ignoreVCS(true)
|
||||||
|
->name('*.php')
|
||||||
|
->notName('Compiler.php')
|
||||||
|
->notName('ClassLoader.php')
|
||||||
|
->notName('InstalledVersions.php')
|
||||||
|
->in(__DIR__.'/..')
|
||||||
|
->sort($finderSort)
|
||||||
|
;
|
||||||
|
foreach ($finder as $file) {
|
||||||
|
$this->addFile($phar, $file);
|
||||||
|
}
|
||||||
|
$envFile = new \SplFileInfo(__DIR__.'/../../.env');
|
||||||
|
$this->addFile($phar, $envFile);
|
||||||
|
// Add vendor files
|
||||||
|
$finder = new Finder();
|
||||||
|
$finder->files()
|
||||||
|
->ignoreVCS(true)
|
||||||
|
->notPath('/\/(composer\.(json|lock)|[A-Z]+\.md(?:own)?|\.gitignore|appveyor.yml|phpunit\.xml\.dist|phpstan\.neon\.dist|phpstan-config\.neon|phpstan-baseline\.neon)$/')
|
||||||
|
->notPath('/bin\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\.phar)(\.bat)?$/')
|
||||||
|
->notPath('justinrainbow/json-schema/demo/')
|
||||||
|
->notPath('justinrainbow/json-schema/dist/')
|
||||||
|
->notPath('composer/installed.json')
|
||||||
|
->notPath('composer/LICENSE')
|
||||||
|
->notPath('keys/private.key')
|
||||||
|
->notPath('bin/patch-type-declarations')
|
||||||
|
->notPath('bin/var-dump-server')
|
||||||
|
->notPath('bin/yaml-lint')
|
||||||
|
->notPath('psr/cache/LICENSE.txt')
|
||||||
|
->notPath('symfony/console/Resources/bin/hiddeninput.exe')
|
||||||
|
->notPath('symfony/console/Resources/completion.bash')
|
||||||
|
->notPath('symfony/console/Resources/completion.fish')
|
||||||
|
->notPath('symfony/console/Resources/completion.zsh')
|
||||||
|
->notPath('symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd')
|
||||||
|
->notPath('symfony/error-handler/Resources/assets/')
|
||||||
|
->notPath('symfony/var-dumper/Resources/css/htmlDescriptor.css')
|
||||||
|
->notPath('symfony/var-dumper/Resources/js/htmlDescriptor.js')
|
||||||
|
->notPath('symfony/runtime/Internal/autoload_runtime.template')
|
||||||
|
->notPath('symfony/routing/Loader/schema/routing/routing-1.0.xsd')
|
||||||
|
->notPath('symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd')
|
||||||
|
->notPath('symfony/framework-bundle/Resources/config/routing/errors.xml')
|
||||||
|
->exclude('Tests')
|
||||||
|
->exclude('tests')
|
||||||
|
->exclude('docs')
|
||||||
|
->in(__DIR__.'/../../vendor/')
|
||||||
|
->in(__DIR__.'/../../config/')
|
||||||
|
->in(__DIR__.'/../../data/')
|
||||||
|
->in(__DIR__.'/../../var/')
|
||||||
|
->sort($finderSort)
|
||||||
|
;
|
||||||
|
$extraFiles = [];
|
||||||
|
$unexpectedFiles = [];
|
||||||
|
foreach ($finder as $file) {
|
||||||
|
if (false !== ($index = array_search($file->getRealPath(), $extraFiles, true))) {
|
||||||
|
unset($extraFiles[$index]);
|
||||||
|
} elseif (!Preg::isMatch('{(^LICENSE$|\.php$)}', $file->getFilename())) {
|
||||||
|
//$unexpectedFiles[] = (string) $file;
|
||||||
|
}
|
||||||
|
if (Preg::isMatch('{\.php[\d.]*$}', $file->getFilename())) {
|
||||||
|
$this->addFile($phar, $file);
|
||||||
|
} else {
|
||||||
|
$this->addFile($phar, $file, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($extraFiles) > 0) {
|
||||||
|
throw new \RuntimeException('These files were expected but not added to the phar, they might be excluded or gone from the source package:'.PHP_EOL.var_export($extraFiles, true));
|
||||||
|
}
|
||||||
|
if (count($unexpectedFiles) > 0) {
|
||||||
|
throw new \RuntimeException('These files were unexpectedly added to the phar, make sure they are excluded or listed in $extraFiles:'.PHP_EOL.var_export($unexpectedFiles, true));
|
||||||
|
}
|
||||||
|
$envFile = dirname(__FILE__).'/../../.env';
|
||||||
|
$envFileContent = file_get_contents($envFile);
|
||||||
|
$this->addDployBin($phar);
|
||||||
|
$phar->addFromString('composer.json', '');
|
||||||
|
$phar->addFromString('.env', $envFileContent);
|
||||||
|
$phar['.env']->chmod(0777);
|
||||||
|
$stub = $this->getStub();
|
||||||
|
$phar->setStub($stub);
|
||||||
|
//$phar->compressFiles(\Phar::GZ);
|
||||||
|
$phar->stopBuffering();
|
||||||
|
$privateKey = $this->getPrivateKey();
|
||||||
|
$private = openssl_get_privatekey(file_get_contents($privateKey));
|
||||||
|
openssl_sign((string)file_get_contents($pharFile), $signature, $private, OPENSSL_ALGO_SHA384);
|
||||||
|
file_put_contents($signatureFile, base64_encode($signature));
|
||||||
|
rename($pharFile, $finalFile);
|
||||||
|
unset($phar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPrivateKey(string $privateKey)
|
||||||
|
{
|
||||||
|
$this->privateKey = $privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrivateKey()
|
||||||
|
{
|
||||||
|
return $this->privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRelativeFilePath(\SplFileInfo $file): string
|
||||||
|
{
|
||||||
|
$realPath = (string)$file->getRealPath();
|
||||||
|
$pathPrefix = dirname(__DIR__, 2).DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
$pos = strpos($realPath, $pathPrefix);
|
||||||
|
$relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath;
|
||||||
|
|
||||||
|
return strtr($relativePath, '\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = true): void
|
||||||
|
{
|
||||||
|
$path = $this->getRelativeFilePath($file);
|
||||||
|
$content = file_get_contents((string) $file);
|
||||||
|
if ($strip) {
|
||||||
|
//$content = $this->stripWhitespace($content);
|
||||||
|
} elseif ('LICENSE' === $file->getFilename()) {
|
||||||
|
$content = "\n".$content."\n";
|
||||||
|
}
|
||||||
|
if ($path === 'src/Composer/Composer.php') {
|
||||||
|
$content = strtr(
|
||||||
|
$content,
|
||||||
|
[
|
||||||
|
'@package_version@' => $this->version,
|
||||||
|
'@package_branch_alias_version@' => $this->branchAliasVersion,
|
||||||
|
'@release_date@' => $this->versionDate->format('Y-m-d H:i:s'),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$content = Preg::replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content);
|
||||||
|
}
|
||||||
|
$phar->addFromString($path, $content);
|
||||||
|
$phar[$path]->chmod(0777);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addDployBin(\Phar $phar): void
|
||||||
|
{
|
||||||
|
$content = file_get_contents(__DIR__.'/../../bin/dploy');
|
||||||
|
//$content = Preg::replace('{^#!/usr/bin/env php8.2\s*}', '', $content);
|
||||||
|
$phar->addFromString('bin/dploy', $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function stripWhitespace(string $source): string
|
||||||
|
{
|
||||||
|
if (!function_exists('token_get_all')) {
|
||||||
|
return $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = '';
|
||||||
|
foreach (token_get_all($source) as $token) {
|
||||||
|
if (is_string($token)) {
|
||||||
|
$output .= $token;
|
||||||
|
} elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) {
|
||||||
|
$output .= str_repeat("\n", substr_count($token[1], "\n"));
|
||||||
|
} elseif (T_WHITESPACE === $token[0]) {
|
||||||
|
// reduce wide spaces
|
||||||
|
$whitespace = Preg::replace('{[ \t]+}', ' ', $token[1]);
|
||||||
|
// normalize newlines to \n
|
||||||
|
$whitespace = Preg::replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
|
||||||
|
// trim leading spaces
|
||||||
|
$whitespace = Preg::replace('{\n +}', "\n", $whitespace);
|
||||||
|
$output .= $whitespace;
|
||||||
|
} else {
|
||||||
|
$output .= $token[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getStub(): string
|
||||||
|
{
|
||||||
|
$stub = <<<'EOF'
|
||||||
|
#!/usr/bin/env php8.2
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (!class_exists('Phar')) {
|
||||||
|
echo 'PHP\'s phar extension is missing.' . PHP_EOL;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Phar::mapPhar('dploy.phar');
|
||||||
|
|
||||||
|
EOF;
|
||||||
|
|
||||||
|
return $stub . <<<'EOF'
|
||||||
|
require 'phar://dploy.phar/bin/dploy';
|
||||||
|
|
||||||
|
__HALT_COMPILER();
|
||||||
|
EOF;
|
||||||
|
}
|
||||||
|
}
|
26
src/Compiler/bootstrap.php
Executable file
26
src/Compiler/bootstrap.php
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Composer\Autoload\ClassLoader;
|
||||||
|
|
||||||
|
function includeIfExists(string $file): ?ClassLoader
|
||||||
|
{
|
||||||
|
return file_exists($file) ? include $file : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!$loader = includeIfExists(__DIR__.'/../../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../../autoload.php'))) {
|
||||||
|
echo 'You must set up the project dependencies using `composer install`'.PHP_EOL.
|
||||||
|
'See https://getcomposer.org/download/ for instructions on installing Composer'.PHP_EOL;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $loader;
|
96
src/Console/Application.php
Executable file
96
src/Console/Application.php
Executable file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Console\Application as BaseApplication;
|
||||||
|
use Symfony\Component\Console\Command\HelpCommand;
|
||||||
|
use Symfony\Component\Console\Command\CompleteCommand;
|
||||||
|
use Symfony\Component\Console\Command\DumpCompletionCommand;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use App\Dploy;
|
||||||
|
use App\Command\InitCommand;
|
||||||
|
use App\Command\ListCommand;
|
||||||
|
use App\Command\DeployCommand;
|
||||||
|
use App\Command\SelfUpdateCommand;
|
||||||
|
class Application extends BaseApplication
|
||||||
|
{
|
||||||
|
const APPLICATION_NAME = 'Dploy';
|
||||||
|
const APPLICATION_LOGO = '
|
||||||
|
_____ _____ _ ______ __
|
||||||
|
| __ \| __ \| | / __ \ \ / /
|
||||||
|
| | | | |__) | | | | | \ \_/ /
|
||||||
|
| | | | ___/| | | | | |\ /
|
||||||
|
| |__| | | | |___| |__| | | |
|
||||||
|
|_____/|_| |______\____/ |_|
|
||||||
|
|
||||||
|
|
||||||
|
';
|
||||||
|
private KernelInterface $kernel;
|
||||||
|
public function __construct(KernelInterface $kernel)
|
||||||
|
{
|
||||||
|
$this->kernel = $kernel;
|
||||||
|
parent::__construct($this->kernel);
|
||||||
|
}
|
||||||
|
public function doRun(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$this->setApplicationNameAndVersion();
|
||||||
|
return parent::doRun($input, $output);
|
||||||
|
}
|
||||||
|
private function setApplicationNameAndVersion(): void
|
||||||
|
{
|
||||||
|
$version = Dploy::getVersion();
|
||||||
|
$this->setName(self::APPLICATION_NAME);
|
||||||
|
$this->setVersion($version);
|
||||||
|
}
|
||||||
|
private function init()
|
||||||
|
{
|
||||||
|
if ($this->initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->initialized = true;
|
||||||
|
|
||||||
|
foreach ($this->getDefaultCommands() as $command) {
|
||||||
|
$this->add($command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function getHelp(): string
|
||||||
|
{
|
||||||
|
return self::APPLICATION_LOGO . parent::getHelp();
|
||||||
|
}
|
||||||
|
public function getLongVersion(): string
|
||||||
|
{
|
||||||
|
$name = $this->getName();
|
||||||
|
$version = $this->getVersion();
|
||||||
|
$longVersion = sprintf('<info>%s</info> version <comment>%s</comment>', $name, $version);
|
||||||
|
return $longVersion;
|
||||||
|
|
||||||
|
}
|
||||||
|
protected function getDefaultCommands(): array
|
||||||
|
{
|
||||||
|
$listCommand = new ListCommand();
|
||||||
|
$listCommand->setHidden(true);
|
||||||
|
$commands = [
|
||||||
|
new HelpCommand(),
|
||||||
|
$listCommand,
|
||||||
|
new CompleteCommand(),
|
||||||
|
new DumpCompletionCommand(),
|
||||||
|
new InitCommand(),
|
||||||
|
new DeployCommand(),
|
||||||
|
];
|
||||||
|
if (true === IS_PHAR) {
|
||||||
|
$commands[] = new SelfUpdateCommand();
|
||||||
|
}
|
||||||
|
return $commands;
|
||||||
|
}
|
||||||
|
public function getCommands(): array
|
||||||
|
{
|
||||||
|
return $this->getDefaultCommands();
|
||||||
|
}
|
||||||
|
public function getContainer(): ContainerInterface
|
||||||
|
{
|
||||||
|
return $this->kernel->getContainer();
|
||||||
|
}
|
||||||
|
}
|
0
src/Controller/.gitignore
vendored
Executable file
0
src/Controller/.gitignore
vendored
Executable file
31
src/Deployment/Command/CommandExecutor.php
Executable file
31
src/Deployment/Command/CommandExecutor.php
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Command;
|
||||||
|
|
||||||
|
use App\System\Command;
|
||||||
|
use App\System\Process;
|
||||||
|
|
||||||
|
class CommandExecutor
|
||||||
|
{
|
||||||
|
public function execute(Command $command, $timeout = 30): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$runInBackground = $command->runInBackground();
|
||||||
|
$process = Process::fromShellCommandline($command->getCommand(), '/tmp/');
|
||||||
|
$process->setCommand($command);
|
||||||
|
if (true === $runInBackground) {
|
||||||
|
$process->start();
|
||||||
|
} else {
|
||||||
|
$process->setTimeout($timeout);
|
||||||
|
$process->run();
|
||||||
|
if (false === $process->isSuccessful()) {
|
||||||
|
throw new \RuntimeException($process->getErrorOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$fullCommand = $command->getCommand();
|
||||||
|
$errorMessage = sprintf('Command "%s : %s" failed, error message: %s', $command->getName(), $fullCommand, $e->getMessage());
|
||||||
|
throw new \Exception($errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
src/Deployment/Config.php
Executable file
117
src/Deployment/Config.php
Executable file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
private array $data = [];
|
||||||
|
private ?bool $configParsed = null;
|
||||||
|
|
||||||
|
public function getGitRepository(): ?string
|
||||||
|
{
|
||||||
|
return $this->getValue('git_repository');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeployDirectory(): ?string
|
||||||
|
{
|
||||||
|
$deployConfig = $this->getValue('deploy');
|
||||||
|
$deployDirectory = $deployConfig['directory'] ?? '';
|
||||||
|
return $deployDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOverlaysDirectory(): string
|
||||||
|
{
|
||||||
|
$deployConfigDirectory = $this->getConfigDirectory();
|
||||||
|
$overlaysDirectory = sprintf('%s/overlays', $deployConfigDirectory);
|
||||||
|
return $overlaysDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfigDirectory(): string
|
||||||
|
{
|
||||||
|
$systemUserName = $this->getSystemUserName();
|
||||||
|
$configDirectory = sprintf('/home/%s/.dploy', $systemUserName);
|
||||||
|
return $configDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHomeDirectory()
|
||||||
|
{
|
||||||
|
$homeDirectory = $_SERVER['HOME'] ?? '';
|
||||||
|
return $homeDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfigFile(): string
|
||||||
|
{
|
||||||
|
$configDirectory = $this->getConfigDirectory();
|
||||||
|
$configFile = sprintf('%s/config.yml', $configDirectory);
|
||||||
|
return $configFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReleasesDirectory(): string
|
||||||
|
{
|
||||||
|
$deployDirectory = $this->getDeployDirectory();
|
||||||
|
$releasesDirectory = sprintf('%s/releases', $deployDirectory);
|
||||||
|
return $releasesDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSharedDirectory(): string
|
||||||
|
{
|
||||||
|
$deployDirectory = $this->getDeployDirectory();
|
||||||
|
$sharedDirectory = sprintf('%s/shared', $deployDirectory);
|
||||||
|
return $sharedDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSharedDirectories(): array
|
||||||
|
{
|
||||||
|
$deployConfig = $this->getValue('deploy');
|
||||||
|
$sharedDirectories = (true == isset($deployConfig['shared_directories']) ? (array)$deployConfig['shared_directories'] : []);
|
||||||
|
return $sharedDirectories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBeforeDeployCommands(): array
|
||||||
|
{
|
||||||
|
$deployConfig = $this->getValue('deploy');
|
||||||
|
$beforeDeployCommands = (true == isset($deployConfig['before_commands']) ? (array)$deployConfig['before_commands'] : []);
|
||||||
|
return $beforeDeployCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAfterDeployCommands(): array
|
||||||
|
{
|
||||||
|
$deployConfig = $this->getValue('deploy');
|
||||||
|
$afterDeployCommands = (true == isset($deployConfig['after_commands']) ? (array)$deployConfig['after_commands'] : []);
|
||||||
|
return $afterDeployCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseConfigFile(): void
|
||||||
|
{
|
||||||
|
if (true === is_null($this->configParsed)) {
|
||||||
|
$configFile = $this->getConfigFile();
|
||||||
|
$data = Yaml::parseFile($configFile);
|
||||||
|
if (true === isset($data['project'])) {
|
||||||
|
$this->data = $data['project'];
|
||||||
|
}
|
||||||
|
$this->configParsed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getValue(string $key): mixed
|
||||||
|
{
|
||||||
|
$this->parseConfigFile();
|
||||||
|
$value = $this->data[$key] ?? '';
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSystemUserName(): string
|
||||||
|
{
|
||||||
|
$systemUserId = $this->getSystemUserId();
|
||||||
|
$processUser = posix_getpwuid($systemUserId);
|
||||||
|
$systemUserName = $processUser['name'];
|
||||||
|
return $systemUserName;
|
||||||
|
}
|
||||||
|
public function getSystemUserId(): int
|
||||||
|
{
|
||||||
|
$systemUserId = posix_geteuid();
|
||||||
|
return $systemUserId;
|
||||||
|
}
|
||||||
|
}
|
131
src/Deployment/Deployment.php
Executable file
131
src/Deployment/Deployment.php
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use App\Deployment\Task\GitCloneRepository as GitCloneRepositoryTask;
|
||||||
|
use App\Deployment\Task\CopyOverlayFiles as CopyOverlayFilesTask;
|
||||||
|
use App\Deployment\Task\SymlinkSharedDirectories as SymlinkSharedDirectoriesTask;
|
||||||
|
use App\Deployment\Task\ExecuteCommand as ExecuteCommandTask;
|
||||||
|
use App\Deployment\Task\SetPermissions as SetPermissionsTask;
|
||||||
|
use App\Deployment\Task\ReleaseSwitch as ReleaseSwitchTask;
|
||||||
|
use App\Deployment\Task\CleanUpReleases as CleanUpReleasesTask;
|
||||||
|
|
||||||
|
class Deployment
|
||||||
|
{
|
||||||
|
private ?string $releaseName = null;
|
||||||
|
private array $tasks = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private OutputInterface $output,
|
||||||
|
private readonly Config $config,
|
||||||
|
private readonly string $version
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deploy(): void
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
$this->executeTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function executeTasks(): void
|
||||||
|
{
|
||||||
|
$this->addTasks();
|
||||||
|
foreach ($this->tasks as $task) {
|
||||||
|
$this->output->writeln(sprintf('<comment>%s ...</comment>', $task->getDescription()));
|
||||||
|
$task->run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addTasks(): void
|
||||||
|
{
|
||||||
|
$gitCloneRepositoryTask = new GitCloneRepositoryTask($this);
|
||||||
|
$copyOverlayFilesTask = new CopyOverlayFilesTask($this);
|
||||||
|
$symlinkSharedDirectoriesTask = new SymlinkSharedDirectoriesTask($this);
|
||||||
|
$this->tasks = array_merge([
|
||||||
|
$gitCloneRepositoryTask,
|
||||||
|
$copyOverlayFilesTask,
|
||||||
|
$symlinkSharedDirectoriesTask
|
||||||
|
], $this->tasks);
|
||||||
|
$beforeCommands = $this->config->getBeforeDeployCommands();
|
||||||
|
if (false === empty($beforeCommands)) {
|
||||||
|
foreach ($beforeCommands as $command) {
|
||||||
|
if (false === empty($command)) {
|
||||||
|
$executeCommandTask = new ExecuteCommandTask($this);
|
||||||
|
$executeCommandTask->setCommand($command);
|
||||||
|
$this->tasks[] = $executeCommandTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//$setPermissionsTask = new SetPermissionsTask($this);
|
||||||
|
$releaseSwitchTask = new ReleaseSwitchTask($this);
|
||||||
|
$this->tasks = array_merge($this->tasks, [$releaseSwitchTask]);
|
||||||
|
$afterCommands = $this->config->getAfterDeployCommands();
|
||||||
|
if (false === empty($afterCommands)) {
|
||||||
|
foreach ($afterCommands as $command) {
|
||||||
|
if (false === empty($command)) {
|
||||||
|
$executeCommandTask = new ExecuteCommandTask($this);
|
||||||
|
$executeCommandTask->setCommand($command);
|
||||||
|
$this->tasks[] = $executeCommandTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cleanUpReleasesTask = new CleanUpReleasesTask($this);
|
||||||
|
$this->tasks[] = $cleanUpReleasesTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVersion(): string
|
||||||
|
{
|
||||||
|
return $this->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOutput(): OutputInterface
|
||||||
|
{
|
||||||
|
return $this->output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReleaseName(): string
|
||||||
|
{
|
||||||
|
if (true === is_null($this->releaseName)) {
|
||||||
|
$dateTime = new \DateTime('now');
|
||||||
|
$this->releaseName = sprintf('%s-%s', $dateTime->format('Y-m-d-H-i-s'), $this->version);
|
||||||
|
}
|
||||||
|
return $this->releaseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReleaseDirectory(): string
|
||||||
|
{
|
||||||
|
$releaseName = $this->getReleaseName();
|
||||||
|
$releasesDirectory = $this->config->getReleasesDirectory();
|
||||||
|
$releaseDirectory = sprintf('%s/%s', $releasesDirectory, $releaseName);
|
||||||
|
return $releaseDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfig(): Config
|
||||||
|
{
|
||||||
|
return $this->config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(): void
|
||||||
|
{
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$deployDirectory = $this->config->getDeployDirectory();
|
||||||
|
$systemUserName = $this->config->getSystemUserName();
|
||||||
|
if (false === $filesystem->exists($deployDirectory)) {
|
||||||
|
throw new \Exception(sprintf('Deploy directory "%s" does not exist.', $deployDirectory));
|
||||||
|
}
|
||||||
|
if (false === str_starts_with($deployDirectory, sprintf('/home/%s/htdocs', $systemUserName))) {
|
||||||
|
throw new \Exception(sprintf('System User "%s" is not part of the deploy directory: "%s"', $systemUserName, $deployDirectory));
|
||||||
|
}
|
||||||
|
$overlaysDirectory = $this->config->getOverlaysDirectory();
|
||||||
|
if (false === $filesystem->exists($overlaysDirectory)) {
|
||||||
|
throw new \Exception(sprintf('Overlays directory "%s" does not exist.', $overlaysDirectory));
|
||||||
|
}
|
||||||
|
$gitRepository = $this->config->getGitRepository();
|
||||||
|
if (true === empty($gitRepository)) {
|
||||||
|
throw new \Exception('Git repository cannot be empty.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/Deployment/Setup.php
Executable file
53
src/Deployment/Setup.php
Executable file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment;
|
||||||
|
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
class Setup
|
||||||
|
{
|
||||||
|
private const DIRECTORY_CHMOD = 0760;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly Config $config
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): void
|
||||||
|
{
|
||||||
|
$gitRepository = $this->config->getGitRepository();
|
||||||
|
$deployDirectory = $this->config->getDeployDirectory();
|
||||||
|
if (true === empty($gitRepository)) {
|
||||||
|
throw new \Exception('git repository cannot be empty.');
|
||||||
|
}
|
||||||
|
if (true === empty($deployDirectory)) {
|
||||||
|
throw new \Exception('deploy directory cannot be empty.');
|
||||||
|
}
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$configDirectory = $this->config->getConfigDirectory();
|
||||||
|
if (false === $filesystem->exists($configDirectory)) {
|
||||||
|
$filesystem->mkdir($configDirectory, self::DIRECTORY_CHMOD);
|
||||||
|
}
|
||||||
|
$releasesDirectory = $this->config->getReleasesDirectory();
|
||||||
|
if (false === $filesystem->exists($releasesDirectory)) {
|
||||||
|
$filesystem->mkdir($releasesDirectory, self::DIRECTORY_CHMOD);
|
||||||
|
}
|
||||||
|
$overlaysDirectory = $this->config->getOverlaysDirectory();
|
||||||
|
if (false === $filesystem->exists($overlaysDirectory)) {
|
||||||
|
$filesystem->mkdir($overlaysDirectory, self::DIRECTORY_CHMOD);
|
||||||
|
}
|
||||||
|
$sharedDirectory = $this->config->getSharedDirectory();
|
||||||
|
if (false === $filesystem->exists($sharedDirectory)) {
|
||||||
|
$filesystem->mkdir($sharedDirectory, self::DIRECTORY_CHMOD);
|
||||||
|
}
|
||||||
|
$sharedDirectories = $this->config->getSharedDirectories();
|
||||||
|
if (false === empty($sharedDirectories)) {
|
||||||
|
foreach ($sharedDirectories as $directory) {
|
||||||
|
$directory = sprintf('%s/%s', $sharedDirectory, $directory);
|
||||||
|
if (false === $filesystem->exists($directory)) {
|
||||||
|
$filesystem->mkdir($directory, self::DIRECTORY_CHMOD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/Deployment/Task/CleanUpReleases.php
Executable file
19
src/Deployment/Task/CleanUpReleases.php
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Task;
|
||||||
|
|
||||||
|
use App\Deployment\Task\Task as BaseTask;
|
||||||
|
|
||||||
|
class CleanUpReleases extends BaseTask
|
||||||
|
{
|
||||||
|
protected string $description = 'Clean Up Releases';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$deployment = $this->getDeployment();
|
||||||
|
$config = $deployment->getConfig();
|
||||||
|
$releasesDirectory = $config->getReleasesDirectory();
|
||||||
|
$command = sprintf('cd %s && ls -t | tail -n +4 | xargs rm -rf', $releasesDirectory);
|
||||||
|
$this->runCommand($command);
|
||||||
|
}
|
||||||
|
}
|
28
src/Deployment/Task/CopyOverlayFiles.php
Executable file
28
src/Deployment/Task/CopyOverlayFiles.php
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Task;
|
||||||
|
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use App\Deployment\Task\Task as BaseTask;
|
||||||
|
|
||||||
|
class CopyOverlayFiles extends BaseTask
|
||||||
|
{
|
||||||
|
protected string $description = 'Copying Overlay Files';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$deployment = $this->getDeployment();
|
||||||
|
$config = $deployment->getConfig();
|
||||||
|
$releaseDirectory = $deployment->getReleaseDirectory();
|
||||||
|
$overlaysDirectory = $config->getOverlaysDirectory();
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($overlaysDirectory), \RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
foreach($objects as $object){
|
||||||
|
if (true === $object->isFile()) {
|
||||||
|
$originFile = $object->getRealPath();
|
||||||
|
$targetFile = str_replace($overlaysDirectory, $releaseDirectory, $originFile);
|
||||||
|
$filesystem->copy($originFile, $targetFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
src/Deployment/Task/ExecuteCommand.php
Executable file
51
src/Deployment/Task/ExecuteCommand.php
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Task;
|
||||||
|
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use App\Deployment\Task\Task as BaseTask;
|
||||||
|
|
||||||
|
class ExecuteCommand extends BaseTask
|
||||||
|
{
|
||||||
|
protected string $description = 'Executing Command';
|
||||||
|
private string $command;
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$deployment = $this->getDeployment();
|
||||||
|
$output = $deployment->getOutput();
|
||||||
|
$command = $this->getCommand();
|
||||||
|
$process = Process::fromShellCommandline($command, '/tmp/');
|
||||||
|
$process->setTimeout(3600);
|
||||||
|
$process->run(function ($type, $buffer) use ($output) {
|
||||||
|
if (false === empty($buffer)) {
|
||||||
|
$output->write($buffer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (false === $process->isSuccessful()) {
|
||||||
|
throw new \RuntimeException($process->getErrorOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
$command = $this->getCommand();
|
||||||
|
$this->description = sprintf('%s: %s', $this->description, $command);
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCommand(string $command): void
|
||||||
|
{
|
||||||
|
$this->command = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommand(): string
|
||||||
|
{
|
||||||
|
$deployment = $this->getDeployment();
|
||||||
|
$releaseDirectory = $deployment->getReleaseDirectory();
|
||||||
|
if (true === str_contains($this->command, '{release_directory}')) {
|
||||||
|
$this->command = str_replace('{release_directory}', $releaseDirectory, $this->command);
|
||||||
|
}
|
||||||
|
return $this->command;
|
||||||
|
}
|
||||||
|
}
|
38
src/Deployment/Task/GitCloneRepository.php
Executable file
38
src/Deployment/Task/GitCloneRepository.php
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Task;
|
||||||
|
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use App\Deployment\Task\Task as BaseTask;
|
||||||
|
|
||||||
|
class GitCloneRepository extends BaseTask
|
||||||
|
{
|
||||||
|
protected string $description = 'Cloning Git Repository';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$deployment = $this->getDeployment();
|
||||||
|
$config = $deployment->getConfig();
|
||||||
|
$gitRepository = $config->getGitRepository();
|
||||||
|
$version = $deployment->getVersion();
|
||||||
|
$releaseDirectory = $deployment->getReleaseDirectory();
|
||||||
|
$command = sprintf('/usr/bin/git clone -c advice.detachedHead=false --progress --depth 1 --branch %s %s %s', escapeshellarg($version), escapeshellarg($gitRepository), escapeshellarg($releaseDirectory));
|
||||||
|
$output = $deployment->getOutput();
|
||||||
|
$process = Process::fromShellCommandline($command, '/tmp/');
|
||||||
|
$process->setTimeout(600);
|
||||||
|
$process->run(function ($type, $buffer) use ($output) {
|
||||||
|
if (false === empty($buffer)) {
|
||||||
|
$output->write($buffer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (false === $process->isSuccessful()) {
|
||||||
|
throw new \RuntimeException($process->getErrorOutput());
|
||||||
|
}
|
||||||
|
$gitDirectory = sprintf('%s/.git', $releaseDirectory);
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
if (true === $filesystem->exists($gitDirectory)) {
|
||||||
|
$filesystem->remove($gitDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/Deployment/Task/ReleaseSwitch.php
Executable file
21
src/Deployment/Task/ReleaseSwitch.php
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Task;
|
||||||
|
|
||||||
|
use App\Deployment\Task\Task as BaseTask;
|
||||||
|
|
||||||
|
class ReleaseSwitch extends BaseTask
|
||||||
|
{
|
||||||
|
protected string $description = 'Switching Release';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$deployment = $this->getDeployment();
|
||||||
|
$config = $deployment->getConfig();
|
||||||
|
$deployDirectory = $config->getDeployDirectory();
|
||||||
|
$releaseDirectory = $deployment->getReleaseDirectory();
|
||||||
|
$currentDirectory = sprintf('%s/current', $deployDirectory);
|
||||||
|
$command = sprintf('ln -sfn %s %s', $releaseDirectory, $currentDirectory);
|
||||||
|
$this->runCommand($command);
|
||||||
|
}
|
||||||
|
}
|
26
src/Deployment/Task/SetPermissions.php
Executable file
26
src/Deployment/Task/SetPermissions.php
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Task;
|
||||||
|
|
||||||
|
use App\Deployment\Task\Task as BaseTask;
|
||||||
|
|
||||||
|
class SetPermissions extends BaseTask
|
||||||
|
{
|
||||||
|
private const DIRECTORY_CHMOD = 770;
|
||||||
|
private const FILE_CHMOD = 660;
|
||||||
|
|
||||||
|
protected string $description = 'Setting Permissions';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$deployment = $this->getDeployment();
|
||||||
|
$releaseDirectory = $deployment->getReleaseDirectory();
|
||||||
|
$command = sprintf('/usr/bin/find %s -type d -exec chmod %s {} \; && /usr/bin/find %s -type f -exec chmod %s {} \;',
|
||||||
|
escapeshellarg($releaseDirectory),
|
||||||
|
self::DIRECTORY_CHMOD,
|
||||||
|
escapeshellarg($releaseDirectory),
|
||||||
|
self::FILE_CHMOD
|
||||||
|
);
|
||||||
|
$this->runCommand($command);
|
||||||
|
}
|
||||||
|
}
|
28
src/Deployment/Task/SymlinkSharedDirectories.php
Executable file
28
src/Deployment/Task/SymlinkSharedDirectories.php
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Task;
|
||||||
|
|
||||||
|
use App\Deployment\Task\Task as BaseTask;
|
||||||
|
|
||||||
|
class SymlinkSharedDirectories extends BaseTask
|
||||||
|
{
|
||||||
|
protected string $description = 'Symlink Shared Directories';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$deployment = $this->getDeployment();
|
||||||
|
$config = $deployment->getConfig();
|
||||||
|
$releaseDirectory = $deployment->getReleaseDirectory();
|
||||||
|
$sharedDirectories = $config->getSharedDirectories();
|
||||||
|
if (false === empty($sharedDirectories)) {
|
||||||
|
foreach ($sharedDirectories as $sharedDirectory) {
|
||||||
|
$sharedDirectory = rtrim(ltrim($sharedDirectory, '/'), '/');
|
||||||
|
$sharedDirectoryPath = sprintf('%s/%s', rtrim($releaseDirectory, '/'), $sharedDirectory);
|
||||||
|
$deleteDestinationCommand = sprintf('rm -rf %s', $sharedDirectoryPath);
|
||||||
|
$this->runCommand($deleteDestinationCommand);
|
||||||
|
$symlinkCommand = sprintf('cd %s && ln -sfrn ../../shared/%s %s', $releaseDirectory, $sharedDirectory, $sharedDirectory);
|
||||||
|
$this->runCommand($symlinkCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/Deployment/Task/Task.php
Executable file
41
src/Deployment/Task/Task.php
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Deployment\Task;
|
||||||
|
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use App\Deployment\Deployment;
|
||||||
|
|
||||||
|
abstract class Task
|
||||||
|
{
|
||||||
|
protected string $description;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private Deployment $deployment
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeployment(): Deployment
|
||||||
|
{
|
||||||
|
return $this->deployment;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function runCommand(string $command, $timeout = 900)
|
||||||
|
{
|
||||||
|
$process = Process::fromShellCommandline($command, '/tmp/');
|
||||||
|
$process->setTimeout($timeout);
|
||||||
|
$process->run();
|
||||||
|
if (false === $process->isSuccessful()) {
|
||||||
|
throw new \RuntimeException($process->getErrorOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
src/Dploy.php
Executable file
111
src/Dploy.php
Executable file
@ -0,0 +1,111 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
use Symfony\Component\Console\Helper\ProgressBar;
|
||||||
|
|
||||||
|
class Dploy
|
||||||
|
{
|
||||||
|
private const BASE_URL = 'https://dploy.cloudpanel.io';
|
||||||
|
private const REQUEST_TIMEOUT = '30';
|
||||||
|
public const CHANNEL_STABLE = 'stable';
|
||||||
|
public const CHANNELS = ['preview', 'stable'];
|
||||||
|
private float $percentageDownloaded = 0;
|
||||||
|
private ?string $tmpFile;
|
||||||
|
public function __construct(
|
||||||
|
private HttpClientInterface $httpClient,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function downloadVersion(string $version, OutputInterface $output): string
|
||||||
|
{
|
||||||
|
$downloadedFile = '';
|
||||||
|
$remoteFile = sprintf('%s/download/%s/dploy', self::BASE_URL, $version);
|
||||||
|
$progressBar = new ProgressBar($output, 100);
|
||||||
|
$progressBar->setFormat('verbose');
|
||||||
|
$this->percentageDownloaded = 0;
|
||||||
|
$response = $this->httpClient->request('GET', $remoteFile, [
|
||||||
|
'on_progress' => function (int $downloadedInBytes, int $downloadFilesizeInBytes, array $info) use ($progressBar): void {
|
||||||
|
if ($downloadedInBytes > 0) {
|
||||||
|
$downloadedInMegabyte = round($downloadedInBytes/1000000);
|
||||||
|
$downloadFilesizeInMegabyte = round($downloadFilesizeInBytes/1000000);
|
||||||
|
$percentageDownloaded = (int)round(($downloadedInMegabyte/$downloadFilesizeInMegabyte)*100);
|
||||||
|
if ($percentageDownloaded > 0 && $percentageDownloaded > $this->percentageDownloaded) {
|
||||||
|
$this->percentageDownloaded = $percentageDownloaded;
|
||||||
|
$progressBar->setProgress($percentageDownloaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
if (200 == $response->getStatusCode()) {
|
||||||
|
$this->tmpFile = tempnam(sys_get_temp_dir(), '');
|
||||||
|
$tmpFile = sprintf('%s.phar', $this->tmpFile);
|
||||||
|
rename($this->tmpFile, $tmpFile);
|
||||||
|
$this->tmpFile = $tmpFile;
|
||||||
|
$body = $response->getContent();
|
||||||
|
file_put_contents($this->tmpFile, $body);
|
||||||
|
$downloadedFile = $this->tmpFile;
|
||||||
|
} else {
|
||||||
|
throw new \Exception(sprintf('Cannot download file %s, status code: %s', $remoteFile, $response->getStatusCode()));
|
||||||
|
}
|
||||||
|
return $downloadedFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignatureForVersion(string $version): string
|
||||||
|
{
|
||||||
|
$signature = '';
|
||||||
|
$signatureFile = sprintf('%s/download/%s/dploy.sig', self::BASE_URL, $version);
|
||||||
|
$response = $this->httpClient->request('GET', $signatureFile, ['timeout' => self::REQUEST_TIMEOUT]);
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
if (200 == $statusCode) {
|
||||||
|
$signature = (string)$response->getContent();
|
||||||
|
$signature = base64_decode($signature);
|
||||||
|
} else {
|
||||||
|
throw new \Exception(sprintf('Signature file %s not available.', $signatureFile));
|
||||||
|
}
|
||||||
|
return $signature;
|
||||||
|
}
|
||||||
|
public function getLatest(string $channel)
|
||||||
|
{
|
||||||
|
$latest = $this->getVersionsData($channel);
|
||||||
|
return $latest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getVersionsData(string $channel): array
|
||||||
|
{
|
||||||
|
$requestUrl = sprintf('%s/versions.json', self::BASE_URL);
|
||||||
|
$response = $this->httpClient->request('GET', $requestUrl, ['timeout' => self::REQUEST_TIMEOUT]);
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
if (200 == $statusCode) {
|
||||||
|
$versionsData = (array)$response->toArray();
|
||||||
|
if (true === isset($versionsData[$channel]) && false === empty($versionsData[$channel])) {
|
||||||
|
$versionsData = $versionsData[$channel];
|
||||||
|
return $versionsData;
|
||||||
|
} else {
|
||||||
|
throw new \Exception(sprintf('No versions data available for channel %s.', $channel));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \Exception(sprintf('Versions file %s not available.', $requestUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function getChannels(): array
|
||||||
|
{
|
||||||
|
return self::CHANNELS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function getVersion(): string
|
||||||
|
{
|
||||||
|
$version = $_ENV['APP_VERSION'] ?? '';
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (true === isset($this->tmpFile) && true === file_exists($this->tmpFile)) {
|
||||||
|
@unlink($this->tmpFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/Kernel.php
Executable file
38
src/Kernel.php
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||||
|
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||||
|
|
||||||
|
class Kernel extends BaseKernel
|
||||||
|
{
|
||||||
|
use MicroKernelTrait;
|
||||||
|
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
setlocale(LC_CTYPE, "en_US.UTF-8");
|
||||||
|
parent::boot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getHomeDirectory()
|
||||||
|
{
|
||||||
|
$homeDirectory = $_SERVER['HOME'];
|
||||||
|
return $homeDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheDir(): string
|
||||||
|
{
|
||||||
|
$homeDirectory = $this->getHomeDirectory();
|
||||||
|
$cacheDir = sprintf('%s/.dploy/.app/cache', $homeDirectory);
|
||||||
|
return $cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLogDir(): string
|
||||||
|
{
|
||||||
|
$homeDirectory = $this->getHomeDirectory();
|
||||||
|
$logDir = sprintf('%s/.dploy/.app/logs', $homeDirectory);
|
||||||
|
return $logDir;
|
||||||
|
}
|
||||||
|
}
|
60
src/SelfUpdate/Config.php
Executable file
60
src/SelfUpdate/Config.php
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\SelfUpdate;
|
||||||
|
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
public function get(string $key): mixed
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'dploy-directory':
|
||||||
|
$homeDirectory = $this->get('home-directory');
|
||||||
|
$dployDirectory = sprintf('%s/.dploy', $homeDirectory);
|
||||||
|
return $dployDirectory;
|
||||||
|
case 'cache-directory':
|
||||||
|
$dployDirectory = $this->get('dploy-directory');
|
||||||
|
$cacheDirectory = sprintf('/home/%s/.cache', $dployDirectory);
|
||||||
|
return $cacheDirectory;
|
||||||
|
case 'home-directory':
|
||||||
|
$homeDirectory = $_SERVER['HOME'] ?? '';
|
||||||
|
return $homeDirectory;
|
||||||
|
case 'channel':
|
||||||
|
$channel = '';
|
||||||
|
$dployDirectory = $this->get('dploy-directory');
|
||||||
|
$channelFile = sprintf('%s/.channel', $dployDirectory);
|
||||||
|
if (true === file_exists($channelFile)) {
|
||||||
|
$channel = trim(file_get_contents($channelFile));
|
||||||
|
}
|
||||||
|
return $channel;
|
||||||
|
case 'channel-file':
|
||||||
|
$dployDirectory = $this->get('dploy-directory');
|
||||||
|
$channelFile = sprintf('%s/.channel', $dployDirectory);
|
||||||
|
return $channelFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(string $key, string $value): void
|
||||||
|
{
|
||||||
|
$filesystem = new Filesystem();
|
||||||
|
$dployDirectory = $this->get('dploy-directory');
|
||||||
|
if (false === file_exists($dployDirectory)) {
|
||||||
|
$filesystem->mkdir($dployDirectory, 0770);
|
||||||
|
}
|
||||||
|
switch ($key) {
|
||||||
|
case 'channel':
|
||||||
|
$channelFile = $this->get('channel-file');
|
||||||
|
file_put_contents($channelFile, $value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function getSystemUserName(): string
|
||||||
|
{
|
||||||
|
$processUser = posix_getpwuid(posix_geteuid());
|
||||||
|
$systemUserName = $processUser['name'];
|
||||||
|
return $systemUserName;
|
||||||
|
}
|
||||||
|
}
|
23
src/Util/Retry.php
Executable file
23
src/Util/Retry.php
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Util;
|
||||||
|
|
||||||
|
class Retry
|
||||||
|
{
|
||||||
|
static public function retry(callable $fn, $retries = 2, $delay = 5): mixed
|
||||||
|
{
|
||||||
|
beginning:
|
||||||
|
try {
|
||||||
|
return $fn();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if (!$retries) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
$retries--;
|
||||||
|
if ($delay) {
|
||||||
|
sleep($delay);
|
||||||
|
}
|
||||||
|
goto beginning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
symfony.lock
Executable file
58
symfony.lock
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"symfony/console": {
|
||||||
|
"version": "6.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.3",
|
||||||
|
"ref": "da0c8be8157600ad34f10ff0c9cc91232522e047"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin/console"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/flex": {
|
||||||
|
"version": "2.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
".env"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/framework-bundle": {
|
||||||
|
"version": "6.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.2",
|
||||||
|
"ref": "af47254c5e4cd543e6af3e4508298ffebbdaddd3"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/cache.yaml",
|
||||||
|
"config/packages/framework.yaml",
|
||||||
|
"config/preload.php",
|
||||||
|
"config/routes/framework.yaml",
|
||||||
|
"config/services.yaml",
|
||||||
|
"public/index.php",
|
||||||
|
"src/Controller/.gitignore",
|
||||||
|
"src/Kernel.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/routing": {
|
||||||
|
"version": "6.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "6.2",
|
||||||
|
"ref": "e0a11b4ccb8c9e70b574ff5ad3dfdcd41dec5aa6"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/routing.yaml",
|
||||||
|
"config/routes.yaml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user