#!/bin/bash ############################################################ # Decription: Script to manage stack docker # # Author: Gilles Mouchet (gilles.mouchet@gmail.com) # Creation Date: 12-06-2025 # Version: 1.0 # Usage: ./manage.sh # Changelog: # V1.0 - dd-Mmm-2025 - GMo # Added # - Creation of script from scratch # ############################################################ #----------------------------------------------------------------- # Doesn't change anything from here #----------------------------------------------------------------- version="1.0-rc1" prog_name="./$(/bin/basename $0)" containers_to_start=(init minio minio-cli grafana loki promtail prometheus node-exporter proxy) containers_all=("${containers_to_start[@]}" log-generator dns-tools) # Couleurs VERT="\e[32m" ROUGE="\e[31m" NORMAL="\e[0m" #----------------------------------------------------------------- # Functions #----------------------------------------------------------------- # display help function print_usage { /bin/cat << EOF Usage: $prog_name [options] Options: config Create folder for persit. vol. and copy conf files param Display all vars defined in .env file list Display container (servie) list define in docker compose file create Create container start-stack Start stack '${COMPOSE_PROJECT_NAME}' stop Stop stack '${COMPOSE_PROJECT_NAME}' down Stop stack '${COMPOSE_PROJECT_NAME}' and delete containers restart Restart stack '${COMPOSE_PROJECT_NAME}' version,-v,--version Display script version help,-h,--help Display this help Examples: To start stack '${COMPOSE_PROJECT_NAME}' $prog_name start To stop stack '${COMPOSE_PROJECT_NAME}' $prog_name stop To start a specific container and his depend docker compose up -d Ex: docker compose up minio-cli -d To create a new stack $prog_name config $prog_name create $prog_name start EOF } #----------------------------------------------------------------- # create folders for persistent volume and config file function create_folder { for folder_item in "${folder_to_create[@]}"; do echo -n "Create folder '${folder_item}': " sudo mkdir -p ${folder_item} rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR${NORMAL}" exit $rc fi done } #----------------------------------------------------------------- # remove folders for persistent volume and config file function remove_folder { for folder_item in "${folder_to_create[@]}"; do # we doesn't delete the certs folder. # this folder is maybe shared with other container if [ "${folder_item}" != "${PRX_CERTS_DIR}" ]; then echo -n "Remove folder '${folder_item}': " sudo rm -rf ${folder_item} rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR${NORMAL}" exit $rc fi fi done } #----------------------------------------------------------------- # copy config file function copy_conf_file { echo -n "Copy loki config file into '${LOKI_CONF_DIR}/': " envsubst < ./config/loki/local-config.yaml.tmpl | \ sudo tee ${LOKI_CONF_DIR}/local-config.yaml > /dev/null rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi echo -n "Copy proxy (nginx) config file into '${PRX_NGINX_CONF_DIR}/': " sudo cp ./config/nginx/default.conf ${PRX_NGINX_CONF_DIR}/. > /dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi echo -n "Copy the Prometheus configuration file into '${PROM_CONF_DIR}/': " sudo cp ./config/prometheus/prometheus.yml ${PROM_CONF_DIR}/. > /dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi echo -n "Copy promtail config file into '${PROMTAIL_CONF_DIR}/': " sudo cp ./config/promtail/config.yml ${PROMTAIL_CONF_DIR}/. > /dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi } #----------------------------------------------------------------- # create all containers function create_container { for cont_item in "${containers_all[@]}"; do # check if container exist. If not create it if [ $(docker ps -a | grep ${COMPOSE_PROJECT_NAME}-$cont_item | wc -l) -eq 0 ]; then echo -n "Create container $cont_item: " if docker compose create $cont_item > /dev/null 2>&1; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR${NORMAL}" fi # else # echo "Container $cont_item already exist" fi done } #----------------------------------------------------------------- # start container function start_container { for cont_item in "${containers_to_start[@]}"; do echo -n "Start container $cont_item: " if docker compose up -d $cont_item > /dev/null 2>&1; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR${NORMAL}" fi done } #----------------------------------------------------------------- # create grafana ds # $1 = ds name # $2 = url function add_gf_data_source { curl -k -X POST "${GF_ROOT_URL}/api/datasources" \ -u "${GF_ADMIN_USER}:${GF_ADMIN_PASS}" \ -H "Content-Type: application/json" \ -d '{ "name": "'"${1^}"'", "type": "'"$1"'", "access": "proxy", "url": "'"$2"'", "basicAuth": false, "isDefault": false, "jsonData": { "maxLines": 1000 } }' > /dev/null 2>&1 } #----------------------------------------------------------------- # import grafana dashboard # $1 = dashboard ID # $2 = source # $3 = dashboard title. Needed for check if already impoerted function import_gf_dashboard { db_file=/tmp/dashboard-$1.json wr_file=/tmp/wrapped-dashboard.json #download json curl -s -k -o "$db_file" "https://grafana.com/api/dashboards/$1/revisions/latest/download" if [ ! -s "$db_file" ]; then exit 1 fi # stage 2 - clean up and add datadource sed -i 's/"id":[ ]*[0-9]\+/"id": null/g' "$db_file" sed -i 's/"uid":[ ]*"[^"]*"/"uid": null/g' "$db_file" sed -i 's/"datasource": ".*"/"datasource": "'"${2^}"'"/g' "$db_file" # stage 3 - creating the final JSON file expected by the Grafana API echo '{' > "$wr_file" echo '"dashboard":' >> "$wr_file" cat "$db_file" >> "$wr_file" echo ',' >> "$wr_file" echo '"overwrite": true' >> "$wr_file" echo '}' >> "$wr_file" # stage 4 - import dashboard into grafana curl -s -k -X POST "$GF_ROOT_URL/api/dashboards/db" \ -u "$GF_ADMIN_USER:$GF_ADMIN_PASS" \ -H "Content-Type: application/json" \ --data-binary "@$wr_file" > /dev/null 2>&1 #rm -f $db_file $wr_file } #----------------------------------------------------------------- # MAIN #----------------------------------------------------------------- # read .env if exist if [ ! -f .env ]; then echo "file '.env' doesn't exist!" echo "See README.md" exit fi set -a source .env set +a # create array folder to create folder_to_create=(${PRX_CERTS_DIR} ${PRX_NGINX_CONF_DIR} ${MINIO_DATA_ROOT_DIR} ${MINIO_CONF_CLI_DIR} ${GF_DATA_DIR} ${PROM_CONF_DIR} ${PROM_DATA_DIR} ${LOKI_CONF_DIR} ${LOKI_DATA_DIR} ${LOKI_GEN_LOG_DIR} ${PROMTAIL_CONF_DIR}) case "$1" in param) cat << EOF COMPOSE_PROJECT_NAME = ${COMPOSE_PROJECT_NAME} PRX_CERTS_DIR = ${PRX_CERTS_DIR} PRX_NGINX_CONF_DIR = ${PRX_NGINX_CONF_DIR} MINIO_DATA_ROOT_DIR = ${MINIO_DATA_ROOT_DIR} MINIO_CONF_CLI_DIR = ${MINIO_CONF_CLI_DIR} MINIO_ACCESS_KEY = ${MINIO_ACCESS_KEY} MINIO_SECRET_KEY = ${MINIO_SECRET_KEY} MINIO_BUCKET_NAME = ${MINIO_BUCKET_NAME} MINIO_BUCKET_USER = ${MINIO_BUCKET_USER} MINIO_BUCKET_PASS = ${MINIO_BUCKET_PASS} MINIO_BUCKET_POL_NAME = ${MINIO_BUCKET_POL_NAME} MINIO_REDIRECT_URL = ${MINIO_REDIRECT_URL} MINIO_ALIAS = ${MINIO_ALIAS} GF_ADMIN_USER = ${GF_ADMIN_USER} GF_ADMIN_PASS = ${GF_ADMIN_PASS} GF_DATA_DIR = ${GF_DATA_DIR} GF_ROOT_URL = ${GF_ROOT_URL} PROM_CONF_DIR = ${PROM_CONF_DIR} PROM_DATA_DIR = ${PROM_DATA_DIR} LOKI_CONF_DIR = ${LOKI_CONF_DIR} LOKI_DATA_DIR = ${LOKI_DATA_DIR} LOKI_GEN_LOG_DIR = ${LOKI_GEN_LOG_DIR} PROMTAIL_CONF_DIR = ${PROMTAIL_CONF_DIR} EOF ;; start-stack) echo -e "---- Create Folders ----" create_folder echo -e "\n---- Copy config file ----" copy_conf_file echo -e "\n---- Config minio ----" #----- echo -n "Start minio container: " if docker compose up -d minio > /dev/null 2>&1; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR${NORMAL}" fi #---- echo -n "Wait until minio container is started: " while ! curl -s "http://$HOSTNAME:9000/minio/health/live" >/dev/null; do sleep 1 done echo -e "${VERT}STARTED${NORMAL}" #----- echo -n "Start minio-cli container: " docker compose up -d minio-cli > /dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi #----- echo -n "Create minios alias: " docker exec ${COMPOSE_PROJECT_NAME}-minio-cli-1 mc alias set ${MINIO_ALIAS} http://minio:9000 \ ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY} > /dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi #----- echo -n "Copy mino policy config file into ${MINIO_CONF_CLI_DIR}/: " envsubst < ./config/minio/policy.json.tmpl | sudo tee ${MINIO_CONF_CLI_DIR}/.mc/policy.json > /dev/null rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi #----- echo -n "Create user for bucket ${MINIO_BUCKET_NAME}: " docker exec ${COMPOSE_PROJECT_NAME}-minio-cli-1 mc admin user add \ ${MINIO_ALIAS} ${MINIO_BUCKET_USER} ${MINIO_BUCKET_PASS} >/dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi #----- echo -n "Create policy for access to bucket ${MINIO_BUCKET_NAME}: " docker exec ${COMPOSE_PROJECT_NAME}-minio-cli-1 mc admin policy create \ ${MINIO_ALIAS} ${MINIO_BUCKET_POL_NAME} /root/.mc/policy.json >/dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi #----- echo -n "Attach policy to user ${MINIO_BUCKET_USER}: " docker exec ${COMPOSE_PROJECT_NAME}-minio-cli-1 mc admin policy attach \ ${MINIO_ALIAS} ${MINIO_BUCKET_POL_NAME} --user=${MINIO_BUCKET_USER} >/dev/null 2>&1 rc=$? if [ $rc -eq 0 ]; then echo -e "${VERT}SUCCESS${NORMAL}" else echo -e "${ROUGE}ERROR ($rc)${NORMAL}" exit $rc fi #----- echo -n "Create bucket if not exist: " docker exec ${COMPOSE_PROJECT_NAME}-minio-cli-1 mc ls "${MINIO_ALIAS}/$MINIO_BUCKET_NAME" >/dev/null 2>&1 rc=$? if [ $rc -ne 0 ]; then docker exec -it ${COMPOSE_PROJECT_NAME}-minio-cli-1 /bin/sh -c \ "mc mb ${MINIO_ALIAS}/${MINIO_BUCKET_NAME}" >/dev/null 2>&1 rc1=$? if [ $rc1 -eq 0 ]; then echo -e "${VERT}SUCCESSFULLY CREATED${NORMAL}" else echo -e "${ROUGE}ERROR ($rc1)${NORMAL}" exit $rc1 fi else echo -e "${VERT}ALREADY EXISTING${NORMAL}" fi # ---- echo -e "\n---- Create container ----" create_container echo -e "\n---- Start container ----" start_container echo -e "\n---- Create Grafana DS ----" # ---- # wait until grafan is up echo -n "Wait until grafana is starting: " timeout=60 # Max time to wait in seconds interval=2 # Wait time between checks elapsed=0 while true; do status_code=$(curl -k -s -o /dev/null -w "%{http_code}" \ -u "$GF_ADMIN_USER:$GF_ADMIN_PASS" \ "$GF_ROOT_URL/api/health") if [ "$status_code" -eq 200 ]; then echo -e "${VERT}STARTED${NORMAL}" break fi if [ "$elapsed" -ge "$timeout" ]; then echo -e "${ROUGE}ERROR ($status_code)${NORMAL}" exit 1 fi sleep "$interval" elapsed=$((elapsed + interval)) done # ---- echo -n "Create Loki DS: " add_gf_data_source loki http://loki:3100 rc1=$? if [ $rc1 -eq 0 ]; then echo -e "${VERT}SUCCESSFULLY CREATED${NORMAL}" else echo -e "${ROUGE}ERROR ($rc1)${NORMAL}" exit $rc1 fi # ---- echo -n "Create Prometheus DS: " add_gf_data_source prometheus http://prometheus:9090 "" rc1=$? if [ $rc1 -eq 0 ]; then echo -e "${VERT}SUCCESSFULLY CREATED${NORMAL}" else echo -e "${ROUGE}ERROR ($rc1)${NORMAL}" exit $rc1 fi echo -e "\n---- Install Grafana dashboard ----" # ---- db_name="Node Exporter Full" echo -n "Import '$db_name' dashboard: " # check if already imported response=$(curl -k -s -u "$GF_ADMIN_USER:$GF_ADMIN_PASS" "$GF_ROOT_URL/api/search?query=") if echo "$response" | grep -iq "\"title\":\"$db_name\""; then echo -e "${VERT}ALREADY IMPORTED${NORMAL}" else import_gf_dashboard 1860 prometheus rc1=$? if [ $rc1 -eq 0 ]; then echo -e "${VERT}SUCCESSFULLY IMPORTED${NORMAL}" else echo -e "${ROUGE}ERROR ($rc1)${NORMAL}" exit $rc1 fi fi # ---- db_name="Logs / App" echo -n "Import '$db_name' dashboard: " # check if already imported response=$(curl -k -s -u "$GF_ADMIN_USER:$GF_ADMIN_PASS" "$GF_ROOT_URL/api/search?query=") if echo "$response" | grep -iq "\"title\":\"$db_name\""; then echo -e "${VERT}ALREADY IMPORTED${NORMAL}" else import_gf_dashboard 13639 loki rc1=$? if [ $rc1 -eq 0 ]; then echo -e "${VERT}SUCCESSFULLY IMPORTED${NORMAL}" else echo -e "${ROUGE}ERROR ($rc1)${NORMAL}" exit $rc1 fi fi # ---- #db_name="Prometheus 2.0 Overview" db_name="Prometheus All Metrics" echo -n "Import '$db_name' dashboard: " # check if already imported response=$(curl -k -s -u "$GF_ADMIN_USER:$GF_ADMIN_PASS" "$GF_ROOT_URL/api/search?query=") if echo "$response" | grep -iq "\"title\":\"$db_name\""; then echo -e "${VERT}ALREADY IMPORTED${NORMAL}" else #import_gf_dashboard 3662 prometheus import_gf_dashboard 19268 prometheus rc1=$? if [ $rc1 -eq 0 ]; then echo -e "${VERT}SUCCESSFULLY IMPORTED${NORMAL}" else echo -e "${ROUGE}ERROR ($rc1)${NORMAL}" exit $rc1 fi fi # ---- db_name="MinIO Dashboard" echo -n "Import '$db_name' dashboard: " # check if already imported response=$(curl -k -s -u "$GF_ADMIN_USER:$GF_ADMIN_PASS" "$GF_ROOT_URL/api/search?query=") if echo "$response" | grep -iq "\"title\":\"$db_name\""; then echo -e "${VERT}ALREADY IMPORTED${NORMAL}" else import_gf_dashboard 13502 prometheus rc1=$? if [ $rc1 -eq 0 ]; then echo -e "${VERT}SUCCESSFULLY IMPORTED${NORMAL}" else echo -e "${ROUGE}ERROR ($rc1)${NORMAL}" exit $rc1 fi fi ;; stop-stack) docker compose stop ;; down-down) docker compose down ;; del-stack) # confirm installation while true; do read -p "Are you sure you want to permanently delete the stack '${COMPOSE_PROJECT_NAME}'? [y/n] ? " yn case $yn in [Yy]* ) break;; * ) exit; #echo "Please answer yes or no.";; esac done echo -e "\n---- Stop and remove all container ----" docker compose down echo -e "\n---- Delete all folder ----" remove_folder ;; list) echo "-------------------------------------" echo " Container list" echo "-------------------------------------" docker compose config --services | sort #for cont_item in "${containers_all[@]}"; do # echo "- $cont_item" #done ;; -h|--help|help) print_usage exit 0 ;; -v|--version|version) cat << EOF $(basename "$0") v$version (c) 1990 - $(date +%Y) by Gilles Mouchet Non-Commercial Use License – See LICENSE for details EOF exit 0 ;; *) print_usage exit 0 ;; esac