A plug-n-pray command
This commit is contained in:
parent
73fdcbb74f
commit
012eec7ee9
|
|
@ -0,0 +1,372 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blax\Workkit\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class PlugNPrayCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'workkit:plug-n-pray
|
||||||
|
{--php=8.4 : PHP version for the Docker image}
|
||||||
|
{--name= : Project name (default: directory name)}
|
||||||
|
{--host= : Traefik hostname (default: name.localhost)}
|
||||||
|
{--db= : Database name (default: project name)}
|
||||||
|
{--db-pass=secret : MySQL root password}
|
||||||
|
{--image=blaxsoftware/laravel : Docker image name}
|
||||||
|
{--no-queue : Disable queue worker}
|
||||||
|
{--no-scheduler : Disable scheduler}
|
||||||
|
{--horizon : Enable Horizon (disables basic queue)}
|
||||||
|
{--no-redis : Skip Redis service}
|
||||||
|
{--no-mysql : Skip MySQL service}
|
||||||
|
{--websocket : Enable WebSocket server (blax/laravel-websockets)}
|
||||||
|
{--websocket-port=6001 : WebSocket server port}
|
||||||
|
{--force : Overwrite existing files}';
|
||||||
|
|
||||||
|
protected $description = 'Generate a Docker Compose boilerplate for this Laravel project (plug-n-pray)';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$basePath = base_path();
|
||||||
|
|
||||||
|
$phpVersion = $this->option('php');
|
||||||
|
$image = $this->option('image');
|
||||||
|
$force = $this->option('force');
|
||||||
|
|
||||||
|
$projectName = $this->option('name')
|
||||||
|
?: strtolower(preg_replace('/[^a-zA-Z0-9]/', '-', basename($basePath)));
|
||||||
|
$projectName = trim(preg_replace('/--+/', '-', $projectName), '-');
|
||||||
|
|
||||||
|
$traefikHost = $this->option('host') ?: "{$projectName}.localhost";
|
||||||
|
$dbName = $this->option('db') ?: str_replace('-', '_', $projectName);
|
||||||
|
$dbPassword = $this->option('db-pass');
|
||||||
|
|
||||||
|
$enableQueue = ! $this->option('no-queue');
|
||||||
|
$enableScheduler = ! $this->option('no-scheduler');
|
||||||
|
$enableHorizon = (bool) $this->option('horizon');
|
||||||
|
$enableRedis = ! $this->option('no-redis');
|
||||||
|
$enableMysql = ! $this->option('no-mysql');
|
||||||
|
$enableWebsocket = (bool) $this->option('websocket');
|
||||||
|
$websocketPort = $this->option('websocket-port');
|
||||||
|
|
||||||
|
if ($enableHorizon) {
|
||||||
|
$enableQueue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety check
|
||||||
|
if (! $force && file_exists("{$basePath}/docker-compose.yml")) {
|
||||||
|
$this->error('docker-compose.yml already exists. Use --force to overwrite.');
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('');
|
||||||
|
$this->info('==========================================');
|
||||||
|
$this->info(' plug-n-pray 🙏');
|
||||||
|
$this->info('==========================================');
|
||||||
|
$this->table(
|
||||||
|
['Setting', 'Value'],
|
||||||
|
[
|
||||||
|
['Project', $projectName],
|
||||||
|
['PHP', $phpVersion],
|
||||||
|
['Image', "{$image}:php{$phpVersion}"],
|
||||||
|
['Traefik host', $traefikHost],
|
||||||
|
['MySQL', $enableMysql ? "Yes ({$dbName})" : 'No'],
|
||||||
|
['Redis', $enableRedis ? 'Yes' : 'No'],
|
||||||
|
['Queue', $enableQueue ? 'Yes' : 'No'],
|
||||||
|
['Scheduler', $enableScheduler ? 'Yes' : 'No'],
|
||||||
|
['Horizon', $enableHorizon ? 'Yes' : 'No'],
|
||||||
|
['WebSocket', $enableWebsocket ? "Yes (port {$websocketPort})" : 'No'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! $force && ! $this->confirm('Generate Docker files with these settings?', true)) {
|
||||||
|
$this->info('Aborted.');
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create supervisor directory
|
||||||
|
if (! is_dir("{$basePath}/docker/supervisor")) {
|
||||||
|
mkdir("{$basePath}/docker/supervisor", 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate docker-compose.yml
|
||||||
|
$compose = $this->generateCompose(
|
||||||
|
$projectName,
|
||||||
|
$phpVersion,
|
||||||
|
$image,
|
||||||
|
$traefikHost,
|
||||||
|
$dbName,
|
||||||
|
$dbPassword,
|
||||||
|
$enableQueue,
|
||||||
|
$enableScheduler,
|
||||||
|
$enableHorizon,
|
||||||
|
$enableRedis,
|
||||||
|
$enableMysql,
|
||||||
|
$enableWebsocket,
|
||||||
|
$websocketPort
|
||||||
|
);
|
||||||
|
file_put_contents("{$basePath}/docker-compose.yml", $compose);
|
||||||
|
|
||||||
|
// Generate .env.docker
|
||||||
|
$envDocker = $this->generateEnvDocker(
|
||||||
|
$traefikHost,
|
||||||
|
$dbName,
|
||||||
|
$dbPassword,
|
||||||
|
$enableRedis,
|
||||||
|
$enableMysql,
|
||||||
|
$enableWebsocket,
|
||||||
|
$websocketPort
|
||||||
|
);
|
||||||
|
file_put_contents("{$basePath}/.env.docker", $envDocker);
|
||||||
|
|
||||||
|
// Generate websocket supervisor config
|
||||||
|
if ($enableWebsocket) {
|
||||||
|
$wsConf = <<<CONF
|
||||||
|
[program:websocket]
|
||||||
|
command=/usr/local/bin/php -d variables_order=EGPCS /var/www/html/artisan websockets:serve --host=0.0.0.0 --port={$websocketPort}
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=www-data
|
||||||
|
priority=30
|
||||||
|
startsecs=5
|
||||||
|
startretries=100
|
||||||
|
stopsignal=TERM
|
||||||
|
stopwaitsecs=15
|
||||||
|
stdout_logfile=/proc/1/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/proc/1/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
CONF;
|
||||||
|
file_put_contents("{$basePath}/docker/supervisor/websocket.conf", $wsConf);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('');
|
||||||
|
$this->info('Files created:');
|
||||||
|
$this->line(' docker-compose.yml');
|
||||||
|
$this->line(' .env.docker');
|
||||||
|
$this->line(' docker/supervisor/');
|
||||||
|
if ($enableWebsocket) {
|
||||||
|
$this->line(' docker/supervisor/websocket.conf');
|
||||||
|
}
|
||||||
|
$this->info('');
|
||||||
|
$this->info('Next steps:');
|
||||||
|
$this->line(" 1. Merge .env.docker into your .env");
|
||||||
|
$this->line(" 2. Create the network (once): docker network create web");
|
||||||
|
$this->line(" 3. Start: docker compose up -d");
|
||||||
|
$this->line(" 4. Visit: http://{$traefikHost}");
|
||||||
|
$this->info('');
|
||||||
|
$this->info('Pray it works. 🙏');
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateCompose(
|
||||||
|
string $name,
|
||||||
|
string $php,
|
||||||
|
string $image,
|
||||||
|
string $host,
|
||||||
|
string $dbName,
|
||||||
|
string $dbPassword,
|
||||||
|
bool $queue,
|
||||||
|
bool $scheduler,
|
||||||
|
bool $horizon,
|
||||||
|
bool $redis,
|
||||||
|
bool $mysql,
|
||||||
|
bool $websocket = false,
|
||||||
|
string $websocketPort = '6001'
|
||||||
|
): string {
|
||||||
|
$enableQueue = $queue ? 'true' : 'false';
|
||||||
|
$enableScheduler = $scheduler ? 'true' : 'false';
|
||||||
|
$enableHorizon = $horizon ? 'true' : 'false';
|
||||||
|
|
||||||
|
$yaml = <<<YAML
|
||||||
|
# Generated by workkit:plug-n-pray
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
|
internal:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
YAML;
|
||||||
|
|
||||||
|
// Volumes
|
||||||
|
$volumes = [];
|
||||||
|
if ($mysql) {
|
||||||
|
$volumes[] = ' mysql-data:';
|
||||||
|
}
|
||||||
|
if ($redis) {
|
||||||
|
$volumes[] = ' redis-data:';
|
||||||
|
}
|
||||||
|
if (! empty($volumes)) {
|
||||||
|
$yaml .= "volumes:\n" . implode("\n", $volumes) . "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// App service
|
||||||
|
$depends = '';
|
||||||
|
if ($mysql || $redis) {
|
||||||
|
$depLines = [];
|
||||||
|
if ($mysql) {
|
||||||
|
$depLines[] = " mysql:\n condition: service_healthy";
|
||||||
|
}
|
||||||
|
if ($redis) {
|
||||||
|
$depLines[] = " redis:\n condition: service_healthy";
|
||||||
|
}
|
||||||
|
$depends = " depends_on:\n" . implode("\n", $depLines) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$yaml .= <<<YAML
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: {$image}:php{$php}
|
||||||
|
container_name: {$name}-app
|
||||||
|
restart: unless-stopped
|
||||||
|
working_dir: /var/www/html
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www/html
|
||||||
|
- ./docker/supervisor:/etc/supervisor/custom.d
|
||||||
|
environment:
|
||||||
|
ENABLE_QUEUE: "{$enableQueue}"
|
||||||
|
ENABLE_SCHEDULER: "{$enableScheduler}"
|
||||||
|
ENABLE_HORIZON: "{$enableHorizon}"
|
||||||
|
ENABLE_LARAVEL_PERMS: "1"
|
||||||
|
YAML;
|
||||||
|
|
||||||
|
if ($websocket) {
|
||||||
|
$yaml .= " PUSHER_PORT: \"{$websocketPort}\"\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$yaml .= <<<YAML
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
- internal
|
||||||
|
{$depends} labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.docker.network=web
|
||||||
|
- traefik.http.routers.{$name}.rule=Host(`{$host}`)
|
||||||
|
- traefik.http.routers.{$name}.entrypoints=web
|
||||||
|
- traefik.http.routers.{$name}.service={$name}-http
|
||||||
|
- traefik.http.services.{$name}-http.loadbalancer.server.port=80
|
||||||
|
- traefik.http.routers.{$name}-tls.rule=Host(`{$host}`)
|
||||||
|
- traefik.http.routers.{$name}-tls.entrypoints=websecure
|
||||||
|
- traefik.http.routers.{$name}-tls.tls=true
|
||||||
|
- traefik.http.routers.{$name}-tls.service={$name}-https
|
||||||
|
- traefik.http.services.{$name}-https.loadbalancer.server.port=80
|
||||||
|
YAML;
|
||||||
|
|
||||||
|
if ($websocket) {
|
||||||
|
$yaml .= <<<YAML
|
||||||
|
# WebSocket
|
||||||
|
- traefik.http.routers.{$name}-ws.rule=Host(`ws-{$host}`)
|
||||||
|
- traefik.http.routers.{$name}-ws.entrypoints=web
|
||||||
|
- traefik.http.routers.{$name}-ws.service={$name}-ws
|
||||||
|
- traefik.http.services.{$name}-ws.loadbalancer.server.port={$websocketPort}
|
||||||
|
- traefik.http.routers.{$name}-wss.rule=Host(`ws-{$host}`)
|
||||||
|
- traefik.http.routers.{$name}-wss.entrypoints=websecure
|
||||||
|
- traefik.http.routers.{$name}-wss.tls=true
|
||||||
|
- traefik.http.routers.{$name}-wss.service={$name}-wss
|
||||||
|
- traefik.http.services.{$name}-wss.loadbalancer.server.port={$websocketPort}
|
||||||
|
YAML;
|
||||||
|
}
|
||||||
|
|
||||||
|
$yaml .= "\n";
|
||||||
|
|
||||||
|
// MySQL
|
||||||
|
if ($mysql) {
|
||||||
|
$yaml .= <<<YAML
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: {$name}-mysql
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: "{$dbPassword}"
|
||||||
|
MYSQL_DATABASE: "{$dbName}"
|
||||||
|
volumes:
|
||||||
|
- mysql-data:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p{$dbPassword}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
YAML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
if ($redis) {
|
||||||
|
$yaml .= <<<YAML
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: {$name}-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
YAML;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $yaml;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateEnvDocker(
|
||||||
|
string $host,
|
||||||
|
string $dbName,
|
||||||
|
string $dbPassword,
|
||||||
|
bool $redis,
|
||||||
|
bool $mysql,
|
||||||
|
bool $websocket = false,
|
||||||
|
string $websocketPort = '6001'
|
||||||
|
): string {
|
||||||
|
$env = "# Generated by workkit:plug-n-pray\n\nAPP_URL=http://{$host}\n";
|
||||||
|
|
||||||
|
if ($mysql) {
|
||||||
|
$env .= <<<ENV
|
||||||
|
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=mysql
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE={$dbName}
|
||||||
|
DB_USERNAME=root
|
||||||
|
DB_PASSWORD={$dbPassword}
|
||||||
|
ENV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($redis) {
|
||||||
|
$env .= <<<ENV
|
||||||
|
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
CACHE_STORE=redis
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
ENV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($websocket) {
|
||||||
|
$env .= <<<ENV
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=pusher
|
||||||
|
PUSHER_APP_ID=app-id
|
||||||
|
PUSHER_APP_KEY=app-key
|
||||||
|
PUSHER_APP_SECRET=app-secret
|
||||||
|
PUSHER_HOST=127.0.0.1
|
||||||
|
PUSHER_PORT={$websocketPort}
|
||||||
|
PUSHER_SCHEME=http
|
||||||
|
LARAVEL_WEBSOCKETS_PORT={$websocketPort}
|
||||||
|
ENV;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $env . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace Blax\Workkit;
|
namespace Blax\Workkit;
|
||||||
|
|
||||||
|
use Blax\Workkit\Commands\PlugNPrayCommand;
|
||||||
|
|
||||||
class WorkkitServiceProvider extends \Illuminate\Support\ServiceProvider
|
class WorkkitServiceProvider extends \Illuminate\Support\ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
@ -21,6 +23,10 @@ class WorkkitServiceProvider extends \Illuminate\Support\ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
//
|
if ($this->app->runningInConsole()) {
|
||||||
|
$this->commands([
|
||||||
|
PlugNPrayCommand::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue