├── app ├── stubs │ ├── php │ │ ├── ext-imagick.ini │ │ ├── ext-redis.ini │ │ ├── ext-memcached.ini │ │ ├── ext-ioncube.ini │ │ ├── z-session-memcached.ini │ │ ├── ext-xdebug.ini │ │ ├── ext-apcu.ini │ │ ├── ext-xdebug-5.6.ini │ │ ├── ext-xdebug-7.0.ini │ │ ├── ext-xdebug-7.1.ini │ │ ├── ext-opcache.ini │ │ ├── z-performance.ini │ │ └── smtp_catcher.php │ ├── localhost │ │ ├── no-entry.jpg │ │ └── index.php │ ├── httpd-vhost.conf │ ├── httpd-vhost-ssl.conf │ ├── config │ │ ├── filesystems.php │ │ └── env.php │ ├── httpd-proxy-vhost.conf │ ├── httpd-proxy-vhost-ssl.conf │ ├── openssl.conf │ ├── my.cnf │ ├── httpd.conf │ └── completion │ │ └── bash ├── Facades │ ├── Stub.php │ ├── Cli.php │ ├── MemcachedSession.php │ ├── PhpHelper.php │ ├── IonCubeHelper.php │ ├── BrewService.php │ ├── File.php │ ├── Secure.php │ ├── ApacheHelper.php │ ├── Brew.php │ └── PeclHelper.php ├── Commands │ ├── Memcached │ │ ├── FlushCommand.php │ │ ├── RestartCommand.php │ │ ├── InstallCommand.php │ │ ├── StopCommand.php │ │ ├── StartCommand.php │ │ ├── UninstallCommand.php │ │ └── SessionCommand.php │ ├── Apache │ │ ├── RestartCommand.php │ │ ├── StopCommand.php │ │ ├── StartCommand.php │ │ ├── HostRevokeCommand.php │ │ ├── HostPortCreateCommand.php │ │ ├── InstallCommand.php │ │ ├── HostCreateCommand.php │ │ └── UninstallCommand.php │ ├── MySql │ │ ├── RestartCommand.php │ │ ├── StopCommand.php │ │ ├── StartCommand.php │ │ ├── UninstallCommand.php │ │ └── InstallCommand.php │ ├── Redis │ │ ├── RestartCommand.php │ │ ├── StopCommand.php │ │ ├── UninstallCommand.php │ │ ├── StartCommand.php │ │ ├── InstallCommand.php │ │ └── FlushCommand.php │ ├── DnsMasq │ │ ├── RestartCommand.php │ │ ├── StopCommand.php │ │ ├── StartCommand.php │ │ ├── UninstallCommand.php │ │ └── InstallCommand.php │ ├── MailHog │ │ ├── RestartCommand.php │ │ ├── StopCommand.php │ │ ├── StartCommand.php │ │ ├── InstallCommand.php │ │ └── UninstallCommand.php │ ├── RabbitMq │ │ ├── RestartCommand.php │ │ ├── StopCommand.php │ │ ├── StartCommand.php │ │ ├── VhostListCommand.php │ │ ├── InstallCommand.php │ │ ├── VhostDeleteCommand.php │ │ ├── QueueListCommand.php │ │ ├── VhostCreateCommand.php │ │ └── UninstallCommand.php │ ├── Services │ │ ├── StopCommand.php │ │ ├── StartCommand.php │ │ └── StatusCommand.php │ ├── Database │ │ ├── ListCommand.php │ │ ├── DropCommand.php │ │ ├── CreateCommand.php │ │ ├── InstallCommand.php │ │ ├── ExportCommand.php │ │ └── ImportCommand.php │ ├── Site │ │ ├── UnlinkCommand.php │ │ ├── LinkProxyCommand.php │ │ └── LinkCommand.php │ ├── Secure │ │ ├── InstallCommand.php │ │ ├── RevokeCommand.php │ │ └── GenerateCommand.php │ ├── CompletionCommand.php │ ├── Php │ │ ├── SwitchCommand.php │ │ ├── IoncubeCommand.php │ │ ├── UninstallCommand.php │ │ ├── XdebugCommand.php │ │ └── InstallCommand.php │ ├── InstallCommand.php │ ├── ConfigDumpCommand.php │ └── UninstallCommand.php ├── Helper │ └── ConfigMerge.php ├── Services │ ├── Stubs.php │ ├── CommandLine.php │ ├── Php.php │ ├── MemcachedSession.php │ ├── Files.php │ ├── Brew.php │ ├── IonCube.php │ ├── BrewService.php │ ├── Pecl.php │ ├── Secure.php │ └── Apache.php ├── Command.php └── Providers │ └── AppServiceProvider.php ├── .gitignore ├── box.json ├── config ├── env.php ├── app.php └── commands.php ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── patches └── command-revert.patch ├── composer.json ├── bootstrap └── app.php ├── sage └── README.md /app/stubs/php/ext-imagick.ini: -------------------------------------------------------------------------------- 1 | [imagick] 2 | extension="imagick.so" -------------------------------------------------------------------------------- /app/stubs/php/ext-redis.ini: -------------------------------------------------------------------------------- 1 | [redis] 2 | extension="redis.so" 3 | -------------------------------------------------------------------------------- /app/stubs/php/ext-memcached.ini: -------------------------------------------------------------------------------- 1 | [memcached] 2 | extension="memcached.so" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | /.vscode 4 | /.vagrant 5 | /builds/* 6 | -------------------------------------------------------------------------------- /app/stubs/php/ext-ioncube.ini: -------------------------------------------------------------------------------- 1 | [ioncube] 2 | zend_extension="ioncube.so" 3 | -------------------------------------------------------------------------------- /app/stubs/localhost/no-entry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytorbyk/sage/HEAD/app/stubs/localhost/no-entry.jpg -------------------------------------------------------------------------------- /app/stubs/php/z-session-memcached.ini: -------------------------------------------------------------------------------- 1 | session.save_handler=memcached 2 | session.save_path=127.0.0.1:11211 3 | -------------------------------------------------------------------------------- /app/stubs/php/ext-xdebug.ini: -------------------------------------------------------------------------------- 1 | [xdebug] 2 | zend_extension="xdebug.so" 3 | xdebug.mode=debug 4 | #xdebug.start_with_request=yes 5 | -------------------------------------------------------------------------------- /app/stubs/php/ext-apcu.ini: -------------------------------------------------------------------------------- 1 | [apcu] 2 | extension="apcu.so" 3 | apc.enabled=1 4 | apc.shm_size=64M 5 | apc.ttl=7200 6 | apc.enable_cli=1 7 | -------------------------------------------------------------------------------- /app/stubs/php/ext-xdebug-5.6.ini: -------------------------------------------------------------------------------- 1 | [xdebug] 2 | zend_extension="xdebug.so" 3 | xdebug.remote_enable=1 4 | xdebug.remote_host=localhost 5 | xdebug.remote_handler=dbgp 6 | xdebug.remote_port=9000 7 | xdebug.remote_autostart=0 8 | -------------------------------------------------------------------------------- /app/stubs/php/ext-xdebug-7.0.ini: -------------------------------------------------------------------------------- 1 | [xdebug] 2 | zend_extension="xdebug.so" 3 | xdebug.remote_enable=1 4 | xdebug.remote_host=localhost 5 | xdebug.remote_handler=dbgp 6 | xdebug.remote_port=9000 7 | xdebug.remote_autostart=0 8 | -------------------------------------------------------------------------------- /app/stubs/php/ext-xdebug-7.1.ini: -------------------------------------------------------------------------------- 1 | [xdebug] 2 | zend_extension="xdebug.so" 3 | xdebug.remote_enable=1 4 | xdebug.remote_host=localhost 5 | xdebug.remote_handler=dbgp 6 | xdebug.remote_port=9000 7 | xdebug.remote_autostart=0 8 | -------------------------------------------------------------------------------- /app/stubs/httpd-vhost.conf: -------------------------------------------------------------------------------- 1 | 2 | DocumentRoot "DOCUMENT_ROOT" 3 | 4 | ServerName DOMAIN 5 | SERVER_ALIAS 6 | 7 | ErrorLog "LOGS_PATH/DOMAIN-error.log" 8 | CustomLog "LOGS_PATH/DOMAIN-access.log" common 9 | 10 | VIRTUAL_HOST_SSL 11 | -------------------------------------------------------------------------------- /app/stubs/httpd-vhost-ssl.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | DocumentRoot "DOCUMENT_ROOT" 4 | 5 | ServerName DOMAIN 6 | SERVER_ALIAS 7 | 8 | ErrorLog "LOGS_PATH/DOMAIN-ssl-error.log" 9 | CustomLog "LOGS_PATH/DOMAIN-ssl-access.log" common 10 | 11 | SSLEngine on 12 | SSLCertificateFile "CERTIFICATE_CRT" 13 | SSLCertificateKeyFile "CERTIFICATE_KEY" 14 | 15 | -------------------------------------------------------------------------------- /app/stubs/localhost/index.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /app/stubs/config/filesystems.php: -------------------------------------------------------------------------------- 1 | 'dump', 5 | 'disks' => [ 6 | 'dump' => [ 7 | 'driver' => 'local', 8 | 'root' => config('env.db.dump_path'), 9 | 'disable_asserts' => true 10 | ], 11 | 'm2_configs' => [ 12 | 'driver' => 'local', 13 | 'root' => config('env.m2.configs_path') 14 | ] 15 | ] 16 | ]; 17 | -------------------------------------------------------------------------------- /app/stubs/httpd-proxy-vhost.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerName DOMAIN 3 | SERVER_ALIAS 4 | 5 | 6 | ProxyPreserveHost On 7 | ProxyPass http://localhost:PORT/ 8 | ProxyPassReverse http://localhost:PORT/ 9 | 10 | 11 | ErrorLog "LOGS_PATH/DOMAIN-error.log" 12 | CustomLog "LOGS_PATH/DOMAIN-access.log" common 13 | 14 | VIRTUAL_HOST_SSL 15 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | "directories": [ 4 | "app", 5 | "bootstrap", 6 | "config", 7 | "patches", 8 | "vendor" 9 | ], 10 | "files": [ 11 | "composer.json" 12 | ], 13 | "exclude-composer-files": false, 14 | "compression": "GZ", 15 | "shebang": "#!/usr/bin/php", 16 | "compactors": [ 17 | "Herrera\\Box\\Compactor\\Php", 18 | "Herrera\\Box\\Compactor\\Json" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /app/stubs/httpd-proxy-vhost-ssl.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | ServerName DOMAIN 4 | SERVER_ALIAS 5 | 6 | 7 | ProxyPreserveHost On 8 | ProxyPass http://localhost:PORT/ 9 | ProxyPassReverse http://localhost:PORT/ 10 | 11 | 12 | ErrorLog "LOGS_PATH/DOMAIN-ssl-error.log" 13 | CustomLog "LOGS_PATH/DOMAIN-ssl-access.log" common 14 | 15 | SSLEngine on 16 | SSLCertificateFile "CERTIFICATE_CRT" 17 | SSLCertificateKeyFile "CERTIFICATE_KEY" 18 | 19 | -------------------------------------------------------------------------------- /app/Facades/Stub.php: -------------------------------------------------------------------------------- 1 | env('HOME') . DIRECTORY_SEPARATOR . $homeFolder, 9 | 'home_public' => env('HOME') . DIRECTORY_SEPARATOR .'x' . $app['name'], 10 | 'config_path' => env('HOME') . DIRECTORY_SEPARATOR . $homeFolder . DIRECTORY_SEPARATOR . 'config.php', 11 | ]; 12 | 13 | if (file_exists($env['config_path'])) { 14 | $localConfig = include $env['config_path']; 15 | if (!empty($localConfig) && is_array($localConfig)) { 16 | return $env + $localConfig; 17 | } 18 | } 19 | return $env; 20 | -------------------------------------------------------------------------------- /app/Facades/MemcachedSession.php: -------------------------------------------------------------------------------- 1 | mergeRecursiveConfigFrom(require $path, $key); 17 | } 18 | 19 | /** 20 | * @param array $config 21 | * @param string $key 22 | * @return void 23 | */ 24 | protected function mergeRecursiveConfigFrom(array $config, $key) 25 | { 26 | config([$key => array_replace_recursive($config, (array)config($key))]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Commands/Apache/RestartCommand.php: -------------------------------------------------------------------------------- 1 | info('Apache Restart:'); 29 | $this->call(StopCommand::COMMAND); 30 | $this->call(StartCommand::COMMAND); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /patches/command-revert.patch: -------------------------------------------------------------------------------- 1 | 2 | @package laravel-zero/framework 3 | 4 | Index: src/Commands/Command.php 5 | IDEA additional info: 6 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 7 | <+>UTF-8 8 | =================================================================== 9 | --- src/Commands/Command.php (date 1546196279000) 10 | +++ src/Commands/Command.php (date 1546196279000) 11 | @@ -51,7 +51,7 @@ 12 | * Performs the given task, outputs and 13 | * returns the result. 14 | */ 15 | - public function task(string $title = '', $task = null): bool 16 | + public function task(string $title = '', $task = null) 17 | { 18 | return $this->__call('task', func_get_args()); 19 | } 20 | -------------------------------------------------------------------------------- /app/Commands/MySql/RestartCommand.php: -------------------------------------------------------------------------------- 1 | info('MySQL Restart:'); 29 | 30 | $this->call(StopCommand::COMMAND); 31 | $this->call(StartCommand::COMMAND); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Commands/Redis/RestartCommand.php: -------------------------------------------------------------------------------- 1 | info('Redis restart:'); 29 | 30 | $this->call(StopCommand::COMMAND); 31 | $this->call(StartCommand::COMMAND); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Facades/IonCubeHelper.php: -------------------------------------------------------------------------------- 1 | info('DnsMasq restart:'); 29 | 30 | $this->call(StopCommand::COMMAND); 31 | $this->call(StartCommand::COMMAND); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Facades/BrewService.php: -------------------------------------------------------------------------------- 1 | info('MailHog restart:'); 29 | 30 | $this->call(StopCommand::COMMAND); 31 | $this->call(StartCommand::COMMAND); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/RestartCommand.php: -------------------------------------------------------------------------------- 1 | info('RabbitMq restart:'); 29 | 30 | $this->call(StopCommand::COMMAND); 31 | $this->call(StartCommand::COMMAND); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Commands/Memcached/RestartCommand.php: -------------------------------------------------------------------------------- 1 | info('Memcached restart:'); 29 | 30 | $this->call(StopCommand::COMMAND); 31 | $this->call(StartCommand::COMMAND); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Facades/File.php: -------------------------------------------------------------------------------- 1 | info('Stop services:'); 31 | foreach ($services as $service) { 32 | $this->call($service . ':stop'); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Commands/Services/StartCommand.php: -------------------------------------------------------------------------------- 1 | info('Start services:'); 31 | foreach ($services as $service) { 32 | $this->call($service . ':start'); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Commands/Database/ListCommand.php: -------------------------------------------------------------------------------- 1 | argument('query'); 31 | $query = $query ? '%' . $query . '%' : ''; 32 | Cli::passthru('mysqlshow ' . $query); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Commands/Database/DropCommand.php: -------------------------------------------------------------------------------- 1 | argument('name'); 30 | 31 | $this->task(sprintf('Drop DB %s if exists', $name), function () use ($name) { 32 | Cli::run(sprintf("mysql -e 'DROP DATABASE IF EXISTS `%s`'", $name)); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Commands/MySql/StopCommand.php: -------------------------------------------------------------------------------- 1 | task('MySQL Stop', function () { 30 | try { 31 | BrewService::stop((string)config('env.mysql.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/Redis/StopCommand.php: -------------------------------------------------------------------------------- 1 | task('Redis Stop', function () { 30 | try { 31 | BrewService::stop((string)config('env.redis.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/Redis/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Uninstall Redis:'); 30 | 31 | if (Brew::isInstalled((string)config('env.redis.formula'))) { 32 | $this->call(StopCommand::COMMAND); 33 | $this->uninstallFormula((string)config('env.redis.formula')); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Commands/Site/UnlinkCommand.php: -------------------------------------------------------------------------------- 1 | info('Unlink website:'); 32 | 33 | $this->call(HostRevokeCommand::COMMAND, ['domain' => $this->argument('domain')]); 34 | $this->call(RestartCommand::COMMAND); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Commands/Apache/StopCommand.php: -------------------------------------------------------------------------------- 1 | task('Apache Stop', function () { 30 | try { 31 | BrewService::stop((string)config('env.apache.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/DnsMasq/StopCommand.php: -------------------------------------------------------------------------------- 1 | task('DnsMasq Stop', function () { 30 | try { 31 | BrewService::stop((string)config('env.dns.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/MySql/StartCommand.php: -------------------------------------------------------------------------------- 1 | task('MySQL Start', function () { 30 | try { 31 | BrewService::start((string)config('env.mysql.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/Redis/StartCommand.php: -------------------------------------------------------------------------------- 1 | task('Redis Start', function () { 30 | try { 31 | BrewService::start((string)config('env.redis.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/Memcached/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Install Memcached:'); 30 | 31 | $this->installFormula((string)config('env.memcached.formula')); 32 | 33 | foreach (config('env.memcached.dependencies') as $formula) { 34 | Brew::ensureInstalled($formula); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/DnsMasq/StartCommand.php: -------------------------------------------------------------------------------- 1 | task('DnsMasq Start', function () { 30 | try { 31 | BrewService::start((string)config('env.dns.formula'), true); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/MailHog/StopCommand.php: -------------------------------------------------------------------------------- 1 | task('MailHog Stop', function () { 30 | try { 31 | BrewService::stop((string)config('env.mailhog.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/Redis/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Install Redis:'); 29 | 30 | $needInstall = $this->installFormula((string)config('env.redis.formula')); 31 | 32 | if ($needInstall) { 33 | $this->call(StartCommand::COMMAND); 34 | } else { 35 | $this->call(RestartCommand::COMMAND); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Commands/Apache/StartCommand.php: -------------------------------------------------------------------------------- 1 | task('Apache Start', function () { 30 | try { 31 | BrewService::start((string)config('env.apache.formula'), true); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/MailHog/StartCommand.php: -------------------------------------------------------------------------------- 1 | task('MailHog Start', function () { 30 | try { 31 | BrewService::start((string)config('env.mailhog.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/StopCommand.php: -------------------------------------------------------------------------------- 1 | task('RabbitMq Stop', function () { 30 | try { 31 | BrewService::stop((string)config('env.rabbitmq.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/Memcached/StopCommand.php: -------------------------------------------------------------------------------- 1 | task('Memcached Stop', function () { 30 | try { 31 | BrewService::stop((string)config('env.memcached.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/StartCommand.php: -------------------------------------------------------------------------------- 1 | task('RabbitMq Start', function () { 30 | try { 31 | BrewService::start((string)config('env.rabbitmq.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/Redis/FlushCommand.php: -------------------------------------------------------------------------------- 1 | argument('db'); 31 | 32 | if (empty($db) && $db !== '0') { 33 | Cli::passthru('redis-cli FLUSHALL'); 34 | } else { 35 | Cli::passthru(sprintf('redis-cli -n %d FLUSHDB', (int)$db)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Commands/Memcached/StartCommand.php: -------------------------------------------------------------------------------- 1 | task('Memcached Start', function () { 30 | try { 31 | BrewService::start((string)config('env.memcached.formula')); 32 | } catch (\Exception $e) { 33 | return $e->getMessage(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Facades/ApacheHelper.php: -------------------------------------------------------------------------------- 1 | info('Install secure stuff:'); 30 | 31 | $this->installFormula((string)config('env.secure.formula')); 32 | 33 | $this->task('Ensure certificate directory created', function () { 34 | File::ensureDirExists((string)config('env.secure.certificates_path')); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/stubs/my.cnf: -------------------------------------------------------------------------------- 1 | [client] 2 | user=root 3 | password=MYSQL_PASSWORD 4 | 5 | [mysqld] 6 | sql_mode="NO_AUTO_VALUE_ON_ZERO" 7 | bind-address = 127.0.0.1 8 | log-error=LOGS_PATH/mysql.log 9 | 10 | collation_server=utf8_general_ci 11 | character_set_server=utf8 12 | init-connect='SET NAMES utf8' 13 | 14 | max_allowed_packet = 1073741824 15 | innodb_strict_mode = off 16 | query_cache_limit = 64M 17 | query_cache_size = 512M 18 | query_cache_type = 1 19 | table_open_cache = 100 20 | wait_timeout = 28800 21 | tmp_table_size = 1G 22 | max_heap_table_size = 1G 23 | innodb_buffer_pool_size = 1500M 24 | innodb_log_buffer_size = 128M 25 | innodb_lock_wait_timeout= 200 26 | log-queries-not-using-indexes 27 | long_query_time = 2 28 | skip-external-locking 29 | 30 | #innodb_force_recovery = 1 31 | 32 | [mysqldump] 33 | quick 34 | quote-names 35 | max_allowed_packet = 1073741824 36 | -------------------------------------------------------------------------------- /app/stubs/php/ext-opcache.ini: -------------------------------------------------------------------------------- 1 | opcache.enable = 1 2 | opcache.enable_cli = 1 3 | opcache.memory_consumption = 2048 4 | opcache.interned_strings_buffer = 20 5 | opcache.file_cache=1 6 | opcache.max_accelerated_files = 80000 7 | opcache.max_wasted_percentage = 5 8 | opcache.use_cwd = 1 9 | opcache.validate_timestamps = 1 10 | opcache.revalidate_freq = 0 11 | opcache.file_update_protection = 2 12 | opcache.revalidate_path = 0 13 | opcache.save_comments = 1 14 | opcache.load_comments = 1 15 | opcache.fast_shutdown = 1 16 | opcache.enable_file_override = 0 17 | opcache.optimization_level = 0xffffffff 18 | opcache.inherited_hack = 1 19 | opcache.blacklist_filename = "" 20 | opcache.max_file_size = 0 21 | opcache.consistency_checks = 0 22 | opcache.force_restart_timeout = 180 23 | opcache.error_log = "" 24 | opcache.log_verbosity_level = 1 25 | opcache.preferred_memory_model = "" 26 | opcache.protect_memory = 0 27 | apc.cache_by_default = false 28 | -------------------------------------------------------------------------------- /app/Commands/Apache/HostRevokeCommand.php: -------------------------------------------------------------------------------- 1 | argument('domain'); 32 | 33 | $this->call(RevokeCommand::COMMAND, ['domain' => $domain]); 34 | 35 | $this->task('Delete Apache Virtual Host', function () use ($domain) { 36 | ApacheHelper::deleteVHost($domain); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/VhostListCommand.php: -------------------------------------------------------------------------------- 1 | getOutput()->write(Cli::run('rabbitmqadmin list vhosts name')); 31 | } catch (\Symfony\Component\Process\Exception\ProcessFailedException $e) { 32 | $this->error(trim($e->getProcess()->getErrorOutput())); 33 | } catch (\Exception $e) { 34 | $this->error($e->getMessage()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Commands/Database/CreateCommand.php: -------------------------------------------------------------------------------- 1 | argument('name'); 30 | 31 | if ($this->option('force')) { 32 | $this->call(DropCommand::COMMAND, ['name' => $name]); 33 | } 34 | 35 | $this->task(sprintf('Create DB %s if not exists', $name), function () use ($name) { 36 | Cli::run(sprintf("mysql -e 'CREATE DATABASE IF NOT EXISTS `%s`'", $name)); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Commands/Memcached/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Uninstall Memcached:'); 30 | 31 | foreach (config('env.memcached.dependencies') as $formula) { 32 | Brew::ensureUninstalled($formula); 33 | } 34 | 35 | if (Brew::isInstalled((string)config('env.memcached.formula'))) { 36 | $this->call(StopCommand::COMMAND); 37 | $this->uninstallFormula((string)config('env.memcached.formula')); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Services/Stubs.php: -------------------------------------------------------------------------------- 1 | getPath($name)); 29 | } 30 | 31 | /** 32 | * @param string $name 33 | * @param array $vars 34 | * @return string 35 | */ 36 | public function get(string $name, array $vars = []): string 37 | { 38 | $content = File::get($this->getPath($name)); 39 | 40 | foreach ($vars as $name => $value) { 41 | $content = str_replace($name, $value, $content); 42 | } 43 | 44 | return $content; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/stubs/php/z-performance.ini: -------------------------------------------------------------------------------- 1 | ; Add TIMEZONE string for replacing with machine timezone 2 | date.timezone = "TIMEZONE" 3 | 4 | ; Max execution time per request 5 | max_execution_time = 18000 6 | 7 | ; Max memory per instance 8 | memory_limit = 4G 9 | 10 | ; The maximum size of an uploaded file. 11 | upload_max_filesize = 128M 12 | 13 | ; Sets max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize 14 | post_max_size = 128M 15 | 16 | ; Sets max input vars. Sometimes Magento sends too many variables, for instance saving big bundle product 17 | max_input_vars = 6000 18 | 19 | session.auto_start = off 20 | session.gc_probability = 0 21 | suhosin.session.cryptua = off 22 | 23 | ; Disable garbage collector 24 | zend.enable_gc = off 25 | 26 | ; Transparent output compression using the zlib library 27 | zlib.output_compression = On 28 | 29 | ; Use Smtp for sending emails 30 | sendmail_path = SMTP_CATCHER_PATH 31 | 32 | phar.readonly = Off 33 | 34 | error_log = ERROR_LOG_FILE 35 | 36 | sys_temp_dir = '/private/var/tmp/' 37 | -------------------------------------------------------------------------------- /app/Commands/Secure/RevokeCommand.php: -------------------------------------------------------------------------------- 1 | argument('domain'); 31 | 32 | $this->task('Delete SSL certificate', function () use ($domain) { 33 | if (Secure::hasPredefined($domain)) { 34 | return 'SSL certificate is predefined. Skipping...'; 35 | } 36 | 37 | if (!Secure::canGenerate($domain)) { 38 | return 'The domain cannot be secured. Skipping...'; 39 | } 40 | 41 | Secure::delete($domain); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Facades/Brew.php: -------------------------------------------------------------------------------- 1 | info('Install MailHog:'); 31 | 32 | $needInstall = $this->installFormula((string)config('env.mailhog.formula')); 33 | 34 | Secure::generate((string)config('env.mailhog.domain')); 35 | ApacheHelper::configureProxyVHost((string)config('env.mailhog.domain'), '8025'); 36 | 37 | if ($needInstall) { 38 | $this->call(StartCommand::COMMAND); 39 | } else { 40 | $this->call(RestartCommand::COMMAND); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Install RabbitMq:'); 31 | 32 | $needInstall = $this->installFormula((string)config('env.rabbitmq.formula')); 33 | 34 | Secure::generate((string)config('env.rabbitmq.domain')); 35 | ApacheHelper::configureProxyVHost((string)config('env.rabbitmq.domain'), '15672'); 36 | 37 | if ($needInstall) { 38 | $this->call(StartCommand::COMMAND); 39 | } else { 40 | $this->call(RestartCommand::COMMAND); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Commands/MySql/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Uninstall MySQL:'); 31 | 32 | $this->uninstallFormula((string)config('env.mysql.formula')); 33 | 34 | $this->task('Delete configuration', function () { 35 | File::delete(config('env.mysql.brew_config_path')); 36 | }); 37 | 38 | if ($this->option('force')) { 39 | $this->task('Delete Data', function () { 40 | File::deleteDirectory((string)config('env.mysql.data_dir_path')); 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yurii/sage", 3 | "description": "CLI helper for local development", 4 | "keywords": ["brew", "console", "cli", "mamp", "sage"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Yurii Torbyk", 9 | "email": "mail@yurii.me" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.2", 14 | "laravel-zero/framework": "^6.4", 15 | "nunomaduro/laravel-console-menu": "^2.3", 16 | "symfony/yaml": "^5.3", 17 | "vaimo/composer-patches": "^4.22" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "App\\": "app/" 22 | } 23 | }, 24 | "config": { 25 | "preferred-install": "dist", 26 | "sort-packages": true, 27 | "optimize-autoloader": true, 28 | "platform": { 29 | "ext-posix": "0" 30 | }, 31 | "allow-plugins": { 32 | "vaimo/composer-patches": true 33 | } 34 | }, 35 | "minimum-stability": "dev", 36 | "prefer-stable": true, 37 | "bin": ["sage"], 38 | "extra": { 39 | "patches-search": "patches" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Commands/Site/LinkProxyCommand.php: -------------------------------------------------------------------------------- 1 | info('Link proxy website:'); 33 | 34 | $this->call(HostPortCreateCommand::COMMAND, [ 35 | 'port' => $this->argument('port'), 36 | 'domain' => $this->argument('domain'), 37 | 'aliases' => $this->argument('aliases'), 38 | '--not-secure' => $this->option('not-secure') 39 | ]); 40 | 41 | $this->call(RestartCommand::COMMAND); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/VhostDeleteCommand.php: -------------------------------------------------------------------------------- 1 | argument('name'); 31 | 32 | $this->task(sprintf('Delete RabbitMQ VHost %s', $name), static function () use ($name) { 33 | try { 34 | Cli::run(sprintf('rabbitmqadmin delete vhost name=%s', $name)); 35 | } catch (\Symfony\Component\Process\Exception\ProcessFailedException $e) { 36 | return trim($e->getProcess()->getErrorOutput()); 37 | } catch (\Exception $e) { 38 | return $e->getMessage(); 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Facades/PeclHelper.php: -------------------------------------------------------------------------------- 1 | info('Link website:'); 34 | 35 | $this->call(HostCreateCommand::COMMAND, [ 36 | 'domain' => $this->argument('domain'), 37 | 'aliases' => $this->argument('aliases'), 38 | '--path' => $this->option('path'), 39 | '--not-secure' => $this->option('not-secure') 40 | ]); 41 | 42 | $this->call(RestartCommand::COMMAND); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Commands/Services/StatusCommand.php: -------------------------------------------------------------------------------- 1 | info('Services:'); 35 | foreach ($services as $service) { 36 | $isRunning = $servicesStatus[(string)config(sprintf('env.%s.formula', $service))] ?? false; 37 | $status = $isRunning ? $this->successText('running') : $this->errorText('stopped'); 38 | 39 | $this->comment(sprintf("%-{$maxLength}s %s", $service . ':', $status)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/QueueListCommand.php: -------------------------------------------------------------------------------- 1 | argument('vhost'); 31 | $columns = $vhostName ? 'name' : 'vhost name'; 32 | $vhostFilter = $vhostName ? '-V ' . $vhostName : ''; 33 | 34 | try { 35 | $this->getOutput()->write(Cli::run(sprintf('rabbitmqadmin list queues %s %s', $columns, $vhostFilter))); 36 | } catch (\Symfony\Component\Process\Exception\ProcessFailedException $e) { 37 | $this->error(trim($e->getProcess()->getErrorOutput())); 38 | } catch (\Exception $e) { 39 | $this->error($e->getMessage()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Commands/Secure/GenerateCommand.php: -------------------------------------------------------------------------------- 1 | argument('domain'); 31 | $aliases = $this->argument('aliases'); 32 | 33 | $this->task('Generate SSL certificate', function () use ($domain, $aliases) { 34 | if (!Secure::canSecure($domain)) { 35 | return $this->errorText('The domain cannot be secured'); 36 | } 37 | 38 | if (Secure::canGenerate($domain)) { 39 | Secure::delete($domain); 40 | Secure::generate($domain, $aliases); 41 | return true; 42 | } 43 | 44 | return 'SSL certificate is predefined. Skipping...'; 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Commands/MailHog/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Uninstall MailHog:'); 32 | 33 | if (Brew::isInstalled((string)config('env.mailhog.formula'))) { 34 | $this->call(StopCommand::COMMAND); 35 | $this->uninstallFormula((string)config('env.mailhog.formula')); 36 | } 37 | 38 | ApacheHelper::deleteVHost((string)config('env.mailhog.domain')); 39 | 40 | $this->task('Delete MailHog Data', function () { 41 | $this->deleteData(); 42 | }); 43 | } 44 | 45 | /** 46 | * @return void 47 | */ 48 | private function deleteData() 49 | { 50 | File::deleteDirectory((string)config('env.mailhog.log_path')); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/VhostCreateCommand.php: -------------------------------------------------------------------------------- 1 | argument('name'); 32 | 33 | if ($this->option('force')) { 34 | $this->call(VhostDeleteCommand::COMMAND, ['name' => $name]); 35 | } 36 | 37 | $this->task(sprintf('Create RabbitMQ VHost %s', $name), static function () use ($name) { 38 | try { 39 | Cli::run(sprintf('rabbitmqadmin declare vhost name=%s', $name)); 40 | } catch (\Symfony\Component\Process\Exception\ProcessFailedException $e) { 41 | return trim($e->getProcess()->getErrorOutput()); 42 | } catch (\Exception $e) { 43 | return $e->getMessage(); 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Commands/DnsMasq/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Uninstall DnsMasq:'); 32 | 33 | if (Brew::isInstalled((string)config('env.dns.formula'))) { 34 | $this->call(StopCommand::COMMAND); 35 | $this->uninstallFormula((string)config('env.dns.formula')); 36 | } 37 | 38 | $this->task('Delete DnsMasq config', function () { 39 | $this->deleteConfig(); 40 | }); 41 | } 42 | 43 | /** 44 | * @return void 45 | */ 46 | private function deleteConfig() 47 | { 48 | Cli::run(sprintf('sudo rm -rf %s', config('env.dns.resolver_path'))); 49 | File::delete(config('env.dns.brew_config_path')); 50 | File::deleteDirectory((string)config('env.dns.brew_config_dir_path')); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Commands/Apache/HostPortCreateCommand.php: -------------------------------------------------------------------------------- 1 | argument('domain'); 34 | $aliases = $this->argument('aliases'); 35 | $secure = !$this->option('not-secure'); 36 | 37 | if (Secure::canSecure($domain) && $secure) { 38 | $this->call(GenerateCommand::COMMAND, ['domain' => $domain, 'aliases' => $aliases]); 39 | } 40 | 41 | $this->task('Create Apache Virtual Host-Proxy', function () use ($secure) { 42 | ApacheHelper::configureProxyVHost( 43 | $this->argument('domain'), 44 | $this->argument('port'), 45 | $this->argument('aliases'), 46 | $secure 47 | ); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Commands/RabbitMq/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Uninstall RabbitMq:'); 32 | 33 | if (Brew::isInstalled((string)config('env.rabbitmq.formula'))) { 34 | $this->call(StopCommand::COMMAND); 35 | $this->uninstallFormula((string)config('env.rabbitmq.formula')); 36 | } 37 | 38 | ApacheHelper::deleteVHost((string)config('env.rabbitmq.domain')); 39 | 40 | $this->task('Delete RabbitMq Data', function () { 41 | $this->deleteData(); 42 | }); 43 | } 44 | 45 | /** 46 | * @return void 47 | */ 48 | private function deleteData() 49 | { 50 | File::deleteDirectory((string)config('env.rabbitmq.brew_config_dir_path')); 51 | File::deleteDirectory((string)config('env.rabbitmq.brew_lib_dir_path')); 52 | File::deleteDirectory((string)config('env.rabbitmq.log_dir_path')); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Services/CommandLine.php: -------------------------------------------------------------------------------- 1 | runCommand($command); 21 | 22 | if (!$process->isSuccessful()) { 23 | throw new ProcessFailedException($process); 24 | } 25 | 26 | return $process->getOutput(); 27 | } 28 | 29 | /** 30 | * @param string $command 31 | * @return string 32 | */ 33 | public function runQuietly(string $command): string 34 | { 35 | $process = $this->runCommand($command); 36 | return $process->isSuccessful() ? $process->getOutput() : ''; 37 | } 38 | 39 | /** 40 | * @param string $command 41 | * @return Process 42 | */ 43 | private function runCommand(string $command): Process 44 | { 45 | $process = new Process($command, null, null, null, 5400.); 46 | $process->run(); 47 | return $process; 48 | } 49 | 50 | /** 51 | * Pass the command to the command line and display the output. 52 | * 53 | * @param string $command 54 | * @return void 55 | */ 56 | public function passthru(string $command): void 57 | { 58 | passthru($command); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/Commands/Database/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Install DB dump stuff:'); 30 | 31 | $this->installFormula((string)config('env.progress.formula')); 32 | 33 | $this->task('Ensure DB dumps directory created', function () { 34 | File::ensureDirExists((string)config('env.db.dump_path')); 35 | }); 36 | 37 | $this->task('Setup Locale setting for Bash', function () { 38 | 39 | $bashrcPath = (string)config('env.completion.bashrc_path'); 40 | $bashProfilePath = (string)config('env.completion.bash_profile_path'); 41 | 42 | if ((!File::exists($bashrcPath) || strpos(File::get($bashrcPath), 'LC_ALL') === false) 43 | && (!File::exists($bashProfilePath) || strpos(File::get($bashProfilePath), 'LC_ALL') === false) 44 | ) { 45 | File::append( 46 | $bashProfilePath, 47 | PHP_EOL . 'export LC_ALL=en_US.UTF-8' . PHP_EOL . 'export LANG=en_US.UTF-8' . PHP_EOL 48 | ); 49 | } 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/stubs/httpd.conf: -------------------------------------------------------------------------------- 1 | Listen 80 2 | ServerName localhost 3 | 4 | User CURRENT_USER 5 | Group staff 6 | 7 | LoadModule rewrite_module lib/httpd/modules/mod_rewrite.so 8 | LoadModule vhost_alias_module lib/httpd/modules/mod_vhost_alias.so 9 | LoadModule socache_shmcb_module lib/httpd/modules/mod_socache_shmcb.so 10 | LoadModule ssl_module lib/httpd/modules/mod_ssl.so 11 | 12 | LoadModule proxy_module lib/httpd/modules/mod_proxy.so 13 | LoadModule proxy_http_module lib/httpd/modules/mod_proxy_http.so 14 | 15 | PHP_MODULE_LOADER 16 | 17 | 18 | 19 | DirectoryIndex index.php index.html 20 | 21 | 22 | 23 | SetHandler application/x-httpd-php 24 | 25 | 26 | 27 | AllowOverride All 28 | Require all granted 29 | 30 | 31 | ErrorLog "LOGS_PATH/apache-error.log" 32 | 33 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 34 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 35 | 36 | 37 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio 38 | 39 | 40 | CustomLog "LOGS_PATH/apache-access.log" common 41 | 42 | 43 | Listen 443 44 | SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4 45 | SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4 46 | SSLHonorCipherOrder on 47 | SSLProtocol all -SSLv3 48 | SSLProxyProtocol all -SSLv3 49 | SSLPassPhraseDialog builtin 50 | SSLSessionCache "shmcb:/usr/local/var/run/apache2/ssl_scache(512000)" 51 | SSLSessionCacheTimeout 300 52 | 53 | Include VHOSTS_PATH/00-default.conf 54 | Include VHOSTS_PATH/*.conf 55 | -------------------------------------------------------------------------------- /app/Commands/Apache/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Install Apache:'); 34 | 35 | $this->task('Ensure Apache is not running', function () { 36 | Cli::runQuietly('sudo apachectl stop'); 37 | Cli::runQuietly('sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist'); 38 | 39 | if (Brew::isInstalled((string)config('env.apache.formula'))) { 40 | BrewService::stop((string)config('env.apache.formula')); 41 | } 42 | }); 43 | 44 | $this->installFormula((string)config('env.apache.formula')); 45 | 46 | $this->task('Configure Apache', function () { 47 | ApacheHelper::configure(); 48 | File::ensureDirExists('/usr/local/var/log/httpd'); 49 | }); 50 | 51 | $this->task('Create default Virtual Host (localhost)', function () { 52 | ApacheHelper::initDefaultLocalhostVHost(); 53 | }); 54 | 55 | $this->call(StartCommand::COMMAND); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Console\Kernel::class, 31 | LaravelZero\Framework\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Debug\ExceptionHandler::class, 36 | Illuminate\Foundation\Exceptions\Handler::class 37 | ); 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Return The Application 42 | |-------------------------------------------------------------------------- 43 | | 44 | | This script returns the application instance. The instance is given to 45 | | the calling script so we can separate the building of the instances 46 | | from the actual running of the application and sending responses. 47 | | 48 | */ 49 | 50 | return $app; 51 | -------------------------------------------------------------------------------- /app/Commands/Apache/HostCreateCommand.php: -------------------------------------------------------------------------------- 1 | argument('domain'); 35 | $aliases = $this->argument('aliases'); 36 | $secure = !$this->option('not-secure'); 37 | $path = $this->option('path'); 38 | 39 | $hostPath = $this->getCurrentPath($path); 40 | if (!$this->verifyPath($hostPath, false)) { 41 | $this->error('Passed path does not exist or not a folder: ' . $hostPath); 42 | return; 43 | } 44 | 45 | if (Secure::canSecure($domain) && $secure) { 46 | $this->call(GenerateCommand::COMMAND, ['domain' => $domain, 'aliases' => $aliases]); 47 | } 48 | 49 | $this->task('Create Apache Virtual Host', function () use ($hostPath, $secure) { 50 | ApacheHelper::configureVHost( 51 | $this->argument('domain'), 52 | $hostPath, 53 | $this->argument('aliases'), 54 | $secure 55 | ); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Services/Php.php: -------------------------------------------------------------------------------- 1 | getLinkedPhp(); 19 | if ($currentVersion) { 20 | Brew::unlink($this->getFormula($currentVersion)); 21 | } 22 | 23 | Brew::link($this->getFormula($version)); 24 | } 25 | 26 | /** 27 | * @return null|string 28 | */ 29 | public function getLinkedPhp(): ?string 30 | { 31 | if (!File::isLink((string)config('env.php.brew_path'))) { 32 | return null; 33 | } 34 | 35 | $resolvedPath = File::readLink((string)config('env.php.brew_path')); 36 | 37 | foreach (config('env.php.versions') as $phpVersion) { 38 | if (strpos($resolvedPath, $phpVersion) !== false) { 39 | return $phpVersion; 40 | } 41 | } 42 | 43 | throw new \DomainException('Unable to determine linked PHP.'); 44 | } 45 | 46 | /** 47 | * @param string $version 48 | * @return void 49 | */ 50 | public function link(string $version): void 51 | { 52 | Brew::link($this->getFormula($version)); 53 | } 54 | 55 | /** 56 | * @param string $version 57 | * @return void 58 | */ 59 | public function unlink(string $version): void 60 | { 61 | Brew::unlink($this->getFormula($version)); 62 | } 63 | 64 | /** 65 | * @param string $version 66 | * @return string 67 | */ 68 | public function getFormula(string $version): string 69 | { 70 | return config('env.php.main_version') === $version ? 'php' : 'php@' . $version; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Services/MemcachedSession.php: -------------------------------------------------------------------------------- 1 | getConfigIniFilePath()); 19 | } 20 | 21 | /** 22 | * @return bool 23 | */ 24 | public function isInstalled(): bool 25 | { 26 | return PeclHelper::isInstalled(Pecl::MEMCACHED_EXTENSION); 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function enable(): void 33 | { 34 | if (!File::exists($this->getConfigIniFilePath(true))) { 35 | throw new \RuntimeException('Memcached session config file is not found.'); 36 | } 37 | File::move($this->getConfigIniFilePath(true), $this->getConfigIniFilePath()); 38 | } 39 | 40 | /** 41 | * @return void 42 | */ 43 | public function disable(): void 44 | { 45 | if (!File::exists($this->getConfigIniFilePath())) { 46 | throw new \RuntimeException('Memcached session config file is not found.'); 47 | } 48 | File::move($this->getConfigIniFilePath(), $this->getConfigIniFilePath(true)); 49 | } 50 | 51 | /** 52 | * @return void 53 | */ 54 | public function configure(): void 55 | { 56 | File::put(PeclHelper::getConfDPath() . 'z-session-memcached.ini', Stub::get('php/z-session-memcached.ini')); 57 | } 58 | 59 | /** 60 | * @param bool $disabled 61 | * @return string 62 | */ 63 | private function getConfigIniFilePath(bool $disabled = false): string 64 | { 65 | return PeclHelper::getConfDPath() . 'z-session-memcached.ini' . ($disabled ? '.disabled' : ''); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/stubs/php/smtp_catcher.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | 60 | -------------------------------------------------------------------------------- /app/Commands/Apache/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Uninstall Apache:'); 34 | 35 | $this->task('Ensure Apache is not running', function () { 36 | Cli::runQuietly('sudo apachectl stop'); 37 | Cli::runQuietly('sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist'); 38 | 39 | if (Brew::isInstalled((string)config('env.apache.formula'))) { 40 | try { 41 | BrewService::stop((string)config('env.apache.formula')); 42 | } catch (\Exception $e) { 43 | return $e->getMessage(); 44 | } 45 | } 46 | }); 47 | 48 | $this->uninstallFormula((string)config('env.apache.formula')); 49 | 50 | $this->task('Delete Apache configuration', function () { 51 | File::delete((string)config('env.apache.config')); 52 | File::deleteDirectory((string)config('env.apache.localhost_path')); 53 | File::deleteDirectory((string)config('env.apache.brew_config_dir_path')); 54 | }); 55 | 56 | if ($this->option('force')) { 57 | File::deleteDirectory((string)config('env.apache.vhosts')); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | make(Illuminate\Contracts\Console\Kernel::class); 36 | 37 | $status = $kernel->handle( 38 | $input = new Symfony\Component\Console\Input\ArgvInput, 39 | new Symfony\Component\Console\Output\ConsoleOutput 40 | ); 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Shutdown The Application 45 | |-------------------------------------------------------------------------- 46 | | 47 | | Once Artisan has finished running, we will fire off the shutdown events 48 | | so that any final work may be done by the application before we shut 49 | | down the process. This is the last thing to happen to the request. 50 | | 51 | */ 52 | 53 | $kernel->terminate($input, $status); 54 | 55 | exit($status); 56 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | 'Sage', 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Application Version 20 | |-------------------------------------------------------------------------- 21 | | 22 | | This value determines the "version" your application is currently running 23 | | in. You may want to follow the "Semantic Versioning" - Given a version 24 | | number MAJOR.MINOR.PATCH when an update happens: https://semver.org. 25 | | 26 | */ 27 | 'version' => '0.15.0', 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Application Environment 32 | |-------------------------------------------------------------------------- 33 | | 34 | | This value determines the "environment" your application is currently 35 | | running in. This may determine how you prefer to configure various 36 | | services your application utilizes. Should be true in production. 37 | | 38 | */ 39 | 'production' => true, 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Autoloaded Service Providers 44 | |-------------------------------------------------------------------------- 45 | | 46 | | The service providers listed here will be automatically loaded on the 47 | | request to your application. Feel free to add your own services to 48 | | this array to grant expanded functionality to your applications. 49 | | 50 | */ 51 | 'providers' => [ 52 | App\Providers\AppServiceProvider::class, 53 | ], 54 | 55 | ]; 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Sage 3 | 4 | Sage is helper for development environment on macOS (High Sierra, Mojave, Catalina and Big Sur on intel). 5 | 6 | 7 | ## Installation 8 | 9 | 1. Since Sage depends on Brew. Install or update [Homebrew](https://brew.sh/) to the latest version using brew update. 10 | ```bash 11 | # if not installed 12 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 13 | 14 | # add the next line into your ~/.bash_profile file (create if not exists) 15 | 16 | export PATH="$PATH:/usr/local/sbin:$HOME/bin:$HOME/.composer/vendor/bin" 17 | 18 | 19 | # if installed 20 | 21 | brew update 22 | brew upgrade 23 | ``` 24 | 2. Since there is no php by default in macOS it hsould be istalled manually via brew. 25 | ```bash 26 | brew install shivammathur/php/php@7.3 27 | ``` 28 | 29 | 3. Download phar package from [the latest release](https://github.com/ytorbyk/sage/releases/latest) and put it in `$HOME/bin` folder. 30 | ```bash 31 | curl -L https://github.com/ytorbyk/sage/releases/latest/download/sage.phar > $HOME/bin/bin-sage 32 | chmod +x $HOME/bin/bin-sage 33 | ``` 34 | 4. Create $HOME/bin/sage txt file with the next contend 35 | ```bash 36 | #!/usr/bin/env bash 37 | /usr/local/opt/php@7.3/bin/php "$HOME/bin/bin-sage" "$@" 38 | ``` 39 | 5. Make the txt file executable 40 | ```bash 41 | chown +x $HOME/bin/sage 42 | ``` 43 | 44 | 6. [Optional step] Customize configuration 45 | ```bash 46 | # It creates configuration dump ~/xSage/config.php. 47 | # You can customize and move it to ~/.sage/config.php before next step if you want. 48 | 49 | sage env:config-dump 50 | ``` 51 | 52 | 7. Install and configure required environments 53 | ```bash 54 | # It's automatic, you will prompt to enter your password once and two times MySQL root password. 55 | # If you don't have installed MySQL before, just press enter (there is no password by default). 56 | # After installation MySQL root password is 1 (until you changed it in ~/.sage/config.php config in node mysql.password) 57 | 58 | sage env:install 59 | ``` 60 | 61 | 8. [Optional step] Install Bash completion for the application 62 | ```bash 63 | sage env:completion 64 | ``` 65 | 66 | 9. Ready to use 67 | ```bash 68 | # Displays a list of supported commands with short descriptions 69 | 70 | sage list 71 | ``` 72 | -------------------------------------------------------------------------------- /app/Services/Files.php: -------------------------------------------------------------------------------- 1 | isDirectory($path)) { 19 | $this->makeDirectory($path, $mode, $recursive, $force); 20 | } 21 | } 22 | 23 | /** 24 | * Determine if the given path is a symbolic link. 25 | * 26 | * @param string $path 27 | * @return bool 28 | */ 29 | public function isLink(string $path): bool 30 | { 31 | return is_link($path); 32 | } 33 | 34 | /** 35 | * Resolve the given symbolic link. 36 | * 37 | * @param string $path 38 | * @return string|false 39 | */ 40 | public function readLink(string $path) 41 | { 42 | return readlink($path); 43 | } 44 | 45 | /** 46 | * @param float $size 47 | * @return string 48 | */ 49 | public function getFormatedFileSize(float $size): string 50 | { 51 | $arBytes = [ 52 | [ 53 | 'tag' => 'GB', 54 | 'value' => pow(1024, 3) 55 | ], 56 | [ 57 | 'tag' => 'MB', 58 | 'value' => pow(1024, 2) 59 | ], 60 | [ 61 | 'tag' => 'KB', 62 | 'value' => 1024 63 | ], 64 | [ 65 | 'tag' => 'B', 66 | 'value' => 1 67 | ] 68 | ]; 69 | 70 | $result = '0'; 71 | foreach ($arBytes as $arItem) { 72 | if ($size >= $arItem['value']) { 73 | $result = $size / $arItem['value']; 74 | $result = str_replace('.', ',' , strval(round($result, 2))) . ' ' . $arItem['tag']; 75 | break; 76 | } 77 | } 78 | return $result; 79 | } 80 | 81 | /** 82 | * Read a file as a stream. 83 | * 84 | * @param string $filePath 85 | * @return resource|false 86 | */ 87 | public function readStream(string $filePath) 88 | { 89 | return fopen($filePath, 'rb'); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/Commands/CompletionCommand.php: -------------------------------------------------------------------------------- 1 | argument('shell'); 33 | switch ($shell) { 34 | case 'bash': 35 | $this->setupForBash(); 36 | break; 37 | default: 38 | $this->error(sprintf('Wrong shell, currently supported: bash')); 39 | return; 40 | } 41 | 42 | $this->output->success('Restart all open terminal windows and completion is ready to use.'); 43 | } 44 | 45 | /** 46 | * @return void 47 | */ 48 | private function setupForBash(): void 49 | { 50 | $this->task(sprintf('Ensure [%s] installed', config('env.completion.formula')), function () { 51 | Brew::ensureInstalled((string)config('env.completion.formula')); 52 | }); 53 | 54 | $this->task('Copy completion script', function () { 55 | File::copy( 56 | Stub::getPath('completion/bash'), 57 | (string)config('env.completion.brew_config_completion_path') 58 | ); 59 | }); 60 | 61 | $this->task('Include Brew completion to Bash', function () { 62 | 63 | $sourceText = (string)config('env.completion.brew_completion'); 64 | $bashrcPath = (string)config('env.completion.bashrc_path'); 65 | $bashProfilePath = (string)config('env.completion.bash_profile_path'); 66 | 67 | if ((!File::exists($bashrcPath) || strpos(File::get($bashrcPath), $sourceText) === false) 68 | && (!File::exists($bashProfilePath) || strpos(File::get($bashProfilePath), $sourceText) === false) 69 | ) { 70 | File::append($bashProfilePath, PHP_EOL . $sourceText . PHP_EOL); 71 | } 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/Commands/Php/SwitchCommand.php: -------------------------------------------------------------------------------- 1 | argument('version') ?: $this->getVersion(); 37 | 38 | if (!$phpVersion) { 39 | return; 40 | } 41 | 42 | if (!Brew::isInstalled(PhpHelper::getFormula($phpVersion))) { 43 | $this->warn("PHP {$phpVersion} is not installed."); 44 | return; 45 | } 46 | 47 | $currentVersion = PhpHelper::getLinkedPhp(); 48 | $supportedVersions = config('env.php.versions'); 49 | 50 | if (!in_array($phpVersion, $supportedVersions, true)) { 51 | $this->warn("PHP {$phpVersion} is not available. The following versions are supported: " . implode(' ', $supportedVersions)); 52 | } 53 | 54 | if ($phpVersion === $currentVersion) { 55 | $this->info("{$phpVersion} version is current. Skipping..."); 56 | return; 57 | } 58 | 59 | $this->info('Enable PHP v' . $phpVersion . ':'); 60 | 61 | $this->task('Relink php', function () use ($phpVersion) { 62 | PhpHelper::switchTo($phpVersion); 63 | }); 64 | 65 | 66 | if (File::isFile((string)config('env.apache.config'))) { 67 | $this->task('Update apache config', function () use ($phpVersion) { 68 | ApacheHelper::linkPhp($phpVersion); 69 | }); 70 | } 71 | 72 | if (!$this->option('skip') 73 | && 74 | (!config('env.php.skip_apache_restart') || $this->option('restart')) 75 | ) { 76 | $this->call(RestartCommand::COMMAND); 77 | } 78 | } 79 | 80 | /** 81 | * @return string|false 82 | */ 83 | private function getVersion() 84 | { 85 | return $this->menu('Switch PHP', array_combine(config('env.php.versions'), config('env.php.versions'))); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/Commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Installing and configuring local environment:'); 41 | 42 | $brewInstalled = $this->task('Check if Brew installed', function () { 43 | return Brew::isBrewAvailable(); 44 | }); 45 | if (!$brewInstalled) { 46 | $this->info('Brew is not installed, it is required. Run the next command:'); 47 | $this->comment('/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"'); 48 | return; 49 | } 50 | 51 | File::ensureDirExists((string)config('env.home_public')); 52 | File::ensureDirExists((string)config('env.logs_path')); 53 | 54 | foreach (config('env.software') as $formula) { 55 | $this->installFormula($formula); 56 | } 57 | 58 | $this->call(DnsMasqInstall::COMMAND); 59 | $this->call(MySqlInstall::COMMAND); 60 | $this->call(DatabaseInstall::COMMAND); 61 | $this->call(ApacheInstall::COMMAND); 62 | $this->call(SecureInstall::COMMAND); 63 | $this->call(PhpInstall::COMMAND); 64 | 65 | $this->call(SwitchCommand::COMMAND, ['version' => '8.1']); 66 | 67 | $this->call(RedisInstall::COMMAND); 68 | $this->call(MailHogInstall::COMMAND); 69 | $this->call(RabbitMqInstall::COMMAND); 70 | 71 | $this->call(CompletionInstall::COMMAND); 72 | 73 | $this->output->success(sprintf('%s is successfully installed and ready to use!', config('app.name'))); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/Commands/MySql/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Install MySQL:'); 33 | 34 | $needInstall = $this->installFormula((string)config('env.mysql.formula')); 35 | 36 | $this->task(sprintf('Link [%s] formula', config('env.mysql.formula')), function () { 37 | Brew::link((string)config('env.mysql.formula')); 38 | }); 39 | 40 | $this->task('Configure MySQL', function () { 41 | $this->configureMySQL(); 42 | }); 43 | 44 | if ($needInstall === true) { 45 | $this->call(RestartCommand::COMMAND); 46 | $this->updateSecureSettings(); 47 | } 48 | 49 | $this->call(RestartCommand::COMMAND); 50 | } 51 | 52 | /** 53 | * @return void 54 | */ 55 | private function configureMySQL(): void 56 | { 57 | File::chmod((string)config('env.mysql.data_dir_path'), 0777); 58 | $mysqlConfig = Stub::get( 59 | 'my.cnf', 60 | [ 61 | 'MYSQL_PASSWORD' => (string)config('env.mysql.password'), 62 | 'LOGS_PATH' => (string)config('env.logs_path') 63 | ] 64 | ); 65 | File::put((string)config('env.mysql.brew_config_path'), $mysqlConfig); 66 | } 67 | 68 | /** 69 | * @return void 70 | */ 71 | private function updateSecureSettings(): void 72 | { 73 | $mysqlPasswordMessage = 'Enter previously installed MySQL root password. If was not installed any, just press enter (empty password)!'; 74 | $this->error($mysqlPasswordMessage); 75 | $this->task(sprintf('Update MySQL Password to "%s"', config('env.mysql.password')), function () { 76 | Cli::passthru(sprintf('mysql -u root -p -e "UPDATE mysql.user SET authentication_string=PASSWORD(\'%s\') WHERE User=\'root\'"', config('env.mysql.password'))); 77 | }); 78 | 79 | $this->error($mysqlPasswordMessage); 80 | $this->task('Flush MySQL privileges', function () { 81 | Cli::passthru('mysql -u root -p -e "FLUSH PRIVILEGES;"'); 82 | }); 83 | 84 | $this->task('Delete anonymous users', function () { 85 | Cli::passthru('mysql -e "DELETE FROM mysql.user WHERE User=\'\';"'); 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/Commands/Php/IoncubeCommand.php: -------------------------------------------------------------------------------- 1 | argument('action'); 38 | 39 | if (!$action) { 40 | $action = $this->getAction(); 41 | } elseif (!in_array($action, $this->allowedActions, true)) { 42 | $this->warn('Wrong action. Allowed actions: ' . implode('|', $this->allowedActions)); 43 | return; 44 | } 45 | 46 | $shouldApacheRestart = false; 47 | 48 | if ($action === 'on') { 49 | $shouldApacheRestart = $this->enable(); 50 | } 51 | 52 | if ($action === 'off') { 53 | $shouldApacheRestart = $this->disable(); 54 | } 55 | 56 | 57 | if (!$this->option('skip') && $shouldApacheRestart) { 58 | $this->call(RestartCommand::COMMAND); 59 | } 60 | } 61 | 62 | /** 63 | * @return bool 64 | */ 65 | private function enable(): bool 66 | { 67 | if (!IonCubeHelper::isInstalled()) { 68 | $this->warn('IonCube is not installed'); 69 | return false; 70 | } 71 | 72 | if (IonCubeHelper::isEnabled()) { 73 | $this->warn('IonCube is already enabled'); 74 | return false; 75 | } 76 | 77 | $this->task('IonCube enable', function () { 78 | IonCubeHelper::enable(); 79 | }); 80 | return true; 81 | } 82 | 83 | /** 84 | * @return bool 85 | */ 86 | private function disable(): bool 87 | { 88 | if (!IonCubeHelper::isEnabled()) { 89 | $this->warn('IonCube is already disabled'); 90 | return false; 91 | } 92 | 93 | $this->task('IonCube disable', function () { 94 | IonCubeHelper::disable(); 95 | }); 96 | return true; 97 | } 98 | 99 | /** 100 | * @return null|string 101 | */ 102 | private function getAction(): ?string 103 | { 104 | if (IonCubeHelper::isEnabled()) { 105 | $action = 'off'; 106 | $confirm = 'IonCube is enabled, do you want to disable?'; 107 | } else { 108 | $action = 'on'; 109 | $confirm = 'IonCube is disabled, do you want to enable?'; 110 | } 111 | 112 | return $this->confirm($confirm, true) ? $action : null; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/Commands/ConfigDumpCommand.php: -------------------------------------------------------------------------------- 1 | task('Ensure public home folder exists', function () { 37 | File::ensureDirExists((string)config('env.home_public')); 38 | }); 39 | 40 | $this->writeConfig((bool)$this->option('skip-custom')); 41 | 42 | $this->comment('Config dump: ' . $this->getConfigDumpPath()); 43 | $this->info('The config will not be used.'); 44 | $this->info('It could be customized and moved to: ' . config('env.config_path')); 45 | $this->info('There should be changed values only, they will be merged with default values.'); 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | private function getConfigDumpPath(): string 52 | { 53 | return config('env.home_public') . DIRECTORY_SEPARATOR . 'config.php'; 54 | } 55 | 56 | /** 57 | * @param bool $skipCustom 58 | * @return void 59 | */ 60 | private function writeConfig(bool $skipCustom = false): void 61 | { 62 | $config = $skipCustom ? $this->loadDefaultConfig() : config('env'); 63 | 64 | $this->task('Generate config', function () use ($config) { 65 | $content = $this->varExportShort($config, 1); 66 | File::put( 67 | $this->getConfigDumpPath(), ' $value) { 94 | $expanded[] = str_repeat(self::INDENT, $depth) 95 | . ($indexed ? '' : $this->varExportShort($key) . ' => ') 96 | . $this->varExportShort($value, $depth + 1); 97 | } 98 | 99 | return sprintf("[\n%s\n%s]", implode(",\n", $expanded), str_repeat(self::INDENT, $depth - 1)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/Commands/DnsMasq/InstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Install DnsMasq:'); 31 | 32 | $needInstall = $this->installFormula((string)config('env.dns.formula')); 33 | 34 | $this->task('Configure DnsMasq', function () { 35 | $this->configureDnsMasq(); 36 | }); 37 | 38 | if ($needInstall) { 39 | $this->call(StartCommand::COMMAND); 40 | } else { 41 | $this->call(RestartCommand::COMMAND); 42 | } 43 | } 44 | 45 | /** 46 | * @return void 47 | */ 48 | private function configureDnsMasq(): void 49 | { 50 | File::ensureDirExists((string)config('env.home')); 51 | $this->createCustomConfigFile(); 52 | $domains = (array)config('env.dns.domains'); 53 | $this->setConfig($domains); 54 | $this->createDomainResolvers($domains); 55 | } 56 | 57 | /** 58 | * @return void 59 | */ 60 | private function createCustomConfigFile() 61 | { 62 | $customConfigPath = config('env.dns.config_path'); 63 | if (!$this->customConfigIsImported($customConfigPath)) { 64 | File::put( 65 | (string)config('env.dns.brew_config_path'), 66 | 'conf-file=' . $customConfigPath . PHP_EOL 67 | ); 68 | } 69 | } 70 | 71 | /** 72 | * @param string $customConfigPath 73 | * @return bool 74 | */ 75 | private function customConfigIsImported($customConfigPath) 76 | { 77 | return strpos(File::get((string)config('env.dns.brew_config_path')), $customConfigPath) !== false; 78 | } 79 | 80 | /** 81 | * @param string[] $domains 82 | * @return void 83 | */ 84 | private function setConfig($domains) 85 | { 86 | $content = ''; 87 | foreach ($domains as $domain) { 88 | $content .= 'address=/.' . $domain . '/127.0.0.1' . PHP_EOL; 89 | } 90 | 91 | File::put((string)config('env.dns.config_path'), $content); 92 | } 93 | 94 | /** 95 | * @param string[] $domains 96 | * @return void 97 | */ 98 | private function createDomainResolvers($domains) 99 | { 100 | Cli::run(sprintf('sudo rm -rf %s', config('env.dns.resolver_path'))); 101 | Cli::run(sprintf('sudo mkdir %s', config('env.dns.resolver_path'))); 102 | 103 | foreach ($domains as $domain) { 104 | Cli::run(sprintf('sudo bash -c "echo \'nameserver 127.0.0.1\' > %s/%s"', config('env.dns.resolver_path'), $domain)); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/Commands/Memcached/SessionCommand.php: -------------------------------------------------------------------------------- 1 | argument('action'); 38 | 39 | if (!$action) { 40 | $action = $this->getAction(); 41 | } elseif (!in_array($action, $this->allowedActions, true)) { 42 | $this->warn('Wrong action. Allowed actions: ' . implode('|', $this->allowedActions)); 43 | return; 44 | } 45 | 46 | $shouldApacheRestart = false; 47 | 48 | if ($action === 'on') { 49 | $shouldApacheRestart = $this->enable(); 50 | } 51 | 52 | if ($action === 'off') { 53 | $shouldApacheRestart = $this->disable(); 54 | } 55 | 56 | 57 | if (!$this->option('skip') && $shouldApacheRestart) { 58 | $this->call(RestartCommand::COMMAND); 59 | } 60 | } 61 | 62 | /** 63 | * @return bool 64 | */ 65 | private function enable(): bool 66 | { 67 | if (!MemcachedSession::isInstalled()) { 68 | $this->warn('Memcached is not installed'); 69 | return false; 70 | } 71 | 72 | if (MemcachedSession::isEnabled()) { 73 | $this->warn('Memcached as session storage is already enabled'); 74 | return false; 75 | } 76 | 77 | $this->task('Memcached as session storage enable', function () { 78 | MemcachedSession::enable(); 79 | }); 80 | return true; 81 | } 82 | 83 | /** 84 | * @return bool 85 | */ 86 | private function disable(): bool 87 | { 88 | if (!MemcachedSession::isEnabled()) { 89 | $this->warn('Memcached as session storage is already disabled'); 90 | return false; 91 | } 92 | 93 | $this->task('Memcached as session storage disable', function () { 94 | MemcachedSession::disable(); 95 | }); 96 | return true; 97 | } 98 | 99 | /** 100 | * @return null|string 101 | */ 102 | private function getAction(): ?string 103 | { 104 | if (MemcachedSession::isEnabled()) { 105 | $action = 'off'; 106 | $confirm = 'Memcached as session storage is enabled, do you want to disable?'; 107 | } else { 108 | $action = 'on'; 109 | $confirm = 'Memcached as session storage is disabled, do you want to enable?'; 110 | } 111 | 112 | return $this->confirm($confirm, true) ? $action : null; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /config/commands.php: -------------------------------------------------------------------------------- 1 | NunoMaduro\LaravelConsoleSummary\SummaryCommand::class, 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Commands Paths 20 | |-------------------------------------------------------------------------- 21 | | 22 | | This value determines the "paths" that should be loaded by the console's 23 | | kernel. Foreach "path" present on the array provided below the kernel 24 | | will extract all "Illuminate\Console\Command" based class commands. 25 | | 26 | */ 27 | 'paths' => [app_path('Commands')], 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Added Commands 32 | |-------------------------------------------------------------------------- 33 | | 34 | | You may want to include a single command class without having to load an 35 | | entire folder. Here you can specify which commands should be added to 36 | | your list of commands. The console's kernel will try to load them. 37 | | 38 | */ 39 | 'add' => [ 40 | // .. 41 | ], 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Hidden Commands 46 | |-------------------------------------------------------------------------- 47 | | 48 | | Your application commands will always be visible on the application list 49 | | of commands. But you can still make them "hidden" specifying an array 50 | | of commands below. All "hidden" commands can still be run/executed. 51 | | 52 | */ 53 | 'hidden' => [ 54 | NunoMaduro\LaravelConsoleSummary\SummaryCommand::class, 55 | Symfony\Component\Console\Command\HelpCommand::class, 56 | App\Commands\Apache\InstallCommand::class, 57 | App\Commands\Apache\UninstallCommand::class, 58 | App\Commands\Database\InstallCommand::class, 59 | App\Commands\DnsMasq\InstallCommand::class, 60 | App\Commands\DnsMasq\UninstallCommand::class, 61 | App\Commands\MySql\InstallCommand::class, 62 | App\Commands\MySql\UninstallCommand::class, 63 | App\Commands\Php\InstallCommand::class, 64 | App\Commands\Php\UninstallCommand::class, 65 | App\Commands\Secure\InstallCommand::class, 66 | ], 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Removed Commands 71 | |-------------------------------------------------------------------------- 72 | | 73 | | Do you have a service provider that loads a list of commands that 74 | | you don't need? No problem. Laravel Zero allows you to specify 75 | | below a list of commands that you don't to see in your app. 76 | | 77 | */ 78 | 'remove' => [ 79 | Illuminate\Console\Scheduling\ScheduleRunCommand::class, 80 | Illuminate\Console\Scheduling\ScheduleFinishCommand::class, 81 | Illuminate\Foundation\Console\VendorPublishCommand::class, 82 | ], 83 | 84 | ]; 85 | -------------------------------------------------------------------------------- /app/Commands/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info('Uninstalling local environment:'); 37 | 38 | $brewInstalled = $this->task('Check if Brew installed', function () { 39 | return Brew::isBrewAvailable(); 40 | }); 41 | if (!$brewInstalled) { 42 | $this->info('Brew is not installed. Nothing to uninstall!'); 43 | } 44 | 45 | foreach (config('env.software') as $formula) { 46 | $this->uninstallFormula($formula); 47 | } 48 | 49 | $this->call(DnsMasqUninstall::COMMAND); 50 | $this->call(MySqlUninstall::COMMAND, ['--force' => $this->option('force')]); 51 | $this->call(ApacheUninstall::COMMAND, ['--force' => $this->option('force')]); 52 | $this->call(PhpUninstall::COMMAND); 53 | $this->call(MemcachedUninstall::COMMAND); 54 | $this->call(RedisUninstall::COMMAND); 55 | $this->call(MailHogUninstall::COMMAND); 56 | $this->call(RabbitMqUninstall::COMMAND); 57 | 58 | if ($this->option('force')) { 59 | File::deleteDirectory((string)config('env.home')); 60 | $this->uninstallCompletion(); 61 | } 62 | 63 | $this->output->success(sprintf('%s is successfully uninstalled :(', config('app.name'))); 64 | } 65 | 66 | /** 67 | * @return void 68 | */ 69 | private function uninstallCompletion(): void 70 | { 71 | $this->info('Uninstall completion:'); 72 | 73 | $this->task(sprintf('Uninstall [%s]', config('env.completion.formula')), function () { 74 | Brew::ensureUninstalled((string)config('env.completion.formula')); 75 | }); 76 | 77 | $this->task('Delete completion script', function () { 78 | File::delete(config('env.completion.brew_config_completion_path')); 79 | }); 80 | 81 | $this->task('Remove include Brew completion from Bash', function () { 82 | $sourceText = (string)config('env.completion.brew_completion'); 83 | 84 | $bashrcPath = (string)config('env.completion.bashrc_path'); 85 | if (File::exists($bashrcPath)) { 86 | File::put($bashrcPath, str_replace($sourceText, '', File::get($bashrcPath))); 87 | } 88 | 89 | $bashProfilePath = (string)config('env.completion.bash_profile_path'); 90 | if (File::exists($bashProfilePath)) { 91 | File::put($bashProfilePath, str_replace($sourceText, '', File::get($bashProfilePath))); 92 | } 93 | }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/Commands/Database/ExportCommand.php: -------------------------------------------------------------------------------- 1 | argument('name') ?: $this->getDbName(); 34 | if (!$dbName) { 35 | return; 36 | } 37 | 38 | $file = $this->argument('file') ?: $this->getDumpName($dbName); 39 | 40 | $dumpPath = $this->getDumpPath($this->updateDumpExtension($file)); 41 | $packCommand = $this->getPackCommand($dumpPath); 42 | 43 | $filter = "sed -e 's/DEFINER[ ]*=[ ]*[^*]*\*/\*/' " 44 | . " | sed -e 's/DEFINER[ ]*=[ ]*[^*]*PROCEDURE/PROCEDURE/'" 45 | . " | sed -e 's/DEFINER[ ]*=[ ]*[^*]*FUNCTION/FUNCTION/'" 46 | . " | sed -e 's/ROW_FORMAT=FIXED//g'"; 47 | 48 | Cli::passthru("mysqldump {$dbName} --routines=true" 49 | . ' | pv -b -t -w 80 -N Export ' 50 | . (!$this->option('skip-filter') ? " | {$filter}" : '') 51 | . " {$packCommand} > {$dumpPath}"); 52 | 53 | $this->task(sprintf('DB %s exported', $dbName)); 54 | $this->comment(sprintf('Dump path: %s', $dumpPath)); 55 | } 56 | 57 | /** 58 | * @return string|null 59 | */ 60 | private function getDbName(): ?string 61 | { 62 | $dbs = Cli::run('mysql -N -e "SHOW DATABASES"'); 63 | $dbs = array_filter(explode(PHP_EOL, $dbs)); 64 | $dbs = array_diff($dbs, ['sys', 'mysql', 'performance_schema', 'information_schema']); 65 | return $this->askWithCompletion('Enter DB name', $dbs); 66 | } 67 | 68 | /** 69 | * @param string $dbName 70 | * @return string 71 | */ 72 | private function getDumpName(string $dbName): string 73 | { 74 | $defaultName = $dbName . '.' . date('d-m-Y') . '.sql.gz'; 75 | return $this->ask('Enter Dump file name (location)', $defaultName); 76 | } 77 | 78 | /** 79 | * @param string $file 80 | * @return string 81 | */ 82 | private function updateDumpExtension(string $file): string 83 | { 84 | return in_array(File::extension($file), ['sql', 'gz'], true) ? $file : $file . '.sql.gz'; 85 | } 86 | 87 | /** 88 | * @param string $dumpPath 89 | * @return string 90 | */ 91 | private function getPackCommand(string $dumpPath): string 92 | { 93 | return File::extension($dumpPath) === 'gz' ? '| gzip' : ''; 94 | } 95 | 96 | /** 97 | * @param string $file 98 | * @return string 99 | */ 100 | private function getDumpPath(string $file): string 101 | { 102 | if (strpos($file, '/') === 0) { 103 | $dbPath = $file; 104 | } elseif (strpos($file, './') === 0) { 105 | $dbPath = getcwd() . DIRECTORY_SEPARATOR . substr($file, 2); 106 | } else { 107 | $dbPath = config('env.db.dump_path') . DIRECTORY_SEPARATOR . $file; 108 | } 109 | 110 | return $dbPath; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/Commands/Php/UninstallCommand.php: -------------------------------------------------------------------------------- 1 | info(sprintf('Uninstall PHP v%s', $phpVersion)); 42 | $this->uninstallVersion($phpVersion); 43 | } 44 | 45 | // foreach (config('env.php.dependencies') as $formula) { 46 | // Brew::ensureUninstalled($formula); 47 | // } 48 | 49 | File::deleteDirectory((string)config('env.php.brew_etc_path')); 50 | File::deleteDirectory((string)config('env.php.brew_lib_path')); 51 | } 52 | 53 | /** 54 | * @param string $phpVersion 55 | * @return void 56 | */ 57 | private function uninstallVersion(string $phpVersion): void 58 | { 59 | $this->task('Ensure no PHP is linked', function () { 60 | $currentVersion = PhpHelper::getLinkedPhp(); 61 | if ($currentVersion !== null) { 62 | PhpHelper::unlink($currentVersion); 63 | } 64 | }); 65 | 66 | $isPhpInstalled = $this->task(sprintf('Need to be uninstalled PHP v%s?', $phpVersion), function () use ($phpVersion) { 67 | return Brew::isInstalled(PhpHelper::getFormula($phpVersion)) ?: 'Uninstalled. Skip'; 68 | }); 69 | if ($isPhpInstalled !== true) { 70 | return; 71 | } 72 | 73 | $this->task(sprintf('Link PHP v%s', $phpVersion), function () use ($phpVersion) { 74 | PhpHelper::link($phpVersion); 75 | }); 76 | 77 | $this->uninstallPeclExtension($phpVersion, Pecl::XDEBUG_EXTENSION); 78 | 79 | if ($phpVersion !== '5.6') { 80 | $this->uninstallPeclExtension($phpVersion, Pecl::IMAGICK_EXTENSION); 81 | $this->uninstallPeclExtension($phpVersion, Pecl::MEMCACHED_EXTENSION); 82 | } 83 | 84 | $this->task('[ioncube] uninstall', function () { 85 | try { 86 | IonCubeHelper::uninstall(); 87 | } catch (\Exception $e) { 88 | return $e->getMessage(); 89 | } 90 | }); 91 | 92 | $this->task(sprintf('Uninstall %s Brew formula', PhpHelper::getFormula($phpVersion)), function () use ($phpVersion) { 93 | BrewService::stop(PhpHelper::getFormula($phpVersion)); 94 | Brew::uninstall(PhpHelper::getFormula($phpVersion), ['--force']); 95 | }); 96 | 97 | $this->task('PECL delete config', function () use ($phpVersion) { 98 | PeclHelper::deleteConfigs($phpVersion); 99 | }); 100 | } 101 | 102 | /** 103 | * @param string $phpVersion 104 | * @param string $extension 105 | * @return void 106 | */ 107 | private function uninstallPeclExtension(string $phpVersion, string $extension): void 108 | { 109 | $apcuInstalled = $this->task(sprintf('[%s] need to be uninstalled?', $extension), function () use ($extension) { 110 | return PeclHelper::isInstalled($extension) ?: 'Uninstalled. Skip'; 111 | }); 112 | if ($apcuInstalled === true) { 113 | $this->task(sprintf('[%s] uninstall', $extension), function () use ($phpVersion, $extension) { 114 | try { 115 | PeclHelper::uninstall($extension, $phpVersion); 116 | } catch (\Exception $e) { 117 | return $e->getMessage(); 118 | } 119 | }); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/Services/Brew.php: -------------------------------------------------------------------------------- 1 | isInstalled($formula)) { 58 | return false; 59 | } 60 | 61 | $this->install($formula, $options, $taps); 62 | return true; 63 | } 64 | 65 | /** 66 | * @param string $formula 67 | * @param string[] $options 68 | * @param array $tap 69 | * @return string 70 | * 71 | * @throws \DomainException 72 | */ 73 | public function install(string $formula, array $options = [], array $tap = null): string 74 | { 75 | $vendor = ''; 76 | if (!empty($tap)) { 77 | $this->tap(... $tap); 78 | $vendor = array_shift($tap) . '/'; 79 | } 80 | 81 | try { 82 | return Cli::run('brew install ' . $vendor . $formula . ' ' . implode(' ', $options)); 83 | } catch (ProcessFailedException $e) { 84 | throw new \DomainException('Brew was unable to install [' . $formula . '].', 0, $e); 85 | } 86 | } 87 | 88 | /** 89 | * @param string $formula 90 | * @param string[] $options 91 | * @return bool 92 | */ 93 | public function ensureUninstalled(string $formula, array $options = []): bool 94 | { 95 | if (!$this->isInstalled($formula)) { 96 | return false; 97 | } 98 | 99 | $this->uninstall($formula, $options); 100 | return true; 101 | } 102 | 103 | /** 104 | * @param string $formula 105 | * @param string[] $options 106 | * @return string 107 | */ 108 | public function uninstall(string $formula, array $options = []): string 109 | { 110 | try { 111 | return Cli::run('brew uninstall ' . $formula . ' ' . implode(' ', $options)); 112 | } catch (ProcessFailedException $e) { 113 | throw new \DomainException('Brew was unable to uninstall [' . $formula . '].', 0, $e); 114 | } 115 | } 116 | 117 | /** 118 | * @param string[] $formulas 119 | * @return void 120 | */ 121 | public function tap(string ... $formulas): void 122 | { 123 | $formulas = is_array($formulas) ? $formulas : [$formulas]; 124 | 125 | foreach ($formulas as $formula) { 126 | Cli::run('brew tap ' . $formula); 127 | } 128 | } 129 | 130 | /** 131 | * @param string[] $formulas 132 | * @return void 133 | */ 134 | public function unTap(string ... $formulas): void 135 | { 136 | $formulas = is_array($formulas) ? $formulas : [$formulas]; 137 | 138 | foreach ($formulas as $formula) { 139 | Cli::run('brew untap ' . $formula); 140 | } 141 | } 142 | 143 | /** 144 | * @param string $formula 145 | * @return bool 146 | */ 147 | public function hasTap(string $formula): bool 148 | { 149 | return strpos(Cli::run('brew tap | grep ' . $formula), $formula) !== false; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/Services/IonCube.php: -------------------------------------------------------------------------------- 1 | 'https://downloads.ioncube.com/loader_downloads/ioncube_loaders_dar_x86-64.tar.gz', 23 | '7.2' => 'https://downloads.ioncube.com/loader_downloads/ioncube_loaders_dar_x86-64.tar.gz', 24 | '7.1' => 'https://downloads.ioncube.com/loader_downloads/ioncube_loaders_dar_x86-64.tar.gz', 25 | '7.0' => 'https://downloads.ioncube.com/loader_downloads/ioncube_loaders_dar_x86-64.tar.gz', 26 | '5.6' => 'https://downloads.ioncube.com/loader_downloads/ioncube_loaders_dar_x86-64.tar.gz', 27 | 'extension_type' => Pecl::ZEND_EXTENSION_TYPE, 28 | 'extension_php_name' => 'the ionCube PHP Loader' 29 | ]; 30 | 31 | /** 32 | * @return bool 33 | */ 34 | public function isInstalled(): bool 35 | { 36 | return File::exists($this->extensionPath()); 37 | } 38 | 39 | /** 40 | * @return bool 41 | */ 42 | public function isEnabled(): bool 43 | { 44 | $extensions = explode("\n", Cli::runQuietly("php -m | grep '" . self::IONCUBE_PHP_NAME . "'")); 45 | return in_array(self::IONCUBE_PHP_NAME, $extensions); 46 | } 47 | 48 | /** 49 | * @return void 50 | * @throws \RuntimeException 51 | */ 52 | public function enable(): void 53 | { 54 | if (!File::exists($this->iniPath(true))) { 55 | throw new \RuntimeException('IounCube config file is not found.'); 56 | } 57 | 58 | File::move($this->iniPath(true), $this->iniPath()); 59 | } 60 | 61 | /** 62 | * @return void 63 | */ 64 | public function disable(): void 65 | { 66 | if (!File::exists($this->iniPath())) { 67 | throw new \RuntimeException('IounCube config file is not found.'); 68 | } 69 | 70 | File::move($this->iniPath(), $this->iniPath(true)); 71 | } 72 | 73 | /** 74 | * @param bool $disabled 75 | * @return string 76 | */ 77 | private function iniPath(bool $disabled = false): string 78 | { 79 | return PeclHelper::getConfdPath() . 'ext-ioncube.ini' . ($disabled ? '.disabled' : ''); 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | private function extensionPath(): string 86 | { 87 | return PeclHelper::getExtensionDirectory() . DIRECTORY_SEPARATOR . self::IONCUBE_EXTENSION . '.so'; 88 | } 89 | 90 | /** 91 | * @return void 92 | */ 93 | public function configure(): void 94 | { 95 | File::ensureDirExists(PeclHelper::getConfdPath()); 96 | 97 | File::put($this->iniPath(), Stub::get('php/ext-ioncube.ini')); 98 | File::delete($this->iniPath(true)); 99 | } 100 | 101 | /** 102 | * @param string $phpVersion 103 | * @return void 104 | */ 105 | public function install(string $phpVersion): void 106 | { 107 | if (empty($this->setting[$phpVersion])) { 108 | throw new \RuntimeException(sprintf('PHP v%s is not supported', $phpVersion)); 109 | } 110 | 111 | File::ensureDirExists((string)config('env.tmp_path')); 112 | 113 | 114 | $url = $this->setting[$phpVersion]; 115 | 116 | $urlSplit = explode('/', $url); 117 | $archiveName = $urlSplit[count($urlSplit) - 1]; 118 | 119 | $extensionPath = sprintf('%s/ioncube/ioncube_loader_dar_%s.so', config('env.tmp_path'), $phpVersion); 120 | 121 | if (!File::exists($extensionPath)) { 122 | Cli::run(sprintf("cd %s && curl -O %s", config('env.tmp_path'), $url)); 123 | Cli::run(sprintf("cd %s && tar -xvzf %s", config('env.tmp_path'), $archiveName)); 124 | } 125 | 126 | if (!File::exists($extensionPath)) { 127 | throw new \RuntimeException('Something went wrong while IonCube installation'); 128 | } 129 | File::copy($extensionPath, $this->extensionPath()); 130 | } 131 | 132 | /** 133 | * @return void 134 | */ 135 | public function uninstall(): void 136 | { 137 | File::delete($this->iniPath()); 138 | File::delete($this->iniPath(true)); 139 | File::delete($this->extensionPath()); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/Services/BrewService.php: -------------------------------------------------------------------------------- 1 | getServiceData($service)[self::SERVICE_STARTED]; 24 | } 25 | 26 | /** 27 | * @return bool[] 28 | */ 29 | public function getServicesStatus(): array 30 | { 31 | return array_map(static function ($serviceData) { 32 | return $serviceData[self::SERVICE_STARTED] ?? false; 33 | }, $this->getServices()); 34 | } 35 | 36 | /** 37 | * @param string $service 38 | * @return array 39 | * 40 | * @throws \DomainException 41 | */ 42 | private function getServiceData(string $service): array 43 | { 44 | if (!Brew::isInstalled($service)) { 45 | throw new \DomainException("[{$service}] formula is not installed"); 46 | } 47 | 48 | $services = $this->getServices(); 49 | if (!isset($services[$service])) { 50 | throw new \DomainException("[{$service}] formula is not a service"); 51 | } 52 | 53 | return $services[$service]; 54 | } 55 | 56 | /** 57 | * $result['serviceName'] = ['started' => bool, 'root' => bool] 58 | * 59 | * @return array 60 | */ 61 | private function getServices(): array 62 | { 63 | return array_merge( 64 | $this->parseServicesData(explode(PHP_EOL, Cli::run('brew services list'))), 65 | $this->parseServicesData(explode(PHP_EOL, Cli::run('sudo brew services list')), true) 66 | ); 67 | } 68 | 69 | /** 70 | * @param array $serviceLines 71 | * @param bool $skipStopped 72 | * @return array 73 | */ 74 | private function parseServicesData(array $serviceLines, bool $skipStopped = false): array 75 | { 76 | $services = []; 77 | 78 | array_shift($serviceLines); 79 | foreach ($serviceLines as $serviceLine) { 80 | $serviceLine = array_values(array_filter(explode(' ', $serviceLine))); 81 | if (empty($serviceLine)) { 82 | continue; 83 | } 84 | 85 | $isStarted = ($serviceLine[1] === 'started' || $serviceLine[1] === 'unknown'); 86 | if ($isStarted === false && $skipStopped === true) { 87 | continue; 88 | } 89 | 90 | $services[$serviceLine[0]] = [ 91 | self::SERVICE_STARTED => $isStarted, 92 | self::SERVICE_ROOT => null 93 | ]; 94 | 95 | if ($services[$serviceLine[0]][self::SERVICE_STARTED] === true) { 96 | $services[$serviceLine[0]][self::SERVICE_ROOT] = ($serviceLine[2] === 'root'); 97 | } 98 | } 99 | return $services; 100 | } 101 | 102 | /** 103 | * @param string $service 104 | * @param bool $root 105 | * @return bool 106 | * 107 | * @throws \DomainException 108 | */ 109 | public function start(string $service, bool $root = false): bool 110 | { 111 | $serviceData = $this->getServiceData($service); 112 | 113 | if ($serviceData[self::SERVICE_STARTED] === true && $serviceData[self::SERVICE_ROOT] === $root) { 114 | return false; 115 | } 116 | 117 | if ($serviceData[self::SERVICE_STARTED] === true) { 118 | $this->stop($service); 119 | } 120 | 121 | $commandPrefix = $root ? 'sudo ' : ''; 122 | Cli::run($commandPrefix . 'brew services start ' . $service); 123 | return true; 124 | } 125 | 126 | /** 127 | * @param string $service 128 | * @return void 129 | * 130 | * @throws \DomainException 131 | */ 132 | public function stop(string $service): void 133 | { 134 | $serviceData = $this->getServiceData($service); 135 | 136 | if ($serviceData[self::SERVICE_STARTED] === true) { 137 | $commandPrefix = $serviceData[self::SERVICE_ROOT] ? 'sudo ' : ''; 138 | Cli::run($commandPrefix . 'brew services stop ' . $service); 139 | } 140 | } 141 | 142 | /** 143 | * @param string $service 144 | * @param bool $root 145 | * @return void 146 | * 147 | * @throws \DomainException 148 | */ 149 | public function restart(string $service, bool $root = false): void 150 | { 151 | $this->stop($service); 152 | $this->start($service, $root); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /app/Commands/Database/ImportCommand.php: -------------------------------------------------------------------------------- 1 | 'gunzip -cf', 33 | 'zip' => 'unzip -p', 34 | 'sql' => '', 35 | ]; 36 | 37 | /** 38 | * @return void 39 | */ 40 | public function handle(): void 41 | { 42 | $name = $this->argument('name') ?: $this->ask('Enter Db name'); 43 | if (!$name) { 44 | return; 45 | } 46 | 47 | $file = $this->argument('file') ?: $this->getDumpName(); 48 | if ($file === null) { 49 | return; 50 | } 51 | 52 | $dbPath = $this->getDumpPath($file); 53 | if (!$this->verifyPath($dbPath)) { 54 | $this->error(sprintf('Passed path does not exist or not a file: %s', $dbPath)); 55 | return; 56 | } 57 | 58 | $fileType = File::extension($file); 59 | if (!isset($this->fileType[File::extension($file)])) { 60 | $this->error(sprintf('The file type is not supported: %s', $fileType)); 61 | return; 62 | } 63 | 64 | $this->call(CreateCommand::COMMAND, ['name' => $name, '--force' => true]); 65 | 66 | $dumpPath = $dbPath; 67 | 68 | $tmpFilePath = config('env.tmp_path') . DIRECTORY_SEPARATOR . 'dump.sql'; 69 | if (!empty($this->fileType[$fileType])) { 70 | File::ensureDirExists((string)config('env.tmp_path')); 71 | File::delete($tmpFilePath); 72 | 73 | Cli::passthru("{$this->fileType[$fileType]} {$dbPath} | pv -b -t -w 80 -N Unpack > {$tmpFilePath}"); 74 | $dumpPath = $tmpFilePath; 75 | } 76 | 77 | $filter = "sed -e 's/DEFINER[ ]*=[ ]*[^*]*\*/\*/' " 78 | . " | sed -e 's/DEFINER[ ]*=[ ]*[^*]*PROCEDURE/PROCEDURE/'" 79 | . " | sed -e 's/DEFINER[ ]*=[ ]*[^*]*FUNCTION/FUNCTION/'" 80 | . " | sed -e 's/ROW_FORMAT=FIXED//g'"; 81 | 82 | Cli::passthru("pv {$dumpPath} -w 80 -N Import " 83 | . (!$this->option('skip-filter') ? " | {$filter}" : '') 84 | . " | mysql --force {$name}"); 85 | 86 | File::delete($tmpFilePath); 87 | $this->task('Imported!'); 88 | } 89 | 90 | /** 91 | * @return string|null 92 | */ 93 | private function getDumpName(): ?string 94 | { 95 | $dumps = $this->getDumpList(); 96 | 97 | $options = array_map(function ($dump) { 98 | return sprintf('%-50s %-15s %s', $dump['name'], $dump['size'], $dump['date']); 99 | }, $dumps); 100 | 101 | return $this->menu('Import DB', $options); 102 | } 103 | 104 | /** 105 | * @return string[] 106 | */ 107 | private function getDumpList(): array 108 | { 109 | /** @var \Symfony\Component\Finder\SplFileInfo[] $files */ 110 | $files = File::files((string)config('env.db.dump_path')); 111 | 112 | $dumps = []; 113 | foreach ($files as $file) { 114 | if (!$file->isFile() || !isset($this->fileType[$file->getExtension()])) { 115 | continue; 116 | } 117 | 118 | $dumps[$file->getFilename()] = [ 119 | 'name' => $file->getFilename(), 120 | 'size' => File::getFormatedFileSize($file->getSize()), 121 | 'date' => date('d M Y', $file->getCTime()), 122 | ]; 123 | } 124 | return $dumps; 125 | } 126 | 127 | /** 128 | * @param string $file 129 | * @return string 130 | */ 131 | private function getDumpPath(string $file): string 132 | { 133 | if (strpos($file, '/') === 0) { 134 | $dbPath = $file; 135 | } elseif (strpos($file, './') === 0) { 136 | $dbPath = getcwd() . DIRECTORY_SEPARATOR . substr($file, 2); 137 | } else { 138 | $dbPath = config('env.db.dump_path') . DIRECTORY_SEPARATOR . $file; 139 | } 140 | 141 | return $dbPath; 142 | } 143 | 144 | 145 | /** 146 | * @param string $dbPath 147 | * @return bool 148 | */ 149 | private function verifyPath(string $dbPath): bool 150 | { 151 | return File::exists($dbPath) && File::isFile($dbPath); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/Commands/Php/XdebugCommand.php: -------------------------------------------------------------------------------- 1 | argument('action'); 41 | 42 | if (!$action) { 43 | $action = $this->getAction(); 44 | } elseif (!in_array($action, $this->allowedActions, true)) { 45 | $this->warn('Wrong action. Allowed actions: ' . implode('|', $this->allowedActions)); 46 | return; 47 | } 48 | 49 | $shouldApacheRestart = false; 50 | 51 | if ($action === 'on') { 52 | $shouldApacheRestart = $this->enable((bool)$this->option('remote-autostart')); 53 | } 54 | 55 | if ($action === 'off') { 56 | $shouldApacheRestart = $this->disable(); 57 | } 58 | 59 | if (!$this->option('skip') && $shouldApacheRestart) { 60 | $this->call(RestartCommand::COMMAND); 61 | } 62 | } 63 | 64 | /** 65 | * @param bool $remoteAutostart 66 | * @return bool 67 | */ 68 | private function enable(bool $remoteAutostart = false): bool 69 | { 70 | if (!PeclHelper::isInstalled(Pecl::XDEBUG_EXTENSION)) { 71 | $this->warn('xDebug is not installed'); 72 | return false; 73 | } 74 | 75 | if (PeclHelper::isEnabled(Pecl::XDEBUG_EXTENSION)) { 76 | $this->warn('xDebug is already enabled'); 77 | return false; 78 | } 79 | 80 | $this->task('xDebug enable', function () { 81 | PeclHelper::enable(Pecl::XDEBUG_EXTENSION); 82 | }); 83 | 84 | if ($remoteAutostart) { 85 | $this->task('xDebug enable autostart', function () { 86 | $this->enableAutostart(); 87 | }); 88 | } 89 | return true; 90 | } 91 | 92 | /** 93 | * @return void 94 | */ 95 | private function enableAutostart(): void 96 | { 97 | $configContent = File::get(PeclHelper::iniPath(Pecl::XDEBUG_EXTENSION)); 98 | $configContent = str_replace('#xdebug.start_with_request=yes', 'xdebug.start_with_request=yes', $configContent); 99 | $configContent = str_replace('xdebug.remote_autostart=0', 'xdebug.remote_autostart=1', $configContent); 100 | File::put(PeclHelper::iniPath(Pecl::XDEBUG_EXTENSION), $configContent); 101 | } 102 | 103 | /** 104 | * @return bool 105 | */ 106 | private function disable(): bool 107 | { 108 | if (!PeclHelper::isEnabled(Pecl::XDEBUG_EXTENSION)) { 109 | $this->warn('xDebug is already disabled'); 110 | return false; 111 | } 112 | 113 | $this->task('xDebug disable', function () { 114 | $this->disableAutostart(); 115 | PeclHelper::disable(Pecl::XDEBUG_EXTENSION); 116 | }); 117 | return true; 118 | } 119 | 120 | /** 121 | * @return void 122 | */ 123 | private function disableAutostart(): void 124 | { 125 | $configContent = File::get(PeclHelper::iniPath(Pecl::XDEBUG_EXTENSION)); 126 | if (strpos('#xdebug.start_with_request=yes', $configContent) === false) { 127 | $configContent = str_replace('xdebug.start_with_request=yes', '#xdebug.start_with_request=yes', $configContent); 128 | } 129 | $configContent = str_replace('xdebug.remote_autostart=1', 'xdebug.remote_autostart=0', $configContent); 130 | File::put(PeclHelper::iniPath(Pecl::XDEBUG_EXTENSION), $configContent); 131 | } 132 | 133 | /** 134 | * @return null|string 135 | */ 136 | private function getAction(): ?string 137 | { 138 | if (PeclHelper::isEnabled(Pecl::XDEBUG_EXTENSION)) { 139 | $action = 'off'; 140 | $confirm = 'xDebug is enabled, do you want to disable?'; 141 | } else { 142 | $action = 'on'; 143 | $confirm = 'xDebug is disabled, do you want to enable?'; 144 | } 145 | 146 | return $this->confirm($confirm, true) ? $action : null; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /app/stubs/config/env.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'dns', 6 | 'apache', 7 | 'mysql', 8 | 'memcached', 9 | 'redis', 10 | 'mailhog', 11 | 'rabbitmq' 12 | ], 13 | 14 | 'dns' => [ 15 | 'formula' => 'dnsmasq', 16 | 'domains' => ['test'], 17 | 'config_path' => config('env.home') . DIRECTORY_SEPARATOR . 'dnsmasq.conf', 18 | 'brew_config_path' => '/usr/local/etc/dnsmasq.conf', 19 | 'brew_config_dir_path' => '/usr/local/etc/dnsmasq.d', 20 | 'resolver_path' => '/etc/resolver', 21 | ], 22 | 23 | 'mysql' => [ 24 | 'formula' => 'mysql@5.7', 25 | 'password' => '1', 26 | 'brew_config_path' => '/usr/local/etc/my.cnf', 27 | 'data_dir_path' => '/usr/local/var/mysql' 28 | ], 29 | 30 | 'progress' => [ 31 | 'formula' => 'pv' 32 | ], 33 | 34 | 'db' => [ 35 | 'dump_path' => config('env.home_public') . DIRECTORY_SEPARATOR . 'databases', 36 | ], 37 | 38 | 'apache' => [ 39 | 'formula' => 'httpd', 40 | 'vhosts' => config('env.home') . DIRECTORY_SEPARATOR . 'apache-vhosts', 41 | 'config' => config('env.home') . DIRECTORY_SEPARATOR . 'httpd.conf', 42 | 'brew_config_path' => '/usr/local/etc/httpd/httpd.conf', 43 | 'brew_config_dir_path' => '/usr/local/etc/httpd', 44 | 'localhost_path' => config('env.home_public') . DIRECTORY_SEPARATOR . 'localhost', 45 | 'php_module' => 'LoadModule php{high_version}_module /usr/local/opt/php@{version}/lib/httpd/modules/libphp{high_version}.so', 46 | 'php_module_header' => '#Load PHP Module', 47 | ], 48 | 49 | 'php' => [ 50 | 'main_version' => '8.2', 51 | 'brew_path' => '/usr/local/bin/php', 52 | 'brew_etc_path' => '/usr/local/etc/php', 53 | 'brew_lib_path' => '/usr/local/lib/php', 54 | 'brew_pear_path' => '/usr/local/share/pear', 55 | 'dependencies' => [ 56 | 'autoconf', 57 | 'pkg-config', 58 | 'imagemagick', 59 | 'zlib', 60 | ], 61 | 'taps' => [ 62 | '5.6' => 'shivammathur/php', 63 | '7.0' => 'shivammathur/php', 64 | '7.1' => 'shivammathur/php', 65 | '7.2' => 'shivammathur/php', 66 | '7.3' => 'shivammathur/php', 67 | '7.4' => 'shivammathur/php', 68 | '8.0' => 'shivammathur/php', 69 | '8.1' => 'shivammathur/php', 70 | '8.2' => 'shivammathur/php', 71 | ], 72 | 'versions' => [ 73 | '5.6', 74 | '7.0', 75 | '7.1', 76 | '7.2', 77 | '7.3', 78 | '7.4', 79 | '8.0', 80 | '8.1', 81 | '8.2', 82 | ], 83 | 'skip_apache_restart' => false, 84 | 'smtp_catcher' => 'files', 85 | 'smtp_catcher_mailhog' => '/usr/local/bin/MailHog sendmail no@email', 86 | 'mail_path' => config('env.home_public') . DIRECTORY_SEPARATOR . 'mail', 87 | 'smtp_catcher_files' => config('env.home') . DIRECTORY_SEPARATOR . 'smtp_catcher.php', 88 | ], 89 | 90 | 'memcached' => [ 91 | 'formula' => 'memcached', 92 | 'dependencies' => [ 93 | 'libmemcached', 94 | ] 95 | ], 96 | 97 | 'mailhog' => [ 98 | 'formula' => 'mailhog', 99 | 'domain' => 'mailhog.test', 100 | 'log_path' => '/usr/local/var/log/mailhog.log' 101 | ], 102 | 103 | 'rabbitmq' => [ 104 | 'formula' => 'rabbitmq', 105 | 'domain' => 'rabbitmq.test', 106 | 'brew_config_dir_path' => '/usr/local/etc/rabbitmq', 107 | 'brew_lib_dir_path' => '/usr/local/var/lib/rabbitmq', 108 | 'log_dir_path' => '/usr/local/var/log/rabbitmq' 109 | ], 110 | 111 | 'redis' => [ 112 | 'formula' => 'redis' 113 | ], 114 | 115 | 'secure' => [ 116 | 'formula' => 'openssl', 117 | 'certificates_path' => config('env.home') . DIRECTORY_SEPARATOR . 'certificates', 118 | 'securable_domain' => '.test', 119 | 'secured_domains' => [], 120 | ], 121 | 122 | 'completion' => [ 123 | 'formula' => 'bash-completion', 124 | 'brew_config_completion_path' => '/usr/local/etc/bash_completion.d' . DIRECTORY_SEPARATOR . strtolower(config('app.name')), 125 | 'brew_completion' => 'source $(brew --prefix)/etc/bash_completion', 126 | 'bashrc_path' => env('HOME') . DIRECTORY_SEPARATOR . '.bashrc', 127 | 'bash_profile_path' => env('HOME') . DIRECTORY_SEPARATOR . '.bash_profile' 128 | ], 129 | 130 | 'software' => [ 131 | 'git', 132 | 'composer', 133 | 'bash' 134 | ], 135 | 136 | 'm2' => [ 137 | 'configs_path' => config('env.home_public') . DIRECTORY_SEPARATOR . 'm2' . DIRECTORY_SEPARATOR . 'configs', 138 | ], 139 | 140 | 'backup_path' => config('env.home_public') . DIRECTORY_SEPARATOR . 'backups', 141 | 'logs_path' => config('env.home_public') . DIRECTORY_SEPARATOR . 'logs', 142 | 'tmp_path' => config('env.home') . DIRECTORY_SEPARATOR . 'tmp', 143 | ]; 144 | -------------------------------------------------------------------------------- /app/Command.php: -------------------------------------------------------------------------------- 1 | output->writeln($this->successText($string), $this->parseVerbosity($verbosity)); 31 | } 32 | 33 | /** 34 | * @param string $string 35 | * @return string 36 | */ 37 | protected function successText(string $string): string 38 | { 39 | return "$string"; 40 | } 41 | 42 | /** 43 | * Write a string as error output. 44 | * 45 | * @param string $string 46 | * @param null|int|string $verbosity 47 | * @return void 48 | */ 49 | public function error($string, $verbosity = null) 50 | { 51 | $this->output->writeln($this->errorText($string), $this->parseVerbosity($verbosity)); 52 | } 53 | 54 | /** 55 | * @param string $string 56 | * @return string 57 | */ 58 | protected function errorText(string $string): string 59 | { 60 | return "$string"; 61 | } 62 | 63 | /** 64 | * @param string $title 65 | * @param null $task 66 | * @return null 67 | */ 68 | public function task(string $title = '', $task = null) 69 | { 70 | $this->output->write("$title: processing..."); 71 | 72 | $result = is_callable($task) ? $task() : $task; 73 | 74 | if (is_string($result) && !empty($result)) { 75 | $resultText = "$result"; 76 | } elseif ($result === true || $result === null) { 77 | $resultText = $this->successText('✔'); 78 | } else { 79 | $resultText = $this->errorText('𐄂'); 80 | } 81 | 82 | if ($this->output->isDecorated()) { // Determines if we can use escape sequences 83 | // Move the cursor to the beginning of the line 84 | $this->output->write("\x0D"); 85 | 86 | // Erase the line 87 | $this->output->write("\x1B[2K"); 88 | } else { 89 | $this->output->writeln(''); // Make sure we first close the previous line 90 | } 91 | 92 | $this->output->writeln("$title: " . $resultText); 93 | 94 | return $result; 95 | } 96 | 97 | /** 98 | * @param string $title 99 | * @param array $options 100 | * @return string|null 101 | */ 102 | public function menu(string $title, array $options = []) 103 | { 104 | $addMenuOption = function (CliMenuBuilder $menuBuilder, array $options, &$optionSelected) use (&$addMenuOption) : void 105 | { 106 | foreach ($options as $value => $label) { 107 | if (is_array($label)) { 108 | $menuBuilder->addSubMenu($value, function (CliMenuBuilder $subMenu) use ($value, $label, &$optionSelected, &$addMenuOption) { 109 | $subMenu->setTitle($value); 110 | $subMenu->disableDefaultItems(); 111 | 112 | $addMenuOption($subMenu, $label, $optionSelected); 113 | 114 | $subMenu->addLineBreak('-'); 115 | $subMenu->addItem('Go Back', new GoBackAction); 116 | }); 117 | } else { 118 | $menuBuilder->addMenuItem( 119 | new MenuOption( 120 | $value, $label, function (CliMenu $menu) use (&$optionSelected) { 121 | $optionSelected = $menu->getSelectedItem(); 122 | $menu->close(); 123 | }) 124 | ); 125 | } 126 | } 127 | }; 128 | 129 | $menuBuilder = new CliMenuBuilder; 130 | $menuBuilder->setTitle($title); 131 | $menuBuilder->setWidth(110); 132 | $menuBuilder->setTitleSeparator('='); 133 | $menuBuilder->setForegroundColour('green'); 134 | $menuBuilder->setBackgroundColour('black'); 135 | 136 | $optionSelected = null; 137 | $addMenuOption($menuBuilder, $options, $optionSelected); 138 | 139 | $menuBuilder->addLineBreak('-'); 140 | $menuBuilder->setExitButtonText('Cancel'); 141 | $menuBuilder->build()->open(); 142 | 143 | return $optionSelected instanceof MenuOption ? $optionSelected->getValue() : null; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | task(sprintf('Does [%s] need to be installed?', $formula), function () use ($formula) { 41 | return !BrewFacade::isInstalled($formula) ?: 'Installed. Skip'; 42 | }); 43 | 44 | if ($needInstall === true) { 45 | /** @var Command $this */ 46 | $this->task(sprintf('Install [%s] Brew formula', $formula), function () use ($formula, $options, $tap) { 47 | BrewFacade::install($formula, $options, (array)$tap); 48 | }); 49 | } 50 | return $needInstall === true; 51 | } 52 | ); 53 | 54 | Command::macro( 55 | 'uninstallFormula', 56 | function (string $formula) { 57 | /** @var Command $this */ 58 | $isInstalled = $this->task(sprintf('Does [%s] need to be uninstalled?', $formula), function () use ($formula) { 59 | return BrewFacade::isInstalled($formula) ?: 'Uninstalled. Skip'; 60 | }); 61 | 62 | if ($isInstalled === true) { 63 | /** @var Command $this */ 64 | $this->task(sprintf('Uninstall [%s] Brew formula', $formula), function () use ($formula) { 65 | BrewFacade::uninstall($formula, ['--force']); 66 | }); 67 | } 68 | 69 | return $isInstalled === true; 70 | } 71 | ); 72 | 73 | Command::macro( 74 | 'getCurrentPath', 75 | function (?string $path): string { 76 | if (null === $path) { 77 | $hostPath = getcwd(); 78 | } elseif (strpos($path, '/') === 0) { 79 | $hostPath = $path; 80 | } else { 81 | $hostPath = getcwd() . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR); 82 | } 83 | 84 | return $hostPath; 85 | } 86 | ); 87 | 88 | Command::macro( 89 | 'getFilePath', 90 | function (string $file, string $defaultRoot): string { 91 | if (strpos($file, '/') === 0) { 92 | $path = $file; 93 | } elseif (strpos($file, './') === 0) { 94 | $path = getcwd() . DIRECTORY_SEPARATOR . substr($file, 2); 95 | } else { 96 | $path = $defaultRoot . DIRECTORY_SEPARATOR . $file; 97 | } 98 | 99 | return $path; 100 | } 101 | ); 102 | 103 | Command::macro( 104 | 'verifyPath', 105 | function (string $path, bool $isFile = true): bool { 106 | return FileStubs::exists($path) 107 | && ($isFile && FileStubs::isFile($path)) || (!$isFile && FileStubs::isDirectory($path)); 108 | } 109 | ); 110 | } 111 | 112 | /** 113 | * @return void 114 | */ 115 | public function register(): void 116 | { 117 | $this->app->extend('files', function () { 118 | return new Files; 119 | }); 120 | 121 | $this->app->bind('stubs', function () { 122 | return new Stubs; 123 | }); 124 | 125 | $this->app->bind('command-line', function () { 126 | return new CommandLine; 127 | }); 128 | 129 | $this->app->bind('brew', function () { 130 | return new Brew; 131 | }); 132 | 133 | $this->app->bind('brew.service', function () { 134 | return new BrewService; 135 | }); 136 | 137 | $this->app->bind('secure', function () { 138 | return new Secure; 139 | }); 140 | 141 | $this->app->bind('apache.helper', function () { 142 | return new Apache; 143 | }); 144 | 145 | $this->app->bind('php.helper', function () { 146 | return new Php; 147 | }); 148 | 149 | $this->app->bind('memcached.session', function () { 150 | return new MemcachedSession; 151 | }); 152 | 153 | $this->app->bind('pecl.helper', function () { 154 | return new Pecl; 155 | }); 156 | 157 | $this->app->bind('ioncube.helper', function () { 158 | return new IonCube; 159 | }); 160 | 161 | $this->mergeRecursiveConfigFromPath(BrewStubs::getPath('config/env.php'), 'env'); 162 | $this->mergeRecursiveConfigFromPath(BrewStubs::getPath('config/filesystems.php'), 'env.filesystems'); 163 | $this->mergeRecursiveConfigFrom(config('env.filesystems'), 'filesystems'); 164 | 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /app/Services/Pecl.php: -------------------------------------------------------------------------------- 1 | [ 26 | '5.6' => '2.5.5', 27 | '7.0' => '2.7.2', 28 | '7.1' => '2.9.8', 29 | '7.2' => '3.1.5', 30 | '7.3' => '3.1.5', 31 | '7.4' => '3.1.5', 32 | 'extension_type' => self::ZEND_EXTENSION_TYPE 33 | ] 34 | ]; 35 | 36 | /** 37 | * @param string $extension 38 | * @return bool 39 | */ 40 | public function isInstalled(string $extension): bool 41 | { 42 | return strpos(Cli::runQuietly('pecl list | grep ' . $extension), $extension) !== false; 43 | } 44 | 45 | /** 46 | * @param string $extension 47 | * @return bool 48 | */ 49 | public function isEnabled(string $extension): bool 50 | { 51 | $extensions = explode("\n", Cli::runQuietly("php -m | grep '$extension'")); 52 | return in_array($extension, $extensions); 53 | } 54 | 55 | /** 56 | * @param string $extension 57 | * @return void 58 | */ 59 | public function enable(string $extension): void 60 | { 61 | if (!File::exists($this->iniPath($extension, true))) { 62 | throw new \RuntimeException($extension . ' config file is not found.'); 63 | } 64 | File::move($this->iniPath($extension, true), $this->iniPath($extension)); 65 | } 66 | 67 | /** 68 | * @param string $extension 69 | * @return void 70 | */ 71 | public function disable(string $extension): void 72 | { 73 | if (!File::exists($this->iniPath($extension))) { 74 | throw new \RuntimeException($extension . ' config file is not found.'); 75 | } 76 | File::move($this->iniPath($extension), $this->iniPath($extension, true)); 77 | } 78 | 79 | /** 80 | * @param string $extension 81 | * @param bool $disabled 82 | * @return string 83 | */ 84 | public function iniPath(string $extension, bool $disabled = false): string 85 | { 86 | return $this->getConfDPath() . "ext-{$extension}.ini" . ($disabled ? '.disabled' : ''); 87 | } 88 | 89 | /** 90 | * @return string 91 | */ 92 | public function getConfDPath(): string 93 | { 94 | return dirname($this->getPhpIniPath()) . '/conf.d/'; 95 | } 96 | 97 | /** 98 | * @return string 99 | */ 100 | public function getPhpIniPath(): string 101 | { 102 | return str_replace("\n", '', Cli::run('pecl config-get php_ini')); 103 | } 104 | 105 | /** 106 | * @return string 107 | */ 108 | public function getExtensionDirectory(): string 109 | { 110 | return str_replace("\n", '', Cli::run('pecl config-get ext_dir')); 111 | } 112 | 113 | /** 114 | * @return void 115 | */ 116 | public function updatePeclChannel(): void 117 | { 118 | Cli::run('pecl channel-update pecl.php.net'); 119 | } 120 | 121 | /**` 122 | * @param string $extension 123 | * @param string $phpVersion 124 | * @return void 125 | */ 126 | public function install(string $extension, string $phpVersion): void 127 | { 128 | $extensionVersion = isset($this->extensions[$extension][$phpVersion]) ? $this->extensions[$extension][$phpVersion] : null; 129 | $extensionVersion = $extensionVersion === null ? $extension : $extension . '-' . $extensionVersion; 130 | 131 | Cli::run("pecl uninstall -r $extension"); 132 | $result = Cli::run(sprintf('printf "\n" | pecl install %s', $extensionVersion)); 133 | 134 | if (!preg_match("/Installing '(.*{$extension}.so)'/", $result)) { 135 | throw new \DomainException("Could not find installation path for: $extension\n\n$result"); 136 | } 137 | 138 | if (strpos($result, "Error:")) { 139 | throw new \DomainException("Installation path found, but installation failed:\n\n$result"); 140 | } 141 | } 142 | 143 | /** 144 | * @param string $extension 145 | * @param string $phpVersion 146 | * @return void 147 | */ 148 | public function configure(string $extension, string $phpVersion): void 149 | { 150 | $stubName = "php/ext-{$extension}.ini"; 151 | $stubNameVersion = "php/ext-{$extension}-{$phpVersion}.ini"; 152 | 153 | if (Stub::isExist($stubNameVersion)) { 154 | $this->removeIniDefinition($extension); 155 | File::put($this->iniPath($extension), Stub::get($stubNameVersion)); 156 | } elseif (Stub::isExist($stubName)) { 157 | $this->removeIniDefinition($extension); 158 | File::put($this->iniPath($extension), Stub::get($stubName)); 159 | } 160 | } 161 | 162 | /** 163 | * Replace and remove all directives of the .so file for the given extension within the php.ini file. 164 | * 165 | * @param $extension 166 | * The extension key name. 167 | */ 168 | private function removeIniDefinition($extension) 169 | { 170 | $phpIniPath = $this->getPhpIniPath(); 171 | $phpIniFile = File::get($phpIniPath); 172 | $phpIniFile = preg_replace('/;?(zend_extension|extension)\=".*' . $extension . '.so"/', '', $phpIniFile); 173 | File::put($phpIniPath, $phpIniFile); 174 | } 175 | 176 | /** 177 | * @param string $extension 178 | * @param string $phpVersion 179 | * @return void 180 | */ 181 | public function uninstall(string $extension, string $phpVersion): void 182 | { 183 | $extensionVersion = isset($this->extensions[$extension][$phpVersion]) ? $this->extensions[$extension][$phpVersion] : null; 184 | $extensionVersion = $extensionVersion === null ? $extension : $extension . '-' . $extensionVersion; 185 | 186 | Cli::run("pecl uninstall $extensionVersion"); 187 | } 188 | 189 | /** 190 | * @param string $phpVersion 191 | * @return void 192 | */ 193 | public function deleteConfigs(string $phpVersion): void 194 | { 195 | $pearDirSuffix = config('env.php.main_version') === $phpVersion ? '' : '@' . $phpVersion; 196 | File::deleteDirectory(config('env.php.brew_pear_path') . $pearDirSuffix); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /app/Services/Secure.php: -------------------------------------------------------------------------------- 1 | canGenerate($domain) || $this->hasPredefined($domain); 20 | } 21 | 22 | /** 23 | * @param string $domain 24 | * @return bool 25 | */ 26 | public function canGenerate(string $domain): bool 27 | { 28 | $securableDomain = config('env.secure.securable_domain'); 29 | return substr($domain, -strlen($securableDomain)) === $securableDomain; 30 | } 31 | 32 | /** 33 | * @param string $domain 34 | * @return bool 35 | */ 36 | public function hasPredefined(string $domain): bool 37 | { 38 | $securedDomainsData = config('env.secure.secured_domains'); 39 | $domain = $this->getPredefinedDomain($domain); 40 | return !empty($securedDomainsData[$domain]['crt']) && !empty($securedDomainsData[$domain]['key']); 41 | } 42 | 43 | /** 44 | * @param string $domain 45 | * @return null|string 46 | */ 47 | private function getPredefinedDomain(string $domain): ?string 48 | { 49 | $securedDomainsData = config('env.secure.secured_domains'); 50 | return isset($securedDomainsData[$domain]) ? $domain : $this->getWildcardPredefinedDomain($domain); 51 | } 52 | 53 | /** 54 | * @param string $domain 55 | * @return null|string 56 | */ 57 | private function getWildcardPredefinedDomain(string $domain): ?string 58 | { 59 | $securedDomainsData = config('env.secure.secured_domains'); 60 | $securedDomains = array_filter(array_keys($securedDomainsData), function($value) use ($domain) { 61 | if (strpos($value, '*.') !== 0) { 62 | return false; 63 | } 64 | 65 | $value = str_replace('*.', '.', $value); 66 | return substr($domain, -strlen($value)) === $value; 67 | }); 68 | return !empty($securedDomains) ? array_shift($securedDomains) : null; 69 | } 70 | 71 | /** 72 | * @param string $domain 73 | * @param string[] $aliases 74 | * @return void 75 | */ 76 | public function generate(string $domain, array $aliases = []): void 77 | { 78 | if (!$this->canGenerate($domain)) { 79 | throw new \RuntimeException("The $domain cannot be secured"); 80 | } 81 | 82 | $this->delete($domain); 83 | 84 | File::ensureDirExists((string)config('env.secure.certificates_path')); 85 | 86 | $this->buildCertificateConf($domain, $aliases); 87 | $this->createPrivateKey($domain); 88 | $this->createSigningRequest($domain); 89 | $this->generateCertificates($domain); 90 | $this->trustCertificate($domain); 91 | } 92 | 93 | /** 94 | * @param string $domain 95 | * @return void 96 | */ 97 | public function delete(string $domain): void 98 | { 99 | if (!$this->canGenerate($domain)) { 100 | return; 101 | } 102 | 103 | File::delete( 104 | [ 105 | $this->getFilePath($domain, 'crt'), 106 | $this->getFilePath($domain, 'key'), 107 | $this->getFilePath($domain, 'conf'), 108 | $this->getFilePath($domain, 'csr') 109 | ] 110 | ); 111 | 112 | Cli::runQuietly(sprintf('sudo security delete-certificate -c "%s" -t', $domain)); 113 | Cli::runQuietly(sprintf('sudo security delete-certificate -c "%s"', $domain)); 114 | } 115 | 116 | /** 117 | * @param string $domain 118 | * @param string[] $aliases 119 | * @return void 120 | */ 121 | private function buildCertificateConf(string $domain, array $aliases = []): void 122 | { 123 | $domains = empty($aliases) ? [$domain] : array_merge([$domain], $aliases); 124 | 125 | $dnsNames = ''; 126 | $count = 0; 127 | foreach ($domains as $name) { 128 | $count++; 129 | $dnsNames .= 'DNS.' . $count . ' = ' . $name . PHP_EOL; 130 | } 131 | 132 | $config = Stub::get('openssl.conf', ['DNS_NAMES' => $dnsNames]); 133 | File::put($this->getFilePath($domain, 'conf'), $config); 134 | } 135 | 136 | /** 137 | * @param string $domain 138 | * @return void 139 | */ 140 | private function createPrivateKey(string $domain): void 141 | { 142 | $command = sprintf('openssl genrsa -out %s 2048', $this->getFilePath($domain, 'key')); 143 | Cli::run($command); 144 | } 145 | 146 | /** 147 | * @param string $domain 148 | * @return void 149 | */ 150 | private function createSigningRequest(string $domain): void 151 | { 152 | $command = sprintf( 153 | 'openssl req -new -key %s -out %s -subj "/C=/ST=/O=/localityName=/commonName=*.%s/organizationalUnitName=/emailAddress=/" -config %s -passin pass:', 154 | $this->getFilePath($domain, 'key'), 155 | $this->getFilePath($domain, 'csr'), 156 | $domain, 157 | $this->getFilePath($domain, 'conf') 158 | ); 159 | Cli::run($command); 160 | } 161 | 162 | /** 163 | * @param string $domain 164 | * @return void 165 | */ 166 | private function generateCertificates(string $domain): void 167 | { 168 | $command = sprintf( 169 | 'openssl x509 -req -days 365 -in %s -signkey %s -out %s -extensions v3_req -extfile %s', 170 | $this->getFilePath($domain, 'csr'), 171 | $this->getFilePath($domain, 'key'), 172 | $this->getFilePath($domain, 'crt'), 173 | $this->getFilePath($domain, 'conf') 174 | ); 175 | Cli::run($command); 176 | } 177 | 178 | /** 179 | * @param string $domain 180 | * @return void 181 | */ 182 | private function trustCertificate(string $domain): void 183 | { 184 | $command = sprintf( 185 | 'sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain %s', 186 | $this->getFilePath($domain, 'crt') 187 | ); 188 | Cli::run($command); 189 | } 190 | 191 | /** 192 | * @param string $domain 193 | * @param string $fileType 194 | * @return string 195 | */ 196 | public function getFilePath(string $domain, string $fileType): string 197 | { 198 | if ($this->canGenerate($domain)) { 199 | return config('env.secure.certificates_path') . DIRECTORY_SEPARATOR . $domain . '.' . $fileType; 200 | } 201 | 202 | if ($this->hasPredefined($domain) && in_array($fileType, ['crt', 'key'])) { 203 | return config('env.secure.secured_domains')[$this->getPredefinedDomain($domain)][$fileType]; 204 | } 205 | 206 | throw new \RuntimeException('Path cannot be built.'); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /app/Services/Apache.php: -------------------------------------------------------------------------------- 1 | getConfPath($domain)); 20 | } 21 | 22 | /** 23 | * @param string $domain 24 | * @param string $documentRoot 25 | * @param string[] $aliases 26 | * @param bool $secure 27 | * @return void 28 | */ 29 | public function configureVHost(string $domain, string $documentRoot, array $aliases = [], bool $secure = true): void 30 | { 31 | $this->configureHost($domain, $documentRoot, '', $aliases, $secure); 32 | } 33 | 34 | /** 35 | * @param string $domain 36 | * @param string $port 37 | * @param string[] $aliases 38 | * @param bool $secure 39 | * @return void 40 | */ 41 | public function configureProxyVHost(string $domain, string $port, array $aliases = [], bool $secure = true): void 42 | { 43 | $this->configureHost($domain, '', $port, $aliases, $secure); 44 | } 45 | 46 | /** 47 | * @param string $domain 48 | * @param string $documentRoot 49 | * @param string $port 50 | * @param array $aliases 51 | * @param bool $secure 52 | * @return void 53 | */ 54 | private function configureHost(string $domain, string $documentRoot = '', string $port = '', array $aliases = [], bool $secure = true) 55 | { 56 | $vhostTemplate = empty($documentRoot) && !empty($port) ? 'httpd-proxy-vhost.conf' : 'httpd-vhost.conf'; 57 | $sslVhostTemplate = empty($documentRoot) && !empty($port) ? 'httpd-proxy-vhost-ssl.conf' : 'httpd-vhost-ssl.conf'; 58 | 59 | File::ensureDirExists((string)config('env.apache.vhosts')); 60 | $this->deleteVHost($domain); 61 | 62 | $serverAliases = !empty($aliases) 63 | ? 'ServerAlias ' . implode(' ', $aliases) 64 | : ''; 65 | 66 | $virtualHostSsl = ''; 67 | if ($secure && Secure::canSecure($domain)) { 68 | $virtualHostSsl = Stub::get( 69 | $sslVhostTemplate, 70 | [ 71 | 'DOMAIN' => $domain, 72 | 'SERVER_ALIAS' => $serverAliases, 73 | 'DOCUMENT_ROOT' => $documentRoot, 74 | 'PORT' => $port, 75 | 'LOGS_PATH' => config('env.logs_path'), 76 | 'CERTIFICATE_CRT' => Secure::getFilePath($domain, 'crt'), 77 | 'CERTIFICATE_KEY' => Secure::getFilePath($domain, 'key') 78 | ] 79 | ); 80 | } 81 | 82 | $vhostConfig = Stub::get( 83 | $vhostTemplate, 84 | [ 85 | 'DOMAIN' => $domain, 86 | 'SERVER_ALIAS' => $serverAliases, 87 | 'DOCUMENT_ROOT' => $documentRoot, 88 | 'PORT' => $port, 89 | 'LOGS_PATH' => config('env.logs_path'), 90 | 'VIRTUAL_HOST_SSL' => $virtualHostSsl, 91 | ] 92 | ); 93 | File::put($this->getConfPath($domain), $vhostConfig); 94 | } 95 | 96 | /** 97 | * @return void 98 | */ 99 | public function unlinkPhp(): void 100 | { 101 | $config = File::get((string)config('env.apache.config')); 102 | foreach (config('env.php.versions') as $phpVersion) { 103 | $config = $this->removePhpVersion($config, $phpVersion); 104 | } 105 | File::put((string)config('env.apache.config'), $config); 106 | } 107 | 108 | /** 109 | * @param string $version 110 | * @return void 111 | */ 112 | public function linkPhp(string $version): void 113 | { 114 | $this->unlinkPhp(); 115 | 116 | $config = File::get((string)config('env.apache.config')); 117 | $phpModuleHeader = config('env.apache.php_module_header') . PHP_EOL; 118 | if (strpos($config, $phpModuleHeader) === false) { 119 | throw new \RuntimeException('Apache config is broken'); 120 | } 121 | 122 | $phpModuleLoad = $this->getPhpModuleLoad($version); 123 | $config = str_replace($phpModuleHeader, $phpModuleHeader . $phpModuleLoad . PHP_EOL, $config); 124 | File::put((string)config('env.apache.config'), $config); 125 | } 126 | 127 | /** 128 | * @param string $config 129 | * @param string $phpVersion 130 | * @return string 131 | */ 132 | private function removePhpVersion(string $config, string $phpVersion): string 133 | { 134 | $phpModuleLoad = $this->getPhpModuleLoad($phpVersion); 135 | 136 | $config = str_replace('#' . $phpModuleLoad . PHP_EOL, '', $config); 137 | $config = str_replace('#' . $phpModuleLoad, '', $config); 138 | $config = str_replace($phpModuleLoad . PHP_EOL, '', $config); 139 | $config = str_replace($phpModuleLoad, '', $config); 140 | 141 | return $config; 142 | } 143 | 144 | /** 145 | * @param string $phpVersion 146 | * @return string 147 | */ 148 | private function getPhpModuleLoad($phpVersion): string 149 | { 150 | $highVersion = explode('.', $phpVersion); 151 | $highVersion = array_shift($highVersion); 152 | $highVersion = $highVersion < 8 ? $highVersion : ''; 153 | 154 | $phpModuleLoad = config('env.apache.php_module'); 155 | $phpModuleLoad = str_replace('{version}', $phpVersion, $phpModuleLoad); 156 | $phpModuleLoad = str_replace('{high_version}', $highVersion, $phpModuleLoad); 157 | 158 | return $phpModuleLoad; 159 | } 160 | 161 | /** 162 | * @param string $domain 163 | * @return string 164 | */ 165 | private function getConfPath(string $domain): string 166 | { 167 | $configFilename = $domain === 'localhost' ? '00-default' : $domain; 168 | return config('env.apache.vhosts') . DIRECTORY_SEPARATOR . $configFilename . '.conf'; 169 | } 170 | 171 | /** 172 | * @return void 173 | */ 174 | public function configure(): void 175 | { 176 | File::ensureDirExists((string)config('env.apache.vhosts')); 177 | File::ensureDirExists((string)config('env.logs_path')); 178 | 179 | $apacheConfig = Stub::get( 180 | 'httpd.conf', 181 | [ 182 | 'CURRENT_USER' => $_SERVER['USER'], 183 | 'PHP_MODULE_LOADER' => config('env.apache.php_module_header'), 184 | 'VHOSTS_PATH' => config('env.apache.vhosts'), 185 | 'LOGS_PATH' => config('env.logs_path'), 186 | ] 187 | ); 188 | File::put((string)config('env.apache.config'), $apacheConfig); 189 | $this->includeToHttpdConfig(); 190 | } 191 | 192 | /** 193 | * @return void 194 | */ 195 | private function includeToHttpdConfig(): void 196 | { 197 | $includeConfig = sprintf('Include %s', config('env.apache.config')); 198 | if (strpos(File::get((string)config('env.apache.brew_config_path')), $includeConfig) === false) { 199 | File::append((string)config('env.apache.brew_config_path'), PHP_EOL . $includeConfig . PHP_EOL); 200 | } 201 | } 202 | 203 | /** 204 | * @return void 205 | */ 206 | public function initDefaultLocalhostVHost() 207 | { 208 | $valetDir = (string)config('env.apache.localhost_path'); 209 | File::ensureDirExists($valetDir); 210 | File::put($valetDir . '/index.php', Stub::get('localhost/index.php')); 211 | File::put($valetDir . '/no-entry.jpg', Stub::get('localhost/no-entry.jpg')); 212 | 213 | $this->configureVHost('localhost', $valetDir, [], false); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /app/stubs/completion/bash: -------------------------------------------------------------------------------- 1 | _sage() 2 | { 3 | local cur script coms opts com 4 | COMPREPLY=() 5 | _get_comp_words_by_ref -n : cur words 6 | 7 | # for an alias, get the real script behind it 8 | if [[ $(type -t ${words[0]}) == "alias" ]]; then 9 | script=$(alias ${words[0]} | sed -E "s/alias ${words[0]}='(.*)'/\1/") 10 | else 11 | script=${words[0]} 12 | fi 13 | 14 | # lookup for command 15 | for word in ${words[@]:1}; do 16 | if [[ $word != -* ]]; then 17 | com=$word 18 | break 19 | fi 20 | done 21 | 22 | # completing for an option 23 | if [[ ${cur} == --* ]] ; then 24 | opts="--help --quiet --verbose --version --ansi --no-ansi --no-interaction --env" 25 | 26 | case "$com" in 27 | 28 | help) 29 | opts="${opts} --format --raw" 30 | ;; 31 | 32 | list) 33 | opts="${opts} --raw --format" 34 | ;; 35 | 36 | apache:host) 37 | opts="${opts} --path --not-secure" 38 | ;; 39 | 40 | apache:host-revoke) 41 | opts="${opts} " 42 | ;; 43 | 44 | apache:install) 45 | opts="${opts} " 46 | ;; 47 | 48 | apache:proxy:host) 49 | opts="${opts} --not-secure" 50 | ;; 51 | 52 | apache:restart) 53 | opts="${opts} " 54 | ;; 55 | 56 | apache:start) 57 | opts="${opts} " 58 | ;; 59 | 60 | apache:stop) 61 | opts="${opts} " 62 | ;; 63 | 64 | apache:uninstall) 65 | opts="${opts} --force" 66 | ;; 67 | 68 | db:create) 69 | opts="${opts} --force" 70 | ;; 71 | 72 | db:drop) 73 | opts="${opts} " 74 | ;; 75 | 76 | db:export) 77 | opts="${opts} --skip-filter" 78 | ;; 79 | 80 | db:import) 81 | opts="${opts} --skip-filter" 82 | ;; 83 | 84 | db:install) 85 | opts="${opts} " 86 | ;; 87 | 88 | db:list) 89 | opts="${opts} " 90 | ;; 91 | 92 | dns:install) 93 | opts="${opts} " 94 | ;; 95 | 96 | dns:restart) 97 | opts="${opts} " 98 | ;; 99 | 100 | dns:start) 101 | opts="${opts} " 102 | ;; 103 | 104 | dns:stop) 105 | opts="${opts} " 106 | ;; 107 | 108 | dns:uninstall) 109 | opts="${opts} " 110 | ;; 111 | 112 | env:completion) 113 | opts="${opts} " 114 | ;; 115 | 116 | env:config-dump) 117 | opts="${opts} --skip-custom" 118 | ;; 119 | 120 | env:install) 121 | opts="${opts} " 122 | ;; 123 | 124 | env:uninstall) 125 | opts="${opts} --force" 126 | ;; 127 | 128 | m2:configure) 129 | opts="${opts} --magento-path --scope --scope-code" 130 | ;; 131 | 132 | mailhog:install) 133 | opts="${opts} " 134 | ;; 135 | 136 | mailhog:restart) 137 | opts="${opts} " 138 | ;; 139 | 140 | mailhog:start) 141 | opts="${opts} " 142 | ;; 143 | 144 | mailhog:stop) 145 | opts="${opts} " 146 | ;; 147 | 148 | mailhog:uninstall) 149 | opts="${opts} " 150 | ;; 151 | 152 | memcached:flush) 153 | opts="${opts} " 154 | ;; 155 | 156 | memcached:install) 157 | opts="${opts} " 158 | ;; 159 | 160 | memcached:restart) 161 | opts="${opts} " 162 | ;; 163 | 164 | memcached:session) 165 | opts="${opts} --skip" 166 | ;; 167 | 168 | memcached:start) 169 | opts="${opts} " 170 | ;; 171 | 172 | memcached:stop) 173 | opts="${opts} " 174 | ;; 175 | 176 | memcached:uninstall) 177 | opts="${opts} " 178 | ;; 179 | 180 | mysql:install) 181 | opts="${opts} " 182 | ;; 183 | 184 | mysql:restart) 185 | opts="${opts} " 186 | ;; 187 | 188 | mysql:start) 189 | opts="${opts} " 190 | ;; 191 | 192 | mysql:stop) 193 | opts="${opts} " 194 | ;; 195 | 196 | mysql:uninstall) 197 | opts="${opts} --force" 198 | ;; 199 | 200 | php:install) 201 | opts="${opts} " 202 | ;; 203 | 204 | php:ioncube) 205 | opts="${opts} --skip" 206 | ;; 207 | 208 | php:switch) 209 | opts="${opts} --skip" 210 | ;; 211 | 212 | php:uninstall) 213 | opts="${opts} " 214 | ;; 215 | 216 | php:xdebug) 217 | opts="${opts} --skip --remote-autostart" 218 | ;; 219 | 220 | rabbitmq:install) 221 | opts="${opts} " 222 | ;; 223 | 224 | rabbitmq:queue:list) 225 | opts="${opts} " 226 | ;; 227 | 228 | rabbitmq:restart) 229 | opts="${opts} " 230 | ;; 231 | 232 | rabbitmq:start) 233 | opts="${opts} " 234 | ;; 235 | 236 | rabbitmq:stop) 237 | opts="${opts} " 238 | ;; 239 | 240 | rabbitmq:uninstall) 241 | opts="${opts} " 242 | ;; 243 | 244 | rabbitmq:vhost:create) 245 | opts="${opts} --force" 246 | ;; 247 | 248 | rabbitmq:vhost:delete) 249 | opts="${opts} " 250 | ;; 251 | 252 | rabbitmq:vhost:list) 253 | opts="${opts} " 254 | ;; 255 | 256 | redis:flush) 257 | opts="${opts} " 258 | ;; 259 | 260 | redis:install) 261 | opts="${opts} " 262 | ;; 263 | 264 | redis:restart) 265 | opts="${opts} " 266 | ;; 267 | 268 | redis:start) 269 | opts="${opts} " 270 | ;; 271 | 272 | redis:stop) 273 | opts="${opts} " 274 | ;; 275 | 276 | redis:uninstall) 277 | opts="${opts} " 278 | ;; 279 | 280 | secure:generate) 281 | opts="${opts} " 282 | ;; 283 | 284 | secure:install) 285 | opts="${opts} " 286 | ;; 287 | 288 | secure:revoke) 289 | opts="${opts} " 290 | ;; 291 | 292 | services:start) 293 | opts="${opts} " 294 | ;; 295 | 296 | services:status) 297 | opts="${opts} " 298 | ;; 299 | 300 | services:stop) 301 | opts="${opts} " 302 | ;; 303 | 304 | site:link) 305 | opts="${opts} --path --not-secure" 306 | ;; 307 | 308 | site:proxy:link) 309 | opts="${opts} --not-secure" 310 | ;; 311 | 312 | site:unlink) 313 | opts="${opts} " 314 | ;; 315 | 316 | esac 317 | 318 | COMPREPLY=($(compgen -W "${opts}" -- ${cur})) 319 | __ltrim_colon_completions "$cur" 320 | 321 | return 0; 322 | fi 323 | 324 | # completing for a command 325 | if [[ $cur == $com ]]; then 326 | coms="help list apache:host apache:host-revoke apache:install apache:proxy:host apache:restart apache:start apache:stop apache:uninstall db:create db:drop db:export db:import db:install db:list dns:install dns:restart dns:start dns:stop dns:uninstall env:completion env:config-dump env:install env:uninstall m2:configure mailhog:install mailhog:restart mailhog:start mailhog:stop mailhog:uninstall memcached:flush memcached:install memcached:restart memcached:session memcached:start memcached:stop memcached:uninstall mysql:install mysql:restart mysql:start mysql:stop mysql:uninstall php:install php:ioncube php:switch php:uninstall php:xdebug rabbitmq:install rabbitmq:queue:list rabbitmq:restart rabbitmq:start rabbitmq:stop rabbitmq:uninstall rabbitmq:vhost:create rabbitmq:vhost:delete rabbitmq:vhost:list redis:flush redis:install redis:restart redis:start redis:stop redis:uninstall secure:generate secure:install secure:revoke services:start services:status services:stop site:link site:proxy:link site:unlink" 327 | 328 | COMPREPLY=($(compgen -W "${coms}" -- ${cur})) 329 | __ltrim_colon_completions "$cur" 330 | 331 | return 0 332 | fi 333 | } 334 | 335 | complete -o default -F _sage sage 336 | -------------------------------------------------------------------------------- /app/Commands/Php/InstallCommand.php: -------------------------------------------------------------------------------- 1 | [Pecl::IMAGICK_EXTENSION, Pecl::REDIS_EXTENSION, Pecl::MEMCACHED_EXTENSION], 45 | ]; 46 | 47 | /** 48 | * @return void 49 | */ 50 | public function handle(): void 51 | { 52 | $this->call(MemcachedInstallCommand::COMMAND); 53 | 54 | $phpVersions = config('env.php.versions'); 55 | 56 | foreach (config('env.php.dependencies') as $formula) { 57 | Brew::ensureInstalled($formula); 58 | } 59 | 60 | $this->setupSmtpCatcher(); 61 | 62 | foreach ($phpVersions as $phpVersion) { 63 | if ($phpVersion === '7.3') { 64 | continue; 65 | } 66 | 67 | $this->info(sprintf('Install PHP v%s', $phpVersion)); 68 | $this->installVersion($phpVersion); 69 | } 70 | 71 | $this->call(MemcachedStartCommand::COMMAND); 72 | 73 | File::deleteDirectory((string)config('env.tmp_path')); 74 | } 75 | 76 | /** 77 | * @param string $phpVersion 78 | * @return void 79 | */ 80 | private function installVersion(string $phpVersion): void 81 | { 82 | $this->task('Ensure no PHP is linked', function () { 83 | $currentVersion = PhpHelper::getLinkedPhp(); 84 | if ($currentVersion !== null) { 85 | PhpHelper::unlink($currentVersion); 86 | } 87 | }); 88 | 89 | $phpTaps = config('env.php.taps'); 90 | $taps = !empty($phpTaps[$phpVersion]) ? $phpTaps[$phpVersion] : null; 91 | 92 | $installOptions = config('env.php.install_options'); 93 | $options = !empty($installOptions[$phpVersion]) ? $installOptions[$phpVersion] : []; 94 | 95 | if ($this->installFormula(PhpHelper::getFormula($phpVersion), $options, $taps)) { 96 | BrewService::stop(PhpHelper::getFormula($phpVersion)); 97 | } 98 | 99 | $this->task(sprintf('PHP v%s link', $phpVersion), function () use ($phpVersion) { 100 | PhpHelper::link($phpVersion); 101 | }); 102 | 103 | $this->task(sprintf('PHP v%s update ini files', $phpVersion), function () use ($phpVersion) { 104 | $this->tunePhpIni($phpVersion); 105 | $this->tuneOpCache(); 106 | }); 107 | 108 | $this->task('PECL updating channel', function () { 109 | PeclHelper::updatePeclChannel(); 110 | }); 111 | 112 | $this->installPeclExtension($phpVersion, Pecl::XDEBUG_EXTENSION); 113 | 114 | if (empty($this->skipExtension[$phpVersion]) 115 | || !in_array(Pecl::IMAGICK_EXTENSION, $this->skipExtension[$phpVersion], true) 116 | ) { 117 | $this->installPeclExtension($phpVersion, Pecl::IMAGICK_EXTENSION); 118 | } 119 | 120 | if (empty($this->skipExtension[$phpVersion]) 121 | || !in_array(Pecl::REDIS_EXTENSION, $this->skipExtension[$phpVersion], true) 122 | ) { 123 | $this->installPeclExtension($phpVersion, Pecl::REDIS_EXTENSION); 124 | } 125 | 126 | if (empty($this->skipExtension[$phpVersion]) 127 | || !in_array(Pecl::MEMCACHED_EXTENSION, $this->skipExtension[$phpVersion], true) 128 | ) { 129 | $this->installPeclExtension($phpVersion, Pecl::MEMCACHED_EXTENSION); 130 | MemcachedSession::configure(); 131 | } 132 | 133 | $this->installIonCube($phpVersion); 134 | 135 | $this->call(MemcachedSessionCommand::COMMAND, ['action' => 'off', '--skip' => 1]); 136 | $this->call(IoncubeCommand::COMMAND, ['action' => 'off', '--skip' => 1]); 137 | $this->call(XdebugCommand::COMMAND, ['action' => 'off', '--skip' => 1]); 138 | } 139 | 140 | /** 141 | * @param string $phpVersion 142 | * @param string $extension 143 | * @return void 144 | */ 145 | private function installPeclExtension(string $phpVersion, string $extension): void 146 | { 147 | $needInstall = $this->task(sprintf('[%s] need to be installed?', $extension), function () use ($phpVersion, $extension) { 148 | return !PeclHelper::isInstalled($extension) ?: 'Installed. Skip'; 149 | }); 150 | if ($needInstall === true) { 151 | $this->task(sprintf('[%s] install', $extension), function () use ($phpVersion, $extension) { 152 | PeclHelper::install($extension, $phpVersion); 153 | }); 154 | } 155 | 156 | $this->task(sprintf('[%s] configure', $extension), function () use ($phpVersion, $extension) { 157 | PeclHelper::configure($extension, $phpVersion); 158 | }); 159 | } 160 | 161 | /** 162 | * @param string $phpVersion 163 | * @return void 164 | */ 165 | private function installIonCube(string $phpVersion): void 166 | { 167 | $ioncubeNeedInstall = $this->task('[ioncube] need to be installed?', function () use ($phpVersion) { 168 | return !IonCubeHelper::isInstalled() ?: 'Installed. Skip'; 169 | }); 170 | 171 | $ioncubeNeedConfigure = true; 172 | if ($ioncubeNeedInstall === true) { 173 | $ioncubeNeedConfigure = $this->task('[ioncube] install', function () use ($phpVersion) { 174 | try { 175 | IonCubeHelper::install($phpVersion); 176 | } catch (\Exception $e) { 177 | return $e->getMessage(); 178 | } 179 | return true; 180 | }); 181 | } 182 | 183 | if ($ioncubeNeedConfigure === true) { 184 | $this->task('[ioncube] configure', function () { 185 | IonCubeHelper::configure(); 186 | }); 187 | } 188 | } 189 | 190 | /** 191 | * @return void 192 | */ 193 | private function tuneOpCache() 194 | { 195 | if (!File::exists(PeclHelper::getConfdPath() . 'ext-opcache.ini.origin')) { 196 | File::move( 197 | PeclHelper::getConfdPath() . 'ext-opcache.ini', 198 | PeclHelper::getConfdPath() . 'ext-opcache.ini.origin' 199 | ); 200 | } 201 | $originOpCacheConfig = File::get(PeclHelper::getConfdPath() . 'ext-opcache.ini.origin') . PHP_EOL; 202 | File::put( 203 | PeclHelper::getConfdPath() . 'ext-opcache.ini', 204 | $originOpCacheConfig . Stub::get('php/ext-opcache.ini') 205 | ); 206 | } 207 | 208 | /** 209 | * @return void 210 | */ 211 | private function tunePhpIni(string $phpVersion) 212 | { 213 | File::ensureDirExists(PeclHelper::getConfdPath()); 214 | 215 | $smtpCatcher = config('env.php.smtp_catcher'); 216 | $smtpCatcher = in_array($smtpCatcher, $this->supportedSmtpCatchers, true) ? $smtpCatcher : 'files'; 217 | 218 | $phpZIni = Stub::get('php/z-performance.ini', [ 219 | 'TIMEZONE' => $this->getSystemTimeZone(), 220 | 'SMTP_CATCHER_PATH' => config('env.php.smtp_catcher_' . $smtpCatcher), 221 | 'ERROR_LOG_FILE' => config('env.home_public') . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . 'php' . $phpVersion . '-error.log' 222 | ]); 223 | File::put(PeclHelper::getConfdPath() . 'z-performance.ini', $phpZIni); 224 | } 225 | 226 | /** 227 | * @return void 228 | */ 229 | private function setupSmtpCatcher() 230 | { 231 | $mailDir = (string)config('env.php.mail_path'); 232 | $smtpCatcherPath = (string)config('env.php.smtp_catcher_files'); 233 | File::ensureDirExists((string)config('env.home')); 234 | File::ensureDirExists($mailDir); 235 | 236 | $smtpCatcher = Stub::get('php/smtp_catcher.php', [ 237 | 'TIMEZONE' => $this->getSystemTimeZone(), 238 | 'MAIL_FOLDER' => $mailDir 239 | ]); 240 | File::put($smtpCatcherPath, $smtpCatcher); 241 | File::chmod($smtpCatcherPath, 0755); 242 | } 243 | 244 | /** 245 | * @return string 246 | */ 247 | private function getSystemTimeZone() 248 | { 249 | $systemZoneName = readlink('/etc/localtime'); 250 | // All versions below High Sierra 251 | $systemZoneName = str_replace('/usr/share/zoneinfo/', '', $systemZoneName); 252 | // macOS High Sierra has a new location for the timezone info 253 | $systemZoneName = str_replace('/var/db/timezone/zoneinfo/', '', $systemZoneName); 254 | 255 | return $systemZoneName; 256 | } 257 | } 258 | --------------------------------------------------------------------------------