[ '.dep/repo', '.git', '.env', 'deploy.php', 'deploy.lock', 'current', 'shared/', 'var/*', ], 'exclude_upload' => [ '.dep/repo/', 'deploy.lock', 'current', 'shared/', ], 'exclude-file' => false, 'include' => [ 'var/', ], 'include-file' => false, 'filter' => [], 'filter-file' => false, 'filter-perdir' => false, 'flags' => 'rzlc', 'options' => ['delete', 'delete-after'], 'timeout' => 600, ]; protected array $tasks = [ 'rsync:warmup', 'rsync:remote', 'rsync:switch_current', ]; protected function setDefaultConfiguration(): void { $this->set('port', 22); $this->set('rsync', []); $this->set('rsync_default', $this->defaultConfig); $this->set('rsync_dest', '{{user}}@{{hostname}}:\'{{remote_path}}/\''); $this->set('rsync_config', $this->rsyncConfig(...)); $this->set('rsync_flags', $this->rsyncFlags(...)); $this->set('rsync_excludes', $this->rsyncExcludes(...)); $this->set('rsync_excludes_download', $this->rsyncExcludesDownload(...)); $this->set('rsync_excludes_upload', $this->rsyncExcludesUpload(...)); $this->set('rsync_includes', $this->rsyncIncludes(...)); $this->set('rsync_filter', $this->rsyncFilter(...)); $this->set('rsync_options', $this->rsyncOptions(...)); $this->set('rsync_timeout', $this->rsyncTimeout(...)); } /** * Warmup remote Rsync target */ public function warmup(): void { if (!testLocally('[ -d "{{deploy_path}}" ]')) { runLocally('mkdir -p {{deploy_path}}'); } if (testLocally('[ -d "{{deploy_path}}" ]')) { $config = $this->get('rsync_config'); $identityFile = $this->get('identity_file') ? ' -i ' . $this->get('identity_file') : ''; runLocally( ' rsync \ -e "ssh -p {{port}}' . $identityFile . '" \ {{rsync_flags}} \ {{rsync_options}} \ {{rsync_timeout}} \ --exclude=' . escapeshellarg('shared') . ' \ {{rsync_excludes_download}} \ {{rsync_includes}} \ {{rsync_filter}} \ {{rsync_dest}} {{deploy_path}} ', $config ); } else { warning('No destination folder found.'); } } /** * Rsync local->remote */ public function remote(): void { if (test('[ -d "{{remote_path}}" ]')) { $config = $this->get('rsync_config'); $identityFile = $this->get('identity_file') ? ' -i ' . $this->get('identity_file') : ''; runLocally( ' rsync \ -e "ssh -p {{port}}' . $identityFile . '" \ {{rsync_flags}} \ {{rsync_options}} \ {{rsync_timeout}} \ {{rsync_includes}} \ --exclude=' . escapeshellarg('shared/') . ' \ {{rsync_excludes_upload}} \ {{rsync_filter}} \ {{deploy_path}}/ {{rsync_dest}} ', $config ); } else { warning('No destination folder found.'); } } /** * Sync current after release was uploaded */ public function switchCurrent(): void { $config = $this->get('rsync_config'); $identityFile = $this->get('identity_file') ? ' -i ' . $this->get('identity_file') : ''; runLocally( ' rsync \ -e "ssh -p {{port}}' . $identityFile . '" \ {{rsync_flags}} \ {{rsync_options}} \ {{deploy_path}}/current {{rsync_dest}} ', $config ); } public function rsyncConfig(): array { $default = $this->get('rsync_default'); $config = $this->get('rsync'); return Rsync::arrayMergeRecursiveDistinct($default, $config); } public function rsyncFlags(): string { $config = $this->get('rsync_config'); $flags = $config['flags'] ?? ''; return $flags ? ' -' . $flags : ''; } public function rsyncExcludes(): string { $config = $this->get('rsync_config'); $excludes = $config['exclude'] ?? []; $excludeFile = $config['exclude-file'] ?? ''; $excludesRsync = ''; foreach ($excludes as $exclude) { $excludesRsync .= ' --exclude=' . escapeshellarg($exclude); } if ( !empty($excludeFile) && file_exists($excludeFile) && is_file($excludeFile) && is_readable($excludeFile) ) { $excludesRsync .= ' --exclude-from=' . escapeshellarg($excludeFile); } return $excludesRsync; } public function rsyncExcludesDownload(): string { $config = $this->get('rsync_config'); $excludes = $config['exclude_download'] ?? $config['exclude'] ?? []; $excludeFile = $config['exclude-file'] ?? ''; $excludesRsync = ''; foreach ($excludes as $exclude) { $excludesRsync .= ' --exclude=' . escapeshellarg($exclude); } if ( !empty($excludeFile) && file_exists($excludeFile) && is_file($excludeFile) && is_readable($excludeFile) ) { $excludesRsync .= ' --exclude-from=' . escapeshellarg($excludeFile); } return $excludesRsync; } public function rsyncExcludesUpload(): string { $config = $this->get('rsync_config'); $excludes = $config['exclude_upload'] ?? $config['exclude'] ?? []; $excludeFile = $config['exclude-file'] ?? ''; $excludesRsync = ''; foreach ($excludes as $exclude) { $excludesRsync .= ' --exclude=' . escapeshellarg($exclude); } if ( !empty($excludeFile) && file_exists($excludeFile) && is_file($excludeFile) && is_readable($excludeFile) ) { $excludesRsync .= ' --exclude-from=' . escapeshellarg($excludeFile); } return $excludesRsync; } public function rsyncIncludes(): string { $config = $this->get('rsync_config'); $includes = $config['include'] ?? []; $includeFile = $config['include-file'] ?? ''; $includesRsync = ''; foreach ($includes as $include) { $includesRsync .= ' --include=' . escapeshellarg($include); } if ( !empty($includeFile) && file_exists($includeFile) && is_file($includeFile) && is_readable($includeFile) ) { $includesRsync .= ' --include-from=' . escapeshellarg($includeFile); } return $includesRsync; } public function rsyncFilter(): string { $config = $this->get('rsync_config'); $filters = $config['filter'] ?? []; $filterFile = $config['filter-file'] ?? ''; $filterPerDir = $config['filter-perdir'] ?? ''; $filtersRsync = ''; foreach ($filters as $filter) { $filtersRsync .= " --filter='$filter'"; } if (!empty($filterFile)) { $filtersRsync .= " --filter='merge $filterFile'"; } if (!empty($filterPerDir)) { $filtersRsync .= " --filter='dir-merge $filterPerDir'"; } return $filtersRsync; } public function rsyncOptions(): string { $config = $this->get('rsync_config'); $options = $config['options'] ?? []; $optionsRsync = []; foreach ($options as $option) { $optionsRsync[] = '--' . $option; } return ' ' . implode(' ', $optionsRsync); } public function rsyncTimeout(): string { $config = $this->get('rsync_config'); $timeout = $config['timeout'] ?? 0; return $timeout ? ' --timeout=' . $timeout : ''; } public static function arrayMergeRecursiveDistinct(array $array1, array &$array2): array { $merged = $array1; foreach ($array2 as $key => &$value) { if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { $merged[$key] = self::arrayMergeRecursiveDistinct($merged[$key], $value); } else { $merged[$key] = $value; } } return $merged; } }