]> git.immae.eu Git - perso/Immae/Config/Nix.git/commitdiff
Switch to colemna
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Thu, 29 Dec 2022 07:00:22 +0000 (08:00 +0100)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Sun, 9 Apr 2023 15:16:33 +0000 (17:16 +0200)
16 files changed:
flakes/backports/flake.lock
flakes/backports/flake.nix
modules/private/system.nix
modules/private/system/backup-2.nix
modules/private/system/dilion.nix
modules/private/system/eldiron.nix
modules/private/system/monitoring-1.nix
modules/private/system/quatresaisons.nix
modules/private/websites/tools/tools/shaarli.nix
nixops/Makefile
nixops/default.nix
nixops/scripts/with_env
overlays/shaarli/default.nix
pkgs/shaarli/default.nix [new file with mode: 0644]
pkgs/shaarli/shaarli_ldap.patch [new file with mode: 0644]
shell.nix

index a16b090fdc83e68b273bac9a7d7948b30716cb60..b5ecf00fca6278d3b3a0285c8045ec9f88948832 100644 (file)
@@ -2,11 +2,11 @@
   "nodes": {
     "flake-utils": {
       "locked": {
-        "lastModified": 1659877975,
-        "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+        "lastModified": 1667395993,
+        "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+        "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
         "type": "github"
       },
       "original": {
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1663087123,
-        "narHash": "sha256-cNIRkF/J4mRxDtNYw+9/fBNq/NOA2nCuPOa3EdIyeDs=",
+        "lastModified": 1672080458,
+        "narHash": "sha256-Ukjn8YUwZevxDPaVUmTx2sf9bCcIJSasmLz+xjGBKrs=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "9608ace7009ce5bc3aeb940095e01553e635cbc7",
+        "rev": "1eb875e811dd59e21e77f6337f2c1592889b48b3",
         "type": "github"
       },
       "original": {
index 463639fd33032a6cca90b17691fe305199de5f2d..d23a7ae5dd037ba503baa58bafb5ecb04834d439 100644 (file)
@@ -13,6 +13,7 @@
         khard = pkgs.khard;
         khal = pkgs.khal;
         go-task = pkgs.go-task;
+        colmena = pkgs.colmena;
       };
       legacyPackages = packages;
       apps = {
@@ -20,6 +21,7 @@
         khard = flake-utils.lib.mkApp { drv = packages.khard; name = "khard"; };
         khal = flake-utils.lib.mkApp { drv = packages.khal; name = "khal"; };
         go-task = flake-utils.lib.mkApp { drv = packages.go-task; name = "go-task"; };
+        colmena = flake-utils.lib.mkApp { drv = packages.colmena; name = "colmena"; };
       };
     }
   ) // rec {
       khard = final: prev: { khard = self.packages."${final.system}".khard; };
       khal = final: prev: { khal = self.packages."${final.system}".khal; };
       go-task = final: prev: { go-task = self.packages."${final.system}".go-task; };
+      colmena = final: prev: { colmena = self.packages."${final.system}".colmena; };
     };
     overlay = final: prev: ({}
       // overlays.ntfy-sh final prev
       // overlays.khard final prev
       // overlays.khal final prev
       // overlays.go-task final prev
+      // overlays.colmena final prev
     );
 
     nixosModules = {
index d0943abef708036d34531ccdae9e0e8b4a8bad72..1eea0f3335cf4801bdf5d0082b8e2dc353bee23a 100644 (file)
@@ -1,11 +1,11 @@
 { pkgs, lib, config, name, nodes, ... }:
 {
   config = {
-    deployment.secrets."secret_vars.yml" = {
-      source = builtins.toString ../../nixops/secrets/vars.yml;
-      destination = config.secrets.secretsVars;
-      owner.user = "root";
-      owner.group = "root";
+    networking.hostName = name;
+    deployment.keys."vars.yml" = {
+      keyFile = builtins.toString ../../nixops/secrets/vars.yml;
+      user = "root";
+      group = "root";
       permissions = "0400";
     };
 
index 83caf68b278390db31dcb3acb07fb9db8b2d55dd..451057461eef140f53703a4b341218fbb6353f31 100644 (file)
@@ -3,7 +3,6 @@
   deployment = {
     targetUser = "root";
     targetHost = lib.head config.hostEnv.ips.main.ip4;
-    substituteOnDestination = true;
   };
   # ssh-keyscan backup-2 | nix-shell -p ssh-to-age --run ssh-to-age
   secrets.ageKeys = [ "age1kk3nr27qu42j28mcfdag5lhq0zu2pky7gfanvne8l4z2ctevjpgskmw0sr" ];
index 569c088d8d0822be8123d18296a5ca0d065e84ec..1b5168243e62c5294fed8d561232f7469e567213 100644 (file)
@@ -3,7 +3,6 @@
   deployment = {
     targetUser = "root";
     targetHost = lib.head config.hostEnv.ips.main.ip4;
-    substituteOnDestination = true;
   };
   # ssh-keyscan dilion | nix-shell -p ssh-to-age --run ssh-to-age
   secrets.ageKeys = [ "age1x49n6qa0arkdpq8530s7umgm0gqkq90exv4jep97q30rfnzknpaqate06a" ];
index 6ae3875a1f7753fbc50829cb926f312d45c2b0b0..091c9f46e8d63ccbd564e78b1399cea81153adfd 100644 (file)
@@ -3,7 +3,6 @@
   deployment = {
     targetUser = "root";
     targetHost = lib.head config.hostEnv.ips.main.ip4;
-    substituteOnDestination = true;
   };
   # ssh-keyscan eldiron | nix-shell -p ssh-to-age --run ssh-to-age
   secrets.ageKeys = [ "age1dxr5lhvtnjssfaqpnf6qx80h8gfwkxg3tdf35m6n9wljmk7wadfs3kmahj" ];
index c45835f1616bb124ab2e9f5c14216a1e59164884..71eee338d22e16ebd16c6b9b03af6bd4473a82b2 100644 (file)
@@ -3,7 +3,6 @@
   deployment = {
     targetUser = "root";
     targetHost = lib.head config.hostEnv.ips.main.ip4;
-    substituteOnDestination = true;
   };
   # ssh-keyscan monitoring-1 | nix-shell -p ssh-to-age --run ssh-to-age
   secrets.ageKeys = [ "age1dn4lzhgxusqrpjjnzm7w8ml39ptf326htuzmpqdqs2gg3wq7cqzqxuvx8k" ];
index ed6f12904cddac077cfea25f89cadfb06e96e327..2938a342c4187ff41c65d9f806c4d244f6fba9b7 100644 (file)
@@ -200,7 +200,6 @@ in
   deployment = {
     targetUser = "root";
     targetHost = lib.head config.hostEnv.ips.main.ip4;
-    substituteOnDestination = true;
   };
   # ssh-keyscan quatresaison | nix-shell -p ssh-to-age --run ssh-to-age
   secrets.ageKeys = [ "age1yz8u6xvh2fltvyp96ep8crce3qx4tuceyhun6pwddfe0uvcrkarscxl7e7" ];
index e7f106c008e4a7633eca1a1beb6c6eb9753c6ddd..79fbbc9880487b806fad0445beb764ba65e18980 100644 (file)
@@ -6,7 +6,7 @@ in rec {
     install -m 0755 -o ${apache.user} -g ${apache.group} -d ${varDir} \
       ${varDir}/cache ${varDir}/pagecache ${varDir}/tmp ${varDir}/data
     '';
-  webRoot = shaarli varDir;
+  webRoot = shaarli.override { inherit varDir; };
   apache = rec {
     user = "wwwrun";
     group = "wwwrun";
index fb9da4cb2f6fa30cf4d99e45ad20fab8415438fa..11c5a0f5c4548e7cc4e60e1acc44bfc532e3a481 100644 (file)
@@ -18,6 +18,8 @@ setup:
 ###### Morph regular tasks
 PROFILE=/nix/var/nix/profiles/per-user/immae/morph/immaeEu
 TARGET ?=
+COMMON_COLEMNA_ARGS = -v
+#Only enabled in colemna 0.4: --nix-option allow-unsafe-native-code-during-evaluation true --nix-option allow-import-from-derivation true --nix-option substituters https://cache.nixos.org/
 MORPH_ARGS ?=
 ifdef TARGET
   # multiple targets: --on="{machine1,machine2}" (works with * glob too)
@@ -51,27 +53,27 @@ ssh-4c:
        ./scripts/with_env bash -c 'ssh -i $$SSH_IDENTITY_FILE root@quatresaisons $(SSH_ARGS)'
 
 debug:
-       ./scripts/with_env morph build --show-trace default.nix $(MORPH_ARGS)
+       ./scripts/with_env colmena build $(COMMON_COLEMNA_ARGS) --show-trace -f default.nix $(MORPH_ARGS)
 
 build:
-       ./scripts/with_env morph build default.nix $(MORPH_ARGS)
+       ./scripts/with_env colmena build $(COMMON_COLEMNA_ARGS) -f default.nix $(MORPH_ARGS)
 
-dry-run:
-       ./scripts/with_env morph build --dry-run default.nix $(MORPH_ARGS)
+#dry-run:
+#      ./scripts/with_env morph build -v --dry-run default.nix $(MORPH_ARGS)
 
 upload:
-       ./scripts/with_env morph push default.nix $(MORPH_ARGS)
+       ./scripts/with_env colmena apply $(COMMON_COLEMNA_ARGS) push -f default.nix $(MORPH_ARGS)
 
 deploy:
-       ./scripts/with_env morph deploy default.nix switch --keep-result --upload-secrets $(MORPH_ARGS)
+       ./scripts/with_env colmena apply $(COMMON_COLEMNA_ARGS) switch -f default.nix --keep-result $(MORPH_ARGS)
        nix-env -p $(PROFILE) --set .gcroots/default.nix
 
 next-boot:
-       ./scripts/with_env morph deploy default.nix boot --keep-result --upload-secrets $(MORPH_ARGS)
+       ./scripts/with_env colmena apply -v boot -f default.nix --keep-result $(MORPH_ARGS)
        nix-env -p $(PROFILE) --set .gcroots/default.nix
 
 deploy-reboot:
-       ./scripts/with_env morph deploy default.nix boot --reboot --upload-secrets $(MORPH_ARGS)
+       ./scripts/with_env colmena apply $(COMMON_COLEMNA_ARGS) boot -f default.nix --reboot $(MORPH_ARGS)
 
 .PHONY: ssh-eldiron ssh-dilion ssh-monitoring-1 ssh-backup-2 debug build upload deploy deploy-reboot
 
index d2c446dd240dfbc595af4acad73acfc5dc4d8721..96296dfa48be84b5abea541c3fb031568752ae37 100644 (file)
@@ -1,11 +1,5 @@
 {
-  network = {
-    nixConfig = {
-      substituters = "https://cache.nixos.org/";
-      allow-unsafe-native-code-during-evaluation = "true";
-      allow-import-from-derivation = "true";
-    };
-  };
+  meta.nixpkgs = import <nixpkgs> { overlays = []; };
   dilion = import ../modules/private/system/dilion.nix;
   eldiron = import ../modules/private/system/eldiron.nix;
   backup-2 = import ../modules/private/system/backup-2.nix;
index c570ccf2afcd95cde5495679f18fce4f40911f1f..8bc26edf047a1a44969f351abdd6c88fe5f3546d 100755 (executable)
@@ -17,6 +17,11 @@ trap finish EXIT
 
 sops -d secrets/vars.yml | yq -r .ssl_keys.nix_repository > $TEMP/id_ed25519
 
+cat > $TEMP/ssh_config <<EOF
+Host *
+IdentityFile $TEMP/id_ed25519
+EOF
+export SSH_CONFIG_FILE="$TEMP/ssh_config"
 export SSH_IDENTITY_FILE="$TEMP/id_ed25519"
 
 "$@"
index 3b37ee8a31a8974e882d2abc31d61899b96e6392..e1ff1918dc8533b60b365c90734cd723ebd7ca16 100644 (file)
@@ -1,15 +1,3 @@
 self: super: {
-  shaarli = varDir: super.shaarli.overrideAttrs (old: rec {
-    version = "0.10.2";
-    src = self.fetchurl {
-      url = "https://github.com/shaarli/Shaarli/releases/download/v${version}/shaarli-v${version}-full.tar.gz";
-      sha256 = "0h8sspj7siy3vgpi2i3gdrjcr5935fr4dfwq2zwd70sjx2sh9s78";
-    };
-    patchPhase = "";
-    patches = (old.patches or []) ++ [ ./shaarli_ldap.patch ];
-    installPhase = (old.installPhase or "") + ''
-      cp .htaccess $out/
-      ln -sf ${varDir}/{cache,pagecache,tmp,data} $out/
-      '';
-  });
+  shaarli = self.callPackage ../../pkgs/shaarli {};
 }
diff --git a/pkgs/shaarli/default.nix b/pkgs/shaarli/default.nix
new file mode 100644 (file)
index 0000000..23f9937
--- /dev/null
@@ -0,0 +1,24 @@
+{ varDir ? "/var/lib/shaarli", stdenv, fetchurl }:
+
+stdenv.mkDerivation rec {
+  pname = "shaarli";
+  version = "0.10.2";
+  src = fetchurl {
+    url = "https://github.com/shaarli/Shaarli/releases/download/v${version}/shaarli-v${version}-full.tar.gz";
+    sha256 = "0h8sspj7siy3vgpi2i3gdrjcr5935fr4dfwq2zwd70sjx2sh9s78";
+  };
+  patchPhase = "";
+
+  outputs = [ "out" "doc" ];
+
+  patches = [ ./shaarli_ldap.patch ];
+  installPhase = ''
+    rm -r {cache,pagecache,tmp,data}/
+    mkdir -p $doc/share/doc
+    mv doc/ $doc/share/doc/shaarli
+    mkdir $out/
+    cp -R ./* $out
+    cp .htaccess $out/
+    ln -sf ${varDir}/{cache,pagecache,tmp,data} $out/
+  '';
+}
diff --git a/pkgs/shaarli/shaarli_ldap.patch b/pkgs/shaarli/shaarli_ldap.patch
new file mode 100644 (file)
index 0000000..e66a54f
--- /dev/null
@@ -0,0 +1,425 @@
+commit a19c24edc1057bd411821f9e3e7d1d309d38b1bb
+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..58ae2dd 100644
+--- a/index.php
++++ b/index.php
+@@ -121,7 +121,32 @@
+     $_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) && isset($_POST["login"])) {
++    $userspace = preg_replace("/[^-_A-Za-z0-9]/", '', $_POST["login"]);
++    error_log("debugImmae: setting userspace from POST: " . $userspace);
++}
++
++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 +200,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 +230,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 +267,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 +296,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 +1745,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 +1781,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 +1790,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 +1867,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 +1891,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);
index 5da8687461d953dd6b1f63090f7778724d756cdb..0512c544fb59c874551b583cd18deefe758a89f1 100644 (file)
--- a/shell.nix
+++ b/shell.nix
@@ -1,4 +1,4 @@
 { pkgs ? import <nixpkgs> { overlays = builtins.attrValues (import ./overlays); } }:
 pkgs.mkShell {
-  buildInputs = [ pkgs.nixUnstable pkgs.python3 pkgs.sops pkgs.morph pkgs.niv pkgs.curl pkgs.shellcheck pkgs.jq pkgs.gnumake pkgs.yq ];
+  buildInputs = [ pkgs.nixUnstable pkgs.python3 pkgs.colmena pkgs.sops pkgs.morph pkgs.niv pkgs.curl pkgs.shellcheck pkgs.jq pkgs.gnumake pkgs.yq ];
 }