Reduce connector model to WebDAV connections

This commit is contained in:
Terranom674
2026-08-20 10:39:58 +02:00
parent c5331d275e
commit d2fd72bbc1

View File

@@ -4,15 +4,6 @@ if (!defined('PHPWG_ROOT_PATH'))
die('Hacking attempt!'); die('Hacking attempt!');
} }
/**
* NC Connector migration and verification phase.
*
* The existing /etc/piwigo-sync installation remains the production path.
* Bratonien Tools imports a copy of its configuration into its own connection
* store and verifies that copy independently. Importing and verification never
* change or stop the legacy sync.
*/
function bratonien_tools_nc_connector_table() function bratonien_tools_nc_connector_table()
{ {
if (function_exists('bratonien_tools_table')) if (function_exists('bratonien_tools_table'))
@@ -30,9 +21,9 @@ function bratonien_tools_nc_connector_ensure_table()
id int(11) NOT NULL AUTO_INCREMENT, id int(11) NOT NULL AUTO_INCREMENT,
connection_key varchar(64) NOT NULL, connection_key varchar(64) NOT NULL,
name varchar(255) NOT NULL, name varchar(255) NOT NULL,
adapter enum('local','remote') NOT NULL DEFAULT 'local', adapter enum('local','remote') NOT NULL DEFAULT 'remote',
enabled tinyint(1) NOT NULL DEFAULT 0, enabled tinyint(1) NOT NULL DEFAULT 0,
takeover_state enum('imported','verified','active','disabled') NOT NULL DEFAULT 'imported', takeover_state enum('imported','verified','active','disabled') NOT NULL DEFAULT 'disabled',
config_json mediumtext NOT NULL, config_json mediumtext NOT NULL,
secret_blob mediumtext DEFAULT NULL, secret_blob mediumtext DEFAULT NULL,
created datetime NOT NULL, created datetime NOT NULL,
@@ -64,29 +55,13 @@ function bratonien_tools_nc_connector_secret_key()
function bratonien_tools_nc_connector_encrypt_secret($plain) function bratonien_tools_nc_connector_encrypt_secret($plain)
{ {
$plain = (string)$plain; $plain = (string)$plain;
if ($plain === '') if ($plain === '') return '';
{ if (!function_exists('openssl_encrypt')) throw new RuntimeException('OpenSSL wird zum sicheren Speichern der Connector-Zugangsdaten benötigt.');
return '';
}
if (!function_exists('openssl_encrypt'))
{
throw new RuntimeException('OpenSSL wird zum sicheren Speichern der Connector-Zugangsdaten benoetigt.');
}
$iv = random_bytes(12); $iv = random_bytes(12);
$tag = ''; $tag = '';
$cipher = openssl_encrypt( $cipher = openssl_encrypt($plain, 'aes-256-gcm', bratonien_tools_nc_connector_secret_key(), OPENSSL_RAW_DATA, $iv, $tag);
$plain, if ($cipher === false) throw new RuntimeException('Connector-Zugangsdaten konnten nicht verschlüsselt werden.');
'aes-256-gcm',
bratonien_tools_nc_connector_secret_key(),
OPENSSL_RAW_DATA,
$iv,
$tag
);
if ($cipher === false)
{
throw new RuntimeException('Connector-Zugangsdaten konnten nicht verschluesselt werden.');
}
return base64_encode(json_encode(array( return base64_encode(json_encode(array(
'v'=>1, 'v'=>1,
@@ -99,104 +74,40 @@ function bratonien_tools_nc_connector_encrypt_secret($plain)
function bratonien_tools_nc_connector_decrypt_secret($blob) function bratonien_tools_nc_connector_decrypt_secret($blob)
{ {
$blob = trim((string)$blob); $blob = trim((string)$blob);
if ($blob === '') if ($blob === '') return '';
{ if (!function_exists('openssl_decrypt')) throw new RuntimeException('OpenSSL wird zum Lesen der Connector-Zugangsdaten benötigt.');
return '';
}
if (!function_exists('openssl_decrypt'))
{
throw new RuntimeException('OpenSSL wird zum Lesen der Connector-Zugangsdaten benoetigt.');
}
$outer = base64_decode($blob, true); $outer = base64_decode($blob, true);
$payload = is_string($outer) ? json_decode($outer, true) : null; $payload = is_string($outer) ? json_decode($outer, true) : null;
if (!is_array($payload) || (int)($payload['v'] ?? 0) !== 1) if (!is_array($payload) || (int)($payload['v'] ?? 0) !== 1) throw new RuntimeException('Gespeicherte Connector-Zugangsdaten haben ein unbekanntes Format.');
{
throw new RuntimeException('Gespeicherte Connector-Zugangsdaten haben ein unbekanntes Format.');
}
$iv = base64_decode((string)($payload['iv'] ?? ''), true); $iv = base64_decode((string)($payload['iv'] ?? ''), true);
$tag = base64_decode((string)($payload['tag'] ?? ''), true); $tag = base64_decode((string)($payload['tag'] ?? ''), true);
$cipher = base64_decode((string)($payload['data'] ?? ''), true); $cipher = base64_decode((string)($payload['data'] ?? ''), true);
if (!is_string($iv) || !is_string($tag) || !is_string($cipher)) if (!is_string($iv) || !is_string($tag) || !is_string($cipher)) throw new RuntimeException('Gespeicherte Connector-Zugangsdaten sind beschädigt.');
{
throw new RuntimeException('Gespeicherte Connector-Zugangsdaten sind beschaedigt.');
}
$plain = openssl_decrypt(
$cipher,
'aes-256-gcm',
bratonien_tools_nc_connector_secret_key(),
OPENSSL_RAW_DATA,
$iv,
$tag
);
if ($plain === false)
{
throw new RuntimeException('Connector-Zugangsdaten konnten nicht entschluesselt werden.');
}
$plain = openssl_decrypt($cipher, 'aes-256-gcm', bratonien_tools_nc_connector_secret_key(), OPENSSL_RAW_DATA, $iv, $tag);
if ($plain === false) throw new RuntimeException('Connector-Zugangsdaten konnten nicht entschlüsselt werden.');
return (string)$plain; return (string)$plain;
} }
function bratonien_tools_nc_connector_read_config($path) function bratonien_tools_nc_connector_is_webdav(array $row)
{ {
$config = array(); $config = isset($row['config']) && is_array($row['config']) ? $row['config'] : array();
if (!is_readable($path)) return (string)($row['adapter'] ?? '') === 'remote' && (string)($config['source_mode'] ?? '') === 'webdav-placeholder';
{
return $config;
}
$lines = @file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!is_array($lines))
{
return $config;
}
foreach ($lines as $line)
{
$line = trim($line);
if ($line === '' || $line[0] === '#')
{
continue;
}
if (!preg_match('/^([A-Z0-9_]+)=(.*)$/', $line, $matches))
{
continue;
}
$value = trim($matches[2]);
$length = strlen($value);
if ($length >= 2)
{
$first = $value[0];
$last = $value[$length - 1];
if (($first === '"' && $last === '"') || ($first === "'" && $last === "'"))
{
$value = substr($value, 1, -1);
}
}
$config[$matches[1]] = $value;
}
return $config;
} }
function bratonien_tools_nc_connector_connections() function bratonien_tools_nc_connector_connections()
{ {
bratonien_tools_nc_connector_ensure_table(); bratonien_tools_nc_connector_ensure_table();
$table = bratonien_tools_nc_connector_table(); $table = bratonien_tools_nc_connector_table();
$result = pwg_query("SELECT id, connection_key, name, adapter, enabled, takeover_state, config_json, secret_blob, created, updated FROM `$table` ORDER BY id"); $result = pwg_query("SELECT id, connection_key, name, adapter, enabled, takeover_state, config_json, secret_blob, created, updated FROM `$table` WHERE adapter='remote' ORDER BY id");
$connections = array(); $connections = array();
while ($row = pwg_db_fetch_assoc($result)) while ($row = pwg_db_fetch_assoc($result))
{ {
$config = json_decode((string)$row['config_json'], true); $config = json_decode((string)$row['config_json'], true);
if (!is_array($config)) if (!is_array($config) || (string)($config['source_mode'] ?? '') !== 'webdav-placeholder') continue;
{
$config = array();
}
$verification = isset($config['verification']) && is_array($config['verification']) ? $config['verification'] : array();
$has_fallback = false; $has_fallback = false;
try try
@@ -205,8 +116,7 @@ function bratonien_tools_nc_connector_connections()
$credentials = json_decode($plain, true); $credentials = json_decode($plain, true);
if (is_array($credentials)) if (is_array($credentials))
{ {
$has_fallback = trim((string)($credentials['piwigo_user'] ?? '')) !== '' $has_fallback = trim((string)($credentials['piwigo_user'] ?? '')) !== '' && (string)($credentials['piwigo_password'] ?? '') !== '';
&& (string)($credentials['piwigo_password'] ?? '') !== '';
} }
} }
catch (Throwable $e) catch (Throwable $e)
@@ -218,17 +128,17 @@ function bratonien_tools_nc_connector_connections()
$row['id'] = (int)$row['id']; $row['id'] = (int)$row['id'];
$row['enabled'] = (bool)$row['enabled']; $row['enabled'] = (bool)$row['enabled'];
$row['config'] = $config; $row['config'] = $config;
$row['host'] = isset($config['host']) ? (string)$config['host'] : ''; $row['host'] = (string)($config['nextcloud_url'] ?? '');
$row['database'] = isset($config['database']) ? (string)$config['database'] : ''; $row['database'] = '';
$row['user'] = isset($config['user']) ? (string)$config['user'] : ''; $row['user'] = (string)($config['nextcloud_access_user'] ?? $config['access_user'] ?? '');
$row['source_view'] = isset($config['source_view']) ? (string)$config['source_view'] : ''; $row['source_view'] = 'WebDAV';
$row['storage_count'] = isset($config['storages']) && is_array($config['storages']) ? count($config['storages']) : 0; $row['storage_count'] = isset($config['roots']) && is_array($config['roots']) ? count($config['roots']) : 0;
$row['fallback_stored'] = $has_fallback; $row['fallback_stored'] = $has_fallback;
$row['verification'] = $verification; $row['verification'] = array();
$row['verified_ok'] = !empty($verification['ok']); $row['verified_ok'] = true;
$row['verified_at'] = isset($verification['checked_at']) ? (string)$verification['checked_at'] : ''; $row['verified_at'] = '';
$row['source_count'] = isset($verification['source_count']) ? (int)$verification['source_count'] : null; $row['source_count'] = $row['storage_count'];
$row['verification_checks'] = isset($verification['checks']) && is_array($verification['checks']) ? $verification['checks'] : array(); $row['verification_checks'] = array();
$connections[] = $row; $connections[] = $row;
} }
@@ -240,245 +150,28 @@ function bratonien_tools_nc_connector_connection($id, $with_secret = false)
bratonien_tools_nc_connector_ensure_table(); bratonien_tools_nc_connector_ensure_table();
$table = bratonien_tools_nc_connector_table(); $table = bratonien_tools_nc_connector_table();
$id = (int)$id; $id = (int)$id;
if ($id <= 0) if ($id <= 0) return null;
{
return null;
}
$columns = 'id, connection_key, name, adapter, enabled, takeover_state, config_json, created, updated'; $columns = 'id, connection_key, name, adapter, enabled, takeover_state, config_json, created, updated';
if ($with_secret) if ($with_secret) $columns .= ', secret_blob';
{ $result = pwg_query("SELECT $columns FROM `$table` WHERE id = $id AND adapter='remote' LIMIT 1");
$columns .= ', secret_blob'; if (!pwg_db_num_rows($result)) return null;
}
$result = pwg_query("SELECT $columns FROM `$table` WHERE id = $id LIMIT 1");
if (!pwg_db_num_rows($result))
{
return null;
}
$row = pwg_db_fetch_assoc($result); $row = pwg_db_fetch_assoc($result);
$row['id'] = (int)$row['id']; $row['id'] = (int)$row['id'];
$row['enabled'] = (bool)$row['enabled']; $row['enabled'] = (bool)$row['enabled'];
$row['config'] = json_decode((string)$row['config_json'], true); $row['config'] = json_decode((string)$row['config_json'], true);
if (!is_array($row['config'])) if (!is_array($row['config']) || (string)($row['config']['source_mode'] ?? '') !== 'webdav-placeholder') return null;
{
$row['config'] = array();
}
return $row; return $row;
} }
function bratonien_tools_nc_connector_import_bundle_path()
{
return '/tmp/bratonien-tools-nc-import.json';
}
function bratonien_tools_nc_connector_import_legacy()
{
$path = bratonien_tools_nc_connector_import_bundle_path();
if (!is_readable($path))
{
throw new RuntimeException('Kein lesbares Migrationspaket gefunden. Fuehre zuerst den angezeigten Connector-Migrationsbefehl im Piwigo-LXC aus.');
}
$raw = @file_get_contents($path);
$bundle = is_string($raw) ? json_decode($raw, true) : null;
if (!is_array($bundle) || (int)($bundle['format'] ?? 0) !== 1 || !is_array($bundle['config'] ?? null))
{
throw new RuntimeException('Das Migrationspaket ist ungueltig oder unvollstaendig.');
}
$legacy = $bundle['config'];
$required = array('NC_DB_HOST', 'NC_DB_PORT', 'NC_DB_NAME', 'NC_DB_USER', 'NC_DB_VIEW');
foreach ($required as $key)
{
if (!isset($legacy[$key]) || trim((string)$legacy[$key]) === '')
{
throw new RuntimeException('Im Migrationspaket fehlt '.$key.'.');
}
}
$password = isset($bundle['db_password']) ? (string)$bundle['db_password'] : '';
if ($password === '')
{
throw new RuntimeException('Das Migrationspaket enthaelt kein Datenbankpasswort.');
}
$config = array(
'host' => (string)$legacy['NC_DB_HOST'],
'port' => (string)$legacy['NC_DB_PORT'],
'database' => (string)$legacy['NC_DB_NAME'],
'user' => (string)$legacy['NC_DB_USER'],
'source_view' => (string)$legacy['NC_DB_VIEW'],
'activity_view' => isset($legacy['NC_ACTIVITY_VIEW']) ? (string)$legacy['NC_ACTIVITY_VIEW'] : 'piwigo_showcase_activity',
'gallery_root' => isset($legacy['GALLERY_ROOT']) ? (string)$legacy['GALLERY_ROOT'] : '',
'state_dir' => isset($legacy['STATE_DIR']) ? (string)$legacy['STATE_DIR'] : '',
'status_file' => isset($legacy['STATUS_FILE']) ? (string)$legacy['STATUS_FILE'] : '',
'quiet_seconds' => isset($legacy['QUIET_SECONDS']) ? (int)$legacy['QUIET_SECONDS'] : 120,
'max_wait_seconds' => isset($legacy['MAX_WAIT_SECONDS']) ? (int)$legacy['MAX_WAIT_SECONDS'] : 900,
'full_sync_seconds' => isset($legacy['FULL_SYNC_SECONDS']) ? (int)$legacy['FULL_SYNC_SECONDS'] : 86400,
'storages' => isset($bundle['storages']) && is_array($bundle['storages']) ? $bundle['storages'] : array(),
'imported_from' => '/etc/piwigo-sync/piwigo.conf',
'legacy_sync_enabled' => isset($legacy['PIWIGO_SYNC_ENABLED']) && (string)$legacy['PIWIGO_SYNC_ENABLED'] === '1',
);
$key_material = $config['host'].'|'.$config['database'].'|'.$config['user'].'|'.$config['source_view'];
$connection_key = 'legacy-'.substr(hash('sha256', $key_material), 0, 24);
$table = bratonien_tools_nc_connector_table();
bratonien_tools_nc_connector_ensure_table();
$now = date('Y-m-d H:i:s');
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$secret_blob = bratonien_tools_nc_connector_encrypt_secret($password);
$escaped_key = pwg_db_real_escape_string($connection_key);
$existing = pwg_query("SELECT id FROM `$table` WHERE connection_key = '$escaped_key' LIMIT 1");
if (pwg_db_num_rows($existing))
{
$row = pwg_db_fetch_assoc($existing);
$id = (int)$row['id'];
pwg_query("UPDATE `$table` SET
name = 'Bestehende Nextcloud',
adapter = 'local',
enabled = 0,
takeover_state = 'imported',
config_json = '".pwg_db_real_escape_string($config_json)."',
secret_blob = '".pwg_db_real_escape_string($secret_blob)."',
updated = '".pwg_db_real_escape_string($now)."'
WHERE id = $id");
}
else
{
pwg_query("INSERT INTO `$table`
(connection_key, name, adapter, enabled, takeover_state, config_json, secret_blob, created, updated)
VALUES (
'$escaped_key',
'Bestehende Nextcloud',
'local',
0,
'imported',
'".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)."'
)");
}
@unlink($path);
return array(
'message' => 'Bestehende Nextcloud-Verbindung wurde in den NC Connector importiert. Der produktive Legacy-Sync bleibt unveraendert aktiv; die importierte Verbindung ist noch nicht aktiviert.',
);
}
function bratonien_tools_nc_connector_view_name($name)
{
$name = trim((string)$name);
if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/', $name))
{
throw new RuntimeException('Ungueltiger PostgreSQL-View-Name in der Connector-Konfiguration.');
}
$parts = explode('.', $name);
$quoted = array();
foreach ($parts as $part)
{
$quoted[] = '"'.str_replace('"', '""', $part).'"';
}
return implode('.', $quoted);
}
function bratonien_tools_nc_connector_psql($config, $password, $query)
{
$psql = '/usr/bin/psql';
if (!is_executable($psql))
{
throw new RuntimeException('PostgreSQL-Client /usr/bin/psql ist nicht installiert oder nicht ausfuehrbar.');
}
if (!function_exists('proc_open'))
{
throw new RuntimeException('PHP-Funktion proc_open ist deaktiviert; Connector-Verifikation kann nicht ausgefuehrt werden.');
}
$command = array(
$psql,
'-X', '-A', '-t',
'-v', 'ON_ERROR_STOP=1',
'-h', (string)$config['host'],
'-p', (string)$config['port'],
'-U', (string)$config['user'],
'-d', (string)$config['database'],
'-c', (string)$query,
);
$descriptors = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$env = array(
'PGPASSWORD' => (string)$password,
'PGCONNECT_TIMEOUT' => '5',
'LC_ALL' => 'C',
);
$process = @proc_open($command, $descriptors, $pipes, null, $env);
if (!is_resource($process))
{
throw new RuntimeException('PostgreSQL-Pruefprozess konnte nicht gestartet werden.');
}
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($process);
$stdout = trim((string)$stdout);
$stderr = trim((string)$stderr);
if ($exit !== 0)
{
$detail = $stderr !== '' ? $stderr : 'psql Exit-Code '.$exit;
throw new RuntimeException('PostgreSQL-Abfrage fehlgeschlagen: '.$detail);
}
return $stdout;
}
function bratonien_tools_nc_connector_mount_points()
{
$mounts = array();
$content = @file_get_contents('/proc/self/mountinfo');
if (!is_string($content))
{
return $mounts;
}
foreach (preg_split('/\r\n|\r|\n/', $content) as $line)
{
if ($line === '') continue;
$parts = explode(' ', $line);
if (count($parts) < 5) continue;
$mount = str_replace(array('\\040','\\011','\\012','\\134'), array(' ',"\t","\n",'\\'), $parts[4]);
$mounts[rtrim($mount, '/')] = true;
}
return $mounts;
}
function bratonien_tools_nc_connector_status() function bratonien_tools_nc_connector_status()
{ {
$connections = bratonien_tools_nc_connector_connections(); $connections = bratonien_tools_nc_connector_connections();
$legacy_config = '/etc/piwigo-sync/piwigo.conf';
$legacy_present = is_readable($legacy_config);
$active_count = 0;
foreach ($connections as $connection)
{
if (!empty($connection['enabled'])) $active_count++;
}
return array( return array(
'phase' => 'native-runtime', 'phase'=>'webdav',
'legacy_config_path' => $legacy_config,
'legacy_present' => $legacy_present,
'connections'=>$connections, 'connections'=>$connections,
'connection_count'=>count($connections), 'connection_count'=>count($connections),
'active_count' => $active_count, 'active_count'=>count($connections),
); );
} }