From 2d5de4580fce710ef610088aec52843f926da991 Mon Sep 17 00:00:00 2001 From: Andrew Schmelyun Date: Sun, 5 Mar 2023 14:33:23 -0500 Subject: [PATCH 1/7] Adds support for local ssl with mkcert commands during add and start --- src/Commands/FleetAddCommand.php | 25 ++++++++++++++++++++++++- src/Commands/FleetStartCommand.php | 10 +++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/Commands/FleetAddCommand.php b/src/Commands/FleetAddCommand.php index ae3d3bf..3bec63f 100644 --- a/src/Commands/FleetAddCommand.php +++ b/src/Commands/FleetAddCommand.php @@ -5,11 +5,14 @@ use Aschmelyun\Fleet\Fleet; use Composer\InstalledVersions; use Illuminate\Console\Command; +use Symfony\Component\Process\Process; use Symfony\Component\Yaml\Yaml; class FleetAddCommand extends Command { - public $signature = 'fleet:add {domain?}'; + public $signature = 'fleet:add + {domain? : The test domain to use} + {--ssl : Include local SSL with mkcert}'; public $description = 'Installs Fleet support onto the current application'; @@ -103,6 +106,26 @@ public function handle(): int $yaml['networks']['fleet']['external'] = true; + // determine if the user wants to use SSL and add support if so + if ($this->option('ssl')) { + $this->info(' 🔒 Adding SSL support...'); + + $homeDirectory = new Process(['sh', '-c', 'echo $HOME']); + $homeDirectory->run(); + $homeDirectory = trim($homeDirectory->getOutput()); + + $process = Fleet::process("mkdir -p {$homeDirectory}/.config/mkcert/certs"); + $process = Fleet::process("mkcert -cert-file {$homeDirectory}/.config/mkcert/certs/{$domain}.crt -key-file {$homeDirectory}/.config/mkcert/certs/{$domain}.key {$domain}"); + if (!$process->isSuccessful()) { + $this->error('mkcert is not installed or configured incorrectly, please install it and try again'); + $this->line('For more information, check out mkcert.dev'); + + return self::FAILURE; + } + + $yaml['services'][$heading]['labels'][] = "traefik.http.routers.{$heading}.tls=true"; + } + file_put_contents(base_path('docker-compose.yml'), Yaml::dump($yaml, 6)); // call fleet:start to determine if the fleet network and traefik container is up diff --git a/src/Commands/FleetStartCommand.php b/src/Commands/FleetStartCommand.php index b8d8b4c..ada35b2 100644 --- a/src/Commands/FleetStartCommand.php +++ b/src/Commands/FleetStartCommand.php @@ -4,6 +4,7 @@ use Aschmelyun\Fleet\Fleet; use Illuminate\Console\Command; +use Symfony\Component\Process\Process; class FleetStartCommand extends Command { @@ -29,13 +30,20 @@ public function handle(): int $this->line($process->getOutput()); } + // just in case the mkcert directory doesn't exist, create it + $process = Fleet::process("mkdir -p ~/.config/mkcert"); + + $homeDirectory = new Process(['sh', '-c', 'echo $HOME']); + $homeDirectory->run(); + $homeDirectory = trim($homeDirectory->getOutput()); + // is the fleet traefik container running? if not, start it up $process = Fleet::process('docker ps --filter name=^fleet$ --format {{.ID}}'); if (!$process->getOutput()) { $this->info('No Fleet container, spinning it up...'); $process = Fleet::process( - 'docker run -d -p 8080:8080 -p 80:80 --network=fleet -v /var/run/docker.sock:/var/run/docker.sock --name=fleet traefik:v2.9 --api.insecure=true --providers.docker', + "docker run -d -p 8080:8080 -p 80:80 -p 443:443 --network=fleet -v /var/run/docker.sock:/var/run/docker.sock -v {$homeDirectory}/.config/mkcert:/etc/traefik --name=fleet traefik:v2.9 --api.insecure=true --providers.docker --entryPoints.web.address=:80 --entryPoints.websecure.address=:443 --providers.file.directory=/etc/traefik/conf --providers.file.watch=true", true ); } From 2fb0a4a935519477d6cc11c5142fbb3a2232b6af Mon Sep 17 00:00:00 2001 From: Andrew Schmelyun Date: Sun, 5 Mar 2023 15:05:50 -0500 Subject: [PATCH 2/7] Adds helper for creating directories, manipulates ssl.yml conf file for local certs --- src/Commands/FleetAddCommand.php | 16 +++++++++++++++- src/Commands/FleetStartCommand.php | 2 +- src/Fleet.php | 6 ++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Commands/FleetAddCommand.php b/src/Commands/FleetAddCommand.php index 3bec63f..f9bc379 100644 --- a/src/Commands/FleetAddCommand.php +++ b/src/Commands/FleetAddCommand.php @@ -114,7 +114,8 @@ public function handle(): int $homeDirectory->run(); $homeDirectory = trim($homeDirectory->getOutput()); - $process = Fleet::process("mkdir -p {$homeDirectory}/.config/mkcert/certs"); + Fleet::makeSslDirectories(); + $process = Fleet::process("mkcert -cert-file {$homeDirectory}/.config/mkcert/certs/{$domain}.crt -key-file {$homeDirectory}/.config/mkcert/certs/{$domain}.key {$domain}"); if (!$process->isSuccessful()) { $this->error('mkcert is not installed or configured incorrectly, please install it and try again'); @@ -123,6 +124,19 @@ public function handle(): int return self::FAILURE; } + $sslFile = "{$homeDirectory}/.config/mkcert/conf/ssl.yml"; + $ssl = []; + if (file_exists($sslFile)) { + $ssl = Yaml::parseFile($sslFile); + } + + $ssl['tls']['certificates'][] = [ + 'certFile' => "/etc/traefik/certs/{$domain}.crt", + 'keyFile' => "/etc/traefik/certs/{$domain}.key", + ]; + + file_put_contents($sslFile, Yaml::dump($ssl, 6)); + $yaml['services'][$heading]['labels'][] = "traefik.http.routers.{$heading}.tls=true"; } diff --git a/src/Commands/FleetStartCommand.php b/src/Commands/FleetStartCommand.php index ada35b2..2add2c5 100644 --- a/src/Commands/FleetStartCommand.php +++ b/src/Commands/FleetStartCommand.php @@ -31,7 +31,7 @@ public function handle(): int } // just in case the mkcert directory doesn't exist, create it - $process = Fleet::process("mkdir -p ~/.config/mkcert"); + Fleet::makeSslDirectories(); $homeDirectory = new Process(['sh', '-c', 'echo $HOME']); $homeDirectory->run(); diff --git a/src/Fleet.php b/src/Fleet.php index ab347ec..e568d2d 100755 --- a/src/Fleet.php +++ b/src/Fleet.php @@ -22,4 +22,10 @@ public static function process(string $command, bool $withBuffer = false): Proce return $process; } + + public static function makeSslDirectories(): void + { + self::process("mkdir -p ~/.config/mkcert/certs"); + self::process("mkdir -p ~/.config/mkcert/conf"); + } } From a582efbdcfbc9871968932fb404ca57098c977f2 Mon Sep 17 00:00:00 2001 From: Andrew Schmelyun Date: Sun, 5 Mar 2023 17:19:55 -0500 Subject: [PATCH 3/7] Refactor add and start commands to offload functionality to helper classes --- src/Commands/FleetAddCommand.php | 110 +++++++++++++---------------- src/Commands/FleetStartCommand.php | 41 ++++++----- src/Fleet.php | 6 -- src/FleetServiceProvider.php | 8 +++ src/Support/Docker.php | 52 ++++++++++++++ src/Support/Filesystem.php | 85 ++++++++++++++++++++++ 6 files changed, 214 insertions(+), 88 deletions(-) create mode 100644 src/Support/Docker.php create mode 100644 src/Support/Filesystem.php diff --git a/src/Commands/FleetAddCommand.php b/src/Commands/FleetAddCommand.php index f9bc379..ad26a39 100644 --- a/src/Commands/FleetAddCommand.php +++ b/src/Commands/FleetAddCommand.php @@ -3,9 +3,9 @@ namespace Aschmelyun\Fleet\Commands; use Aschmelyun\Fleet\Fleet; +use Aschmelyun\Fleet\Support\Filesystem; use Composer\InstalledVersions; use Illuminate\Console\Command; -use Symfony\Component\Process\Process; use Symfony\Component\Yaml\Yaml; class FleetAddCommand extends Command @@ -16,7 +16,7 @@ class FleetAddCommand extends Command public $description = 'Installs Fleet support onto the current application'; - public function handle(): int + public function handle(Filesystem $filesystem): int { // set the domain to the one the user provided, or ask what it should be $domain = $this->argument('domain'); @@ -48,32 +48,19 @@ public function handle(): int } } - // determine what port 8081+ is available + // determine what port 8081+ is available and set it as the APP_PORT $port = 8081; while ($this->isPortTaken($port)) { $port++; } - $file = base_path('.env'); - if (!file_exists($file)) { - $this->error("Application .env file is missing, can't continue"); - + try { + $filesystem->writeToEnvFile('APP_PORT', $port); + } catch (\Exception $e) { + $this->error($e->getMessage()); return self::FAILURE; } - $env = file_get_contents($file); - $env = explode("\n", $env); - - $filteredEnvAppPort = array_filter($env, fn ($line) => str_starts_with($line, 'APP_PORT')); - if (!empty($filteredEnvAppPort)) { - $env[key($filteredEnvAppPort)] = "APP_PORT={$port}"; - } else { - $insert = ["APP_PORT={$port}"]; - array_splice($env, 5, 0, $insert); - } - - file_put_contents(base_path('.env'), implode("\n", $env)); - // add a modified docker-compose.yml file to include traefik labels $file = base_path('docker-compose.backup.yml'); if (!file_exists($file)) { @@ -86,57 +73,28 @@ public function handle(): int return self::FAILURE; } - $yaml = Yaml::parseFile($file); - - $heading = str_replace('.', '-', $domain); - - $service = $yaml['services'][array_keys($yaml['services'])[0]]; - unset($yaml['services'][array_keys($yaml['services'])[0]]); - - $yaml['services'] = [$heading => $service, ...$yaml['services']]; - - $yaml['services'][$heading]['networks'][] = 'fleet'; - $yaml['services'][$heading]['labels'] = [ - "traefik.http.routers.{$heading}.rule=Host(`{$domain}`)", - "traefik.http.services.{$heading}.loadbalancer.server.port=80", - ]; - - unset($yaml['services'][$heading]['ports'][0]); - $yaml['services'][$heading]['ports'] = array_values($yaml['services'][$heading]['ports']); - - $yaml['networks']['fleet']['external'] = true; + $yaml = $this->generateYamlForDockerCompose($file, $domain, $filesystem); // determine if the user wants to use SSL and add support if so if ($this->option('ssl')) { $this->info(' 🔒 Adding SSL support...'); - $homeDirectory = new Process(['sh', '-c', 'echo $HOME']); - $homeDirectory->run(); - $homeDirectory = trim($homeDirectory->getOutput()); - - Fleet::makeSslDirectories(); - - $process = Fleet::process("mkcert -cert-file {$homeDirectory}/.config/mkcert/certs/{$domain}.crt -key-file {$homeDirectory}/.config/mkcert/certs/{$domain}.key {$domain}"); - if (!$process->isSuccessful()) { - $this->error('mkcert is not installed or configured incorrectly, please install it and try again'); - $this->line('For more information, check out mkcert.dev'); - + try { + $filesystem->createCertificates($domain); + } catch (\Exception $e) { + $this->error($e->getMessage()); + $this->line('For more information, try checking out the documentation at mkcert.dev'); return self::FAILURE; } - $sslFile = "{$homeDirectory}/.config/mkcert/conf/ssl.yml"; - $ssl = []; - if (file_exists($sslFile)) { - $ssl = Yaml::parseFile($sslFile); + try { + $filesystem->createSslConfig($domain); + } catch (\Exception $e) { + $this->error($e->getMessage()); + return self::FAILURE; } - $ssl['tls']['certificates'][] = [ - 'certFile' => "/etc/traefik/certs/{$domain}.crt", - 'keyFile' => "/etc/traefik/certs/{$domain}.key", - ]; - - file_put_contents($sslFile, Yaml::dump($ssl, 6)); - + $heading = str_replace('.', '-', $domain); $yaml['services'][$heading]['labels'][] = "traefik.http.routers.{$heading}.tls=true"; } @@ -152,6 +110,36 @@ public function handle(): int return self::SUCCESS; } + private function generateYamlForDockerCompose(string $file, string $domain, Filesystem $filesystem): array + { + $yaml = Yaml::parseFile($file); + + $heading = str_replace('.', '-', $domain); + + // resets the top service key to the domain name + $service = $yaml['services'][array_keys($yaml['services'])[0]]; + unset($yaml['services'][array_keys($yaml['services'])[0]]); + + // adds the entire services array back with the new domain key + $yaml['services'] = [$heading => $service, ...$yaml['services']]; + + // adds the traefik labels to the yaml file + $yaml['services'][$heading]['networks'][] = 'fleet'; + $yaml['services'][$heading]['labels'] = [ + "traefik.http.routers.{$heading}.rule=Host(`{$domain}`)", + "traefik.http.services.{$heading}.loadbalancer.server.port=80", + ]; + + // removes port binding for our app service + unset($yaml['services'][$heading]['ports'][0]); + $yaml['services'][$heading]['ports'] = array_values($yaml['services'][$heading]['ports']); + + // adds the fleet network + $yaml['networks']['fleet']['external'] = true; + + return $yaml; + } + private function isPortTaken($port): bool { $process = Fleet::process("lsof -nP -iTCP:{$port} -sTCP:LISTEN"); diff --git a/src/Commands/FleetStartCommand.php b/src/Commands/FleetStartCommand.php index 2add2c5..070cf89 100644 --- a/src/Commands/FleetStartCommand.php +++ b/src/Commands/FleetStartCommand.php @@ -2,9 +2,9 @@ namespace Aschmelyun\Fleet\Commands; -use Aschmelyun\Fleet\Fleet; +use Aschmelyun\Fleet\Support\Docker; +use Aschmelyun\Fleet\Support\Filesystem; use Illuminate\Console\Command; -use Symfony\Component\Process\Process; class FleetStartCommand extends Command { @@ -12,40 +12,39 @@ class FleetStartCommand extends Command public $description = 'Starts up the Fleet network and Traefik container'; - public function handle(): int + public function handle(Filesystem $filesystem, Docker $docker): int { // is the fleet docker network running? if not, start it up - $process = Fleet::process('docker network ls --filter name=^fleet$ --format {{.ID}}'); - - if (!$process->getOutput()) { + if (!$docker->getNetwork('fleet')) { $this->info('No Fleet network, creating one...'); - $process = Fleet::process('docker network create fleet'); - if (!$process->isSuccessful()) { + try { + $id = $docker->createNetwork('fleet'); + } catch (\Exception $e) { $this->error('Could not start Fleet Docker network'); + $this->line($e->getMessage()); return self::FAILURE; } - $this->line($process->getOutput()); + $this->line($id); } // just in case the mkcert directory doesn't exist, create it - Fleet::makeSslDirectories(); - - $homeDirectory = new Process(['sh', '-c', 'echo $HOME']); - $homeDirectory->run(); - $homeDirectory = trim($homeDirectory->getOutput()); + $filesystem->createSslDirectories(); // is the fleet traefik container running? if not, start it up - $process = Fleet::process('docker ps --filter name=^fleet$ --format {{.ID}}'); - - if (!$process->getOutput()) { + if (!$docker->getContainer('fleet')) { $this->info('No Fleet container, spinning it up...'); - $process = Fleet::process( - "docker run -d -p 8080:8080 -p 80:80 -p 443:443 --network=fleet -v /var/run/docker.sock:/var/run/docker.sock -v {$homeDirectory}/.config/mkcert:/etc/traefik --name=fleet traefik:v2.9 --api.insecure=true --providers.docker --entryPoints.web.address=:80 --entryPoints.websecure.address=:443 --providers.file.directory=/etc/traefik/conf --providers.file.watch=true", - true - ); + + try { + $docker->startFleetTraefikContainer(); + } catch (\Exception $e) { + $this->error('Could not start Fleet Traefik container'); + $this->line($e->getMessage()); + + return self::FAILURE; + } } return self::SUCCESS; diff --git a/src/Fleet.php b/src/Fleet.php index e568d2d..ab347ec 100755 --- a/src/Fleet.php +++ b/src/Fleet.php @@ -22,10 +22,4 @@ public static function process(string $command, bool $withBuffer = false): Proce return $process; } - - public static function makeSslDirectories(): void - { - self::process("mkdir -p ~/.config/mkcert/certs"); - self::process("mkdir -p ~/.config/mkcert/conf"); - } } diff --git a/src/FleetServiceProvider.php b/src/FleetServiceProvider.php index 970ddc3..8347d24 100644 --- a/src/FleetServiceProvider.php +++ b/src/FleetServiceProvider.php @@ -6,11 +6,19 @@ use Aschmelyun\Fleet\Commands\FleetRemoveCommand; use Aschmelyun\Fleet\Commands\FleetStartCommand; use Aschmelyun\Fleet\Commands\FleetStopCommand; +use Aschmelyun\Fleet\Support\Docker; +use Aschmelyun\Fleet\Support\Filesystem; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; class FleetServiceProvider extends PackageServiceProvider { + public function registeringPackage() + { + $this->app->singleton(Docker::class); + $this->app->singleton(Filesystem::class); + } + public function configurePackage(Package $package): void { $package diff --git a/src/Support/Docker.php b/src/Support/Docker.php new file mode 100644 index 0000000..6f15dcf --- /dev/null +++ b/src/Support/Docker.php @@ -0,0 +1,52 @@ +isSuccessful()) { + return trim($process->getOutput()); + } + + return null; + } + + public function createNetwork(string $name): string + { + $process = Fleet::process("docker network create {$name}"); + + if ($process->isSuccessful()) { + return trim($process->getOutput()); + } + + throw new \Exception('Could not create Docker network'); + } + + public function getContainer(string $name): ?string + { + $process = Fleet::process("docker ps --filter name=^{$name}$ --format '{{.ID}}'"); + + if ($process->isSuccessful()) { + return trim($process->getOutput()); + } + + return null; + } + + public function startFleetTraefikContainer(): void + { + $homeDirectory = app(Filesystem::class)->getHomeDirectory(); + + Fleet::process( + "docker run -d -p 8080:8080 -p 80:80 -p 443:443 --network=fleet -v /var/run/docker.sock:/var/run/docker.sock -v {$homeDirectory}/.config/mkcert:/etc/traefik --name=fleet traefik:v2.9 --api.insecure=true --providers.docker --entryPoints.web.address=:80 --entryPoints.websecure.address=:443 --providers.file.directory=/etc/traefik/conf --providers.file.watch=true", + true + ); + } +} \ No newline at end of file diff --git a/src/Support/Filesystem.php b/src/Support/Filesystem.php new file mode 100644 index 0000000..6b6346a --- /dev/null +++ b/src/Support/Filesystem.php @@ -0,0 +1,85 @@ +makeDirectory($this->getHomeDirectory() . '/.config/mkcert/certs'); + $this->makeDirectory($this->getHomeDirectory() . '/.config/mkcert/conf'); + } + + public function getHomeDirectory(): string + { + $homeDirectory = new Process(['sh', '-c', 'echo $HOME']); + $homeDirectory->run(); + + return trim($homeDirectory->getOutput()); + } + + public function writeToEnvFile(string $key, string $value): void + { + $file = base_path('.env'); + if (!file_exists($file)) { + throw new \Exception('Application .env file is missing, can\'t continue'); + } + + $env = file_get_contents($file); + $env = explode("\n", $env); + + $filteredEnvAppPort = array_filter($env, fn ($line) => str_starts_with($line, $key)); + if (!empty($filteredEnvAppPort)) { + $env[key($filteredEnvAppPort)] = "{$key}={$value}"; + } else { + $insert = ["{$key}={$value}"]; + array_splice($env, 5, 0, $insert); + } + + file_put_contents(base_path('.env'), implode("\n", $env)); + } + + public function createCertificates(string $domain): void + { + $this->createSslDirectories(); + + $process = Fleet::process("mkcert -install"); + if (!$process->isSuccessful()) { + throw new \Exception('mkcert is not installed or configured incorrectly, please install it and try again'); + } + + $process = Fleet::process( + "mkcert -cert-file {$this->getHomeDirectory()}/.config/mkcert/certs/{$domain}.crt -key-file {$this->getHomeDirectory()}/.config/mkcert/certs/{$domain}.key {$domain}" + ); + if (!$process->isSuccessful()) { + throw new \Exception('mkcert is not installed or configured incorrectly, please install it and try again'); + } + } + + public function createSslConfig(string $domain): void + { + $sslConfigFile = "{$this->getHomeDirectory()}/.config/mkcert/conf/ssl.yml"; + $ssl = []; + if (file_exists($sslConfigFile)) { + $ssl = Yaml::parseFile($sslConfigFile); + } + + $ssl['tls']['certificates'][] = [ + 'certFile' => "/etc/traefik/certs/{$domain}.crt", + 'keyFile' => "/etc/traefik/certs/{$domain}.key", + ]; + + file_put_contents($sslConfigFile, Yaml::dump($ssl, 6)); + } +} \ No newline at end of file From 3079dd44a88ee8b03faccc954c088c45b6c07509 Mon Sep 17 00:00:00 2001 From: Andrew Schmelyun Date: Sun, 5 Mar 2023 17:21:06 -0500 Subject: [PATCH 4/7] PHPStan and Pint --- phpstan.neon.dist | 1 - src/Commands/FleetAddCommand.php | 13 ++++++++----- src/Commands/FleetRemoveCommand.php | 4 ++-- src/Commands/FleetStartCommand.php | 4 ++-- src/Commands/FleetStopCommand.php | 4 ++-- src/Support/Docker.php | 5 ++--- src/Support/Filesystem.php | 20 ++++++++++---------- 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a91953b..e005ac7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -6,7 +6,6 @@ parameters: paths: - src - config - - database tmpDir: build/phpstan checkOctaneCompatibility: true checkModelProperties: true diff --git a/src/Commands/FleetAddCommand.php b/src/Commands/FleetAddCommand.php index ad26a39..861d76f 100644 --- a/src/Commands/FleetAddCommand.php +++ b/src/Commands/FleetAddCommand.php @@ -20,7 +20,7 @@ public function handle(Filesystem $filesystem): int { // set the domain to the one the user provided, or ask what it should be $domain = $this->argument('domain'); - if (!$domain) { + if (! $domain) { $domain = $this->ask('What domain name would you like to use for this app?', 'laravel.localhost'); } @@ -30,13 +30,13 @@ public function handle(Filesystem $filesystem): int } // determine if laravel/sail is a required-dev package in the root composer file - if (!InstalledVersions::isInstalled('laravel/sail')) { + if (! InstalledVersions::isInstalled('laravel/sail')) { $this->error(' Laravel Sail is required for this package'); $this->line(' For more information, check out https://laravel.com/docs/sail#installation'); } // if the docker-compose.yml file isn't available, publish it - if (!file_exists(base_path('docker-compose.yml')) && !file_exists(base_path('docker-compose.backup.yml'))) { + if (! file_exists(base_path('docker-compose.yml')) && ! file_exists(base_path('docker-compose.backup.yml'))) { $this->info('No docker-compose.yml file available, running sail:install...'); $this->call('sail:install'); } @@ -58,16 +58,17 @@ public function handle(Filesystem $filesystem): int $filesystem->writeToEnvFile('APP_PORT', $port); } catch (\Exception $e) { $this->error($e->getMessage()); + return self::FAILURE; } // add a modified docker-compose.yml file to include traefik labels $file = base_path('docker-compose.backup.yml'); - if (!file_exists($file)) { + if (! file_exists($file)) { $file = base_path('docker-compose.yml'); } - if (!file_exists($file)) { + if (! file_exists($file)) { $this->error('A docker-compose.yml file or a docker-compose.backup.yml file does not exist'); return self::FAILURE; @@ -84,6 +85,7 @@ public function handle(Filesystem $filesystem): int } catch (\Exception $e) { $this->error($e->getMessage()); $this->line('For more information, try checking out the documentation at mkcert.dev'); + return self::FAILURE; } @@ -91,6 +93,7 @@ public function handle(Filesystem $filesystem): int $filesystem->createSslConfig($domain); } catch (\Exception $e) { $this->error($e->getMessage()); + return self::FAILURE; } diff --git a/src/Commands/FleetRemoveCommand.php b/src/Commands/FleetRemoveCommand.php index e88fa42..586f345 100644 --- a/src/Commands/FleetRemoveCommand.php +++ b/src/Commands/FleetRemoveCommand.php @@ -15,14 +15,14 @@ public function handle(): int { // determine if fleet is installed, return a response if not $file = base_path('docker-compose.yml'); - if (!file_exists($file)) { + if (! file_exists($file)) { $this->error('A docker-compose.yml file does not exist'); return self::FAILURE; } $yaml = Yaml::parseFile($file); - if (!isset($yaml['networks']['fleet'])) { + if (! isset($yaml['networks']['fleet'])) { $this->info(' Fleet is not currently installed on this application'); return self::SUCCESS; diff --git a/src/Commands/FleetStartCommand.php b/src/Commands/FleetStartCommand.php index 070cf89..d942029 100644 --- a/src/Commands/FleetStartCommand.php +++ b/src/Commands/FleetStartCommand.php @@ -15,7 +15,7 @@ class FleetStartCommand extends Command public function handle(Filesystem $filesystem, Docker $docker): int { // is the fleet docker network running? if not, start it up - if (!$docker->getNetwork('fleet')) { + if (! $docker->getNetwork('fleet')) { $this->info('No Fleet network, creating one...'); try { @@ -34,7 +34,7 @@ public function handle(Filesystem $filesystem, Docker $docker): int $filesystem->createSslDirectories(); // is the fleet traefik container running? if not, start it up - if (!$docker->getContainer('fleet')) { + if (! $docker->getContainer('fleet')) { $this->info('No Fleet container, spinning it up...'); try { diff --git a/src/Commands/FleetStopCommand.php b/src/Commands/FleetStopCommand.php index 56ed3f4..7c059c3 100644 --- a/src/Commands/FleetStopCommand.php +++ b/src/Commands/FleetStopCommand.php @@ -13,7 +13,7 @@ class FleetStopCommand extends Command public function handle(): int { - if (!$this->confirm('This will stop and remove all Sail instances running on the Fleet network, do you want to continue?')) { + if (! $this->confirm('This will stop and remove all Sail instances running on the Fleet network, do you want to continue?')) { return self::SUCCESS; } @@ -25,7 +25,7 @@ public function handle(): int $this->line("Removing container {$id}"); $process = Fleet::process("docker rm -f {$id}"); - if (!$process->isSuccessful()) { + if (! $process->isSuccessful()) { $this->error("Error removing container {$id}"); return self::FAILURE; diff --git a/src/Support/Docker.php b/src/Support/Docker.php index 6f15dcf..9ac4f18 100644 --- a/src/Support/Docker.php +++ b/src/Support/Docker.php @@ -3,7 +3,6 @@ namespace Aschmelyun\Fleet\Support; use Aschmelyun\Fleet\Fleet; -use Aschmelyun\Fleet\Support\Filesystem; class Docker { @@ -43,10 +42,10 @@ public function getContainer(string $name): ?string public function startFleetTraefikContainer(): void { $homeDirectory = app(Filesystem::class)->getHomeDirectory(); - + Fleet::process( "docker run -d -p 8080:8080 -p 80:80 -p 443:443 --network=fleet -v /var/run/docker.sock:/var/run/docker.sock -v {$homeDirectory}/.config/mkcert:/etc/traefik --name=fleet traefik:v2.9 --api.insecure=true --providers.docker --entryPoints.web.address=:80 --entryPoints.websecure.address=:443 --providers.file.directory=/etc/traefik/conf --providers.file.watch=true", true ); } -} \ No newline at end of file +} diff --git a/src/Support/Filesystem.php b/src/Support/Filesystem.php index 6b6346a..8ea4050 100644 --- a/src/Support/Filesystem.php +++ b/src/Support/Filesystem.php @@ -2,23 +2,23 @@ namespace Aschmelyun\Fleet\Support; +use Aschmelyun\Fleet\Fleet; use Symfony\Component\Process\Process; use Symfony\Component\Yaml\Yaml; -use Aschmelyun\Fleet\Fleet; class Filesystem { public function makeDirectory(string $path): void { - if (!is_dir($path)) { + if (! is_dir($path)) { mkdir($path, 0755, true); } } public function createSslDirectories(): void { - $this->makeDirectory($this->getHomeDirectory() . '/.config/mkcert/certs'); - $this->makeDirectory($this->getHomeDirectory() . '/.config/mkcert/conf'); + $this->makeDirectory($this->getHomeDirectory().'/.config/mkcert/certs'); + $this->makeDirectory($this->getHomeDirectory().'/.config/mkcert/conf'); } public function getHomeDirectory(): string @@ -32,7 +32,7 @@ public function getHomeDirectory(): string public function writeToEnvFile(string $key, string $value): void { $file = base_path('.env'); - if (!file_exists($file)) { + if (! file_exists($file)) { throw new \Exception('Application .env file is missing, can\'t continue'); } @@ -40,7 +40,7 @@ public function writeToEnvFile(string $key, string $value): void $env = explode("\n", $env); $filteredEnvAppPort = array_filter($env, fn ($line) => str_starts_with($line, $key)); - if (!empty($filteredEnvAppPort)) { + if (! empty($filteredEnvAppPort)) { $env[key($filteredEnvAppPort)] = "{$key}={$value}"; } else { $insert = ["{$key}={$value}"]; @@ -54,15 +54,15 @@ public function createCertificates(string $domain): void { $this->createSslDirectories(); - $process = Fleet::process("mkcert -install"); - if (!$process->isSuccessful()) { + $process = Fleet::process('mkcert -install'); + if (! $process->isSuccessful()) { throw new \Exception('mkcert is not installed or configured incorrectly, please install it and try again'); } $process = Fleet::process( "mkcert -cert-file {$this->getHomeDirectory()}/.config/mkcert/certs/{$domain}.crt -key-file {$this->getHomeDirectory()}/.config/mkcert/certs/{$domain}.key {$domain}" ); - if (!$process->isSuccessful()) { + if (! $process->isSuccessful()) { throw new \Exception('mkcert is not installed or configured incorrectly, please install it and try again'); } } @@ -82,4 +82,4 @@ public function createSslConfig(string $domain): void file_put_contents($sslConfigFile, Yaml::dump($ssl, 6)); } -} \ No newline at end of file +} From 714a65b123e1075d75377a6e85f7f466df4f6e35 Mon Sep 17 00:00:00 2001 From: Andrew Schmelyun Date: Sun, 5 Mar 2023 17:42:45 -0500 Subject: [PATCH 5/7] Refactors the fleet:remove command --- src/Commands/FleetRemoveCommand.php | 34 ++++++++++++++--------------- src/Support/Filesystem.php | 16 ++++++++++++++ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/Commands/FleetRemoveCommand.php b/src/Commands/FleetRemoveCommand.php index 586f345..ac19aa3 100644 --- a/src/Commands/FleetRemoveCommand.php +++ b/src/Commands/FleetRemoveCommand.php @@ -2,6 +2,7 @@ namespace Aschmelyun\Fleet\Commands; +use Aschmelyun\Fleet\Support\Filesystem; use Illuminate\Console\Command; use Symfony\Component\Yaml\Yaml; @@ -11,7 +12,7 @@ class FleetRemoveCommand extends Command public $description = 'Removes Fleet support from the current application'; - public function handle(): int + public function handle(Filesystem $filesystem): int { // determine if fleet is installed, return a response if not $file = base_path('docker-compose.yml'); @@ -40,37 +41,34 @@ public function handle(): int } // remove all fleet additions to the .env and docker-compose.yml files - $file = base_path('.env'); - if (file_exists($file)) { - $env = file_get_contents($file); - $env = explode("\n", $env); - - foreach ($env as $index => $line) { - if (str_starts_with($line, 'APP_PORT')) { - unset($env[$index]); - } - } + $filesystem->removeFromEnvFile('APP_PORT'); + $this->removeYamlFromDockerCompose($yaml); - file_put_contents(base_path('.env'), implode("\n", $env)); - } + // return info back to the user + $this->info(' ✨ All done! Fleet has been successfully removed from this application'); + + return self::SUCCESS; + } + private function removeYamlFromDockerCompose(array $yaml): void + { + // remove the custom domain as the first service key $service = $yaml['services'][array_keys($yaml['services'])[0]]; unset($yaml['services'][array_keys($yaml['services'])[0]]); + // and replace it with the default, laravel.test $yaml['services'] = ['laravel.test' => $service, ...$yaml['services']]; + // reset the networks and labels $yaml['services']['laravel.test']['networks'] = ['sail']; unset($yaml['services']['laravel.test']['labels']); + // reset the ports $yaml['services']['laravel.test']['ports'][] = '${APP_PORT:-80}:80'; + // remove the fleet network unset($yaml['networks']['fleet']); file_put_contents(base_path('docker-compose.yml'), Yaml::dump($yaml, 6)); - - // return info back to the user - $this->info(' ✨ All done! Fleet has been successfully removed from this application'); - - return self::SUCCESS; } } diff --git a/src/Support/Filesystem.php b/src/Support/Filesystem.php index 8ea4050..4df70b1 100644 --- a/src/Support/Filesystem.php +++ b/src/Support/Filesystem.php @@ -50,6 +50,22 @@ public function writeToEnvFile(string $key, string $value): void file_put_contents(base_path('.env'), implode("\n", $env)); } + public function removeFromEnvFile(string $key): void + { + $file = base_path('.env'); + if (file_exists($file)) { + $env = explode("\n", file_get_contents($file)); + + foreach ($env as $index => $line) { + if (str_starts_with($line, $key)) { + unset($env[$index]); + } + } + + file_put_contents(base_path('.env'), implode("\n", $env)); + } + } + public function createCertificates(string $domain): void { $this->createSslDirectories(); From 70bea34ef13bea4f94ebf232109676c54919b659 Mon Sep 17 00:00:00 2001 From: Andrew Schmelyun Date: Sun, 5 Mar 2023 17:45:38 -0500 Subject: [PATCH 6/7] Refactors the fleet:stop command to use the Docker support class --- src/Commands/FleetStopCommand.php | 29 ++++++++++++++++------------- src/Support/Docker.php | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/Commands/FleetStopCommand.php b/src/Commands/FleetStopCommand.php index 7c059c3..8b76de8 100644 --- a/src/Commands/FleetStopCommand.php +++ b/src/Commands/FleetStopCommand.php @@ -3,6 +3,7 @@ namespace Aschmelyun\Fleet\Commands; use Aschmelyun\Fleet\Fleet; +use Aschmelyun\Fleet\Support\Docker; use Illuminate\Console\Command; class FleetStopCommand extends Command @@ -11,29 +12,31 @@ class FleetStopCommand extends Command public $description = 'Stops and removes the Fleet network and app containers'; - public function handle(): int + public function handle(Docker $docker): int { if (! $this->confirm('This will stop and remove all Sail instances running on the Fleet network, do you want to continue?')) { return self::SUCCESS; } // stop and remove all docker containers running on the fleet network - $process = Fleet::process('docker ps -a --filter network=fleet --format {{.ID}}'); + try { + $docker->removeContainers('fleet'); + } catch (\Exception $e) { + $this->error('Could not remove Fleet containers'); + $this->line($e->getMessage()); - $ids = explode("\n", $process->getOutput()); - foreach (array_filter($ids) as $id) { - $this->line("Removing container {$id}"); - - $process = Fleet::process("docker rm -f {$id}"); - if (! $process->isSuccessful()) { - $this->error("Error removing container {$id}"); - - return self::FAILURE; - } + return self::FAILURE; } // remove the fleet docker network - $process = Fleet::process('docker network rm fleet'); + try { + $docker->removeNetwork('fleet'); + } catch (\Exception $e) { + $this->error('Could not remove Fleet network'); + $this->line($e->getMessage()); + + return self::FAILURE; + } $this->info(' Fleet has been successfully stopped and all active containers have been removed'); diff --git a/src/Support/Docker.php b/src/Support/Docker.php index 9ac4f18..2650015 100644 --- a/src/Support/Docker.php +++ b/src/Support/Docker.php @@ -28,6 +28,11 @@ public function createNetwork(string $name): string throw new \Exception('Could not create Docker network'); } + public function removeNetwork(string $name): void + { + Fleet::process("docker network rm {$name}"); + } + public function getContainer(string $name): ?string { $process = Fleet::process("docker ps --filter name=^{$name}$ --format '{{.ID}}'"); @@ -39,6 +44,16 @@ public function getContainer(string $name): ?string return null; } + public function removeContainers(string $network): void + { + $process = Fleet::process("docker ps -a --filter network={$network} --format {{.ID}}"); + + $ids = explode("\n", $process->getOutput()); + foreach (array_filter($ids) as $id) { + Fleet::process("docker rm -f {$id}"); + } + } + public function startFleetTraefikContainer(): void { $homeDirectory = app(Filesystem::class)->getHomeDirectory(); From 694647507b43f7c123275e59f5eb4c7cedf31fb2 Mon Sep 17 00:00:00 2001 From: Andrew Schmelyun Date: Sun, 5 Mar 2023 17:50:22 -0500 Subject: [PATCH 7/7] Updates readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index d46e37f..a2bc2be 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,16 @@ And your site will be available at the domain you provided! > Note: If you chose a domain that doesn't end in `.localhost`, you will need to add an entry to your hosts file to direct traffic to 127.0.0.1 +## Local SSL + +Fleet supports local SSL on your custom domains through the power of [mkcert](https://mkcert.dev). After you've installed it on your machine, you can use the `--ssl` option when using the `fleet:add` command to enable it for your application. + +```bash +php artisan fleet:add my-app.localhost --ssl +``` + +A local certificate will be generated and stored in `~/.config/mkcert/certs`. After spinning up your site with Sail, your specified domain will have https enabled. + ## Additional Usage By default, whenever you use `fleet:add`, a Docker network and container are both started to handle the traffic from your local domain name(s).