Explorar el Código

Possiblity to autoinstall in Docker Compose (#3353)

* Possiblity to autoinstall in Docker Compose

#fix https://github.com/FreshRSS/FreshRSS/issues/3349

It is simply calling our existing CLI: do-install.php and
create-user.php
https://github.com/FreshRSS/FreshRSS/tree/master/cli

FreshRSS will typically be ready a few seconds before the database, so
introduce a tolerance when the database is not available / up (yet) by
trying a few times to connect. Also useful to avoid service interruption
when DB service is restarted.

Example:

```yml
freshrss-app:
    image: freshrss/freshrss
    container_name:
freshrss-app
    hostname: freshrss-app
    restart: unless-stopped

ports:
      - "8080:80"
    depends_on:
      - freshrss-db

volumes:
      - data:/var/www/FreshRSS/data
      -
extensions:/var/www/FreshRSS/extensions
    environment:
      CRON_MIN:
'*/20'
      FRESHRSS_ENV: development
      FRESHRSS_INSTALL: |-

--api_enabled
        --base_url https://rss.example.net

--db-base freshrss
        --db-host freshrss-db
        --db-password
freshrss
        --db-type pgsql
        --db-user freshrss

--default_user admin
        --language en
      FRESHRSS_USER: |-

--api_password freshrss
        --email user@example.net

--language en
        --password freshrss
        --user admin
      TZ:
Europe/Paris
```

* Minor type f in find

* shellcheck
Alexandre Alapetite hace 5 años
padre
commit
8dfe209799
Se han modificado 6 ficheros con 131 adiciones y 69 borrados
  1. 5 7
      Docker/docker-compose.yml
  2. 52 7
      Docker/entrypoint.sh
  3. 7 4
      cli/_cli.php
  4. 2 2
      cli/create-user.php
  5. 2 2
      cli/do-install.php
  6. 63 47
      lib/Minz/ModelPdo.php

+ 5 - 7
Docker/docker-compose.yml

@@ -9,9 +9,9 @@ services:
     volumes:
       - db:/var/lib/postgresql/data
     environment:
-      - POSTGRES_USER=freshrss
-      - POSTGRES_PASSWORD=freshrss
-      - POSTGRES_DB=freshrss
+      POSTGRES_USER: freshrss
+      POSTGRES_PASSWORD: freshrss
+      POSTGRES_DB: freshrss
 
   freshrss-app:
     image: freshrss/freshrss:latest
@@ -26,10 +26,8 @@ services:
       - data:/var/www/FreshRSS/data
       - extensions:/var/www/FreshRSS/extensions
     environment:
-      - CRON_MIN=*/20
-      - TZ=Europe/Copenhagen
-    labels:
-      - "traefik.port=80"
+      CRON_MIN: '*/20'
+      TZ: Europe/Paris
 
 volumes:
   db:

+ 52 - 7
Docker/entrypoint.sh

@@ -2,15 +2,12 @@
 
 php -f ./cli/prepare.php >/dev/null
 
-chown -R :www-data .
-chmod -R g+r . && chmod -R g+w ./data/
-
-find /etc/php*/ -name php.ini -exec sed -r -i "\\#^;?date.timezone#s#^.*#date.timezone = $TZ#" {} \;
-find /etc/php*/ -name php.ini -exec sed -r -i "\\#^;?post_max_size#s#^.*#post_max_size = 32M#" {} \;
-find /etc/php*/ -name php.ini -exec sed -r -i "\\#^;?upload_max_filesize#s#^.*#upload_max_filesize = 32M#" {} \;
+find /etc/php*/ -type f -name php.ini -exec sed -r -i "\\#^;?date.timezone#s#^.*#date.timezone = $TZ#" {} \;
+find /etc/php*/ -type f -name php.ini -exec sed -r -i "\\#^;?post_max_size#s#^.*#post_max_size = 32M#" {} \;
+find /etc/php*/ -type f -name php.ini -exec sed -r -i "\\#^;?upload_max_filesize#s#^.*#upload_max_filesize = 32M#" {} \;
 
 if [ -n "$LISTEN" ]; then
-	find /etc/apache2/ -name FreshRSS.Apache.conf -exec sed -r -i "\\#^Listen#s#^.*#Listen $LISTEN#" {} \;
+	find /etc/apache2/ -type f -name FreshRSS.Apache.conf -exec sed -r -i "\\#^Listen#s#^.*#Listen $LISTEN#" {} \;
 fi
 
 if [ -n "$CRON_MIN" ]; then
@@ -23,4 +20,52 @@ if [ -n "$CRON_MIN" ]; then
 	crontab -l | sed -r "\\#FreshRSS#s#^[^ ]+ #$CRON_MIN #" | crontab -
 fi
 
+if [ -n "$FRESHRSS_INSTALL" ]; then
+	# shellcheck disable=SC2046
+	php -f ./cli/do-install.php -- \
+		$(echo "$FRESHRSS_INSTALL" | sed -r 's/[\r\n]+/\n/g' | paste -s -) \
+		1>/tmp/out.txt 2>/tmp/err.txt
+	EXITCODE=$?
+	grep -v 'Remember to' /tmp/out.txt
+	grep -v 'Please use' /tmp/err.txt 1>&2
+
+	if [ $EXITCODE -eq 3 ]; then
+		echo 'ℹ️ FreshRSS already installed; no change performed.'
+	elif [ $EXITCODE -eq 0 ]; then
+		echo '✅ FreshRSS successfully installed.'
+	else
+		rm -f /tmp/out.txt /tmp/err.txt
+		echo '❌ FreshRSS error during installation!'
+		exit $EXITCODE
+	fi
+
+	rm -f /tmp/out.txt /tmp/err.txt
+fi
+
+if [ -n "$FRESHRSS_USER" ]; then
+	# shellcheck disable=SC2046
+	php -f ./cli/create-user.php -- \
+		$(echo "$FRESHRSS_USER" | sed -r 's/[\r\n]+/\n/g' | paste -s -) \
+		1>/tmp/out.txt 2>/tmp/err.txt
+	EXITCODE=$?
+	grep -v 'Remember to' /tmp/out.txt
+	cat /tmp/err.txt 1>&2
+
+	if [ $EXITCODE -eq 3 ]; then
+		echo 'ℹ️ FreshRSS user already exists; no change performed.'
+	elif [ $EXITCODE -eq 0 ]; then
+		echo '✅ FreshRSS user successfully created.'
+		./cli/list-users.php | xargs -n1 ./cli/actualize-user.php --user
+	else
+		rm -f /tmp/out.txt /tmp/err.txt
+		echo '❌ FreshRSS error during the creation of a user!'
+		exit $EXITCODE
+	fi
+
+	rm -f /tmp/out.txt /tmp/err.txt
+fi
+
+chown -R :www-data .
+chmod -R g+r . && chmod -R g+w ./data/
+
 exec "$@"

+ 7 - 4
cli/_cli.php

@@ -3,6 +3,7 @@ if (php_sapi_name() !== 'cli') {
 	die('FreshRSS error: This PHP script may only be invoked from command line!');
 }
 
+const EXIT_CODE_ALREADY_EXISTS = 3;
 const REGEX_INPUT_OPTIONS = '/^--/';
 const REGEX_PARAM_OPTIONS = '/:*$/';
 
@@ -16,9 +17,9 @@ Minz_Translate::init('en');
 
 FreshRSS_Context::$isCli = true;
 
-function fail($message) {
+function fail($message, $exitCode=1) {
 	fwrite(STDERR, $message . "\n");
-	die(1);
+	die($exitCode);
 }
 
 function cliInitUser($username) {
@@ -39,12 +40,14 @@ function cliInitUser($username) {
 }
 
 function accessRights() {
-	echo ' Remember to re-apply the appropriate access rights, such as:' , "\n",
+	echo 'ℹ️ Remember to re-apply the appropriate access rights, such as:',
 		"\t", 'sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/', "\n";
 }
 
 function done($ok = true) {
-	fwrite(STDERR, 'Result: ' . ($ok ? 'success' : 'fail') . "\n");
+	if (!$ok) {
+		fwrite(STDERR, (empty($_SERVER['argv'][0]) ? 'Process' : basename($_SERVER['argv'][0])) . ' failed!' . "\n");
+	}
 	exit($ok ? 0 : 1);
 }
 

+ 2 - 2
cli/create-user.php

@@ -11,7 +11,7 @@ if (!FreshRSS_user_Controller::checkUsername($username)) {
 
 $usernames = listUsers();
 if (preg_grep("/^$username$/i", $usernames)) {
-	fail('FreshRSS error: username already taken “' . $username . '”');
+	fail('FreshRSS warning: username already exists “' . $username . '”', EXIT_CODE_ALREADY_EXISTS);
 }
 
 echo 'FreshRSS creating user “', $username, "”…\n";
@@ -38,7 +38,7 @@ if (!empty($options['api_password'])) {
 
 invalidateHttpCache(FreshRSS_Context::$system_conf->default_user);
 
-echo ' Remember to refresh the feeds of the user: ', $username , "\n",
+echo 'ℹ️ Remember to refresh the feeds of the user: ', $username ,
 	"\t", './cli/actualize-user.php --user ', $username, "\n";
 
 accessRights();

+ 2 - 2
cli/do-install.php

@@ -3,7 +3,7 @@
 require(__DIR__ . '/_cli.php');
 
 if (!file_exists(DATA_PATH . '/do-install.txt')) {
-	fail('FreshRSS seems to be already installed! Please use `./cli/reconfigure.php` instead.');
+	fail('FreshRSS seems to be already installed!' . "\n" . 'Please use `./cli/reconfigure.php` instead.', EXIT_CODE_ALREADY_EXISTS);
 }
 
 $params = array(
@@ -107,7 +107,7 @@ if (!$ok) {
 	fail('FreshRSS database error: ' . (empty($_SESSION['bd_error']) ? 'Unknown error' : $_SESSION['bd_error']));
 }
 
-echo ' Remember to create the default user: ', $config['default_user'] , "\n",
+echo 'ℹ️ Remember to create the default user: ', $config['default_user'],
 	"\t", './cli/create-user.php --user ', $config['default_user'], " --password 'password' --more-options\n";
 
 accessRights();

+ 63 - 47
lib/Minz/ModelPdo.php

@@ -21,6 +21,51 @@ class Minz_ModelPdo {
 	protected $pdo;
 	protected $current_user;
 
+	private function dbConnect() {
+		$db = Minz_Configuration::get('system')->db;
+		$driver_options = isset($db['pdo_options']) && is_array($db['pdo_options']) ? $db['pdo_options'] : [];
+		$dbServer = parse_url('db://' . $db['host']);
+		$dsn = '';
+		$dsnParams = empty($db['connection_uri_params']) ? '' : (';' . $db['connection_uri_params']);
+
+		switch ($db['type']) {
+			case 'mysql':
+				$dsn = 'mysql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']) . ';charset=utf8mb4';
+				if (!empty($db['base'])) {
+					$dsn .= ';dbname=' . $db['base'];
+				}
+				if (!empty($dbServer['port'])) {
+					$dsn .= ';port=' . $dbServer['port'];
+				}
+				$driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4';
+				$this->pdo = new Minz_PdoMysql($dsn . $dsnParams, $db['user'], $db['password'], $driver_options);
+				$this->pdo->setPrefix($db['prefix'] . $this->current_user . '_');
+				break;
+			case 'sqlite':
+				$dsn = 'sqlite:' . join_path(DATA_PATH, 'users', $this->current_user, 'db.sqlite');
+				$this->pdo = new Minz_PdoSqlite($dsn . $dsnParams, $db['user'], $db['password'], $driver_options);
+				$this->pdo->setPrefix('');
+				break;
+			case 'pgsql':
+				$dsn = 'pgsql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']);
+				if (!empty($db['base'])) {
+					$dsn .= ';dbname=' . $db['base'];
+				}
+				if (!empty($dbServer['port'])) {
+					$dsn .= ';port=' . $dbServer['port'];
+				}
+				$this->pdo = new Minz_PdoPgsql($dsn . $dsnParams, $db['user'], $db['password'], $driver_options);
+				$this->pdo->setPrefix($db['prefix'] . $this->current_user . '_');
+				break;
+			default:
+				throw new Minz_PDOConnectionException(
+					'Invalid database type!',
+					$db['user'], Minz_Exception::ERROR
+				);
+		}
+		self::$sharedPdo = $this->pdo;
+	}
+
 	/**
 	 * Créé la connexion à la base de données à l'aide des variables
 	 * HOST, BASE, USER et PASS définies dans le fichier de configuration
@@ -45,57 +90,28 @@ class Minz_ModelPdo {
 		$this->current_user = $currentUser;
 		self::$sharedCurrentUser = $currentUser;
 
-		$conf = Minz_Configuration::get('system');
-		$db = $conf->db;
-
-		$driver_options = isset($db['pdo_options']) && is_array($db['pdo_options']) ? $db['pdo_options'] : [];
-		$dbServer = parse_url('db://' . $db['host']);
-		$dsn = '';
-		$dsnParams = empty($db['connection_uri_params']) ? '' : (';' . $db['connection_uri_params']);
-
-		try {
-			switch ($db['type']) {
-				case 'mysql':
-					$dsn = 'mysql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']) . ';charset=utf8mb4';
-					if (!empty($db['base'])) {
-						$dsn .= ';dbname=' . $db['base'];
-					}
-					if (!empty($dbServer['port'])) {
-						$dsn .= ';port=' . $dbServer['port'];
-					}
-					$driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4';
-					$this->pdo = new Minz_PdoMysql($dsn . $dsnParams, $db['user'], $db['password'], $driver_options);
-					$this->pdo->setPrefix($db['prefix'] . $currentUser . '_');
-					break;
-				case 'sqlite':
-					$dsn = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite');
-					$this->pdo = new Minz_PdoSqlite($dsn . $dsnParams, $db['user'], $db['password'], $driver_options);
-					$this->pdo->setPrefix('');
+		$ex = null;
+		//Attempt a few times to connect to database
+		for ($attempt = 1; $attempt <= 5; $attempt++) {
+			try {
+				$this->dbConnect();
+				return;
+			} catch (PDOException $e) {
+				$ex = $e;
+				if (empty($e->errorInfo[0]) || $e->errorInfo[0] !== '08006') {
+					//We are only only interested in: SQLSTATE connection exception / connection failure
 					break;
-				case 'pgsql':
-					$dsn = 'pgsql:host=' . (empty($dbServer['host']) ? $db['host'] : $dbServer['host']);
-					if (!empty($db['base'])) {
-						$dsn .= ';dbname=' . $db['base'];
-					}
-					if (!empty($dbServer['port'])) {
-						$dsn .= ';port=' . $dbServer['port'];
-					}
-					$this->pdo = new Minz_PdoPgsql($dsn . $dsnParams, $db['user'], $db['password'], $driver_options);
-					$this->pdo->setPrefix($db['prefix'] . $currentUser . '_');
-					break;
-				default:
-					throw new Minz_PDOConnectionException(
-						'Invalid database type!',
-						$db['user'], Minz_Exception::ERROR
-					);
+				}
 			}
-			self::$sharedPdo = $this->pdo;
-		} catch (Exception $e) {
-			throw new Minz_PDOConnectionException(
-				$e->getMessage(),
+			sleep(2);
+		}
+
+		$db = Minz_Configuration::get('system')->db;
+
+		throw new Minz_PDOConnectionException(
+				$ex->getMessage(),
 				$db['user'], Minz_Exception::ERROR
 			);
-		}
 	}
 
 	public function beginTransaction() {