]> git.immae.eu Git - perso/Immae/Config/Nix.git/commitdiff
Add shaarli website
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Thu, 7 Feb 2019 00:36:13 +0000 (01:36 +0100)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Thu, 7 Feb 2019 00:36:13 +0000 (01:36 +0100)
nixops/modules/websites/default.nix
nixops/modules/websites/tools/tools/default.nix
nixops/modules/websites/tools/tools/shaarli.nix [new file with mode: 0644]
nixops/modules/websites/tools/tools/shaarli_ldap.patch [new file with mode: 0644]

index 8563995d72f47f556dfe10ecd1f3dcaac71af9be..830040783f0f3bd653403a2ea7573733f7fffaf7 100644 (file)
@@ -405,6 +405,7 @@ in
       phpPackage = pkgs.php;
       phpOptions = ''
         session.save_path = "/var/lib/php/sessions"
+        post_max_size = 20M
         session.gc_maxlifetime = 60*60*24*15
         session.cache_expire = 60*24*30
         '';
index 333ffb0c79e9cf6129b34f7340dd1ccf20bb4650..41f47a3f458305c072fac90711dd7b8d197d788d 100644 (file)
@@ -18,6 +18,9 @@ let
       inherit (mylibs) fetchedGithub;
       env = myconfig.env.tools.rompr;
     };
+    shaarli = pkgs.callPackage ./shaarli.nix {
+      env = myconfig.env.tools.shaarli;
+    };
 
     cfg = config.services.myWebsites.tools.tools;
 in {
@@ -35,7 +38,8 @@ in {
       ++ roundcubemail.apache.modules
       ++ wallabag.apache.modules
       ++ yourls.apache.modules
-      ++ rompr.apache.modules;
+      ++ rompr.apache.modules
+      ++ shaarli.apache.modules;
 
     services.ympd = ympd.config // { enable = true; };
 
@@ -51,6 +55,7 @@ in {
         wallabag.apache.vhostConf
         yourls.apache.vhostConf
         rompr.apache.vhostConf
+        shaarli.apache.vhostConf
       ];
     };
 
@@ -61,6 +66,7 @@ in {
       wallabag = wallabag.phpFpm.pool;
       yourls = yourls.phpFpm.pool;
       rompr = rompr.phpFpm.pool;
+      shaarli = shaarli.phpFpm.pool;
     };
 
     system.activationScripts = {
@@ -69,6 +75,7 @@ in {
       wallabag = wallabag.activationScript;
       yourls = yourls.activationScript;
       rompr = rompr.activationScript;
+      shaarli = shaarli.activationScript;
     };
 
     systemd.services.tt-rss = {
diff --git a/nixops/modules/websites/tools/tools/shaarli.nix b/nixops/modules/websites/tools/tools/shaarli.nix
new file mode 100644 (file)
index 0000000..9f3779f
--- /dev/null
@@ -0,0 +1,86 @@
+{ lib, env, stdenv, fetchurl }:
+
+let
+  varDir = "/var/lib/shaarli";
+  shaarli = stdenv.mkDerivation rec {
+    name = "shaarli-${version}";
+    version = "0.10.2";
+
+    src = fetchurl {
+      url = "https://github.com/shaarli/Shaarli/releases/download/v${version}/shaarli-v${version}-full.tar.gz";
+      sha256 = "0h8sspj7siy3vgpi2i3gdrjcr5935fr4dfwq2zwd70sjx2sh9s78";
+    };
+
+    outputs = [ "out" "doc" ];
+
+    patches = [ ./shaarli_ldap.patch ];
+
+    installPhase = ''
+      rm -r {cache,pagecache,tmp,data}/
+      ln -sf ../../../..${varDir}/{cache,pagecache,tmp,data} .
+      mkdir -p $doc/share/doc
+      mv doc/ $doc/share/doc/shaarli
+      mkdir $out/
+      cp -R ./* $out
+      cp .htaccess $out/
+    '';
+
+    meta = with stdenv.lib; {
+      description = "The personal, minimalist, super-fast, database free, bookmarking service";
+      license = licenses.gpl3Plus;
+      homepage = https://github.com/shaarli/Shaarli;
+      maintainers = with maintainers; [ schneefux ];
+      platforms = platforms.all;
+    };
+  };
+in rec {
+  activationScript = ''
+    install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir} \
+      ${varDir}/cache ${varDir}/pagecache ${varDir}/tmp ${varDir}/data \
+      ${varDir}/phpSessions
+    '';
+  webRoot = shaarli;
+  apache = {
+    user = "wwwrun";
+    group = "wwwrun";
+    modules =  [ "proxy_fcgi" "rewrite" "env" ];
+    vhostConf = ''
+      Alias /Shaarli "${webRoot}"
+
+      <Directory "${webRoot}">
+        SetEnv SHAARLI_LDAP_PASSWORD "${env.ldap.password}"
+        SetEnv SHAARLI_LDAP_DN       "${env.ldap.dn}"
+        SetEnv SHAARLI_LDAP_HOST     "ldaps://${env.ldap.host}"
+        SetEnv SHAARLI_LDAP_BASE     "${env.ldap.base}"
+        SetEnv SHAARLI_LDAP_FILTER   "${env.ldap.search}"
+
+        DirectoryIndex index.php index.htm index.html
+        Options Indexes FollowSymLinks MultiViews Includes
+        AllowOverride All
+        Require all granted
+        <FilesMatch "\.php$">
+          SetHandler "proxy:unix:${phpFpm.socket}|fcgi://localhost"
+        </FilesMatch>
+      </Directory>
+      '';
+  };
+  phpFpm = rec {
+    basedir = builtins.concatStringsSep ":" [ webRoot varDir ];
+    socket = "/var/run/phpfpm/shaarli.sock";
+    pool = ''
+        listen = ${socket}
+        user = ${apache.user}
+        group = ${apache.group}
+        listen.owner = ${apache.user}
+        listen.group = ${apache.group}
+        pm = ondemand
+        pm.max_children = 60
+        pm.process_idle_timeout = 60
+
+        ; Needed to avoid clashes in browser cookies (same domain)
+        php_value[session.name] = ShaarliPHPSESSID
+        php_admin_value[open_basedir] = "${basedir}:/tmp"
+        php_admin_value[session.save_path] = "${varDir}/phpSessions"
+    '';
+  };
+}
diff --git a/nixops/modules/websites/tools/tools/shaarli_ldap.patch b/nixops/modules/websites/tools/tools/shaarli_ldap.patch
new file mode 100644 (file)
index 0000000..9c7315a
--- /dev/null
@@ -0,0 +1,420 @@
+commit bc82ebfd779b8641dadd6787f51639ea9105c3e8
+Author: Ismaël Bouya <ismael.bouya@normalesup.org>
+Date:   Sun Feb 3 20:58:18 2019 +0100
+
+    Add ldap connection
+
+diff --git a/.htaccess b/.htaccess
+index 4c00427..5acd708 100644
+--- a/.htaccess
++++ b/.htaccess
+@@ -6,10 +6,23 @@ RewriteEngine On
+ # Prevent accessing subdirectories not managed by SCM
+ RewriteRule ^(.git|doxygen|vendor) - [F]
++RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
++RewriteRule ^(.*) - [E=BASE:%1]
++
++RewriteCond %{ENV:REDIRECT_BASE} (.+)
++RewriteRule .* - [E=BASE:%1]
++
+ # Forward the "Authorization" HTTP header
+ RewriteCond %{HTTP:Authorization} ^(.*)
+ RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
++RewriteCond %{REQUEST_FILENAME} !-f
++RewriteCond %{REQUEST_FILENAME} !-d
++RewriteRule ^((?!api/)[^/]*)/?(.*)$ $2?%{QUERY_STRING} [E=USERSPACE:$1]
++
++RewriteCond %{ENV:REDIRECT_USERSPACE} (.+)
++RewriteRule .* - [E=USERSPACE:%1]
++
+ # REST API
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
+index 911873a..f21a1ef 100644
+--- a/application/ApplicationUtils.php
++++ b/application/ApplicationUtils.php
+@@ -191,6 +191,9 @@ public static function checkResourcePermissions($conf)
+             $conf->get('resource.page_cache'),
+             $conf->get('resource.raintpl_tmp'),
+         ) as $path) {
++            if (! is_dir($path)) {
++                mkdir($path, 0755, true);
++            }
+             if (! is_readable(realpath($path))) {
+                 $errors[] = '"'.$path.'" '. t('directory is not readable');
+             }
+diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
+index 32aaea4..99efc15 100644
+--- a/application/config/ConfigManager.php
++++ b/application/config/ConfigManager.php
+@@ -21,6 +21,11 @@ class ConfigManager
+     public static $DEFAULT_PLUGINS = array('qrcode');
++    /**
++     * @var string User space.
++     */
++    protected $userSpace;
++
+     /**
+      * @var string Config folder.
+      */
+@@ -41,12 +46,36 @@ class ConfigManager
+      *
+      * @param string $configFile Configuration file path without extension.
+      */
+-    public function __construct($configFile = 'data/config')
++    public function __construct($configFile = null, $userSpace = null)
+     {
+-        $this->configFile = $configFile;
++        $this->userSpace = $this->findLDAPUser($userSpace);
++        if ($configFile !== null) {
++            $this->configFile = $configFile;
++        } else {
++            $this->configFile = ($this->userSpace === null) ? 'data/config' : 'data/' . $this->userSpace . '/config';
++        }
+         $this->initialize();
+     }
++    public function findLDAPUser($login, $password = null) {
++        $connect = ldap_connect(getenv('SHAARLI_LDAP_HOST'));
++        ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
++        if (!$connect || !ldap_bind($connect, getenv('SHAARLI_LDAP_DN'), getenv('SHAARLI_LDAP_PASSWORD'))) {
++            return false;
++        }
++
++        $search_query = str_replace('%login%', ldap_escape($login), getenv('SHAARLI_LDAP_FILTER'));
++
++        $search = ldap_search($connect, getenv('SHAARLI_LDAP_BASE'), $search_query);
++        $info = ldap_get_entries($connect, $search);
++
++        if (ldap_count_entries($connect, $search) == 1 && (is_null($password) || ldap_bind($connect, $info[0]["dn"], $password))) {
++            return $login;
++        } else {
++            return null;
++        }
++    }
++
+     /**
+      * Reset the ConfigManager instance.
+      */
+@@ -269,6 +298,16 @@ public function getConfigFileExt()
+         return $this->configFile . $this->configIO->getExtension();
+     }
++    /**
++     * Get the current userspace.
++     *
++     * @return mixed User space.
++     */
++    public function getUserSpace()
++    {
++      return $this->userSpace;
++    }
++
+     /**
+      * Recursive function which find asked setting in the loaded config.
+      *
+@@ -342,19 +381,31 @@ protected static function removeConfig($settings, &$conf)
+      */
+     protected function setDefaultValues()
+     {
+-        $this->setEmpty('resource.data_dir', 'data');
+-        $this->setEmpty('resource.config', 'data/config.php');
+-        $this->setEmpty('resource.datastore', 'data/datastore.php');
+-        $this->setEmpty('resource.ban_file', 'data/ipbans.php');
+-        $this->setEmpty('resource.updates', 'data/updates.txt');
+-        $this->setEmpty('resource.log', 'data/log.txt');
+-        $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
+-        $this->setEmpty('resource.history', 'data/history.php');
++        if ($this->userSpace === null) {
++          $data = 'data';
++          $tmp  = 'tmp';
++          $cache = 'cache';
++          $pagecache = 'pagecache';
++        } else {
++          $data = 'data/' . ($this->userSpace);
++          $tmp  = 'tmp/' . ($this->userSpace);
++          $cache = 'cache/' . ($this->userSpace);
++          $pagecache = 'pagecache/' . ($this->userSpace);
++        }
++
++        $this->setEmpty('resource.data_dir', $data);
++        $this->setEmpty('resource.config', $data . '/config.php');
++        $this->setEmpty('resource.datastore', $data . '/datastore.php');
++        $this->setEmpty('resource.ban_file', $data . '/ipbans.php');
++        $this->setEmpty('resource.updates', $data . '/updates.txt');
++        $this->setEmpty('resource.log', $data . '/log.txt');
++        $this->setEmpty('resource.update_check', $data . '/lastupdatecheck.txt');
++        $this->setEmpty('resource.history', $data . '/history.php');
+         $this->setEmpty('resource.raintpl_tpl', 'tpl/');
+         $this->setEmpty('resource.theme', 'default');
+-        $this->setEmpty('resource.raintpl_tmp', 'tmp/');
+-        $this->setEmpty('resource.thumbnails_cache', 'cache');
+-        $this->setEmpty('resource.page_cache', 'pagecache');
++        $this->setEmpty('resource.raintpl_tmp', $tmp);
++        $this->setEmpty('resource.thumbnails_cache', $cache);
++        $this->setEmpty('resource.page_cache', $pagecache);
+         $this->setEmpty('security.ban_after', 4);
+         $this->setEmpty('security.ban_duration', 1800);
+diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php
+index d6784d6..bdfaca7 100644
+--- a/application/security/LoginManager.php
++++ b/application/security/LoginManager.php
+@@ -32,6 +32,9 @@ class LoginManager
+     /** @var string User sign-in token depending on remote IP and credentials */
+     protected $staySignedInToken = '';
++    protected $lastErrorReason = '';
++    protected $lastErrorIsBanishable = false;
++
+     /**
+      * Constructor
+      *
+@@ -83,7 +86,7 @@ public function getStaySignedInToken()
+      */
+     public function checkLoginState($cookie, $clientIpId)
+     {
+-        if (! $this->configManager->exists('credentials.login')) {
++        if (! $this->configManager->exists('credentials.login') || (isset($_SESSION['username']) && $_SESSION['username'] && $this->configManager->get('credentials.login') !== $_SESSION['username'])) {
+             // Shaarli is not configured yet
+             $this->isLoggedIn = false;
+             return;
+@@ -133,20 +136,40 @@ public function isLoggedIn()
+      */
+     public function checkCredentials($remoteIp, $clientIpId, $login, $password)
+     {
+-        $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
++        $this->lastErrorIsBanishable = false;
++
++        if ($this->configManager->getUserSpace() !== null && $this->configManager->getUserSpace() !== $login) {
++          logm($this->configManager->get('resource.log'),
++               $remoteIp,
++               'Trying to login to wrong user space');
++          $this->lastErrorReason = 'You’re trying to access the wrong account.';
++          return false;
++        }
+-        if ($login != $this->configManager->get('credentials.login')
+-            || $hash != $this->configManager->get('credentials.hash')
+-        ) {
++        logm($this->configManager->get('resource.log'),
++             $remoteIp,
++             'Trying LDAP connection');
++        $result = $this->configManager->findLDAPUser($login, $password);
++        if ($result === false) {
+             logm(
+                 $this->configManager->get('resource.log'),
+                 $remoteIp,
+-                'Login failed for user ' . $login
++                'Impossible to connect to LDAP'
+             );
++            $this->lastErrorReason = 'Server error.';
++            return false;
++        } else if (is_null($result)) {
++            logm(
++              $this->configManager->get('resource.log'),
++              $remoteIp,
++              'Login failed for user ' . $login
++            );
++            $this->lastErrorIsBanishable = true;
++            $this->lastErrorReason = 'Wrong login/password.';
+             return false;
+         }
+-        $this->sessionManager->storeLoginInfo($clientIpId);
++        $this->sessionManager->storeLoginInfo($clientIpId, $login);
+         logm(
+             $this->configManager->get('resource.log'),
+             $remoteIp,
+@@ -187,6 +210,10 @@ protected function writeBanFile()
+      */
+     public function handleFailedLogin($server)
+     {
++        if (!$this->lastErrorIsBanishable) {
++          return $this->lastErrorReason ?: 'Error during login.';
++        };
++
+         $ip = $server['REMOTE_ADDR'];
+         $trusted = $this->configManager->get('security.trusted_proxies', []);
+@@ -215,6 +242,7 @@ public function handleFailedLogin($server)
+             );
+         }
+         $this->writeBanFile();
++        return $this->lastErrorReason ?: 'Error during login.';
+     }
+     /**
+diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php
+index b8b8ab8..5eb4aac 100644
+--- a/application/security/SessionManager.php
++++ b/application/security/SessionManager.php
+@@ -111,10 +111,10 @@ public static function checkId($sessionId)
+      *
+      * @param string $clientIpId Client IP address identifier
+      */
+-    public function storeLoginInfo($clientIpId)
++    public function storeLoginInfo($clientIpId, $login = null)
+     {
+         $this->session['ip'] = $clientIpId;
+-        $this->session['username'] = $this->conf->get('credentials.login');
++        $this->session['username'] = $login ?: $this->conf->get('credentials.login');
+         $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
+     }
+diff --git a/index.php b/index.php
+index 4b86a3e..85376e8 100644
+--- a/index.php
++++ b/index.php
+@@ -121,7 +121,27 @@
+     $_COOKIE['shaarli'] = session_id();
+ }
+-$conf = new ConfigManager();
++$folderBase = getenv("BASE");
++
++if (getenv("USERSPACE")) {
++    if (isset($_GET["do"]) && $_GET["do"] == "login") {
++        header("Location: $folderBase/?do=login");
++        exit;
++    }
++    $userspace = preg_replace("/[^-_A-Za-z0-9]/", '', getenv("USERSPACE"));
++} else if (isset($_SESSION["username"]) && $_SESSION["username"]) {
++    header("Location: " . $folderBase . "/" . $_SESSION["username"] . "?");
++    exit;
++} else if (!isset($_GET["do"]) || $_GET["do"] != "login") {
++    header("Location: $folderBase/?do=login");
++    exit;
++}
++
++if (isset($userspace)) {
++  $conf = new ConfigManager(null, $userspace);
++} else {
++  $conf = new ConfigManager();
++}
+ $sessionManager = new SessionManager($_SESSION, $conf);
+ $loginManager = new LoginManager($GLOBALS, $conf, $sessionManager);
+ $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']);
+@@ -175,7 +195,7 @@
+     }
+     // Display the installation form if no existing config is found
+-    install($conf, $sessionManager, $loginManager);
++    install($conf, $sessionManager, $loginManager, $userspace);
+ }
+ $loginManager->checkLoginState($_COOKIE, $clientIpId);
+@@ -205,6 +225,7 @@ function isLoggedIn()
+         && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password'])
+     ) {
+         $loginManager->handleSuccessfulLogin($_SERVER);
++        $userspace = $_POST['login'];
+         $cookiedir = '';
+         if (dirname($_SERVER['SCRIPT_NAME']) != '/') {
+@@ -241,25 +262,25 @@ function isLoggedIn()
+                     $uri .= '&'.$param.'='.urlencode($_GET[$param]);
+                 }
+             }
+-            header('Location: '. $uri);
++            header('Location: '. $userspace . $uri);
+             exit;
+         }
+         if (isset($_GET['edit_link'])) {
+-            header('Location: ?edit_link='. escape($_GET['edit_link']));
++            header('Location: ' . $userspace . '?edit_link='. escape($_GET['edit_link']));
+             exit;
+         }
+         if (isset($_POST['returnurl'])) {
+             // Prevent loops over login screen.
+             if (strpos($_POST['returnurl'], 'do=login') === false) {
+-                header('Location: '. generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST']));
++                header('Location: ' . generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST']));
+                 exit;
+             }
+         }
+-        header('Location: ?'); exit;
++        header('Location: '. $userspace . '?'); exit;
+     } else {
+-        $loginManager->handleFailedLogin($_SERVER);
++        $errorReason = $loginManager->handleFailedLogin($_SERVER);
+         $redir = '&username='. urlencode($_POST['login']);
+         if (isset($_GET['post'])) {
+             $redir .= '&post=' . urlencode($_GET['post']);
+@@ -270,7 +291,7 @@ function isLoggedIn()
+             }
+         }
+         // Redirect to login screen.
+-        echo '<script>alert("'. t("Wrong login/password.") .'");document.location=\'?do=login'.$redir.'\';</script>';
++        echo '<script>alert("'. t($errorReason) .'");document.location=\'?do=login'.$redir.'\';</script>';
+         exit;
+     }
+ }
+@@ -1719,7 +1740,7 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
+  * @param SessionManager $sessionManager SessionManager instance
+  * @param LoginManager   $loginManager   LoginManager instance
+  */
+-function install($conf, $sessionManager, $loginManager) {
++function install($conf, $sessionManager, $loginManager, $userspace) {
+     // On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
+     if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705);
+@@ -1755,7 +1776,7 @@ function install($conf, $sessionManager, $loginManager) {
+     }
+-    if (!empty($_POST['setlogin']) && !empty($_POST['setpassword']))
++    if (true)
+     {
+         $tz = 'UTC';
+         if (!empty($_POST['continent']) && !empty($_POST['city'])
+@@ -1764,15 +1785,15 @@ function install($conf, $sessionManager, $loginManager) {
+             $tz = $_POST['continent'].'/'.$_POST['city'];
+         }
+         $conf->set('general.timezone', $tz);
+-        $login = $_POST['setlogin'];
+-        $conf->set('credentials.login', $login);
++        $conf->set('credentials.login', $userspace);
+         $salt = sha1(uniqid('', true) .'_'. mt_rand());
+         $conf->set('credentials.salt', $salt);
+-        $conf->set('credentials.hash', sha1($_POST['setpassword'] . $login . $salt));
++        $hash = sha1(uniqid('', true) .'_'. mt_rand());
++        $conf->set('credentials.hash', $hash);
+         if (!empty($_POST['title'])) {
+             $conf->set('general.title', escape($_POST['title']));
+         } else {
+-            $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER)));
++            $conf->set('general.title', ucwords(str_replace("_", " ", $userspace)));
+         }
+         $conf->set('translation.language', escape($_POST['language']));
+         $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
+@@ -1841,7 +1862,12 @@ function install($conf, $sessionManager, $loginManager) {
+ $app = new \Slim\App($container);
+ // REST API routes
+-$app->group('/api/v1', function() {
++if (isset($userspace)) {
++  $mountpoint = '/' . $userspace . '/api/v1';
++} else {
++  $mountpoint = '/api/v1';
++}
++$app->group($mountpoint, function() {
+     $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo');
+     $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks');
+     $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink');
+@@ -1860,7 +1886,7 @@ function install($conf, $sessionManager, $loginManager) {
+ $response = $app->run(true);
+ // Hack to make Slim and Shaarli router work together:
+ // If a Slim route isn't found and NOT API call, we call renderPage().
+-if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
++if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], $mountpoint) === false) {
+     // We use UTF-8 for proper international characters handling.
+     header('Content-Type: text/html; charset=utf-8');
+     renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager);