eveseat/installer

View on GitHub
src/Commands/Install/Production.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php

/*
 * This file is part of SeAT
 *
 * Copyright (C) 2015 to 2021 Leon Jacobs
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

namespace Seat\Installer\Commands\Install;

use Seat\Installer\Utils\Apache;
use Seat\Installer\Utils\Composer;
use Seat\Installer\Utils\Crontab;
use Seat\Installer\Utils\MySql;
use Seat\Installer\Utils\Nginx;
use Seat\Installer\Utils\OsUpdates;
use Seat\Installer\Utils\PackageInstaller;
use Seat\Installer\Utils\Redis;
use Seat\Installer\Utils\Requirements;
use Seat\Installer\Utils\Seat;
use Seat\Installer\Utils\Supervisor;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;

/**
 * Class InstallProdCommand.
 * @package Seat\Installer
 */
class Production extends Command
{
    /**
     * @var
     */
    protected $io;

    /**
     * @var
     */
    protected $mysql_credentials;

    /**
     * @var
     */
    protected $webserver_choice;

    /**
     * @var
     */
    protected $seat_destination;

    /**
     * @var
     */
    protected $minimum_stability;

    /**
     * @var array
     */
    protected $webserver_info = [
        'apache' => [
            'installer' => Apache::class,
        ],
        'nginx'  => [
            'installer' => Nginx::class,
        ],
    ];

    /**
     * Setup the command.
     */
    protected function configure()
    {

        $this
            ->setName('install:production')
            ->setDescription('Install a SeAT Production Instance')
            ->addOption('seat-destination', 's', InputOption::VALUE_OPTIONAL,
                'Destination folder to install to', '/var/www/seat')
            ->addOption('minimum-stability', null, InputOption::VALUE_OPTIONAL,
                'The minimum stability used for the install', 'stable')
            ->setHelp('This command allows you to install SeAT on your system');
    }

    /**
     * @param \Symfony\Component\Console\Input\InputInterface   $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     *
     * @return int|null|void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {

        $this->io = new SymfonyStyle($input, $output);
        $this->io->title('SeAT Installer');

        // Start by figuring out where we are going to install SeAT
        $this->seat_destination = $input->getOption('seat-destination');
        $this->minimum_stability = $input->getOption('minimum-stability');

        // Ensure that we should continue.
        if (! $this->confirmContinue()) {

            $this->io->text('Installer stopped via user cancel.');

            return;
        }

        // No interaction means we have to set the webserver
        // choice manually.
        if ($input->getOption('no-interaction'))
            $this->webserver_choice = 'apache';

        else
            // Get the webserver to use.
            $this->webserver_choice = $this->io->choice('Which webserver do you want to use?', [
                'apache', 'nginx',
            ], 'apache');

        // Prepare the SeAT installation directory
        $this->createInstallDirectory();

        // Process requirements
        if (! $this->checkRequirements())
            return;

        $this->checkComposer();

        $this->updateOs();

        $this->configureMySql();

        $this->configureRedis();

        $this->installPhpPackages();

        $this->installSeat($this->minimum_stability);

        $this->installWebserver();

        $this->setupCrontab();

        $this->setupSupervisor();

        $this->io->success('Installation complete!');

    }

    /**
     * @return bool
     */
    protected function confirmContinue()
    {

        $this->io->text('This installer will install SeAT on this server ' .
            'with hostname: ' . gethostname());
        $this->io->newline();

        $this->io->text('SeAT will be installed at: ' . $this->seat_destination .
            '. If the directory does not exist, it will be created.');
        $this->io->newline();

        $this->io->text('The following is a short summary of actions that ' .
            'will be performed:');
        $this->io->newline();
        $this->io->listing([
            'Check the needed software depedencies.',
            'Ensure Composer is available for use.',
            'Update the Operating System.',
            'Configure / Install MySQL.',
            'Install OS Dependencies.',
            'Install SeAT.',
            'Install & Configure supervisor.',
            'Setup the crontab for SeAT.',
            'Install and configure a Webserver.',
        ]);

        $this->io->text('It may be needed to restart the installer sometimes to continue.');

        if ($this->io->confirm('Would like to continue with the installation?'))
            return true;

        return false;
    }

    /**
     * Creates the installation directory.
     */
    protected function createInstallDirectory()
    {

        $fs = new Filesystem();
        $fs->mkdir($this->seat_destination, 0755);
        $this->io->success('Directory ' . $this->seat_destination . ' created.');

    }

    /**
     * @return bool
     */
    protected function checkRequirements(): bool
    {

        // Requirements
        $this->io->text('Checking Requirements');

        $requirements = new Requirements($this->io);

        $requirements->checkSoftwareRequirements();

        // Some PHP dependencies can break this tool. Force
        // a rerun of they are not met.
        if (! $requirements->checkPhpRequirements())
            return false;

        $requirements->checkAccessRequirements();
        $requirements->checkCommandRequirements();

        if (! $requirements->hasAllRequirements())
            return false;

        $this->io->success('Passed requirements check');

        return true;

    }

    /**
     * Ensures that composer is ready to use.
     */
    protected function checkComposer()
    {

        $this->io->text('Checking Composer installation');

        $composer = new Composer($this->io);

        // If we dont have composer, install it.
        if (! $composer->hasComposer())
            $composer->install();
        else
            $composer->update();
    }

    /**
     * Ensure the Operating System is up to date.
     */
    protected function updateOs()
    {

        $this->io->text('Updating Operating System');
        $updates = new OsUpdates($this->io);
        $updates->update();

        $this->io->success('Operating System Update Complete');

    }

    /**
     * Configure MySQL for use.
     */
    protected function configureMySql()
    {

        $mysql = new MySql($this->io);

        // Check if MySQL is already installed. If so, prompt for
        // credentials to use.
        if ($mysql->isInstalled()) {

            $this->io->warning('MySQL appears to already be installed.');
            $this->io->text('Entering mode to get access details for SeAT to use. ' .
                'It is recommended that you create a *new* database and MySQL user ' .
                'for SeAT. The user must have the following MySQL privileges on the ' .
                'SeAT database:');
            $this->io->text('CREATE, LOCK TABLES, INDEX, INSERT, SELECT, UPDATE, DELETE, DROP, ALTER');
            $this->io->text('A user can be created with the following SQL statement:');
            $this->io->text('grant all on seat.* to seat@localhost identified by \'password\';');
            $this->io->newLine();

            $connected = false;

            while (! $connected) {

                $this->io->text('If you are back here after a failed intall run, check ' .
                    'the file at ~/.seat-credentials for the auto-generated seat users password.');

                $this->io->text('Please provide database details:');
                $username = $this->io->ask('Username');
                $password = $this->io->askHidden('Password', function ($input) {

                    return $input;
                });
                $database = $this->io->ask('Database');

                $mysql->setCredentials([
                    'username' => $username,
                    'password' => $password,
                    'database' => $database,
                ]);

                $connected = $mysql->testCredentails();

                if (! $connected)
                    $this->io->error('Unable to connect to MySql. Please retry.');

            }

            $this->io->success('Database connected!');

            // Save the credentials that worked
            $mysql->saveCredentials();

            // Get the creds from the mysql Object
            $this->mysql_credentials = $mysql->getCredentials();

        } else {

            // MySQL is not installed. Do the installation.
            $mysql->install();

            // Configuration
            $mysql->configure();

            // And save the credentials.
            $mysql->saveCredentials();

            // Get the creds from the mysql Object
            $this->mysql_credentials = $mysql->getCredentials();

        }

    }

    /**
     * Get Redis installed and enabled.
     */
    protected function configureRedis()
    {

        $redis = new Redis($this->io);
        $redis->install();
        $redis->enable();

        $this->io->success('Redis configuration compelte');
    }

    /**
     * Install the OS packages needed for SeAT.
     */
    protected function installPhpPackages()
    {

        $installer = new PackageInstaller($this->io);
        $installer->installPackageGroup('php');

    }

    /**
     * Install SeAT.
     */
    protected function installSeat($minimum_stability)
    {

        $seat = new Seat($this->io);
        $seat->setPath($this->seat_destination);
        $seat->install($minimum_stability);
        $seat->configure($this->mysql_credentials);
        $seat->updateConfigCache();

    }

    /**
     * Install and configure the chosen webserver.
     */
    protected function installWebserver()
    {

        $installer = $this->webserver_info[$this->webserver_choice]['installer'];
        $installer = new $installer($this->io);
        $installer->install();
        $installer->harden();
        $installer->configure($this->seat_destination);

    }

    /**
     * Setup the Crontab.
     */
    protected function setupCrontab()
    {

        // We need to ask the webserver class which user we need
        // to use for the crontab entry.
        $webserver = $this->webserver_info[$this->webserver_choice]['installer'];
        $webserver = new $webserver($this->io);

        $crontab = new Crontab($this->io);
        $crontab->install($this->seat_destination, $webserver->getUser());

    }

    /**
     * Setup Supervisor.
     */
    protected function setupSupervisor()
    {

        // We need to ask the webserver class which user we need
        // to use for the supervisor job.
        $webserver = $this->webserver_info[$this->webserver_choice]['installer'];
        $webserver = new $webserver($this->io);

        // Setup a Supervisor instance.
        $supervisor = new Supervisor($this->io);
        $supervisor->setUser($webserver->getUser());
        $supervisor->setPath($this->seat_destination);
        $supervisor->install();
        $supervisor->setup();
        $supervisor->setupIntegration($this->seat_destination);

        // Terminate Horizon
        $seat = new Seat($this->io);
        $seat->setPath($this->seat_destination);
        $seat->terminateHorizon();

        $supervisor->restart();

    }
}