diff --git a/include/nc_connector_manage.inc.php b/include/nc_connector_manage.inc.php index 1d380d2..f6175fe 100644 --- a/include/nc_connector_manage.inc.php +++ b/include/nc_connector_manage.inc.php @@ -7,304 +7,92 @@ if (!defined('PHPWG_ROOT_PATH')) function bratonien_tools_nc_connector_credentials_from_blob($blob) { $plain = bratonien_tools_nc_connector_decrypt_secret($blob); - if ($plain === '') - { - return array('db_password'=>'', 'piwigo_user'=>'', 'piwigo_password'=>''); - } - + if ($plain === '') return array('db_password'=>'', 'piwigo_user'=>'', 'piwigo_password'=>''); $decoded = json_decode($plain, true); if (is_array($decoded) && isset($decoded['db_password'])) { return array( - 'db_password' => (string)($decoded['db_password'] ?? ''), - 'piwigo_user' => (string)($decoded['piwigo_user'] ?? ''), - 'piwigo_password' => (string)($decoded['piwigo_password'] ?? ''), + 'db_password'=>(string)($decoded['db_password'] ?? ''), + 'piwigo_user'=>(string)($decoded['piwigo_user'] ?? ''), + 'piwigo_password'=>(string)($decoded['piwigo_password'] ?? ''), ); } - - // Backwards compatibility for migrated 0.14.x connections. return array('db_password'=>$plain, 'piwigo_user'=>'', 'piwigo_password'=>''); } function bratonien_tools_nc_connector_encrypt_credentials($db_password, $piwigo_user, $piwigo_password) { - $payload = json_encode(array( - 'v' => 1, - 'db_password' => (string)$db_password, - 'piwigo_user' => (string)$piwigo_user, - 'piwigo_password' => (string)$piwigo_password, - ), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - - if (!is_string($payload)) - { - throw new RuntimeException('Connector-Zugangsdaten konnten nicht serialisiert werden.'); - } - + $payload=json_encode(array('v'=>1,'db_password'=>(string)$db_password,'piwigo_user'=>(string)$piwigo_user,'piwigo_password'=>(string)$piwigo_password),JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); + if(!is_string($payload)) throw new RuntimeException('Connector-Zugangsdaten konnten nicht serialisiert werden.'); return bratonien_tools_nc_connector_encrypt_secret($payload); } function bratonien_tools_nc_connector_parse_storages($raw) { - $storages = array(); - $lines = preg_split('/\r\n|\r|\n/', trim((string)$raw)); - foreach ($lines as $line) + $storages=array();$lines=preg_split('/\r\n|\r|\n/',trim((string)$raw)); + foreach($lines as $line) { - $line = trim($line); - if ($line === '') - { - continue; - } - $parts = array_map('trim', explode('|', $line)); - if (count($parts) !== 3 || $parts[0] === '' || $parts[1] === '' || $parts[2] === '') - { - throw new RuntimeException('Storage-Zeilen muessen das Format storage_id | source_prefix | local_mount verwenden.'); - } - if ($parts[2][0] !== '/') - { - throw new RuntimeException('Der lokale Storage-Mount muss ein absoluter Pfad sein.'); - } - $storages[] = array( - 'storage_id' => $parts[0], - 'source_prefix' => $parts[1], - 'local_mount' => rtrim($parts[2], '/'), - ); - } - if (!$storages) - { - throw new RuntimeException('Mindestens ein Storage muss konfiguriert werden.'); + $line=trim($line);if($line==='')continue;$parts=array_map('trim',explode('|',$line)); + if(count($parts)!==3||$parts[0]===''||$parts[1]===''||$parts[2]==='')throw new RuntimeException('Storage-Zeilen muessen das Format storage_id | source_prefix | local_mount verwenden.'); + if($parts[2][0]!=='/')throw new RuntimeException('Der lokale Storage-Mount muss ein absoluter Pfad sein.'); + $storages[]=array('storage_id'=>$parts[0],'source_prefix'=>$parts[1],'local_mount'=>rtrim($parts[2],'/')); } + if(!$storages)throw new RuntimeException('Mindestens ein Storage muss konfiguriert werden.'); return $storages; } function bratonien_tools_nc_connector_create_local() { - $required = array( - 'nc_name' => 'Name', - 'nc_host' => 'PostgreSQL-Host', - 'nc_database' => 'Datenbank', - 'nc_user' => 'Reader-Benutzer', - 'nc_db_password' => 'Reader-Passwort', - 'nc_source_view' => 'Source-View', - 'nc_activity_view' => 'Activity-View', - 'nc_gallery_root' => 'Galerie-Pfad', - 'nc_piwigo_user' => 'Piwigo-Benutzer', - 'nc_piwigo_password' => 'Piwigo-Passwort', - ); - - foreach ($required as $field => $label) - { - if (trim((string)($_POST[$field] ?? '')) === '') - { - throw new RuntimeException($label.' fehlt.'); - } - } - - $port = (int)($_POST['nc_port'] ?? 5432); - if ($port < 1 || $port > 65535) - { - throw new RuntimeException('Ungueltiger PostgreSQL-Port.'); - } - - $gallery_root = rtrim(trim((string)$_POST['nc_gallery_root']), '/'); - if ($gallery_root === '' || $gallery_root[0] !== '/') - { - throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.'); - } - - $config = array( - 'host' => trim((string)$_POST['nc_host']), - 'port' => (string)$port, - 'database' => trim((string)$_POST['nc_database']), - 'user' => trim((string)$_POST['nc_user']), - 'source_view' => trim((string)$_POST['nc_source_view']), - 'activity_view' => trim((string)$_POST['nc_activity_view']), - 'gallery_root' => $gallery_root, - 'state_dir' => '', - 'status_file' => '', - 'quiet_seconds' => max(0, (int)($_POST['nc_quiet_seconds'] ?? 120)), - 'max_wait_seconds' => max(60, (int)($_POST['nc_max_wait_seconds'] ?? 900)), - 'full_sync_seconds' => max(300, (int)($_POST['nc_full_sync_seconds'] ?? 86400)), - 'storages' => bratonien_tools_nc_connector_parse_storages($_POST['nc_storages'] ?? ''), - 'origin' => 'native', - ); - - bratonien_tools_nc_connector_view_name($config['source_view']); - bratonien_tools_nc_connector_view_name($config['activity_view']); - - $table = bratonien_tools_nc_connector_table(); - bratonien_tools_nc_connector_ensure_table(); - $now = date('Y-m-d H:i:s'); - $connection_key = 'local-'.bin2hex(random_bytes(12)); - $secret_blob = bratonien_tools_nc_connector_encrypt_credentials( - (string)$_POST['nc_db_password'], - trim((string)$_POST['nc_piwigo_user']), - (string)$_POST['nc_piwigo_password'] - ); - $config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - if (!is_string($config_json)) - { - throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.'); - } - - pwg_query("INSERT INTO `$table` - (connection_key, name, adapter, enabled, takeover_state, config_json, secret_blob, created, updated) - VALUES ('".pwg_db_real_escape_string($connection_key)."', - '".pwg_db_real_escape_string(trim((string)$_POST['nc_name']))."', - 'local', 0, 'disabled', - '".pwg_db_real_escape_string($config_json)."', - '".pwg_db_real_escape_string($secret_blob)."', - '".pwg_db_real_escape_string($now)."', - '".pwg_db_real_escape_string($now)."')"); - - $id = (int)pwg_db_insert_id(); - $config['state_dir'] = '/var/lib/bratonien-tools/nc-connector/connection-'.$id; - $config['status_file'] = $config['state_dir'].'/connector-status.json'; - $config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - pwg_query("UPDATE `$table` SET config_json='".pwg_db_real_escape_string($config_json)."' WHERE id=".$id); - + $required=array('nc_name'=>'Name','nc_host'=>'PostgreSQL-Host','nc_database'=>'Datenbank','nc_user'=>'Reader-Benutzer','nc_db_password'=>'Reader-Passwort','nc_source_view'=>'Source-View','nc_activity_view'=>'Activity-View','nc_gallery_root'=>'Galerie-Pfad','nc_piwigo_user'=>'Piwigo-Benutzer','nc_piwigo_password'=>'Piwigo-Passwort'); + foreach($required as $field=>$label)if(trim((string)($_POST[$field]??''))==='')throw new RuntimeException($label.' fehlt.'); + $port=(int)($_POST['nc_port']??5432);if($port<1||$port>65535)throw new RuntimeException('Ungueltiger PostgreSQL-Port.'); + $gallery_root=rtrim(trim((string)$_POST['nc_gallery_root']),'/');if($gallery_root===''||$gallery_root[0]!=='/')throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.'); + $config=array('host'=>trim((string)$_POST['nc_host']),'port'=>(string)$port,'database'=>trim((string)$_POST['nc_database']),'user'=>trim((string)$_POST['nc_user']),'source_view'=>trim((string)$_POST['nc_source_view']),'activity_view'=>trim((string)$_POST['nc_activity_view']),'gallery_root'=>$gallery_root,'state_dir'=>'','status_file'=>'','quiet_seconds'=>max(0,(int)($_POST['nc_quiet_seconds']??120)),'max_wait_seconds'=>max(60,(int)($_POST['nc_max_wait_seconds']??900)),'full_sync_seconds'=>max(300,(int)($_POST['nc_full_sync_seconds']??86400)),'storages'=>bratonien_tools_nc_connector_parse_storages($_POST['nc_storages']??''),'origin'=>'native'); + bratonien_tools_nc_connector_view_name($config['source_view']);bratonien_tools_nc_connector_view_name($config['activity_view']); + $table=bratonien_tools_nc_connector_table();bratonien_tools_nc_connector_ensure_table();$now=date('Y-m-d H:i:s');$connection_key='local-'.bin2hex(random_bytes(12));$secret_blob=bratonien_tools_nc_connector_encrypt_credentials((string)$_POST['nc_db_password'],trim((string)$_POST['nc_piwigo_user']),(string)$_POST['nc_piwigo_password']);$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);if(!is_string($config_json))throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.'); + pwg_query("INSERT INTO `$table` (connection_key,name,adapter,enabled,takeover_state,config_json,secret_blob,created,updated) VALUES ('".pwg_db_real_escape_string($connection_key)."','".pwg_db_real_escape_string(trim((string)$_POST['nc_name']))."','local',0,'disabled','".pwg_db_real_escape_string($config_json)."','".pwg_db_real_escape_string($secret_blob)."','".pwg_db_real_escape_string($now)."','".pwg_db_real_escape_string($now)."')"); + $id=(int)pwg_db_insert_id();$config['state_dir']='/var/lib/bratonien-tools/nc-connector/connection-'.$id;$config['status_file']=$config['state_dir'].'/connector-status.json';$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);pwg_query("UPDATE `$table` SET config_json='".pwg_db_real_escape_string($config_json)."' WHERE id=".$id); return array('message'=>'Nextcloud-Verbindung wurde angelegt. Bitte zuerst technisch pruefen und danach im LXC aktivieren.'); } function bratonien_tools_nc_connector_verify_managed() { - $id = isset($_POST['connection_id']) ? (int)$_POST['connection_id'] : 0; - $connection = bratonien_tools_nc_connector_connection($id, true); - if (!$connection) + $id=isset($_POST['connection_id'])?(int)$_POST['connection_id']:0;$connection=bratonien_tools_nc_connector_connection($id,true);if(!$connection)throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');if((string)$connection['adapter']!=='local')throw new RuntimeException('Diese Verifikation ist derzeit nur fuer lokale Connector-Verbindungen verfuegbar.'); + $config=$connection['config'];foreach(array('host','port','database','user','source_view','activity_view') as $key)if(trim((string)($config[$key]??''))==='')throw new RuntimeException('Connector-Konfiguration ist unvollstaendig: '.$key.' fehlt.'); + $credentials=bratonien_tools_nc_connector_credentials_from_blob($connection['secret_blob']??'');if($credentials['db_password']==='')throw new RuntimeException('Fuer diese Verbindung ist kein Datenbankpasswort gespeichert.'); + $checks=array();$ok=true;$source_count=null; + try{bratonien_tools_nc_connector_psql($config,$credentials['db_password'],'SELECT 1');$checks[]=array('name'=>'PostgreSQL-Verbindung','ok'=>true,'detail'=>'Reader-Anmeldung erfolgreich');}catch(Throwable $e){$checks[]=array('name'=>'PostgreSQL-Verbindung','ok'=>false,'detail'=>$e->getMessage());$ok=false;} + if($ok) { - throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.'); - } - if ((string)$connection['adapter'] !== 'local') - { - throw new RuntimeException('Diese Verifikation ist derzeit nur fuer lokale Connector-Verbindungen verfuegbar.'); + try{$source_view=bratonien_tools_nc_connector_view_name($config['source_view']);$value=bratonien_tools_nc_connector_psql($config,$credentials['db_password'],'SELECT COUNT(*) FROM '.$source_view);if(!preg_match('/^\d+$/',$value))throw new RuntimeException('Source-View lieferte keinen gueltigen Zaehler.');$source_count=(int)$value;$checks[]=array('name'=>'Source-View','ok'=>true,'detail'=>$source_count.' Quelle(n) lesbar');}catch(Throwable $e){$checks[]=array('name'=>'Source-View','ok'=>false,'detail'=>$e->getMessage());$ok=false;} + try{$activity_view=bratonien_tools_nc_connector_view_name($config['activity_view']);bratonien_tools_nc_connector_psql($config,$credentials['db_password'],'SELECT 1 FROM '.$activity_view.' LIMIT 1');$checks[]=array('name'=>'Activity-View','ok'=>true,'detail'=>'View lesbar');}catch(Throwable $e){$checks[]=array('name'=>'Activity-View','ok'=>false,'detail'=>$e->getMessage());$ok=false;} } + $storages=isset($config['storages'])&&is_array($config['storages'])?$config['storages']:array();$mount_points=bratonien_tools_nc_connector_mount_points(); + if(!$storages){$checks[]=array('name'=>'Storage-Mounts','ok'=>false,'detail'=>'Keine Storage-Zuordnung gespeichert');$ok=false;} + else foreach($storages as $index=>$storage){$path=rtrim((string)($storage['local_mount']??''),'/');$exists=$path!==''&&is_dir($path);$readable=$exists&&is_readable($path);$mounted=$path!==''&&isset($mount_points[$path]);$storage_ok=$exists&&$readable&&$mounted;$storage_id=(string)($storage['storage_id']??('#'.($index+1)));$detail=$storage_id.' -> '.($path!==''?$path:'(kein Pfad)');if(!$exists)$detail.=' (Pfad fehlt)';elseif(!$readable)$detail.=' (nicht lesbar)';elseif(!$mounted)$detail.=' (kein aktiver Mount)';else$detail.=' (bereit)';$checks[]=array('name'=>'Storage '.($index+1),'ok'=>$storage_ok,'detail'=>$detail);if(!$storage_ok)$ok=false;} + $config['verification']=array('checked_at'=>date('Y-m-d H:i:s'),'ok'=>$ok,'source_count'=>$source_count,'checks'=>$checks);$table=bratonien_tools_nc_connector_table();$config_json=json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);if(!is_string($config_json))throw new RuntimeException('Verifikationsergebnis konnte nicht gespeichert werden.');$state=$ok?'verified':((string)$connection['takeover_state']==='disabled'?'disabled':'imported');$now=date('Y-m-d H:i:s');pwg_query("UPDATE `$table` SET takeover_state='".pwg_db_real_escape_string($state)."', enabled=0, config_json='".pwg_db_real_escape_string($config_json)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id); + if(!$ok)throw new RuntimeException('Connector-Verifikation ist fehlgeschlagen. Die Verbindung bleibt deaktiviert; Details stehen im NC Connector.');return array('message'=>'Connector-Verbindung wurde erfolgreich verifiziert. PostgreSQL, Views und Storage-Mounts sind erreichbar.'); +} - $config = $connection['config']; - foreach (array('host','port','database','user','source_view','activity_view') as $key) - { - if (trim((string)($config[$key] ?? '')) === '') - { - throw new RuntimeException('Connector-Konfiguration ist unvollstaendig: '.$key.' fehlt.'); - } - } - - $credentials = bratonien_tools_nc_connector_credentials_from_blob($connection['secret_blob'] ?? ''); - if ($credentials['db_password'] === '') - { - throw new RuntimeException('Fuer diese Verbindung ist kein Datenbankpasswort gespeichert.'); - } - - $checks = array(); - $ok = true; - $source_count = null; - - try - { - bratonien_tools_nc_connector_psql($config, $credentials['db_password'], 'SELECT 1'); - $checks[] = array('name'=>'PostgreSQL-Verbindung', 'ok'=>true, 'detail'=>'Reader-Anmeldung erfolgreich'); - } - catch (Throwable $e) - { - $checks[] = array('name'=>'PostgreSQL-Verbindung', 'ok'=>false, 'detail'=>$e->getMessage()); - $ok = false; - } - - if ($ok) - { - try - { - $source_view = bratonien_tools_nc_connector_view_name($config['source_view']); - $value = bratonien_tools_nc_connector_psql($config, $credentials['db_password'], 'SELECT COUNT(*) FROM '.$source_view); - if (!preg_match('/^\d+$/', $value)) - { - throw new RuntimeException('Source-View lieferte keinen gueltigen Zaehler.'); - } - $source_count = (int)$value; - $checks[] = array('name'=>'Source-View', 'ok'=>true, 'detail'=>$source_count.' Quelle(n) lesbar'); - } - catch (Throwable $e) - { - $checks[] = array('name'=>'Source-View', 'ok'=>false, 'detail'=>$e->getMessage()); - $ok = false; - } - - try - { - $activity_view = bratonien_tools_nc_connector_view_name($config['activity_view']); - bratonien_tools_nc_connector_psql($config, $credentials['db_password'], 'SELECT 1 FROM '.$activity_view.' LIMIT 1'); - $checks[] = array('name'=>'Activity-View', 'ok'=>true, 'detail'=>'View lesbar'); - } - catch (Throwable $e) - { - $checks[] = array('name'=>'Activity-View', 'ok'=>false, 'detail'=>$e->getMessage()); - $ok = false; - } - } - - $storages = isset($config['storages']) && is_array($config['storages']) ? $config['storages'] : array(); - $mount_points = bratonien_tools_nc_connector_mount_points(); - if (!$storages) - { - $checks[] = array('name'=>'Storage-Mounts', 'ok'=>false, 'detail'=>'Keine Storage-Zuordnung gespeichert'); - $ok = false; - } - else - { - foreach ($storages as $index => $storage) - { - $path = rtrim((string)($storage['local_mount'] ?? ''), '/'); - $exists = $path !== '' && is_dir($path); - $readable = $exists && is_readable($path); - $mounted = $path !== '' && isset($mount_points[$path]); - $storage_ok = $exists && $readable && $mounted; - $storage_id = (string)($storage['storage_id'] ?? ('#'.($index + 1))); - $detail = $storage_id.' -> '.($path !== '' ? $path : '(kein Pfad)'); - if (!$exists) $detail .= ' (Pfad fehlt)'; - elseif (!$readable) $detail .= ' (nicht lesbar)'; - elseif (!$mounted) $detail .= ' (kein aktiver Mount)'; - else $detail .= ' (bereit)'; - $checks[] = array('name'=>'Storage '.($index + 1), 'ok'=>$storage_ok, 'detail'=>$detail); - if (!$storage_ok) $ok = false; - } - } - - $config['verification'] = array( - 'checked_at' => date('Y-m-d H:i:s'), - 'ok' => $ok, - 'source_count' => $source_count, - 'checks' => $checks, - ); - $table = bratonien_tools_nc_connector_table(); - $config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - if (!is_string($config_json)) - { - throw new RuntimeException('Verifikationsergebnis konnte nicht gespeichert werden.'); - } - $state = $ok ? 'verified' : ((string)$connection['takeover_state'] === 'disabled' ? 'disabled' : 'imported'); - $now = date('Y-m-d H:i:s'); - pwg_query("UPDATE `$table` SET takeover_state='".pwg_db_real_escape_string($state)."', enabled=0, config_json='".pwg_db_real_escape_string($config_json)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id); - - if (!$ok) - { - throw new RuntimeException('Connector-Verifikation ist fehlgeschlagen. Die Verbindung bleibt deaktiviert; Details stehen im NC Connector.'); - } - - return array('message'=>'Connector-Verbindung wurde erfolgreich verifiziert. PostgreSQL, Views und Storage-Mounts sind erreichbar.'); +function bratonien_tools_nc_connector_remove_tree($path) +{ + $path=rtrim((string)$path,'/');if($path===''||$path==='/'||!file_exists($path))return true; + if(is_link($path)||is_file($path))return @unlink($path); + $items=@scandir($path);if(!is_array($items))return false; + foreach($items as $item){if($item==='.'||$item==='..')continue;if(!bratonien_tools_nc_connector_remove_tree($path.'/'.$item))return false;} + return @rmdir($path); } function bratonien_tools_nc_connector_delete() { - $id = (int)($_POST['connection_id'] ?? 0); - $connection = bratonien_tools_nc_connector_connection($id, false); - if (!$connection) + $id=(int)($_POST['connection_id']??0);$connection=bratonien_tools_nc_connector_connection($id,false);if(!$connection)throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');if(!empty($connection['enabled'])||(string)$connection['takeover_state']==='active')throw new RuntimeException('Eine aktive Verbindung kann nicht geloescht werden. Sie muss zuerst deaktiviert werden.'); + $config=is_array($connection['config'])?$connection['config']:array(); + $public_status=rtrim(PHPWG_ROOT_PATH,'/').'/_data/bratonien-tools/nc-connector-status/connection-'.$id.'.json';if(is_file($public_status))@unlink($public_status); + $state_dir=rtrim((string)($config['state_dir']??''),'/'); + if($state_dir!==''&&strpos($state_dir,'/var/lib/bratonien-tools/nc-connector/connection-'.$id)===0&&file_exists($state_dir)) { - throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.'); + bratonien_tools_nc_connector_remove_tree($state_dir); } - if (!empty($connection['enabled']) || (string)$connection['takeover_state'] === 'active') - { - throw new RuntimeException('Eine aktive Verbindung kann nicht geloescht werden. Sie muss zuerst deaktiviert werden.'); - } - - $table = bratonien_tools_nc_connector_table(); - pwg_query("DELETE FROM `$table` WHERE id=".$id." LIMIT 1"); - return array('message'=>'Connector-Verbindung wurde geloescht.'); + $table=bratonien_tools_nc_connector_table();pwg_query("DELETE FROM `$table` WHERE id=".$id." LIMIT 1"); + return array('message'=>'Connector-Verbindung und erreichbare Connector-eigene Statusdaten wurden geloescht. Nextcloud- und Piwigo-Bilder blieben unverändert.'); }