2019-04-13 04:26:07 +00:00
< ? php
namespace App\Console\Commands ;
use Illuminate\Console\Command ;
2019-12-11 06:04:03 +00:00
use Illuminate\Support\Facades\Redis ;
2022-06-09 06:09:01 +00:00
use \PDO ;
2019-04-13 04:26:07 +00:00
class Installer extends Command
{
/**
* The name and signature of the console command .
*
* @ var string
*/
2022-06-09 06:09:01 +00:00
protected $signature = 'install {--dangerously-overwrite-env : Re-run installation and overwrite current .env }' ;
2019-04-13 04:26:07 +00:00
/**
* The console command description .
*
* @ var string
*/
protected $description = 'CLI Installer' ;
2022-06-09 06:09:01 +00:00
public $installType = 'Simple' ;
2019-04-13 04:26:07 +00:00
/**
* Create a new command instance .
*
* @ return void
*/
public function __construct ()
{
parent :: __construct ();
}
/**
* Execute the console command .
*
* @ return mixed
*/
public function handle ()
{
$this -> welcome ();
}
protected function welcome ()
{
$this -> info ( ' ____ _ ______ __ ' );
$this -> info ( ' / __ \(_) _____ / / __/__ ____/ / ' );
$this -> info ( ' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / ' );
$this -> info ( ' / ____/ /> </ __/ / __/ __/ /_/ / ' );
$this -> info ( ' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ ' );
$this -> info ( ' ' );
$this -> info ( ' Welcome to the Pixelfed Installer!' );
$this -> info ( ' ' );
$this -> info ( ' ' );
$this -> info ( 'Pixelfed version: ' . config ( 'pixelfed.version' ));
$this -> line ( ' ' );
2022-06-15 09:18:49 +00:00
$this -> installerSteps ();
}
protected function installerSteps ()
{
2022-06-09 06:09:01 +00:00
$this -> envCheck ();
2022-06-15 09:18:49 +00:00
$this -> envCreate ();
$this -> installType ();
2022-06-15 09:23:50 +00:00
if ( $this -> installType === 'Advanced' ) {
$this -> info ( 'Installer: Advanced...' );
2022-06-19 06:01:55 +00:00
$this -> checkPHPRequiredDependencies ();
$this -> checkPHPOptionalDependencies ();
2022-06-15 09:18:49 +00:00
$this -> checkFFmpegDependencies ();
2022-06-19 05:28:33 +00:00
$this -> checkOptimiseDependencies ();
2022-06-15 09:18:49 +00:00
$this -> checkDiskPermissions ();
$this -> envProd ();
$this -> instanceDB ();
$this -> instanceRedis ();
$this -> instanceURL ();
$this -> activityPubSettings ();
$this -> laravelSettings ();
$this -> instanceSettings ();
$this -> mediaSettings ();
$this -> dbMigrations ();
2022-06-16 01:32:47 +00:00
$this -> validateEnv ();
2022-06-16 01:35:05 +00:00
$this -> resetArtisanCache ();
2022-06-15 09:18:49 +00:00
} else {
$this -> info ( 'Installer: Simple...' );
2022-06-15 09:23:50 +00:00
$this -> checkDiskPermissions ();
$this -> envProd ();
$this -> instanceDB ();
$this -> instanceRedis ();
$this -> instanceURL ();
$this -> activityPubSettings ();
$this -> instanceSettings ();
$this -> dbMigrations ();
2022-06-16 01:32:47 +00:00
$this -> validateEnv ();
2022-06-16 01:35:05 +00:00
$this -> resetArtisanCache ();
2022-06-15 09:18:49 +00:00
}
2022-06-09 06:09:01 +00:00
}
protected function envCheck ()
{
if ( file_exists ( base_path ( '.env' )) &&
2022-06-15 09:18:49 +00:00
filesize ( base_path ( '.env' )) !== 0 &&
! $this -> option ( 'dangerously-overwrite-env' )
2022-06-09 06:09:01 +00:00
) {
$this -> line ( '' );
2022-06-15 09:18:49 +00:00
$this -> error ( 'Existing .env File Found - Installation Aborted' );
$this -> line ( 'Run the following command to re-run the installer: php artisan install --dangerously-overwrite-env' );
2022-06-09 06:09:01 +00:00
$this -> line ( '' );
exit ;
}
}
2022-06-15 09:18:49 +00:00
protected function envCreate ()
2022-06-09 06:09:01 +00:00
{
2022-06-15 09:18:49 +00:00
$this -> line ( '' );
2022-06-15 10:02:51 +00:00
$this -> info ( 'Creating .env if required' );
2022-06-15 09:18:49 +00:00
if ( ! file_exists ( app () -> environmentFilePath ())) {
2022-06-15 09:39:19 +00:00
exec ( 'cp .env.example .env' );
2022-06-15 09:18:49 +00:00
}
2019-04-13 04:26:07 +00:00
}
2022-06-09 06:09:01 +00:00
2022-06-15 09:18:49 +00:00
protected function installType ()
2019-04-13 04:26:07 +00:00
{
2022-06-15 09:23:50 +00:00
$type = $this -> choice ( 'Select installation type' , [ 'Simple' , 'Advanced' ], 1 );
2022-06-15 09:18:49 +00:00
$this -> installType = $type ;
2019-04-13 04:26:07 +00:00
}
2022-06-19 06:01:55 +00:00
protected function checkPHPRequiredDependencies ()
2019-04-13 04:26:07 +00:00
{
2022-06-15 09:18:49 +00:00
$this -> line ( ' ' );
$this -> info ( 'Checking for required php extensions...' );
2019-04-13 04:26:07 +00:00
$extensions = [
'bcmath' ,
'ctype' ,
'curl' ,
'json' ,
'mbstring' ,
2022-06-09 06:09:01 +00:00
'openssl' ,
2022-06-19 06:01:55 +00:00
];
foreach ( $extensions as $ext ) {
if ( extension_loaded ( $ext ) == false ) {
$this -> error ( " - \" { $ext } \" Required PHP extension not found, aborting installation " );
exit ;
}
}
$this -> info ( " - Required PHP extensions found! " );
}
protected function checkPHPOptionalDependencies ()
{
$this -> line ( ' ' );
$this -> info ( 'Checking For Optional PHP Extensions...' );
$extensions = [
2022-06-19 05:45:13 +00:00
'gd' ,
'intl' ,
'xml' ,
'zip' ,
'redis' ,
2019-04-13 04:26:07 +00:00
];
2022-06-19 05:45:13 +00:00
2019-04-13 04:26:07 +00:00
foreach ( $extensions as $ext ) {
if ( extension_loaded ( $ext ) == false ) {
2022-06-19 06:01:55 +00:00
$this -> error ( " - \" { $ext } \" Optional PHP extension not found " );
} else {
$this -> info ( " - \" { $dep } \" Optional PHP extension found " );
2019-04-13 04:26:07 +00:00
}
}
2022-06-19 06:01:55 +00:00
}
2022-06-15 09:18:49 +00:00
protected function checkFFmpegDependencies ()
{
$this -> line ( ' ' );
$this -> info ( 'Checking for required FFmpeg dependencies...' );
$ffmpeg = exec ( 'which ffmpeg' );
if ( empty ( $ffmpeg )) {
2022-06-19 05:28:33 +00:00
$this -> error ( " - \" { $ext } \" FFmpeg not found, aborting installation " );
2022-06-15 09:18:49 +00:00
exit ;
} else {
$this -> info ( '- Found FFmpeg!' );
}
2019-04-13 04:26:07 +00:00
}
2022-06-19 05:28:33 +00:00
protected function checkOptimiseDependencies ()
{
$this -> line ( ' ' );
2022-06-19 05:30:08 +00:00
$this -> info ( 'Checking for optional Media Optimisation dependencies...' );
2022-06-19 05:28:33 +00:00
$dependencies = [
'jpegoptim' ,
'optipng' ,
'pngquant' ,
'gifsicle' ,
];
foreach ( $dependencies as $dep ) {
$which = exec ( " which $dep " );
if ( empty ( $which )) {
$this -> error ( " - \" { $dep } \" not found " );
} else {
$this -> info ( " - \" { $dep } \" found " );
}
}
}
2019-04-13 04:26:07 +00:00
2022-06-15 09:18:49 +00:00
protected function checkDiskPermissions ()
2019-04-13 04:26:07 +00:00
{
2022-06-15 09:18:49 +00:00
$this -> line ( '' );
$this -> info ( 'Checking for proper filesystem permissions...' );
$this -> callSilently ( 'storage:link' );
2019-04-13 04:26:07 +00:00
$paths = [
base_path ( 'bootstrap' ),
base_path ( 'storage' )
];
foreach ( $paths as $path ) {
if ( is_writeable ( $path ) == false ) {
$this -> error ( " - Invalid permission found! Aborting installation. " );
$this -> error ( " Please make the following path writeable by the web server: " );
$this -> error ( " $path " );
exit ;
} else {
2022-06-15 09:18:49 +00:00
$this -> info ( " - Found valid permissions for { $path } " );
2019-04-13 04:26:07 +00:00
}
}
}
2022-06-15 09:18:49 +00:00
protected function envProd ()
2019-04-13 04:26:07 +00:00
{
$this -> line ( '' );
2022-06-15 09:18:49 +00:00
$this -> info ( 'Enabling production' );
$this -> updateEnvFile ( 'APP_ENV' , 'production' );
$this -> updateEnvFile ( 'APP_DEBUG' , 'false' );
$this -> call ( 'key:generate' , [ '--force' => true ]);
}
protected function instanceDB ()
{
$this -> line ( '' );
$this -> info ( 'Database Settings:' );
2019-06-13 20:09:20 +00:00
$database = $this -> choice ( 'Select database driver' , [ 'mysql' , 'pgsql' ], 0 );
2022-06-09 06:09:01 +00:00
$database_host = $this -> ask ( 'Select database host' , '127.0.0.1' );
$database_port_default = $database === 'mysql' ? 3306 : 5432 ;
$database_port = $this -> ask ( 'Select database port' , $database_port_default );
2019-06-13 20:09:20 +00:00
2022-06-09 06:09:01 +00:00
$database_db = $this -> ask ( 'Select database' , 'pixelfed' );
$database_username = $this -> ask ( 'Select database username' , 'pixelfed' );
2022-06-15 09:18:49 +00:00
$database_password = $this -> secret ( 'Select database password' );
2019-06-13 20:09:20 +00:00
2022-06-15 09:18:49 +00:00
$this -> updateEnvFile ( 'DB_CONNECTION' , $database );
$this -> updateEnvFile ( 'DB_HOST' , $database_host );
$this -> updateEnvFile ( 'DB_PORT' , $database_port );
$this -> updateEnvFile ( 'DB_DATABASE' , $database_db );
$this -> updateEnvFile ( 'DB_USERNAME' , $database_username );
2022-06-09 06:09:01 +00:00
$this -> updateEnvFile ( 'DB_PASSWORD' , $database_password );
2022-06-15 09:18:49 +00:00
$this -> info ( 'Testing Database...' );
2022-06-09 06:09:01 +00:00
$dsn = " { $database } :dbname= { $database_db } ;host= { $database_host } ;port= { $database_port } ; " ;
try {
2022-06-15 09:18:49 +00:00
$dbh = new PDO ( $dsn , $database_username , $database_password , [ PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION ]);
2022-06-09 06:09:01 +00:00
} catch ( \PDOException $e ) {
2022-06-15 09:18:49 +00:00
$this -> error ( 'Cannot connect to database, check your details and try again' );
exit ;
2019-06-13 20:09:20 +00:00
}
2022-06-15 09:18:49 +00:00
$this -> info ( '- Connected to DB Successfully' );
}
2019-06-13 20:09:20 +00:00
2022-06-15 09:18:49 +00:00
protected function instanceRedis ()
{
$this -> line ( '' );
$this -> info ( 'Redis Settings:' );
$redis_client = $this -> choice ( 'Set redis client (PHP extension)' , [ 'phpredis' , 'predis' ], 0 );
$redis_host = $this -> ask ( 'Set redis host' , 'localhost' );
$redis_password = $this -> ask ( 'Set redis password' , 'null' );
$redis_port = $this -> ask ( 'Set redis port' , 6379 );
$this -> updateEnvFile ( 'REDIS_SCHEME' , 'tcp' );
$this -> updateEnvFile ( 'REDIS_HOST' , $redis_host );
$this -> updateEnvFile ( 'REDIS_PASSWORD' , $redis_password );
$this -> updateEnvFile ( 'REDIS_PORT' , $redis_port );
$this -> info ( 'Testing Redis...' );
$redis = Redis :: connection ();
if ( $redis -> ping ()) {
$this -> info ( '- Connected to Redis Successfully!' );
} else {
$this -> error ( 'Cannot connect to Redis, check your details and try again' );
exit ;
}
}
2019-06-13 20:09:20 +00:00
2022-06-15 09:18:49 +00:00
protected function instanceURL ()
{
$this -> line ( '' );
$this -> info ( 'Instance URL Settings:' );
$name = $this -> ask ( 'Site name [ex: Pixelfed]' , 'Pixelfed' );
2019-06-13 20:09:20 +00:00
2022-06-15 09:18:49 +00:00
$domain = $this -> ask ( 'Site Domain [ex: pixelfed.com]' );
$domain = strtolower ( $domain );
if ( empty ( $domain )) {
$this -> error ( 'You must set the site domain' );
exit ;
}
if ( starts_with ( $domain , 'http' )) {
$this -> error ( 'The site domain cannot start with https://, you must use the FQDN (eg: example.org)' );
exit ;
}
if ( strpos ( $domain , '.' ) == false ) {
$this -> error ( 'You must enter a valid site domain' );
exit ;
}
2019-04-13 04:26:07 +00:00
2022-06-15 09:18:49 +00:00
$this -> updateEnvFile ( 'APP_NAME' , $name );
$this -> updateEnvFile ( 'APP_URL' , 'https://' . $domain );
$this -> updateEnvFile ( 'APP_DOMAIN' , $domain );
$this -> updateEnvFile ( 'ADMIN_DOMAIN' , $domain );
$this -> updateEnvFile ( 'SESSION_DOMAIN' , $domain );
}
2019-06-13 20:09:20 +00:00
2022-06-15 09:18:49 +00:00
protected function laravelSettings ()
{
$this -> line ( '' );
$this -> info ( 'Laravel Settings (Defaults are recommended):' );
$session = $this -> choice ( 'Select session driver' , [ " database " , " file " , " cookie " , " redis " , " memcached " , " array " ], 0 );
$cache = $this -> choice ( 'Select cache driver' , [ " redis " , " apc " , " array " , " database " , " file " , " memcached " ], 0 );
$queue = $this -> choice ( 'Select queue driver' , [ " redis " , " database " , " sync " , " beanstalkd " , " sqs " , " null " ], 0 );
$broadcast = $this -> choice ( 'Select broadcast driver' , [ " log " , " redis " , " pusher " , " null " ], 0 );
$log = $this -> choice ( 'Select Log Channel' , [ " stack " , " single " , " daily " , " stderr " , " syslog " , " null " ], 0 );
$horizon = $this -> ask ( 'Set Horizon Prefix [ex: horizon-]' , 'horizon-' );
$this -> updateEnvFile ( 'SESSION_DRIVER' , $session );
$this -> updateEnvFile ( 'CACHE_DRIVER' , $cache );
$this -> updateEnvFile ( 'QUEUE_DRIVER' , $cache );
$this -> updateEnvFile ( 'BROADCAST_DRIVER' , $cache );
$this -> updateEnvFile ( 'LOG_CHANNEL' , $log );
$this -> updateEnvFile ( 'HORIZON_PREFIX' , $horizon );
}
protected function instanceSettings ()
{
$this -> line ( '' );
$this -> info ( 'Instance Settings:' );
$max_registration = $this -> ask ( 'Set Maximum users on this instance.' , '1000' );
2022-06-09 06:09:01 +00:00
$open_registration = $this -> choice ( 'Allow new registrations?' , [ 'false' , 'true' ], 0 );
2022-06-15 09:18:49 +00:00
$enforce_email_verification = $this -> choice ( 'Enforce email verification?' , [ 'false' , 'true' ], 0 );
$enable_mobile_apis = $this -> choice ( 'Enable mobile app/apis support?' , [ 'false' , 'true' ], 1 );
$this -> updateEnvFile ( 'PF_MAX_USERS' , $max_registration );
2019-06-13 20:09:20 +00:00
$this -> updateEnvFile ( 'OPEN_REGISTRATION' , $open_registration );
2022-06-15 09:18:49 +00:00
$this -> updateEnvFile ( 'ENFORCE_EMAIL_VERIFICATION' , $enforce_email_verification );
$this -> updateEnvFile ( 'OAUTH_ENABLED' , $enable_mobile_apis );
$this -> updateEnvFile ( 'EXP_EMC' , $enable_mobile_apis );
}
2019-06-13 20:09:20 +00:00
2022-06-15 09:18:49 +00:00
protected function activityPubSettings ()
{
$this -> line ( '' );
$this -> info ( 'Federation Settings:' );
2022-06-09 06:09:01 +00:00
$activitypub_federation = $this -> choice ( 'Enable ActivityPub federation?' , [ 'false' , 'true' ], 1 );
2022-06-15 09:18:49 +00:00
2022-06-09 06:09:01 +00:00
$this -> updateEnvFile ( 'ACTIVITY_PUB' , $activitypub_federation );
2022-06-15 09:18:49 +00:00
$this -> updateEnvFile ( 'AP_REMOTE_FOLLOW' , $activitypub_federation );
2022-06-09 06:09:01 +00:00
$this -> updateEnvFile ( 'AP_INBOX' , $activitypub_federation );
2022-06-15 09:18:49 +00:00
$this -> updateEnvFile ( 'AP_OUTBOX' , $activitypub_federation );
2022-06-09 06:09:01 +00:00
$this -> updateEnvFile ( 'AP_SHAREDINBOX' , $activitypub_federation );
2022-06-15 09:18:49 +00:00
}
2022-06-09 06:09:01 +00:00
2022-06-15 09:18:49 +00:00
protected function mediaSettings ()
{
$this -> line ( '' );
$this -> info ( 'Media Settings:' );
$optimize_media = $this -> choice ( 'Optimize media uploads? Requires jpegoptim and other dependencies!' , [ 'false' , 'true' ], 1 );
$image_quality = $this -> ask ( 'Set image optimization quality between 1-100. Default is 80%, lower values use less disk space at the expense of image quality.' , '80' );
if ( $image_quality < 1 ) {
$this -> error ( 'Min image quality is 1. You should avoid such a low value, 60 at minimum is recommended.' );
exit ;
}
if ( $image_quality > 100 ) {
$this -> error ( 'Max image quality is 100' );
exit ;
}
2022-06-15 09:29:37 +00:00
$this -> info ( 'Note: Max photo size cannot exceed `post_max_size` in php.ini.' );
2022-06-15 09:18:49 +00:00
$max_photo_size = $this -> ask ( 'Max photo upload size in kilobytes. Default 15000 which is equal to 15MB' , '15000' );
$max_caption_length = $this -> ask ( 'Max caption limit. Default to 500, max 5000.' , '500' );
if ( $max_caption_length > 5000 ) {
$this -> error ( 'Max caption length is 5000 characters.' );
exit ;
}
2022-06-09 06:09:01 +00:00
2022-06-15 09:18:49 +00:00
$max_album_length = $this -> ask ( 'Max photos allowed per album. Choose a value between 1 and 10.' , '4' );
if ( $max_album_length < 1 ) {
$this -> error ( 'Min album length is 1 photos per album.' );
exit ;
}
if ( $max_album_length > 10 ) {
$this -> error ( 'Max album length is 10 photos per album.' );
exit ;
}
$this -> updateEnvFile ( 'PF_OPTIMIZE_IMAGES' , $optimize_media );
$this -> updateEnvFile ( 'IMAGE_QUALITY' , $image_quality );
$this -> updateEnvFile ( 'MAX_PHOTO_SIZE' , $max_photo_size );
$this -> updateEnvFile ( 'MAX_CAPTION_LENGTH' , $max_caption_length );
$this -> updateEnvFile ( 'MAX_ALBUM_LENGTH' , $max_album_length );
}
2022-06-09 06:09:01 +00:00
2022-06-15 09:18:49 +00:00
protected function dbMigrations ()
{
$this -> line ( '' );
$this -> info ( 'Note: We recommend running database migrations now!' );
$confirm = $this -> choice ( 'Do you want to run the database migrations?' , [ 'Yes' , 'No' ], 0 );
2022-06-09 06:09:01 +00:00
2022-06-15 09:18:49 +00:00
if ( $confirm === 'Yes' ) {
sleep ( 3 );
2022-06-15 09:45:12 +00:00
$this -> line ( '' );
$this -> info ( 'Migrating DB:' );
2022-06-15 09:18:49 +00:00
$this -> call ( 'migrate' , [ '--force' => true ]);
2022-06-15 09:45:12 +00:00
$this -> line ( '' );
$this -> info ( 'Importing Cities:' );
2022-06-15 09:45:33 +00:00
$this -> call ( 'import:cities' );
2022-06-15 09:45:12 +00:00
$this -> line ( '' );
$this -> info ( 'Creating Federation Instance Actor:' );
2022-06-15 09:45:33 +00:00
$this -> call ( 'instance:actor' );
2022-06-15 09:45:12 +00:00
$this -> line ( '' );
$this -> info ( 'Creating Password Keys for API:' );
2022-06-15 09:46:11 +00:00
$this -> call ( 'passport:keys' , [ '--force' => true ]);
2022-06-15 09:18:49 +00:00
$confirm = $this -> choice ( 'Do you want to create an admin account?' , [ 'Yes' , 'No' ], 0 );
if ( $confirm === 'Yes' ) {
$this -> call ( 'user:create' );
}
}
}
protected function resetArtisanCache ()
{
2022-06-16 01:32:47 +00:00
$this -> call ( 'config:cache' );
$this -> call ( 'route:cache' );
$this -> call ( 'view:cache' );
}
protected function validateEnv ()
{
$this -> checkEnvKeys ( 'APP_KEY' , " key:generate failed? " );
$this -> checkEnvKeys ( 'APP_ENV' , " APP_ENV value should be production " );
$this -> checkEnvKeys ( 'APP_DEBUG' , " APP_DEBUG value should be false " );
2019-06-13 20:09:20 +00:00
}
2022-06-15 09:18:49 +00:00
#####
# Installer Functions
#####
2022-06-16 01:32:47 +00:00
protected function checkEnvKeys ( $key , $error )
{
$envPath = app () -> environmentFilePath ();
$payload = file_get_contents ( $envPath );
if ( $existing = $this -> existingEnv ( $key , $payload )) {
} else {
$this -> info ( " $key empty - $error " );
}
}
2019-06-13 20:09:20 +00:00
protected function updateEnvFile ( $key , $value )
{
$envPath = app () -> environmentFilePath ();
$payload = file_get_contents ( $envPath );
if ( $existing = $this -> existingEnv ( $key , $payload )) {
$payload = str_replace ( " { $key } = { $existing } " , " { $key } = \" { $value } \" " , $payload );
$this -> storeEnv ( $payload );
} else {
$payload = $payload . " \n { $key } = \" { $value } \" \n " ;
$this -> storeEnv ( $payload );
}
}
protected function existingEnv ( $needle , $haystack )
{
preg_match ( " /^ { $needle } =[^ \r \n ]*/m " , $haystack , $matches );
if ( $matches && count ( $matches )) {
return substr ( $matches [ 0 ], strlen ( $needle ) + 1 );
}
return false ;
}
protected function storeEnv ( $payload )
{
$file = fopen ( app () -> environmentFilePath (), 'w' );
fwrite ( $file , $payload );
fclose ( $file );
2019-04-13 04:26:07 +00:00
}
2022-06-09 06:09:01 +00:00
protected function parseSize ( $size ) {
2022-06-15 09:18:49 +00:00
$unit = preg_replace ( '/[^bkmgtpezy]/i' , '' , $size );
$size = preg_replace ( '/[^0-9\.]/' , '' , $size );
if ( $unit ) {
return round ( $size * pow ( 1024 , stripos ( 'bkmgtpezy' , $unit [ 0 ])));
}
else {
return round ( $size );
}
2022-06-09 06:09:01 +00:00
}
2019-04-13 04:26:07 +00:00
}