ci: Add ci_lint_ci.sh and .shellcheckrc

Add ci_lint_ci.sh for linting the CI config files and scripts.
The linting is based on yamllint and shellcheck.
diff --git a/ci/.shellcheckrc b/ci/.shellcheckrc
new file mode 100644
index 0000000..3ee2a1e
--- /dev/null
+++ b/ci/.shellcheckrc
@@ -0,0 +1,7 @@
+# Disable all the "quote to prevent globbing or word splitting" advice.
+# We need word splitting for well-known variables like MAKEFLAGS and CFLAGS.
+disable=SC2086,SC2206
+
+# Disable the "possible misspelling" warnings that might be flagged, e.g.,
+# inside function ci_trace_build.
+disable=SC2153
diff --git a/ci/ci_lint_ci.sh b/ci/ci_lint_ci.sh
new file mode 100755
index 0000000..ce3793d
--- /dev/null
+++ b/ci/ci_lint_ci.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+set -e
+
+# Copyright (c) 2019-2023 Cosmin Truta.
+#
+# Use, modification and distribution are subject
+# to the Boost Software License, Version 1.0.
+# See the accompanying file LICENSE_BSL_1_0.txt
+# or visit http://www.boost.org/LICENSE_1_0.txt
+#
+# SPDX-License-Identifier: BSL-1.0
+
+# shellcheck source="ci/lib/ci.lib.sh"
+source "$(dirname "$0")/lib/ci.lib.sh"
+cd "$CI_TOPLEVEL_DIR"
+
+CI_SHELLCHECK="$(command -v shellcheck || true)"
+CI_YAMLLINT="$(command -v yamllint || true)"
+CI_LINT_COUNTER=0
+
+function ci_lint_ci_config_files {
+    ci_info "linting: CI config files"
+    local MY_FILE
+    if [[ -x $CI_YAMLLINT ]]
+    then
+        ci_spawn "$CI_YAMLLINT" --version
+        for MY_FILE in "$CI_TOPLEVEL_DIR"/.*.yml
+        do
+            ci_spawn "$CI_YAMLLINT" --strict "$MY_FILE" ||
+                CI_LINT_COUNTER=$((CI_LINT_COUNTER + 1))
+        done
+    else
+        ci_warn "program not found: 'yamllint'; skipping checks"
+    fi
+}
+
+function ci_lint_ci_scripts {
+    ci_info "linting: CI scripts"
+    local MY_FILE
+    if [[ -x $CI_SHELLCHECK ]]
+    then
+        ci_spawn "$CI_SHELLCHECK" --version
+        for MY_FILE in "$CI_SCRIPT_DIR"/*.sh
+        do
+            ci_spawn "$CI_SHELLCHECK" -x "$MY_FILE" ||
+                CI_LINT_COUNTER=$((CI_LINT_COUNTER + 1))
+        done
+    else
+        ci_warn "program not found: 'shellcheck'; skipping checks"
+    fi
+}
+
+function ci_lint_ci_scripts_license {
+    ci_info "linting: CI scripts license"
+    ci_spawn grep -F "Boost Software License" ci/LICENSE_BSL_1_0.txt || {
+        ci_warn "bad or missing CI license file: '$CI_SCRIPT_DIR/LICENSE_BSL_1_0.txt'"
+        CI_LINT_COUNTER=$((CI_LINT_COUNTER + 1))
+    }
+}
+
+function main {
+    [[ $# -eq 0 ]] || {
+        ci_info "note: this program accepts environment options only"
+        ci_err "unsupported command argument: '$1'"
+    }
+    ci_lint_ci_config_files
+    ci_lint_ci_scripts
+    ci_lint_ci_scripts_license
+    if [[ $CI_LINT_COUNTER -eq 0 ]]
+    then
+        ci_info "success!"
+        exit 0
+    else
+        ci_info "failed on $CI_LINT_COUNTER file(s)"
+        exit 1
+    fi
+}
+
+main "$@"
diff --git a/ci/ci_verify_cmake.sh b/ci/ci_verify_cmake.sh
index 9fdd031..e8a23b7 100755
--- a/ci/ci_verify_cmake.sh
+++ b/ci/ci_verify_cmake.sh
@@ -10,6 +10,7 @@
 #
 # SPDX-License-Identifier: BSL-1.0
 
+# shellcheck source="ci/lib/ci.lib.sh"
 source "$(dirname "$0")/lib/ci.lib.sh"
 cd "$CI_TOPLEVEL_DIR"
 
diff --git a/ci/ci_verify_configure.sh b/ci/ci_verify_configure.sh
index 96ada92..2f2631b 100755
--- a/ci/ci_verify_configure.sh
+++ b/ci/ci_verify_configure.sh
@@ -10,6 +10,7 @@
 #
 # SPDX-License-Identifier: BSL-1.0
 
+# shellcheck source="ci/lib/ci.lib.sh"
 source "$(dirname "$0")/lib/ci.lib.sh"
 cd "$CI_TOPLEVEL_DIR"
 
diff --git a/ci/ci_verify_makefiles.sh b/ci/ci_verify_makefiles.sh
index fc05d27..a4398cd 100755
--- a/ci/ci_verify_makefiles.sh
+++ b/ci/ci_verify_makefiles.sh
@@ -10,6 +10,7 @@
 #
 # SPDX-License-Identifier: BSL-1.0
 
+# shellcheck source="ci/lib/ci.lib.sh"
 source "$(dirname "$0")/lib/ci.lib.sh"
 cd "$CI_TOPLEVEL_DIR"