Просмотр исходного кода

CLI do-install

https://github.com/FreshRSS/FreshRSS/issues/1095
https://github.com/FreshRSS/FreshRSS/issues/1090
Alexandre Alapetite 9 лет назад
Родитель
Сommit
ab4ece6780
6 измененных файлов с 260 добавлено и 140 удалено
  1. 1 4
      app/Controllers/userController.php
  2. 28 133
      app/install.php
  3. 6 1
      cli/_cli.php
  4. 8 2
      cli/create-user.php
  5. 102 0
      cli/do-install.php
  6. 115 0
      lib/lib_install.php

+ 1 - 4
app/Controllers/userController.php

@@ -24,7 +24,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		}
 	}
 
-	private static function hashPassword($passwordPlain) {
+	public static function hashPassword($passwordPlain) {
 		if (!function_exists('password_hash')) {
 			include_once(LIB_PATH . '/password_compat.php');
 		}
@@ -112,9 +112,6 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 				$userConfig['language'] = 'en';
 			}
 
-			$default_user = FreshRSS_Context::$system_conf->default_user;
-			$ok &= (strcasecmp($new_user_name, $default_user) !== 0);	//It is forbidden to alter the default user
-
 			$ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers()));	//Not an existing user, case-insensitive
 
 			$configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php');

+ 28 - 133
app/install.php

@@ -4,15 +4,12 @@ if (function_exists('opcache_reset')) {
 }
 header("Content-Security-Policy: default-src 'self'");
 
-define('BCRYPT_COST', 9);
+require(LIB_PATH . '/lib_install.php');
 
 session_name('FreshRSS');
 session_set_cookie_params(0, dirname(empty($_SERVER['REQUEST_URI']) ? '/' : dirname($_SERVER['REQUEST_URI'])), null, false, true);
 session_start();
 
-Minz_Configuration::register('default_system', join_path(DATA_PATH, 'config.default.php'));
-Minz_Configuration::register('default_user', join_path(USERS_PATH, '_', 'config.default.php'));
-
 if (isset($_GET['step'])) {
 	define('STEP',(int)$_GET['step']);
 } else {
@@ -26,13 +23,13 @@ if (STEP === 3 && isset($_POST['type'])) {
 if (isset($_SESSION['bd_type'])) {
 	switch ($_SESSION['bd_type']) {
 	case 'mysql':
-		include(APP_PATH . '/SQL/install.sql.mysql.php');
+		include_once(APP_PATH . '/SQL/install.sql.mysql.php');
 		break;
 	case 'sqlite':
-		include(APP_PATH . '/SQL/install.sql.sqlite.php');
+		include_once(APP_PATH . '/SQL/install.sql.sqlite.php');
 		break;
 	case 'pgsql':
-		include(APP_PATH . '/SQL/install.sql.pgsql.php');
+		include_once(APP_PATH . '/SQL/install.sql.pgsql.php');
 		break;
 	}
 }
@@ -131,12 +128,7 @@ function saveStep2() {
 
 		$password_plain = param('passwordPlain', false);
 		if ($password_plain !== false && cryptAvailable()) {
-			if (!function_exists('password_hash')) {
-				include_once(LIB_PATH . '/password_compat.php');
-			}
-			$passwordHash = password_hash($password_plain, PASSWORD_BCRYPT, array('cost' => BCRYPT_COST));
-			$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
-			$_SESSION['passwordHash'] = $passwordHash;
+			$_SESSION['passwordHash'] = FreshRSS_user_Controller::hashPassword($password_plain);
 		}
 
 		if (empty($_SESSION['old_entries']) ||
@@ -149,7 +141,7 @@ function saveStep2() {
 			return false;
 		}
 
-		$_SESSION['salt'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__)));
+		$_SESSION['salt'] = generateSalt();
 		if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) {
 			$_SESSION['old_entries'] = $user_default_config->old_entries;
 		}
@@ -171,7 +163,7 @@ function saveStep2() {
 
 		recursive_unlink($user_dir);
 		mkdir($user_dir);
-		file_put_contents($user_config_path, "<?php\n return " . var_export($config_array, true) . ';');
+		file_put_contents($user_config_path, "<?php\n return " . var_export($config_array, true) . ";\n");
 
 		header('Location: index.php?step=3');
 	}
@@ -225,35 +217,29 @@ function saveStep3() {
 		);
 
 		@unlink(join_path(DATA_PATH, 'config.php'));	//To avoid access-rights problems
-		file_put_contents(join_path(DATA_PATH, 'config.php'), "<?php\n return " . var_export($config_array, true) . ';');
+		file_put_contents(join_path(DATA_PATH, 'config.php'), "<?php\n return " . var_export($config_array, true) . ";\n");
 
-		$res = checkBD();
+		$config_array['db']['default_user'] = $config_array['default_user'];
+		$ok = checkDb($config_array['db']) && checkDbUser($config_array['db']);
+		if (!$ok) {
+			@unlink(join_path(DATA_PATH, 'config.php'));
+		}
 
-		if ($res) {
+		if ($ok) {
 			$_SESSION['bd_error'] = '';
 			header('Location: index.php?step=4');
-		} elseif (empty($_SESSION['bd_error'])) {
-			$_SESSION['bd_error'] = 'Unknown error!';
+		} else {
+			$_SESSION['bd_error'] = empty(config_array['db']['bd_error']) ? 'Unknown error!' : config_array['db']['bd_error'];
 		}
 	}
 	invalidateHttpCache();
 }
 
-function deleteInstall() {
-	$res = unlink(join_path(DATA_PATH, 'do-install.txt'));
-
-	if (!$res) {
-		return false;
-	}
-
-	header('Location: index.php');
-}
-
 
 /*** VÉRIFICATIONS ***/
 function checkStep() {
 	$s0 = checkStep0();
-	$s1 = checkStep1();
+	$s1 = checkRequirements();
 	$s2 = checkStep2();
 	$s3 = checkStep3();
 	if (STEP > 0 && $s0['all'] != 'ok') {
@@ -279,49 +265,6 @@ function checkStep0() {
 	);
 }
 
-function checkStep1() {
-	$php = version_compare(PHP_VERSION, '5.3.3') >= 0;
-	$minz = file_exists(join_path(LIB_PATH, 'Minz'));
-	$curl = extension_loaded('curl');
-	$pdo_mysql = extension_loaded('pdo_mysql');
-	$pdo_sqlite = extension_loaded('pdo_sqlite');
-	$pdo_pgsql = extension_loaded('pdo_pgsql');
-	$pdo = $pdo_mysql || $pdo_sqlite || $pdo_pgsql;
-	$pcre = extension_loaded('pcre');
-	$ctype = extension_loaded('ctype');
-	$dom = class_exists('DOMDocument');
-	$xml = function_exists('xml_parser_create');
-	$json = function_exists('json_encode');
-	$data = DATA_PATH && is_writable(DATA_PATH);
-	$cache = CACHE_PATH && is_writable(CACHE_PATH);
-	$users = USERS_PATH && is_writable(USERS_PATH);
-	$favicons = is_writable(join_path(DATA_PATH, 'favicons'));
-	$http_referer = is_referer_from_same_domain();
-
-	return array(
-		'php' => $php ? 'ok' : 'ko',
-		'minz' => $minz ? 'ok' : 'ko',
-		'curl' => $curl ? 'ok' : 'ko',
-		'pdo-mysql' => $pdo_mysql ? 'ok' : 'ko',
-		'pdo-sqlite' => $pdo_sqlite ? 'ok' : 'ko',
-		'pdo-pgsql' => $pdo_pgsql ? 'ok' : 'ko',
-		'pdo' => $pdo ? 'ok' : 'ko',
-		'pcre' => $pcre ? 'ok' : 'ko',
-		'ctype' => $ctype ? 'ok' : 'ko',
-		'dom' => $dom ? 'ok' : 'ko',
-		'xml' => $xml ? 'ok' : 'ko',
-		'json' => $json ? 'ok' : 'ko',
-		'data' => $data ? 'ok' : 'ko',
-		'cache' => $cache ? 'ok' : 'ko',
-		'users' => $users ? 'ok' : 'ko',
-		'favicons' => $favicons ? 'ok' : 'ko',
-		'http_referer' => $http_referer ? 'ok' : 'ko',
-		'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && $xml &&
-		         $data && $cache && $users && $favicons && $http_referer ?
-		         'ok' : 'ko'
-	);
-}
-
 function freshrss_already_installed() {
 	$conf_path = join_path(DATA_PATH, 'config.php');
 	if (!file_exists($conf_path)) {
@@ -392,60 +335,15 @@ function checkStep3() {
 	);
 }
 
-function checkBD() {
+function checkDbUser(&$dbOptions) {
 	$ok = false;
-
+	$str = $dbOptions['bd_dsn'];
+	$driver_options = $dbOptions['bd_options'];
 	try {
-		$str = '';
-		$driver_options = null;
-		switch ($_SESSION['bd_type']) {
-		case 'mysql':
-			$driver_options = array(
-				PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4'
-			);
-
-			try {	// on ouvre une connexion juste pour créer la base si elle n'existe pas
-				$str = 'mysql:host=' . $_SESSION['bd_host'] . ';';
-				$c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
-				$sql = sprintf(SQL_CREATE_DB, $_SESSION['bd_base']);
-				$res = $c->query($sql);
-			} catch (PDOException $e) {
-			}
-
-			// on écrase la précédente connexion en sélectionnant la nouvelle BDD
-			$str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
-			break;
-		case 'sqlite':
-			$str = 'sqlite:' . join_path(USERS_PATH, $_SESSION['default_user'], 'db.sqlite');
-			$driver_options = array(
-				PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
-			);
-			break;
-		case 'pgsql':
-			$driver_options = array(
-				PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
-			);
-
-			try {	// on ouvre une connexion juste pour créer la base si elle n'existe pas
-				$str = 'pgsql:host=' . $_SESSION['bd_host'] . ';dbname=postgres';
-				$c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
-				$sql = sprintf(SQL_CREATE_DB, $_SESSION['bd_base']);
-				$res = $c->query($sql);
-			} catch (PDOException $e) {
-				syslog(LOG_DEBUG, 'pgsql ' . $e->getMessage());
-			}
-
-			// on écrase la précédente connexion en sélectionnant la nouvelle BDD
-			$str = 'pgsql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
-			break;
-		default:
-			return false;
-		}
-
-		$c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
+		$c = new PDO($str, $dbOptions['bd_user'], $dbOptions['bd_password'], $driver_options);
 
 		if (defined('SQL_CREATE_TABLES')) {
-			$sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user'], _t('gen.short.default_category'));
+			$sql = sprintf(SQL_CREATE_TABLES, $dbOptions['bd_prefix_user'], _t('gen.short.default_category'));
 			$stm = $c->prepare($sql);
 			$ok = $stm->execute();
 		} else {
@@ -453,7 +351,7 @@ function checkBD() {
 			if (is_array($SQL_CREATE_TABLES)) {
 				$ok = true;
 				foreach ($SQL_CREATE_TABLES as $instruction) {
-					$sql = sprintf($instruction, $_SESSION['bd_prefix_user'], _t('gen.short.default_category'));
+					$sql = sprintf($instruction, $dbOptions['bd_prefix_user'], _t('gen.short.default_category'));
 					$stm = $c->prepare($sql);
 					$ok &= $stm->execute();
 				}
@@ -461,13 +359,8 @@ function checkBD() {
 		}
 	} catch (PDOException $e) {
 		$ok = false;
-		$_SESSION['bd_error'] = $e->getMessage();
+		$dbOptions['bd_error'] = $e->getMessage();
 	}
-
-	if (!$ok) {
-		@unlink(join_path(DATA_PATH, 'config.php'));
-	}
-
 	return $ok;
 }
 
@@ -510,7 +403,7 @@ function printStep0() {
 
 // @todo refactor this view with the check_install action
 function printStep1() {
-	$res = checkStep1();
+	$res = checkRequirements();
 ?>
 	<noscript><p class="alert alert-warn"><span class="alert-head"><?php echo _t('gen.short.attention'); ?></span> <?php echo _t('install.javascript_is_better'); ?></p></noscript>
 
@@ -805,7 +698,9 @@ case 3:
 case 4:
 	break;
 case 5:
-	deleteInstall();
+	if (deleteInstall()) {
+		header('Location: index.php');
+	}
 	break;
 }
 ?>

+ 6 - 1
cli/_cli.php

@@ -38,7 +38,12 @@ function cliInitUser($username) {
 	return $username;
 }
 
-function done($ok) {
+function accessRights() {
+	echo '• Remember to re-apply the appropriate access rights, such as:' , "\n",
+		"\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");
 	exit($ok ? 0 : 1);
 }

+ 8 - 2
cli/create-user.php

@@ -12,8 +12,8 @@ $options = getopt('', array(
 	));
 
 if (empty($options['user'])) {
-	fail('Usage: ' . basename(__FILE__) . " --user username --password 'password' --api-password 'api_password'" .
-		" --language en --email user@example.net --token 'longRandomString'");
+	fail('Usage: ' . basename(__FILE__) . " --user username ( --password 'password' --api-password 'api_password'" .
+		" --language en --email user@example.net --token 'longRandomString' )");
 }
 $username = $options['user'];
 if (!ctype_alnum($username)) {
@@ -35,6 +35,12 @@ $ok = FreshRSS_user_Controller::createUser($username,
 		'token' => empty($options['token']) ? '' : $options['token'],
 	));
 
+if (!$ok) {
+	fail('FreshRSS could not create user!');
+}
+
 invalidateHttpCache(FreshRSS_Context::$system_conf->default_user);
 
+accessRights();
+
 done($ok);

+ 102 - 0
cli/do-install.php

@@ -0,0 +1,102 @@
+#!/usr/bin/php
+<?php
+require('_cli.php');
+require(LIB_PATH . '/lib_install.php');
+
+$params = array(
+		'environment:',
+		'base_url:',
+		'language:',
+		'title:',
+		'default_user:',
+		'allow_anonymous',
+		'allow_anonymous_refresh',
+		'auth_type:',
+		'api_enabled',
+		'allow_robots',
+	);
+
+$dBparams = array(
+		'db-type:',
+		'db-host:',
+		'db-user:',
+		'db-password:',
+		'db-base:',
+		'db-prefix:',
+	);
+
+$options = getopt('', array_merge($params, $dBparams));
+
+if (empty($options['default_user']) || empty($options['auth_type'])) {
+	fail('Usage: ' . basename(__FILE__) . " --default_user admin --auth_type form" .
+		" ( --environment production --base_url https://rss.example.net/" .
+		" --language en --title FreshRSS --allow_anonymous --api_enabled" .
+		" --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123" .
+		" --db-base freshrss --db-prefix freshrss )");
+}
+
+fwrite(STDERR, 'FreshRSS install…' . "\n");
+
+$requirements = checkRequirements();
+if ($requirements['all'] !== 'ok') {
+	$message = 'FreshRSS install failed requirements:' . "\n";
+	foreach ($requirements as $requirement => $check) {
+		if ($check !== 'ok' && $requirement !== 'all') {
+			$message .= '• ' . $requirement . "\n";
+		}
+	}
+	fail($message);
+}
+
+if (!ctype_alnum($options['default_user'])) {
+	fail('FreshRSS invalid default username (must be ASCII alphanumeric): ' . $options['default_user']);
+}
+
+if (!in_array($options['auth_type'], array('form', 'http_auth', 'none'))) {
+	fail('FreshRSS invalid authentication method (auth_type must be one of { form, http_auth, none }: ' . $options['auth_type']);
+}
+
+$config = array(
+		'salt' => generateSalt(),
+		'db' => FreshRSS_Context::$system_conf->db,
+	);
+
+foreach ($params as $param) {
+	$param = rtrim($param, ':');
+	if (isset($options[$param])) {
+		$config[$param] = $options[$param] === false ? true : $options[$param];
+	}
+}
+
+if ((!empty($config['base_url'])) && server_is_public($config['base_url'])) {
+	$config['pubsubhubbub_enabled'] = true;
+}
+
+foreach ($dBparams as $dBparam) {
+	$dBparam = rtrim($dBparam, ':');
+	if (!empty($options[$dBparam])) {
+		$param = substr($dBparam, strlen('db-'));
+		$config['db'][$param] = $options[$dBparam];
+	}
+}
+
+if (file_put_contents(join_path(DATA_PATH, 'config.php'), "<?php\n return " . var_export($config, true) . ";\n") === false) {
+	fail('FreshRSS could not write configuration file!: ' . join_path(DATA_PATH, 'config.php'));
+}
+
+$config['db']['default_user'] = $config['default_user'];
+if (!checkDb($config['db'])) {
+	@unlink(join_path(DATA_PATH, 'config.php'));
+	fail('FreshRSS database error: ' . (empty($config['db']['bd_error']) ? 'Unknown error' : $config['db']['bd_error']));
+}
+
+echo '• Remember to create the default user: ', $config['default_user'] , "\n",
+	"\t", './cli/create-user.php --user ', $config['default_user'] , " --password 'password' --more-options\n";
+
+accessRights();
+
+if (!deleteInstall()) {
+	fail('FreshRSS access right problem while deleting install file!');
+}
+
+done();

+ 115 - 0
lib/lib_install.php

@@ -0,0 +1,115 @@
+<?php
+
+define('BCRYPT_COST', 9);
+
+Minz_Configuration::register('default_system', join_path(DATA_PATH, 'config.default.php'));
+Minz_Configuration::register('default_user', join_path(USERS_PATH, '_', 'config.default.php'));
+
+function checkRequirements() {
+	$php = version_compare(PHP_VERSION, '5.3.3') >= 0;
+	$minz = file_exists(join_path(LIB_PATH, 'Minz'));
+	$curl = extension_loaded('curl');
+	$pdo_mysql = extension_loaded('pdo_mysql');
+	$pdo_sqlite = extension_loaded('pdo_sqlite');
+	$pdo_pgsql = extension_loaded('pdo_pgsql');
+	$pdo = $pdo_mysql || $pdo_sqlite || $pdo_pgsql;
+	$pcre = extension_loaded('pcre');
+	$ctype = extension_loaded('ctype');
+	$dom = class_exists('DOMDocument');
+	$xml = function_exists('xml_parser_create');
+	$json = function_exists('json_encode');
+	$data = DATA_PATH && is_writable(DATA_PATH);
+	$cache = CACHE_PATH && is_writable(CACHE_PATH);
+	$users = USERS_PATH && is_writable(USERS_PATH);
+	$favicons = is_writable(join_path(DATA_PATH, 'favicons'));
+	$http_referer = is_referer_from_same_domain();
+
+	return array(
+		'php' => $php ? 'ok' : 'ko',
+		'minz' => $minz ? 'ok' : 'ko',
+		'curl' => $curl ? 'ok' : 'ko',
+		'pdo-mysql' => $pdo_mysql ? 'ok' : 'ko',
+		'pdo-sqlite' => $pdo_sqlite ? 'ok' : 'ko',
+		'pdo-pgsql' => $pdo_pgsql ? 'ok' : 'ko',
+		'pdo' => $pdo ? 'ok' : 'ko',
+		'pcre' => $pcre ? 'ok' : 'ko',
+		'ctype' => $ctype ? 'ok' : 'ko',
+		'dom' => $dom ? 'ok' : 'ko',
+		'xml' => $xml ? 'ok' : 'ko',
+		'json' => $json ? 'ok' : 'ko',
+		'data' => $data ? 'ok' : 'ko',
+		'cache' => $cache ? 'ok' : 'ko',
+		'users' => $users ? 'ok' : 'ko',
+		'favicons' => $favicons ? 'ok' : 'ko',
+		'http_referer' => $http_referer ? 'ok' : 'ko',
+		'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && $xml &&
+		         $data && $cache && $users && $favicons && $http_referer ?
+		         'ok' : 'ko'
+	);
+}
+
+function generateSalt() {
+	return sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__)));
+}
+
+function checkDb(&$dbOptions) {
+	$dsn = '';
+	try {
+		$driver_options = null;
+		switch ($dbOptions['type']) {
+		case 'mysql':
+			include_once(APP_PATH . '/SQL/install.sql.mysql.php');
+			$driver_options = array(
+				PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4'
+			);
+			try {	// on ouvre une connexion juste pour créer la base si elle n'existe pas
+				$dsn = 'mysql:host=' . $dbOptions['host'] . ';';
+				$c = new PDO($dsn, $dbOptions['user'], $dbOptions['password'], $driver_options);
+				$sql = sprintf(SQL_CREATE_DB, $dbOptions['base']);
+				$res = $c->query($sql);
+			} catch (PDOException $e) {
+				syslog(LOG_DEBUG, 'FreshRSS MySQL warning: ' . $e->getMessage());
+			}
+			// on écrase la précédente connexion en sélectionnant la nouvelle BDD
+			$dsn = 'mysql:host=' . $dbOptions['host'] . ';dbname=' . $dbOptions['base'];
+			break;
+		case 'sqlite':
+			include_once(APP_PATH . '/SQL/install.sql.sqlite.php');
+			$dsn = 'sqlite:' . join_path(USERS_PATH, $dbOptions['default_user'], 'db.sqlite');
+			$driver_options = array(
+				PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+			);
+			break;
+		case 'pgsql':
+			include_once(APP_PATH . '/SQL/install.sql.pgsql.php');
+			$driver_options = array(
+				PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+			);
+			try {	// on ouvre une connexion juste pour créer la base si elle n'existe pas
+				$dsn = 'pgsql:host=' . $dbOptions['host'] . ';dbname=postgres';
+				$c = new PDO($dsn, $dbOptions['user'], $dbOptions['password'], $driver_options);
+				$sql = sprintf(SQL_CREATE_DB, $dbOptions['base']);
+				$res = $c->query($sql);
+			} catch (PDOException $e) {
+				syslog(LOG_DEBUG, 'FreshRSS PostgreSQL warning: ' . $e->getMessage());
+			}
+			// on écrase la précédente connexion en sélectionnant la nouvelle BDD
+			$dsn = 'pgsql:host=' . $dbOptions['host'] . ';dbname=' . $dbOptions['base'];
+			break;
+		default:
+			return false;
+		}
+	} catch (PDOException $e) {
+		$dsn = '';
+		$dbOptions['error'] = $e->getMessage();
+	}
+	$dbOptions['dsn'] = $dsn;
+	$dbOptions['options'] = $driver_options;
+	return $dsn != '';
+}
+
+function deleteInstall() {
+	$path = join_path(DATA_PATH, 'do-install.txt');
+	@unlink($path);
+	return !file_exists($path);
+}