module: add gerrit
diff --git a/MANIFEST b/MANIFEST
index 2cb4b02..c78241a 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -42,3 +42,17 @@
 host=github.com/phokz
 method=git-clone
 # @@MIRROR_ENDED@@
+
+# @@MIRROR_BEGIN@@
+modname=gerrit
+host=$war_mirror
+file=gerrit-$war_version.war
+method=wget-file
+# @@MIRROR_ENDED@@
+
+# @@MIRROR_BEGIN@@
+modname=gerrit
+host=$jar_sslib_mir1/$jar_sslib_mir2/$jar_sslib_mir3
+file=$jar_sslib.jar
+method=wget-file
+# @@MIRROR_ENDED@@
diff --git a/module/gerrit/holder.sh b/module/gerrit/holder.sh
new file mode 100644
index 0000000..5774c12
--- /dev/null
+++ b/module/gerrit/holder.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+#
+# holder.sh
+#
+# Copyright 2019 Luigi Santivetti <luigi.santivetti@gmail.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# gerrit template
+_GERRIT_DKRC_SERVICE_="${module}"
+_GERRIT_DKRC_IMAGE_="${host_name}-img-${_GERRIT_DKRC_SERVICE_}"
+_GERRIT_DKRC_CONTAINER_="${host_name}-con-${_GERRIT_DKRC_SERVICE_}"
+_GERRIT_DKRC_FRONTEND_IP_="172.28.0.3"
+_GERRIT_DKRC_DOCKERFILE_="${dockerfile}"
+_GERRIT_DKRC_ENTRYPOINT_="${entrypoint}"
+_GERRIT_DKRC_CONTEXT_="${mod_docker_d}"
+_GERRIT_DKRC_ROOTFS_="${mod_rootfs_d}"
+_GERRIT_DKRC_ETC_D_="${etc_d}"
+if [ "$gerrit_has_https" -eq 1 ]; then
+	_GERRIT_HAS_HTTPS_="1"
+	_GERRIT_DKRC_KEYSTORE_F_="${keystore_f}"
+	_GERRIT_DKRC_SSLIB_F_="${jar_sslib_f}"
+	_GERRIT_KEYSTORE_="/var/gerrit/etc/$(basename "$keystore_f")"
+	_GERRIT_SSLIB_="/var/gerrit/lib/${jar_sslib}.jar"
+	_GERRIT_SECURE_STORE_="com.googlesource.gerrit.plugins.secureconfig.SecureConfigStore"
+	_GERRIT_SECURE_CIPHER_="PBEWithSHA1AndRC2_40"
+	_GERRIT_PROXY_PORT_="443"
+	_GERRIT_PROXY_PROTOCOL_="https"
+	_GERRIT_PASSWD_F_="${PASSWD_F}"
+else
+	_GERRIT_HAS_HTTPS_="0"
+	_GERRIT_DKRC_KEYSTORE_F_=""
+	_GERRIT_DKRC_SSLIB_F_=""
+	_GERRIT_KEYSTORE_=""
+	_GERRIT_SSLIB_=""
+	_GERRIT_SECURE_STORE_=""
+	_GERRIT_SECURE_CIPHER_=""
+	_GERRIT_PROXY_PORT_="8079"
+	_GERRIT_PROXY_PROTOCOL_="proxy-http"
+fi
+_GERRIT_SSH_PORT_="29418"
+_GERRIT_CANON_URL_="${_GERRIT_PROXY_PROTOCOL_}://www.${host_name}/gerrit/"
+_GERRIT_APT_GET_PACKAGE_="${war_version}-${revision}"
+_GERRIT_LISTEN_URL_="${_GERRIT_PROXY_PROTOCOL_}://${_GERRIT_DKRC_FRONTEND_IP_}:${_GERRIT_PROXY_PORT_}/gerrit/"
+_GERRIT_JAVA_URANDOM_="-Djava.security.egd=file:/dev/./urandom"
+_GERRIT_JAVA_HOME_D_="/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre"
+if [ "${mod_mode}" = "${debug}" ]; then
+	_GERRIT_APT_GET_INSTALL_NMAP_="RUN apt-get -y install nmap"
+	_GERRIT_APT_GET_INSTALL_PING_="RUN apt-get -y install net-tools"
+else
+	_GERRIT_APT_GET_INSTALL_NMAP_="# Skip install nmap for ${mod_mode}"
+	_GERRIT_APT_GET_INSTALL_PING_="# Skip install ping for ${mod_mode}"
+fi
diff --git a/module/gerrit/module.sh b/module/gerrit/module.sh
new file mode 100644
index 0000000..1361d03
--- /dev/null
+++ b/module/gerrit/module.sh
@@ -0,0 +1,261 @@
+#!/bin/bash
+#
+# module.sh - gerrit
+#
+# Copyright 2019 Luigi Santivetti <luigi.santivetti@gmail.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+declare -r module="gerrit"
+source $common_sh
+
+module_enable $module
+
+declare -r etc_d="$mod_rootfs_d/etc"
+
+declare -r keystore="$host_name.keystore"
+declare -r keystore_pkcs12="$host_name.pkcs12.keystore"
+declare -r keystore_f="$etc_d/$keystore_pkcs12"
+declare -r config_f="$etc_d/gerrit.config"
+declare -r secure_f="$etc_d/secure.config"
+declare -r entrypoint="entrypoint.sh"
+declare -r entrypoint_f="$mod_docker_d/$entrypoint"
+declare -r dockerfile="Dockerfile"
+declare -r dockerfile_f="$mod_docker_d/$dockerfile"
+declare -r gerrit_cli_f="$mod_docker_d/gerrit-cli.sh"
+
+declare -r war_version="3.0.0"
+declare -r war_mirror="https://gerrit-releases.storage.googleapis.com"
+declare -r revision="1"
+
+# Gerrit is always configured with a loopback on localhost. If the web server
+# is configured to only utilise SSL encryption for all the public incoming
+# and outgoing traffic, unencrypted requests and responses over simple http
+# made to and from Gerrit (i.e. headers) are never exposed to the outside
+# world. 'gerrit_has_https' allows to also encrypt looped back traffic that,
+# as it stands, should be considered redundant.
+declare -r gerrit_has_https=1
+if [ "$gerrit_has_https" -eq 1 ]; then
+	declare -r jar_sslib_mir1="https://gerrit-ci.gerritforge.com/job"
+	declare -r jar_sslib_mir2="plugin-secure-config-bazel-stable-3.0/lastSuccessfulBuild"
+	declare -r jar_sslib_mir3="artifact/bazel-bin/plugins/secure-config"
+	declare -r jar_sslib="secure-config"
+	declare -r jar_sslib_f="${mod_rootfs_d}/lib/${jar_sslib}.jar"
+fi
+
+# Staged resources
+declare -r war_f="$mod_staging_d/gerrit-$war_version.war"
+
+declare -ar mod_more_dirs=( $etc_d )
+declare -ar mod_more_files=( $config_f $secure_f $entrypoint_f $dockerfile_f $gerrit_cli_f )
+declare -ar mod_more_trefs=( config_t secure_t entrypoint_bang_t dockerfile_t gerrit_cli_bang_t )
+
+function tod_watch
+{
+	__watch_module_common || return $s_err
+}
+
+function tod_doins
+{
+	__fetch_module_common || return $s_err
+	__gerrit_do_sitepath  || return $s_err
+
+	# Update $config_f after init is done
+	__doins_module_common || return $s_err
+	__gerrit_do_lock      || return $s_err
+}
+
+function tod_fetch
+{
+	__fetch_module_common || return $s_err
+}
+
+function tod_upins
+{
+	__gerrit_do_unlock    || return $s_err
+	__upins_module_common || return $s_err
+	__gerrit_do_lock      || return $s_err
+}
+
+function tod_clmod
+{
+	__clmod_module_common || return $s_err
+}
+
+function tod_clins
+{
+	__clins_module_common || return $s_err
+}
+
+function tod_doall
+{
+	tod_doins || return $s_err
+	tod_upmod || return $s_err
+}
+
+function tod_upall
+{
+	tod_upins || return $s_err
+	tod_upmod || return $s_err
+}
+
+function tod_upmod
+{
+	__fetch_module_common || return $s_err
+	__gerrit_do_keystore  || return $s_err
+}
+
+function __gerrit_do_lock
+{
+	set_mode "0644" "root" $config_f
+	set_mode "0644" "root" $secure_f
+}
+
+function __gerrit_do_unlock
+{
+	set_mode "0666" "1000" $config_f
+	set_mode "0666" "1000" $secure_f
+}
+
+function __gerrit_do_sitepath
+{
+	[ -f "$war_f" ] || {
+		lets -l -e "missing $(basename $war_f), is it in the MANIFEST?"
+		return $s_err
+	}
+
+	# Make a bare skeleton of the essential rootfs tree
+	java -jar "$war_f" init --batch --skip-plugins --delete-caches --no-auto-start \
+		 --site-path "$mod_rootfs_d" 2>&1 | lets -l -x "java"
+	[ ${PIPESTATUS[0]} -eq 0 ] || {
+		lets -l -e "failed to init gerrit SITE_PATH"
+		return $s_err
+	}
+
+	sleep 1
+
+	# Remove runtime files and directories
+	rm -f $mod_rootfs_d/etc/{ssh,secure}*
+	rm -Rf $mod_rootfs_d/{static,index,logs,data,index,cache,git,db,tmp}/*
+
+	# Make sure gerrit.config is present before any real init is ever attempted
+	[ -f "$config_f" ]
+}
+
+function __gerrit_do_sslib
+{
+	# In order to use encrypted password, sslib needs to be available in /lib
+	cp $mod_staging_d/$jar_sslib.jar $jar_sslib_f
+	chmod 0664 $jar_sslib_f
+
+	local gerrit_war
+	if [ -f "$mod_rootfs_d/bin/gerrit.war" ]; then
+		gerrit_war="$mod_rootfs_d/bin/gerrit.war"
+	elif [ -f "$mod_staging_d/gerrit-$war_version.war" ]; then
+		gerrit_war="$mod_staging_d/gerrit-$war_version.war"
+	else
+		lets -l -e "gerrit.war not found"
+		return $s_err
+	fi
+
+	if [ -z "$PASSWD_F" ] || [ ! -f "$PASSWD_F" ]; then
+			lets -l -e "keystore needs PASSWD_F"
+			return $s_err
+	fi
+
+	source $PASSWD_F
+
+	[ -n "${_PASSWD_GERRIT_KEYSTORE_KEY_PASS_}" ] || {
+		lets -l -e "_PASSWD_GERRIT_KEYSTORE_KEY_PASS_ not defined"
+		return $s_err
+	}
+
+	__gerrit_do_unlock || return $s_err
+	java -jar $gerrit_war passwd -d "$mod_rootfs_d" \
+		 httpd.sslKeyPassword "${_PASSWD_GERRIT_KEYSTORE_KEY_PASS_}" | lets -l -x "sslib"
+	java -jar $gerrit_war passwd -d "$mod_rootfs_d" \
+		 auth.registerEmailPrivateKey "${_PASSWD_GERRIT_EMAIL_PRIV_KEY_}" | lets -l -x "sslib"
+
+	[ ${PIPESTATUS[0]} -eq 0 ] || return $s_err
+	__gerrit_do_lock
+}
+
+function __gerrit_do_keystore
+{
+	if [ "$gerrit_has_https" -ne 1 ]; then
+		lets -l -i "secure config not enabled"
+		return $s_ok
+	fi
+
+	if [ ! -f "$mod_staging_d/$keystore" ] || \
+		   [ ! -f "$mod_staging_d/$keystore_pkcs12" ]; then
+		[ -n "$PASSWD_F" ] && [ -f "$PASSWD_F" ] || {
+			lets -l -e "keystore needs PASSWD_F"
+			return $s_err
+		}
+
+		rm -f $mod_staging_d/$keystore
+		rm -f $mod_staging_d/$keystore_pkcs12
+
+		source $PASSWD_F
+
+		# This isn't run as part of templates creation, make sure variables are
+		# defined
+		[ -n "${_PASSWD_GERRIT_KEYSTORE_STORE_PASS_}" ] && \
+			[ -n "${_PASSWD_GERRIT_KEYSTORE_KEY_PASS_}" ] || {
+				lets -l -e "keystore needs passwords"
+				return $s_err
+			}
+
+		keytool -keystore $mod_staging_d/$keystore -v \
+				-alias $module -genkey -keyalg RSA \
+				-dname "cn=$host_name, ou=$host_name, o=$host_name, c=$host_name" \
+				-storepass ${_PASSWD_GERRIT_KEYSTORE_STORE_PASS_} \
+				-keypass ${_PASSWD_GERRIT_KEYSTORE_KEY_PASS_} 2>&1 | \
+			lets -l -x "keytool"
+		[ ${PIPESTATUS[0]} -eq 0 ] || {
+			lets -l -e "failed to create keystore: $mod_staging_d/$keystore"
+			return $s_err
+		}
+
+		lets -l -i "converting to keystore to PKCS12 format ..."
+
+		# Warning:  Different store and key passwords not supported for PKCS12 KeyStores.
+		# So just use the same for both
+		keytool -importkeystore -deststoretype pkcs12 -noprompt -v \
+				-deststorepass ${_PASSWD_GERRIT_KEYSTORE_KEY_PASS_} \
+				-destkeypass ${_PASSWD_GERRIT_KEYSTORE_KEY_PASS_} \
+				-destkeystore $mod_staging_d/$keystore_pkcs12 \
+				-srcstorepass ${_PASSWD_GERRIT_KEYSTORE_STORE_PASS_} \
+				-srckeypass ${_PASSWD_GERRIT_KEYSTORE_KEY_PASS_} \
+				-srckeystore $mod_staging_d/$keystore -alias $module 2>&1 | \
+			lets -l -x "keytool"
+		[ "${PIPESTATUS[0]}" -eq 0 ] || {
+			lets -l -e "failed to convert PKCS12 keystore: $mod_staging_d/$keystore"
+			return $s_err
+		}
+	fi
+
+	# Last bit, if we got this far, then we want to store the key password
+	# and gerrit requires it at start up. Do this using gerrit sslib
+	__gerrit_do_sslib || return $s_err
+
+	sudo cp $mod_staging_d/$keystore_pkcs12 $keystore_f || return $s_err
+	sudo chmod 0600 $keystore_f && sudo chown root:root $keystore_f
+}
diff --git a/module/gerrit/scheme.sh b/module/gerrit/scheme.sh
new file mode 100644
index 0000000..01164cf
--- /dev/null
+++ b/module/gerrit/scheme.sh
@@ -0,0 +1,519 @@
+#!/bin/bash
+#
+# scheme.sh - gerrit
+#
+# Copyright 2019 Luigi Santivetti <luigi.santivetti@gmail.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+declare -r entrypoint_bang_t="\
+#!/bin/bash -e
+
+if [ \"\${1}\" = \"init\" ]; then
+	echo \"Initializing Gerrit site ...\"
+
+	# NOTE: This is the real in-container init that actually matters
+	# for properly start using Gerrit Code Review.
+
+	java -jar /var/gerrit/bin/gerrit.war init --batch \\
+		--no-auto-start --install-all-plugins -d /var/gerrit
+	java -jar /var/gerrit/bin/gerrit.war reindex -d /var/gerrit
+	git config -f /var/gerrit/etc/gerrit.config \\
+		--add container.javaOptions \"${_GERRIT_JAVA_URANDOM_}\"
+fi
+
+git config -f /var/gerrit/etc/gerrit.config \\
+	gerrit.canonicalWebUrl \"\${CANONICAL_WEB_URL:-http://\$HOSTNAME}\"
+git config -f /var/gerrit/etc/gerrit.config \\
+	httpd.listenUrl \"\${LISTEN_URL:-proxy-http://\$HOSTNAME}\"
+
+sleep 1
+
+if [ \"\${1}\" != \"init\" ]; then
+	echo \"Running Gerrit ...\"
+	/var/gerrit/bin/gerrit.sh run
+fi"
+
+if [ "$gerrit_has_https" -eq 1 ]; then
+	declare -r config_t_has_secure_store="\
+secureStoreClass = ${_GERRIT_SECURE_STORE_}"
+	declare -r config_t_has_keystore="\
+	sslKeyStore = ${_GERRIT_KEYSTORE_}"
+	declare -r config_t_has_secure_config="\
+[secureConfig]
+	cipher = ${_GERRIT_SECURE_CIPHER_}
+	passwordLength = 256"
+else
+	declare -r config_t_has_secure_store=""
+	declare -r config_t_has_keystore=""
+	declare -r config_t_has_secure_config=""
+fi
+
+declare -r config_t="\
+[gerrit]
+	basePath = git
+	canonicalWebUrl = ${_GERRIT_CANON_URL_}
+${config_t_has_secure_store}
+
+[index]
+	type = LUCENE
+
+[auth]
+	type = http
+	gitBasicAuth = true
+	gitBasicAuthPolicy = HTTP
+
+[sendemail]
+	smtpServer = localhost
+
+[sshd]
+	listenAddress = *:${_GERRIT_SSH_PORT_}
+
+[httpd]
+	listenUrl = ${_GERRIT_LISTEN_URL_}
+${config_t_has_keystore}
+
+[cache]
+	directory = cache
+
+[container]
+	user = root
+
+[receive]
+	enableSignedPush = false
+
+${config_t_has_secure_config}"
+
+# secure.config isn't part of the instance create/update process, however
+# it must be defined in mod_more_files in the first place.
+declare -r secure_t="$(cat $secure_f 2>/dev/null | grep -E -- "^[^#]" 2>/dev/null)"
+
+declare -r dockerfile_t="\
+FROM ubuntu:16.04
+MAINTAINER Gerrit Code Review Community
+
+# Add Gerrit packages repository
+RUN echo \"deb mirror://mirrorlist.gerritforge.com/bionic gerrit contrib\" \\
+	> /etc/apt/sources.list.d/GerritForge.list
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 847005AE619067D5
+
+RUN apt-get update
+RUN apt-key update
+RUN apt-get -y install sudo
+
+# Needed by Gerrit 3.0 ?!
+RUN apt-get -y install gnupg2
+
+${_GERRIT_APT_GET_INSTALL_NMAP_}
+${_GERRIT_APT_GET_INSTALL_PING_}
+
+# Install OpenJDK and Gerrit in two subsequent transactions
+# (pre-trans Gerrit script needs to have access to the Java command)
+RUN apt-get -y install openjdk-8-jdk
+
+# Add entrypoint
+ADD entrypoint.sh /
+RUN	chmod +x /entrypoint.sh
+
+# Init Gerrit
+RUN apt-get -y install gerrit=${_GERRIT_APT_GET_PACKAGE_} && \\
+	/entrypoint.sh init && \\
+	rm -f /var/gerrit/etc/{ssh,secure}* && \\
+	rm -Rf /var/gerrit/{static,index,logs,data,index,cache,git,db,tmp}/* && \\
+	chown -R gerrit:gerrit /var/gerrit
+
+# Debian 9 fails with 'USER gerrit'
+USER root
+
+# Define environment
+ENV CANONICAL_WEB_URL=
+ENV LISTEN_URL=
+
+# Allow incoming traffic
+EXPOSE ${_GERRIT_PROXY_PORT_} ${_GERRIT_SSH_PORT_}
+
+VOLUME [\"/var/gerrit/git\", \"/var/gerrit/index\", \\
+	\"/var/gerrit/cache\", \"/var/gerrit/db\", \"/var/gerrit/etc\"]
+
+# Start Gerrit
+ENTRYPOINT /entrypoint.sh"
+
+declare -r gerrit_cli_bang_t="\
+#!/bin/bash
+
+if (return 0 2>/dev/null); then
+	echo \"You must run this script\" >&2
+	return 1
+fi
+
+function __help
+{
+	if [ -n \"\$1\" ]; then
+		echo \"error: unknown option: \$1\" >&2
+		echo \"------------------------\">&2
+	fi
+
+	cat <<EOF; exit 0
+`printf \"\\033[1m%s\\033[0m\\n\" \"NAME\"`
+
+  \${BASH_SOURCE[0]} - Gerrit bash CLI for ${host_name}
+
+`printf \"\\033[1m%s\\033[0m\\n\" \"USAGE\"`
+
+  \$ [ ENVIRONMENT ... ] \${BASH_SOURCE[0]} [ OPTION ARG ] ARGS
+
+`printf \"\\033[1m%s\\033[0m\\n\" \"ENVIRONMENT\"`
+
+  SSH_PORT            ssh port Gerrit listens on, default: ${_GERRIT_SSH_PORT_}
+  SSH_USER            registered Gerrit user, default: NULL
+  SSH_HOST            host where Gerrit runs, default: ${host_name}
+
+  User, port and host passed on the command line take precedence over
+  those defined in the environment
+
+`printf \"\\033[1m%s\\033[0m\\n\" \"OPTION\"`
+
+  -p                  ssh port
+  -h                  ssh host
+  -u                  ssh user
+  -a                  action to perform
+
+  Available actions:
+
+  import              <src path> <dst path>
+                      import in Gerrit all bare repositories found in src, if it
+                      doesn't exist yet and if it's a valid src
+  dry-import          <src path> <dst path>
+                      create an empty project in Gerrit for each bare repository
+                      found in src, if doesn't exist yet and if it's a valid src
+  list                <flags>
+                      list Gerrit projects with additional flags
+  listall             -
+                      list all Gerrit projects
+  flushall            -
+                      flush all Gerrit project caches
+  delete              <project name>
+                      delete named project and related data from Gerrit
+  create              <flags> <project name>
+                      create named project with additional flags
+  create-permission   <project name>
+                      create permission project, for access control only
+  cli                 <$@>
+                      any valid list of arguments
+
+`printf \"\\033[1m%s\\033[0m\\n\" \"EXAMPLES\"`
+
+  \$ \${BASH_SOURCE[0]} -u linus -a import \"old_site_path\" \"new_site_path\"
+  \$ SSH_PORT=1234 SSH_USER=linus \${BASH_SOURCE[0]} -a listall
+
+  \$ export SSH_USER=linus
+  \$ \${BASH_SOURCE[0]} -a create \"wheel\"
+
+`printf \"\\033[1m%s\\033[0m\\n\" \"END\"`
+EOF
+}
+
+function set_environment
+{
+	local SSH_PORT=\${1:-\$SSH_PORT}
+	declare -gr SSH_PORT=\${SSH_PORT:-$_GERRIT_SSH_PORT_}
+
+	local SSH_USER=\${2:-\$SSH_USER}
+	declare -gr SSH_USER=\${SSH_USER:-}
+
+	local SSH_HOST=\${3:-\$SSH_HOST}
+	declare -gr SSH_HOST=\${SSH_HOST:-$host_name}
+}
+
+function gerrit_ssh
+{
+	ssh -p \$SSH_PORT \$SSH_USER@\$SSH_HOST \${@}
+}
+
+function gerrit_cli
+{
+	gerrit_ssh \"gerrit \${@}\"
+}
+
+function gerrit_create_project
+{
+	gerrit_cli create-project \${@}
+}
+
+function gerrit_list_projects
+{
+	gerrit_cli ls-projects \${@}
+}
+
+function gerrit_delete_project
+{
+	local -r flags=\"--yes-really-delete\"
+
+	gerrit_ssh \"delete-project delete \$flags \${@}\"
+}
+
+function gerrit_flush_all_caches
+{
+	local -r flags=\"flush-caches --all\"
+
+	gerrit_cli \$flags
+}
+
+function glp_all_projects
+{
+	local -r flags=\"--all --type all\"
+
+	gerrit_list_projects \$flags
+}
+
+function gcp_permission_only
+{
+	local -r flags=\"--permissions-only --empty-commit\"
+	local -r project_name=\"\$1\"
+
+	gerrit_create_project \$flags \$project_name
+}
+
+function gcp_import_is_valid_src_repo
+{
+	local -r git_path=\"\$1\"
+
+	if ! (echo \"\${git_path}\" | grep -qE -- \".+\\.git\$\"); then
+		echo \"error: \$(basename \"\$git_path\"): invalid name\" >&2
+		return 1
+	fi
+
+	if ! (GIT_DIR=\$git_path git rev-parse --is-bare-repository &>/dev/null); then
+		echo \"error: \$(basename \"\$git_path\"): not bare\" >&2
+		return 1
+	fi
+}
+
+function gcp_import_project
+{
+	local -r repo_name=\"\${1%.git}\"
+	local -r repo=\"\$1\"
+	local -r src=\"\$2\"
+	local -r dst=\"\$3\"
+	local -r dry=\"\$4\"
+
+	# List of projects to explicitly skip
+	local -ar skiplist=(
+		All-Projects
+		All-Users
+	)
+
+	if printf \"%s\n\" \"\${skiplist[@]}\" | grep -qFx -- \"\$repo_name\"; then
+		echo \"warning: \$repo_name: flagged out\" >&2
+		return 1
+	fi
+
+	if ! gerrit_create_project \$repo_name; then
+		echo \"error: \$repo_name: create failed\" >&2
+		return 1
+	fi
+
+	if [ ! -d \"\$src/\$repo\" ]; then
+		echo \"error: \$repo: repo source not found\" >&2
+		return 1
+	fi
+
+	if [ ! -d \"\$dst/\$repo\" ]; then
+		echo \"error: \$repo: destination not found\" >&2
+		return 1
+	fi
+
+	if [ \"\$dry\" != \"true\" ]; then
+		if ! sudo -E cp -ar \$src/\$repo/* \$dst/\$repo; then
+			echo \"error: \$repo: copy failed\" >&2
+			return 1
+		fi
+
+		if ! gerrit_flush_all_caches; then
+			echo \"warning: \$project_name: flush cache\" >&2
+		fi
+	fi
+}
+
+function gcp_import_repo_list
+{
+	# Do this once and avoid multiple ssh access
+	local -ar exist_proj_list=( \$(glp_all_projects) )
+	local -a valid_repo_list
+	# Arguments order matters
+	local -r dry=\"\$1\"; shift
+	local -r src=\"\$1\"; shift
+	local -r dst=\"\$1\"; shift
+	local proj repo ret act
+
+	for repo in \${@}; do
+		if printf \"%s\n\" \"\${exist_proj_list[@]}\" | \\
+				grep -qFx -- \"\${repo%.git}\"; then
+			echo \"warning: \${repo%.git}: already exists\" >&2
+			continue
+		fi
+		gcp_import_is_valid_src_repo \"\$src/\$repo\" || continue
+		valid_repo_list+=( \"\$repo\" )
+	done
+
+	if [ \"\${#valid_repo_list[@]}\" -gt 0 ]; then
+		[ \"\$dry\" = \"true\" ] && act=\"dry-copy\" || act=\"copy\"
+		echo \"info: attempt to \$act\" >&2
+		echo >&2
+		echo \"     *  from \$src\" >&2
+		echo \"     *    to \$dst\" >&2
+		echo >&2
+		cat -n <(printf \"%s\\\n\" \"\${valid_repo_list[@]}\") >&2
+		echo >&2
+		echo \"info: press any key to continue, [CTRL^C] to cancel\"
+		echo >&2
+		read -s -n 1
+
+		for repo in \${valid_repo_list[@]}; do
+			gcp_import_project \$repo \$src \$dst \$dry && ret=\"done\" || ret=\"skip\"
+			printf \"%s: %s\n\" \"\$ret\" \"\$repo\" >&2
+		done
+	else
+		echo \"warning: nothing to do\" >&2
+	fi
+}
+
+# @1      : path to find bare git repositories from
+# @2      : path copy repositories into
+# @desc   : import any bare repository found in \$1 into \$2
+function gcp_import_copy_from_into
+{
+	local -r regex=\".+/.+[^/]\\.git\$\"
+	local -r src=\"\$(realpath -q \"\$1\")\"
+	local -r dst=\"\$(realpath -q \"\$2\")\"
+	local -r dry=\"\$3\"
+	local flags
+
+	# Flag order matters
+	flags=\"-type d\"
+	flags+=\" -regex \$regex\"
+
+	local -ar list=\$(find \"\$src\" \$flags 2>/dev/null)
+
+	gcp_import_repo_list \$dry \$src \$dst \${list[@]//\$src\\//}
+}
+
+function __do_import
+{
+	if [ -z \"\$1\" ] || [ ! -d \"\$1\" ]; then
+		echo \"error: invalid source path\" >&2
+		exit 1
+	fi
+
+	if [ -z \"\$2\" ] || [ ! -d \"\$2\" ]; then
+		echo \"error: invalid target path\" >&2
+		exit 1
+	fi
+
+	gcp_import_copy_from_into \$1 \$2 \"false\"
+}
+
+function __do_dry_import
+{
+	if [ -z \"\$1\" ] || [ ! -d \"\$1\" ]; then
+		echo \"error: invalid source path\" >&2
+		exit 1
+	fi
+
+	if [ -z \"\$2\" ] || [ ! -d \"\$2\" ]; then
+		echo \"error: invalid target path\" >&2
+		exit 1
+	fi
+
+	gcp_import_copy_from_into \$1 \$2 \"true\"
+}
+
+function __do_cli
+{
+	gerrit_cli \${@}
+}
+
+function __do_flushall
+{
+	gerrit_flush_all_caches
+}
+
+function __do_list
+{
+	gerrit_list_projects \$@
+}
+
+function __do_listall
+{
+	glp_all_projects
+}
+
+function __do_delete
+{
+	gerrit_delete_project \$@
+}
+
+function __do_create
+{
+	gerrit_create_project \$@
+}
+
+function __do_cpermi
+{
+	gcp_permission_only \$@
+}
+
+function __do_unknown
+{
+	if [ \"\$1\" != help ]; then
+		echo \"error: unknown action: \$1\" >&2
+		echo >&2
+	fi
+	__help
+}
+
+while getopts \":hp:s:u:a:\" OPTION; do
+	case \"\$OPTION\" in
+		 p ) declare -rg port=\"\$OPTARG\" ;;
+		 s ) declare -rg host=\"\$OPTARG\" ;;
+		 u ) declare -rg user=\"\$OPTARG\" ;;
+		 a ) declare -rg __do=\"\$OPTARG\" ;;
+		\\? ) __help \$OPTARG ;;
+	esac
+done
+
+if ! set_environment \"\$port\" \"\$user\" \"\$host\"; then
+	echo \"error: user must be set, try ? or -a help\"
+	exit 1
+fi
+
+shift \$((OPTIND - 1))
+
+case \"\$__do\" in
+	import            ) __do_import \$@     ;;
+	dry-import        ) __do_dry_import \$@ ;;
+	list              ) __do_list \$@       ;;
+	listall           ) __do_listall       ;;
+	flushall          ) __do_flushall      ;;
+	delete            ) __do_delete \$@     ;;
+	create            ) __do_create \$@     ;;
+	create-permission ) __do_cpermi \$@     ;;
+	cli               ) __do_cli \$@        ;;
+	*                 ) __do_unknown \$__do ;;
+esac"