diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e94a20ee32b5ada65a496abd021e174419e83404..9e96fa1767e09c3fba62193a65757d7f7c247868 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,6 +7,7 @@ stages:
   - tests
   - distrib
   - compatibility
+  - release
   - publish
 
 ################################################################################
@@ -23,6 +24,7 @@ variables:
   DEFAULT: "master"
   OCAML: "4.11"
   PUBLISH: "no"
+  RELEASE: "no"
 
 ################################################################################
 ### PREPARE
@@ -156,33 +158,16 @@ external-plugins:
 
 # API documentation
 
-.build_template: &api_doc_template
+api-doc: # Note: the Nix store avoids rebuilding
   stage: distrib
   variables:
     OUT: "api"
   script:
     - ./nix/build-proxy.sh api-doc
-
-api-doc-default: # Check that we successfully build but no artifact
-  <<: *api_doc_template
-
-api-doc-artifact: # Note: the Nix store avoids rebuilding
-  <<: *api_doc_template
   artifacts:
     paths:
       - api/frama-c-api.tar.gz
     expire_in: 7 days
-  when: manual
-
-api-doc-scheduled: # Keep artifact for published branch each night
-  <<: *api_doc_template
-  artifacts:
-    paths:
-      - api/frama-c-api.tar.gz
-    expire_in: 7 days # Note: the LAST artifact of the ref is always kept
-  only:
-    variables:
-      - $PUBLISH == "yes"
 
 # Build distribution tarball
 
@@ -225,10 +210,6 @@ lint:
     OUT: "manuals"
   script:
     - ./nix/build-proxy.sh manuals
-
-manuals:
-  <<: *manuals_template
-  when: manual
   artifacts:
     paths:
       - manuals/*.pdf
@@ -236,6 +217,36 @@ manuals:
       - manuals/*.txt
     expire_in: 7 days # Note: the LAST artifact of the ref is always kept
 
+manuals:
+  <<: *manuals_template
+  except:
+    variables:
+      - $RELEASE == "yes"
+  when: manual
+
+manuals-for-release:
+  <<: *manuals_template
+  only:
+    variables:
+      - $RELEASE == "yes"
+
+release-content:
+  stage: distrib
+  script:
+    - ./nix/shell-checkers.sh "./dev/build-release.sh"
+  needs:
+    - api-doc
+    - build-distrib-tarball
+    - manuals-for-release
+  artifacts:
+    paths:
+      - website
+      - wiki
+      - opam-repository
+  only:
+    variables:
+      - $RELEASE == "yes"
+
 ################################################################################
 ### COMPATIBILITY
 
@@ -284,7 +295,7 @@ ocaml-versions-nightly:
 
 # Opam pin
 
-.build_template: &opam_template
+.build_template: &opam_pin_template
   stage: compatibility
   image: 'ocaml/opam:ubuntu-20.04-ocaml-$OCAML'
   script:
@@ -296,15 +307,20 @@ ocaml-versions-nightly:
     - docker
 
 opam-pin:
-  <<: *opam_template
-  when: manual
+  <<: *opam_pin_template
   except:
-    - schedules
+    refs:
+      - schedules
+    variables:
+      - $RELEASE == "yes"
 
-opam-pin-nightly:
-  <<: *opam_template
+opam-pin-automatic:
+  <<: *opam_pin_template
   only:
-    - schedules
+    refs:
+      - schedules
+    variables:
+      - $RELEASE == "yes"
 
 # TODO: Enable this rule later:
 #
@@ -338,14 +354,69 @@ opam-pin-nightly:
 src-distrib-tests:
   <<: *src_distrib_tests_template
   except:
-    - schedules
+    refs:
+      - schedules
+    variables:
+      - $RELEASE == "yes"
 
-src-distrib-tests-nightly:
+src-distrib-tests-long:
   <<: *src_distrib_tests_template
   # The Opam target may affect this job
   timeout: 2h
   only:
-    - schedules
+    refs:
+      - schedules
+    variables:
+      - $RELEASE == "yes"
+
+################################################################################
+### RELEASE
+
+release-frama-c:
+  stage: release
+  script:
+    - (! git merge-base --is-ancestor a1e186c68a6418a53b3dc06237f49e8dcbf75f4a origin/$CI_COMMIT_BRANCH)
+    - echo "$FRAMA_C_PUBLIC_SSH_PRIVATE_KEY" | nix run -f channel:nixos-19.03 coreutils --command base64 -d > nix/frama-c-public/id_ed25519
+    - nix run -f channel:nixos-19.03 coreutils --command chmod 400 nix/frama-c-public/id_ed25519
+    - GIT_SSH=nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git push git@git.frama-c.com:pub/frama-c.git origin/$CI_COMMIT_BRANCH:refs/heads/$CI_COMMIT_BRANCH
+  only:
+    variables:
+      - $RELEASE == "yes"
+  when: manual
+  interruptible: false
+
+release-opam:
+  stage: release
+  script:
+    - echo "$FRAMA_C_PUBLIC_SSH_PRIVATE_KEY" | nix run -f channel:nixos-19.03 coreutils --command base64 -d > nix/frama-c-public/id_ed25519
+    - nix run -f channel:nixos-19.03 coreutils --command chmod 400 nix/frama-c-public/id_ed25519
+    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command ./nix/frama-c-public/publish-opam.sh
+  only:
+    variables:
+      - $RELEASE == "yes"
+  interruptible: false
+
+release-website:
+  stage: release
+  script:
+    - echo "$FRAMA_C_PUBLIC_SSH_PRIVATE_KEY" | nix run -f channel:nixos-19.03 coreutils --command base64 -d > nix/frama-c-public/id_ed25519
+    - nix run -f channel:nixos-19.03 coreutils --command chmod 400 nix/frama-c-public/id_ed25519
+    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command ./nix/frama-c-public/publish-website.sh
+  only:
+    variables:
+      - $RELEASE == "yes"
+  interruptible: false
+
+release-wiki:
+  stage: release
+  script:
+    - echo "$FRAMA_C_PUBLIC_SSH_PRIVATE_KEY" | nix run -f channel:nixos-19.03 coreutils --command base64 -d > nix/frama-c-public/id_ed25519
+    - nix run -f channel:nixos-19.03 coreutils --command chmod 400 nix/frama-c-public/id_ed25519
+    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command ./nix/frama-c-public/publish-wiki.sh
+  only:
+    variables:
+      - $RELEASE == "yes"
+  interruptible: false
 
 ################################################################################
 ### PUBLISH
@@ -376,25 +447,25 @@ publish-frama-c:
       - $PUBLISH == "yes"
   interruptible: false
 
-publish-meta:
+publish-fclang:
   stage: publish
   script:
     - echo "$FRAMA_C_PUBLIC_SSH_PRIVATE_KEY" | nix run -f channel:nixos-19.03 coreutils --command base64 -d > nix/frama-c-public/id_ed25519
     - nix run -f channel:nixos-19.03 coreutils --command chmod 400 nix/frama-c-public/id_ed25519
-    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git clone git@git.frama-c.com:frama-c/meta.git nix/frama-c-public/meta
-    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git -C nix/frama-c-public/meta push git@git.frama-c.com:pub/meta origin/master:refs/heads/master
+    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git clone git@git.frama-c.com:frama-c/frama-clang.git nix/frama-c-public/frama-clang
+    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git -C nix/frama-c-public/frama-clang push git@git.frama-c.com:pub/frama-clang origin/master:refs/heads/master
   only:
     variables:
       - $PUBLISH == "yes"
   interruptible: false
 
-publish-fclang:
+publish-meta:
   stage: publish
   script:
     - echo "$FRAMA_C_PUBLIC_SSH_PRIVATE_KEY" | nix run -f channel:nixos-19.03 coreutils --command base64 -d > nix/frama-c-public/id_ed25519
     - nix run -f channel:nixos-19.03 coreutils --command chmod 400 nix/frama-c-public/id_ed25519
-    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git clone git@git.frama-c.com:frama-c/frama-clang.git nix/frama-c-public/frama-clang
-    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git -C nix/frama-c-public/frama-clang push git@git.frama-c.com:pub/frama-clang origin/master:refs/heads/master
+    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git clone git@git.frama-c.com:frama-c/meta.git nix/frama-c-public/meta
+    - GIT_SSH=$PWD/nix/frama-c-public/ssh.sh nix run -f channel:nixos-19.03 openssh --command git -C nix/frama-c-public/meta push git@git.frama-c.com:pub/meta origin/master:refs/heads/master
   only:
     variables:
       - $PUBLISH == "yes"
diff --git a/dev/build-release.sh b/dev/build-release.sh
index e4859e6584590e7268cf1532d079955b98c9c3bf..75ce8d408fa1381db696aaae7132494cc9899c8a 100755
--- a/dev/build-release.sh
+++ b/dev/build-release.sh
@@ -72,6 +72,8 @@ fi
 MANUALS_DIR="./manuals"
 API_DIR="./api"
 
+FRAMAC_COM_DOWNLOAD="https://www.frama-c.com/download"
+
 ##########################################################################
 # Check stable or beta and build names
 
@@ -82,7 +84,7 @@ VERSION_SAFE="$(echo ${VERSION/~/-})"
 VERSION_MODIFIER=$(cat VERSION | sed -e s/[0-9.]*\\\(.*\\\)/\\1/)
 VERSION_MAJOR=$(cat VERSION | sed -e s/\\\([0-9]*\\\).[0-9]*.*/\\1/)
 VERSION_MINOR=$(cat VERSION | sed -e s/[0-9]*.\\\([0-9]*\\\).*/\\1/)
-# TAG="$(git describe --tag)"
+TAG="$(git describe --tag)"
 CODENAME="$(cat VERSION_CODENAME)"
 LOWER_CODENAME="$(echo "$CODENAME" | tr '[:upper:]' '[:lower:]')"
 VERSION_AND_CODENAME="${VERSION_SAFE}-${CODENAME}"
@@ -93,13 +95,13 @@ if [ "$VERSION_MODIFIER" == "+dev" ]; then
   exit 2
 fi
 
-# if [ "$VERSION_SAFE" != "$TAG" ]; then
-#   echo "The current commit is not tagged with the current version:"
-#   echo "Frama-C Version: $VERSION"
-#   echo "Frama-C Tag    : $TAG"
-#   echo_red "Aborting"
-#   exit 2
-# fi
+if [ "$VERSION_SAFE" != "$TAG" ]; then
+  echo "The current commit is not tagged with the current version:"
+  echo "Frama-C Version: $VERSION"
+  echo "Frama-C Tag    : $TAG"
+  echo_red "Aborting"
+  exit 2
+fi
 
 if test -n "$VERSION_MODIFIER"; then
   FINAL=no
@@ -157,6 +159,7 @@ MANUALS=(
 
 COMPANIONS=(
   "aorai-example"
+  "hello"
 )
 
 for manual in "${MANUALS[@]}"; do
@@ -164,7 +167,7 @@ for manual in "${MANUALS[@]}"; do
 done
 
 for companion in "${COMPANIONS[@]}"; do
-  prepare_file "$MANUALS_DIR/$companion.tgz"
+  prepare_file "$MANUALS_DIR/$companion.tar.gz"
 done
 
 prepare_file "$MANUALS_DIR/acsl-version.txt"
@@ -200,21 +203,13 @@ function version_name {
 # $2 : extension
 
 function copy_normal {
-  if [ "yes" == "$GENERIC" ]; then
-    cp "$MANUALS_DIR/$1.$2" "$MANS_TARGET_DIR/$(generic_name $1).$2"
-  fi
+  cp "$MANUALS_DIR/$1.$2" "$MANS_TARGET_DIR/$(generic_name $1).$2"
   cp "$MANUALS_DIR/$1.$2" "$MANS_TARGET_DIR/$(version_name $1).$2"
 }
 
 function copy_e_acsl {
-  if [ "yes" == "$EACSL_SUBDIR" ]; then
-    EACSL_TARGET_DIR="$MANS_TARGET_DIR/e-acsl"
-  else
-    EACSL_TARGET_DIR="$MANS_TARGET_DIR"
-  fi
-  if [ "yes" == "$GENERIC" ]; then
-    cp "$MANUALS_DIR/$1.$2" "$EACSL_TARGET_DIR/$(generic_name $1).$2"
-  fi
+  EACSL_TARGET_DIR="$MANS_TARGET_DIR/e-acsl"
+  cp "$MANUALS_DIR/$1.$2" "$EACSL_TARGET_DIR/$(generic_name $1).$2"
   cp "$MANUALS_DIR/$1.$2" "$EACSL_TARGET_DIR/$(version_name $1).$2"
 }
 
@@ -227,18 +222,14 @@ function copy_files {
     fi
   done
   for companion in "${COMPANIONS[@]}"; do
-    copy_normal $companion "tgz"
+    copy_normal $companion "tar.gz"
   done
 
   # Eva has an old manual name that might be in use:
-  if [ "yes" == "$GENERIC" ]; then
-    cp "$MANS_TARGET_DIR/frama-c-eva-manual.pdf" "$MANS_TARGET_DIR/frama-c-value-analysis.pdf"
-  fi
+  cp "$MANS_TARGET_DIR/frama-c-eva-manual.pdf" "$MANS_TARGET_DIR/frama-c-value-analysis.pdf"
 
   cp $TARGZ_BASE "$GZ_TARGET_DIR/$TARGZ_VERSION"
-  if [ "yes" == "$GENERIC" ]; then
-    cp $TARGZ_BASE "$GZ_TARGET_DIR/$TARGZ_GENERIC"
-  fi
+  cp $TARGZ_BASE "$GZ_TARGET_DIR/$TARGZ_GENERIC"
 
   cp $TARGZ_API "$MANS_TARGET_DIR/frama-c-$VERSION_AND_CODENAME-api.tar.gz"
 }
@@ -258,7 +249,7 @@ cat opam/opam | grep -v "^version\:" | grep -v "^name\:" > $OPAM_FC_DIR/opam
 cat >>$OPAM_FC_DIR/opam << EOL
 
 url {
-  src: "https://git.frama-c.com/pub/frama-c/-/wikis/downloads/$TARGZ_VERSION"
+  src: "$FRAMAC_COM_DOWNLOAD/$TARGZ_VERSION"
   checksum: "sha256=$(sha256sum $TARGZ_BASE | cut -d" " -f1)"
 }
 EOL
@@ -271,19 +262,8 @@ echo "Opam file built"
 show_step "Building website"
 
 WIKI_DIR="wiki"
-WIKI_DL_DIR="$WIKI_DIR/downloads"
-WIKI_MANS_DIR="$WIKI_DIR/manuals"
 
 mkdir -p $WIKI_DIR
-mkdir -p $WIKI_DL_DIR
-mkdir -p $WIKI_MANS_DIR
-
-GZ_TARGET_DIR="$WIKI_DL_DIR"
-MANS_TARGET_DIR="$WIKI_MANS_DIR"
-EACSL_SUBDIR="no"
-GENERIC="no"
-
-copy_files
 
 echo "Download directory built"
 
@@ -291,12 +271,23 @@ WIKI_PAGE="$WIKI_DIR/Frama-C-${VERSION_AND_CODENAME}.md"
 
 echo "# Frama-C release $VERSION ($CODENAME)" > $WIKI_PAGE
 echo "## Sources" >> $WIKI_PAGE
-echo "- [$TARGZ_VERSION](downloads/$TARGZ_VERSION)" >> $WIKI_PAGE
+echo "- [$TARGZ_VERSION]($FRAMAC_COM_DOWNLOAD/$TARGZ_VERSION)" >> $WIKI_PAGE
 echo "" >> $WIKI_PAGE
 echo "## Manuals" >> $WIKI_PAGE
-for manual in  "${MANUALS[@]}"; do
+for manual in "${MANUALS[@]}"; do
+  if [[ $manual =~ ^e-acsl.*$ ]]; then
+    DIR="$FRAMAC_COM_DOWNLOAD/e-acsl"
+  else
+    DIR="$FRAMAC_COM_DOWNLOAD"
+  fi
   NAME=$(version_name $manual)
-  echo "- [$manual](manuals/$NAME.pdf)" >> $WIKI_PAGE
+  echo "- [$manual]($DIR/$NAME.pdf)" >> $WIKI_PAGE
+done
+echo "" >> $WIKI_PAGE
+echo "## Companion archives" >> $WIKI_PAGE
+for archive in "${COMPANIONS[@]}"; do
+  NAME=$(version_name $archive)
+  echo "- [$archive]($FRAMAC_COM_DOWNLOAD/$NAME.tar.gz)" >> $WIKI_PAGE
 done
 echo "" >> $WIKI_PAGE
 echo "## Main changes" >> $WIKI_PAGE
@@ -432,7 +423,7 @@ releases:
     - name: Aoraï manual
       link: /download/aorai-manual-$VERSION_AND_CODENAME.pdf
       help: Aoraï example
-      help_link: /download/aorai-example-$VERSION_AND_CODENAME.tgz
+      help_link: /download/aorai-example-$VERSION_AND_CODENAME.tar.gz
     - name: Metrics manual"
       link: /download/metrics-manual-$VERSION_AND_CODENAME.pdf"
     - name: Rte manual"
diff --git a/nix/frama-c-public/publish-opam.sh b/nix/frama-c-public/publish-opam.sh
new file mode 100755
index 0000000000000000000000000000000000000000..eddc6fa6f2b2ef5622efcd9781b182577e10041b
--- /dev/null
+++ b/nix/frama-c-public/publish-opam.sh
@@ -0,0 +1,112 @@
+#! /usr/bin/env bash
+##########################################################################
+#                                                                        #
+#  This file is part of Frama-C.                                         #
+#                                                                        #
+#  Copyright (C) 2007-2022                                               #
+#    CEA (Commissariat à l'énergie atomique et aux énergies              #
+#         alternatives)                                                  #
+#                                                                        #
+#  you can redistribute it and/or modify it under the terms of the GNU   #
+#  Lesser General Public License as published by the Free Software       #
+#  Foundation, version 2.1.                                              #
+#                                                                        #
+#  It 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 Lesser General Public License for more details.                   #
+#                                                                        #
+#  See the GNU Lesser General Public License version 2.1                 #
+#  for more details (enclosed in the file licenses/LGPLv2.1).            #
+#                                                                        #
+##########################################################################
+
+# Note:
+#
+# While this script can be run locally, it is meant to run in the Frama-C CI.
+# Thus, it expects to be run from the root of the Frama-C directory and that
+# some CI artifacts are available. Namely:
+#   - the 'opam-repository' as generated by the script Frama-C/dev/build-release.sh
+# Availability of the files is NOT checked when the script starts.
+# The script:
+#   - clones the 'Frama-C/opam-repository' as 'pub-opam-repository'
+#   - creates a remote on the 'opam/opam-repository' named 'opam'
+#   - creates a branch based on 'opam/master'
+#   - copies the content of 'opam-repository' to 'pub-opam-repository'
+#   - commits the changes
+#   - pushes the branch
+
+##########################################################################
+
+OCAML_OPAM_GIT="git@github.com:ocaml/opam-repository.git"
+PUB_OPAM_GIT="git@github.com:Frama-C/opam-repository.git"
+PUB_OPAM_DIR="pub-opam-repository"
+SRC_OPAM_DIR="opam-repository"
+
+VERSION="$(cat VERSION)"
+CODENAME="$(cat VERSION_CODENAME)"
+
+BRANCH="frama-c.$VERSION"
+
+if ! git clone $PUB_OPAM_GIT $PUB_OPAM_DIR
+then
+  echo "Failed to clone website directory"
+  exit 128
+fi
+
+GIT="git -C $PUB_OPAM_DIR"
+$GIT remote add opam $OCAML_OPAM_GIT
+
+$GIT config user.name  "Frama-CI Bot"
+$GIT config user.email "frama-ci-bot@frama-c.com"
+
+function fetch_opam {
+  $GIT fetch opam
+  if [ "$?" -ne "0" ]; then
+    echo "Can't fetch from ocaml opam repository"
+    exit 128
+  fi
+}
+
+function checkout {
+  if git ls-remote --quiet --exit-code $PUB_OPAM_GIT $BRANCH
+  then
+    $GIT checkout $BRANCH
+  else
+    $GIT checkout -b $BRANCH opam/master --no-track
+  fi
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to checkout branch '$BRANCH', aborting"
+    exit 128
+  fi
+}
+
+function commit {
+  $GIT commit -m "Frama-C: new release ($VERSION-$CODENAME)"
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to commit on branch"
+    exit 128
+  fi
+}
+
+function push {
+  if git ls-remote --quiet --exit-code $PUB_OPAM_GIT $BRANCH
+  then
+    $GIT push
+  else
+    $GIT push --set-upstream origin $BRANCH
+  fi
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to push branch"
+    exit 128
+  fi
+}
+
+fetch_opam
+checkout
+
+cp -r $SRC_OPAM_DIR/* $PUB_OPAM_DIR
+$GIT add -A
+
+commit
+push
diff --git a/nix/frama-c-public/publish-website.sh b/nix/frama-c-public/publish-website.sh
new file mode 100755
index 0000000000000000000000000000000000000000..42169aaa98d3c574686876cb0a7b12d6a2b7e4f7
--- /dev/null
+++ b/nix/frama-c-public/publish-website.sh
@@ -0,0 +1,118 @@
+#! /usr/bin/env bash
+##########################################################################
+#                                                                        #
+#  This file is part of Frama-C.                                         #
+#                                                                        #
+#  Copyright (C) 2007-2022                                               #
+#    CEA (Commissariat à l'énergie atomique et aux énergies              #
+#         alternatives)                                                  #
+#                                                                        #
+#  you can redistribute it and/or modify it under the terms of the GNU   #
+#  Lesser General Public License as published by the Free Software       #
+#  Foundation, version 2.1.                                              #
+#                                                                        #
+#  It 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 Lesser General Public License for more details.                   #
+#                                                                        #
+#  See the GNU Lesser General Public License version 2.1                 #
+#  for more details (enclosed in the file licenses/LGPLv2.1).            #
+#                                                                        #
+##########################################################################
+
+# Note:
+#
+# While this script can be run locally, it is meant to run in the Frama-C CI.
+# Thus, it expects to be run from the root of the Frama-C directory and that
+# some CI artifacts are available. Namely:
+#   - the 'website' as generated by the script Frama-C/dev/build-release.sh
+# Availability of the files is NOT checked when the script starts.
+# The following variables are used in the cloned website:
+#   - GITLAB_USER_NAME (who triggered the job)
+#   - GITLAB_USER_EMAIL
+#   - GITLAB_USER_LOGIN
+#   - GITLAB_USER_ID
+# The script:
+#   - clones the website as 'pub-website'
+#   - checkout 'stable/release/<VERSION>-<LOWER CODENAME>'
+#     if the branch does not exist in the repo, it is created
+#   - copies the content of 'website' to 'pub-website'
+#   - commits the changes
+#   - pushes the branch
+#     if the branch does not exist in the repo, a MR is created
+
+##########################################################################
+
+PUB_WEBSITE_GIT="git@git.frama-c.com:pub/pub.frama-c.com.git"
+PUB_WEBSITE_DIR="pub-website"
+SRC_WEBSITE_DIR="website"
+
+VERSION="$(cat VERSION)"
+CODENAME="$(cat VERSION_CODENAME)"
+LOWER_CODENAME="$(echo "$CODENAME" | tr '[:upper:]' '[:lower:]')"
+
+BRANCH="release/$VERSION-$LOWER_CODENAME"
+
+if ! git clone $PUB_WEBSITE_GIT $PUB_WEBSITE_DIR
+then
+  echo "Failed to clone website directory"
+  exit 128
+fi
+
+GIT="git -C $PUB_WEBSITE_DIR"
+
+$GIT config user.name  "Frama-CI Bot"
+$GIT config user.email "frama-ci-bot@frama-c.com"
+
+function checkout {
+  if git ls-remote --quiet --exit-code $PUB_WEBSITE_GIT $BRANCH
+  then
+    $GIT checkout $BRANCH
+  else
+    $GIT checkout -b $BRANCH
+  fi
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to checkout branch '$BRANCH', aborting"
+    exit 128
+  fi
+}
+
+function commit {
+  $GIT commit -F- <<EOF
+[release] prepare $VERSION-$LOWER_CODENAME
+
+On behalf of "$GITLAB_USER_NAME" <$GITLAB_USER_EMAIL> (@$GITLAB_USER_LOGIN)
+EOF
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to commit on branch"
+    exit 128
+  fi
+}
+
+function push {
+  if git ls-remote --quiet --exit-code $PUB_WEBSITE_GIT $BRANCH
+  then
+    $GIT push
+  else
+    $GIT push \
+      --set-upstream origin $BRANCH \
+      -o merge_request.create \
+      -o merge_request.title="Release $VERSION-$CODENAME" \
+      -o merge_request.assign="$GITLAB_USER_ID"\
+      -o merge_request.draft \
+      -o ci.skip
+  fi
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to push branch"
+    exit 128
+  fi
+}
+
+checkout
+
+cp -r $SRC_WEBSITE_DIR/* $PUB_WEBSITE_DIR
+$GIT add -A
+
+commit
+push
diff --git a/nix/frama-c-public/publish-wiki.sh b/nix/frama-c-public/publish-wiki.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c43eaa07300c545459c59b6831d4e098ebd1f750
--- /dev/null
+++ b/nix/frama-c-public/publish-wiki.sh
@@ -0,0 +1,97 @@
+#! /usr/bin/env bash
+##########################################################################
+#                                                                        #
+#  This file is part of Frama-C.                                         #
+#                                                                        #
+#  Copyright (C) 2007-2022                                               #
+#    CEA (Commissariat à l'énergie atomique et aux énergies              #
+#         alternatives)                                                  #
+#                                                                        #
+#  you can redistribute it and/or modify it under the terms of the GNU   #
+#  Lesser General Public License as published by the Free Software       #
+#  Foundation, version 2.1.                                              #
+#                                                                        #
+#  It 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 Lesser General Public License for more details.                   #
+#                                                                        #
+#  See the GNU Lesser General Public License version 2.1                 #
+#  for more details (enclosed in the file licenses/LGPLv2.1).            #
+#                                                                        #
+##########################################################################
+
+# Note:
+#
+# While this script can be run locally, it is meant to run in the Frama-C CI.
+# Thus, it expects to be run from the root of the Frama-C directory and that
+# some CI artifacts are available. Namely:
+#   - the 'wiki' as generated by the script Frama-C/dev/build-release.sh
+# Availability of the files is NOT checked when the script starts.
+# The following variables are used in the cloned wiki:
+#   - GITLAB_USER_NAME (who triggered the job)
+#   - GITLAB_USER_EMAIL
+#   - GITLAB_USER_LOGIN
+# The script:
+#   - clones the wiki as 'pub-wiki'
+#   - copies the content of 'wiki' to 'pub-wiki'
+#   - commits the changes
+#   - pushes
+
+##########################################################################
+
+PUB_WIKI_GIT="git@git.frama-c.com:pub/frama-c.wiki.git"
+PUB_WIKI_DIR="pub-wiki"
+SRC_WIKI_DIR="wiki"
+
+VERSION="$(cat VERSION)"
+CODENAME="$(cat VERSION_CODENAME)"
+LOWER_CODENAME="$(echo "$CODENAME" | tr '[:upper:]' '[:lower:]')"
+
+if ! git clone $PUB_WIKI_GIT pub-wiki
+then
+  echo "Failed to clone wiki directory"
+  exit 128
+fi
+
+GIT="git -C $PUB_WIKI_DIR"
+
+$GIT config user.name  "Frama-CI Bot"
+$GIT config user.email "frama-ci-bot@frama-c.com"
+
+function checkout {
+  $GIT checkout master
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to checkout branch 'master', aborting"
+    exit 128
+  fi
+}
+
+
+function commit {
+  $GIT commit -F- <<EOF
+[release] prepare $VERSION-$LOWER_CODENAME
+
+On behalf of "$GITLAB_USER_NAME" <$GITLAB_USER_EMAIL> (@$GITLAB_USER_LOGIN)
+EOF
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to commit on branch"
+    exit 128
+  fi
+}
+
+function push {
+  $GIT push
+  if [ "$?" -ne "0" ]; then
+    echo "Failed to push branch"
+    exit 128
+  fi
+}
+
+checkout
+
+cp -r $SRC_WIKI_DIR/* $PUB_WIKI_DIR
+$GIT add -A
+
+commit
+push