748 lines
18 KiB
PHP
748 lines
18 KiB
PHP
<?php
|
|
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
|
|
|
|
namespace Icinga\Application;
|
|
|
|
use DirectoryIterator;
|
|
use ErrorException;
|
|
use Exception;
|
|
use Icinga\Application\ProvidedHook\DbMigration;
|
|
use ipl\I18n\GettextTranslator;
|
|
use ipl\I18n\StaticTranslator;
|
|
use LogicException;
|
|
use Icinga\Application\Modules\Manager as ModuleManager;
|
|
use Icinga\Authentication\User\UserBackend;
|
|
use Icinga\Data\ConfigObject;
|
|
use Icinga\Exception\ConfigurationError;
|
|
use Icinga\Exception\NotReadableError;
|
|
use Icinga\Exception\IcingaException;
|
|
|
|
/**
|
|
* This class bootstraps a thin Icinga application layer
|
|
*
|
|
* Usage example for CLI:
|
|
* <code>
|
|
* use Icinga\Application\Cli;
|
|
|
|
* Cli::start();
|
|
* </code>
|
|
*
|
|
* Usage example for Icinga Web application:
|
|
* <code>
|
|
* use Icinga\Application\Web;
|
|
* Web::start()->dispatch();
|
|
* </code>
|
|
*
|
|
* Usage example for Icinga-Web 1.x compatibility mode:
|
|
* <code>
|
|
* use Icinga\Application\LegacyWeb;
|
|
* LegacyWeb::start()->setIcingaWebBasedir(ICINGAWEB_BASEDIR)->dispatch();
|
|
* </code>
|
|
*/
|
|
abstract class ApplicationBootstrap
|
|
{
|
|
/**
|
|
* Base directory
|
|
*
|
|
* Parent folder for at least application, bin, modules and public
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $baseDir;
|
|
|
|
/**
|
|
* Application directory
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $appDir;
|
|
|
|
/**
|
|
* Icinga library directory
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $libDir;
|
|
|
|
/**
|
|
* Configuration directory
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $configDir;
|
|
|
|
/**
|
|
* Locale directory
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $localeDir;
|
|
|
|
/**
|
|
* Common storage directory
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $storageDir;
|
|
|
|
/**
|
|
* External library paths
|
|
*
|
|
* @var string[]
|
|
*/
|
|
protected $libraryPaths;
|
|
|
|
/**
|
|
* Loaded external libraries
|
|
*
|
|
* @var Libraries
|
|
*/
|
|
protected $libraries;
|
|
|
|
/**
|
|
* Icinga class loader
|
|
*
|
|
* @var ClassLoader
|
|
*/
|
|
private $loader;
|
|
|
|
/**
|
|
* Config object
|
|
*
|
|
* @var Config
|
|
*/
|
|
protected $config;
|
|
|
|
/**
|
|
* Module manager
|
|
*
|
|
* @var ModuleManager
|
|
*/
|
|
private $moduleManager;
|
|
|
|
/**
|
|
* Flag indicates we're on cli environment
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $isCli = false;
|
|
|
|
/**
|
|
* Flag indicates we're on web environment
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $isWeb = false;
|
|
|
|
/**
|
|
* Whether Icinga Web 2 requires setup
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $requiresSetup = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param string $baseDir Icinga Web 2 base directory
|
|
* @param string $configDir Path to Icinga Web 2's configuration files
|
|
* @param string $storageDir Path to Icinga Web 2's stored files
|
|
*/
|
|
protected function __construct($baseDir = null, $configDir = null, $storageDir = null)
|
|
{
|
|
if ($baseDir === null) {
|
|
$baseDir = dirname($this->getBootstrapDirectory());
|
|
}
|
|
$this->baseDir = $baseDir;
|
|
$this->appDir = $baseDir . '/application';
|
|
if (substr(__DIR__, 0, 8) === 'phar:///') {
|
|
$this->libDir = dirname(dirname(__DIR__));
|
|
} else {
|
|
$this->libDir = realpath(__DIR__ . '/../..');
|
|
}
|
|
|
|
$this->setupAutoloader();
|
|
|
|
if ($configDir === null) {
|
|
$configDir = getenv('ICINGAWEB_CONFIGDIR');
|
|
if ($configDir === false) {
|
|
$configDir = Platform::isWindows()
|
|
? $baseDir . '/config'
|
|
: '/etc/icingaweb2';
|
|
}
|
|
}
|
|
$canonical = realpath($configDir);
|
|
$this->configDir = $canonical ? $canonical : $configDir;
|
|
|
|
if ($storageDir === null) {
|
|
$storageDir = getenv('ICINGAWEB_STORAGEDIR');
|
|
if ($storageDir === false) {
|
|
$storageDir = Platform::isWindows()
|
|
? $baseDir . '/storage'
|
|
: '/var/lib/icingaweb2';
|
|
}
|
|
}
|
|
$canonical = realpath($storageDir);
|
|
$this->storageDir = $canonical ? $canonical : $storageDir;
|
|
|
|
if ($this->libraryPaths === null) {
|
|
$libraryPaths = getenv('ICINGAWEB_LIBDIR');
|
|
if ($libraryPaths !== false) {
|
|
$this->libraryPaths = array_filter(array_map(
|
|
'realpath',
|
|
explode(':', $libraryPaths)
|
|
), 'is_dir');
|
|
} else {
|
|
$this->libraryPaths = is_dir('/usr/share/icinga-php')
|
|
? ['/usr/share/icinga-php']
|
|
: [];
|
|
}
|
|
}
|
|
|
|
Icinga::setApp($this);
|
|
|
|
require_once dirname(__FILE__) . '/functions.php';
|
|
}
|
|
|
|
/**
|
|
* Bootstrap interface method for concrete bootstrap objects
|
|
*
|
|
* @return mixed
|
|
*/
|
|
abstract protected function bootstrap();
|
|
|
|
/**
|
|
* Get loaded external libraries
|
|
*
|
|
* @return Libraries
|
|
*/
|
|
public function getLibraries()
|
|
{
|
|
return $this->libraries;
|
|
}
|
|
|
|
/**
|
|
* Getter for module manager
|
|
*
|
|
* @return ModuleManager
|
|
*/
|
|
public function getModuleManager()
|
|
{
|
|
return $this->moduleManager;
|
|
}
|
|
|
|
/**
|
|
* Getter for class loader
|
|
*
|
|
* @return ClassLoader
|
|
*/
|
|
public function getLoader()
|
|
{
|
|
return $this->loader;
|
|
}
|
|
|
|
/**
|
|
* Getter for configuration object
|
|
*
|
|
* @return Config
|
|
*/
|
|
public function getConfig()
|
|
{
|
|
return $this->config;
|
|
}
|
|
|
|
/**
|
|
* Flag indicates we're on cli environment
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isCli()
|
|
{
|
|
return $this->isCli;
|
|
}
|
|
|
|
/**
|
|
* Flag indicates we're on web environment
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isWeb()
|
|
{
|
|
return $this->isWeb;
|
|
}
|
|
|
|
/**
|
|
* Helper to glue directories together
|
|
*
|
|
* @param string $dir
|
|
* @param string $subdir
|
|
*
|
|
* @return string
|
|
*/
|
|
private function getDirWithSubDir($dir, $subdir = null)
|
|
{
|
|
if ($subdir !== null) {
|
|
$dir .= '/' . ltrim($subdir, '/');
|
|
}
|
|
|
|
return $dir;
|
|
}
|
|
|
|
/**
|
|
* Get the base directory
|
|
*
|
|
* @param string $subDir Optional sub directory to get
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getBaseDir($subDir = null)
|
|
{
|
|
return $this->getDirWithSubDir($this->baseDir, $subDir);
|
|
}
|
|
|
|
/**
|
|
* Get the application directory
|
|
*
|
|
* @param string $subDir Optional sub directory to get
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getApplicationDir($subDir = null)
|
|
{
|
|
return $this->getDirWithSubDir($this->appDir, $subDir);
|
|
}
|
|
|
|
/**
|
|
* Get the configuration directory
|
|
*
|
|
* @param string $subDir Optional sub directory to get
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getConfigDir($subDir = null)
|
|
{
|
|
return $this->getDirWithSubDir($this->configDir, $subDir);
|
|
}
|
|
|
|
/**
|
|
* Get the common storage directory
|
|
*
|
|
* @param string $subDir Optional sub directory to get
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getStorageDir($subDir = null)
|
|
{
|
|
return $this->getDirWithSubDir($this->storageDir, $subDir);
|
|
}
|
|
|
|
/**
|
|
* Get the Icinga library directory
|
|
*
|
|
* @param string $subDir Optional sub directory to get
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getLibraryDir($subDir = null)
|
|
{
|
|
return $this->getDirWithSubDir($this->libDir, $subDir);
|
|
}
|
|
|
|
/**
|
|
* Get the path to the bootstrapping directory
|
|
*
|
|
* This is usually /public for Web and EmbeddedWeb and /bin for the CLI
|
|
*
|
|
* @return string
|
|
*
|
|
* @throws LogicException If the base directory can not be detected
|
|
*/
|
|
public function getBootstrapDirectory()
|
|
{
|
|
$script = $_SERVER['SCRIPT_FILENAME'];
|
|
$canonical = realpath($script);
|
|
if ($canonical !== false) {
|
|
$dir = dirname($canonical);
|
|
} elseif (substr($script, -14) === '/webrouter.php') {
|
|
// If Icinga Web 2 is served using PHP's built-in webserver with our webrouter.php script, the $_SERVER
|
|
// variable SCRIPT_FILENAME is set to DOCUMENT_ROOT/webrouter.php which is not a valid path to
|
|
// realpath but DOCUMENT_ROOT here still is the bootstrapping directory
|
|
$dir = dirname($script);
|
|
} else {
|
|
throw new LogicException('Can\'t detected base directory');
|
|
}
|
|
return $dir;
|
|
}
|
|
|
|
/**
|
|
* Start the bootstrap
|
|
*
|
|
* @param string $baseDir Icinga Web 2 base directory
|
|
* @param string $configDir Path to Icinga Web 2's configuration files
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function start($baseDir = null, $configDir = null)
|
|
{
|
|
$application = new static($baseDir, $configDir);
|
|
$application->bootstrap();
|
|
return $application;
|
|
}
|
|
|
|
/**
|
|
* Setup Icinga class loader
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setupAutoloader()
|
|
{
|
|
require_once $this->libDir . '/Icinga/Application/ClassLoader.php';
|
|
|
|
$this->loader = new ClassLoader();
|
|
$this->loader->registerNamespace('Icinga', $this->libDir . '/Icinga');
|
|
$this->loader->registerNamespace('Icinga', $this->libDir . '/Icinga', $this->appDir);
|
|
$this->loader->register();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Setup module manager
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function setupModuleManager()
|
|
{
|
|
$paths = $this->getAvailableModulePaths();
|
|
$this->moduleManager = new ModuleManager(
|
|
$this,
|
|
$this->configDir . '/enabledModules',
|
|
$paths
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
protected function getAvailableModulePaths()
|
|
{
|
|
$paths = [];
|
|
|
|
$configured = getenv('ICINGAWEB_MODULES_DIR');
|
|
if (! $configured) {
|
|
$configured = $this->config->get('global', 'module_path', $this->baseDir . '/modules');
|
|
}
|
|
|
|
$nextIsPhar = false;
|
|
foreach (explode(PATH_SEPARATOR, $configured) as $path) {
|
|
if ($path === 'phar') {
|
|
$nextIsPhar = true;
|
|
continue;
|
|
}
|
|
|
|
if ($nextIsPhar) {
|
|
$nextIsPhar = false;
|
|
$paths[] = 'phar:' . $path;
|
|
} else {
|
|
$paths[] = $path;
|
|
}
|
|
}
|
|
|
|
return $paths;
|
|
}
|
|
|
|
/**
|
|
* Load all enabled modules
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function loadEnabledModules()
|
|
{
|
|
try {
|
|
$this->moduleManager->loadEnabledModules();
|
|
} catch (NotReadableError $e) {
|
|
Logger::error(new IcingaException('Cannot load enabled modules. An exception was thrown:', $e));
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Load the setup module if Icinga Web 2 requires setup or the setup token exists
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function loadSetupModuleIfNecessary()
|
|
{
|
|
if (! @file_exists($this->config->resolvePath('authentication.ini'))) {
|
|
$this->requiresSetup = true;
|
|
if ($this->moduleManager->hasInstalled('setup')) {
|
|
$this->moduleManager->loadModule('setup');
|
|
}
|
|
} elseif ($this->setupTokenExists()) {
|
|
// Load setup module but do not require setup
|
|
if ($this->moduleManager->hasInstalled('setup')) {
|
|
$this->moduleManager->loadModule('setup');
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get whether Icinga Web 2 requires setup
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function requiresSetup()
|
|
{
|
|
return $this->requiresSetup;
|
|
}
|
|
|
|
/**
|
|
* Get whether the setup token exists
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function setupTokenExists()
|
|
{
|
|
return @file_exists($this->config->resolvePath('setup.token'));
|
|
}
|
|
|
|
/**
|
|
* Load external libraries
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function loadLibraries()
|
|
{
|
|
$this->libraries = new Libraries();
|
|
foreach ($this->libraryPaths as $libraryPath) {
|
|
foreach (new DirectoryIterator($libraryPath) as $path) {
|
|
if (! $path->isDot() && is_dir($path->getRealPath())) {
|
|
$this->libraries->registerPath($path->getPathname())
|
|
->registerAutoloader();
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Setup default logging
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function setupLogging()
|
|
{
|
|
Logger::create(
|
|
new ConfigObject(
|
|
array(
|
|
'log' => 'syslog'
|
|
)
|
|
)
|
|
);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Load Configuration
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function loadConfig()
|
|
{
|
|
Config::$configDir = $this->configDir;
|
|
|
|
try {
|
|
$this->config = Config::app();
|
|
} catch (NotReadableError $e) {
|
|
Logger::error(new IcingaException('Cannot load application configuration. An exception was thrown:', $e));
|
|
$this->config = new Config();
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Error handling configuration
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function setupErrorHandling()
|
|
{
|
|
error_reporting(E_ALL | E_STRICT);
|
|
ini_set('display_startup_errors', 1);
|
|
ini_set('display_errors', 1);
|
|
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
|
if (! (error_reporting() & $errno)) {
|
|
// Error was suppressed with the @-operator
|
|
return false; // Continue with the normal error handler
|
|
}
|
|
switch ($errno) {
|
|
case E_NOTICE:
|
|
case E_WARNING:
|
|
case E_STRICT:
|
|
case E_RECOVERABLE_ERROR:
|
|
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
|
}
|
|
return false; // Continue with the normal error handler
|
|
});
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set up logger
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function setupLogger()
|
|
{
|
|
if ($this->config->hasSection('logging')) {
|
|
$loggingConfig = $this->config->getSection('logging');
|
|
|
|
try {
|
|
Logger::create($loggingConfig);
|
|
} catch (ConfigurationError $e) {
|
|
Logger::getInstance()->registerConfigError($e->getMessage());
|
|
|
|
try {
|
|
Logger::getInstance()->setLevel($loggingConfig->get('level', Logger::ERROR));
|
|
} catch (ConfigurationError $e) {
|
|
Logger::getInstance()->registerConfigError($e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set up the user backend factory
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function setupUserBackendFactory()
|
|
{
|
|
try {
|
|
UserBackend::setConfig(Config::app('authentication'));
|
|
} catch (NotReadableError $e) {
|
|
Logger::error(
|
|
new IcingaException('Cannot load user backend configuration. An exception was thrown:', $e)
|
|
);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Detect the timezone
|
|
*
|
|
* @return null|string
|
|
*/
|
|
protected function detectTimezone()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Set up the timezone
|
|
*
|
|
* @return $this
|
|
*/
|
|
final protected function setupTimezone()
|
|
{
|
|
$timezone = $this->detectTimezone();
|
|
if ($timezone === null || @date_default_timezone_set($timezone) === false) {
|
|
date_default_timezone_set(@date_default_timezone_get());
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Detect the locale
|
|
*
|
|
* @return null|string
|
|
*/
|
|
protected function detectLocale()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Prepare internationalization using gettext
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function prepareInternationalization()
|
|
{
|
|
StaticTranslator::$instance = (new GettextTranslator())
|
|
->setDefaultDomain('icinga');
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set up internationalization using gettext
|
|
*
|
|
* @return $this
|
|
*/
|
|
final protected function setupInternationalization()
|
|
{
|
|
/** @var GettextTranslator $translator */
|
|
$translator = StaticTranslator::$instance;
|
|
|
|
if ($this->hasLocales()) {
|
|
$translator->addTranslationDirectory($this->getLocaleDir(), 'icinga');
|
|
}
|
|
|
|
$locale = $this->detectLocale();
|
|
if ($locale === null) {
|
|
$locale = $translator->getDefaultLocale();
|
|
}
|
|
|
|
try {
|
|
$translator->setLocale($locale);
|
|
} catch (Exception $error) {
|
|
Logger::error($error);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string Our locale directory
|
|
*/
|
|
public function getLocaleDir()
|
|
{
|
|
if ($this->localeDir === null) {
|
|
$L10nLocales = getenv('ICINGAWEB_LOCALEDIR') ?: '/usr/share/icinga-L10n/locale';
|
|
if (file_exists($L10nLocales) && is_dir($L10nLocales)) {
|
|
$this->localeDir = $L10nLocales;
|
|
} else {
|
|
$this->localeDir = false;
|
|
}
|
|
}
|
|
|
|
return $this->localeDir;
|
|
}
|
|
|
|
/**
|
|
* return bool Whether Icinga Web has translations
|
|
*/
|
|
public function hasLocales()
|
|
{
|
|
$localedir = $this->getLocaleDir();
|
|
return $localedir !== false && file_exists($localedir) && is_dir($localedir);
|
|
}
|
|
|
|
/**
|
|
* Register all hooks provided by the main application
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function registerApplicationHooks(): self
|
|
{
|
|
Hook::register('DbMigration', DbMigration::class, DbMigration::class);
|
|
|
|
return $this;
|
|
}
|
|
}
|