From 3b38cb3d50b471883a82fa331c6a9015416bf811 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 17 Feb 2026 09:02:15 -0500 Subject: [PATCH 01/10] refactor(setup/pgsql): move role creation priv check to helper Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 44 +++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 5ace7f6bcbee3..974b87d7a8f22 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -21,26 +21,15 @@ class PostgreSQL extends AbstractDatabase { * @throws DatabaseSetupException */ public function setupDatabase(): void { + $canCreateRoles = false; + try { $connection = $this->connect([ 'dbname' => 'postgres' ]); + if ($this->tryCreateDbUser) { - //check for roles creation rights in postgresql - $builder = $connection->getQueryBuilder(); - $builder->automaticTablePrefix(false); - $query = $builder - ->select('rolname') - ->from('pg_roles') - ->where($builder->expr()->eq('rolcreaterole', new Literal('TRUE'))) - ->andWhere($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser))); - - try { - $result = $query->executeQuery(); - $canCreateRoles = $result->rowCount() > 0; - } catch (DatabaseException $e) { - $canCreateRoles = false; - } + $canCreateRoles = $this->checkCanCreateRoles($connection); if ($canCreateRoles) { $connectionMainDatabase = $this->connect(); @@ -103,6 +92,31 @@ public function setupDatabase(): void { } } + /** + * Checks if the current user has CREATEROLE privilege. + */ + private function checkCanCreateRoles(Connection $connection): bool { + try { + $builder = $connection->getQueryBuilder(); + $builder->automaticTablePrefix(false); + + $query = $builder + ->select('rolname') + ->from('pg_roles') + ->where($builder->expr()->eq('rolcreaterole', new Literal('TRUE'))) + ->andWhere($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser))); + + $result = $query->executeQuery(); + return $result->rowCount() > 0; + } catch (DatabaseException $e) { + $this->logger->debug('Could not check role creation privileges', [ + 'exception' => $e, + 'app' => 'pgsql.setup', + ]); + return false; + } + } + private function createDatabase(Connection $connection): void { if (!$this->databaseExists($connection)) { //The database does not exists... let's create it From 4787dff6dda1ed726b8412ae68b77ebbe1ac0421 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 17 Feb 2026 10:03:20 -0500 Subject: [PATCH 02/10] refactor(setup/pgsql): move db verification logic to helper Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 45 +++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 974b87d7a8f22..9dab7e63054bf 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -77,19 +77,8 @@ public function setupDatabase(): void { ]); } - // connect to the database (dbname=$this->dbname) and check if it needs to be filled - $this->dbUser = $this->config->getValue('dbuser'); - $this->dbPassword = $this->config->getValue('dbpassword'); - $connection = $this->connect(); - try { - $connection->connect(); - } catch (\Exception $e) { - $this->logger->error($e->getMessage(), [ - 'exception' => $e, - ]); - throw new DatabaseSetupException($this->trans->t('PostgreSQL Login and/or password not valid'), - $this->trans->t('You need to enter details of an existing account.'), 0, $e); - } + // Verify we can connect with the configured credentials + $this->verifyDatabaseConnection(); } /** @@ -182,4 +171,34 @@ private function createDBUser(Connection $connection): void { ]); } } + + /** + * Verifies connection to the Nextcloud database with configured credentials. + * + * @throws DatabaseSetupException If connection fails + */ + private function verifyDatabaseConnection(): void { + // Reload credentials from config (may have been updated + to verify config) + $this->dbUser = $this->config->getValue('dbuser'); + $this->dbPassword = $this->config->getValue('dbpassword'); + + try { + $connection = $this->connect(); // Create new connection object with final config + $connection->connect(); // Actually connect to verify credentials work + } catch (\Exception $e) { + $this->logger->error('Database connection verification failed', [ + 'user' => $this->dbUser, + 'database' => $this->dbName, + 'exception' => $e, + 'app' => 'pgsql.setup', + ]); + + throw new DatabaseSetupException( + $this->trans->t('PostgreSQL login and/or password not valid'), + $this->trans->t('You need to enter details of an existing account.'), + 0, + $e + ); + } + } } From f4e7a7ff5d72a586e31c669833bf715b9da6834c Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 17 Feb 2026 10:36:38 -0500 Subject: [PATCH 03/10] refactor(setup/pgsql): avoid mutating properties until necessary And consolidate credential generation logic in a single place Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 35 ++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 9dab7e63054bf..a02ea39b8bc2c 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -33,17 +33,12 @@ public function setupDatabase(): void { if ($canCreateRoles) { $connectionMainDatabase = $this->connect(); - //use the admin login data for the new database user - - //add prefix to the postgresql user name to prevent collisions - $this->dbUser = 'oc_admin'; - //create a new password so we don't need to store the admin config in the config file - $this->dbPassword = Server::get(ISecureRandom::class)->generate(30, ISecureRandom::CHAR_ALPHANUMERIC); - + // Create Nextcloud-specific database user $this->createDBUser($connection); } } + // Store new credentials in config $this->config->setValues([ 'dbuser' => $this->dbUser, 'dbpassword' => $this->dbPassword, @@ -129,12 +124,12 @@ private function createDatabase(Connection $connection): void { } } - private function userExists(Connection $connection): bool { + private function userExists(Connection $connection, string $roleName): bool { $builder = $connection->getQueryBuilder(); $builder->automaticTablePrefix(false); $query = $builder->select('*') ->from('pg_roles') - ->where($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser))); + ->where($builder->expr()->eq('rolname', $builder->createNamedParameter($roleName))); $result = $query->executeQuery(); return $result->rowCount() > 0; } @@ -150,21 +145,31 @@ private function databaseExists(Connection $connection): bool { } private function createDBUser(Connection $connection): void { - $dbUser = $this->dbUser; + // Generate Nextcloud-specific credentials so we don't need to store / use the db admin credentials + $baseUser = 'oc_admin'; + $newUser = $baseUser; + $newPassword = Server::get(ISecureRandom::class)->generate(30, ISecureRandom::CHAR_ALPHANUMERIC); + + // Find/generate an available username try { $i = 1; - while ($this->userExists($connection)) { + while ($this->userExists($connection, $newUser)) { $i++; - $this->dbUser = $dbUser . $i; + $newUser = $baseUser . $i; } - // create the user - $query = $connection->prepare('CREATE USER "' . addslashes($this->dbUser) . "\" CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'"); + // Create the new user + $query = $connection->prepare('CREATE USER "' . addslashes($newUser) . "\" CREATEDB PASSWORD '" . addslashes($newPassword) . "'"); $query->executeStatement(); + + // Grant database access if database already exists if ($this->databaseExists($connection)) { - $query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO "' . addslashes($this->dbUser) . '"'); + $query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO "' . addslashes($newUser) . '"'); $query->executeStatement(); } + + $this->dbUser = $newUser; + $this->dbPassword = $newPassword; } catch (DatabaseException $e) { $this->logger->error('Error while trying to create database user', [ 'exception' => $e, From 00074b72c19e43198a65fa5ae42bffc6e53dd43f Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 17 Feb 2026 11:01:48 -0500 Subject: [PATCH 04/10] refactor(setup/pgsql): make connect object names less ambiguous Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index a02ea39b8bc2c..519a2aa263a2f 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -22,19 +22,19 @@ class PostgreSQL extends AbstractDatabase { */ public function setupDatabase(): void { $canCreateRoles = false; + $adminDBConnection = null; // For admin tasks on 'postgres' DB + $nextcloudDBConnection = null; // For schema setup on Nextcloud DB try { - $connection = $this->connect([ - 'dbname' => 'postgres' - ]); + $adminDBConnection = $this->connect(['dbname' => 'postgres']); if ($this->tryCreateDbUser) { - $canCreateRoles = $this->checkCanCreateRoles($connection); + $canCreateRoles = $this->checkCanCreateRoles($adminDBConnection); if ($canCreateRoles) { - $connectionMainDatabase = $this->connect(); + $nextcloudDBConnection = $this->connect(); // Create Nextcloud-specific database user - $this->createDBUser($connection); + $this->createDBUser($adminDBConnection); } } @@ -44,10 +44,8 @@ public function setupDatabase(): void { 'dbpassword' => $this->dbPassword, ]); - //create the database - $this->createDatabase($connection); - // the connection to dbname=postgres is not needed anymore - $connection->close(); + $this->createDatabase($adminDBConnection); + $adminDBConnection->close(); if ($this->tryCreateDbUser) { if ($canCreateRoles) { @@ -58,8 +56,8 @@ public function setupDatabase(): void { // Therefore we assume that the database is only used by one user/service which is Nextcloud // Additional services should get installed in a separate database in order to stay secure // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS - $connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"'); - $connectionMainDatabase->close(); + $nextcloudDBConnection->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"'); + $nextcloudDBConnection->close(); } } } catch (\Exception $e) { From f8e1dc0da26653acf0187ae7e6f0f3d396ce0923 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 17 Feb 2026 11:30:55 -0500 Subject: [PATCH 05/10] refactor(setup/pgsql): use a single connection object and better handle admin credentials Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 43 ++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 519a2aa263a2f..3ef101a88d296 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -18,34 +18,51 @@ class PostgreSQL extends AbstractDatabase { public $dbprettyname = 'PostgreSQL'; /** - * @throws DatabaseSetupException + * Sets up PostgreSQL database and user for Nextcloud installation. + * + * This method handles two scenarios: + * 1. Automatic setup: Creates user, database, and schema when connecting as superuser + * 2. Manual setup: Uses pre-configured credentials when automatic setup fails + * + * @throws DatabaseSetupException If database connection or setup fails */ public function setupDatabase(): void { $canCreateRoles = false; - $adminDBConnection = null; // For admin tasks on 'postgres' DB - $nextcloudDBConnection = null; // For schema setup on Nextcloud DB + $connection = null; + + // Save original admin credentials before createDBUser() overwrites them + $adminUser = $this->dbUser; + $adminPassword = $this->dbPassword; try { - $adminDBConnection = $this->connect(['dbname' => 'postgres']); + // Connect to 'postgres' maintenance database for administrative tasks + $connection = $this->connect(['dbname' => 'postgres']); if ($this->tryCreateDbUser) { - $canCreateRoles = $this->checkCanCreateRoles($adminDBConnection); + $canCreateRoles = $this->checkCanCreateRoles($connection); if ($canCreateRoles) { - $nextcloudDBConnection = $this->connect(); - // Create Nextcloud-specific database user - $this->createDBUser($adminDBConnection); + // Create Nextcloud-specific database user with random credentials + // This updates $this->dbUser and $this->dbPassword + $this->createDBUser($connection); } } - // Store new credentials in config + // Store generated credentials in config $this->config->setValues([ 'dbuser' => $this->dbUser, 'dbpassword' => $this->dbPassword, ]); - $this->createDatabase($adminDBConnection); - $adminDBConnection->close(); + // Create the Nextcloud database + $this->createDatabase($connection); + + // Switch to Nextcloud database, still using admin credentials + $connection->close(); + $connection = $this->connect([ + 'user' => $adminUser, + 'password' => $adminPassword, + ]); if ($this->tryCreateDbUser) { if ($canCreateRoles) { @@ -56,8 +73,8 @@ public function setupDatabase(): void { // Therefore we assume that the database is only used by one user/service which is Nextcloud // Additional services should get installed in a separate database in order to stay secure // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS - $nextcloudDBConnection->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"'); - $nextcloudDBConnection->close(); + $connection->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"'); + $connection->close(); } } } catch (\Exception $e) { From d110f259dca205f63550a806969714dfa9680d7d Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 17 Feb 2026 12:03:42 -0500 Subject: [PATCH 06/10] refactor(setup/pgsql): Split up creation code path from non-creation Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 89 ++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 3ef101a88d296..4c30ba6112425 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -27,6 +27,26 @@ class PostgreSQL extends AbstractDatabase { * @throws DatabaseSetupException If database connection or setup fails */ public function setupDatabase(): void { + if ($this->tryCreateDbUser) { + // Automatic setup: create user, database, and schema + $this->performAutomaticSetup(); + } else { + // Manual setup: just save the provided credentials + $this->config->setValues([ + 'dbuser' => $this->dbUser, + 'dbpassword' => $this->dbPassword, + ]); + } + + // Always verify the final configuration works + $this->verifyDatabaseConnection(); + } + + /** + * Performs automatic database setup when connecting as a superuser. + * Creates a Nextcloud-specific user, database, and user-owned schema. + */ + private function performAutomaticSetup(): void { $canCreateRoles = false; $connection = null; @@ -38,57 +58,60 @@ public function setupDatabase(): void { // Connect to 'postgres' maintenance database for administrative tasks $connection = $this->connect(['dbname' => 'postgres']); - if ($this->tryCreateDbUser) { - $canCreateRoles = $this->checkCanCreateRoles($connection); + $canCreateRoles = $this->checkCanCreateRoles($connection); - if ($canCreateRoles) { - // Create Nextcloud-specific database user with random credentials - // This updates $this->dbUser and $this->dbPassword - $this->createDBUser($connection); - } + if ($canCreateRoles) { + // Create Nextcloud-specific database user with random credentials + // This updates $this->dbUser and $this->dbPassword + $this->createDBUser($connection); } - // Store generated credentials in config + // Store credentials in config (generated usually - sometimes original) $this->config->setValues([ 'dbuser' => $this->dbUser, 'dbpassword' => $this->dbPassword, ]); - // Create the Nextcloud database + // ALWAYS attempt to create database (works with or without CREATEROLE) $this->createDatabase($connection); - // Switch to Nextcloud database, still using admin credentials - $connection->close(); - $connection = $this->connect([ - 'user' => $adminUser, - 'password' => $adminPassword, - ]); + // Create user-owned schema only if we created a new user + if ($canCreateRoles) { + // Switch to Nextcloud database, still using admin credentials + $connection->close(); + $connection = $this->connect([ + 'user' => $adminUser, + 'password' => $adminPassword, + ]); - if ($this->tryCreateDbUser) { - if ($canCreateRoles) { - // Go to the main database and grant create on the public schema - // The code below is implemented to make installing possible with PostgreSQL version 15: - // https://www.postgresql.org/docs/release/15.0/ - // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases - // Therefore we assume that the database is only used by one user/service which is Nextcloud - // Additional services should get installed in a separate database in order to stay secure - // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS - $connection->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"'); - $connection->close(); - } + // Set schema permissions: + // Go to the main database and grant create on the public schema + // The code below is implemented to make installing possible with PostgreSQL version 15: + // https://www.postgresql.org/docs/release/15.0/ + // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases + // Therefore we assume that the database is only used by one user/service which is Nextcloud + // Additional services should get installed in a separate database in order to stay secure + // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS + $connection->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"'); + $connection->close(); } - } catch (\Exception $e) { - $this->logger->warning('Error trying to connect as "postgres", assuming database is setup and tables need to be created', [ - 'exception' => $e, + + } catch (\Exception $e) { // @todo: catch more specific DatabaseException | \PDOException $e instead? + // Automatic setup failed - log and continue with verification (upstream.. which will give up if if necessary) + $this->logger->warning('Automatic database setup failed, will attempt to verify manual configuration', [ + 'exception' => $e, + 'app' => 'pgsql.setup', ]); + + // Ensure credentials are saved even if automatic setup failed $this->config->setValues([ 'dbuser' => $this->dbUser, 'dbpassword' => $this->dbPassword, ]); + } finally { + // Clean up connection + $connection?->close(); } - - // Verify we can connect with the configured credentials - $this->verifyDatabaseConnection(); } /** From b8641561ea50f80c3c403b18b133b656c06198fb Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 17 Feb 2026 12:23:34 -0500 Subject: [PATCH 07/10] refactor(setup/pgsql): split out schema mgmt logic + enhance its logging Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 53 ++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 4c30ba6112425..70f752d1165ce 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -66,7 +66,7 @@ private function performAutomaticSetup(): void { $this->createDBUser($connection); } - // Store credentials in config (generated usually - sometimes original) + // Store credentials in config (generated if canCreateRoles, otherwise original admin credentials) $this->config->setValues([ 'dbuser' => $this->dbUser, 'dbpassword' => $this->dbPassword, @@ -84,20 +84,12 @@ private function performAutomaticSetup(): void { 'password' => $adminPassword, ]); - // Set schema permissions: - // Go to the main database and grant create on the public schema - // The code below is implemented to make installing possible with PostgreSQL version 15: - // https://www.postgresql.org/docs/release/15.0/ - // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases - // Therefore we assume that the database is only used by one user/service which is Nextcloud - // Additional services should get installed in a separate database in order to stay secure - // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS - $connection->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"'); - $connection->close(); + // Adjust GRANTs on public SCHEMA to Nextcloud expectations + $this->grantCreateOnPublicSchema($connection); } } catch (\Exception $e) { // @todo: catch more specific DatabaseException | \PDOException $e instead? - // Automatic setup failed - log and continue with verification (upstream.. which will give up if if necessary) + // Automatic setup failed - log and continue with verification (upstream.. which will give up if necessary) $this->logger->warning('Automatic database setup failed, will attempt to verify manual configuration', [ 'exception' => $e, 'app' => 'pgsql.setup', @@ -114,6 +106,39 @@ private function performAutomaticSetup(): void { } } + // @todo: replace with user-schema approach and drop public requirement entirely on new installations + private function grantCreateOnPublicSchema(Connection $connection): void { + try { + // Go to the main database and grant create on the public schema + // The code below is implemented to make installing possible with PostgreSQL version 15: + // https://www.postgresql.org/docs/release/15.0/ + // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases + // Therefore we assume that the database is only used by one user/service which is Nextcloud + // Additional services should get installed in a separate database in order to stay secure + // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS + $connection->executeQuery( + 'GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"' + ); + + $this->logger->info('Granted CREATE on SCHEMA public for PostgreSQL 15+ compatibility', [ + 'dbuser' => $this->dbUser, + 'app' => 'pgsql.setup', + ]); + } catch (DatabaseException $e) { + $this->logger->error('Failed to grant CREATE on public SCHEMA', [ + 'dbusr' => $this->dbUser, + 'exception' => $e, + 'app' => 'pgsql.setup', + ]); + throw new DatabaseSetupException( + $this->trans->t('Could not adjust database schema'), + $this->trans->t('Check logs for details.'), + 0, + $e + ); + } + } + /** * Checks if the current user has CREATEROLE privilege. */ @@ -148,6 +173,7 @@ private function createDatabase(Connection $connection): void { } catch (DatabaseException $e) { $this->logger->error('Error while trying to create database', [ 'exception' => $e, + 'app' => 'pgsql.setup', ]); } } else { @@ -157,6 +183,7 @@ private function createDatabase(Connection $connection): void { } catch (DatabaseException $e) { $this->logger->error('Error while trying to restrict database permissions', [ 'exception' => $e, + 'app' => 'pgsql.setup', ]); } } @@ -242,6 +269,6 @@ private function verifyDatabaseConnection(): void { 0, $e ); - } + } } } From 1cdd0af24c9ed367d3212be43bf39a427872e9a6 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 17 Feb 2026 12:31:23 -0500 Subject: [PATCH 08/10] refactor(setup/pgsql): log chosen db username Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 70f752d1165ce..647ee088393eb 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -44,7 +44,7 @@ public function setupDatabase(): void { /** * Performs automatic database setup when connecting as a superuser. - * Creates a Nextcloud-specific user, database, and user-owned schema. + * Creates a Nextcloud-specific user, database, and adjusts schema. */ private function performAutomaticSetup(): void { $canCreateRoles = false; @@ -88,7 +88,7 @@ private function performAutomaticSetup(): void { $this->grantCreateOnPublicSchema($connection); } - } catch (\Exception $e) { // @todo: catch more specific DatabaseException | \PDOException $e instead? + } catch (\Exception $e) { // @todo: catch more specific DatabaseException | DatabaseSetupException | \PDOException $e instead? // Automatic setup failed - log and continue with verification (upstream.. which will give up if necessary) $this->logger->warning('Automatic database setup failed, will attempt to verify manual configuration', [ 'exception' => $e, @@ -126,7 +126,7 @@ private function grantCreateOnPublicSchema(Connection $connection): void { ]); } catch (DatabaseException $e) { $this->logger->error('Failed to grant CREATE on public SCHEMA', [ - 'dbusr' => $this->dbUser, + 'dbuser' => $this->dbUser, 'exception' => $e, 'app' => 'pgsql.setup', ]); @@ -223,6 +223,14 @@ private function createDBUser(Connection $connection): void { $newUser = $baseUser . $i; } + if ($newUser !== $baseUser) { + $this->logger->info('Using alternate username for database user', [ + 'original' => $baseUser, + 'actual' => $newUser, + 'app' => 'pgsql.setup', + ]); + } + // Create the new user $query = $connection->prepare('CREATE USER "' . addslashes($newUser) . "\" CREATEDB PASSWORD '" . addslashes($newPassword) . "'"); $query->executeStatement(); @@ -238,6 +246,7 @@ private function createDBUser(Connection $connection): void { } catch (DatabaseException $e) { $this->logger->error('Error while trying to create database user', [ 'exception' => $e, + 'app' => 'pgsql.setup', ]); } } From 1cc43bfad59645be39081b56d71cc2c91324f265 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 18 Feb 2026 08:03:20 -0500 Subject: [PATCH 09/10] chore(setup/pgsql): php-cs fixup Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 647ee088393eb..22ed4209c4e17 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -23,7 +23,7 @@ class PostgreSQL extends AbstractDatabase { * This method handles two scenarios: * 1. Automatic setup: Creates user, database, and schema when connecting as superuser * 2. Manual setup: Uses pre-configured credentials when automatic setup fails - * + * * @throws DatabaseSetupException If database connection or setup fails */ public function setupDatabase(): void { @@ -91,8 +91,8 @@ private function performAutomaticSetup(): void { } catch (\Exception $e) { // @todo: catch more specific DatabaseException | DatabaseSetupException | \PDOException $e instead? // Automatic setup failed - log and continue with verification (upstream.. which will give up if necessary) $this->logger->warning('Automatic database setup failed, will attempt to verify manual configuration', [ - 'exception' => $e, - 'app' => 'pgsql.setup', + 'exception' => $e, + 'app' => 'pgsql.setup', ]); // Ensure credentials are saved even if automatic setup failed @@ -253,7 +253,7 @@ private function createDBUser(Connection $connection): void { /** * Verifies connection to the Nextcloud database with configured credentials. - * + * * @throws DatabaseSetupException If connection fails */ private function verifyDatabaseConnection(): void { From f9f27cec203eba775e1ba9308e41e1216a73a9cc Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 18 Feb 2026 08:30:30 -0500 Subject: [PATCH 10/10] fix(setup/pgsql): use injected ISecureRandom Signed-off-by: Josh --- lib/private/Setup/PostgreSQL.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index 22ed4209c4e17..2415b5cee413d 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -12,7 +12,6 @@ use OC\DB\Connection; use OC\DB\QueryBuilder\Literal; use OCP\Security\ISecureRandom; -use OCP\Server; class PostgreSQL extends AbstractDatabase { public $dbprettyname = 'PostgreSQL'; @@ -213,7 +212,7 @@ private function createDBUser(Connection $connection): void { // Generate Nextcloud-specific credentials so we don't need to store / use the db admin credentials $baseUser = 'oc_admin'; $newUser = $baseUser; - $newPassword = Server::get(ISecureRandom::class)->generate(30, ISecureRandom::CHAR_ALPHANUMERIC); + $newPassword = $this->random->generate(30, ISecureRandom::CHAR_ALPHANUMERIC); // Find/generate an available username try {