diff --git a/.env.example b/.env.example index 0927a9a1..85fdb66c 100644 --- a/.env.example +++ b/.env.example @@ -89,3 +89,14 @@ TYPESENSE_API_KEY=xyz NIGHTWATCH_TOKEN= NIGHTWATCH_REQUEST_SAMPLE_RATE=0.1 + +# SSH Tunnel Configuration for Database Migration +SSH_TUNNEL_USER= +SSH_TUNNEL_HOSTNAME= +SSH_TUNNEL_PORT=22 +SSH_TUNNEL_LOCAL_PORT=3307 +SSH_TUNNEL_IDENTITY_FILE= +SSH_TUNNEL_PRIVATE_KEY="-----BEGIN OPENSSH PRIVATE KEY----- +oi!jkdiososbXCbnNzaC1rZXktdjEABG5vbmUAAAAEbm9uZQAAAAAAAAFwAAAAdzc2gtcn +... (votre clé SSH privée complète ici) ... +-----END OPENSSH PRIVATE KEY-----" diff --git a/app-modules/database-migration/README.md b/app-modules/database-migration/README.md index f52515b2..b44bdb1e 100644 --- a/app-modules/database-migration/README.md +++ b/app-modules/database-migration/README.md @@ -44,10 +44,18 @@ Configure your environment variables in `.env`: # SSH Tunnel Configuration SSH_TUNNEL_USER=your-ssh-user SSH_TUNNEL_HOSTNAME=your-server.com -SSH_TUNNEL_IDENTITY_FILE=/path/to/your/private/key SSH_TUNNEL_LOCAL_PORT=3307 SSH_TUNNEL_BIND_PORT=3306 SSH_TUNNEL_BIND_ADDRESS=127.0.0.1 + +# Option 1: Utiliser un fichier de clé SSH (par défaut) +SSH_TUNNEL_IDENTITY_FILE=/path/to/your/private/key + +# Option 2: Utiliser le contenu de la clé SSH via variable d'environnement (recommandé pour Docker) +SSH_TUNNEL_PRIVATE_KEY="-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAFwAAAAdzc2gtcn +... (votre clé SSH privée complète ici) ... +-----END OPENSSH PRIVATE KEY-----" SSH_TUNNEL_AUTO_ACTIVATE=false SSH_TUNNEL_LOGGING_ENABLED=true SSH_TUNNEL_LOGGING_CHANNEL=default diff --git a/app-modules/database-migration/config/ssh-tunnel.php b/app-modules/database-migration/config/ssh-tunnel.php index 5dd088d5..ad6457d2 100644 --- a/app-modules/database-migration/config/ssh-tunnel.php +++ b/app-modules/database-migration/config/ssh-tunnel.php @@ -57,6 +57,7 @@ 'hostname' => env('SSH_TUNNEL_HOSTNAME'), 'port' => env('SSH_TUNNEL_PORT', 22), 'identity_file' => env('SSH_TUNNEL_IDENTITY_FILE', '~/.ssh/id_rsa'), + 'private_key_content' => env('SSH_TUNNEL_PRIVATE_KEY'), 'options' => env('SSH_TUNNEL_SSH_OPTIONS', '-o StrictHostKeyChecking=no'), 'verbosity' => env('SSH_TUNNEL_VERBOSITY', ''), ], diff --git a/app-modules/database-migration/src/Commands/MigrateDatabaseCommand.php b/app-modules/database-migration/src/Commands/MigrateDatabaseCommand.php index f44dc0f5..3a2d19cd 100644 --- a/app-modules/database-migration/src/Commands/MigrateDatabaseCommand.php +++ b/app-modules/database-migration/src/Commands/MigrateDatabaseCommand.php @@ -11,7 +11,7 @@ final class MigrateDatabaseCommand extends Command { protected $signature = 'db:migrate-mysql-to-pgsql - {--tables=* : Specific tables to migrate (optional)} + {--tables= : Specific tables to migrate (optional)} {--chunk=1000 : Number of records to process per chunk} {--dry-run : Show what would be migrated without actually doing it}'; @@ -23,9 +23,9 @@ public function handle( ): int { $this->info('🚀 Starting MySQL to PostgreSQL migration...'); - // Ensure SSH tunnel is active if (! $tunnelService->isActive()) { $this->warn('SSH tunnel is not active. Attempting to activate...'); + $tunnelService->activate(); } @@ -46,8 +46,7 @@ public function handle( } try { - // Get tables to migrate - $tables = $specificTables ?: $migrationService->getSourceTables(); + $tables = $specificTables ? explode(',', $specificTables) : $migrationService->getSourceTables(); if (blank($tables)) { $this->error('❌ No tables found to migrate'); @@ -60,24 +59,34 @@ public function handle( $progressBar = $this->output->createProgressBar(count($tables)); $progressBar->start(); - foreach ($tables as $table) { - if ($table === null) { - continue; - } - - $this->newLine(); - $this->info("🔄 Migrating table: {$table}"); + if (! $isDryRun) { + $migrationService->disableForeignKeyConstraints(); + } + try { + foreach ($tables as $table) { + if (blank($table)) { + continue; + } + + $this->newLine(); + $this->info("🔄 Migrating table: {$table}"); + + if (! $isDryRun) { + $migrationService->migrateTable($table, $chunkSize, function ($processed, $total): void { + $this->line(" 📊 Processed {$processed}/{$total} records"); + }); + } else { + $count = $migrationService->getTableRecordCount($table); + $this->line(" 📊 Would migrate {$count} records"); + } + + $progressBar->advance(); + } + } finally { if (! $isDryRun) { - $migrationService->migrateTable($table, $chunkSize, function ($processed, $total): void { - $this->line(" 📊 Processed {$processed}/{$total} records"); - }); - } else { - $count = $migrationService->getTableRecordCount($table); - $this->line(" 📊 Would migrate {$count} records"); + $migrationService->enableForeignKeyConstraints(); } - - $progressBar->advance(); } $progressBar->finish(); @@ -95,6 +104,14 @@ public function handle( $this->error("❌ Migration failed: {$e->getMessage()}"); return Command::FAILURE; + } finally { + $this->info('🧹 Cleaning up SSH tunnel and temporary files...'); + + $tunnelService->destroy(); + $this->info('✅ SSH tunnel destroyed'); + + $tunnelService->forceCleanupTempKeyFile(); + $this->info('✅ Temporary SSH key file cleaned up'); } } } diff --git a/app-modules/database-migration/src/Services/DatabaseMigrationService.php b/app-modules/database-migration/src/Services/DatabaseMigrationService.php index 46b12ff4..6aa96ab4 100644 --- a/app-modules/database-migration/src/Services/DatabaseMigrationService.php +++ b/app-modules/database-migration/src/Services/DatabaseMigrationService.php @@ -43,11 +43,11 @@ private function getExcludedTables(): array return [ 'migrations', 'password_resets', - 'password_reset_tokens', 'personal_access_tokens', 'failed_jobs', 'jobs', 'job_batches', + 'temporary_uploads', ]; } @@ -61,45 +61,65 @@ public function getTableRecordCount(string $table): int ->count(); } - /** - * Migrate a single table from source to target - */ public function migrateTable(string $table, int $chunkSize = 1000, ?callable $progressCallback = null): void { - // First, ensure the table exists in target database if (! Schema::connection($this->targetConnection)->hasTable($table)) { - throw new \Exception("Table '{$table}' does not exist in target database. Run migrations first."); + return; } - // Clear existing data in target table DB::connection($this->targetConnection)->table($table)->truncate(); $totalRecords = $this->getTableRecordCount($table); $processedRecords = 0; - // Process data in chunks - DB::connection($this->sourceConnection) - ->table($table) - ->orderBy('id') - ->chunk($chunkSize, function (Collection $records) use ( - $table, - &$processedRecords, - $totalRecords, - $progressCallback - ): void { - $data = $records->map(fn ($record): array => $this->transformRecord((array) $record))->toArray(); - - // Insert into target database - DB::connection($this->targetConnection) - ->table($table) - ->insert($data); - - $processedRecords += count($records); - - if ($progressCallback) { - $progressCallback($processedRecords, $totalRecords); - } - }); + $query = DB::connection($this->sourceConnection)->table($table); + + if ($this->hasIdColumn($table)) { + $query->orderBy('id'); + } else { + $columns = Schema::connection($this->sourceConnection)->getColumnListing($table); + + if (! empty($columns)) { + $query->orderBy($columns[0]); + } + } + + $query->chunk($chunkSize, function (Collection $records) use ( + $table, + &$processedRecords, + $totalRecords, + $progressCallback + ): void { + $data = $records->map(fn ($record): array => $this->transformRecord((array) $record))->toArray(); + + DB::connection($this->targetConnection) + ->table($table) + ->insert($data); + + $processedRecords += count($records); + + if ($progressCallback) { + $progressCallback($processedRecords, $totalRecords); + } + }); + } + + public function disableForeignKeyConstraints(): void + { + DB::connection($this->targetConnection) + ->statement('SET session_replication_role = replica;'); + } + + public function enableForeignKeyConstraints(): void + { + DB::connection($this->targetConnection) + ->statement('SET session_replication_role = DEFAULT;'); + } + + private function hasIdColumn(string $table): bool + { + return Schema::connection($this->sourceConnection) + ->hasColumn($table, 'id'); } /** @@ -112,15 +132,10 @@ private function transformRecord(array $record): array { foreach ($record as $key => $value) { // Handle MySQL boolean fields (tinyint) to PostgreSQL boolean - if (is_int($value) && in_array($value, [0, 1]) && preg_match('/^(is_|has_|can_|should_|enabled|active|published|verified)/', $key)) { + if (is_int($value) && in_array($value, [0, 1]) && preg_match('/^(is_|has_|can_|should_|enabled|active|certified|public|featured|published|pinned|opt_in|sponsored|verified|locked)/', $key)) { $record[$key] = (bool) $value; } - // Handle empty strings that should be null in PostgreSQL - if ($value === '') { - $record[$key] = null; - } - // Handle MySQL timestamp '0000-00-00 00:00:00' to null if ($value === '0000-00-00 00:00:00') { $record[$key] = null; diff --git a/app-modules/database-migration/src/Services/SshTunnelService.php b/app-modules/database-migration/src/Services/SshTunnelService.php index ad617027..8be4d884 100644 --- a/app-modules/database-migration/src/Services/SshTunnelService.php +++ b/app-modules/database-migration/src/Services/SshTunnelService.php @@ -15,6 +15,8 @@ class SshTunnelService private string $sshCommand; + private ?string $tempKeyFile = null; + /** @var array */ private array $output = []; @@ -73,6 +75,9 @@ public function destroy(): bool $this->log('SSH tunnel destroyed successfully'); } + // Nettoyer le fichier temporaire de clé SSH s'il existe + $this->cleanupTempKeyFile(); + return $result; } @@ -116,13 +121,15 @@ private function buildCommands(): void $config['local']['port'] ); - // Build SSH tunnel command + // Build SSH tunnel command with identity file handling + $identityOption = $this->buildIdentityOption($config); + $this->sshCommand = sprintf( - '%s %s %s -N -i %s -L %d:%s:%d -p %d %s@%s', + '%s %s %s -N %s -L %d:%s:%d -p %d %s@%s', $config['executables']['ssh'], $config['ssh']['options'], $config['ssh']['verbosity'], - $config['ssh']['identity_file'], + $identityOption, $config['local']['port'], $config['remote']['bind_address'], $config['remote']['bind_port'], @@ -140,6 +147,99 @@ private function runCommand(string $command): bool return $returnVar === 0; } + private function buildIdentityOption(array $config): string + { + // Si une clé privée est fournie via variable d'environnement + if (! empty($config['ssh']['private_key_content'])) { + $tempKeyFile = $this->createTempKeyFile($config['ssh']['private_key_content']); + + return sprintf('-i %s', $tempKeyFile); + } + + // Sinon utiliser le fichier d'identité classique + return sprintf('-i %s', $config['ssh']['identity_file']); + } + + private function createTempKeyFile(string $keyContent): string + { + // Nettoyer et normaliser le contenu de la clé SSH + $cleanedKeyContent = $this->normalizeKeyContent($keyContent); + + // Utiliser un nom de fichier plus persistant basé sur un hash du contenu + $keyHash = hash('sha256', $cleanedKeyContent); + $tempFile = sys_get_temp_dir().'/ssh_key_'.substr($keyHash, 0, 16); + + // Si le fichier existe déjà avec le même contenu, le réutiliser + if (file_exists($tempFile)) { + $existingContent = file_get_contents($tempFile); + if ($existingContent === $cleanedKeyContent) { + $this->tempKeyFile = $tempFile; + $this->log('Reusing existing SSH key file', ['file' => $tempFile]); + + return $tempFile; + } + } + + // Écrire le contenu de la clé dans le fichier temporaire + if (file_put_contents($tempFile, $cleanedKeyContent) === false) { + throw new SshTunnelException('Unable to write SSH key to temporary file'); + } + + // Définir les permissions appropriées pour la clé SSH (600) + if (chmod($tempFile, 0600) === false) { + unlink($tempFile); + + throw new SshTunnelException('Unable to set proper permissions on SSH key file'); + } + + // Stocker la référence pour le nettoyage ultérieur + $this->tempKeyFile = $tempFile; + + $this->log('Temporary SSH key file created', ['file' => $tempFile]); + + return $tempFile; + } + + private function normalizeKeyContent(string $keyContent): string + { + // Remplacer les \n littéraux par de vrais retours à la ligne + $normalized = str_replace('\\n', "\n", $keyContent); + + // Nettoyer les espaces en début/fin + $normalized = trim($normalized); + + // S'assurer que la clé se termine par un retour à la ligne + if (! str_ends_with($normalized, "\n")) { + $normalized .= "\n"; + } + + return $normalized; + } + + private function cleanupTempKeyFile(): void + { + if ($this->tempKeyFile !== null && file_exists($this->tempKeyFile)) { + if (unlink($this->tempKeyFile)) { + $this->log('Temporary SSH key file cleaned up', ['file' => $this->tempKeyFile]); + } else { + $this->log('Failed to cleanup temporary SSH key file', ['file' => $this->tempKeyFile]); + } + $this->tempKeyFile = null; + } + } + + public function forceCleanupTempKeyFile(): void + { + $this->cleanupTempKeyFile(); + } + + public function __destruct() + { + // Ne pas nettoyer automatiquement à la destruction pour permettre + // la réutilisation du fichier pendant la durée de vie de l'application + // Le nettoyage doit être fait explicitement via forceCleanupTempKeyFile() + } + private function log(string $message, array $context = []): void { if (! config('ssh-tunnel.logging.enabled', true)) { diff --git a/app-modules/database-migration/tests/Feature/MigrateDatabaseCommandTest.php b/app-modules/database-migration/tests/Feature/MigrateDatabaseCommandTest.php index 9eb2a127..775f5223 100644 --- a/app-modules/database-migration/tests/Feature/MigrateDatabaseCommandTest.php +++ b/app-modules/database-migration/tests/Feature/MigrateDatabaseCommandTest.php @@ -11,9 +11,10 @@ }); it('can run dry run migration', function (): void { - // Mock the services $mockTunnelService = $this->mock(SshTunnelService::class); $mockTunnelService->shouldReceive('isActive')->andReturn(true); + $mockTunnelService->shouldReceive('destroy')->once(); + $mockTunnelService->shouldReceive('forceCleanupTempKeyFile')->once(); $mockMigrationService = $this->mock(DatabaseMigrationService::class); $mockMigrationService->shouldReceive('getSourceTables') @@ -37,10 +38,11 @@ }); it('activates ssh tunnel if not active', function (): void { - // Mock the services $mockTunnelService = $this->mock(SshTunnelService::class); $mockTunnelService->shouldReceive('isActive')->andReturn(false, true); $mockTunnelService->shouldReceive('activate')->once(); + $mockTunnelService->shouldReceive('destroy')->once(); + $mockTunnelService->shouldReceive('forceCleanupTempKeyFile')->once(); $mockMigrationService = $this->mock(DatabaseMigrationService::class); $mockMigrationService->shouldReceive('getSourceTables') @@ -59,7 +61,6 @@ }); it('fails when ssh tunnel cannot be activated', function (): void { - // Mock the services $mockTunnelService = $this->mock(SshTunnelService::class); $mockTunnelService->shouldReceive('isActive')->andReturn(false); $mockTunnelService->shouldReceive('activate')->once(); @@ -72,9 +73,10 @@ }); it('can migrate specific tables', function (): void { - // Mock the services $mockTunnelService = $this->mock(SshTunnelService::class); $mockTunnelService->shouldReceive('isActive')->andReturn(true); + $mockTunnelService->shouldReceive('destroy')->once(); + $mockTunnelService->shouldReceive('forceCleanupTempKeyFile')->once(); $mockMigrationService = $this->mock(DatabaseMigrationService::class); $mockMigrationService->shouldReceive('getTableRecordCount') diff --git a/app/Console/Commands/Benchmark/BenchmarkCommand.php b/app/Console/Commands/Benchmark/BenchmarkCommand.php new file mode 100644 index 00000000..72c3dada --- /dev/null +++ b/app/Console/Commands/Benchmark/BenchmarkCommand.php @@ -0,0 +1,151 @@ +displayHeader(); + $this->runBenchmarkTests(); + + return Command::SUCCESS; + } + + private function displayHeader(): void + { + $this->newLine(); + $this->line(' Performance Benchmark: blank() vs empty() '); + $this->newLine(); + } + + private function runBenchmarkTests(): void + { + $testCases = $this->getTestCases(); + + foreach ($testCases as $description => $value) { + $this->line("Testing: {$description}"); + + $blankMetrics = $this->measurePerformance(fn (): bool => blank($value)); + $emptyMetrics = $this->measurePerformance(fn (): bool => empty($value)); + + $this->displayResults($blankMetrics, $emptyMetrics); + $this->newLine(); + } + } + + /** + * @return array{time: float, memory: float} + */ + private function measurePerformance(callable $callback): array + { + $iterations = 100000; + + $startTime = microtime(true); + $startMemory = memory_get_usage(true); + + for ($i = 0; $i < $iterations; $i++) { + $callback(); + } + + $endTime = microtime(true); + $endMemory = memory_get_usage(true); + + return [ + 'time' => $endTime - $startTime, + 'memory' => $endMemory - $startMemory, + ]; + } + + /** + * @param array{time: float, memory: float} $blankMetrics + * @param array{time: float, memory: float} $emptyMetrics + */ + private function displayResults(array $blankMetrics, array $emptyMetrics): void + { + $blankTime = $this->formatTime($blankMetrics['time']); + $emptyTime = $this->formatTime($emptyMetrics['time']); + + $blankMemory = $this->formatMemory($blankMetrics['memory']); + $emptyMemory = $this->formatMemory($emptyMetrics['memory']); + + $this->line(sprintf( + 'blank() TIME: %s MEMORY: %s ', + $blankTime, + $blankMemory + )); + + $this->line(sprintf( + 'empty() TIME: %s MEMORY: %s ', + $emptyTime, + $emptyMemory + )); + + $isFasterBlank = $blankMetrics['time'] < $emptyMetrics['time']; + $ratio = $isFasterBlank + ? round($emptyMetrics['time'] / $blankMetrics['time'], 2) + : round($blankMetrics['time'] / $emptyMetrics['time'], 2); + + $winner = $isFasterBlank ? 'blank()' : 'empty()'; + $this->line(" {$winner} is {$ratio}x faster"); + } + + private function formatTime(float $timeInSeconds): string + { + $timeInMs = $timeInSeconds * 1000; + + if ($timeInMs < 1) { + return round($timeInMs * 1000, 2).'μs'; + } + + if ($timeInMs < 1000) { + return round($timeInMs, 4).'ms'; + } + + return round($timeInSeconds, 2).'s'; + } + + private function formatMemory(float $bytes): string + { + if ($bytes === 0.0) { + return '0B'; + } + + if (abs($bytes) < 1024) { + return round($bytes, 2).'B'; + } + + if (abs($bytes) < 1048576) { + return round($bytes / 1024, 2).'KB'; + } + + return round($bytes / 1048576, 2).'MB'; + } + + /** + * @return array + */ + private function getTestCases(): array + { + return [ + 'null value' => null, + 'empty string' => '', + 'whitespace string' => ' ', + 'zero string' => '0', + 'zero integer' => 0, + 'false boolean' => false, + 'empty array' => [], + 'non-empty string' => 'hello world', + 'non-empty array' => [1, 2, 3, 4, 5], + 'true boolean' => true, + ]; + } +} diff --git a/config/filesystems.php b/config/filesystems.php index f30947d1..39dcacda 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -57,7 +57,7 @@ 'throw' => false, ], - 'media-s3' => [ + 'media' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), @@ -71,15 +71,6 @@ 'directory_visibility' => 'public', 'throw' => false, ], - - 'media' => [ - 'driver' => 'local', - 'root' => public_path('media'), - 'url' => env('APP_URL').'/media', - 'visibility' => 'public', - 'throw' => false, - ], - ], /* diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 2bdd1093..e29c8adc 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -7,117 +7,399 @@ services: NODE_VERSION: 22 PHP_VERSION: 8.4 image: laravelcm/php + restart: unless-stopped extra_hosts: - 'host.docker.internal:host-gateway' healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:${APP_PORT:-8080}/up'] + test: [ 'CMD', 'curl', '-f', 'http://localhost:${APP_PORT:-8080}/up' ] timeout: 30s environment: - APP_NAME: ${APP_NAME} - APP_ENV: ${APP_ENV} - APP_KEY: ${APP_KEY} - APP_DEBUG: ${APP_DEBUG} - APP_DOMAIN: ${APP_DOMAIN} - APP_URL: ${APP_URL} - ASSET_URL: ${ASSET_URL} - APP_TIMEZONE: ${APP_TIMEZONE} - APP_LOCALE: ${APP_LOCALE} - APP_FALLBACK_LOCALE: ${APP_FALLBACK_LOCALE} - APP_MAINTENANCE_DRIVER: ${APP_MAINTENANCE_DRIVER} - BCRYPT_ROUNDS: ${BCRYPT_ROUNDS} - DB_CONNECTION: ${DB_CONNECTION} - DB_HOST: ${DB_HOST} - DB_PORT: ${DB_PORT} - DB_DATABASE: ${DB_DATABASE} - DB_USERNAME: ${DB_USERNAME} - DB_PASSWORD: ${DB_PASSWORD} - LOG_CHANNEL: ${LOG_CHANNEL} - LOG_STACK: ${LOG_STACK} - LOG_DEPRECATIONS_CHANNEL: ${LOG_DEPRECATIONS_CHANNEL} - LOG_LEVEL: ${LOG_LEVEL} - SESSION_DRIVER: ${SESSION_DRIVER} - SESSION_LIFETIME: ${SESSION_LIFETIME} - SESSION_ENCRYPT: ${SESSION_ENCRYPT} - SESSION_PATH: ${SESSION_PATH} - SESSION_DOMAIN: ${SESSION_DOMAIN} - BROADCAST_CONNECTION: ${BROADCAST_CONNECTION} - FILESYSTEM_DISK: ${FILESYSTEM_DISK} - MEDIA_DISK: ${MEDIA_DISK} - FILAMENT_FILESYSTEM_DISK: ${MEDIA_DISK} - FILAMENT_PATH: ${FILAMENT_PATH} - QUEUE_CONNECTION: ${QUEUE_CONNECTION} - CACHE_STORE: ${CACHE_STORE} - CACHE_PREFIX: ${CACHE_PREFIX} - MEMCACHED_HOST: ${MEMCACHED_HOST} - REDIS_URL: ${REDIS_URL} - MAIL_MAILER: ${MAIL_MAILER} - MAIL_HOST: ${MAIL_HOST} - MAIL_PORT: ${MAIL_PORT} - MAIL_USERNAME: ${MAIL_USERNAME} - MAIL_PASSWORD: ${MAIL_PASSWORD} - MAIL_ENCRYPTION: ${MAIL_ENCRYPTION} - MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS} - MAIL_FROM_NAME: ${MAIL_FROM_NAME} - MAIL_SUPPORT: ${MAIL_SUPPORT} - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} - AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION} - AWS_BUCKET: ${AWS_BUCKET} - AWS_ENDPOINT: ${AWS_ENDPOINT} - AWS_URL: ${AWS_URL} - AWS_USE_PATH_STYLE_ENDPOINT: ${AWS_USE_PATH_STYLE_ENDPOINT} - AWS_DIRECTORY: ${AWS_DIRECTORY} - VITE_APP_NAME: ${VITE_APP_NAME} - OPENAI_API_KEY: ${OPENAI_API_KEY} - OPENAI_ORGANIZATION: ${OPENAI_ORGANIZATION} - OPENAI_PROJECT: ${OPENAI_PROJECT} - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} - ANTHROPIC_API_VERSION: ${ANTHROPIC_API_VERSION} - OLLAMA_URL: ${OLLAMA_URL} - DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} - SCOUT_DRIVER: ${SCOUT_DRIVER} - TYPESENSE_HOST: ${TYPESENSE_HOST} - TYPESENSE_PORT: ${TYPESENSE_PORT} - TYPESENSE_PROTOCOL: ${TYPESENSE_PROTOCOL} - TYPESENSE_API_KEY: ${TYPESENSE_API_KEY} - STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} - STRIPE_VERSION: ${STRIPE_VERSION} - NOTCHPAY_PUBLIC_KEY: ${NOTCHPAY_PUBLIC_KEY} - NOTCHPAY_SECRET_KEY: ${NOTCHPAY_SECRET_KEY} - MARKDOWNX_GIPHY_API_KEY: ${MARKDOWNX_GIPHY_API_KEY} - TORCHLIGHT_TOKEN: ${TORCHLIGHT_TOKEN} - TORCHLIGHT_THEME: ${TORCHLIGHT_THEME} - TWITTER_CONSUMER_KEY: ${TWITTER_CONSUMER_KEY} - TWITTER_CONSUMER_SECRET: ${TWITTER_CONSUMER_SECRET} - TWITTER_ACCESS_TOKEN: ${TWITTER_ACCESS_TOKEN} - TWITTER_ACCESS_SECRET: ${TWITTER_ACCESS_SECRET} - TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} - TELEGRAM_CHANNEL: ${TELEGRAM_CHANNEL} - GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} - GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} - GITHUB_REDIRECT: ${GITHUB_REDIRECT} - GITHUB_FINE_GRAINED_TOKEN: ${GITHUB_FINE_GRAINED_TOKEN} - NIGHTWATCH_TOKEN: ${NIGHTWATCH_TOKEN} - NIGHTWATCH_REQUEST_SAMPLE_RATE: ${NIGHTWATCH_REQUEST_SAMPLE_RATE} - OCTANE_SERVER: 'frankenphp' - CADDY_GLOBAL_OPTIONS: | - servers { - trusted_proxies static private_ranges - } - CADDY_SERVER_EXTRA_DIRECTIVES: | - @static { - file - path *.ico *.css *.js *.gif *.webp *.avif *.jpg *.jpeg *.png *.svg *.woff *.woff2 - } - header @static Cache-Control "public, max-age=31536000, s-maxage=31536000" - FRANKENPHP_CONFIG: | - worker { - file ./public/frankenphp-worker.php - } + APP_NAME: ${APP_NAME} + APP_ENV: ${APP_ENV} + APP_KEY: ${APP_KEY} + APP_DEBUG: ${APP_DEBUG} + APP_DOMAIN: ${APP_DOMAIN} + APP_URL: ${APP_URL} + ASSET_URL: ${ASSET_URL} + APP_TIMEZONE: ${APP_TIMEZONE} + APP_LOCALE: ${APP_LOCALE} + APP_FALLBACK_LOCALE: ${APP_FALLBACK_LOCALE} + APP_MAINTENANCE_DRIVER: ${APP_MAINTENANCE_DRIVER} + BCRYPT_ROUNDS: ${BCRYPT_ROUNDS} + DB_CONNECTION: ${DB_CONNECTION} + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT} + DB_DATABASE: ${DB_DATABASE} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + LOG_CHANNEL: ${LOG_CHANNEL} + LOG_STACK: ${LOG_STACK} + LOG_DEPRECATIONS_CHANNEL: ${LOG_DEPRECATIONS_CHANNEL} + LOG_LEVEL: ${LOG_LEVEL} + SESSION_DRIVER: ${SESSION_DRIVER} + SESSION_LIFETIME: ${SESSION_LIFETIME} + SESSION_ENCRYPT: ${SESSION_ENCRYPT} + SESSION_PATH: ${SESSION_PATH} + SESSION_DOMAIN: ${SESSION_DOMAIN} + BROADCAST_CONNECTION: ${BROADCAST_CONNECTION} + FILESYSTEM_DISK: ${FILESYSTEM_DISK} + MEDIA_DISK: ${MEDIA_DISK} + FILAMENT_FILESYSTEM_DISK: ${MEDIA_DISK} + FILAMENT_PATH: ${FILAMENT_PATH} + QUEUE_CONNECTION: ${QUEUE_CONNECTION} + CACHE_STORE: ${CACHE_STORE} + CACHE_PREFIX: ${CACHE_PREFIX} + MEMCACHED_HOST: ${MEMCACHED_HOST} + REDIS_URL: ${REDIS_URL} + MAIL_MAILER: ${MAIL_MAILER} + MAIL_HOST: ${MAIL_HOST} + MAIL_PORT: ${MAIL_PORT} + MAIL_USERNAME: ${MAIL_USERNAME} + MAIL_PASSWORD: ${MAIL_PASSWORD} + MAIL_ENCRYPTION: ${MAIL_ENCRYPTION} + MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS} + MAIL_FROM_NAME: ${MAIL_FROM_NAME} + MAIL_SUPPORT: ${MAIL_SUPPORT} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION} + AWS_BUCKET: ${AWS_BUCKET} + AWS_ENDPOINT: ${AWS_ENDPOINT} + AWS_URL: ${AWS_URL} + AWS_USE_PATH_STYLE_ENDPOINT: ${AWS_USE_PATH_STYLE_ENDPOINT} + AWS_DIRECTORY: ${AWS_DIRECTORY} + VITE_APP_NAME: ${VITE_APP_NAME} + OPENAI_API_KEY: ${OPENAI_API_KEY} + OPENAI_ORGANIZATION: ${OPENAI_ORGANIZATION} + OPENAI_PROJECT: ${OPENAI_PROJECT} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} + ANTHROPIC_API_VERSION: ${ANTHROPIC_API_VERSION} + OLLAMA_URL: ${OLLAMA_URL} + DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} + SCOUT_DRIVER: ${SCOUT_DRIVER} + TYPESENSE_HOST: ${TYPESENSE_HOST} + TYPESENSE_PORT: ${TYPESENSE_PORT} + TYPESENSE_PROTOCOL: ${TYPESENSE_PROTOCOL} + TYPESENSE_API_KEY: ${TYPESENSE_API_KEY} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + STRIPE_VERSION: ${STRIPE_VERSION} + NOTCHPAY_PUBLIC_KEY: ${NOTCHPAY_PUBLIC_KEY} + NOTCHPAY_SECRET_KEY: ${NOTCHPAY_SECRET_KEY} + MARKDOWNX_GIPHY_API_KEY: ${MARKDOWNX_GIPHY_API_KEY} + TORCHLIGHT_TOKEN: ${TORCHLIGHT_TOKEN} + TORCHLIGHT_THEME: ${TORCHLIGHT_THEME} + TWITTER_CONSUMER_KEY: ${TWITTER_CONSUMER_KEY} + TWITTER_CONSUMER_SECRET: ${TWITTER_CONSUMER_SECRET} + TWITTER_ACCESS_TOKEN: ${TWITTER_ACCESS_TOKEN} + TWITTER_ACCESS_SECRET: ${TWITTER_ACCESS_SECRET} + TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} + TELEGRAM_CHANNEL: ${TELEGRAM_CHANNEL} + GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} + GITHUB_REDIRECT: ${GITHUB_REDIRECT} + GITHUB_FINE_GRAINED_TOKEN: ${GITHUB_FINE_GRAINED_TOKEN} + NIGHTWATCH_TOKEN: ${NIGHTWATCH_TOKEN} + NIGHTWATCH_REQUEST_SAMPLE_RATE: ${NIGHTWATCH_REQUEST_SAMPLE_RATE} + SSH_TUNNEL_USER: '${SSH_TUNNEL_USER:-}' + SSH_TUNNEL_HOSTNAME: '${SSH_TUNNEL_HOSTNAME:-}' + SSH_TUNNEL_PORT: '${SSH_TUNNEL_PORT:-22}' + SSH_TUNNEL_PRIVATE_KEY: '${SSH_TUNNEL_PRIVATE_KEY:-}' + SSH_TUNNEL_LOCAL_PORT: '${SSH_TUNNEL_LOCAL_PORT:-3307}' + SSH_TUNNEL_BIND_ADDRESS: '${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}' + SSH_TUNNEL_BIND_PORT: '${SSH_TUNNEL_BIND_PORT:-3306}' + SSH_TUNNEL_VERIFY_PROCESS: '${SSH_TUNNEL_VERIFY_PROCESS:-bash}' + DB_CONNECTION_SECOND: '${DB_CONNECTION_SECOND:-mysql}' + DB_HOST_SECOND: '${DB_HOST_SECOND:-127.0.0.1}' + DB_PORT_SECOND: '${DB_PORT_SECOND:-3307}' + DB_DATABASE_SECOND: '${DB_DATABASE_SECOND:-}' + DB_USERNAME_SECOND: '${DB_USERNAME_SECOND:-}' + DB_PASSWORD_SECOND: '${DB_PASSWORD_SECOND:-}' + OCTANE_SERVER: 'frankenphp' + CADDY_GLOBAL_OPTIONS: | + servers { + trusted_proxies static private_ranges + } + CADDY_SERVER_EXTRA_DIRECTIVES: | + @static { + file + path *.ico *.css *.js *.gif *.webp *.avif *.jpg *.jpeg *.png *.svg *.woff *.woff2 + } + header @static Cache-Control "public, max-age=31536000, s-maxage=31536000" + FRANKENPHP_CONFIG: | + worker { + file ./public/frankenphp-worker.php + } volumes: - - storage:/var/www/html/storage + - storage:/var/www/html/storage networks: - dokploy-network + schedule: + image: laravelcm/php + restart: unless-stopped + command: ['artisan', 'schedule:run'] + stop_signal: SIGTERM + healthcheck: + test: ['CMD', 'healthcheck-schedule'] + start_period: 10s + environment: + APP_NAME: ${APP_NAME} + APP_ENV: ${APP_ENV} + APP_KEY: ${APP_KEY} + APP_DEBUG: ${APP_DEBUG} + APP_DOMAIN: ${APP_DOMAIN} + APP_URL: ${APP_URL} + ASSET_URL: ${ASSET_URL} + APP_TIMEZONE: ${APP_TIMEZONE} + APP_LOCALE: ${APP_LOCALE} + APP_FALLBACK_LOCALE: ${APP_FALLBACK_LOCALE} + APP_MAINTENANCE_DRIVER: ${APP_MAINTENANCE_DRIVER} + BCRYPT_ROUNDS: ${BCRYPT_ROUNDS} + DB_CONNECTION: ${DB_CONNECTION} + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT} + DB_DATABASE: ${DB_DATABASE} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + LOG_CHANNEL: ${LOG_CHANNEL} + LOG_STACK: ${LOG_STACK} + LOG_DEPRECATIONS_CHANNEL: ${LOG_DEPRECATIONS_CHANNEL} + LOG_LEVEL: ${LOG_LEVEL} + SESSION_DRIVER: ${SESSION_DRIVER} + SESSION_LIFETIME: ${SESSION_LIFETIME} + SESSION_ENCRYPT: ${SESSION_ENCRYPT} + SESSION_PATH: ${SESSION_PATH} + SESSION_DOMAIN: ${SESSION_DOMAIN} + BROADCAST_CONNECTION: ${BROADCAST_CONNECTION} + FILESYSTEM_DISK: ${FILESYSTEM_DISK} + MEDIA_DISK: ${MEDIA_DISK} + FILAMENT_FILESYSTEM_DISK: ${MEDIA_DISK} + FILAMENT_PATH: ${FILAMENT_PATH} + QUEUE_CONNECTION: ${QUEUE_CONNECTION} + CACHE_STORE: ${CACHE_STORE} + CACHE_PREFIX: ${CACHE_PREFIX} + MEMCACHED_HOST: ${MEMCACHED_HOST} + REDIS_URL: ${REDIS_URL} + MAIL_MAILER: ${MAIL_MAILER} + MAIL_HOST: ${MAIL_HOST} + MAIL_PORT: ${MAIL_PORT} + MAIL_USERNAME: ${MAIL_USERNAME} + MAIL_PASSWORD: ${MAIL_PASSWORD} + MAIL_ENCRYPTION: ${MAIL_ENCRYPTION} + MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS} + MAIL_FROM_NAME: ${MAIL_FROM_NAME} + MAIL_SUPPORT: ${MAIL_SUPPORT} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION} + AWS_BUCKET: ${AWS_BUCKET} + AWS_ENDPOINT: ${AWS_ENDPOINT} + AWS_URL: ${AWS_URL} + AWS_USE_PATH_STYLE_ENDPOINT: ${AWS_USE_PATH_STYLE_ENDPOINT} + AWS_DIRECTORY: ${AWS_DIRECTORY} + VITE_APP_NAME: ${VITE_APP_NAME} + OPENAI_API_KEY: ${OPENAI_API_KEY} + OPENAI_ORGANIZATION: ${OPENAI_ORGANIZATION} + OPENAI_PROJECT: ${OPENAI_PROJECT} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} + ANTHROPIC_API_VERSION: ${ANTHROPIC_API_VERSION} + OLLAMA_URL: ${OLLAMA_URL} + DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} + SCOUT_DRIVER: ${SCOUT_DRIVER} + TYPESENSE_HOST: ${TYPESENSE_HOST} + TYPESENSE_PORT: ${TYPESENSE_PORT} + TYPESENSE_PROTOCOL: ${TYPESENSE_PROTOCOL} + TYPESENSE_API_KEY: ${TYPESENSE_API_KEY} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + STRIPE_VERSION: ${STRIPE_VERSION} + NOTCHPAY_PUBLIC_KEY: ${NOTCHPAY_PUBLIC_KEY} + NOTCHPAY_SECRET_KEY: ${NOTCHPAY_SECRET_KEY} + MARKDOWNX_GIPHY_API_KEY: ${MARKDOWNX_GIPHY_API_KEY} + TORCHLIGHT_TOKEN: ${TORCHLIGHT_TOKEN} + TORCHLIGHT_THEME: ${TORCHLIGHT_THEME} + TWITTER_CONSUMER_KEY: ${TWITTER_CONSUMER_KEY} + TWITTER_CONSUMER_SECRET: ${TWITTER_CONSUMER_SECRET} + TWITTER_ACCESS_TOKEN: ${TWITTER_ACCESS_TOKEN} + TWITTER_ACCESS_SECRET: ${TWITTER_ACCESS_SECRET} + TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} + TELEGRAM_CHANNEL: ${TELEGRAM_CHANNEL} + GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} + GITHUB_REDIRECT: ${GITHUB_REDIRECT} + GITHUB_FINE_GRAINED_TOKEN: ${GITHUB_FINE_GRAINED_TOKEN} + NIGHTWATCH_TOKEN: ${NIGHTWATCH_TOKEN} + NIGHTWATCH_REQUEST_SAMPLE_RATE: ${NIGHTWATCH_REQUEST_SAMPLE_RATE} + SSH_TUNNEL_USER: '${SSH_TUNNEL_USER:-}' + SSH_TUNNEL_HOSTNAME: '${SSH_TUNNEL_HOSTNAME:-}' + SSH_TUNNEL_PORT: '${SSH_TUNNEL_PORT:-22}' + SSH_TUNNEL_PRIVATE_KEY: '${SSH_TUNNEL_PRIVATE_KEY:-}' + SSH_TUNNEL_LOCAL_PORT: '${SSH_TUNNEL_LOCAL_PORT:-3307}' + SSH_TUNNEL_BIND_ADDRESS: '${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}' + SSH_TUNNEL_BIND_PORT: '${SSH_TUNNEL_BIND_PORT:-3306}' + SSH_TUNNEL_VERIFY_PROCESS: '${SSH_TUNNEL_VERIFY_PROCESS:-bash}' + DB_CONNECTION_SECOND: '${DB_CONNECTION_SECOND:-mysql}' + DB_HOST_SECOND: '${DB_HOST_SECOND:-127.0.0.1}' + DB_PORT_SECOND: '${DB_PORT_SECOND:-3307}' + DB_DATABASE_SECOND: '${DB_DATABASE_SECOND:-}' + DB_USERNAME_SECOND: '${DB_USERNAME_SECOND:-}' + DB_PASSWORD_SECOND: '${DB_PASSWORD_SECOND:-}' + OCTANE_SERVER: 'frankenphp' + CADDY_GLOBAL_OPTIONS: | + servers { + trusted_proxies static private_ranges + } + CADDY_SERVER_EXTRA_DIRECTIVES: | + @static { + file + path *.ico *.css *.js *.gif *.webp *.avif *.jpg *.jpeg *.png *.svg *.woff *.woff2 + } + header @static Cache-Control "public, max-age=31536000, s-maxage=31536000" + FRANKENPHP_CONFIG: | + worker { + file ./public/frankenphp-worker.php + } + volumes: + - storage:/var/www/html/storage + networks: + - dokploy-network + depends_on: + - universy-app + queue: + image: laravelcm/php + restart: unless-stopped + command: + - 'artisan' + - 'queue:listen' + - 'database' + - '--sleep=10' + - '--quiet' + - '--force' + - '--queue=default,media' + stop_signal: SIGTERM + healthcheck: + test: [ 'CMD', 'healthcheck-queue' ] + start_period: 10s + environment: + APP_NAME: ${APP_NAME} + APP_ENV: ${APP_ENV} + APP_KEY: ${APP_KEY} + APP_DEBUG: ${APP_DEBUG} + APP_DOMAIN: ${APP_DOMAIN} + APP_URL: ${APP_URL} + ASSET_URL: ${ASSET_URL} + APP_TIMEZONE: ${APP_TIMEZONE} + APP_LOCALE: ${APP_LOCALE} + APP_FALLBACK_LOCALE: ${APP_FALLBACK_LOCALE} + APP_MAINTENANCE_DRIVER: ${APP_MAINTENANCE_DRIVER} + BCRYPT_ROUNDS: ${BCRYPT_ROUNDS} + DB_CONNECTION: ${DB_CONNECTION} + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT} + DB_DATABASE: ${DB_DATABASE} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + LOG_CHANNEL: ${LOG_CHANNEL} + LOG_STACK: ${LOG_STACK} + LOG_DEPRECATIONS_CHANNEL: ${LOG_DEPRECATIONS_CHANNEL} + LOG_LEVEL: ${LOG_LEVEL} + SESSION_DRIVER: ${SESSION_DRIVER} + SESSION_LIFETIME: ${SESSION_LIFETIME} + SESSION_ENCRYPT: ${SESSION_ENCRYPT} + SESSION_PATH: ${SESSION_PATH} + SESSION_DOMAIN: ${SESSION_DOMAIN} + BROADCAST_CONNECTION: ${BROADCAST_CONNECTION} + FILESYSTEM_DISK: ${FILESYSTEM_DISK} + MEDIA_DISK: ${MEDIA_DISK} + FILAMENT_FILESYSTEM_DISK: ${MEDIA_DISK} + FILAMENT_PATH: ${FILAMENT_PATH} + QUEUE_CONNECTION: ${QUEUE_CONNECTION} + CACHE_STORE: ${CACHE_STORE} + CACHE_PREFIX: ${CACHE_PREFIX} + MEMCACHED_HOST: ${MEMCACHED_HOST} + REDIS_URL: ${REDIS_URL} + MAIL_MAILER: ${MAIL_MAILER} + MAIL_HOST: ${MAIL_HOST} + MAIL_PORT: ${MAIL_PORT} + MAIL_USERNAME: ${MAIL_USERNAME} + MAIL_PASSWORD: ${MAIL_PASSWORD} + MAIL_ENCRYPTION: ${MAIL_ENCRYPTION} + MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS} + MAIL_FROM_NAME: ${MAIL_FROM_NAME} + MAIL_SUPPORT: ${MAIL_SUPPORT} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION} + AWS_BUCKET: ${AWS_BUCKET} + AWS_ENDPOINT: ${AWS_ENDPOINT} + AWS_URL: ${AWS_URL} + AWS_USE_PATH_STYLE_ENDPOINT: ${AWS_USE_PATH_STYLE_ENDPOINT} + AWS_DIRECTORY: ${AWS_DIRECTORY} + VITE_APP_NAME: ${VITE_APP_NAME} + OPENAI_API_KEY: ${OPENAI_API_KEY} + OPENAI_ORGANIZATION: ${OPENAI_ORGANIZATION} + OPENAI_PROJECT: ${OPENAI_PROJECT} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} + ANTHROPIC_API_VERSION: ${ANTHROPIC_API_VERSION} + OLLAMA_URL: ${OLLAMA_URL} + DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} + SCOUT_DRIVER: ${SCOUT_DRIVER} + TYPESENSE_HOST: ${TYPESENSE_HOST} + TYPESENSE_PORT: ${TYPESENSE_PORT} + TYPESENSE_PROTOCOL: ${TYPESENSE_PROTOCOL} + TYPESENSE_API_KEY: ${TYPESENSE_API_KEY} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + STRIPE_VERSION: ${STRIPE_VERSION} + NOTCHPAY_PUBLIC_KEY: ${NOTCHPAY_PUBLIC_KEY} + NOTCHPAY_SECRET_KEY: ${NOTCHPAY_SECRET_KEY} + MARKDOWNX_GIPHY_API_KEY: ${MARKDOWNX_GIPHY_API_KEY} + TORCHLIGHT_TOKEN: ${TORCHLIGHT_TOKEN} + TORCHLIGHT_THEME: ${TORCHLIGHT_THEME} + TWITTER_CONSUMER_KEY: ${TWITTER_CONSUMER_KEY} + TWITTER_CONSUMER_SECRET: ${TWITTER_CONSUMER_SECRET} + TWITTER_ACCESS_TOKEN: ${TWITTER_ACCESS_TOKEN} + TWITTER_ACCESS_SECRET: ${TWITTER_ACCESS_SECRET} + TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} + TELEGRAM_CHANNEL: ${TELEGRAM_CHANNEL} + GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} + GITHUB_REDIRECT: ${GITHUB_REDIRECT} + GITHUB_FINE_GRAINED_TOKEN: ${GITHUB_FINE_GRAINED_TOKEN} + NIGHTWATCH_TOKEN: ${NIGHTWATCH_TOKEN} + NIGHTWATCH_REQUEST_SAMPLE_RATE: ${NIGHTWATCH_REQUEST_SAMPLE_RATE} + SSH_TUNNEL_USER: '${SSH_TUNNEL_USER:-}' + SSH_TUNNEL_HOSTNAME: '${SSH_TUNNEL_HOSTNAME:-}' + SSH_TUNNEL_PORT: '${SSH_TUNNEL_PORT:-22}' + SSH_TUNNEL_PRIVATE_KEY: '${SSH_TUNNEL_PRIVATE_KEY:-}' + SSH_TUNNEL_LOCAL_PORT: '${SSH_TUNNEL_LOCAL_PORT:-3307}' + SSH_TUNNEL_BIND_ADDRESS: '${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}' + SSH_TUNNEL_BIND_PORT: '${SSH_TUNNEL_BIND_PORT:-3306}' + SSH_TUNNEL_VERIFY_PROCESS: '${SSH_TUNNEL_VERIFY_PROCESS:-bash}' + DB_CONNECTION_SECOND: '${DB_CONNECTION_SECOND:-mysql}' + DB_HOST_SECOND: '${DB_HOST_SECOND:-127.0.0.1}' + DB_PORT_SECOND: '${DB_PORT_SECOND:-3307}' + DB_DATABASE_SECOND: '${DB_DATABASE_SECOND:-}' + DB_USERNAME_SECOND: '${DB_USERNAME_SECOND:-}' + DB_PASSWORD_SECOND: '${DB_PASSWORD_SECOND:-}' + OCTANE_SERVER: 'frankenphp' + CADDY_GLOBAL_OPTIONS: | + servers { + trusted_proxies static private_ranges + } + CADDY_SERVER_EXTRA_DIRECTIVES: | + @static { + file + path *.ico *.css *.js *.gif *.webp *.avif *.jpg *.jpeg *.png *.svg *.woff *.woff2 + } + header @static Cache-Control "public, max-age=31536000, s-maxage=31536000" + FRANKENPHP_CONFIG: | + worker { + file ./public/frankenphp-worker.php + } + volumes: + - storage:/var/www/html/storage + networks: + - dokploy-network + depends_on: + - laravelcm networks: dokploy-network: external: true diff --git a/docker-compose.yml b/docker-compose.yml index 1190d4b2..9bdc168d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,6 +47,20 @@ services: SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port='${APP_PORT:-80}'" XDG_CONFIG_HOME: /var/www/html/config XDG_DATA_HOME: /var/www/html/data + SSH_TUNNEL_USER: '${SSH_TUNNEL_USER:-}' + SSH_TUNNEL_HOSTNAME: '${SSH_TUNNEL_HOSTNAME:-}' + SSH_TUNNEL_PORT: '${SSH_TUNNEL_PORT:-22}' + SSH_TUNNEL_PRIVATE_KEY: '${SSH_TUNNEL_PRIVATE_KEY:-}' + SSH_TUNNEL_LOCAL_PORT: '${SSH_TUNNEL_LOCAL_PORT:-3307}' + SSH_TUNNEL_BIND_ADDRESS: '${SSH_TUNNEL_BIND_ADDRESS:-127.0.0.1}' + SSH_TUNNEL_BIND_PORT: '${SSH_TUNNEL_BIND_PORT:-3306}' + SSH_TUNNEL_VERIFY_PROCESS: '${SSH_TUNNEL_VERIFY_PROCESS:-bash}' + DB_CONNECTION_SECOND: '${DB_CONNECTION_SECOND:-mysql}' + DB_HOST_SECOND: '${DB_HOST_SECOND:-127.0.0.1}' + DB_PORT_SECOND: '${DB_PORT_SECOND:-3307}' + DB_DATABASE_SECOND: '${DB_DATABASE_SECOND:-}' + DB_USERNAME_SECOND: '${DB_USERNAME_SECOND:-}' + DB_PASSWORD_SECOND: '${DB_PASSWORD_SECOND:-}' healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:${APP_PORT:-8080}/up'] timeout: 30s diff --git a/rector.php b/rector.php index e2c334ff..424c0eab 100644 --- a/rector.php +++ b/rector.php @@ -7,7 +7,6 @@ use Rector\EarlyReturn\Rector\Return_\ReturnBinaryOrToEarlyReturnRector; use Rector\ValueObject\PhpVersion; use RectorLaravel\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector; -use RectorLaravel\Rector\Empty_\EmptyToBlankAndFilledFuncRector; use RectorLaravel\Rector\MethodCall\AssertStatusToAssertMethodRector; use RectorLaravel\Rector\StaticCall\DispatchToHelperFunctionsRector; use RectorLaravel\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector; @@ -32,7 +31,6 @@ AssertStatusToAssertMethodRector::class, DispatchToHelperFunctionsRector::class, EloquentMagicMethodToQueryBuilderRector::class, - EmptyToBlankAndFilledFuncRector::class, ]) ->withSkip([ __DIR__.'/app/Listeners',