diff --git a/include/nc_connector_scheduler.inc.php b/include/nc_connector_scheduler.inc.php new file mode 100644 index 0000000..b07577d --- /dev/null +++ b/include/nc_connector_scheduler.inc.php @@ -0,0 +1,165 @@ +$base, + 'runtime'=>$base.'/nc-connector-runtime', + 'state_root'=>$base.'/nc-connector-state', + 'scheduler'=>$base.'/nc-connector-scheduler', + 'state'=>$base.'/nc-connector-scheduler/state.json', + 'trigger_lock'=>$base.'/nc-connector-scheduler/trigger.lock', + 'worker_lock'=>$base.'/nc-connector-scheduler/worker.lock', + 'log'=>$base.'/nc-connector-scheduler/last-worker.log', + ); +} + +function bratonien_tools_nc_scheduler_interval() +{ + global $conf; + $interval = isset($conf['bratonien_nc_scheduler_interval']) ? (int)$conf['bratonien_nc_scheduler_interval'] : 60; + return max(60, $interval); +} + +function bratonien_tools_nc_scheduler_ensure_dirs() +{ + foreach (bratonien_tools_nc_scheduler_paths() as $key=>$path) + { + if (in_array($key, array('state','trigger_lock','worker_lock','log'), true)) continue; + if (!is_dir($path) && !@mkdir($path, 0750, true) && !is_dir($path)) + { + return false; + } + } + return true; +} + +function bratonien_tools_nc_scheduler_read_state() +{ + $paths = bratonien_tools_nc_scheduler_paths(); + if (!is_readable($paths['state'])) return array(); + $decoded = json_decode((string)@file_get_contents($paths['state']), true); + return is_array($decoded) ? $decoded : array(); +} + +function bratonien_tools_nc_scheduler_write_state(array $state) +{ + $paths = bratonien_tools_nc_scheduler_paths(); + if (!bratonien_tools_nc_scheduler_ensure_dirs()) return false; + $json = json_encode($state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + if (!is_string($json)) return false; + $tmp = $paths['state'].'.tmp'; + if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) return false; + @chmod($tmp, 0640); + return @rename($tmp, $paths['state']); +} + +function bratonien_tools_nc_scheduler_install() +{ + if (!bratonien_tools_nc_scheduler_ensure_dirs()) return false; + $state = bratonien_tools_nc_scheduler_read_state(); + if (empty($state['next_due'])) + { + $state['next_due'] = time(); + $state['enabled'] = true; + $state['mode'] = 'piwigo-native'; + bratonien_tools_nc_scheduler_write_state($state); + } + return true; +} + +function bratonien_tools_nc_scheduler_php_binary() +{ + foreach (array('/usr/bin/php', '/usr/local/bin/php', PHP_BINARY) as $candidate) + { + if (is_string($candidate) && $candidate !== '' && is_executable($candidate)) return $candidate; + } + return ''; +} + +function bratonien_tools_nc_scheduler_spawn($force = false) +{ + $paths = bratonien_tools_nc_scheduler_paths(); + if (!bratonien_tools_nc_scheduler_ensure_dirs()) + { + throw new RuntimeException('Der native NC-Scheduler kann sein Laufzeitverzeichnis nicht anlegen.'); + } + + $lock = @fopen($paths['trigger_lock'], 'c+'); + if (!is_resource($lock) || !@flock($lock, LOCK_EX | LOCK_NB)) + { + if (is_resource($lock)) fclose($lock); + return array('started'=>false, 'message'=>'Ein NC-Abgleich wird bereits vorbereitet.'); + } + + $state = bratonien_tools_nc_scheduler_read_state(); + $now = time(); + $next_due = (int)($state['next_due'] ?? 0); + if (!$force && $next_due > $now) + { + @flock($lock, LOCK_UN); + fclose($lock); + return array('started'=>false, 'message'=>'Der nächste NC-Abgleich ist noch nicht fällig.'); + } + + $php = bratonien_tools_nc_scheduler_php_binary(); + if ($php === '') + { + @flock($lock, LOCK_UN); + fclose($lock); + throw new RuntimeException('Kein ausführbares PHP-CLI für den nativen NC-Scheduler gefunden.'); + } + + $state['enabled'] = true; + $state['mode'] = 'piwigo-native'; + $state['queued_at'] = $now; + $state['next_due'] = $now + bratonien_tools_nc_scheduler_interval(); + bratonien_tools_nc_scheduler_write_state($state); + + $runner = BRATONIEN_TOOLS_PATH.'runtime/native-runner.php'; + $command = escapeshellarg($php).' '.escapeshellarg($runner).' >> '.escapeshellarg($paths['log']).' 2>&1 &'; + $spec = array( + 0=>array('file','/dev/null','r'), + 1=>array('file','/dev/null','a'), + 2=>array('file','/dev/null','a'), + ); + $process = function_exists('proc_open') ? @proc_open(array('/bin/sh','-c',$command), $spec, $pipes) : false; + $exit = is_resource($process) ? proc_close($process) : 1; + + @flock($lock, LOCK_UN); + fclose($lock); + + if ($exit !== 0) + { + throw new RuntimeException('Der native NC-Abgleich konnte nicht gestartet werden.'); + } + + return array('started'=>true, 'message'=>'NC-Abgleich wurde gestartet.'); +} + +function bratonien_tools_nc_scheduler_tick() +{ + $state = bratonien_tools_nc_scheduler_read_state(); + if (isset($state['enabled']) && !$state['enabled']) return; + $next_due = (int)($state['next_due'] ?? 0); + if ($next_due > time()) return; + + try + { + bratonien_tools_nc_scheduler_spawn(false); + } + catch (Throwable $e) + { + $state = bratonien_tools_nc_scheduler_read_state(); + $state['state'] = 'error'; + $state['message'] = $e->getMessage(); + $state['timestamp'] = time(); + $state['next_due'] = time() + bratonien_tools_nc_scheduler_interval(); + bratonien_tools_nc_scheduler_write_state($state); + } +} diff --git a/include/nc_connector_system.inc.php b/include/nc_connector_system.inc.php index dda464e..caa2138 100644 --- a/include/nc_connector_system.inc.php +++ b/include/nc_connector_system.inc.php @@ -4,109 +4,13 @@ if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!'); } -function bratonien_tools_nc_connector_systemctl_value(array $args) -{ - if (!function_exists('proc_open')) - { - return ''; - } - - $command = array_merge(array('/usr/bin/systemctl'), $args); - $spec = array( - 0 => array('file', '/dev/null', 'r'), - 1 => array('pipe', 'w'), - 2 => array('pipe', 'w'), - ); - $environment = array_merge($_ENV, array('LC_ALL'=>'C', 'LANG'=>'C')); - $process = @proc_open($command, $spec, $pipes, null, $environment); - if (!is_resource($process)) - { - return ''; - } - $stdout = stream_get_contents($pipes[1]); - stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - $exit = proc_close($process); - return $exit === 0 ? trim((string)$stdout) : ''; -} - -function bratonien_tools_nc_connector_parse_systemd_time($value) -{ - $value = trim((string)$value); - if ($value === '' || strtolower($value) === 'n/a') - { - return 0; - } - $parsed = strtotime($value); - return $parsed === false ? 0 : (int)$parsed; -} - -function bratonien_tools_nc_connector_monotonic_to_timestamp($value) -{ - $value = trim((string)$value); - if ($value === '' || $value === '0' || strtolower($value) === 'n/a') - { - return 0; - } - - if (preg_match('/^([0-9]+)(?:us)?$/', $value, $matches)) - { - $next_boot_seconds = ((float)$matches[1]) / 1000000; - } - else - { - return 0; - } - - $uptime_raw = @file_get_contents('/proc/uptime'); - if (!is_string($uptime_raw) || !preg_match('/^([0-9]+(?:\.[0-9]+)?)/', trim($uptime_raw), $uptime_match)) - { - return 0; - } - - $remaining = $next_boot_seconds - (float)$uptime_match[1]; - if ($remaining < -1) - { - return 0; - } - - return (int)round(time() + max(0, $remaining)); -} - -function bratonien_tools_nc_connector_next_from_timer_list($timer) -{ - $line = bratonien_tools_nc_connector_systemctl_value(array( - 'list-timers', '--all', '--no-pager', '--no-legend', $timer, - )); - if ($line === '') - { - return 0; - } - - $first_line = trim((string)strtok($line, "\n")); - if (preg_match('/^(\S+\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+\S+)/', $first_line, $matches)) - { - $parsed = strtotime($matches[1]); - return $parsed === false ? 0 : (int)$parsed; - } - - return 0; -} +require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_scheduler.inc.php'); function bratonien_tools_nc_connector_connection_last_status(array $connection) { $empty = array( - 'timestamp'=>0, - 'label'=>'Nicht verfügbar', - 'state'=>'', - 'message'=>'', - 'auth_mode'=>'', - 'api_state'=>'', - 'api_message'=>'', - 'fallback_state'=>'', - 'fallback_message'=>'', - 'error_detail'=>'', + 'timestamp'=>0,'label'=>'Nicht verfügbar','state'=>'','message'=>'','auth_mode'=>'', + 'api_state'=>'','api_message'=>'','fallback_state'=>'','fallback_message'=>'','error_detail'=>'', ); $connection_id = (int)($connection['id'] ?? 0); @@ -114,42 +18,25 @@ function bratonien_tools_nc_connector_connection_last_status(array $connection) if ($connection_id > 0) { $candidates[] = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools/nc-connector-status/connection-'.$connection_id.'.json'; + $candidates[] = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools/nc-connector-state/connection-'.$connection_id.'/connector-status.json'; } $config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array(); $state_dir = rtrim((string)($config['state_dir'] ?? ''), '/'); - if ($state_dir === '' && $connection_id > 0) - { - $state_dir = '/var/lib/bratonien-tools/nc-connector/connection-'.$connection_id; - } - if ($state_dir !== '') - { - $candidates[] = $state_dir.'/connector-status.json'; - } + if ($state_dir !== '') $candidates[] = $state_dir.'/connector-status.json'; $decoded = null; - foreach ($candidates as $candidate) + foreach (array_unique($candidates) as $candidate) { - if (!is_readable($candidate)) - { - continue; - } + if (!is_readable($candidate)) continue; $value = json_decode((string)@file_get_contents($candidate), true); - if (is_array($value)) - { - $decoded = $value; - break; - } - } - if (!is_array($decoded)) - { - return $empty; + if (is_array($value)) { $decoded = $value; break; } } + if (!is_array($decoded)) return $empty; $timestamp = (int)($decoded['timestamp'] ?? 0); $api = isset($decoded['api']) && is_array($decoded['api']) ? $decoded['api'] : array(); $fallback = isset($decoded['fallback']) && is_array($decoded['fallback']) ? $decoded['fallback'] : array(); - return array( 'timestamp'=>$timestamp, 'label'=>$timestamp > 0 ? date('d.m.Y H:i:s', $timestamp) : 'Nicht verfügbar', @@ -166,99 +53,69 @@ function bratonien_tools_nc_connector_connection_last_status(array $connection) function bratonien_tools_nc_connector_last_status(array $connections) { - $latest = array('timestamp'=>0, 'state'=>'', 'message'=>'', 'auth_mode'=>'', 'api_state'=>'', 'api_message'=>'', 'fallback_state'=>'', 'fallback_message'=>'', 'error_detail'=>''); - + $latest = array('timestamp'=>0,'state'=>'','message'=>'','auth_mode'=>'','api_state'=>'','api_message'=>'','fallback_state'=>'','fallback_message'=>'','error_detail'=>''); foreach ($connections as $connection) { - if (empty($connection['enabled'])) - { - continue; - } - + if (empty($connection['enabled'])) continue; $status = bratonien_tools_nc_connector_connection_last_status($connection); - if ((int)$status['timestamp'] >= (int)$latest['timestamp']) - { - $latest = $status; - } + if ((int)$status['timestamp'] >= (int)$latest['timestamp']) $latest = $status; } - return $latest; } function bratonien_tools_nc_connector_system_status(array $connections = array()) { - $timer = 'bratonien-nc-connector.timer'; - $service = 'bratonien-nc-connector.service'; - - $active = bratonien_tools_nc_connector_systemctl_value(array('is-active', $timer)); - $enabled = bratonien_tools_nc_connector_systemctl_value(array('is-enabled', $timer)); - $service_active_raw = bratonien_tools_nc_connector_systemctl_value(array('is-active', $service)); - $service_active = in_array($service_active_raw, array('active', 'activating'), true); - $service_started_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $service, '--property=ActiveEnterTimestamp', '--value')); - $service_started_timestamp = bratonien_tools_nc_connector_parse_systemd_time($service_started_raw); - - $next_realtime_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $timer, '--property=NextElapseUSecRealtime', '--value')); - $next_timestamp = bratonien_tools_nc_connector_parse_systemd_time($next_realtime_raw); - - if ($next_timestamp <= 0) - { - $next_monotonic_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $timer, '--property=NextElapseUSecMonotonic', '--value')); - $next_timestamp = bratonien_tools_nc_connector_monotonic_to_timestamp($next_monotonic_raw); - } - - if ($next_timestamp <= 0) - { - $next_timestamp = bratonien_tools_nc_connector_next_from_timer_list($timer); - } - + $scheduler = bratonien_tools_nc_scheduler_read_state(); + $enabled = !isset($scheduler['enabled']) || !empty($scheduler['enabled']); + $running = (string)($scheduler['state'] ?? '') === 'running'; + $started = (int)($scheduler['started_at'] ?? 0); + $next = (int)($scheduler['next_due'] ?? 0); $last = bratonien_tools_nc_connector_last_status($connections); - if ($last['timestamp'] <= 0) - { - $last_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $service, '--property=ExecMainExitTimestamp', '--value')); - $last['timestamp'] = bratonien_tools_nc_connector_parse_systemd_time($last_raw); - } - if ($next_timestamp > 0) + if ($next > 0) { - $next_label = date('d.m.Y H:i:s', $next_timestamp); - } - elseif ($service_active && $active === 'active' && $enabled === 'enabled') - { - $next_label = 'Nach Abschluss des aktuellen Laufs'; - } - elseif ($active === 'active' && $enabled === 'enabled') - { - $next_label = 'Wird von systemd neu geplant'; + $next_label = date('d.m.Y H:i:s', $next).' (beim nächsten Piwigo-Aufruf)'; } else { - $next_label = 'Nicht verfügbar'; + $next_label = 'Beim nächsten Piwigo-Aufruf'; + } + + if ((int)$last['timestamp'] <= 0 && !empty($scheduler['finished_at'])) + { + $last['timestamp'] = (int)$scheduler['finished_at']; + $last['state'] = (string)($scheduler['state'] ?? ''); + $last['message'] = (string)($scheduler['message'] ?? ''); + if ((string)$last['state'] === 'error') + { + $detail = trim((string)($scheduler['stderr'] ?? '')); + if ($detail === '') $detail = trim((string)($scheduler['stdout'] ?? '')); + $last['error_detail'] = $detail; + } } return array( - 'timer_name' => $timer, - 'timer_active' => $active === 'active', - 'timer_enabled' => $enabled === 'enabled', - 'service_active' => $service_active, - 'current_run_timestamp' => $service_started_timestamp, - 'current_run_label' => $service_active - ? ($service_started_timestamp > 0 ? 'Läuft seit '.date('d.m.Y H:i:s', $service_started_timestamp) : 'Läuft gerade') - : 'Kein Lauf aktiv', - 'last_run_timestamp' => (int)$last['timestamp'], - 'last_run_label' => $last['timestamp'] > 0 ? date('d.m.Y H:i:s', (int)$last['timestamp']) : 'Nicht verfügbar', - 'last_run_state' => (string)$last['state'], - 'last_run_message' => (string)$last['message'], - 'last_run_auth_mode' => (string)$last['auth_mode'], - 'last_run_api_state' => (string)$last['api_state'], - 'last_run_api_message' => (string)$last['api_message'], - 'last_run_fallback_state' => (string)$last['fallback_state'], - 'last_run_fallback_message' => (string)$last['fallback_message'], - 'last_run_error_detail' => (string)$last['error_detail'], - 'next_run_timestamp' => $next_timestamp, - 'next_run_label' => $next_label, - 'legacy_runtime_exists' => is_dir('/opt/piwigo-sync'), - 'legacy_config_exists' => is_dir('/etc/piwigo-sync'), - 'legacy_service_exists' => is_file('/etc/systemd/system/piwigo-sync.service'), - 'legacy_timer_exists' => is_file('/etc/systemd/system/piwigo-sync.timer'), + 'timer_name'=>'Piwigo nativer NC-Scheduler', + 'timer_active'=>$enabled, + 'timer_enabled'=>$enabled, + 'service_active'=>$running, + 'current_run_timestamp'=>$started, + 'current_run_label'=>$running ? ($started > 0 ? 'Läuft seit '.date('d.m.Y H:i:s', $started) : 'Läuft gerade') : 'Kein Lauf aktiv', + 'last_run_timestamp'=>(int)$last['timestamp'], + 'last_run_label'=>(int)$last['timestamp'] > 0 ? date('d.m.Y H:i:s', (int)$last['timestamp']) : 'Nicht verfügbar', + 'last_run_state'=>(string)$last['state'], + 'last_run_message'=>(string)$last['message'], + 'last_run_auth_mode'=>(string)$last['auth_mode'], + 'last_run_api_state'=>(string)$last['api_state'], + 'last_run_api_message'=>(string)$last['api_message'], + 'last_run_fallback_state'=>(string)$last['fallback_state'], + 'last_run_fallback_message'=>(string)$last['fallback_message'], + 'last_run_error_detail'=>(string)$last['error_detail'], + 'next_run_timestamp'=>$next, + 'next_run_label'=>$next_label, + 'legacy_runtime_exists'=>is_dir('/opt/piwigo-sync'), + 'legacy_config_exists'=>is_dir('/etc/piwigo-sync'), + 'legacy_service_exists'=>is_file('/etc/systemd/system/piwigo-sync.service'), + 'legacy_timer_exists'=>is_file('/etc/systemd/system/piwigo-sync.timer'), ); } diff --git a/include/tool_registry.inc.php b/include/tool_registry.inc.php index 7e68617..b89c5fc 100644 --- a/include/tool_registry.inc.php +++ b/include/tool_registry.inc.php @@ -6,7 +6,7 @@ if (!defined('PHPWG_ROOT_PATH')) if (isset($GLOBALS['template']) && is_object($GLOBALS['template']) && method_exists($GLOBALS['template'], 'func_combine_script')) { - $script_version = function_exists('bratonien_tools_current_version') ? bratonien_tools_current_version() : '0.9.6.27'; + $script_version = function_exists('bratonien_tools_current_version') ? bratonien_tools_current_version() : '0.9.6.30'; $GLOBALS['template']->func_combine_script(array( 'id'=>'bratonien_nc_connector_edit_v2', 'path'=>BRATONIEN_TOOLS_PATH.'js/nc_connector_edit_v2.js', @@ -41,57 +41,13 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_delete_safe.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_webdav.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_wizard_webdav_flow.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_edit.inc.php'); +require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_scheduler.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_system.inc.php'); function bratonien_tools_nc_connector_run_now() { - $service = 'bratonien-nc-connector.service'; - $active = bratonien_tools_nc_connector_systemctl_value(array('is-active', $service)); - if ($active === 'active' || $active === 'activating') - { - return array('message'=>'Der NC-Abgleich läuft bereits.'); - } - - if (!function_exists('proc_open')) - { - throw new RuntimeException('Der NC-Abgleich konnte nicht gestartet werden: proc_open ist nicht verfügbar.'); - } - - $commands = array( - array('/usr/bin/sudo', '-n', '/usr/bin/systemctl', 'start', '--no-block', $service), - array('/usr/bin/systemctl', 'start', '--no-block', $service), - ); - $last_error = ''; - - foreach ($commands as $command) - { - if (!is_executable($command[0])) - { - continue; - } - $spec = array( - 0=>array('file','/dev/null','r'), - 1=>array('pipe','w'), - 2=>array('pipe','w'), - ); - $process = @proc_open($command, $spec, $pipes, null, array_merge($_ENV, array('LC_ALL'=>'C','LANG'=>'C'))); - if (!is_resource($process)) - { - continue; - } - $stdout = trim((string)stream_get_contents($pipes[1])); - $stderr = trim((string)stream_get_contents($pipes[2])); - fclose($pipes[1]); - fclose($pipes[2]); - $exit = proc_close($process); - if ($exit === 0) - { - return array('message'=>'NC-Abgleich wurde gestartet.'); - } - $last_error = $stderr !== '' ? $stderr : $stdout; - } - - throw new RuntimeException('Der NC-Abgleich konnte nicht gestartet werden'.($last_error !== '' ? ': '.$last_error : '.')); + $result = bratonien_tools_nc_scheduler_spawn(true); + return array('message'=>(string)($result['message'] ?? 'NC-Abgleich wurde gestartet.')); } function bratonien_tools_get_tools() diff --git a/main.inc.php b/main.inc.php index 3b19d9e..e6caca7 100644 --- a/main.inc.php +++ b/main.inc.php @@ -1,7 +1,7 @@ time(), 'messages' => $dependency_messages, diff --git a/runtime/native-runner.php b/runtime/native-runner.php new file mode 100644 index 0000000..817c54c --- /dev/null +++ b/runtime/native-runner.php @@ -0,0 +1,100 @@ +#!/usr/bin/env php +array('file','/dev/null','r'), + 1=>array('pipe','w'), + 2=>array('pipe','w'), +); +$process = @proc_open($command, $spec, $pipes, null, $env); +$stdout = ''; +$stderr = ''; +$exit = 1; +if (is_resource($process)) +{ + $stdout = (string)stream_get_contents($pipes[1]); + $stderr = (string)stream_get_contents($pipes[2]); + fclose($pipes[1]); + fclose($pipes[2]); + $exit = proc_close($process); +} +else +{ + $stderr = 'run-all.sh konnte nicht gestartet werden.'; +} + +$state = native_scheduler_state($stateFile); +$state['enabled'] = true; +$state['mode'] = 'piwigo-native'; +$state['state'] = $exit === 0 ? 'success' : 'error'; +$state['message'] = $exit === 0 ? 'NC-Abgleich erfolgreich abgeschlossen.' : 'NC-Abgleich fehlgeschlagen.'; +$state['timestamp'] = time(); +$state['finished_at'] = time(); +$state['exit_code'] = $exit; +$state['stdout'] = trim($stdout); +$state['stderr'] = trim($stderr); +if (empty($state['next_due']) || (int)$state['next_due'] < time()) +{ + $state['next_due'] = time() + 60; +} +native_scheduler_write($stateFile, $state); +exit($exit === 0 ? 0 : 1); diff --git a/runtime/reconcile-webdav.php b/runtime/reconcile-webdav.php index 0de224b..a4e1f36 100644 --- a/runtime/reconcile-webdav.php +++ b/runtime/reconcile-webdav.php @@ -78,8 +78,11 @@ function webdav_source_fingerprint($baseUrl, $user, array $roots) $pluginRoot = dirname(__DIR__); $piwigoRoot = dirname($pluginRoot, 2); $dbConfig = $piwigoRoot.'/local/config/database.inc.php'; -$configDir = '/etc/bratonien-tools/nc-connector'; -$stateRoot = '/var/lib/bratonien-tools/nc-connector'; +$nativeMode = getenv('BRATONIEN_NC_NATIVE') === '1'; +$configDir = trim((string)getenv('BRATONIEN_NC_CONFIG_DIR')); +if ($configDir === '') $configDir = $nativeMode ? rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-connector-runtime' : '/etc/bratonien-tools/nc-connector'; +$stateRoot = trim((string)getenv('BRATONIEN_NC_STATE_ROOT')); +if ($stateRoot === '') $stateRoot = $nativeMode ? rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-connector-state' : '/var/lib/bratonien-tools/nc-connector'; $publicSourceRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-source'; $publicGalleryRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-gallery'; $legacyGalleryRoot = rtrim($piwigoRoot, '/').'/galleries'; @@ -107,11 +110,12 @@ try $rows = $db->query("SELECT id,name,adapter,config_json,secret_blob FROM `{$table}` ORDER BY id DESC"); if (!$rows) fail_webdav_reconcile('Connector-Verbindungen konnten nicht gelesen werden: '.$db->error); - foreach (array($configDir, $publicSourceRoot, $publicGalleryRoot) as $dir) + foreach (array($configDir, $stateRoot, $publicSourceRoot, $publicGalleryRoot) as $dir) { - if (!is_dir($dir) && !mkdir($dir, $dir === $configDir ? 0700 : 0755, true)) fail_webdav_reconcile('Runtime-Verzeichnis konnte nicht angelegt werden: '.$dir); + if (!is_dir($dir) && !mkdir($dir, $dir === $configDir ? 0700 : 0750, true)) fail_webdav_reconcile('Runtime-Verzeichnis konnte nicht angelegt werden: '.$dir); } @chmod($configDir, 0700); + @chmod($stateRoot, 0750); @chmod($publicSourceRoot, 0755); @chmod($publicGalleryRoot, 0755); @@ -150,7 +154,7 @@ try $seenFingerprints[$fingerprint] = $id; $known[$id] = true; - $stateDir = rtrim((string)($config['state_dir'] ?? ''), '/'); + $stateDir = $nativeMode ? $stateRoot.'/connection-'.$id : rtrim((string)($config['state_dir'] ?? ''), '/'); if ($stateDir === '') $stateDir = $stateRoot.'/connection-'.$id; if (!is_dir($stateDir) && !mkdir($stateDir, 0750, true)) fail_webdav_reconcile('State-Verzeichnis konnte nicht angelegt werden.'); @chmod($stateDir, 0750); @@ -219,7 +223,7 @@ try $config['parallel_gallery_root'] = $galleryRoot; $config['source_fingerprint'] = $fingerprint; $config['runtime'] = array( - 'mode'=>'webdav', + 'mode'=>$nativeMode ? 'piwigo-native-webdav' : 'webdav', 'config'=>$configPath, 'piwigo_sync_enabled'=>true, 'reconciled_at'=>date('Y-m-d H:i:s'), diff --git a/runtime/run-all.sh b/runtime/run-all.sh index 7da22cf..65582eb 100644 --- a/runtime/run-all.sh +++ b/runtime/run-all.sh @@ -1,8 +1,18 @@ #!/usr/bin/env bash set -Eeuo pipefail -CONFIG_DIR="/etc/bratonien-tools/nc-connector" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PIWIGO_ROOT_DEFAULT="${BRATONIEN_NC_PIWIGO_ROOT:-$(cd -- "$SCRIPT_DIR/../../.." && pwd)}" +CONFIG_DIR="${BRATONIEN_NC_CONFIG_DIR:-/etc/bratonien-tools/nc-connector}" +NATIVE_MODE="${BRATONIEN_NC_NATIVE:-0}" +GLOBAL_LOCK_DIR="${PIWIGO_ROOT_DEFAULT%/}/_data/bratonien-tools/nc-connector-scheduler" +GLOBAL_LOCK_FILE="$GLOBAL_LOCK_DIR/worker.lock" +mkdir -p -- "$GLOBAL_LOCK_DIR" +exec 8>"$GLOBAL_LOCK_FILE" +if ! flock -n 8; then + echo "NC Connector: ein Lauf ist bereits aktiv." + exit 0 +fi shopt -s nullglob read_config_value() { @@ -39,10 +49,14 @@ write_route_status() { ' "$ROUTE_STATUS_FILE" "$route" "$label" "$detail" "$success" } -php "$SCRIPT_DIR/reconcile.php" +if [[ "$NATIVE_MODE" != "1" ]]; then + php "$SCRIPT_DIR/reconcile.php" +fi php "$SCRIPT_DIR/reconcile-webdav.php" php "$SCRIPT_DIR/cleanup-webdav-piwigo.php" -php "$SCRIPT_DIR/cleanup-stale.php" +if [[ "$NATIVE_MODE" != "1" ]]; then + php "$SCRIPT_DIR/cleanup-stale.php" +fi configs=("$CONFIG_DIR"/connection-*.conf) webdav_configs=("$CONFIG_DIR"/webdav-connection-*.conf) @@ -58,7 +72,7 @@ for candidate in "${webdav_configs[@]}" "${configs[@]}"; do route_piwigo_root="$(read_config_value PIWIGO_ROOT "$candidate")" [[ -n "$route_piwigo_root" ]] && break done -[[ -n "$route_piwigo_root" ]] || route_piwigo_root="/var/www/piwigo" +[[ -n "$route_piwigo_root" ]] || route_piwigo_root="$PIWIGO_ROOT_DEFAULT" ROUTE_STATUS_FILE="${route_piwigo_root%/}/_data/bratonien-tools/nc-connector-status/route-status.json" failure_count=0 @@ -91,43 +105,45 @@ for config in "${webdav_configs[@]}"; do fi done -for config in "${configs[@]}"; do - [[ -f "$config" ]] || continue - name="$(basename "$config")" - connection_id="0" - if [[ "$name" =~ ^connection-([0-9]+)\.conf$ ]]; then - connection_id="${BASH_REMATCH[1]}" - fi - if [[ "$connection_id" -lt 1 ]]; then - echo "NC Connector: $name besitzt keine gueltige Verbindungs-ID." >&2 - failure_count=$((failure_count + 1)) - summary_parts+=("$name: ungueltige Verbindungs-ID") - continue - fi +if [[ "$NATIVE_MODE" != "1" ]]; then + for config in "${configs[@]}"; do + [[ -f "$config" ]] || continue + name="$(basename "$config")" + connection_id="0" + if [[ "$name" =~ ^connection-([0-9]+)\.conf$ ]]; then + connection_id="${BASH_REMATCH[1]}" + fi + if [[ "$connection_id" -lt 1 ]]; then + echo "NC Connector: $name besitzt keine gueltige Verbindungs-ID." >&2 + failure_count=$((failure_count + 1)) + summary_parts+=("$name: ungueltige Verbindungs-ID") + continue + fi - piwigo_root="$(read_config_value PIWIGO_ROOT "$config")" - [[ -n "$piwigo_root" ]] || piwigo_root="/var/www/piwigo" - tombstone_dir="${piwigo_root%/}/_data/bratonien-tools/nc-connector-status" - if [[ -f "$tombstone_dir/deleted-$connection_id" ]]; then - echo "NC Connector: Verbindung $connection_id wurde geloescht; Laufzeitdateien werden entfernt." - rm -f -- "$CONFIG_DIR/connection-$connection_id.conf" \ - "$CONFIG_DIR/connection-$connection_id.db-password" \ - "$CONFIG_DIR/connection-$connection_id.piwigo-password" \ - "$CONFIG_DIR/connection-$connection_id.storages.tsv" \ - "$CONFIG_DIR/connection-$connection_id.roots.tsv" - rm -f -- "$tombstone_dir/deleted-$connection_id" - continue - fi + piwigo_root="$(read_config_value PIWIGO_ROOT "$config")" + [[ -n "$piwigo_root" ]] || piwigo_root="$PIWIGO_ROOT_DEFAULT" + tombstone_dir="${piwigo_root%/}/_data/bratonien-tools/nc-connector-status" + if [[ -f "$tombstone_dir/deleted-$connection_id" ]]; then + echo "NC Connector: Verbindung $connection_id wurde geloescht; Laufzeitdateien werden entfernt." + rm -f -- "$CONFIG_DIR/connection-$connection_id.conf" \ + "$CONFIG_DIR/connection-$connection_id.db-password" \ + "$CONFIG_DIR/connection-$connection_id.piwigo-password" \ + "$CONFIG_DIR/connection-$connection_id.storages.tsv" \ + "$CONFIG_DIR/connection-$connection_id.roots.tsv" + rm -f -- "$tombstone_dir/deleted-$connection_id" + continue + fi - local_count=$((local_count + 1)) - echo "NC Connector Local #$connection_id: $name" - if env PIWIGO_CONFIG="$config" bash "$SCRIPT_DIR/sync.sh"; then - summary_parts+=("Local #$connection_id erfolgreich") - else - failure_count=$((failure_count + 1)) - summary_parts+=("Local #$connection_id fehlgeschlagen") - fi -done + local_count=$((local_count + 1)) + echo "NC Connector Local #$connection_id: $name" + if env PIWIGO_CONFIG="$config" bash "$SCRIPT_DIR/sync.sh"; then + summary_parts+=("Local #$connection_id erfolgreich") + else + failure_count=$((failure_count + 1)) + summary_parts+=("Local #$connection_id fehlgeschlagen") + fi + done +fi summary_detail="$(IFS='; '; printf '%s' "${summary_parts[*]}")" [[ -n "$summary_detail" ]] || summary_detail="Keine Verbindung wurde ausgefuehrt." diff --git a/runtime/sync-webdav.sh b/runtime/sync-webdav.sh index 628f72b..c0b8c06 100644 --- a/runtime/sync-webdav.sh +++ b/runtime/sync-webdav.sh @@ -134,21 +134,33 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then exit "$PIWIGO_EXIT" fi - MEDIA_UNIT="bratonien-nc-media-${CONNECTION_ID}-$(date +%s)" - if ! systemd-run \ - --quiet \ - --collect \ - --unit="$MEDIA_UNIT" \ - --property=RuntimeMaxSec=30min \ - --setenv="PIWIGO_CONFIG=$CONFIG_FILE" \ - /usr/bin/env bash "$SCRIPT_DIR/build-webdav-media.sh"; then - write_status error "Bildaufbereitung konnte nicht im Hintergrund gestartet werden" - exit 1 + if [[ "${BRATONIEN_NC_NATIVE:-0}" == "1" ]]; then + if command -v timeout >/dev/null 2>&1; then + if ! timeout 30m env PIWIGO_CONFIG="$CONFIG_FILE" bash "$SCRIPT_DIR/build-webdav-media.sh"; then + write_status error "Bildaufbereitung ist fehlgeschlagen oder hat das 30-Minuten-Limit erreicht" + exit 1 + fi + elif ! env PIWIGO_CONFIG="$CONFIG_FILE" bash "$SCRIPT_DIR/build-webdav-media.sh"; then + write_status error "Bildaufbereitung ist fehlgeschlagen" + exit 1 + fi + else + MEDIA_UNIT="bratonien-nc-media-${CONNECTION_ID}-$(date +%s)" + if ! systemd-run \ + --quiet \ + --collect \ + --unit="$MEDIA_UNIT" \ + --property=RuntimeMaxSec=30min \ + --setenv="PIWIGO_CONFIG=$CONFIG_FILE" \ + /usr/bin/env bash "$SCRIPT_DIR/build-webdav-media.sh"; then + write_status error "Bildaufbereitung konnte nicht im Hintergrund gestartet werden" + exit 1 + fi fi if grep -q 'Piwigo-Synchronisierung per API erfolgreich' <<<"$PIWIGO_OUTPUT"; then write_status ok \ - "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung läuft im Hintergrund" \ + "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung abgeschlossen" \ "" \ "api" \ "ok" \ @@ -157,7 +169,7 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then "Fallback wurde nicht benötigt" elif grep -q 'Piwigo-Datenbanksynchronisierung per Benutzername/Passwort-Fallback erfolgreich' <<<"$PIWIGO_OUTPUT"; then write_status ok \ - "WebDAV eingelesen und Piwigo über Fallback synchronisiert; Bildaufbereitung läuft im Hintergrund" \ + "WebDAV eingelesen und Piwigo über Fallback synchronisiert; Bildaufbereitung abgeschlossen" \ "" \ "fallback" \ "not_used" \ @@ -165,7 +177,7 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then "ok" \ "Benutzername/Passwort-Fallback erfolgreich" else - write_status ok "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung läuft im Hintergrund" + write_status ok "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung abgeschlossen" fi else write_status ok "WebDAV eingelesen; Piwigo-Synchronisierung ist für diese Verbindung deaktiviert"