├── .github └── workflows │ └── run-tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── logo.jpg ├── logo.png ├── phpunit.xml.dist ├── src ├── Commands │ ├── ServedListCommand.php │ ├── ServedRunCommand.php │ ├── ServedSshCommand.php │ ├── ServedStartCommand.php │ ├── ServedStopCommand.php │ ├── ServedTearDownCommand.php │ ├── ServedUpCommand.php │ ├── ServedXdebugCommand.php │ └── Traits │ │ ├── BindOutputToApp.php │ │ ├── DockerCheck.php │ │ ├── Logo.php │ │ ├── PortCheck.php │ │ └── RunningConfig.php ├── Containers │ ├── ApacheContainer.php │ ├── Container.php │ ├── MailhogContainer.php │ ├── MemcachedContainer.php │ ├── MysqlContainer.php │ ├── NginxContainer.php │ ├── PhpContainer.php │ ├── PostgresContainer.php │ └── RedisContainer.php ├── Docker │ ├── Docker.php │ └── DockerFileBuilder.php ├── Exceptions │ ├── DockerNotInstalledException.php │ ├── DockerNotRunningException.php │ ├── InvalidNamingException.php │ ├── PortAlreadyInUseException.php │ ├── ShellCommandFailedException.php │ └── TtyNotSupportedException.php ├── Images │ ├── ApacheImage.php │ ├── Image.php │ ├── ImageInterface.php │ ├── MailhogImage.php │ ├── MemcachedImage.php │ ├── MysqlImage.php │ ├── NginxImage.php │ ├── PhpImage.php │ ├── PostgresImage.php │ ├── RedisImage.php │ └── stubs │ │ ├── localhost.crt │ │ ├── localhost.key │ │ ├── my-httpd-vhosts.conf │ │ ├── my-httpd.conf │ │ └── nginx.conf ├── ServedName.php ├── ServedServiceProvider.php ├── ServiceManager.php ├── Services │ ├── ApacheService.php │ ├── MailhogService.php │ ├── MemcachedService.php │ ├── MysqlService.php │ ├── NginxService.php │ ├── PhpService.php │ ├── PostgresService.php │ ├── RedisService.php │ ├── Service.php │ └── ServiceInterface.php ├── Shell │ ├── Process.php │ └── Shell.php ├── Traits │ └── Storage.php └── config │ └── served.php └── tests ├── TestCase.php ├── Unit ├── DockerImageTest.php ├── DockerRunTest.php └── NamingTest.php └── expected ├── DockerFile-apache ├── DockerFile-mailhog ├── DockerFile-memcached ├── DockerFile-mysql ├── DockerFile-nginx ├── DockerFile-php ├── DockerFile-postgres └── DockerFile-redis /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Validate composer.json and composer.lock 14 | run: composer validate 15 | 16 | - name: Cache Composer packages 17 | id: composer-cache 18 | uses: actions/cache@v2 19 | with: 20 | path: vendor 21 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 22 | restore-keys: | 23 | ${{ runner.os }}-php- 24 | 25 | - name: Install dependencies 26 | if: steps.composer-cache.outputs.cache-hit != 'true' 27 | run: composer install --prefer-dist --no-progress --no-suggest 28 | 29 | - name: Run tests 30 | run: composer test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | .phpunit.result.cache 4 | .php_cs.cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 René Sinnbeck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel Served 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/sinnbeck/laravel-served.svg?style=flat)](https://packagist.org/packages/sinnbeck/laravel-served) 4 | [![Downloads on Packagist](https://img.shields.io/packagist/dt/sinnbeck/laravel-served.svg?style=flat)](https://packagist.org/packages/sinnbeck/laravel-served) 5 | ![tests](https://github.com/sinnbeck/laravel-served/workflows/tests/badge.svg) 6 | 7 | # Introduction 8 | 9 | Laravel Served is a dockerized version of `php artisan serve`. It makes it easy to quickly start a development environment the laravel way (through a config file). 10 | 11 | The only things you need to get started is 12 | * Php (cli) 13 | * Docker 14 | * Laravel 15 | 16 | >Beware: This package is under active development and major changes can occur at any point. It is therefore a good idea to read the documentation, and republish the config file after each new version. 17 | 18 | ## Available services 19 | These are the available services that Served provide. More will be added in the future. If you are missing something specific, just create a new issue, requesting it. 20 | * Php 21 | * Nginx 22 | * Apache2 23 | * Mysql 24 | * Postgres 25 | * Redis 26 | * Memcached 27 | * Mailhog 28 | 29 | ## Installation 30 | Install the package using composer 31 | ``` 32 | $ composer require sinnbeck/laravel-served --dev 33 | ``` 34 | 35 | ## Running for the first time 36 | It is possible to just start a development server right away after installation. This will bring up 3 docker images: 37 | 38 | |Image|Version|Misc| 39 | |-----|-------|------| 40 | |Php-fpm|7.4|Preinstalled with composer, required php modules for running laravel and xdebug running on port 9000 with IDE key 'served'| 41 | |Nginx|1.19|N/A| 42 | |Mysql|5.7|| 43 | 44 | To start the containers simply run 45 | ``` 46 | $ php artisan served:up 47 | ``` 48 | If this is your first time running the command, it will build the images first before starting the containers. If you have run the `served:up` command before, docker will quickly check the images for updates and served will start the containers again. 49 | 50 | ## Starting and stopping served 51 | After your first run, you can easily start and stop your containers without having to build either images or containers. Simply run 52 | ``` 53 | $ php artisan served:start 54 | ``` 55 | And to stop the containers again without removing anything. 56 | ``` 57 | $ php artisan served:stop 58 | ``` 59 | This is useful to free up ports if you have several projects using the same. 60 | 61 | ## Ssh 62 | To go into a container to work you can run 63 | ``` 64 | $ php artisan served:ssh container_name 65 | ``` 66 | The `container_name` is optional, and will default to php (where you can run artisan etc.). 67 | 68 | >Served doesn't actually ssh into the container, but rather just start a bash shell directly. `served:ssh`just sound better and is quick to type. 69 | 70 | ## Clean up 71 | It is possible to remove all data set up by served. To do this simply run 72 | ``` 73 | $ php artisan served:teardown 74 | ``` 75 | 76 | ## Configuration 77 | While it is possible to just run served without any configuration, it is probably a good idea to configure served for your needs. 78 | To get started you need to publish the config file. 79 | ``` 80 | $ php artisan vendor:publish --provider="Sinnbeck\LaravelServed\ServedServiceProvider" 81 | ``` 82 | ### Name 83 | To avoid naming conflicts between projects, you can define your own name for served configuration. This name will be used when creating network, images and containers. Make sure it is unique between projects! If no name is set, served will use the folder name of the laravel installation (a slug version) 84 | 85 | It is important the name only consists of letters, numbers, `.`, `-` and `_`. Other special characters will throw an exception. 86 | 87 | If you at some point wish to chance the name after having used served on a project, it is important to teardown both images and containers using `served:teardown`. If you have already changed the name and are having issues getting your containers up and running with the new name, just chance the name back, run teardown, and set it to the new name once more. 88 | 89 | ### Php 90 | Here you may specify how php should be built. Any options left blank or removed will be defaulted to using the defaults provided by served. 91 | 92 | ``` 93 | 'php' => [ 94 | 'version' => env('SERVED_PHP_VERSION', '7.4'), 95 | 'modules' => [ 96 | 'pdo_mysql', 97 | 'zip', 98 | ], 99 | 'npm' => true, //enable or disable npm in build 100 | 'xdebug' => [ 101 | 'enabled' => env('SERVED_XDEBUG_ENABLED', true), 102 | 'port' => 9001, 103 | ], 104 | ], 105 | ``` 106 | 107 | The array of modules can be filled with any module found in the url below (except parallel, pthreads and tdlib) 108 | 109 | https://github.com/mlocati/docker-php-extension-installer 110 | 111 | #### Xdebug 112 | It is suggested to install xdebug to make debugging easier. To install it and set it up, simple make sure it is set as enabled in the config, while running `php artisan served:up php` 113 | 114 | As Xdebug can slow down requests, it is possible to quickly turn it off and on, when needed. 115 | 116 | Enable Xdebug 117 | ``` 118 | $ php artisan served:xdebug enable 119 | ``` 120 | Disable Xdebug 121 | ``` 122 | $ php artisan served:xdebug disable 123 | ``` 124 | Interactive toggle Xdebug 125 | ``` 126 | $ php artisan served:xdebug 127 | ``` 128 | Be aware that you need to run `php artisan served:up php` again if you decide to enable Xdebug in the config. It isn't possible to toggle it on an off if it isn't installed in the first place. 129 | 130 | ### Web 131 | Served currently supports nginx and apache. Simply service to whichever you want to use, and set the correct version (or delete the version to have served use a sensible default). Apache currently only supports the latest version and will ignore any version set. 132 | 133 | ``` 134 | 'web' => [ 135 | 'service' => 'nginx', //or apache 136 | 'version' => '1.9.2', 137 | 'port' => env('SERVED_WEB_PORT', 8095), 138 | 'ssl_port' => env('SERVED_WEB_SSL_PORT', 4443), 139 | ], 140 | ``` 141 | If you are trying to use the https address, you will be shown a certificate error. To fix this in Chrome, open chrome://settings/certificates and select the Authorities tab. Click import and find the `localhost.crt` in your `/storage/app/served/web/` directory 142 | 143 | ## Extras 144 | Here you can define extra images that you wish to run. The array key is used as name, meaning it is possible to run the same service more than once, with different names (eg. two mysql instances). 145 | 146 | The current supported images are: 147 | 148 | ### Mysql 149 | Port is used for when connecting to mysql from outside of laravel. 150 | Eg. 127.0.0.1:3306. 151 | 152 | To connect to the database from laravel you need to use the config key (in the example that would be `mysql`) as hostname. The port is the default for mysql (3306) and not the one specified in the config. 153 | 154 | If you wish to override the port you use connect to mysql from outside your docker, you can do so by adding 'SERVED_EXTERNAL_DB_PORT' to your .env 155 | ``` 156 | 'mysql' => [ 157 | 'service' => 'mysql', 158 | 'version' => '5.7', 159 | 'port' => env('SERVED_EXTERNAL_DB_PORT', 3306), 160 | 'root_password' => 'password', 161 | 'database' => env('DB_DATABASE', 'laravel'), 162 | 'username' => env('DB_USERNAME', 'laravel'), 163 | 'password' => env('DB_PASSWORD', 'password'), 164 | ], 165 | ``` 166 | 167 | ### Postgres 168 | To connect to postgresql from laravel you need to use the config key (in the example that would be `postgres`) as hostname. The port is the default for mysql (5432) and not the one specified in the config. To connect from outside of laravel, use the port specified in the config (eg. 54320) and 127.0.0.1 169 | ``` 170 | 'postgres' => [ 171 | 'service' => 'postgres', 172 | 'version' => '12.4', 173 | 'port' => 54320, 174 | 'database' => 'laravel', 175 | 'username' => 'laravel', 176 | 'password' => 'password', 177 | ], 178 | ``` 179 | 180 | ### Redis 181 | Add redis to the modules in php and then add redis to your extras array. 182 | ``` 183 | 'redis' => [ 184 | 'service' => 'redis', 185 | ] 186 | ``` 187 | Change your `REDIS_HOST` in .env to whatever you use as the key (eg. redis) 188 | 189 | ### Memcached 190 | Add memcached to the modules in php and then add memcached to your extras array. 191 | ``` 192 | 'memcached' => [ 193 | 'service' => 'memcached', 194 | ] 195 | ``` 196 | Change your `CACHE_DRIVER` in .env to `memcached` and add `MEMCACHED_HOST` and set it to whatever you use as the key (eg. memcached) 197 | 198 | ### Mailhog 199 | Add mailhog to your extras array. 200 | ``` 201 | 'mail' => [ 202 | 'service' => 'mailhog', 203 | 'port' => 8025 204 | ] 205 | ``` 206 | Change your `MAIL_HOST` in .env to whatever you use as the key (eg. mail), and change `MAIL_PORT`to 1025. To see the mailbox, open http://localhost:8025 in your browser (replace 8025 with whatever port you set in config) 207 | 208 | ## Testing 209 | Run tests with 210 | ``` 211 | $ composer test 212 | ``` 213 | 214 | 215 | ## Todo 216 | - [ ] Testing! 217 | - [ ] Add more images 218 | - [ ] Allow user created services 219 | - [ ] Let served make a proper name if none is set (instead of defaulting to 'served') 220 | - [ ] Handle setting/adding volumes 221 | - [ ] Handle removal of volumes 222 | - [ ] Handle upgrades/downgrades of images 223 | - [ ] Pass cli output interface to other classes to allow outputting to cli from them 224 | - [x] Test on other platforms than linux (Ubuntu) 225 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sinnbeck/laravel-served", 3 | "description": "Dockerized version of artisan serve", 4 | "keywords": [ 5 | "laravel", 6 | "serve", 7 | "docker", 8 | "webserver", 9 | "apache", 10 | "nginx" 11 | ], 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "René Sinnbeck", 16 | "email": "rene.sinnbeck@gmail.com" 17 | } 18 | ], 19 | "require": {}, 20 | "require-dev": { 21 | "symfony/process": "^5.1", 22 | "laravel/framework": ">=6.0", 23 | "orchestra/testbench": "^4.0 || ^5.0 || ^6.0", 24 | "phpunit/phpunit": "^8.0 || ^9.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Sinnbeck\\LaravelServed\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Tests\\": "tests/" 34 | } 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "0.1-dev" 39 | }, 40 | "laravel": { 41 | "providers": [ 42 | "Sinnbeck\\LaravelServed\\ServedServiceProvider" 43 | ] 44 | } 45 | }, 46 | "minimum-stability": "stable", 47 | "scripts": { 48 | "test": "vendor/bin/phpunit" 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinnbeck/laravel-served/85deb6d824384dd6f442e0e4a5cb5d350d1cc64b/logo.jpg -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinnbeck/laravel-served/85deb6d824384dd6f442e0e4a5cb5d350d1cc64b/logo.png -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | src/ 17 | 18 | 19 | 20 | 21 | tests 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Commands/ServedListCommand.php: -------------------------------------------------------------------------------- 1 | checkPrerequisites($docker); 51 | $containers = $docker->listContainers(); 52 | 53 | $this->drawLogo(); 54 | $this->comment(app('served.name')); 55 | $this->table($containers->slice(0, 1)->toArray(), $containers->slice(1)->toArray()); 56 | 57 | return 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Commands/ServedRunCommand.php: -------------------------------------------------------------------------------- 1 | checkPrerequisites($docker); 58 | try { 59 | $this->checkPortConflicts($docker, $manager); 60 | 61 | } 62 | catch (PortAlreadyInUseException $e) { 63 | $this->error($e->getMessage()); 64 | return 1; 65 | } 66 | 67 | $servedName = app('served.name'); 68 | $docker->ensureNetworkExists($servedName); 69 | 70 | $onlyService = $this->argument('service'); 71 | 72 | $serviceList = $manager->loadServices(); 73 | 74 | try { 75 | foreach ($serviceList as $service) { 76 | if ($onlyService && $service->name() !== $onlyService) { 77 | continue; 78 | } 79 | $this->info(sprintf('Starting %s (%s) ...', $service->name(), $service->imageName())); 80 | $service->container()->remove(); 81 | $service->container()->run(); 82 | 83 | } 84 | 85 | } catch (ShellCommandFailedException $exception) { 86 | $this->error($exception->getMessage()); 87 | return 1; 88 | } 89 | $this->line(''); 90 | $this->servedRunning($manager); 91 | return 0; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Commands/ServedSshCommand.php: -------------------------------------------------------------------------------- 1 | argument('service'); 49 | $service = $manager->resolveByName($serviceName); 50 | 51 | try { 52 | $service->container()->ssh(); 53 | 54 | } catch (TtyNotSupportedException $e) { 55 | $this->error('Your platform does not support TTY. This means that artisan cannot handle the docker shell.'); 56 | $this->line(sprintf('Instead run %s manually', $service->container()->fallbackSsh())); 57 | } 58 | 59 | return 0; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Commands/ServedStartCommand.php: -------------------------------------------------------------------------------- 1 | checkPrerequisites($docker); 57 | try { 58 | $this->checkPortConflicts($docker, $manager); 59 | 60 | } 61 | catch (PortAlreadyInUseException $e) { 62 | $this->error($e->getMessage()); 63 | return 1; 64 | } 65 | 66 | $this->comment('Ensuring old containers are stopped ...'); 67 | $this->callSilent('served:stop'); 68 | 69 | $servedName = app('served.name'); 70 | $docker->ensureNetworkExists($servedName); 71 | 72 | $onlyService = $this->argument('service'); 73 | 74 | $serviceList = $manager->loadServices(); 75 | 76 | foreach ($serviceList as $service) { 77 | if ($onlyService && $service->name() !== $onlyService) { 78 | continue; 79 | } 80 | $this->info(sprintf('Starting %s (%s) ...', $service->name(), $service->imageName())); 81 | $service->container()->start(); 82 | 83 | } 84 | $this->line(''); 85 | $this->servedRunning($manager); 86 | return 0; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Commands/ServedStopCommand.php: -------------------------------------------------------------------------------- 1 | checkPrerequisites($docker); 52 | $servedName = app('served.name'); 53 | $docker->ensureNetworkExists($servedName); 54 | 55 | $onlyService = $this->argument('service'); 56 | 57 | $serviceList = $manager->loadServices(); 58 | 59 | foreach ($serviceList as $service) { 60 | if ($onlyService && $service->name() !== $onlyService) { 61 | continue; 62 | } 63 | $this->info(sprintf('Stopping %s (%s) ...', $service->name(), $service->imageName())); 64 | $service->container()->stop(); 65 | 66 | } 67 | 68 | return 0; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Commands/ServedTearDownCommand.php: -------------------------------------------------------------------------------- 1 | checkPrerequisites($docker); 53 | $servedName = app('served.name'); 54 | 55 | $onlyService = $this->argument('service'); 56 | 57 | $serviceList = $manager->loadServices(); 58 | 59 | foreach ($serviceList as $service) { 60 | if ($onlyService && $service->name() !== $onlyService) { 61 | continue; 62 | } 63 | 64 | $this->info(sprintf('Removing container for %s (%s) ...', $service->name(), $service->imageName())); 65 | 66 | $service->container()->remove(); 67 | 68 | } 69 | 70 | foreach ($serviceList as $service) { 71 | if ($onlyService && $service->name() !== $onlyService) { 72 | continue; 73 | } 74 | 75 | $this->info(sprintf('Removing image for %s (%s) ...', $service->name(), $service->imageName())); 76 | 77 | $service->image()->remove(); 78 | 79 | } 80 | 81 | if (!$onlyService) { 82 | $this->info(sprintf('Removing network %s ...', $servedName)); 83 | $docker->removeNetwork($servedName); 84 | } 85 | 86 | return 0; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Commands/ServedUpCommand.php: -------------------------------------------------------------------------------- 1 | checkPrerequisites($docker); 58 | try { 59 | $this->checkPortConflicts($docker, $manager); 60 | 61 | } 62 | catch (PortAlreadyInUseException $e) { 63 | $this->error($e->getMessage()); 64 | return 1; 65 | } 66 | 67 | $servedName = app('served.name'); 68 | $this->info('Creating network: ' . $servedName); 69 | $docker->ensureNetworkExists($servedName); 70 | 71 | $onlyService = $this->argument('service'); 72 | $noCache = $this->option('no-cache'); 73 | $serviceList = $manager->loadServices(); 74 | 75 | try { 76 | foreach ($serviceList as $service) { 77 | if ($onlyService && $service->name() !== $onlyService) { 78 | continue; 79 | } 80 | 81 | $this->info(sprintf('Building %s (%s) ...', $service->name(), $service->imageName())); 82 | $service->build($noCache); 83 | 84 | } 85 | 86 | foreach ($serviceList as $service) { 87 | if ($onlyService && $service->name() !== $onlyService) { 88 | continue; 89 | } 90 | $this->info(sprintf('Starting %s (%s) ...', $service->name(), $service->imageName())); 91 | $service->run(); 92 | 93 | } 94 | 95 | } catch (ShellCommandFailedException $exception) { 96 | $this->error($exception->getMessage()); 97 | return 1; 98 | } 99 | 100 | $this->servedRunning($manager); 101 | 102 | return 0; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Commands/ServedXdebugCommand.php: -------------------------------------------------------------------------------- 1 | checkPrerequisites($docker); 52 | 53 | $service = $manager->resolveByName('php'); 54 | 55 | if (!config( 'served.php.xdebug.enabled', false)) { 56 | $this->error('You aren\'t using xdebug!'); 57 | return 1; 58 | } 59 | 60 | $argument = $this->argument('flag'); 61 | if (!$argument) { 62 | $argument = $this->choice('Do you want to enable or disable?', ['enable', 'disable'], 0); 63 | 64 | } 65 | 66 | if ($argument == 'enable') { 67 | try { 68 | $service->enableXdebug(); 69 | $this->info('Xdebug has been enabled!'); 70 | 71 | } 72 | catch (ProcessFailedException $exception) { 73 | $this->error('Xdebug is already enabled!'); 74 | } 75 | 76 | } elseif ($argument == 'disable') { 77 | try { 78 | $service->disableXdebug(); 79 | $this->info('Xdebug has been disabled!'); 80 | 81 | } 82 | catch (ProcessFailedException $exception) { 83 | $this->error('Xdebug is already disabled!'); 84 | } 85 | 86 | } else { 87 | $this->warn('Only enable and disable are supported.'); 88 | } 89 | 90 | return 0; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Commands/Traits/BindOutputToApp.php: -------------------------------------------------------------------------------- 1 | bindOutput($output); 17 | } 18 | 19 | /** 20 | * bind output to dependency container 21 | * @param OutputInterface $output 22 | */ 23 | private function bindOutput(OutputInterface $output) 24 | { 25 | app()->singleton(OutputInterface::class, function() use ($output) { 26 | return $output; 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Commands/Traits/DockerCheck.php: -------------------------------------------------------------------------------- 1 | verifyDockerIsInstalled(); 18 | 19 | } catch (DockerNotInstalledException $e) { 20 | $this->error('Docker is not installed!'); 21 | } 22 | 23 | try { 24 | $docker->verifyDockerDemonIsRunning(); 25 | 26 | } catch (DockerNotRunningException $e) { 27 | $this->error('Docker daemon isn\'t running!'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Commands/Traits/Logo.php: -------------------------------------------------------------------------------- 1 | line(' __ ___ __ ___ __ 13 | /__` |__ |__) \ / |__ | \ 14 | .__/ |___ | \ \/ |___ |__/ 15 | ', 'fg=blue'); 16 | } 17 | 18 | /** 19 | * @return void 20 | */ 21 | protected function oldLogo(): void 22 | { 23 | $this->line(' _____ _ 24 | | __|___ ___ _ _ ___ _| | 25 | |__ | -_| _| | | -_| . | 26 | |_____|___|_| \_/|___|___|', 'fg=blue'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Commands/Traits/PortCheck.php: -------------------------------------------------------------------------------- 1 | loadServices(); 15 | 16 | $servicesWithPort = collect($serviceList)->filter(function($service) { 17 | return !!$service->container()->port(); 18 | }); 19 | 20 | foreach ($servicesWithPort as $service) { 21 | $port = $service->container()->port(); 22 | $internalPort = $service->container()->internalPort(); 23 | $sslPort = $service->container()->sslPort(); 24 | $internalSslPort = $service->container()->internalSslPort(); 25 | if ($this->testPort($docker, $service, $port, $internalPort) && $this->testPort($docker, $service, $sslPort, $internalSslPort)) { 26 | continue; 27 | } 28 | } 29 | } 30 | 31 | protected function testPort(Docker $docker, Service $service, ?int $port = null, ?int $internalPort = null): bool 32 | { 33 | if (!$port) { 34 | return true; 35 | } 36 | 37 | if ($port == $docker->getUsedPort($service->name(), $internalPort)) { 38 | return true; 39 | } 40 | 41 | $connection = @fsockopen('localhost', $port); 42 | 43 | if (is_resource($connection)) 44 | { 45 | fclose($connection); 46 | throw new PortAlreadyInUseException('Port ' . $port . ' is in use!'); 47 | } 48 | 49 | return true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Commands/Traits/RunningConfig.php: -------------------------------------------------------------------------------- 1 | line(' Laravel has been', 'fg=blue'); 18 | $this->drawLogo(); 19 | $url = 'http://localhost:' . $manager->web()->container()->port(); 20 | $secureUrl = 'https://localhost:' . $manager->web()->container()->sslPort(); 21 | $this->line('Visit the development server at:'); 22 | $this->line(''. $url . ''); 23 | $this->line(''. $secureUrl . ''); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Containers/ApacheContainer.php: -------------------------------------------------------------------------------- 1 | $this->projectName(), 29 | 'container_name' => $this->makeContainerName(), 30 | 'image_name' => $this->makeImageName(), 31 | 'port' => $this->port(), 32 | 'ssl_port' => $this->sslPort(), 33 | 'local_dir' => base_path(), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Containers/Container.php: -------------------------------------------------------------------------------- 1 | name = $name; 46 | $this->config = $config; 47 | $this->shell = $shell; 48 | 49 | $this->parseConfig(); 50 | } 51 | 52 | /** 53 | * @return $this 54 | */ 55 | public function prepare(): self 56 | { 57 | $this->remove(); 58 | 59 | return $this; 60 | } 61 | 62 | public function run() 63 | { 64 | $this->shell->run($this->getDockerRunCommand(), $this->env()); 65 | } 66 | 67 | public function getDockerRunCommand() 68 | { 69 | 70 | $command = $this->isWindows() ? str_replace('\\', '', $this->dockerRunCommand) : $this->dockerRunCommand; 71 | 72 | return 'docker run -d --restart always ' . $command . $this->getProxySettings() . ' "${:image_name}"'; 73 | } 74 | 75 | protected function getProxySettings() 76 | { 77 | $envString = ''; 78 | if ($proxyHttp = config('served.proxy.http', false)) { 79 | $envString .= ' --env=HTTP_PROXY="' . $proxyHttp . '"'; 80 | } 81 | 82 | if ($proxyHttps = config('served.proxy.https', false)) { 83 | $envString .= ' --env=HTTPS_PROXY="' . $proxyHttps . '"'; 84 | } 85 | 86 | return $envString; 87 | } 88 | 89 | public function getEnv() 90 | { 91 | return $this->env(); 92 | } 93 | 94 | /** 95 | * @return void 96 | */ 97 | public function parseConfig(): void 98 | { 99 | foreach ($this->config as $key => $value) { 100 | if ($key === 'port') { 101 | $this->setPort((int) $value); 102 | } 103 | 104 | if ($key === 'ssl_port') { 105 | $this->setSslPort((int) $value); 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * @return void 112 | */ 113 | public function start(): void 114 | { 115 | $this->shell->exec('docker start ' . $this->makeContainerName()); 116 | } 117 | 118 | /** 119 | * @return void 120 | */ 121 | public function stop(): void 122 | { 123 | $this->shell->exec('docker stop ' . $this->makeContainerName()); 124 | } 125 | 126 | /** 127 | * @return array 128 | */ 129 | protected function env() 130 | { 131 | $baseEnv = [ 132 | 'network' => $this->projectName(), 133 | 'container_name' => $this->makeContainerName(), 134 | 'image_name' => $this->makeImageName(), 135 | ]; 136 | 137 | return array_merge($baseEnv, $this->getAdditionalEnv()); 138 | } 139 | 140 | /** 141 | * @return array 142 | */ 143 | protected function getAdditionalEnv(): array 144 | { 145 | return []; 146 | } 147 | 148 | /** 149 | * @return void 150 | */ 151 | public function remove(): void 152 | { 153 | try { 154 | $this->shell->exec('docker rm ' . $this->makeContainerName() . ' -f'); 155 | 156 | } catch (ProcessFailedException $e) { 157 | //Do nothing 158 | } 159 | } 160 | 161 | /** 162 | * @throws TtyNotSupportedException 163 | */ 164 | public function ssh(): void 165 | { 166 | if (!Process::isTtySupported()) { 167 | throw new TtyNotSupportedException('TTY mode is not supported'); 168 | } 169 | 170 | $process = Process::fromShellCommandline('docker exec -ti "${:container}" bash'); 171 | 172 | $process->setTimeout(null); 173 | $process->setTty(true); 174 | 175 | $process->run(null, ['container' => $this->makeContainerName()]); 176 | } 177 | 178 | /** 179 | * @return string 180 | */ 181 | public function fallbackSsh(): string 182 | { 183 | return sprintf('docker exec -ti %s bash', $this->makeContainerName()); 184 | } 185 | 186 | /** 187 | * @return string 188 | */ 189 | protected function makeContainerName(): string 190 | { 191 | return sprintf('served_%s_%s', $this->projectName(), $this->name()); 192 | } 193 | 194 | /** 195 | * @param int $port 196 | * @return $this 197 | */ 198 | public function setPort(int $port): self 199 | { 200 | if ($port) { 201 | $this->port = (int) $port; 202 | } 203 | 204 | return $this; 205 | } 206 | 207 | /** 208 | * @return int 209 | */ 210 | public function port(): ?int 211 | { 212 | return $this->port; 213 | } 214 | 215 | /** 216 | * @param int $port 217 | * @return $this 218 | */ 219 | public function setSslPort(int $port): self 220 | { 221 | if ($port) { 222 | $this->ssl_port = (int) $port; 223 | } 224 | 225 | return $this; 226 | } 227 | 228 | /** 229 | * @return int 230 | */ 231 | public function sslPort(): ?int 232 | { 233 | return $this->ssl_port; 234 | } 235 | 236 | /** 237 | * @return int 238 | */ 239 | public function internalPort(): ?int 240 | { 241 | return $this->internal_port; 242 | } 243 | 244 | /** 245 | * @return int 246 | */ 247 | public function internalSslPort(): ?int 248 | { 249 | return $this->internal_ssl_port; 250 | } 251 | 252 | /** 253 | * @return string 254 | */ 255 | public function projectName(): string 256 | { 257 | return app('served.name'); 258 | } 259 | 260 | /** 261 | * @return string 262 | */ 263 | public function name(): string 264 | { 265 | return $this->name; 266 | } 267 | 268 | /** 269 | * @return string 270 | */ 271 | protected function findDockerFile(): string 272 | { 273 | return $this->storageDirectory() . 'Dockerfile'; 274 | } 275 | 276 | //Duplicate code! 277 | 278 | /** 279 | * @return string 280 | */ 281 | protected function makeImageName(): string 282 | { 283 | return sprintf('served/%s_%s', $this->projectName(), $this->name()); 284 | } 285 | 286 | protected function isWindows() 287 | { 288 | return Str::startsWith(PHP_OS,'WIN'); 289 | } 290 | 291 | } 292 | -------------------------------------------------------------------------------- /src/Containers/MailhogContainer.php: -------------------------------------------------------------------------------- 1 | $this->projectName(), 25 | 'alias' => $this->name(), 26 | 'port' => $this->port(), 27 | 'container_name' => $this->makeContainerName(), 28 | 'image_name' => $this->makeImageName(), 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Containers/MemcachedContainer.php: -------------------------------------------------------------------------------- 1 | $this->projectName(), 18 | 'alias' => $this->name(), 19 | 'container_name' => $this->makeContainerName(), 20 | 'image_name' => $this->makeImageName(), 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Containers/MysqlContainer.php: -------------------------------------------------------------------------------- 1 | $this->projectName(), 34 | 'container_name' => $this->makeContainerName(), 35 | 'image_name' => $this->makeImageName(), 36 | 'port' => $this->port(), 37 | 'alias' => $this->name(), 38 | 'volume' => $this->volume(), 39 | ]; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | protected function volume(): string 46 | { 47 | if ($volume = Arr::get($this->config, 'volume')) { 48 | return $volume; 49 | } 50 | 51 | return $this->projectName() . '_' . $this->name(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Containers/NginxContainer.php: -------------------------------------------------------------------------------- 1 | $this->projectName(), 29 | 'container_name' => $this->makeContainerName(), 30 | 'image_name' => $this->makeImageName(), 31 | 'port' => $this->port(), 32 | 'ssl_port' => $this->sslPort(), 33 | 'local_dir' => base_path(), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Containers/PhpContainer.php: -------------------------------------------------------------------------------- 1 | dockerRunCommand .= $this->volumes(); 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | protected function env(): array 26 | { 27 | return [ 28 | 'network' => $this->projectName(), 29 | 'container_name' => $this->makeContainerName(), 30 | 'image_name' => $this->makeImageName(), 31 | 'local_dir' => base_path(), 32 | ]; 33 | } 34 | 35 | /** 36 | * @return string 37 | */ 38 | protected function volumes(): string 39 | { 40 | $volumes = Arr::get($this->config, 'volumes', []); 41 | if (!$volumes) { 42 | return ''; 43 | } 44 | 45 | return ' ' . collect($volumes)->map(function ($item) { 46 | return '-v="' . $item . '"'; 47 | })->implode(' '); 48 | } 49 | 50 | public function enableXdebug(): void 51 | { 52 | $this->shell->exec('docker exec --user root ' . $this->makeContainerName() . ' mv /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini.old /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini'); 53 | $this->kill(); 54 | } 55 | 56 | public function disableXdebug(): void 57 | { 58 | $this->shell->exec('docker exec --user root ' . $this->makeContainerName() . ' mv /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini.old'); 59 | $this->kill(); 60 | } 61 | 62 | public function kill(): void 63 | { 64 | $this->shell->exec('docker exec --user root ' . $this->makeContainerName() . ' /bin/bash -c "kill USR2 1"'); 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Containers/PostgresContainer.php: -------------------------------------------------------------------------------- 1 | $this->projectName(), 34 | 'container_name' => $this->makeContainerName(), 35 | 'image_name' => $this->makeImageName(), 36 | 'port' => $this->port(), 37 | 'alias' => $this->name(), 38 | 'volume' => $this->volume(), 39 | ]; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | protected function volume(): string 46 | { 47 | if ($volume = Arr::get($this->config, 'volume')) { 48 | return $volume; 49 | } 50 | 51 | return $this->projectName() . '_' . $this->name(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Containers/RedisContainer.php: -------------------------------------------------------------------------------- 1 | $this->projectName(), 18 | 'alias' => $this->name(), 19 | 'container_name' => $this->makeContainerName(), 20 | 'image_name' => $this->makeImageName(), 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Docker/Docker.php: -------------------------------------------------------------------------------- 1 | shell = $shell; 26 | } 27 | 28 | /** 29 | * @return void 30 | * @throws DockerNotInstalledException 31 | */ 32 | public function verifyDockerIsInstalled(): void 33 | { 34 | try { 35 | $this->version(); 36 | 37 | } catch (ProcessFailedException $e) { 38 | throw new DockerNotInstalledException('Docker is missing!'); 39 | } 40 | } 41 | 42 | /** 43 | * @return void 44 | * @throws DockerNotRunningException 45 | */ 46 | public function verifyDockerDemonIsRunning(): void 47 | { 48 | try { 49 | $this->shell->exec('docker info'); 50 | 51 | } catch (ProcessFailedException $e) { 52 | throw new DockerNotRunningException('Docker isn\'t running'); 53 | } 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function version(): string 60 | { 61 | return $this->shell->exec('docker version --format="{{json .Client.Version}}"'); 62 | } 63 | 64 | /** 65 | * @param string $name 66 | * @return void 67 | */ 68 | public function ensureNetworkExists(string $name): void 69 | { 70 | try { 71 | $this->shell->exec('docker network inspect "${:name}"', ['name' => $name]); 72 | 73 | } catch (ProcessFailedException $e) { 74 | //Make network 75 | 76 | $this->shell->exec('docker network create "${:name}"', ['name' => $name]); 77 | } 78 | } 79 | 80 | public function removeNetwork(string $name): void 81 | { 82 | $this->shell->exec('docker network rm ' . $name); 83 | } 84 | 85 | /** 86 | * @return \Illuminate\Support\Collection 87 | */ 88 | public function listContainers() 89 | { 90 | $name = $this->makeName(); 91 | $containers = $this->shell->exec('docker ps --all --filter "name=' . $name . '" --format "{{.ID}}|{{.Names}}|{{.Image}}|{{.Status}}|{{.Ports}}"'); 92 | $formatted = collect(explode("\n", $containers))->filter()->map(function ($row) { 93 | return explode('|', $row); 94 | })->reverse(); 95 | 96 | return $formatted->prepend([ 97 | 'ID', 'Name', 'Image', 'Status', 'Used ports' 98 | ]); 99 | 100 | } 101 | 102 | public function getUsedPort($name, $internalPort) 103 | { 104 | $name = $this->makeName($name); 105 | try { 106 | $usedPort = $this->shell->exec('docker port ' . $name . ' ' . $internalPort); 107 | 108 | } catch (ProcessFailedException $e) { 109 | return null; 110 | } 111 | 112 | return intval(Str::afterLast(trim($usedPort), ':')); 113 | } 114 | 115 | protected function makeName($name = '') 116 | { 117 | return sprintf('served_%s_%s', app('served.name'), $name); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/Docker/DockerFileBuilder.php: -------------------------------------------------------------------------------- 1 | fileStructure[] = sprintf('FROM %1$s:%2$s', $image, $tag); 20 | 21 | return $this; 22 | } 23 | 24 | /** 25 | * @return $this 26 | */ 27 | public function newLine(): self 28 | { 29 | $this->fileStructure[] = ''; 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * @param string $comment 36 | * @param false $newLineBefore 37 | * @return $this 38 | */ 39 | public function comment(string $comment, $newLineBefore = false): self 40 | { 41 | if ($newLineBefore) { 42 | $this->newLine(); 43 | } 44 | 45 | $this->fileStructure[] = '# ' . str_replace("\n", "\n# ", $comment); 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * @param string $arg 52 | * @return $this 53 | */ 54 | public function arg(string $arg): self 55 | { 56 | $this->fileStructure[] = sprintf('ARG %s', $arg); 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * @param string|array $command 63 | * @return $this 64 | */ 65 | public function run($command, $delimiter = '&&'): self 66 | { 67 | if (is_array($command)) { 68 | $command = implode(' \\' . "\n $delimiter ", $command); 69 | } 70 | 71 | $this->fileStructure[] = sprintf('RUN %s', $command); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * @param string $name 78 | * @param string $value 79 | * @return $this 80 | */ 81 | public function env(string $name, string $value): self 82 | { 83 | $this->fileStructure[] = sprintf('ENV %1$s=%2$s', $name, $value); 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * @param $source 90 | * @param string $target 91 | * @param string|null $from 92 | * @return $this 93 | */ 94 | public function copy($source, string $target, ?string $from = null): self 95 | { 96 | if (is_array($source)) { 97 | $source = implode(' ', $source); 98 | $target = rtrim($target, '/') . '/'; 99 | } 100 | $from = $from ? ' --from=' . $from : null; 101 | $this->fileStructure[] = sprintf('COPY%3$s %1$s %2$s', $source, $target, $from); 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * @param string $workdir 108 | * @return $this 109 | */ 110 | public function workdir(string $workdir) 111 | { 112 | $this->fileStructure[] = sprintf('WORKDIR %s', $workdir); 113 | return $this; 114 | } 115 | 116 | /** 117 | * @param string $workdir 118 | * @return $this 119 | */ 120 | public function cmd(array $cmd) 121 | { 122 | $items = array_map(function($item) { 123 | return '"' . $item . '"'; 124 | }, $cmd); 125 | 126 | $this->fileStructure[] = sprintf('CMD [ %s ]', implode(', ', $items)); 127 | return $this; 128 | } 129 | 130 | /** 131 | * @return string 132 | */ 133 | public function __toString(): string 134 | { 135 | return implode("\n", $this->fileStructure); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Exceptions/DockerNotInstalledException.php: -------------------------------------------------------------------------------- 1 | copyDockerFile(__DIR__ . '/stubs/localhost.crt', 'localhost.crt'); 33 | $this->copyDockerFile(__DIR__ . '/stubs/localhost.key', 'localhost.key'); 34 | } 35 | 36 | /** 37 | * @return array 38 | */ 39 | protected function prepareEnv(): array 40 | { 41 | return [ 42 | 'imagename' => $this->makeImageName(), 43 | 'dockerfile' => $this->findDockerFile(), 44 | ]; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function writeDockerFile(): string 51 | { 52 | $command = $this->getBaseDockerFile() 53 | ->env('WEB_PHP_SOCKET', 'served_php:9000') 54 | ->env('WEB_DOCUMENT_ROOT', '/app/public') 55 | ->env('WEB_PHP_TIMEOUT', '60') 56 | ->copy($this->storageDirectory(true) . 'localhost.key', '/opt/docker/etc/httpd/ssl/server.key') 57 | ->copy($this->storageDirectory(true) . 'localhost.crt', '/opt/docker/etc/httpd/ssl/server.crt'); 58 | 59 | return (string)$command; 60 | 61 | } 62 | 63 | public function imageTag(): string 64 | { 65 | return $this->tag; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Images/Image.php: -------------------------------------------------------------------------------- 1 | config = $config; 68 | $this->shell = $shell; 69 | $this->dockerFileBuilder = app(DockerFileBuilder::class); 70 | $this->name = $name; 71 | $this->parseConfig(); 72 | } 73 | 74 | /** 75 | * @return $this 76 | */ 77 | public function prepareBuild(): self 78 | { 79 | $this->prepareConfFiles(); 80 | $dockerFile = $this->writeDockerFile(); 81 | $this->storeDockerfile($dockerFile); 82 | 83 | return $this; 84 | } 85 | 86 | protected function getBaseDockerFile() 87 | { 88 | $command = $this->dockerFileBuilder->from($this->imageName(), $this->imageTag()); 89 | 90 | if ($proxyHttp = config('served.proxy.http', false)) { 91 | $command->env('http_proxy', $proxyHttp); 92 | } 93 | 94 | if ($proxyHttps = config('served.proxy.https', false)) { 95 | $command->env('https_proxy', $proxyHttps); 96 | } 97 | 98 | return $command; 99 | } 100 | 101 | /** 102 | * @return void 103 | */ 104 | public function parseConfig(): void 105 | { 106 | foreach ($this->config as $key => $value) { 107 | if ($key === 'version') { 108 | $this->setImageTag($value); 109 | } 110 | 111 | if ($key === 'alias') { 112 | $this->setAlias($value); 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * @param boolean $noCache 119 | * @return void 120 | */ 121 | public function build($noCache): void 122 | { 123 | $this->shell->run( 124 | $this->prepareBuildCommand($noCache), 125 | $this->prepareEnv() 126 | ); 127 | } 128 | 129 | protected function prepareBuildCommand($noCache) 130 | { 131 | return $this->buildCommand . ($noCache ? $this->buildFlags : ''); 132 | } 133 | 134 | /** 135 | * @return array 136 | */ 137 | protected function prepareEnv() 138 | { 139 | return []; 140 | } 141 | 142 | protected function prepareConfFiles() 143 | { 144 | // 145 | } 146 | 147 | /** 148 | * @return bool 149 | */ 150 | public function imageExists(): bool 151 | { 152 | try { 153 | $this->shell->exec(sprintf('docker inspect image %s', $this->makeImageName())); 154 | return true; 155 | } catch (ProcessFailedException $e) { 156 | return false; 157 | } 158 | } 159 | 160 | /** 161 | * @return void 162 | */ 163 | public function remove(): void 164 | { 165 | $this->shell->exec(sprintf('docker rmi %s -f', $this->makeImageName())); 166 | } 167 | 168 | /** 169 | * @return string 170 | */ 171 | protected function makeImageName(): string 172 | { 173 | return sprintf('served/%s_%s', $this->projectName(), $this->name()); 174 | } 175 | 176 | /** 177 | * @return string 178 | */ 179 | public function imageName(): string 180 | { 181 | return sprintf('%s/%s', $this->library, $this->image); 182 | } 183 | 184 | /** 185 | * @return string 186 | */ 187 | public function imageTag(): string 188 | { 189 | return sprintf('%s%s', $this->tag, $this->tagAddition); 190 | } 191 | 192 | /** 193 | * @param $tag 194 | * @return $this 195 | */ 196 | public function setImageTag($tag): self 197 | { 198 | if ($tag) { 199 | $this->tag = $tag; 200 | } 201 | 202 | return $this; 203 | } 204 | 205 | /** 206 | * @param string $alias 207 | * @return $this 208 | */ 209 | public function setAlias(string $alias): self 210 | { 211 | if ($alias) { 212 | $this->alias = $alias; 213 | } 214 | 215 | return $this; 216 | } 217 | 218 | /** 219 | * @return string 220 | */ 221 | protected function projectName(): string 222 | { 223 | return app('served.name'); 224 | } 225 | 226 | /** 227 | * @return string 228 | */ 229 | public function simpleName() 230 | { 231 | return strtolower(class_basename($this)); 232 | } 233 | 234 | /** 235 | * @return string 236 | */ 237 | public function name(): string 238 | { 239 | return $this->name; 240 | } 241 | 242 | /** 243 | * @param string $content 244 | * @return void 245 | */ 246 | protected function storeDockerfile(string $content): void 247 | { 248 | $storagePath = $this->storageDirectory(); 249 | file_put_contents($storagePath . 'Dockerfile', $content); 250 | } 251 | 252 | /** 253 | * @param string $filePath 254 | * @param string $targetName 255 | * @return void 256 | */ 257 | protected function copyDockerFile(string $filePath, string $targetName): void 258 | { 259 | $storagePath = $this->storageDirectory(); 260 | copy($filePath, $storagePath . $targetName); 261 | } 262 | 263 | /** 264 | * @return string 265 | */ 266 | protected function findDockerFile(): string 267 | { 268 | return $this->storageDirectory() . 'Dockerfile'; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/Images/ImageInterface.php: -------------------------------------------------------------------------------- 1 | $this->makeImageName(), 42 | 'uid' => getmyuid(), 43 | 'dockerfile' => $this->findDockerFile(), 44 | ]; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function writeDockerFile(): string 51 | { 52 | return (string) $this->getBaseDockerFile(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Images/MemcachedImage.php: -------------------------------------------------------------------------------- 1 | $this->makeImageName(), 37 | 'dockerfile' => $this->findDockerFile(), 38 | ]; 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function writeDockerFile(): string 45 | { 46 | return (string) $this->getBaseDockerFile(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Images/MysqlImage.php: -------------------------------------------------------------------------------- 1 | $this->makeImageName(), 30 | 'uid' => getmyuid(), 31 | 'dockerfile' => $this->findDockerFile() 32 | ]; 33 | } 34 | 35 | /** 36 | * @return string 37 | */ 38 | public function writeDockerFile(): string 39 | { 40 | $command = $this->getBaseDockerFile() 41 | ->newLine() 42 | ->comment('Setting env vars for mysql init') 43 | ->env('MYSQL_ROOT_PASSWORD', 'password') 44 | ->env('MYSQL_DATABASE', 'laravel') 45 | ->env('MYSQL_USER', 'laravel') 46 | ->env('MYSQL_PASSWORD', 'password'); 47 | 48 | return (string)$command; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Images/NginxImage.php: -------------------------------------------------------------------------------- 1 | copyDockerFile(__DIR__ . '/stubs/nginx.conf', 'default.conf'); 28 | $this->copyDockerFile(__DIR__ . '/stubs/localhost.crt', 'localhost.crt'); 29 | $this->copyDockerFile(__DIR__ . '/stubs/localhost.key', 'localhost.key'); 30 | } 31 | 32 | /** 33 | * @return array 34 | */ 35 | protected function prepareEnv() 36 | { 37 | return [ 38 | 'imagename' => $this->makeImageName(), 39 | 'uid' => getmyuid(), 40 | 'dockerfile' => $this->findDockerFile(), 41 | ]; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function writeDockerFile(): string 48 | { 49 | $command = $this->getBaseDockerFile() 50 | ->newLine() 51 | ->comment('Copy in new nginx config') 52 | // ->copy('storage/served/nginx/default.conf', '/etc/nginx/conf.d/default.conf'); 53 | ->copy($this->storageDirectory(true) . 'default.conf', '/etc/nginx/conf.d/default.conf') 54 | ->copy($this->storageDirectory(true) . 'localhost.key', '/etc/nginx/ssl/server.key') 55 | ->copy($this->storageDirectory(true) . 'localhost.crt', '/etc/nginx/ssl/server.crt'); 56 | 57 | return (string)$command; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Images/PhpImage.php: -------------------------------------------------------------------------------- 1 | $this->makeImageName(), 36 | 'uid' => getmyuid() <= 1 ? 1000 : getmyuid(), 37 | 'dockerfile' => $this->findDockerFile(), 38 | ]; 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function writeDockerFile(): string 45 | { 46 | $command = $this->getBaseDockerFile(); 47 | 48 | $command->comment('disable warnings for "dangerous" messages', true) 49 | ->env('APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE', '1'); 50 | 51 | $runInstalls = [ 52 | 'apt-get update', 53 | 'apt-get install -y unzip zip gnupg', 54 | 'rm -rf /var/lib/apt/lists/*', 55 | ]; 56 | 57 | $command 58 | ->comment('Adding linux packages', true) 59 | ->run($runInstalls); 60 | 61 | $gpgOptions = ''; 62 | if ($proxyHttp = config('served.proxy.http', false)) { 63 | $gpgOptions .= '--keyserver-options http-proxy=${http_proxy}'; 64 | } 65 | 66 | $command 67 | ->comment('Installing packages for sql dump') 68 | ->run([ 69 | 'set -ex;', 70 | 'key=\'8C718D3B5072E1F5\';', 71 | 'export GNUPGHOME="$(mktemp -d)";', 72 | 'gpg --batch ' . $gpgOptions . ' --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys "$key";', 73 | 'gpg --batch --export "$key" > /etc/apt/trusted.gpg.d/mysql.gpg;', 74 | 'gpgconf --kill all;', 75 | 'rm -rf "$GNUPGHOME";', 76 | 'apt-key list > /dev/null', 77 | ], '') 78 | ->newLine() 79 | ->run([ 80 | 'echo "deb http://repo.mysql.com/apt/debian/ buster mysql-8.0" > /etc/apt/sources.list.d/mysql.list', 81 | 'apt-get update', 82 | 'apt-get install -y mysql-community-client postgresql-client sqlite3', 83 | 'rm -rf /var/lib/apt/lists/*' 84 | ]); 85 | 86 | 87 | $command 88 | ->comment('add development php.ini file', true) 89 | ->run('mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"'); 90 | 91 | $command 92 | ->comment("add a local user with the same uid as the local\nprepare empty composer config directory\nensure user owns its home directory") 93 | ->arg('uid') 94 | ->run([ 95 | 'useradd -G root -u $uid -d /home/served served', 96 | 'mkdir -p /home/served/.composer', 97 | 'chown -R served:served /home/served' 98 | ]) 99 | ->comment("add composer\nset composer to use https", true) 100 | ->run([ 101 | 'curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer', 102 | 'runuser -l served -c "composer config --global repos.packagist composer https://packagist.org"', 103 | ]); 104 | 105 | if (Arr::get($this->config, 'npm', false)) { 106 | $command 107 | ->comment('Adding NPM') 108 | ->run([ 109 | 'curl -sL https://deb.nodesource.com/setup_12.x | bash', 110 | 'apt-get install -y nodejs', 111 | 'curl -L https://www.npmjs.com/install.sh | sh', 112 | ]); 113 | } 114 | 115 | $modules = Arr::get($this->config, 'modules', []); 116 | 117 | if (Arr::get($this->config, 'xdebug.enabled') && !in_array('xdebug', $modules)) { 118 | $modules[] = 'xdebug'; 119 | } 120 | 121 | if ($modules) { 122 | if ($proxyHttp = config('served.proxy.http', false)) { 123 | $command 124 | ->comment('setting proxy for pear') 125 | ->run('pear config-set http_proxy ${http_proxy}'); 126 | } 127 | $command 128 | ->comment('Adding php packages', true) 129 | ->copy('/usr/bin/install-php-extensions', '/usr/bin/', 'mlocati/php-extension-installer') 130 | ->run('install-php-extensions ' . implode(' ', $modules)); 131 | 132 | } 133 | 134 | if (in_array('xdebug', $modules) && Arr::get($this->config, 'xdebug.enabled')) { 135 | $command 136 | ->comment('Adding xdebug', true) 137 | ->run([ 138 | 'echo "[xdebug]" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini"', 139 | 'echo "xdebug.remote_enable = 1" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini"', 140 | 'echo "xdebug.remote_port = ' . Arr::get($this->config, 'xdebug.port', 9001) . '" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini"', 141 | 'echo "xdebug.remote_connect_back = 1" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini"', 142 | 'echo "xdebug.remote_autostart = 1" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini"', 143 | ]); 144 | } 145 | 146 | $command 147 | ->comment('Set work dir', true) 148 | ->workdir('/app'); 149 | 150 | return (string)$command; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Images/PostgresImage.php: -------------------------------------------------------------------------------- 1 | $this->makeImageName(), 30 | 'dockerfile' => $this->findDockerFile() 31 | ]; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function writeDockerFile(): string 38 | { 39 | $command = $this->getBaseDockerFile() 40 | ->newLine() 41 | ->comment('Setting env vars for postgres init') 42 | ->env('POSTGRES_DB', 'laravel') 43 | ->env('POSTGRES_USER', 'laravel') 44 | ->env('POSTGRES_PASSWORD', 'password'); 45 | 46 | return (string)$command; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Images/RedisImage.php: -------------------------------------------------------------------------------- 1 | $this->makeImageName(), 37 | 'uid' => getmyuid(), 38 | 'dockerfile' => $this->findDockerFile(), 39 | ]; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function writeDockerFile(): string 46 | { 47 | $command = $this->getBaseDockerFile() 48 | ->cmd(['redis-server']); 49 | 50 | return (string) $command; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Images/stubs/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFJTCCAw2gAwIBAgIUJUn5rdtwaQhuVMVxlrNM/AiVrXowDQYJKoZIhvcNAQEL 3 | BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIwMTAwMTA5NDkxMVoXDTMwMDky 4 | OTA5NDkxMVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF 5 | AAOCAg8AMIICCgKCAgEA15Su38lrcKIvMnCTviDxUGZkPoFG1QaMAxD++PsDkYM8 6 | Hk/3l4QKowhqGEV8YJOfQa5bptdyRvm0znwro4rVdDa9KJ3Bz5YqM+7IMe2QX5Sd 7 | IGbyGW4pqi8+Ss10GfpfFWp0Xm01np1eFvCMhGdhjZEI5BL2wl8cvCnv8iUUDvqC 8 | BE8VkiQIr8ZnGm9ouDG2uwpW0+cUJS+hOGb8tulxoau/IVcUc/apMBsSiWDvhhb5 9 | P5Pj0fGuF0WUb1ayJHPo55/96DdEpJE9nFzEMXi/zMqGvwteRzKS6WP5LhRdKIAJ 10 | DNa8V3dzGs6oyr6lumKqjKmqHiQmDouuZoRnO1xc25C7XT8SCpIUEy47nRlVmYeX 11 | /fGz6K8ZnU4lxKQS9hsmfYPMWO0ntfnyGrGTgJszSZz6giPE3HmDdY4oJ88KhNhS 12 | MmLtDa5xFIcMbIEazlxiSWM/y84A3XgSMTcyhuQcTizKLzvqD2toQZebOkpDSCyZ 13 | GJc9UGSDBeqOu7F7eXp8FRi3e04MCqtNm9AbYcJCQcVPzcW/13j3z/QESXQuMpdU 14 | s7p7+rknHwlKJHE37gzbEkJZ6qYIJqToGXi0fBE59tKsv8XwwaJete8uA8heIpYE 15 | ch4YaFhHLOJZi6iQX+cCkA50VyH7kZdggL3BqaC9SUIrWV90uaB42ErgZoIHqTcC 16 | AwEAAaNvMG0wHQYDVR0OBBYEFGnfcwb/YmVY8C3APuYCvdKfPhEJMB8GA1UdIwQY 17 | MBaAFGnfcwb/YmVY8C3APuYCvdKfPhEJMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R 18 | BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQA4xKwwyL9B 19 | +Omyp48qiIGhEkmN7gkMjCk3zXkBTRIliytUcL/2SYJP9DqRXJzuD+HOoROesa93 20 | Jq1Zkp7C+6EqXLKbltW+0KNBQiIzuvTqXhK8LoQsiTv60jzWY9RDSwhn26BHZN8s 21 | zPE3/XQJ4rcwNz/V4EB7AbjSR9Y0hQrPhqNNJ17v4yWx/bWO2zSN+Uu7LZQ/2fFh 22 | sTznB8io/ZwoUAmtDzv1qWZajihmsK0d5kJU9T+7Mz3qk1LeaxS/BzHnXek7ul1f 23 | zZ5r6OD0Znlc9BMddVVT0mOAJWRyfSyZjXw8zVCk5S/3WPsFeTALCl/+hQeyRZGH 24 | CtLo0CtpHCVGX3+uP1/U21NsfG9gfCINpBRc68BJcPZJbDXLLVVen5jLEDNN7JGd 25 | ok3rQ49kpqk0ATAk3GtOEJRex8MyryuKEKKvJf5yIA4e6uhBx6pK25cFVJdOygE5 26 | m2OGIcsF7tbR1kvBrcg+k0y4rwK0wJiQeOJGMdLnQ6ZkNBZxkK2/2ggDhcb4f+1v 27 | 2HZ4E+zc5NoKo2f+m41QtKOWecgYDILbPX2uxW/83YGC6qxeEtu5zSMSFjwwbOLL 28 | EBooISukaRW+IZBO18tQm52j8qYFAPB7+fbi6n2NNDyBwLcaeirow2hbCj8xkS61 29 | SSM7Sq9IZjkBeoms4sIfYjxvAX2WUtJhhA== 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /src/Images/stubs/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDXlK7fyWtwoi8y 3 | cJO+IPFQZmQ+gUbVBowDEP74+wORgzweT/eXhAqjCGoYRXxgk59Brlum13JG+bTO 4 | fCujitV0Nr0oncHPlioz7sgx7ZBflJ0gZvIZbimqLz5KzXQZ+l8VanRebTWenV4W 5 | 8IyEZ2GNkQjkEvbCXxy8Ke/yJRQO+oIETxWSJAivxmcab2i4Mba7ClbT5xQlL6E4 6 | Zvy26XGhq78hVxRz9qkwGxKJYO+GFvk/k+PR8a4XRZRvVrIkc+jnn/3oN0SkkT2c 7 | XMQxeL/Myoa/C15HMpLpY/kuFF0ogAkM1rxXd3MazqjKvqW6YqqMqaoeJCYOi65m 8 | hGc7XFzbkLtdPxIKkhQTLjudGVWZh5f98bPorxmdTiXEpBL2GyZ9g8xY7Se1+fIa 9 | sZOAmzNJnPqCI8TceYN1jignzwqE2FIyYu0NrnEUhwxsgRrOXGJJYz/LzgDdeBIx 10 | NzKG5BxOLMovO+oPa2hBl5s6SkNILJkYlz1QZIMF6o67sXt5enwVGLd7TgwKq02b 11 | 0BthwkJBxU/Nxb/XePfP9ARJdC4yl1Szunv6uScfCUokcTfuDNsSQlnqpggmpOgZ 12 | eLR8ETn20qy/xfDBol617y4DyF4ilgRyHhhoWEcs4lmLqJBf5wKQDnRXIfuRl2CA 13 | vcGpoL1JQitZX3S5oHjYSuBmggepNwIDAQABAoICAHCdR9i5RDm7T0JfEp6gYM6q 14 | HjWUnKbNW7iCWV9A6PVLg80l4uWwYUoXLCzvp3BfzTKnXVNDenvfF3dB4B33eVfS 15 | /G9KMaM6A2PLmaKTQfbcEFSL0m48YOF4+mZi+wJTCvaJ/K4TCI6KEEuVbyH/SzOD 16 | jwxtZ5/TxZP5qFFq0xab/+02TsNftXX1A4kIp8CIn7cHSKI7NQfT4Lkw+1Slj+lv 17 | aVGGRrXpJSpvCfjfvV/jgmKW48yZHmMjws2CkV5/eiv9JMr4jBAXmwKiZw6c0Dyv 18 | k0IbMy2oGpx20AUlOCDe+VtOlWZvjJ51VDFM9A/wWL1QKADEy2iPyRPemHqm3g4S 19 | xQD9BjivxUkc9QacLoRunUwfrSe1lL+yyuBuiQWDWK1PEiupPHHL+/gt0d4kuNof 20 | IOZ5dV8irczxU4CLnOa1jI4El37yea+UOlAESUO1a7pd8C5iQuh//t/8H9993Fi6 21 | /gz68/0WbbuVCxRXDEyBtZUB421vpmVF2si2BdZdi/6SWuIbmQbTlBo9J4UPe5P0 22 | 5QpQCgQYi9Otw4U7XQ9h2MuH7TjbKHbY+NSu6q8SHPobCKHzRDU8+rx/wTh0nmKA 23 | B54cNCOq7G5qnZ/6gNa24lcpA1PFdj9b/4AGLkyK0Ww3GSo3NpZ7Ug0QPxsJWm9j 24 | pvblXJ0RGw8ANEOvOugBAoIBAQD3G0TtT/eyqpYCpXL5R5k6DoSo7Zhu+F/RFUJE 25 | LVz0ih6+Cwz6MgDTjt+zyKJxXrozSYim7qO7R1jPfaGC6GqxY9s+IHvqPFri4vhl 26 | /svVURjOEO+WmdI2qxxwSaCXXICriHjHld2bA0O1g7y5Q/8vk2vPOY5Ne4dFTgLm 27 | 8tA2/qRp+8NAqNpM0Oc0vFmtZIHmazvWpQ80ww1wwsxTh8TKUOa5BsFrQs0Gbpe5 28 | b8uvvOSV/ocJHD1erdcliUBRWcjkmgS9ArosFrI1obel0//fTAgcL8j9pDjPslzG 29 | sQrwzL0iNXcGEG0wrcnEyA7BDvXA7CcJkP2Dfb3IYKu8iN03AoIBAQDfVvMeEgru 30 | cUgXbt1m9SIWFY5Vkw7pfzfaj8shOulJcM67HFUg8IZ6bnnIjuOly84RoigbzKsM 31 | GdJ/WzlpGT0XlbH3lb0ZnorFgYRT/YnFqtvRVlcYN9x9kM6ZOM85vA1PUIilVKaC 32 | IkpSeiBXMnN4IoLbjua68TvtZ60zvcmcAja8KteNZ7efJ7FMwnnsI0JYk/N84yl1 33 | LaWVnp+4aneHseq3f19sQawZfNT2TOECijFK21sHgBne5J7dlW413jJp28xVCzF4 34 | sDql+n/suUfyirHnNaUJ6CdUsoi+muIBer0IGOidqoHKi1G25KC5srPcugtKtsxG 35 | URqmXhxMvZQBAoIBAB99uisEJez/EF8F9sEN/tkHQKDGpsZ9oLhknS1TGqWPdJuD 36 | jQPVm4Vaj+e5ifoouFIQ7PlZMESNsyO+PvcP54jz0Nz6BtCzIGIJyt40uoVU8HRS 37 | dDYdJE0TQWyN9YlUoJE7syi8UKGQqPBY+ZQitkK55uNh0mDNfU+3wWbtStu3V/yp 38 | uRhkbG2dsdlmp4cRZ/yVberM3kM7GFtmd+OtScb+yGiME9o3iSlorq1TMgITcI4t 39 | AciHcMrAHMsL0saLSq7XcgMkddVojw2GroDTo6gxaFcvP84TP6o4cNphdaN6dCAH 40 | 8EM1lLS/cRdC555y6Z7Mea0ebTB0tdrzdu9wAcECggEBAJS+1Cyy15T23Yy9ybdI 41 | i+spcCKOTuA0Wn62RhNbqQPAne8Ab7IAf5ALBBEPGY7SrewQk6XwKftlN0ya9SGK 42 | LaYHjP/YOplVfhcMq5VExv1fTged+WOn0LHQP8jMjTdmh3bLrDZwqnUBYX6M1/07 43 | HKxmT8Dq68CV6dOzuSc8v4mn78xivCzxZtoZFXyKCam70fQslX4XzQS94gpEGxw3 44 | zGQTmr+blXIESxquiSeBDFskrq+saHQWXSSWHzh0zXITCoB2YyBA1DINLQJeU9TV 45 | kZV3ygSzNbfjZk6CmZBYly1lEYDTFhnr9YVwRHwKyQDkg+X+AodAN8ydN6KWC0MR 46 | AAECggEBAJyxTnVZFSttq99jY38+tI2KnAAmFtWYiG7EWFvFlh8gslI7jlaD2Q9J 47 | H6NZT3mmKzK0QSYPzGuKLlzsRsFGSemPKqc52TPS/81vknejIv7OIYwPAH2B6PzJ 48 | 8tVUMslEEMXf0LiT538bMgb+lJvMzJRzUvbusZwnGnaXl+ZqFwpkAxa65AjeQC45 49 | mJIJ9v67ka841gszYiFrNJA0wc1DVRVtT0lC3PaewQR/1mdDfZo0eHjj8ry7m+qZ 50 | sCeyZlFTS7kk53Mb0j3Y0YwP3AiciaM7V2EBAYj9PeIIYmq1xkkJkrVjdrDb5cR2 51 | pxldlOxgBdmXQTp/xlAxTR8208AyRxU= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /src/Images/stubs/my-httpd-vhosts.conf: -------------------------------------------------------------------------------- 1 | # Virtual Hosts 2 | # 3 | # Required modules: mod_log_config 4 | 5 | # If you want to maintain multiple domains/hostnames on your 6 | # machine you can setup VirtualHost containers for them. Most configurations 7 | # use only name-based virtual hosts so the server doesn't need to worry about 8 | # IP addresses. This is indicated by the asterisks in the directives below. 9 | # 10 | # Please see the documentation at 11 | # 12 | # for further details before you try to setup virtual hosts. 13 | # 14 | # You may use the command line option '-S' to verify your virtual host 15 | # configuration. 16 | 17 | # 18 | # VirtualHost example: 19 | # Almost any Apache directive may go into a VirtualHost container. 20 | # The first VirtualHost section is used for all requests that do not 21 | # match a ServerName or ServerAlias in any block. 22 | # 23 | 24 | ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://served_php:9000${APACHE_ROOT_DIR}/public/$1 25 | 26 | DocumentRoot ${APACHE_ROOT_DIR}/public 27 | 28 | 29 | Options Indexes FollowSymLinks 30 | AllowOverride All 31 | Require all granted 32 | 33 | 34 | ErrorLog ${APACHE_ROOT_DIR}/logs/error.log 35 | CustomLog ${APACHE_ROOT_DIR}/logs/access.log common 36 | 37 | 38 | 39 | ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://served_php:9000${APACHE_ROOT_DIR}/public/$1 40 | 41 | DocumentRoot ${APACHE_ROOT_DIR}/public 42 | 43 | 44 | Options Indexes FollowSymLinks 45 | AllowOverride All 46 | Require all granted 47 | 48 | 49 | SSLEngine on 50 | SSLCertificateFile /etc/apache2/ssl/localhost.crt 51 | SSLCertificateKeyFile /etc/apache2/ssl/localhost.key 52 | 53 | ErrorLog ${APACHE_ROOT_DIR}/logs/error.log 54 | CustomLog ${APACHE_ROOT_DIR}/logs/access.log common 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/Images/stubs/my-httpd.conf: -------------------------------------------------------------------------------- 1 | # 2 | # This is the main Apache HTTP server configuration file. It contains the 3 | # configuration directives that give the server its instructions. 4 | # See for detailed information. 5 | # In particular, see 6 | # 7 | # for a discussion of each configuration directive. 8 | # 9 | # Do NOT simply read the instructions in here without understanding 10 | # what they do. They're here only as hints or reminders. If you are unsure 11 | # consult the online docs. You have been warned. 12 | # 13 | # Configuration and logfile names: If the filenames you specify for many 14 | # of the server's control files begin with "/" (or "drive:/" for Win32), the 15 | # server will use that explicit path. If the filenames do *not* begin 16 | # with "/", the value of ServerRoot is prepended -- so "logs/access_log" 17 | # with ServerRoot set to "/usr/local/apache2" will be interpreted by the 18 | # server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log" 19 | # will be interpreted as '/logs/access_log'. 20 | 21 | # 22 | # ServerRoot: The top of the directory tree under which the server's 23 | # configuration, error, and log files are kept. 24 | # 25 | # Do not add a slash at the end of the directory path. If you point 26 | # ServerRoot at a non-local disk, be sure to specify a local disk on the 27 | # Mutex directive, if file-based mutexes are used. If you wish to share the 28 | # same ServerRoot for multiple httpd daemons, you will need to change at 29 | # least PidFile. 30 | # 31 | ServerRoot "/usr/local/apache2" 32 | 33 | # 34 | # Mutex: Allows you to set the mutex mechanism and mutex file directory 35 | # for individual mutexes, or change the global defaults 36 | # 37 | # Uncomment and change the directory if mutexes are file-based and the default 38 | # mutex file directory is not on a local disk or is not appropriate for some 39 | # other reason. 40 | # 41 | # Mutex default:logs 42 | 43 | # 44 | # Listen: Allows you to bind Apache to specific IP addresses and/or 45 | # ports, instead of the default. See also the 46 | # directive. 47 | # 48 | # Change this to Listen on specific IP addresses as shown below to 49 | # prevent Apache from glomming onto all bound IP addresses. 50 | # 51 | #Listen 12.34.56.78:80 52 | Listen 80 53 | 54 | # 55 | # Dynamic Shared Object (DSO) Support 56 | # 57 | # To be able to use the functionality of a module which was built as a DSO you 58 | # have to place corresponding `LoadModule' lines at this location so the 59 | # directives contained in it are actually available _before_ they are used. 60 | # Statically compiled modules (those listed by `httpd -l') do not need 61 | # to be loaded here. 62 | # 63 | # Example: 64 | # LoadModule foo_module modules/mod_foo.so 65 | # 66 | LoadModule mpm_event_module modules/mod_mpm_event.so 67 | #LoadModule mpm_prefork_module modules/mod_mpm_prefork.so 68 | #LoadModule mpm_worker_module modules/mod_mpm_worker.so 69 | LoadModule authn_file_module modules/mod_authn_file.so 70 | #LoadModule authn_dbm_module modules/mod_authn_dbm.so 71 | #LoadModule authn_anon_module modules/mod_authn_anon.so 72 | #LoadModule authn_dbd_module modules/mod_authn_dbd.so 73 | #LoadModule authn_socache_module modules/mod_authn_socache.so 74 | LoadModule authn_core_module modules/mod_authn_core.so 75 | LoadModule authz_host_module modules/mod_authz_host.so 76 | LoadModule authz_groupfile_module modules/mod_authz_groupfile.so 77 | LoadModule authz_user_module modules/mod_authz_user.so 78 | #LoadModule authz_dbm_module modules/mod_authz_dbm.so 79 | #LoadModule authz_owner_module modules/mod_authz_owner.so 80 | #LoadModule authz_dbd_module modules/mod_authz_dbd.so 81 | LoadModule authz_core_module modules/mod_authz_core.so 82 | #LoadModule authnz_ldap_module modules/mod_authnz_ldap.so 83 | #LoadModule authnz_fcgi_module modules/mod_authnz_fcgi.so 84 | LoadModule access_compat_module modules/mod_access_compat.so 85 | LoadModule auth_basic_module modules/mod_auth_basic.so 86 | #LoadModule auth_form_module modules/mod_auth_form.so 87 | #LoadModule auth_digest_module modules/mod_auth_digest.so 88 | #LoadModule allowmethods_module modules/mod_allowmethods.so 89 | #LoadModule isapi_module modules/mod_isapi.so 90 | #LoadModule file_cache_module modules/mod_file_cache.so 91 | #LoadModule cache_module modules/mod_cache.so 92 | #LoadModule cache_disk_module modules/mod_cache_disk.so 93 | #LoadModule cache_socache_module modules/mod_cache_socache.so 94 | LoadModule socache_shmcb_module modules/mod_socache_shmcb.so 95 | #LoadModule socache_dbm_module modules/mod_socache_dbm.so 96 | #LoadModule socache_memcache_module modules/mod_socache_memcache.so 97 | #LoadModule socache_redis_module modules/mod_socache_redis.so 98 | #LoadModule watchdog_module modules/mod_watchdog.so 99 | #LoadModule macro_module modules/mod_macro.so 100 | #LoadModule dbd_module modules/mod_dbd.so 101 | #LoadModule bucketeer_module modules/mod_bucketeer.so 102 | #LoadModule dumpio_module modules/mod_dumpio.so 103 | #LoadModule echo_module modules/mod_echo.so 104 | #LoadModule example_hooks_module modules/mod_example_hooks.so 105 | #LoadModule case_filter_module modules/mod_case_filter.so 106 | #LoadModule case_filter_in_module modules/mod_case_filter_in.so 107 | #LoadModule example_ipc_module modules/mod_example_ipc.so 108 | #LoadModule buffer_module modules/mod_buffer.so 109 | #LoadModule data_module modules/mod_data.so 110 | #LoadModule ratelimit_module modules/mod_ratelimit.so 111 | LoadModule reqtimeout_module modules/mod_reqtimeout.so 112 | #LoadModule ext_filter_module modules/mod_ext_filter.so 113 | #LoadModule request_module modules/mod_request.so 114 | #LoadModule include_module modules/mod_include.so 115 | LoadModule filter_module modules/mod_filter.so 116 | #LoadModule reflector_module modules/mod_reflector.so 117 | #LoadModule substitute_module modules/mod_substitute.so 118 | #LoadModule sed_module modules/mod_sed.so 119 | #LoadModule charset_lite_module modules/mod_charset_lite.so 120 | #LoadModule deflate_module modules/mod_deflate.so 121 | #LoadModule xml2enc_module modules/mod_xml2enc.so 122 | #LoadModule proxy_html_module modules/mod_proxy_html.so 123 | #LoadModule brotli_module modules/mod_brotli.so 124 | LoadModule mime_module modules/mod_mime.so 125 | #LoadModule ldap_module modules/mod_ldap.so 126 | LoadModule log_config_module modules/mod_log_config.so 127 | #LoadModule log_debug_module modules/mod_log_debug.so 128 | #LoadModule log_forensic_module modules/mod_log_forensic.so 129 | #LoadModule logio_module modules/mod_logio.so 130 | #LoadModule lua_module modules/mod_lua.so 131 | LoadModule env_module modules/mod_env.so 132 | #LoadModule mime_magic_module modules/mod_mime_magic.so 133 | #LoadModule cern_meta_module modules/mod_cern_meta.so 134 | #LoadModule expires_module modules/mod_expires.so 135 | LoadModule headers_module modules/mod_headers.so 136 | #LoadModule ident_module modules/mod_ident.so 137 | #LoadModule usertrack_module modules/mod_usertrack.so 138 | #LoadModule unique_id_module modules/mod_unique_id.so 139 | LoadModule setenvif_module modules/mod_setenvif.so 140 | LoadModule version_module modules/mod_version.so 141 | #LoadModule remoteip_module modules/mod_remoteip.so 142 | #LoadModule proxy_module modules/mod_proxy.so 143 | #LoadModule proxy_connect_module modules/mod_proxy_connect.so 144 | #LoadModule proxy_ftp_module modules/mod_proxy_ftp.so 145 | #LoadModule proxy_http_module modules/mod_proxy_http.so 146 | #LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so 147 | #LoadModule proxy_scgi_module modules/mod_proxy_scgi.so 148 | #LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so 149 | #LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so 150 | #LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so 151 | #LoadModule proxy_ajp_module modules/mod_proxy_ajp.so 152 | #LoadModule proxy_balancer_module modules/mod_proxy_balancer.so 153 | #LoadModule proxy_express_module modules/mod_proxy_express.so 154 | #LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so 155 | #LoadModule session_module modules/mod_session.so 156 | #LoadModule session_cookie_module modules/mod_session_cookie.so 157 | #LoadModule session_crypto_module modules/mod_session_crypto.so 158 | #LoadModule session_dbd_module modules/mod_session_dbd.so 159 | #LoadModule slotmem_shm_module modules/mod_slotmem_shm.so 160 | #LoadModule slotmem_plain_module modules/mod_slotmem_plain.so 161 | LoadModule ssl_module modules/mod_ssl.so 162 | #LoadModule optional_hook_export_module modules/mod_optional_hook_export.so 163 | #LoadModule optional_hook_import_module modules/mod_optional_hook_import.so 164 | #LoadModule optional_fn_import_module modules/mod_optional_fn_import.so 165 | #LoadModule optional_fn_export_module modules/mod_optional_fn_export.so 166 | #LoadModule dialup_module modules/mod_dialup.so 167 | #LoadModule http2_module modules/mod_http2.so 168 | #LoadModule proxy_http2_module modules/mod_proxy_http2.so 169 | #LoadModule md_module modules/mod_md.so 170 | #LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so 171 | #LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so 172 | #LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so 173 | #LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so 174 | LoadModule unixd_module modules/mod_unixd.so 175 | #LoadModule heartbeat_module modules/mod_heartbeat.so 176 | #LoadModule heartmonitor_module modules/mod_heartmonitor.so 177 | #LoadModule dav_module modules/mod_dav.so 178 | LoadModule status_module modules/mod_status.so 179 | LoadModule autoindex_module modules/mod_autoindex.so 180 | #LoadModule asis_module modules/mod_asis.so 181 | #LoadModule info_module modules/mod_info.so 182 | #LoadModule suexec_module modules/mod_suexec.so 183 | 184 | #LoadModule cgid_module modules/mod_cgid.so 185 | 186 | 187 | #LoadModule cgi_module modules/mod_cgi.so 188 | 189 | #LoadModule dav_fs_module modules/mod_dav_fs.so 190 | #LoadModule dav_lock_module modules/mod_dav_lock.so 191 | #LoadModule vhost_alias_module modules/mod_vhost_alias.so 192 | #LoadModule negotiation_module modules/mod_negotiation.so 193 | LoadModule dir_module modules/mod_dir.so 194 | #LoadModule imagemap_module modules/mod_imagemap.so 195 | #LoadModule actions_module modules/mod_actions.so 196 | #LoadModule speling_module modules/mod_speling.so 197 | #LoadModule userdir_module modules/mod_userdir.so 198 | LoadModule alias_module modules/mod_alias.so 199 | #LoadModule rewrite_module modules/mod_rewrite.so 200 | 201 | 202 | # 203 | # If you wish httpd to run as a different user or group, you must run 204 | # httpd as root initially and it will switch. 205 | # 206 | # User/Group: The name (or #number) of the user/group to run httpd as. 207 | # It is usually good practice to create a dedicated user and group for 208 | # running httpd, as with most system services. 209 | # 210 | User daemon 211 | Group daemon 212 | 213 | 214 | 215 | # 'Main' server configuration 216 | # 217 | # The directives in this section set up the values used by the 'main' 218 | # server, which responds to any requests that aren't handled by a 219 | # definition. These values also provide defaults for 220 | # any containers you may define later in the file. 221 | # 222 | # All of these directives may appear inside containers, 223 | # in which case these default settings will be overridden for the 224 | # virtual host being defined. 225 | # 226 | 227 | # 228 | # ServerAdmin: Your address, where problems with the server should be 229 | # e-mailed. This address appears on some server-generated pages, such 230 | # as error documents. e.g. admin@your-domain.com 231 | # 232 | ServerAdmin you@example.com 233 | 234 | # 235 | # ServerName gives the name and port that the server uses to identify itself. 236 | # This can often be determined automatically, but we recommend you specify 237 | # it explicitly to prevent problems during startup. 238 | # 239 | # If your host doesn't have a registered DNS name, enter its IP address here. 240 | # 241 | #ServerName www.example.com:80 242 | 243 | # 244 | # Deny access to the entirety of your server's filesystem. You must 245 | # explicitly permit access to web content directories in other 246 | # blocks below. 247 | # 248 | 249 | AllowOverride none 250 | Require all denied 251 | 252 | 253 | # 254 | # Note that from this point forward you must specifically allow 255 | # particular features to be enabled - so if something's not working as 256 | # you might expect, make sure that you have specifically enabled it 257 | # below. 258 | # 259 | 260 | # 261 | # DocumentRoot: The directory out of which you will serve your 262 | # documents. By default, all requests are taken from this directory, but 263 | # symbolic links and aliases may be used to point to other locations. 264 | # 265 | DocumentRoot "/app/public" 266 | 267 | # 268 | # Possible values for the Options directive are "None", "All", 269 | # or any combination of: 270 | # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews 271 | # 272 | # Note that "MultiViews" must be named *explicitly* --- "Options All" 273 | # doesn't give it to you. 274 | # 275 | # The Options directive is both complicated and important. Please see 276 | # http://httpd.apache.org/docs/2.4/mod/core.html#options 277 | # for more information. 278 | # 279 | Options Indexes FollowSymLinks 280 | 281 | # 282 | # AllowOverride controls what directives may be placed in .htaccess files. 283 | # It can be "All", "None", or any combination of the keywords: 284 | # AllowOverride FileInfo AuthConfig Limit 285 | # 286 | AllowOverride None 287 | 288 | # 289 | # Controls who can get stuff from this server. 290 | # 291 | Require all granted 292 | 293 | 294 | # 295 | # DirectoryIndex: sets the file that Apache will serve if a directory 296 | # is requested. 297 | # 298 | 299 | DirectoryIndex index.php 300 | 301 | 302 | # 303 | # The following lines prevent .htaccess and .htpasswd files from being 304 | # viewed by Web clients. 305 | # 306 | 307 | Require all denied 308 | 309 | 310 | # 311 | # ErrorLog: The location of the error log file. 312 | # If you do not specify an ErrorLog directive within a 313 | # container, error messages relating to that virtual host will be 314 | # logged here. If you *do* define an error logfile for a 315 | # container, that host's errors will be logged there and not here. 316 | # 317 | ErrorLog /proc/self/fd/2 318 | 319 | # 320 | # LogLevel: Control the number of messages logged to the error_log. 321 | # Possible values include: debug, info, notice, warn, error, crit, 322 | # alert, emerg. 323 | # 324 | LogLevel warn 325 | 326 | 327 | # 328 | # The following directives define some format nicknames for use with 329 | # a CustomLog directive (see below). 330 | # 331 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 332 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 333 | 334 | 335 | # You need to enable mod_logio.c to use %I and %O 336 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio 337 | 338 | 339 | # 340 | # The location and format of the access logfile (Common Logfile Format). 341 | # If you do not define any access logfiles within a 342 | # container, they will be logged here. Contrariwise, if you *do* 343 | # define per- access logfiles, transactions will be 344 | # logged therein and *not* in this file. 345 | # 346 | CustomLog /proc/self/fd/1 common 347 | 348 | # 349 | # If you prefer a logfile with access, agent, and referer information 350 | # (Combined Logfile Format) you can use the following directive. 351 | # 352 | #CustomLog "logs/access_log" combined 353 | 354 | 355 | 356 | # 357 | # Redirect: Allows you to tell clients about documents that used to 358 | # exist in your server's namespace, but do not anymore. The client 359 | # will make a new request for the document at its new location. 360 | # Example: 361 | # Redirect permanent /foo http://www.example.com/bar 362 | 363 | # 364 | # Alias: Maps web paths into filesystem paths and is used to 365 | # access content that does not live under the DocumentRoot. 366 | # Example: 367 | # Alias /webpath /full/filesystem/path 368 | # 369 | # If you include a trailing / on /webpath then the server will 370 | # require it to be present in the URL. You will also likely 371 | # need to provide a section to allow access to 372 | # the filesystem path. 373 | 374 | # 375 | # ScriptAlias: This controls which directories contain server scripts. 376 | # ScriptAliases are essentially the same as Aliases, except that 377 | # documents in the target directory are treated as applications and 378 | # run by the server when requested rather than as documents sent to the 379 | # client. The same rules about trailing "/" apply to ScriptAlias 380 | # directives as to Alias. 381 | # 382 | ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/" 383 | 384 | 385 | 386 | 387 | # 388 | # ScriptSock: On threaded servers, designate the path to the UNIX 389 | # socket used to communicate with the CGI daemon of mod_cgid. 390 | # 391 | #Scriptsock cgisock 392 | 393 | 394 | # 395 | # "/usr/local/apache2/cgi-bin" should be changed to whatever your ScriptAliased 396 | # CGI directory exists, if you have that configured. 397 | # 398 | 399 | AllowOverride None 400 | Options None 401 | Require all granted 402 | 403 | 404 | 405 | # 406 | # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied 407 | # backend servers which have lingering "httpoxy" defects. 408 | # 'Proxy' request header is undefined by the IETF, not listed by IANA 409 | # 410 | RequestHeader unset Proxy early 411 | 412 | 413 | 414 | # 415 | # TypesConfig points to the file containing the list of mappings from 416 | # filename extension to MIME-type. 417 | # 418 | TypesConfig conf/mime.types 419 | 420 | # 421 | # AddType allows you to add to or override the MIME configuration 422 | # file specified in TypesConfig for specific file types. 423 | # 424 | #AddType application/x-gzip .tgz 425 | # 426 | # AddEncoding allows you to have certain browsers uncompress 427 | # information on the fly. Note: Not all browsers support this. 428 | # 429 | #AddEncoding x-compress .Z 430 | #AddEncoding x-gzip .gz .tgz 431 | # 432 | # If the AddEncoding directives above are commented-out, then you 433 | # probably should define those extensions to indicate media types: 434 | # 435 | AddType application/x-compress .Z 436 | AddType application/x-gzip .gz .tgz 437 | 438 | # 439 | # AddHandler allows you to map certain file extensions to "handlers": 440 | # actions unrelated to filetype. These can be either built into the server 441 | # or added with the Action directive (see below) 442 | # 443 | # To use CGI scripts outside of ScriptAliased directories: 444 | # (You will also need to add "ExecCGI" to the "Options" directive.) 445 | # 446 | #AddHandler cgi-script .cgi 447 | 448 | # For type maps (negotiated resources): 449 | #AddHandler type-map var 450 | 451 | # 452 | # Filters allow you to process content before it is sent to the client. 453 | # 454 | # To parse .shtml files for server-side includes (SSI): 455 | # (You will also need to add "Includes" to the "Options" directive.) 456 | # 457 | #AddType text/html .shtml 458 | #AddOutputFilter INCLUDES .shtml 459 | 460 | 461 | # 462 | # The mod_mime_magic module allows the server to use various hints from the 463 | # contents of the file itself to determine its type. The MIMEMagicFile 464 | # directive tells the module where the hint definitions are located. 465 | # 466 | #MIMEMagicFile conf/magic 467 | 468 | # 469 | # Customizable error responses come in three flavors: 470 | # 1) plain text 2) local redirects 3) external redirects 471 | # 472 | # Some examples: 473 | #ErrorDocument 500 "The server made a boo boo." 474 | #ErrorDocument 404 /missing.html 475 | #ErrorDocument 404 "/cgi-bin/missing_handler.pl" 476 | #ErrorDocument 402 http://www.example.com/subscription_info.html 477 | # 478 | 479 | # 480 | # MaxRanges: Maximum number of Ranges in a request before 481 | # returning the entire resource, or one of the special 482 | # values 'default', 'none' or 'unlimited'. 483 | # Default setting is to accept 200 Ranges. 484 | #MaxRanges unlimited 485 | 486 | # 487 | # EnableMMAP and EnableSendfile: On systems that support it, 488 | # memory-mapping or the sendfile syscall may be used to deliver 489 | # files. This usually improves server performance, but must 490 | # be turned off when serving from networked-mounted 491 | # filesystems or if support for these functions is otherwise 492 | # broken on your system. 493 | # Defaults: EnableMMAP On, EnableSendfile Off 494 | # 495 | #EnableMMAP off 496 | #EnableSendfile on 497 | 498 | # Supplemental configuration 499 | # 500 | # The configuration files in the conf/extra/ directory can be 501 | # included to add extra features or to modify the default configuration of 502 | # the server, or you may simply copy their contents here and change as 503 | # necessary. 504 | 505 | # Server-pool management (MPM specific) 506 | #Include conf/extra/httpd-mpm.conf 507 | 508 | # Multi-language error messages 509 | #Include conf/extra/httpd-multilang-errordoc.conf 510 | 511 | # Fancy directory listings 512 | #Include conf/extra/httpd-autoindex.conf 513 | 514 | # Language settings 515 | #Include conf/extra/httpd-languages.conf 516 | 517 | # User home directories 518 | #Include conf/extra/httpd-userdir.conf 519 | 520 | # Real-time info on requests and configuration 521 | #Include conf/extra/httpd-info.conf 522 | 523 | # Virtual hosts 524 | #Include conf/extra/httpd-vhosts.conf 525 | 526 | # Local access to the Apache HTTP Server Manual 527 | #Include conf/extra/httpd-manual.conf 528 | 529 | # Distributed authoring and versioning (WebDAV) 530 | #Include conf/extra/httpd-dav.conf 531 | 532 | # Various default settings 533 | #Include conf/extra/httpd-default.conf 534 | 535 | # Configure mod_proxy_html to understand HTML4/XHTML1 536 | 537 | Include conf/extra/proxy-html.conf 538 | 539 | 540 | # Secure (SSL/TLS) connections 541 | Include conf/extra/httpd-ssl.conf 542 | # 543 | # Note: The following must must be present to support 544 | # starting without SSL on platforms with no /dev/random equivalent 545 | # but a statically compiled-in mod_ssl. 546 | # 547 | 548 | SSLRandomSeed startup builtin 549 | SSLRandomSeed connect builtin 550 | 551 | 552 | -------------------------------------------------------------------------------- /src/Images/stubs/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | root /app/public; 5 | 6 | add_header X-Frame-Options "SAMEORIGIN"; 7 | add_header X-XSS-Protection "1; mode=block"; 8 | add_header X-Content-Type-Options "nosniff"; 9 | 10 | index index.php; 11 | 12 | charset utf-8; 13 | 14 | location / { 15 | try_files $uri $uri/ /index.php?$query_string; 16 | } 17 | 18 | location = /favicon.ico { access_log off; log_not_found off; } 19 | location = /robots.txt { access_log off; log_not_found off; } 20 | 21 | error_page 404 /index.php; 22 | 23 | location ~ \.php$ { 24 | #fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; 25 | fastcgi_param HTTP_PROXY ""; 26 | fastcgi_index index.php; 27 | fastcgi_pass served_php:9000; 28 | #fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 29 | include fastcgi_params; 30 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 31 | fastcgi_buffers 16 16k; 32 | fastcgi_buffer_size 32k; 33 | } 34 | 35 | location ~ /\.(?!well-known).* { 36 | deny all; 37 | } 38 | } 39 | 40 | server { 41 | listen 443 ssl; 42 | server_name localhost; 43 | root /app/public; 44 | 45 | add_header X-Frame-Options "SAMEORIGIN"; 46 | add_header X-XSS-Protection "1; mode=block"; 47 | add_header X-Content-Type-Options "nosniff"; 48 | 49 | ssl_certificate /etc/nginx/ssl/server.crt; 50 | ssl_certificate_key /etc/nginx/ssl/server.key; 51 | 52 | index index.php; 53 | 54 | charset utf-8; 55 | 56 | location / { 57 | try_files $uri $uri/ /index.php?$query_string; 58 | } 59 | 60 | location = /favicon.ico { access_log off; log_not_found off; } 61 | location = /robots.txt { access_log off; log_not_found off; } 62 | 63 | error_page 404 /index.php; 64 | 65 | location ~ \.php$ { 66 | #fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; 67 | fastcgi_param HTTP_PROXY ""; 68 | fastcgi_index index.php; 69 | fastcgi_pass served_php:9000; 70 | #fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 71 | include fastcgi_params; 72 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 73 | fastcgi_buffers 16 16k; 74 | fastcgi_buffer_size 32k; 75 | } 76 | 77 | location ~ /\.(?!well-known).* { 78 | deny all; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ServedName.php: -------------------------------------------------------------------------------- 1 | testAllowedCharacters($configName)) { 17 | throw new InvalidNamingException($configName . ' is not a valid name!'); 18 | } 19 | 20 | return $configName; 21 | } 22 | 23 | return Str::slug($this->getProjectFolderName(), '_'); 24 | } 25 | 26 | protected function testAllowedCharacters($name) 27 | { 28 | preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]+/', $name, $matches); 29 | 30 | return $matches && $matches[0] === $name; 31 | } 32 | 33 | public function getProjectFolderName(): string 34 | { 35 | return basename(base_path()); 36 | } 37 | } -------------------------------------------------------------------------------- /src/ServedServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom( 25 | __DIR__ . '/config/served.php', 'served' 26 | ); 27 | } 28 | 29 | /** 30 | * Bootstrap services. 31 | * 32 | * @return void 33 | */ 34 | public function boot() 35 | { 36 | $this->publishes([ 37 | __DIR__ . '/config/served.php' => config_path('served.php') 38 | ], 'served-config'); 39 | 40 | if ($this->app->runningInConsole()) { 41 | $this->commands([ 42 | ServedUpCommand::class, 43 | ServedRunCommand::class, 44 | ServedStartCommand::class, 45 | ServedStopCommand::class, 46 | ServedTearDownCommand::class, 47 | ServedListCommand::class, 48 | ServedSshCommand::class, 49 | ServedXdebugCommand::class, 50 | ]); 51 | } 52 | 53 | $this->app->singleton('served.name', function () { 54 | return (new ServedName())->projectName(); 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ServiceManager.php: -------------------------------------------------------------------------------- 1 | php(); 37 | 38 | $web = $this->web(); 39 | 40 | $baseServices = collect([$php, $web]); 41 | 42 | foreach (config('served.extras', []) as $name => $config) { 43 | $extras[] = $this->resolve($name, Arr::get($config, 'service'), $config); 44 | } 45 | 46 | return $baseServices->merge($extras); 47 | } 48 | 49 | /** 50 | * ServiceManager constructor. 51 | * @param Shell $shell 52 | */ 53 | public function __construct(Shell $shell) 54 | { 55 | $this->shell = $shell; 56 | } 57 | 58 | /** 59 | * @param string $service 60 | * @param $callback 61 | */ 62 | public function extend(string $service, $callback) 63 | { 64 | $this->extendables[$service] = $callback; 65 | } 66 | 67 | /** 68 | * @return mixed|ApacheService|MysqlService|NginxService|PhpService 69 | * @throws Exception 70 | */ 71 | public function php() 72 | { 73 | $config = config('served.php'); 74 | return $this->resolve('php', Arr::get($config, 'service', 'php'), $config); 75 | } 76 | 77 | /** 78 | * @return mixed|ApacheService|MysqlService|NginxService|PhpService 79 | * @throws Exception 80 | */ 81 | public function web() 82 | { 83 | $config = config('served.web'); 84 | return $this->resolve('web', Arr::get($config, 'service', 'nginx'), $config); 85 | } 86 | 87 | /** 88 | * @param string $name 89 | * @param string $service 90 | * @param array $config 91 | * @return mixed|ApacheService|MysqlService|NginxService|PhpService 92 | * @throws Exception 93 | */ 94 | public function resolve(string $name, string $service, array $config) 95 | { 96 | if (isset($this->extendables[$name])) { 97 | return call_user_func($this->extendables, $name, $config, $this->shell); 98 | } 99 | 100 | switch ($service) { 101 | case 'php': 102 | return new PhpService($name, $config, $this->shell); 103 | 104 | case 'apache': 105 | case 'apache2': 106 | return new ApacheService($name, $config, $this->shell); 107 | 108 | case 'nginx': 109 | return new NginxService($name, $config, $this->shell); 110 | 111 | case 'mysql': 112 | return new MysqlService($name, $config, $this->shell); 113 | 114 | case 'postgres': 115 | case 'pgsql': 116 | return new PostgresService($name, $config, $this->shell); 117 | 118 | case 'redis': 119 | return new RedisService($name, $config, $this->shell); 120 | 121 | case 'memcached': 122 | return new MemcachedService($name, $config, $this->shell); 123 | 124 | case 'mailhog': 125 | return new MailhogService($name, $config, $this->shell); 126 | 127 | case null: 128 | throw new Exception('No service specified for ' . $name); 129 | 130 | default: 131 | throw new Exception('Service ', $service . ' wasn\'t found for ' . $name); 132 | 133 | } 134 | } 135 | 136 | /** 137 | * @param string $name 138 | * @return mixed|ApacheService|MysqlService|NginxService|PhpService 139 | * @throws Exception 140 | */ 141 | public function resolveByName(string $name) 142 | { 143 | if ($name === 'php') { 144 | return $this->php(); 145 | } 146 | 147 | if ($name === 'web') { 148 | return $this->web(); 149 | } 150 | if ($config = config('served.extras.' . $name)) { 151 | return $this->resolve($name, Arr::get($config, 'service'), $config); 152 | } 153 | 154 | throw new Exception('Could not resolve ' . $name); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Services/ApacheService.php: -------------------------------------------------------------------------------- 1 | name, $this->config, $this->shell); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function image(): Image 29 | { 30 | return new ApacheImage($this->name, $this->config, $this->shell); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Services/MailhogService.php: -------------------------------------------------------------------------------- 1 | name, $this->config, $this->shell); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function image(): Image 29 | { 30 | return new MailhogImage($this->name, $this->config, $this->shell); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Services/MemcachedService.php: -------------------------------------------------------------------------------- 1 | name, $this->config, $this->shell); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function image(): Image 29 | { 30 | return new MemcachedImage($this->name, $this->config, $this->shell); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Services/MysqlService.php: -------------------------------------------------------------------------------- 1 | name, $this->config, $this->shell); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function image(): Image 29 | { 30 | return new MysqlImage($this->name, $this->config, $this->shell); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Services/NginxService.php: -------------------------------------------------------------------------------- 1 | name, $this->config, $this->shell); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function image(): Image 29 | { 30 | return new NginxImage($this->name, $this->config, $this->shell); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Services/PhpService.php: -------------------------------------------------------------------------------- 1 | name, $this->config, $this->shell); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function image(): Image 29 | { 30 | return new PhpImage($this->name, $this->config, $this->shell); 31 | } 32 | 33 | public function enableXdebug(): void 34 | { 35 | $this->container()->enableXdebug(); 36 | } 37 | 38 | public function disableXdebug(): void 39 | { 40 | $this->container()->disableXdebug(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Services/PostgresService.php: -------------------------------------------------------------------------------- 1 | name, $this->config, $this->shell); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function image(): Image 29 | { 30 | return new PostgresImage($this->name, $this->config, $this->shell); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Services/RedisService.php: -------------------------------------------------------------------------------- 1 | name, $this->config, $this->shell); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function image(): Image 29 | { 30 | return new RedisImage($this->name, $this->config, $this->shell); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Services/Service.php: -------------------------------------------------------------------------------- 1 | name = $name; 43 | $this->config = $config; 44 | $this->shell = $shell; 45 | } 46 | 47 | /** 48 | * @param bool $noCache 49 | * 50 | * @return void 51 | */ 52 | public function build($noCache = false): void 53 | { 54 | $this->image()->prepareBuild()->build($noCache); 55 | } 56 | 57 | /** 58 | * @return void 59 | */ 60 | public function run(): void 61 | { 62 | $this->container()->prepare()->run(); 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | public function name(): string 69 | { 70 | return $this->name; 71 | } 72 | 73 | /** 74 | * @return string 75 | */ 76 | public function serviceName(): string 77 | { 78 | if (isset($this->serviceName)) { 79 | return $this->serviceName; 80 | 81 | } 82 | return class_basename($this); 83 | } 84 | 85 | /** 86 | * @return string 87 | */ 88 | public function imageName(): string 89 | { 90 | return sprintf('%s %s', $this->serviceName(), $this->image()->imageTag()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Services/ServiceInterface.php: -------------------------------------------------------------------------------- 1 | internal_getDefaultEnv(); 13 | 14 | return $this->internal_replacePlaceholders($command, $env); 15 | } 16 | 17 | protected function internal_getDefaultEnv() { 18 | return $this->privateMethod($this, 'getDefaultEnv')->invoke($this); 19 | } 20 | 21 | protected function internal_replacePlaceholders($command, $env = []) { 22 | return $this->privateMethod($this, 'replacePlaceholders')->invoke($this, $command, $env); 23 | } 24 | 25 | private function privateMethod($object, $method) { 26 | $object = new \ReflectionObject($object); 27 | $method = $object->getMethod($method); 28 | $method->setAccessible(true); 29 | return $method; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Shell/Shell.php: -------------------------------------------------------------------------------- 1 | output = $output; 23 | } 24 | 25 | /** 26 | * @param string $command 27 | * @param array $env 28 | */ 29 | public function run(string $command, array $env = []): void 30 | { 31 | $process = Process::fromShellCommandline($command); 32 | if ($this->output->isVerbose()) { 33 | $this->writeCommand($process, $command, $env); 34 | } 35 | $process->setTimeout(null); 36 | 37 | $process->run($this->writeOutputBuffer(), $env); 38 | } 39 | 40 | /** 41 | * @param string $command 42 | * @param array $env 43 | * @return string 44 | */ 45 | public function exec(string $command, array $env = []): string 46 | { 47 | $process = Process::fromShellCommandline($command); 48 | $process->setTimeout(null); 49 | if ($this->output->isVerbose()) { 50 | $this->writeCommand($process, $command, $env); 51 | } 52 | $callback = null; 53 | if ($this->output->isVeryVerbose()) { 54 | $callback = $this->writeOutputBuffer(); 55 | } 56 | $process->run($callback, $env); 57 | 58 | if (!$process->isSuccessful()) { 59 | throw new ProcessFailedException($process); 60 | } 61 | 62 | return $process->getOutput(); 63 | } 64 | 65 | protected function writeCommand(Process $process, $command, $env = []) 66 | { 67 | $this->output->writeln($process->createCommand($command, $env)); 68 | } 69 | 70 | protected function writeOutputBuffer() { 71 | return function ($type, $buffer) { 72 | if (Process::ERR === $type) { 73 | throw new ShellCommandFailedException('ERROR : : ' . trim($buffer)); 74 | // $this->output->writeln('ERROR : ' . $buffer); 75 | } else { 76 | $this->output->writeln($buffer); 77 | } 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Traits/Storage.php: -------------------------------------------------------------------------------- 1 | name(); 16 | $storagePath = storage_path($basePath); 17 | 18 | if (!is_dir($storagePath)) { 19 | if (!mkdir($storagePath, 0777, true) && !is_dir($storagePath)) { 20 | throw new RuntimeException(sprintf('Directory "%s" was not created', $storagePath)); 21 | } 22 | } 23 | 24 | if ($relative) { 25 | $storagePath = 'storage/' . $basePath; 26 | } 27 | 28 | return $storagePath . '/'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/config/served.php: -------------------------------------------------------------------------------- 1 | env('SERVED_NAME'), 14 | 15 | /** 16 | * If you are behind a proxy, you might need to 17 | * specify this to enable docker to gain access 18 | * to the internet while building your images 19 | */ 20 | 'proxy' => [ 21 | 'http' => env('SERVED_HTTP_PROXY', ''), 22 | 'https' => env('SERVED_HTTPS_PROXY', ''), 23 | ], 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | PHP Server 28 | |-------------------------------------------------------------------------- 29 | | 30 | | Here you can setup which php version you wish to run. 31 | | If no version is specified, latest will be used 32 | | 33 | | Under the modules array, you may add any php modules you wish to install. 34 | | A complete list of modules can be found at: 35 | | https://github.com/mlocati/docker-php-extension-installer#supported-php-extensions 36 | | 37 | */ 38 | 'php' => [ 39 | 'version' => env('SERVED_PHP_VERSION', '7.4'), 40 | 'modules' => [ 41 | 'pdo_mysql', 42 | 'zip', 43 | 'bcmath' 44 | ], 45 | 'npm' => true, 46 | 'xdebug' => [ 47 | 'enabled' => env('SERVED_XDEBUG_ENABLED', true), 48 | 'port' => 9001, 49 | ], 50 | ], 51 | 'web' => [ 52 | 'service' => 'nginx', //or apache 53 | 'version' => '1.9.2', //apache currently only supports latest! 54 | 'port' => env('SERVED_WEB_PORT', 8095), 55 | 'ssl_port' => env('SERVED_WEB_SSL_PORT', 4443), 56 | ], 57 | 'extras' => [ 58 | 'mysql' => [ 59 | 'service' => 'mysql', 60 | 'version' => '5.7', 61 | 'port' => env('SERVED_EXTERNAL_DB_PORT', 3306), 62 | 'root_password' => 'password', 63 | 'database' => env('DB_DATABASE', 'laravel'), 64 | 'username' => env('DB_USERNAME', 'laravel'), 65 | 'password' => env('DB_PASSWORD', 'password'), 66 | ], 67 | ], 68 | ]; 69 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | mock(OutputInterface::class); 15 | } 16 | 17 | protected function getExpectedContent($filename) 18 | { 19 | return file_get_contents(__DIR__ . '/expected/' . $filename); 20 | } 21 | 22 | protected function getPackageProviders($app) 23 | { 24 | return [ServedServiceProvider::class]; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tests/Unit/DockerImageTest.php: -------------------------------------------------------------------------------- 1 | writeDockerFile(); 28 | 29 | $this->assertEquals($this->getExpectedContent('DockerFile-apache'), $content); 30 | } 31 | 32 | /** @test */ 33 | public function it_can_make_nginx_docker_file() 34 | { 35 | $image = new NginxImage('test', [], app(Shell::class)); 36 | $content = $image->writeDockerFile(); 37 | 38 | $this->assertEquals($this->getExpectedContent('DockerFile-nginx'), $content); 39 | } 40 | 41 | /** @test */ 42 | public function it_can_make_php_docker_file() 43 | { 44 | $image = new PhpImage('test', [], app(Shell::class)); 45 | $content = $image->writeDockerFile(); 46 | 47 | $this->assertEquals($this->getExpectedContent('DockerFile-php'), $content); 48 | } 49 | 50 | /** @test */ 51 | public function it_can_make_mysql_docker_file() 52 | { 53 | $image = new MysqlImage('test', [], app(Shell::class)); 54 | $content = $image->writeDockerFile(); 55 | 56 | $this->assertEquals($this->getExpectedContent('DockerFile-mysql'), $content); 57 | } 58 | 59 | /** @test */ 60 | public function it_can_make_postgres_docker_file() 61 | { 62 | $image = new RedisImage('test', [], app(Shell::class)); 63 | $content = $image->writeDockerFile(); 64 | 65 | $this->assertEquals($this->getExpectedContent('DockerFile-redis'), $content); 66 | } 67 | 68 | /** @test */ 69 | public function it_can_make_redis_docker_file() 70 | { 71 | $image = new RedisImage('test', [], app(Shell::class)); 72 | $content = $image->writeDockerFile(); 73 | 74 | $this->assertEquals($this->getExpectedContent('DockerFile-redis'), $content); 75 | } 76 | 77 | /** @test */ 78 | public function it_can_make_memcached_docker_file() 79 | { 80 | $image = new MemcachedImage('test', [], app(Shell::class)); 81 | $content = $image->writeDockerFile(); 82 | 83 | $this->assertEquals($this->getExpectedContent('DockerFile-memcached'), $content); 84 | } 85 | 86 | /** @test */ 87 | public function it_can_make_mailhog_docker_file() 88 | { 89 | $image = new MailhogImage('test', [], app(Shell::class)); 90 | $content = $image->writeDockerFile(); 91 | 92 | $this->assertEquals($this->getExpectedContent('DockerFile-mailhog'), $content); 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /tests/Unit/DockerRunTest.php: -------------------------------------------------------------------------------- 1 | app->config->set('served.name', 'served'); 22 | $this->app->setBasePath('/path'); 23 | } 24 | 25 | /** @test */ 26 | public function it_gets_correct_apache_run_command() 27 | { 28 | $container = new ApacheContainer('apache_test', [], app(Shell::class)); 29 | 30 | $expected = 'docker run -d --restart always --name "${:container_name}" \ 31 | --network="${:network}" \ 32 | -p="${:port}":80 \ 33 | -p="${:ssl_port}":443 \ 34 | -v="${:local_dir}":/app "${:image_name}"'; 35 | 36 | $this->assertEquals($expected, $container->getDockerRunCommand()); 37 | 38 | } 39 | 40 | /** @test */ 41 | public function it_gets_correct_apache_env() 42 | { 43 | $container = new ApacheContainer('apache_test', ['port' => 8090, 'ssl_port' => 4443], app(Shell::class)); 44 | 45 | $expected = [ 46 | 'network' => 'served', 47 | 'container_name' => 'served_served_apache_test', 48 | 'image_name' => 'served/served_apache_test', 49 | 'port' => 8090, 50 | 'ssl_port' => 4443, 51 | 'local_dir' => '/path', 52 | 53 | ]; 54 | 55 | $this->assertEquals($expected, $container->getEnv()); 56 | 57 | } 58 | 59 | /** @test */ 60 | public function it_gets_correct_nginx_run_command() 61 | { 62 | $container = new NginxContainer('nginx_test', [], app(Shell::class)); 63 | 64 | $expected = 'docker run -d --restart always --name "${:container_name}" \ 65 | --network="${:network}" \ 66 | -p="${:port}":80 \ 67 | -p="${:ssl_port}":443 \ 68 | -v="${:local_dir}":/app "${:image_name}"'; 69 | 70 | $this->assertEquals($expected, $container->getDockerRunCommand()); 71 | 72 | } 73 | 74 | /** @test */ 75 | public function it_gets_correct_nginx_env() 76 | { 77 | $container = new NginxContainer('nginx_test', ['port' => 8090, 'ssl_port' => 443], app(Shell::class)); 78 | 79 | $expected = [ 80 | 'network' => 'served', 81 | 'container_name' => 'served_served_nginx_test', 82 | 'image_name' => 'served/served_nginx_test', 83 | 'port' => 8090, 84 | 'ssl_port' => 443, 85 | 'local_dir' => '/path', 86 | 87 | ]; 88 | 89 | $this->assertEquals($expected, $container->getEnv()); 90 | 91 | } 92 | 93 | /** @test */ 94 | public function it_gets_correct_php_run_command() 95 | { 96 | $container = new PhpContainer('php_test', ['volumes' => ['/some/local/path:/some/docker/path']], app(Shell::class)); 97 | 98 | $expected = 'docker run -d --restart always --name "${:container_name}" \ 99 | --user=served:served \ 100 | --network="${:network}" \ 101 | --network-alias=served_php \ 102 | -v="${:local_dir}":/app -v="/some/local/path:/some/docker/path" "${:image_name}"'; 103 | 104 | $this->assertEquals($expected, $container->getDockerRunCommand()); 105 | 106 | } 107 | 108 | /** @test */ 109 | public function it_gets_correct_php_env() 110 | { 111 | $container = new PhpContainer('php_test', [], app(Shell::class)); 112 | 113 | $expected = [ 114 | 'network' => 'served', 115 | 'container_name' => 'served_served_php_test', 116 | 'image_name' => 'served/served_php_test', 117 | 'local_dir' => '/path', 118 | 119 | ]; 120 | 121 | $this->assertEquals($expected, $container->getEnv()); 122 | 123 | } 124 | 125 | /** @test */ 126 | public function it_gets_correct_mysql_run_command() 127 | { 128 | $container = new MysqlContainer('mysql_test', [], app(Shell::class)); 129 | 130 | $expected = 'docker run -d --restart always --name "${:container_name}" \ 131 | --network="${:network}" \ 132 | --network-alias="${:alias}" \ 133 | -p="${:port}":3306 \ 134 | -v="${:volume}":/var/lib/mysql/ "${:image_name}"'; 135 | 136 | $this->assertEquals($expected, $container->getDockerRunCommand()); 137 | 138 | } 139 | 140 | /** @test */ 141 | public function it_gets_correct_mysql_env() 142 | { 143 | $container = new MysqlContainer('mysql_test', ['port' => 3212], app(Shell::class)); 144 | 145 | $expected = [ 146 | 'network' => 'served', 147 | 'container_name' => 'served_served_mysql_test', 148 | 'image_name' => 'served/served_mysql_test', 149 | 'port' => 3212, 150 | 'alias' => 'mysql_test', 151 | 'volume' => 'served_mysql_test', 152 | 153 | ]; 154 | 155 | $this->assertEquals($expected, $container->getEnv()); 156 | 157 | } 158 | 159 | /** @test */ 160 | public function it_gets_correct_postgres_run_command() 161 | { 162 | $container = new PostgresContainer('postgres_test', [], app(Shell::class)); 163 | 164 | $expected = 'docker run -d --restart always --name "${:container_name}" \ 165 | --network="${:network}" \ 166 | --network-alias="${:alias}" \ 167 | -p="${:port}":5432 \ 168 | -v="${:volume}":/var/lib/postgresql/data "${:image_name}"'; 169 | 170 | $this->assertEquals($expected, $container->getDockerRunCommand()); 171 | 172 | } 173 | 174 | /** @test */ 175 | public function it_gets_correct_postgres_env() 176 | { 177 | $container = new PostgresContainer('postgres_test', ['port' => 3212], app(Shell::class)); 178 | 179 | $expected = [ 180 | 'network' => 'served', 181 | 'container_name' => 'served_served_postgres_test', 182 | 'image_name' => 'served/served_postgres_test', 183 | 'port' => 3212, 184 | 'alias' => 'postgres_test', 185 | 'volume' => 'served_postgres_test', 186 | 187 | ]; 188 | 189 | $this->assertEquals($expected, $container->getEnv()); 190 | 191 | } 192 | 193 | 194 | /** @test */ 195 | public function it_gets_correct_redis_run_command() 196 | { 197 | $container = new RedisContainer('redis_test', [], app(Shell::class)); 198 | 199 | $expected = 'docker run -d --restart always --name "${:container_name}" \ 200 | --network="${:network}" \ 201 | --network-alias="${:alias}" "${:image_name}"'; 202 | 203 | $this->assertEquals($expected, $container->getDockerRunCommand()); 204 | 205 | } 206 | 207 | /** @test */ 208 | public function it_gets_correct_redis_env() 209 | { 210 | $container = new RedisContainer('redis_test', [], app(Shell::class)); 211 | 212 | $expected = [ 213 | 'network' => 'served', 214 | 'container_name' => 'served_served_redis_test', 215 | 'image_name' => 'served/served_redis_test', 216 | 'alias' => 'redis_test', 217 | 218 | ]; 219 | 220 | $this->assertEquals($expected, $container->getEnv()); 221 | 222 | } 223 | 224 | /** @test */ 225 | public function it_gets_correct_memcached_run_command() 226 | { 227 | $container = new MemcachedContainer('memcached_test', [], app(Shell::class)); 228 | 229 | $expected = 'docker run -d --restart always --name "${:container_name}" \ 230 | --network="${:network}" \ 231 | --network-alias="${:alias}" "${:image_name}"'; 232 | 233 | $this->assertEquals($expected, $container->getDockerRunCommand()); 234 | 235 | } 236 | 237 | /** @test */ 238 | public function it_gets_correct_memcached_env() 239 | { 240 | $container = new MemcachedContainer('memcached_test', [], app(Shell::class)); 241 | 242 | $expected = [ 243 | 'network' => 'served', 244 | 'container_name' => 'served_served_memcached_test', 245 | 'image_name' => 'served/served_memcached_test', 246 | 'alias' => 'memcached_test', 247 | 248 | ]; 249 | 250 | $this->assertEquals($expected, $container->getEnv()); 251 | 252 | } 253 | 254 | /** @test */ 255 | public function it_gets_correct_mailhog_run_command() 256 | { 257 | $container = new MailhogContainer('mailhog_test', [], app(Shell::class)); 258 | 259 | $expected = 'docker run -d --restart always --name "${:container_name}" \ 260 | --network="${:network}" \ 261 | --network-alias="${:alias}" \ 262 | -p "${:port}":8025 "${:image_name}"'; 263 | 264 | $this->assertEquals($expected, $container->getDockerRunCommand()); 265 | 266 | } 267 | 268 | /** @test */ 269 | public function it_gets_correct_mailhog_env() 270 | { 271 | $container = new RedisContainer('mailhog_test', [], app(Shell::class)); 272 | 273 | $expected = [ 274 | 'network' => 'served', 275 | 'container_name' => 'served_served_mailhog_test', 276 | 'image_name' => 'served/served_mailhog_test', 277 | 'alias' => 'mailhog_test', 278 | 279 | ]; 280 | 281 | $this->assertEquals($expected, $container->getEnv()); 282 | 283 | } 284 | 285 | } 286 | -------------------------------------------------------------------------------- /tests/Unit/NamingTest.php: -------------------------------------------------------------------------------- 1 | app->config->set('served.name', 'my_project'); 21 | 22 | $this->assertEquals('my_project', $this->app->get('served.name')); 23 | } 24 | 25 | /** @test */ 26 | public function it_fails_on_invalid_name() 27 | { 28 | $this->app->config->set('served.name', 'bad|name'); 29 | 30 | $this->expectException(InvalidNamingException::class); 31 | 32 | $this->app->get('served.name'); 33 | 34 | } 35 | 36 | /** @test */ 37 | public function it_fails_on_invalid_name_with_space() 38 | { 39 | $this->app->config->set('served.name', 'bad name'); 40 | 41 | $this->expectException(InvalidNamingException::class); 42 | 43 | $this->app->get('served.name'); 44 | 45 | } 46 | 47 | /** @test */ 48 | public function it_fails_on_invalid_start_character() 49 | { 50 | $this->app->config->set('served.name', '_badname'); 51 | 52 | $this->expectException(InvalidNamingException::class); 53 | 54 | $this->app->get('served.name'); 55 | 56 | } 57 | 58 | /** @test */ 59 | public function it_fails_on_invalid_random_characters() 60 | { 61 | $this->app->config->set('served.name', '_bad|dsad|_23'); 62 | 63 | $this->expectException(InvalidNamingException::class); 64 | 65 | $this->app->get('served.name'); 66 | 67 | } 68 | 69 | /** @test */ 70 | public function it_can_handle_special_characters() 71 | { 72 | $nameHandler = Mockery::mock(ServedName::class)->makePartial(); 73 | 74 | $nameHandler->shouldReceive('getProjectFolderName') 75 | ->andReturn('test-s.123|test'); 76 | 77 | $this->assertEquals('test_s123test', $nameHandler->projectName()); 78 | } 79 | 80 | /** @test */ 81 | public function it_can_handle_whitespace() 82 | { 83 | $nameHandler = Mockery::mock(ServedName::class)->makePartial(); 84 | 85 | $nameHandler->shouldReceive('getProjectFolderName') 86 | ->andReturn('test test'); 87 | 88 | $this->assertEquals('test_test', $nameHandler->projectName()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /tests/expected/DockerFile-apache: -------------------------------------------------------------------------------- 1 | FROM webdevops/apache:latest 2 | ENV WEB_PHP_SOCKET=served_php:9000 3 | ENV WEB_DOCUMENT_ROOT=/app/public 4 | ENV WEB_PHP_TIMEOUT=60 5 | COPY storage/app/served/test/localhost.key /opt/docker/etc/httpd/ssl/server.key 6 | COPY storage/app/served/test/localhost.crt /opt/docker/etc/httpd/ssl/server.crt -------------------------------------------------------------------------------- /tests/expected/DockerFile-mailhog: -------------------------------------------------------------------------------- 1 | FROM mailhog/mailhog:latest -------------------------------------------------------------------------------- /tests/expected/DockerFile-memcached: -------------------------------------------------------------------------------- 1 | FROM library/memcached:latest -------------------------------------------------------------------------------- /tests/expected/DockerFile-mysql: -------------------------------------------------------------------------------- 1 | FROM library/mysql:5.7 2 | 3 | # Setting env vars for mysql init 4 | ENV MYSQL_ROOT_PASSWORD=password 5 | ENV MYSQL_DATABASE=laravel 6 | ENV MYSQL_USER=laravel 7 | ENV MYSQL_PASSWORD=password -------------------------------------------------------------------------------- /tests/expected/DockerFile-nginx: -------------------------------------------------------------------------------- 1 | FROM library/nginx:1.19 2 | 3 | # Copy in new nginx config 4 | COPY storage/app/served/test/default.conf /etc/nginx/conf.d/default.conf 5 | COPY storage/app/served/test/localhost.key /etc/nginx/ssl/server.key 6 | COPY storage/app/served/test/localhost.crt /etc/nginx/ssl/server.crt -------------------------------------------------------------------------------- /tests/expected/DockerFile-php: -------------------------------------------------------------------------------- 1 | FROM library/php:7.4-fpm 2 | 3 | # disable warnings for "dangerous" messages 4 | ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1 5 | 6 | # Adding linux packages 7 | RUN apt-get update \ 8 | && apt-get install -y unzip zip gnupg \ 9 | && rm -rf /var/lib/apt/lists/* 10 | # Installing packages for sql dump 11 | RUN set -ex; \ 12 | key='8C718D3B5072E1F5'; \ 13 | export GNUPGHOME="$(mktemp -d)"; \ 14 | gpg --batch --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys "$key"; \ 15 | gpg --batch --export "$key" > /etc/apt/trusted.gpg.d/mysql.gpg; \ 16 | gpgconf --kill all; \ 17 | rm -rf "$GNUPGHOME"; \ 18 | apt-key list > /dev/null 19 | 20 | RUN echo "deb http://repo.mysql.com/apt/debian/ buster mysql-8.0" > /etc/apt/sources.list.d/mysql.list \ 21 | && apt-get update \ 22 | && apt-get install -y mysql-community-client postgresql-client sqlite3 \ 23 | && rm -rf /var/lib/apt/lists/* 24 | 25 | # add development php.ini file 26 | RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" 27 | # add a local user with the same uid as the local 28 | # prepare empty composer config directory 29 | # ensure user owns its home directory 30 | ARG uid 31 | RUN useradd -G root -u $uid -d /home/served served \ 32 | && mkdir -p /home/served/.composer \ 33 | && chown -R served:served /home/served 34 | 35 | # add composer 36 | # set composer to use https 37 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ 38 | && runuser -l served -c "composer config --global repos.packagist composer https://packagist.org" 39 | 40 | # Set work dir 41 | WORKDIR /app -------------------------------------------------------------------------------- /tests/expected/DockerFile-postgres: -------------------------------------------------------------------------------- 1 | FROM library/postgres:12.4 2 | 3 | # Setting env vars for postgres init 4 | ENV POSTGRES_DB=laravel 5 | ENV POSTGRES_USER=laravel 6 | ENV POSTGRES_PASSWORD=password -------------------------------------------------------------------------------- /tests/expected/DockerFile-redis: -------------------------------------------------------------------------------- 1 | FROM library/redis:latest 2 | CMD [ "redis-server" ] --------------------------------------------------------------------------------