aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Setup
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Setup')
-rw-r--r--lib/private/Setup/AbstractDatabase.php138
-rw-r--r--lib/private/Setup/MySQL.php197
-rw-r--r--lib/private/Setup/OCI.php88
-rw-r--r--lib/private/Setup/PostgreSQL.php169
-rw-r--r--lib/private/Setup/Sqlite.php59
5 files changed, 651 insertions, 0 deletions
diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php
new file mode 100644
index 00000000000..8f6294faa66
--- /dev/null
+++ b/lib/private/Setup/AbstractDatabase.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Setup;
+
+use OC\DB\Connection;
+use OC\DB\ConnectionFactory;
+use OC\DB\MigrationService;
+use OC\SystemConfig;
+use OCP\IL10N;
+use OCP\Migration\IOutput;
+use OCP\Security\ISecureRandom;
+use Psr\Log\LoggerInterface;
+
+abstract class AbstractDatabase {
+ /** @var IL10N */
+ protected $trans;
+ /** @var string */
+ protected $dbUser;
+ /** @var string */
+ protected $dbPassword;
+ /** @var string */
+ protected $dbName;
+ /** @var string */
+ protected $dbHost;
+ /** @var string */
+ protected $dbPort;
+ /** @var string */
+ protected $tablePrefix;
+ /** @var SystemConfig */
+ protected $config;
+ /** @var LoggerInterface */
+ protected $logger;
+ /** @var ISecureRandom */
+ protected $random;
+ /** @var bool */
+ protected $tryCreateDbUser;
+
+ public function __construct(IL10N $trans, SystemConfig $config, LoggerInterface $logger, ISecureRandom $random) {
+ $this->trans = $trans;
+ $this->config = $config;
+ $this->logger = $logger;
+ $this->random = $random;
+ }
+
+ public function validate($config) {
+ $errors = [];
+ if (empty($config['dbuser']) && empty($config['dbname'])) {
+ $errors[] = $this->trans->t('Enter the database Login and name for %s', [$this->dbprettyname]);
+ } elseif (empty($config['dbuser'])) {
+ $errors[] = $this->trans->t('Enter the database Login for %s', [$this->dbprettyname]);
+ } elseif (empty($config['dbname'])) {
+ $errors[] = $this->trans->t('Enter the database name for %s', [$this->dbprettyname]);
+ }
+ if (substr_count($config['dbname'], '.') >= 1) {
+ $errors[] = $this->trans->t('You cannot use dots in the database name %s', [$this->dbprettyname]);
+ }
+ return $errors;
+ }
+
+ public function initialize($config) {
+ $dbUser = $config['dbuser'];
+ $dbPass = $config['dbpass'];
+ $dbName = $config['dbname'];
+ $dbHost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost';
+ $dbPort = !empty($config['dbport']) ? $config['dbport'] : '';
+ $dbTablePrefix = $config['dbtableprefix'] ?? 'oc_';
+
+ $createUserConfig = $this->config->getValue('setup_create_db_user', true);
+ // accept `false` both as bool and string, since setting config values from env will result in a string
+ $this->tryCreateDbUser = $createUserConfig !== false && $createUserConfig !== 'false';
+
+ $this->config->setValues([
+ 'dbname' => $dbName,
+ 'dbhost' => $dbHost,
+ 'dbtableprefix' => $dbTablePrefix,
+ ]);
+
+ $this->dbUser = $dbUser;
+ $this->dbPassword = $dbPass;
+ $this->dbName = $dbName;
+ $this->dbHost = $dbHost;
+ $this->dbPort = $dbPort;
+ $this->tablePrefix = $dbTablePrefix;
+ }
+
+ /**
+ * @param array $configOverwrite
+ * @return \OC\DB\Connection
+ */
+ protected function connect(array $configOverwrite = []): Connection {
+ $connectionParams = [
+ 'host' => $this->dbHost,
+ 'user' => $this->dbUser,
+ 'password' => $this->dbPassword,
+ 'tablePrefix' => $this->tablePrefix,
+ 'dbname' => $this->dbName
+ ];
+
+ // adding port support through installer
+ if (!empty($this->dbPort)) {
+ if (ctype_digit($this->dbPort)) {
+ $connectionParams['port'] = $this->dbPort;
+ } else {
+ $connectionParams['unix_socket'] = $this->dbPort;
+ }
+ } elseif (strpos($this->dbHost, ':')) {
+ // Host variable may carry a port or socket.
+ [$host, $portOrSocket] = explode(':', $this->dbHost, 2);
+ if (ctype_digit($portOrSocket)) {
+ $connectionParams['port'] = $portOrSocket;
+ } else {
+ $connectionParams['unix_socket'] = $portOrSocket;
+ }
+ $connectionParams['host'] = $host;
+ }
+ $connectionParams = array_merge($connectionParams, $configOverwrite);
+ $connectionParams = array_merge($connectionParams, ['primary' => $connectionParams, 'replica' => [$connectionParams]]);
+ $cf = new ConnectionFactory($this->config);
+ $connection = $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams);
+ $connection->ensureConnectedToPrimary();
+ return $connection;
+ }
+
+ abstract public function setupDatabase();
+
+ public function runMigrations(?IOutput $output = null) {
+ if (!is_dir(\OC::$SERVERROOT . '/core/Migrations')) {
+ return;
+ }
+ $ms = new MigrationService('core', \OC::$server->get(Connection::class), $output);
+ $ms->migrate('latest', true);
+ }
+}
diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php
new file mode 100644
index 00000000000..c4794a86743
--- /dev/null
+++ b/lib/private/Setup/MySQL.php
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Setup;
+
+use Doctrine\DBAL\Platforms\MySQL80Platform;
+use Doctrine\DBAL\Platforms\MySQL84Platform;
+use OC\DB\ConnectionAdapter;
+use OC\DB\MySqlTools;
+use OCP\IDBConnection;
+use OCP\Security\ISecureRandom;
+
+class MySQL extends AbstractDatabase {
+ public $dbprettyname = 'MySQL/MariaDB';
+
+ public function setupDatabase() {
+ //check if the database user has admin right
+ $connection = $this->connect(['dbname' => null]);
+
+ // detect mb4
+ $tools = new MySqlTools();
+ if ($tools->supports4ByteCharset(new ConnectionAdapter($connection))) {
+ $this->config->setValue('mysql.utf8mb4', true);
+ $connection = $this->connect(['dbname' => null]);
+ }
+
+ if ($this->tryCreateDbUser) {
+ $this->createSpecificUser('oc_admin', new ConnectionAdapter($connection));
+ }
+
+ $this->config->setValues([
+ 'dbuser' => $this->dbUser,
+ 'dbpassword' => $this->dbPassword,
+ ]);
+
+ //create the database
+ $this->createDatabase($connection);
+
+ //fill the database if needed
+ $query = 'select count(*) from information_schema.tables where table_schema=? AND table_name = ?';
+ $connection->executeQuery($query, [$this->dbName, $this->tablePrefix . 'users']);
+
+ $connection->close();
+ $connection = $this->connect();
+ try {
+ $connection->connect();
+ } catch (\Exception $e) {
+ $this->logger->error($e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ throw new \OC\DatabaseSetupException($this->trans->t('MySQL Login and/or password not valid'),
+ $this->trans->t('You need to enter details of an existing account.'), 0, $e);
+ }
+ }
+
+ /**
+ * @param \OC\DB\Connection $connection
+ */
+ private function createDatabase($connection): void {
+ try {
+ $name = $this->dbName;
+ $user = $this->dbUser;
+ //we can't use OC_DB functions here because we need to connect as the administrative user.
+ $characterSet = $this->config->getValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
+ $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE {$characterSet}_bin;";
+ $connection->executeUpdate($query);
+ } catch (\Exception $ex) {
+ $this->logger->error('Database creation failed.', [
+ 'exception' => $ex,
+ 'app' => 'mysql.setup',
+ ]);
+ return;
+ }
+
+ try {
+ //this query will fail if there aren't the right permissions, ignore the error
+ $query = "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER ON `$name` . * TO '$user'";
+ $connection->executeUpdate($query);
+ } catch (\Exception $ex) {
+ $this->logger->debug('Could not automatically grant privileges, this can be ignored if database user already had privileges.', [
+ 'exception' => $ex,
+ 'app' => 'mysql.setup',
+ ]);
+ }
+ }
+
+ /**
+ * @param IDBConnection $connection
+ * @throws \OC\DatabaseSetupException
+ */
+ private function createDBUser($connection): void {
+ $name = $this->dbUser;
+ $password = $this->dbPassword;
+
+ try {
+ // we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one,
+ // the anonymous user would take precedence when there is one.
+
+ if ($connection->getDatabasePlatform() instanceof MySQL84Platform) {
+ $query = "CREATE USER ?@'localhost' IDENTIFIED WITH caching_sha2_password BY ?";
+ $connection->executeStatement($query, [$name,$password]);
+ $query = "CREATE USER ?@'%' IDENTIFIED WITH caching_sha2_password BY ?";
+ $connection->executeStatement($query, [$name,$password]);
+ } elseif ($connection->getDatabasePlatform() instanceof Mysql80Platform) {
+ // TODO: Remove this elseif section as soon as MySQL 8.0 is out-of-support (after April 2026)
+ $query = "CREATE USER ?@'localhost' IDENTIFIED WITH mysql_native_password BY ?";
+ $connection->executeStatement($query, [$name,$password]);
+ $query = "CREATE USER ?@'%' IDENTIFIED WITH mysql_native_password BY ?";
+ $connection->executeStatement($query, [$name,$password]);
+ } else {
+ $query = "CREATE USER ?@'localhost' IDENTIFIED BY ?";
+ $connection->executeStatement($query, [$name,$password]);
+ $query = "CREATE USER ?@'%' IDENTIFIED BY ?";
+ $connection->executeStatement($query, [$name,$password]);
+ }
+ } catch (\Exception $ex) {
+ $this->logger->error('Database user creation failed.', [
+ 'exception' => $ex,
+ 'app' => 'mysql.setup',
+ ]);
+ throw $ex;
+ }
+ }
+
+ /**
+ * @param string $username
+ * @param IDBConnection $connection
+ */
+ private function createSpecificUser($username, $connection): void {
+ $rootUser = $this->dbUser;
+ $rootPassword = $this->dbPassword;
+
+ //create a random password so we don't need to store the admin password in the config file
+ $saveSymbols = str_replace(['\"', '\\', '\'', '`'], '', ISecureRandom::CHAR_SYMBOLS);
+ $password = $this->random->generate(22, ISecureRandom::CHAR_ALPHANUMERIC . $saveSymbols)
+ . $this->random->generate(2, ISecureRandom::CHAR_UPPER)
+ . $this->random->generate(2, ISecureRandom::CHAR_LOWER)
+ . $this->random->generate(2, ISecureRandom::CHAR_DIGITS)
+ . $this->random->generate(2, $saveSymbols);
+ $this->dbPassword = str_shuffle($password);
+
+ try {
+ //user already specified in config
+ $oldUser = $this->config->getValue('dbuser', false);
+
+ //we don't have a dbuser specified in config
+ if ($this->dbUser !== $oldUser) {
+ //add prefix to the admin username to prevent collisions
+ $adminUser = substr('oc_' . $username, 0, 16);
+
+ $i = 1;
+ while (true) {
+ //this should be enough to check for admin rights in mysql
+ $query = 'SELECT user FROM mysql.user WHERE user=?';
+ $result = $connection->executeQuery($query, [$adminUser]);
+
+ //current dbuser has admin rights
+ $data = $result->fetchAll();
+ $result->closeCursor();
+ //new dbuser does not exist
+ if (count($data) === 0) {
+ //use the admin login data for the new database user
+ $this->dbUser = $adminUser;
+ $this->createDBUser($connection);
+ // if sharding is used we need to manually call this for every shard as those also need the user setup!
+ /** @var ConnectionAdapter $connection */
+ foreach ($connection->getInner()->getShardConnections() as $shard) {
+ $this->createDBUser($shard);
+ }
+
+ break;
+ } else {
+ //repeat with different username
+ $length = strlen((string)$i);
+ $adminUser = substr('oc_' . $username, 0, 16 - $length) . $i;
+ $i++;
+ }
+ }
+ } else {
+ // Reuse existing password if a database config is already present
+ $this->dbPassword = $rootPassword;
+ }
+ } catch (\Exception $ex) {
+ $this->logger->info('Can not create a new MySQL user, will continue with the provided user.', [
+ 'exception' => $ex,
+ 'app' => 'mysql.setup',
+ ]);
+ // Restore the original credentials
+ $this->dbUser = $rootUser;
+ $this->dbPassword = $rootPassword;
+ }
+ }
+}
diff --git a/lib/private/Setup/OCI.php b/lib/private/Setup/OCI.php
new file mode 100644
index 00000000000..61c7f968787
--- /dev/null
+++ b/lib/private/Setup/OCI.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Setup;
+
+class OCI extends AbstractDatabase {
+ public $dbprettyname = 'Oracle';
+
+ protected $dbtablespace;
+
+ public function initialize($config) {
+ parent::initialize($config);
+ if (array_key_exists('dbtablespace', $config)) {
+ $this->dbtablespace = $config['dbtablespace'];
+ } else {
+ $this->dbtablespace = 'USERS';
+ }
+ // allow empty hostname for oracle
+ $this->dbHost = $config['dbhost'];
+
+ $this->config->setValues([
+ 'dbhost' => $this->dbHost,
+ 'dbtablespace' => $this->dbtablespace,
+ ]);
+ }
+
+ public function validate($config) {
+ $errors = [];
+ if (empty($config['dbuser']) && empty($config['dbname'])) {
+ $errors[] = $this->trans->t('Enter the database Login and name for %s', [$this->dbprettyname]);
+ } elseif (empty($config['dbuser'])) {
+ $errors[] = $this->trans->t('Enter the database Login for %s', [$this->dbprettyname]);
+ } elseif (empty($config['dbname'])) {
+ $errors[] = $this->trans->t('Enter the database name for %s', [$this->dbprettyname]);
+ }
+ return $errors;
+ }
+
+ public function setupDatabase() {
+ try {
+ $this->connect();
+ } catch (\Exception $e) {
+ $errorMessage = $this->getLastError();
+ if ($errorMessage) {
+ throw new \OC\DatabaseSetupException($this->trans->t('Oracle connection could not be established'),
+ $errorMessage . ' Check environment: ORACLE_HOME=' . getenv('ORACLE_HOME')
+ . ' ORACLE_SID=' . getenv('ORACLE_SID')
+ . ' LD_LIBRARY_PATH=' . getenv('LD_LIBRARY_PATH')
+ . ' NLS_LANG=' . getenv('NLS_LANG')
+ . ' tnsnames.ora is ' . (is_readable(getenv('ORACLE_HOME') . '/network/admin/tnsnames.ora') ? '' : 'not ') . 'readable', 0, $e);
+ }
+ throw new \OC\DatabaseSetupException($this->trans->t('Oracle Login and/or password not valid'),
+ 'Check environment: ORACLE_HOME=' . getenv('ORACLE_HOME')
+ . ' ORACLE_SID=' . getenv('ORACLE_SID')
+ . ' LD_LIBRARY_PATH=' . getenv('LD_LIBRARY_PATH')
+ . ' NLS_LANG=' . getenv('NLS_LANG')
+ . ' tnsnames.ora is ' . (is_readable(getenv('ORACLE_HOME') . '/network/admin/tnsnames.ora') ? '' : 'not ') . 'readable', 0, $e);
+ }
+
+ $this->config->setValues([
+ 'dbuser' => $this->dbUser,
+ 'dbname' => $this->dbName,
+ 'dbpassword' => $this->dbPassword,
+ ]);
+ }
+
+ /**
+ * @param resource $connection
+ * @return string
+ */
+ protected function getLastError($connection = null) {
+ if ($connection) {
+ $error = oci_error($connection);
+ } else {
+ $error = oci_error();
+ }
+ foreach (['message', 'code'] as $key) {
+ if (isset($error[$key])) {
+ return $error[$key];
+ }
+ }
+ return '';
+ }
+}
diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php
new file mode 100644
index 00000000000..9a686db2e54
--- /dev/null
+++ b/lib/private/Setup/PostgreSQL.php
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Setup;
+
+use OC\DatabaseException;
+use OC\DB\Connection;
+use OC\DB\QueryBuilder\Literal;
+use OCP\Security\ISecureRandom;
+
+class PostgreSQL extends AbstractDatabase {
+ public $dbprettyname = 'PostgreSQL';
+
+ /**
+ * @throws \OC\DatabaseSetupException
+ */
+ public function setupDatabase() {
+ 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->execute();
+ $canCreateRoles = $result->rowCount() > 0;
+ } catch (DatabaseException $e) {
+ $canCreateRoles = false;
+ }
+
+ 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 = \OC::$server->get(ISecureRandom::class)->generate(30, ISecureRandom::CHAR_ALPHANUMERIC);
+
+ $this->createDBUser($connection);
+ }
+ }
+
+ $this->config->setValues([
+ 'dbuser' => $this->dbUser,
+ 'dbpassword' => $this->dbPassword,
+ ]);
+
+ //create the database
+ $this->createDatabase($connection);
+ // the connection to dbname=postgres is not needed anymore
+ $connection->close();
+
+ 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
+ $connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO "' . addslashes($this->dbUser) . '"');
+ $connectionMainDatabase->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,
+ ]);
+ $this->config->setValues([
+ 'dbuser' => $this->dbUser,
+ 'dbpassword' => $this->dbPassword,
+ ]);
+ }
+
+ // 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 \OC\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);
+ }
+ }
+
+ private function createDatabase(Connection $connection) {
+ if (!$this->databaseExists($connection)) {
+ //The database does not exists... let's create it
+ $query = $connection->prepare('CREATE DATABASE ' . addslashes($this->dbName) . ' OWNER "' . addslashes($this->dbUser) . '"');
+ try {
+ $query->execute();
+ } catch (DatabaseException $e) {
+ $this->logger->error('Error while trying to create database', [
+ 'exception' => $e,
+ ]);
+ }
+ } else {
+ $query = $connection->prepare('REVOKE ALL PRIVILEGES ON DATABASE ' . addslashes($this->dbName) . ' FROM PUBLIC');
+ try {
+ $query->execute();
+ } catch (DatabaseException $e) {
+ $this->logger->error('Error while trying to restrict database permissions', [
+ 'exception' => $e,
+ ]);
+ }
+ }
+ }
+
+ private function userExists(Connection $connection) {
+ $builder = $connection->getQueryBuilder();
+ $builder->automaticTablePrefix(false);
+ $query = $builder->select('*')
+ ->from('pg_roles')
+ ->where($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser)));
+ $result = $query->executeQuery();
+ return $result->rowCount() > 0;
+ }
+
+ private function databaseExists(Connection $connection) {
+ $builder = $connection->getQueryBuilder();
+ $builder->automaticTablePrefix(false);
+ $query = $builder->select('datname')
+ ->from('pg_database')
+ ->where($builder->expr()->eq('datname', $builder->createNamedParameter($this->dbName)));
+ $result = $query->executeQuery();
+ return $result->rowCount() > 0;
+ }
+
+ private function createDBUser(Connection $connection) {
+ $dbUser = $this->dbUser;
+ try {
+ $i = 1;
+ while ($this->userExists($connection)) {
+ $i++;
+ $this->dbUser = $dbUser . $i;
+ }
+
+ // create the user
+ $query = $connection->prepare('CREATE USER "' . addslashes($this->dbUser) . "\" CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'");
+ $query->execute();
+ if ($this->databaseExists($connection)) {
+ $query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO "' . addslashes($this->dbUser) . '"');
+ $query->execute();
+ }
+ } catch (DatabaseException $e) {
+ $this->logger->error('Error while trying to create database user', [
+ 'exception' => $e,
+ ]);
+ }
+ }
+}
diff --git a/lib/private/Setup/Sqlite.php b/lib/private/Setup/Sqlite.php
new file mode 100644
index 00000000000..b34b1e32ede
--- /dev/null
+++ b/lib/private/Setup/Sqlite.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Setup;
+
+use OC\DB\ConnectionFactory;
+
+class Sqlite extends AbstractDatabase {
+ public $dbprettyname = 'Sqlite';
+
+ public function validate($config) {
+ return [];
+ }
+
+ public function initialize($config) {
+ /*
+ * Web: When using web based installer its not possible to set dbname
+ * or dbtableprefix. Defaults used from ConnectionFactory and dbtype = 'sqlite'
+ * is written to config.php.
+ *
+ * Cli: When --database-name or --database-table-prefix empty or default
+ * dbtype = 'sqlite' is written to config.php. If you choose a value different
+ * from default these values are written to config.php. This is required because
+ * in connection factory configuration is obtained from config.php.
+ */
+
+ $this->dbName = empty($config['dbname'])
+ ? ConnectionFactory::DEFAULT_DBNAME
+ : $config['dbname'];
+
+ $this->tablePrefix = empty($config['dbtableprefix'])
+ ? ConnectionFactory::DEFAULT_DBTABLEPREFIX
+ : $config['dbtableprefix'];
+
+ if ($this->dbName !== ConnectionFactory::DEFAULT_DBNAME) {
+ $this->config->setValue('dbname', $this->dbName);
+ }
+
+ if ($this->tablePrefix !== ConnectionFactory::DEFAULT_DBTABLEPREFIX) {
+ $this->config->setValue('dbtableprefix', $this->tablePrefix);
+ }
+ }
+
+ public function setupDatabase() {
+ $datadir = $this->config->getValue(
+ 'datadirectory',
+ \OC::$SERVERROOT . '/data'
+ );
+
+ $sqliteFile = $datadir . '/' . $this->dbName . 'db';
+ if (file_exists($sqliteFile)) {
+ unlink($sqliteFile);
+ }
+ }
+}