#!/usr/local/bin/php -nc/usr/local/directadmin/plugins/borg-restore/php.ini '/opt/bb', 'BORG_VARIABLES_FILE' => '/opt/bb/bbvars.sh', 'ADMIN_MODE_ENABLE' => 'TRUE', 'RESULTS_PER_PAGE' => '10', 'DEBUG_MODE' => 'FALSE', ]; if (!is_readable($path)) { return $settings; } $lines = file($path, FILE_IGNORE_NEW_LINES); if ($lines === false) { return $settings; } foreach ($lines as $line) { $line = trim($line); if ($line === '' || $line[0] === '#') { continue; } if (preg_match('/^(BB_PATH|BORG_VARIABLES_FILE|ADMIN_MODE_ENABLE|RESULTS_PER_PAGE|DEBUG_MODE)=(.*)$/', $line, $m)) { $settings[$m[1]] = parse_sh_value($m[2]); } } return $settings; } function load_borg_vars(string $path): array { $vars = [ 'BORG_PASSPHRASE' => null, 'REPO' => null, ]; if (!is_readable($path)) { return $vars; } $lines = file($path, FILE_IGNORE_NEW_LINES); if ($lines === false) { return $vars; } foreach ($lines as $line) { $line = trim($line); if ($line === '' || $line[0] === '#') { continue; } if (preg_match('/^(?:export\\s+)?(BORG_PASSPHRASE|REPO)=(.*)$/', $line, $m)) { $vars[$m[1]] = parse_sh_value($m[2]); } } return $vars; } function load_user_skin(string $user): string { $path = '/usr/local/directadmin/data/users/' . $user . '/user.conf'; if (!is_readable($path)) { return 'enhanced'; } $lines = file($path, FILE_IGNORE_NEW_LINES); if ($lines === false) { return 'enhanced'; } foreach ($lines as $line) { $line = trim($line); if ($line === '' || $line[0] === '#') { continue; } if (preg_match('/^skin=(.+)$/', $line, $m)) { $skin = strtolower(trim($m[1])); return $skin !== '' ? $skin : 'enhanced'; } } return 'enhanced'; } function load_job_vars(string $path): array { $vars = [ 'DA_USER' => '', 'REPONAME' => '', 'USERRESTOREPATH' => '', 'TARGETPATH' => '', 'RESTOREMODE' => '', 'RESTORETYPE' => '', 'START_TS' => '', ]; if (!is_readable($path)) { return $vars; } $lines = file($path, FILE_IGNORE_NEW_LINES); if ($lines === false) { return $vars; } foreach ($lines as $line) { $line = trim($line); if ($line === '' || $line[0] === '#') { continue; } if (preg_match('/^(DA_USER|REPONAME|USERRESTOREPATH|TARGETPATH|RESTOREMODE|RESTORETYPE|START_TS|USERLANG)=(.*)$/', $line, $m)) { $vars[$m[1]] = parse_sh_value($m[2]); } } return $vars; } function job_id_from_file(string $file): string { $base = basename($file); if (substr($base, -3) === '.ok') { $base = substr($base, 0, -3); } elseif (substr($base, -5) === '.fail') { $base = substr($base, 0, -5); } elseif (substr($base, -7) === '.cancel') { $base = substr($base, 0, -7); } if (substr($base, -4) === '.env') { $base = substr($base, 0, -4); } return $base; } function find_job_file(string $job_base, string $file, string $job_id): string { $candidates = [ $job_base . '/processing/' . $file, $job_base . '/pending/' . $file, $job_base . '/done/' . $file, ]; if ($job_id !== '') { $candidates[] = $job_base . '/processing/' . $job_id . '.env'; $candidates[] = $job_base . '/pending/' . $job_id . '.env'; $candidates[] = $job_base . '/done/' . $job_id . '.ok'; $candidates[] = $job_base . '/done/' . $job_id . '.fail'; $candidates[] = $job_base . '/done/' . $job_id . '.cancel'; $candidates[] = $job_base . '/done/' . $job_id . '.env.ok'; $candidates[] = $job_base . '/done/' . $job_id . '.env.fail'; $candidates[] = $job_base . '/done/' . $job_id . '.env.cancel'; } foreach ($candidates as $path) { if (is_file($path)) { return $path; } } return ''; } function read_log_tail(string $path, int $max_bytes = 200000): string { if (!is_readable($path)) { return ''; } $size = filesize($path); if ($size === false || $size <= $max_bytes) { return (string)file_get_contents($path); } $fp = fopen($path, 'rb'); if ($fp === false) { return ''; } fseek($fp, -$max_bytes, SEEK_END); $data = fread($fp, $max_bytes); fclose($fp); return (string)$data; } function normalize_log(string $text): string { $text = str_replace("\r", "\n", $text); $lines = preg_split('/\n/', $text); if (!is_array($lines)) { return $text; } $filtered = []; foreach ($lines as $line) { $trimmed = trim($line); if ($trimmed !== '' && stripos($trimmed, 'Permanently added') !== false && stripos($trimmed, 'known host') !== false) { continue; } $filtered[] = $line; } return implode("\n", $filtered); } function last_non_empty_line(string $text): string { $lines = preg_split('/\r\n|\n|\r/', $text); if (!is_array($lines)) { return ''; } for ($i = count($lines) - 1; $i >= 0; $i--) { $line = trim((string)$lines[$i]); if ($line !== '') { return $line; } } return ''; } function is_calculating_line(string $line): bool { return (bool)preg_match('/calculat/i', $line); } function extract_progress(string $line): ?int { if (preg_match('/\b(\d{1,3})%\b/', $line, $m)) { $p = (int)$m[1]; if ($p < 0) { $p = 0; } elseif ($p > 100) { $p = 100; } return $p; } return null; } function get_job_status(string $job_id, string $job_base): string { if ($job_id === '') { return 'unknown'; } if (is_file($job_base . '/processing/' . $job_id . '.env')) { return 'processing'; } if (is_file($job_base . '/pending/' . $job_id . '.env')) { return 'pending'; } if (is_file($job_base . '/done/' . $job_id . '.ok') || is_file($job_base . '/done/' . $job_id . '.env.ok')) { return 'done'; } if (is_file($job_base . '/done/' . $job_id . '.fail') || is_file($job_base . '/done/' . $job_id . '.env.fail')) { return 'failed'; } if (is_file($job_base . '/done/' . $job_id . '.cancel') || is_file($job_base . '/done/' . $job_id . '.env.cancel')) { return 'canceled'; } return 'unknown'; } function display_status(string $base_status, string $last_line): string { if ($base_status === 'processing' && $last_line !== '' && is_calculating_line($last_line)) { return 'calculating'; } return $base_status; } function normalize_admin_status(string $status): string { if (in_array($status, ['pending', 'processing', 'calculating'], true)) { return 'processing'; } if ($status === 'done') { return 'done'; } if (in_array($status, ['failed', 'canceled'], true)) { return 'failed'; } return 'failed'; } function admin_status_label(string $status): string { if ($status === 'done') { return 'Zakończone'; } if ($status === 'processing') { return 'W trakcie'; } return 'Błąd'; } function admin_status_sort(string $status): int { if ($status === 'processing') { return 1; } if ($status === 'done') { return 2; } return 3; } function restore_mode_label(string $mode): string { if ($mode === 'overwrite') { return 'Nadpisz aktualne dane'; } if ($mode === 'update') { return 'Scalenie'; } if ($mode === 'location') { return 'Przywracanie do lokalizacji'; } return '-'; } function restore_type_label(string $type): string { if ($type === 'mail') { return 'Zawartość skrzynki pocztowej'; } if ($type === 'web') { return 'Pliki www'; } return '-'; } function display_restore_path(string $value): string { $trimmed = trim($value); if ($trimmed === '') { return '-'; } if ($trimmed[0] !== '/') { $trimmed = '/' . $trimmed; } return $trimmed; } function log_missing_include_explanation(string $log_text, string $restore_path_label = ''): string { if (stripos($log_text, 'W przywracanej kopii zapasowej nie odnaleziono wskazanej ścieżki') !== false) { return ''; } if (stripos($log_text, 'The specified restore path was not found in the backup') !== false) { return ''; } if (preg_match("/Include pattern '.*' never matched\\./i", $log_text)) { $suffix = ''; if ($restore_path_label !== '' && $restore_path_label !== '-') { $suffix = ' (' . $restore_path_label . ')'; } return 'W przywracanej kopii zapasowej nie odnaleziono wskazanej ścieżki' . $suffix . '. Prosimy spróbować przywrócić dane z innego archiwum, w którym te pliki mogły się znajdować.'; } return ''; } function collect_jobs(string $job_base, string $log_dir): array { $rows = []; $sources = [ ['dir' => $job_base . '/pending', 'pattern' => '*.env', 'status' => 'pending'], ['dir' => $job_base . '/processing', 'pattern' => '*.env', 'status' => 'processing'], ['dir' => $job_base . '/done', 'pattern' => '*.ok', 'status' => 'done'], ['dir' => $job_base . '/done', 'pattern' => '*.env.ok', 'status' => 'done'], ['dir' => $job_base . '/done', 'pattern' => '*.fail', 'status' => 'failed'], ['dir' => $job_base . '/done', 'pattern' => '*.env.fail', 'status' => 'failed'], ['dir' => $job_base . '/done', 'pattern' => '*.cancel', 'status' => 'canceled'], ['dir' => $job_base . '/done', 'pattern' => '*.env.cancel', 'status' => 'canceled'], ]; foreach ($sources as $source) { foreach (glob($source['dir'] . '/' . $source['pattern']) ?: [] as $path) { $file = basename($path); if (isset($rows[$file])) { continue; } $vars = load_job_vars($path); $job_id = job_id_from_file($file); if ($job_id === '') { continue; } $log_path = $log_dir . '/' . $job_id . '.log'; $base_status = $source['status']; if ($base_status === 'processing' && is_readable($log_path)) { $tail = normalize_log(read_log_tail($log_path, 20000)); $last = last_non_empty_line($tail); $base_status = display_status($base_status, $last); } $status = normalize_admin_status($base_status); $start_ts = is_numeric($vars['START_TS'] ?? '') ? (int)$vars['START_TS'] : (int)(@filemtime($path) ?: 0); if ($start_ts <= 0) { $start_ts = time(); } $rows[$file] = [ 'file' => $file, 'job_id' => $job_id, 'user' => (string)($vars['DA_USER'] ?? ''), 'start_ts' => $start_ts, 'start_text' => date('Y-m-d H:i:s', $start_ts), 'type' => (string)($vars['RESTORETYPE'] ?? ''), 'type_label' => restore_type_label((string)($vars['RESTORETYPE'] ?? '')), 'mode' => (string)($vars['RESTOREMODE'] ?? ''), 'mode_label' => restore_mode_label((string)($vars['RESTOREMODE'] ?? '')), 'status' => $status, 'status_label' => admin_status_label($status), 'status_sort' => admin_status_sort($status), ]; } } return array_values($rows); } function compare_rows(array $a, array $b, string $sort, string $dir): int { $cmp = 0; switch ($sort) { case 'user': $cmp = strcasecmp((string)$a['user'], (string)$b['user']); break; case 'type': $cmp = strcasecmp((string)$a['type_label'], (string)$b['type_label']); break; case 'mode': $cmp = strcasecmp((string)$a['mode_label'], (string)$b['mode_label']); break; case 'status': $cmp = ((int)$a['status_sort']) <=> ((int)$b['status_sort']); break; case 'start': default: $cmp = ((int)$a['start_ts']) <=> ((int)$b['start_ts']); break; } if ($cmp === 0) { $cmp = ((int)$a['start_ts']) <=> ((int)$b['start_ts']); } if ($cmp === 0) { $cmp = strcmp((string)$a['job_id'], (string)$b['job_id']); } return $dir === 'asc' ? $cmp : -$cmp; } function build_admin_url(array $base, array $override = []): string { $query = array_merge($base, $override); foreach ($query as $key => $value) { if ($value === null || $value === '') { unset($query[$key]); } } $qs = http_build_query($query); return '/CMD_PLUGINS_ADMIN/borg-restore/index.html' . ($qs !== '' ? ('?' . $qs) : ''); } $params = read_params(); $action = (string)($params['action'] ?? ''); $username = getenv('USERNAME'); if ($username === false || $username === '') { $username = getenv('LOGNAME') ?: getenv('USER') ?: 'admin'; } $plugin_dir = realpath(__DIR__ . '/..') ?: '/usr/local/directadmin/plugins/borg-restore'; $settings_path = $plugin_dir . '/plugin-settings.conf'; $settings = load_settings($settings_path); $admin_mode_enabled = parse_bool((string)($settings['ADMIN_MODE_ENABLE'] ?? 'TRUE'), true); $debug_mode = parse_bool((string)($settings['DEBUG_MODE'] ?? 'FALSE'), false); $bb_path = trim((string)($settings['BB_PATH'] ?? '/opt/bb')); $borg_vars_file = trim((string)($settings['BORG_VARIABLES_FILE'] ?? '/opt/bb/bbvars.sh')); $bbvars_path = $borg_vars_file !== '' ? $borg_vars_file : $bb_path; if ($bbvars_path !== '' && substr($bbvars_path, -3) !== '.sh') { $bbvars_path = rtrim($bbvars_path, '/') . '/bbvars.sh'; } $bbvars = load_borg_vars($bbvars_path); $admin_missing_passphrase = $debug_mode && is_readable($bbvars_path) && trim((string)($bbvars['BORG_PASSPHRASE'] ?? '')) === ''; $results_per_page = (int)trim((string)($settings['RESULTS_PER_PAGE'] ?? '10')); if ($results_per_page <= 0) { $results_per_page = 10; } if ($results_per_page > 200) { $results_per_page = 200; } $skin = load_user_skin($username); $css_file = $skin === 'evolution' ? 'evolution.css' : 'enhanced.css'; $inline_css = ''; $css_path = $plugin_dir . '/user/' . $css_file; if (is_readable($css_path)) { $inline_css = (string)file_get_contents($css_path); } $job_base = '/usr/local/directadmin/plugins/borg-restore/data/jobs'; $log_dir = '/usr/local/directadmin/plugins/borg-restore/data/logs'; $allowed_sorts = ['user', 'start', 'type', 'mode', 'status']; $sort = strtolower((string)($params['sort'] ?? 'start')); if (!in_array($sort, $allowed_sorts, true)) { $sort = 'start'; } $dir = strtolower((string)($params['dir'] ?? 'desc')) === 'asc' ? 'asc' : 'desc'; $page = (int)($params['page'] ?? 1); if ($page < 1) { $page = 1; } $base_link_params = ['sort' => $sort, 'dir' => $dir, 'page' => $page]; $jobs = []; $total = 0; $total_pages = 1; $page_start = 0; $page_end = 0; $page_jobs = []; if ($admin_mode_enabled) { $jobs = collect_jobs($job_base, $log_dir); usort($jobs, static function (array $a, array $b) use ($sort, $dir): int { return compare_rows($a, $b, $sort, $dir); }); $total = count($jobs); $total_pages = max(1, (int)ceil($total / $results_per_page)); if ($page > $total_pages) { $page = $total_pages; $base_link_params['page'] = $page; } $offset = ($page - 1) * $results_per_page; $page_jobs = array_slice($jobs, $offset, $results_per_page); if ($total > 0) { $page_start = $offset + 1; $page_end = min($offset + count($page_jobs), $total); } } $view_log = null; if ($admin_mode_enabled && $action === 'view_log') { $file = basename((string)($params['job'] ?? '')); if ($file !== '' && preg_match('/^[-A-Za-z0-9_.]+$/', $file)) { $job_id = job_id_from_file($file); $job_file_path = find_job_file($job_base, $file, $job_id); if ($job_file_path !== '') { $vars = load_job_vars($job_file_path); $log_path = $log_dir . '/' . $job_id . '.log'; $log_message = is_readable($log_path) ? normalize_log((string)file_get_contents($log_path)) : 'Log nie istnieje.'; $restore_path_label = display_restore_path((string)($vars['USERRESTOREPATH'] ?? '')); $explanation = log_missing_include_explanation($log_message, $restore_path_label); if ($explanation !== '') { $log_message = rtrim($log_message) . "\n\n" . $explanation; } $base_status = get_job_status($job_id, $job_base); $last_line = last_non_empty_line($log_message); $progress = extract_progress($last_line); $display = display_status($base_status, $last_line); $status = normalize_admin_status($display); if ($status === 'done') { $progress_text = '100%'; } elseif ($status === 'failed') { $progress_text = '0%'; } else { $progress_text = $progress !== null ? ($progress . '%') : ($last_line !== '' ? $last_line : '-'); } $start_ts = is_numeric($vars['START_TS'] ?? '') ? (int)$vars['START_TS'] : (int)(@filemtime($job_file_path) ?: 0); if ($start_ts <= 0) { $start_ts = time(); } $view_log = [ 'job' => $file, 'user' => (string)($vars['DA_USER'] ?? '-'), 'start' => date('Y-m-d H:i:s', $start_ts), 'type' => restore_type_label((string)($vars['RESTORETYPE'] ?? '')), 'mode' => restore_mode_label((string)($vars['RESTOREMODE'] ?? '')), 'path' => $restore_path_label, 'target' => display_restore_path((string)($vars['TARGETPATH'] ?? '')), 'status' => admin_status_label($status), 'progress' => $progress_text, 'content' => $log_message, ]; } else { $view_log = [ 'error' => 'Nie znaleziono wskazanego zadania.', ]; } } else { $view_log = [ 'error' => 'Nieprawidłowy identyfikator zadania.', ]; } } $sort_labels = [ 'user' => 'Nazwa użytkownika', 'start' => 'Data rozpoczęcia', 'type' => 'Rodzaj przywracanych danych', 'mode' => 'Tryb przywracania', 'status' => 'Status', ]; $sort_arrow = static function (string $field) use ($sort, $dir): string { if ($field !== $sort) { return ''; } return $dir === 'asc' ? ' ▲' : ' ▼'; }; $sort_link = static function (string $field) use ($sort, $dir, $base_link_params): string { $next_dir = ($sort === $field && $dir === 'asc') ? 'desc' : 'asc'; return build_admin_url($base_link_params, ['sort' => $field, 'dir' => $next_dir, 'page' => 1]); }; $view_back_url = build_admin_url($base_link_params, ['action' => null, 'job' => null]); ?>
Wyniki na stronę:
Brak operacji do wyświetlenia.
| Logi | |||||
|---|---|---|---|---|---|
| Pokaż logi |