Features 11017 and 11018: setup of mgmt cluster and git repo
This change incorporates the changes to setup a mgmt cluster for
cloud-native operations in OSM following a GitOps model, which includes
the setup of an internal git repository.
Change-Id: If828d18ad64d852a9a89ec9ba7c2d3a96d281565
Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>
diff --git a/installers/mgmt-cluster/flux/scripts/add-age-key-to-cluster.sh b/installers/mgmt-cluster/flux/scripts/add-age-key-to-cluster.sh
new file mode 100755
index 0000000..9d5e52b
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/add-age-key-to-cluster.sh
@@ -0,0 +1,51 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+set -e -o pipefail
+
+export HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
+source "${HERE}/library/functions.sh"
+source "${HERE}/library/trap.sh"
+
+
+AGE_KEY_NAME="$1"
+CLUSTER_DIR="$2"
+
+# Load the contents of both keys
+export PRIVATE_KEY=$(<"${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key")
+export PUBLIC_KEY=$(<"${CREDENTIALS_DIR}/${AGE_KEY_NAME}.pub")
+
+# Add the `age` private key to the cluster as secret:
+kubectl delete secret sops-age --namespace=flux-system 2> /dev/null || true
+# cat "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key" |
+echo "${PRIVATE_KEY}" |
+ kubectl create secret generic sops-age \
+ --namespace=flux-system \
+ --from-file=age.agekey=/dev/stdin
+
+# Create SOPS configuration at the root folder of the management cluster:
+cat <<EOF > "${CLUSTER_DIR}/.sops.yaml"
+creation_rules:
+ - encrypted_regex: ^(data|stringData)$
+ age: ${PUBLIC_KEY}
+ # - path_regex: .*.yaml
+ # encrypted_regex: ^(data|stringData)$
+ # age: ${PUBLIC_KEY}
+EOF
+
+# Add also the public key to the repository so that others who clone the repo can encrypt new files:
+cp "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.pub" "${CLUSTER_DIR}/.sops.pub.asc"
diff --git a/installers/mgmt-cluster/flux/scripts/clone-relevant-repos.sh b/installers/mgmt-cluster/flux/scripts/clone-relevant-repos.sh
new file mode 100755
index 0000000..93ffad9
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/clone-relevant-repos.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+set -e -o pipefail
+
+export HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
+source "${HERE}/library/functions.sh"
+source "${HERE}/library/trap.sh"
+
+
+# Creates base dir in case it did not exist
+mkdir -p "${WORK_REPOS_DIR}"
+
+# Clones `fleet-osm` repo
+[[ "${REMOVE_LOCAL_DIR_BEFORE_CLONING}" == "true" ]] && rm -rf "${FLEET_REPO_DIR}"
+git clone ${GITEA_SSH_URL}/${GITEA_STD_USERNAME}/fleet-osm.git "${FLEET_REPO_DIR}"
+
+# Clones `sw-catalogs-osm` repo
+[[ "${REMOVE_LOCAL_DIR_BEFORE_CLONING}" == "true" ]] && rm -rf "${SW_CATALOGS_REPO_DIR}"
+git clone ${GITEA_SSH_URL}/${GITEA_STD_USERNAME}/sw-catalogs-osm.git "${SW_CATALOGS_REPO_DIR}"
+
+# Forces `main` instead of `master` as default branch
+pushd "${FLEET_REPO_DIR}" > /dev/null
+git symbolic-ref HEAD refs/heads/main
+popd > /dev/null
+pushd "${SW_CATALOGS_REPO_DIR}" > /dev/null
+git symbolic-ref HEAD refs/heads/main
+popd > /dev/null
diff --git a/installers/mgmt-cluster/flux/scripts/create-age-keypair.sh b/installers/mgmt-cluster/flux/scripts/create-age-keypair.sh
new file mode 100755
index 0000000..d3dd9b1
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/create-age-keypair.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+set -e -o pipefail
+
+export HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
+source "${HERE}/library/functions.sh"
+source "${HERE}/library/trap.sh"
+
+
+AGE_KEY_NAME="$1"
+
+# Delete the keys in case they existed already
+rm -f "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key" "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.pub"
+
+# Private key
+age-keygen -o "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key"
+
+# Public key (extracted from comment at private key)
+age-keygen -y "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key" > "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.pub"
diff --git a/installers/mgmt-cluster/flux/scripts/create-new-cluster-folder-structure.sh b/installers/mgmt-cluster/flux/scripts/create-new-cluster-folder-structure.sh
new file mode 100755
index 0000000..9263d74
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/create-new-cluster-folder-structure.sh
@@ -0,0 +1,201 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+set -e -o pipefail
+
+export HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
+source "${HERE}/library/functions.sh"
+source "${HERE}/library/trap.sh"
+
+
+
+# Input values
+export CLUSTER_DIR="$1"
+export PROJECT_DIR="$2"
+export PROFILE_NAME="$3"
+export TEMPLATES_DIR="$4"
+export PUBLIC_KEY="$5"
+
+
+# Helper functions to create the profile ConfigMaps
+function safe_name() {
+ echo "$1" | \
+ sed '/\.\// s|./||' | \
+ sed 's|\.|-|g' | \
+ sed 's|/|-|g' | \
+ sed 's|_|-|g' | \
+ sed 's| |-|g'
+}
+
+function create_profile_configmap() {
+ local CONFIGMAP_NAME=$(safe_name "$1")
+ local PROFILE_REPO_URL="$2"
+ local PROFILE_PATH="$3"
+ kubectl create configmap ${CONFIGMAP_NAME} \
+ --namespace flux-system \
+ --from-literal=repo="${PROFILE_REPO_URL}" \
+ --from-literal=path="${PROFILE_PATH}" \
+ -o yaml \
+ --dry-run=client
+}
+
+# Helper functions to clone secret from one namespace to other
+function clone_secret_to_new_ns_stdout() {
+ local SECRET_NAME="$1"
+ local SOURCE_NS="$2"
+ local DESTINATION_NS="$3"
+
+ kubectl get secret "${SECRET_NAME}" -n "${SOURCE_NS}" -o yaml | \
+ yq 'del(.metadata.uid) | del(.metadata.resourceVersion) | del(.metadata.creationTimestamp)' | \
+ yq ".metadata.namespace = \"${DESTINATION_NS}\""
+}
+
+# Helper function to encrypt secrets from stdin
+function encrypt_secret_from_stdin() {
+ local PUBLIC_KEY="$1"
+
+ # Save secret manifest to temporary file
+ local TMPFILE=$(mktemp /tmp/secret.XXXXXXXXXX.yaml) || exit 1
+ cat > "${TMPFILE}"
+
+ # Encrypt
+ sops \
+ --age=${PUBLIC_KEY} \
+ --encrypt \
+ --encrypted-regex '^(data|stringData)$' \
+ --in-place "${TMPFILE}"
+
+ # Outputs the result and removes the temporary file
+ cat "${TMPFILE}" && rm -f "${TMPFILE}"
+}
+
+# Creates all folders in the profile (as well as env var aliases)
+export ADDON_CTRL_DIR="${PROJECT_DIR}/infra-controller-profiles/${PROFILE_NAME}"
+export ADDON_CONFIG_DIR="${PROJECT_DIR}/infra-config-profiles/${PROFILE_NAME}"
+export RESOURCES_DIR="${PROJECT_DIR}/managed-resources/${PROFILE_NAME}"
+export APPS_DIR="${PROJECT_DIR}/app-profiles/${PROFILE_NAME}"
+mkdir -p "${ADDON_CTRL_DIR}"
+mkdir -p "${ADDON_CONFIG_DIR}"
+mkdir -p "${RESOURCES_DIR}"
+mkdir -p "${APPS_DIR}"
+
+# Copies the templates for cluster setup
+cp "${TEMPLATES_DIR}"/* "${CLUSTER_DIR}/"
+
+# Repo URLs
+export FLEET_REPO_URL="${GITEA_HTTP_URL}/${GITEA_STD_USERNAME}/fleet-osm.git"
+export SW_CATALOGS_REPO_URL="${GITEA_HTTP_URL}/${GITEA_STD_USERNAME}/sw-catalogs-osm.git"
+export INFRA_CONTROLLERS_PATH="./${MGMT_PROJECT_NAME}/infra-controller-profiles/_management"
+export INFRA_CONFIGS_PATH="./${MGMT_PROJECT_NAME}/infra-config-profiles/_management"
+export MANAGED_RESOURCES_PATH="./${MGMT_PROJECT_NAME}/managed-resources/_management"
+export APPS_PATH="./${MGMT_PROJECT_NAME}/app-profiles/_management"
+
+# Render Flux `GitRepository` objects with proper Git URL and relative repo paths
+envsubst < "${TEMPLATES_DIR}/fleet-repo.yaml" > "${CLUSTER_DIR}/fleet-repo.yaml"
+envsubst < "${TEMPLATES_DIR}/sw-catalogs-repo.yaml" > "${CLUSTER_DIR}/sw-catalogs-repo.yaml"
+
+# Secrets to access both Git repos
+# (NOTE: these are the last secrets to be added imperatively)
+kubectl delete secret fleet-repo --namespace flux-system 2> /dev/null || true
+kubectl create secret generic fleet-repo \
+ --namespace flux-system \
+ --from-literal=username="${GITEA_STD_USERNAME}" \
+ --from-literal=password="${GITEA_STD_USER_PASS}"
+
+kubectl delete secret sw-catalogs --namespace flux-system 2> /dev/null || true
+kubectl create secret generic sw-catalogs \
+ --namespace flux-system \
+ --from-literal=username="${GITEA_STD_USERNAME}" \
+ --from-literal=password="${GITEA_STD_USER_PASS}"
+
+# Render Flux `Kustomizations` to sync with default profiles
+envsubst < "${TEMPLATES_DIR}/infra-controllers.yaml" > "${CLUSTER_DIR}/infra-controllers.yaml"
+envsubst < "${TEMPLATES_DIR}/infra-configs.yaml" > "${CLUSTER_DIR}/infra-configs.yaml"
+envsubst < "${TEMPLATES_DIR}/managed-resources.yaml" > "${CLUSTER_DIR}/managed-resources.yaml"
+envsubst < "${TEMPLATES_DIR}/apps.yaml" > "${CLUSTER_DIR}/apps.yaml"
+
+# Create `ConfigMaps` into profiles (and `Namespace` specs when needed) to avoid sync errors
+## Infra controllers ConfigMap
+CONFIGMAP_NAME="infra-controllers"
+PROFILE_REPO_URL="${FLEET_REPO_URL}"
+PROFILE_PATH="${INFRA_CONTROLLERS_PATH}"
+create_profile_configmap \
+ "${CONFIGMAP_NAME}" \
+ "${PROFILE_REPO_URL}" \
+ "${PROFILE_PATH}" \
+ > "${ADDON_CTRL_DIR}/profile-configmap.yaml"
+
+## Infra configurations ConfigMap
+CONFIGMAP_NAME="infra-configs"
+PROFILE_REPO_URL="${FLEET_REPO_URL}"
+PROFILE_PATH="${INFRA_CONFIGS_PATH}"
+create_profile_configmap \
+ "${CONFIGMAP_NAME}" \
+ "${PROFILE_REPO_URL}" \
+ "${PROFILE_PATH}" \
+ > "${ADDON_CONFIG_DIR}/profile-configmap.yaml"
+
+## Managed resources ConfigMap
+CONFIGMAP_NAME="managed-resources"
+PROFILE_REPO_URL="${FLEET_REPO_URL}"
+PROFILE_PATH="${MANAGED_RESOURCES_PATH}"
+create_profile_configmap \
+ "${CONFIGMAP_NAME}" \
+ "${PROFILE_REPO_URL}" \
+ "${PROFILE_PATH}" \
+ > "${RESOURCES_DIR}/profile-configmap.yaml"
+
+## Managed resources namespace
+kubectl create ns ${CONFIGMAP_NAME} \
+ -o yaml \
+ --dry-run=client \
+ > "${RESOURCES_DIR}/namespace.yaml"
+
+### Copy secrets for Git repos from `flux-system` to `managed-resources` namespace
+clone_secret_to_new_ns_stdout \
+ flux-system \
+ flux-system \
+ "${CONFIGMAP_NAME}" | \
+encrypt_secret_from_stdin \
+ "${PUBLIC_KEY}" \
+> "${RESOURCES_DIR}/secret-flux-system.yaml"
+
+clone_secret_to_new_ns_stdout \
+ fleet-repo \
+ flux-system \
+ "${CONFIGMAP_NAME}" | \
+encrypt_secret_from_stdin \
+ "${PUBLIC_KEY}" \
+> "${RESOURCES_DIR}/secret-fleet-repo.yaml"
+
+clone_secret_to_new_ns_stdout \
+ sw-catalogs \
+ flux-system \
+ "${CONFIGMAP_NAME}" | \
+encrypt_secret_from_stdin \
+ "${PUBLIC_KEY}" \
+> "${RESOURCES_DIR}/secret-sw-catalogs.yaml"
+
+## Apps ConfigMap
+CONFIGMAP_NAME="apps"
+PROFILE_REPO_URL="${FLEET_REPO_URL}"
+PROFILE_PATH="${APPS_PATH}"
+create_profile_configmap \
+ "${CONFIGMAP_NAME}" \
+ "${PROFILE_REPO_URL}" \
+ "${PROFILE_PATH}" \
+ > "${APPS_DIR}/profile-configmap.yaml"
diff --git a/installers/mgmt-cluster/flux/scripts/helper-functions.rc b/installers/mgmt-cluster/flux/scripts/helper-functions.rc
new file mode 100644
index 0000000..adc326d
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/helper-functions.rc
@@ -0,0 +1,34 @@
+#!/bin/bash
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+# Helper function to encrypt secrets in-place in manifest file
+function encrypt_secret_inplace() {
+ local FILE="$1"
+ local AGE_KEY_NAME=${AGE_KEY_NAME_MGMT:-"$2"}
+
+ # Load the contents of both keys
+ local PUBLIC_KEY=$(<"${CREDENTIALS_DIR}/${AGE_KEY_NAME}.pub")
+ # local PRIVATE_KEY=$(<"${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key")
+
+ sops \
+ --age=${PUBLIC_KEY} \
+ --encrypt \
+ --encrypted-regex '^(data|stringData)$' \
+ --in-place "${FILE}"
+}
diff --git a/installers/mgmt-cluster/flux/scripts/library/functions.sh b/installers/mgmt-cluster/flux/scripts/library/functions.sh
new file mode 100755
index 0000000..638a1d2
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/library/functions.sh
@@ -0,0 +1,91 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+RESET='\033[0m'
+
+# Colored messages (blue is the default)
+# Examples:
+# m "hello world"
+# m "hello world" "$GREEN"
+function m() {
+ local COLOR=${2:-$BLUE}
+ echo -e "$COLOR$1$RESET"
+}
+
+function copy_function() {
+ local ORIG_FUNC=$(declare -f $1)
+ local NEWNAME_FUNC="$2${ORIG_FUNC#$1}"
+ eval "$NEWNAME_FUNC"
+}
+
+function replace_text() {
+ local FILE=$1
+ local START=$2
+ local END=$3
+ local NEW=$4
+ local T=$(mktemp)
+ head -n $((START-1)) "$FILE" > "$T"
+ echo "$NEW" >> "$T"
+ tail -n +$((END+1)) "$FILE" >> "$T"
+ mv "$T" "$FILE"
+}
+
+function insert_text() {
+ local FILE=$1
+ local START=$2
+ local NEW=$3
+ local T=$(mktemp)
+ head -n $((START-1)) "$FILE" > "$T"
+ echo "$NEW" >> "$T"
+ tail -n +$START "$FILE" >> "$T"
+ mv "$T" "$FILE"
+}
+
+function remove_text() {
+ local FILE=$1
+ local START=$2
+ local END=$3
+ local T=$(mktemp)
+ head -n $((START-1)) "$FILE" > "$T"
+ tail -n +$((END+1)) "$FILE" >> "$T"
+ mv "$T" "$FILE"
+}
+
+function envsubst_cp() {
+ local FROM_FILE=$1
+ local TO_FILE=$2
+ mkdir --parents "$(dirname "$TO_FILE")"
+ cat "$FROM_FILE" | envsubst > "$TO_FILE"
+}
+
+function envsubst_dir() {
+ local FROM_DIR=$1
+ local TO_DIR=$2
+ rm --recursive --force "$TO_DIR"
+ mkdir --parents "$TO_DIR"
+ pushd "$FROM_DIR" > /dev/null
+ local F
+ find . -type f | while read F; do
+ envsubst_cp "$F" "$TO_DIR/$F"
+ done
+ popd > /dev/null
+}
diff --git a/installers/mgmt-cluster/flux/scripts/library/trap.sh b/installers/mgmt-cluster/flux/scripts/library/trap.sh
new file mode 100755
index 0000000..2a1156d
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/library/trap.sh
@@ -0,0 +1,48 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+function goodbye() {
+ local DURATION=$(date --date=@$(( "$(date +%s)" - "$TRAP_START_TIME" )) --utc +%T)
+ local CODE=$1
+ cd "$TRAP_DIR"
+ if [ "$CODE" == 0 ]; then
+ m "$(realpath --relative-to="$HERE" "$0") succeeded! $DURATION" "$GREEN"
+ elif [ "$CODE" == abort ]; then
+ m "Aborted $(realpath --relative-to="$HERE" "$0")! $DURATION" "$RED"
+ else
+ m "Oh no! $(realpath --relative-to="$HERE" "$0") failed! $DURATION" "$RED"
+ fi
+}
+
+function trap_EXIT() {
+ local ERR=$?
+ goodbye "$ERR"
+ exit "$ERR"
+}
+
+function trap_INT() {
+ goodbye abort
+ trap - EXIT
+ exit 1
+}
+
+TRAP_DIR=$PWD
+TRAP_START_TIME=$(date +%s)
+
+trap trap_INT INT
+
+trap trap_EXIT EXIT
diff --git a/installers/mgmt-cluster/flux/scripts/mgmt-cluster-bootstrap.sh b/installers/mgmt-cluster/flux/scripts/mgmt-cluster-bootstrap.sh
new file mode 100755
index 0000000..9cbda51
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/mgmt-cluster-bootstrap.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+set -e -o pipefail
+
+export HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
+source "${HERE}/library/functions.sh"
+source "${HERE}/library/trap.sh"
+
+
+# Bootstrap
+REPO=fleet-osm
+GIT_PATH=./clusters/_management
+GIT_BRANCH=main
+GIT_HTTP_URL=${GITEA_HTTP_URL}/${GITEA_STD_USERNAME}/${REPO}.git
+flux bootstrap git \
+ --url=${GIT_HTTP_URL} \
+ --allow-insecure-http=true \
+ --username=${GITEA_STD_USERNAME} \
+ --password="${GITEA_STD_USER_PASS}" \
+ --token-auth=true \
+ --branch=${GIT_BRANCH} \
+ --path=${GIT_PATH}
+
+# Check if successful
+flux check
diff --git a/installers/mgmt-cluster/flux/scripts/watch-mgmt-cluster.sh b/installers/mgmt-cluster/flux/scripts/watch-mgmt-cluster.sh
new file mode 100755
index 0000000..f934ee8
--- /dev/null
+++ b/installers/mgmt-cluster/flux/scripts/watch-mgmt-cluster.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+watch "kubectl get managed; kubectl get kustomizations -A; kubectl get helmreleases -A"