diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dbe70ecab6317b73ad18577c096684e648b6e733
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,124 @@
+# To use the registry, DOCKER_AUTH_CONFIG must be set
+image: registry.forgemia.inra.fr/agroclim/common/docker-projets-java:latest
+
+variables:
+  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
+  MAVEN_CLI_OPTS: "-s $CI_PROJECT_DIR/ci_settings.xml"
+
+default:
+  # Cache downloaded dependencies and plugins between builds.
+  cache:
+    key: maven-repository
+    paths:
+      - $CI_PROJECT_DIR/.m2/repository
+  tags:
+    - agroclim
+
+stages:
+    - build
+    - test
+    - install
+    - code-check
+    - deploy
+
+.settingsxml:
+  before_script:
+    - echo "Maven settings.xml"
+    - echo "$M2_SETTINGS_XML" > $CI_PROJECT_DIR/ci_settings.xml
+
+dependencies_job:
+  stage: build
+  extends: .settingsxml
+  script:
+    - echo "Download all dependencies (dependencies, plugins, reports)"
+    - mvn -s $CI_PROJECT_DIR/ci_settings.xml dependency:go-offline
+
+build_job:
+  stage: build
+  extends: .settingsxml
+  needs: ["dependencies_job"]
+  script:
+    - echo "Maven compile started"
+    - mvn $MAVEN_CLI_OPTS clean compile test-compile
+    - /usr/bin/tokei --version
+  artifacts:
+    expire_in: 1 week
+    when: always
+    paths:
+      - target
+
+test_job:
+  stage: test
+  extends: .settingsxml
+  needs: ["build_job"]
+  script:
+    - echo "Maven test started"
+    - mvn $MAVEN_CLI_OPTS test org.jacoco:jacoco-maven-plugin:report-aggregate
+  artifacts:
+    expire_in: 1 week
+    when: always
+    paths:
+      - target
+    reports:
+      junit:
+        - target/surefire-reports/TEST-*.xml
+        - target/failsafe-reports/TEST-*.xml
+
+install_job:
+  stage: install
+  extends: .settingsxml
+  needs: ["test_job"]
+  script:
+    - echo "Maven packaging started"
+    - mvn $MAVEN_CLI_OPTS install -Dcheckstyle.skip=true -Dcpd.skip=true -Dpmd.skip=true -DskipTests
+  artifacts:
+    expire_in: 1 week
+    when: always
+    paths:
+      - .m2/repository
+      - target
+
+checkstyle_job:
+  stage: code-check
+  extends: .settingsxml
+  needs: ["install_job"]
+  script:
+    - mvn $MAVEN_CLI_OPTS checkstyle:checkstyle
+
+pmd_job:
+  stage: code-check
+  extends: .settingsxml
+  needs: ["install_job"]
+  script:
+    - mvn $MAVEN_CLI_OPTS pmd:pmd
+
+cpd_job:
+  stage: code-check
+  extends: .settingsxml
+  needs: ["install_job"]
+  script:
+    - mvn $MAVEN_CLI_OPTS pmd:cpd
+
+cobertura_job:
+  stage: deploy
+  needs: ["test_job"]
+  script:
+    # convert report from jacoco to cobertura, using relative project path
+    - python /opt/cover2cover.py
+      target/site/jacoco-aggregate/jacoco.xml
+      $CI_PROJECT_DIR/src/main/java/
+      > target/cobertura.xml
+
+# https://agroclim.pages.mia.inra.fr/agrometinfo/season-handler/
+pages:
+  stage: deploy
+  extends: .settingsxml
+  needs: ["checkstyle_job", "pmd_job", "cpd_job"]
+  script:
+    - mvn $MAVEN_CLI_OPTS site -DskipTests
+  artifacts:
+    expire_in: 1 week
+    when: always
+    paths:
+      - target
+  publish: target/site
diff --git a/README.md b/README.md
index ce7b3b352dbaa4871f5d46aad8918ba79a49e77c..99e7d3d0c6d48a7dffe204080004ffa4e82e2bb2 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ mvn package
 
 ## Usage
 
-See [documentation](src/site/markdown/usage.md).
+See [documentation](src/site/markdown/usage.md), available online at <https://agroclim.pages.mia.inra.fr/agrometinfo/season-handler/>.
 
 Documentation is handled by `maven-site-plugin`.
 
diff --git a/bin/sloccount.sh b/bin/sloccount.sh
index b15e5804f88ded23261ec0eee3f06905db376c32..41aed14ea0f8c5c79791e5e73a8d16a9aa995695 100755
--- a/bin/sloccount.sh
+++ b/bin/sloccount.sh
@@ -1,30 +1,28 @@
 #!/bin/bash
-
-TOKEI2SLOCCOUNT=$(dirname $0)/tokei2sloccount.py
+ROOT_DIR=$(dirname $(dirname $0))
 TOKEI2CLOC=$(dirname $0)/tokei2cloc.py
-TOKEITGZ=tokei-v7.0.3-x86_64-unknown-linux-gnu.tar.gz
-if [ ! -f bin/tokei ]; then
-	mkdir -p ~/tmp bin
-	if [ ! -f ~/tmp/$TOKEITGZ ]; then
-		wget "https://github.com/Aaronepower/tokei/releases/download/v7.0.3/$TOKEITGZ" -O ~/tmp/$TOKEITGZ
+TOKEI=$(command -v tokei)
+if [ ! -x "$TOKEI" ]; then
+	TOKEITGZ=tokei-x86_64-unknown-linux-gnu.tar.gz
+	if [ ! -f bin/tokei ]; then
+		mkdir -p ~/tmp
+		if [ ! -f ~/tmp/$TOKEITGZ ]; then
+			wget "https://github.com/Aaronepower/tokei/releases/download/v12.1.2/$TOKEITGZ" -O ~/tmp/$TOKEITGZ
+		fi
+		tar zxf ~/tmp/$TOKEITGZ -C bin
 	fi
-	tar zxf ~/tmp/$TOKEITGZ -C bin
+	TOKEI=bin/tokei
 fi
-if [ -f bin/tokei ]; then
-	mkdir -p target
-	#bin/tokei -f -o json src | $TOKEI2SLOCCOUNT > target/sloccount.sc
-	bin/tokei -f -o json src | $TOKEI2CLOC > target/cloc.xml
-	exit
+if [ ! -f $TOKEI ]; then
+	echo "Strange, $TOKEI does not exist!"
+	exit 1
 fi
 
-SLOCCOUNT=$(which sloccount);
-if [ "$SLOCCOUNT" != "" ]; then
-	DATADIR=$(dirname $0)/.slocdata;
-	if [ ! -f $DATADIR ]; then
-		mkdir -p $DATADIR;
-	fi
-	mkdir -p target ;
-	/usr/bin/sloccount --datadir $DATADIR --duplicates --wide --details src > target/sloccount.sc;
-else 
-	echo "sloccount not found!";
-fi
\ No newline at end of file
+echo "tokei is installed at $TOKEI"
+$TOKEI --version
+mkdir -p $ROOT_DIR/target
+if [ ! -d $ROOT_DIR/www-client ]; then
+	$TOKEI -f -o json $ROOT_DIR/src | $TOKEI2CLOC > $ROOT_DIR/target/cloc.xml
+	exit
+fi
+$TOKEI -f -o json $ROOT_DIR/www-client/src $ROOT_DIR/www-server/src $ROOT_DIR/www-shared/src | $TOKEI2CLOC > $ROOT_DIR/target/cloc.xml
diff --git a/bin/tokei2cloc.py b/bin/tokei2cloc.py
index 5bd0146007a58a9e821f9738b67f02377327e532..bc41a97876ca9d42b55f6a2aaa1e2590312f3d01 100755
--- a/bin/tokei2cloc.py
+++ b/bin/tokei2cloc.py
@@ -1,29 +1,12 @@
 #!/usr/bin/env python3
 # -*- coding: UTF-8 -*-
-#
-# data.pheno.fr - Phenological data portal of TEMPO
-# Copyright © 2019 TEMPO (contact-tempo@inrae.fr)
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
 
-# $Id: tokei2cloc.py 77 2019-01-11 17:24:18Z omaury $
+# $Id$
 #
 # Author : Olivier Maury
 # Creation Date : 2019-01-15 10:30:29 +0200
-# Last Revision : $Date: 2019-01-11 18:24:18 +0100 (ven., 11 janv. 2019) $
-# Revision : $Rev: 77 $
+# Last Revision : $Date$
+# Revision : $Rev$
 u"""
 NOM
         %prog - Tokei2Cloc
@@ -38,10 +21,10 @@ u"""
         Olivier Maury
 
 VERSION
-        $Date: 2019-01-11 18:24:18 +0100 (ven., 11 janv. 2019) $
+        $Date$
 """
 
-__revision__ = "$Rev: 77 $"
+__revision__ = "$Rev$"
 __author__ = "Olivier Maury"
 import json
 import sys
@@ -52,8 +35,10 @@ results = json.loads(sys.stdin.read())
 nb_files = 0
 nb_lines = 0
 for lang in results:
-    nb_files += len(results[lang]['stats'])
-    nb_lines += int(results[lang]['lines'])
+    nb_files += len(results[lang]['reports'])
+    nb_lines += int(results[lang]['blanks'])
+    nb_lines += int(results[lang]['code'])
+    nb_lines += int(results[lang]['comments'])
 
 print("""<?xml version="1.0"?><results>
 <header>
@@ -73,10 +58,10 @@ total_blank = 0
 total_comment = 0
 total_code = 0
 for lang in results:
-    for result in results[lang]['stats']:
-        blank = int(result['blanks'])
-        comment = int(result['comments'])
-        code = int(result['code'])
+    for result in results[lang]['reports']:
+        blank = int(result['stats']['blanks'])
+        comment = int(result['stats']['comments'])
+        code = int(result['stats']['code'])
         print("""  <file name="%s" blank="%d" comment="%d" code="%d"  language="%s" />""" % 
             (result['name'], blank, comment, code, lang))
         total_blank += blank
diff --git a/pom.xml b/pom.xml
index 769865b76b6b4fdf7263a08faadbe6bb223b4964..1cf20127fd7026228a5694d5e6d2f16c6306b74e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>fr.agrometinfo</groupId>
   <artifactId>season-handler</artifactId>
-  <version>2.0.0</version>
+  <version>2.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>AgroMetInfo SEASON handler</name>
   <description>SEASON handler for AgroMetInfo</description>
@@ -108,6 +108,18 @@
       <version>${junit.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>fr.inrae.agroclim</groupId>
+      <artifactId>season-core-test</artifactId>
+      <version>${season.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.h2database</groupId>
+      <artifactId>h2</artifactId>
+      <version>2.2.224</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
   <build>
     <resources>
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 17f46ee961a60f615c54d90357287ee807cc3985..758cdd2ea23c93e77d341a31dc060e7c121f5d20 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -23,8 +23,8 @@ Some basic use cases are described in the examples given below.
 export JAVA_HOME=~/bin/jdk-17
 
 # show help
-$JAVA_HOME/bin/java -jar target/season-handler-0.1-SNAPSHOT.jar --help
+$JAVA_HOME/bin/java -jar target/agrometinfo-season-handler-2.0.1.jar --help
 
 # launch the handler with configuration
-$JAVA_HOME/bin/java -jar target/season-handler-0.1-SNAPSHOT.jar --config src/test/resources/config-good-with-stages.properties
+$JAVA_HOME/bin/java -jar target/agrometinfo-season-handler-2.0.1.jar --config src/test/resources/config-good-with-stages.properties
 ```
\ No newline at end of file
diff --git a/src/site/markdown/usage.md b/src/site/markdown/usage.md
index a2e2b18e3adfa305f2de3df3818bded829b86e33..22a73e13cdf4d2cabad1cf701390dc14609b017e 100644
--- a/src/site/markdown/usage.md
+++ b/src/site/markdown/usage.md
@@ -25,7 +25,7 @@ Exit codes:
 Default usage is:
 
 ```sh
-$JAVA_HOME/bin/java -jar target/season-handler-0.1-SNAPSHOT.jar --config src/test/resources/config-good-with-stages.properties
+$JAVA_HOME/bin/java -jar target/agrometinfo-season-handler-2.0.1.jar --config src/test/resources/config-good-with-stages.properties
 ```
 
 ## Configuration file
diff --git a/src/test/java/fr/agrometinfo/seasonhandler/JdbcTestInitialization.java b/src/test/java/fr/agrometinfo/seasonhandler/JdbcTestInitialization.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed886631675960ba085632fcf0be4427b3127b81
--- /dev/null
+++ b/src/test/java/fr/agrometinfo/seasonhandler/JdbcTestInitialization.java
@@ -0,0 +1,29 @@
+package fr.agrometinfo.seasonhandler;
+
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import fr.inrae.agroclim.season.core.dao.PersistenceManager;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * Instantiate persistence managers for SEASON.
+ *
+ * @author Olivier Maury
+ */
+@Log4j2
+public final class JdbcTestInitialization implements BeforeAllCallback {
+
+    /**
+     * If initialization was done.
+     */
+    private static boolean started = false;
+
+    @Override
+    public void beforeAll(final ExtensionContext context) {
+        if (!started) {
+            started = true;
+            LOGGER.info("Initializing persistence manager.");
+            PersistenceManager.getINSTANCE().init("META-INF/persistence.properties");
+        }
+    }
+}
diff --git a/src/test/java/fr/agrometinfo/seasonhandler/MainConfigurationTest.java b/src/test/java/fr/agrometinfo/seasonhandler/MainConfigurationTest.java
index 2d7a96d23e7bd99521e64b5c4e8b8d618f0471b7..6d7b91a68106b880325a5fc54e55552eb13eadf0 100644
--- a/src/test/java/fr/agrometinfo/seasonhandler/MainConfigurationTest.java
+++ b/src/test/java/fr/agrometinfo/seasonhandler/MainConfigurationTest.java
@@ -25,12 +25,14 @@ import java.nio.file.Paths;
 import java.util.Optional;
 
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 
 /**
  * Test {@link MainConfiguration}.
  *
  * @author Olivier Maury
  */
+@ExtendWith({JdbcTestInitialization.class})
 class MainConfigurationTest {
 
     @Test
diff --git a/src/test/resources/config-good.properties b/src/test/resources/config-good.properties
index bd2f6c7131e856e97976ee3372bed20c47f18adf..8c142114e2ed899e465f48780cc6917a920e3254 100644
--- a/src/test/resources/config-good.properties
+++ b/src/test/resources/config-good.properties
@@ -26,8 +26,12 @@ season.results.table.reader = season-read
 
 ## Persistence unit "simulation"
 
-simulation.jakarta.persistence.jdbc.driver = org.postgresql.Driver
-simulation.jakarta.persistence.jdbc.url = jdbc:postgresql://localhost/season?ApplicationName=season-cli-simulation-dev
+## with PostgreSQL
+# simulation.jakarta.persistence.jdbc.driver = org.postgresql.Driver
+# simulation.jakarta.persistence.jdbc.url = jdbc:postgresql://localhost/season?ApplicationName=season-cli-simulation-dev
+## for tests, with H2
+simulation.jakarta.persistence.jdbc.driver = org.h2.Driver
+simulation.jakarta.persistence.jdbc.url    = jdbc:h2:mem:test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:schema.functions.sql'\\;RUNSCRIPT FROM 'classpath:schema.tables.sql'\\;RUNSCRIPT FROM 'classpath:schema.views_test.sql'\\;RUNSCRIPT FROM 'classpath:init_data.sql'
 simulation.jakarta.persistence.jdbc.user = season
 simulation.jakarta.persistence.jdbc.password = season
 simulation.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect