#!/bin/bash # The most recent program version. _VERSION="3.4.6" _V="3.4" # The program full name PROGRAM_NAME="Octojoom" # Set Server Hostname SERVER_HOSTNAME="$(hostname)" # Set the back title BACK_TITLE=" Octoleo | ${USER}@${SERVER_HOSTNAME}" # make sure whiptail is installed command -v whiptail >/dev/null 2>&1 || { echo >&2 "ERROR: ${PROGRAM_NAME} v${_VERSION} script require whiptail." exit 1 } # make sure curl is installed command -v curl >/dev/null 2>&1 || { echo >&2 "ERROR: ${PROGRAM_NAME} v${_VERSION} script require curl." exit 1 } # make sure awk is installed command -v awk >/dev/null 2>&1 || { echo >&2 "ERROR: ${PROGRAM_NAME} v${_VERSION} script require awk." exit 1 } # Check if Docker is installed. if ! command -v docker &>/dev/null; then # If Docker is not installed, ask the user if they want to install it. if whiptail --yesno "Docker is not installed. Do you want to install it now?" \ --backtitle "${BACK_TITLE}" 10 60; then # If the user chooses Yes, install Docker. echo "Installing Docker..." # Add Docker GPG key and repository to sources.list. curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Update package index and install Docker. sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io -y # Add the current user to the docker group. sudo groupadd docker sudo usermod -aG docker "$USER" # Enable and start the Docker service. sudo systemctl enable docker.service sudo systemctl start docker.service echo "Docker is installed." else # If the user chooses No, exit the script with an error message. echo >&2 "ERROR: ${PROGRAM_NAME} v${_VERSION} script require docker." exit 1 fi fi # Check if Docker Compose is installed. if ! command -v docker-compose &>/dev/null; then # If Docker Compose is not installed, ask the user if they want to install it. if whiptail --yesno "Docker Compose is not installed. Do you want to install it now?" \ --backtitle "${BACK_TITLE}" 10 60; then # If the user chooses Yes, install Docker Compose. echo "Installing Docker Compose..." # Download the latest Docker Compose binary. COMPOSE_VERSION=$(curl --silent https://api.github.com/repos/docker/compose/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') sudo curl -SL "https://github.com/docker/compose/releases/download/$COMPOSE_VERSION/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # Make the Docker Compose binary executable. sudo chmod +x /usr/local/bin/docker-compose sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose echo "Docker Compose is installed." else # If the user chooses No, exit the script with an error message. echo >&2 "ERROR: ${PROGRAM_NAME} v${_VERSION} script require docker-compose." exit 1 fi fi # just clear the screen clear #####################################################################################################################VDM ######################################## The main method function main() { # we check if we have a type and a task # we use the __TRuST__ convention AS "PUBLIC METHODS" to avoid wrong calls, and hide other functions if [ ${#VDM_CONTAINER_TYPE} -ge 1 ] && [ ${#VDM_TASK} -ge 1 ] && isFunc "${VDM_CONTAINER_TYPE}__TRuST__${VDM_TASK}"; then "${VDM_CONTAINER_TYPE}__TRuST__${VDM_TASK}" else mainMenu fi } #####################################################################################################################VDM ######################################## SETUP TRAEFIK function traefik__TRuST__setup() { # load this container type globals # shellcheck disable=SC1090 [ -f "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" ] && source "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" # check if we have secure switch set setSecureState # setup letsencrypt stuff if $VDM_SECURE; then VDM_REMOVE_SECURE='' VDM_HTTP_SCHEME="https" # get the email if not set while [ ${#VDM_SECURE_EMAIL} -le 1 ]; do # get the value VDM_SECURE_EMAIL=$(getInput 'Enter email for the Letsencrypt setup.' '' 'Enter Valid Email') # keep asking [ ${#VDM_SECURE_EMAIL} -ge 1 ] || { showError "You must enter an email for the Letsencrypt setup." } done # make sure the directory exist mkdir -p "${VDM_PROJECT_PATH}/traefik" # we must create this, else docker creates a folder echo "{}" > "${VDM_PROJECT_PATH}/traefik/acme.json" # make sure the permission are good sudo chmod 600 "${VDM_PROJECT_PATH}/traefik/acme.json" # and is owned by root sudo chown -R root:root "${VDM_PROJECT_PATH}/traefik" else VDM_REMOVE_SECURE="#" VDM_HTTP_SCHEME="http" fi # set the main domain if not set setMainDomain ########################## ### export all we need # global export VDM_CONTAINER_TYPE export VDM_REPO_PATH export VDM_PROJECT_PATH # container export VDM_REMOVE_SECURE export VDM_HTTP_SCHEME export VDM_SECURE_EMAIL ## create the directory if it does not yet already exist # shellcheck disable=SC2174 mkdir -p -m 700 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}" ## place this docker composer file in its place traefikContainer >"${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/docker-compose.yml" ## set permissions chmod 600 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/docker-compose.yml" # saved the file showNotice "Saved ${VDM_CONTAINER_TYPE}:docker-compose.yml file.\nSetup of this container is complete!" # ask if we should continue to enable if (whiptail --yesno "Would you also like to enable this ${VDM_CONTAINER_TYPE^} container" --defaultno \ --title "Enable Container" --backtitle "${BACK_TITLE}" 8 112); then enableContainer "${VDM_CONTAINER_TYPE}" fi ########################## ### unset all no longer needed # container unset VDM_REMOVE_SECURE unset VDM_HTTP_SCHEME unset VDM_SECURE_EMAIL # return a success return 0 } # return the Traefik Container setup yml function traefikContainer() { # we build the yml file cat <"${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/docker-compose.yml" ## set permissions chmod 600 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/docker-compose.yml" # saved the file showNotice "Saved ${VDM_CONTAINER_TYPE}:docker-compose.yml file.\nSetup of this container is complete!" # ask if we should continue to enable if (whiptail --yesno "Would you also like to enable this ${VDM_CONTAINER_TYPE^} container" \ --defaultno --title "Enable Container" --backtitle "${BACK_TITLE}" 8 112); then enableContainer "${VDM_CONTAINER_TYPE}" fi ########################## ### unset all no longer needed # container unset VDM_SUBDOMAIN unset VDM_REMOVE_SECURE unset VDM_ENTRY_POINT unset VDM_PORT_SECURE_LABELS # return a success return 0 } # return the Portainer Container setup yml function portainerContainer() { # we build the yml file cat <>"${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" # get the database name needed while [ ${#vdm_database_name} -le 1 ]; do # get the value vdm_database_name=$(getInput "Enter Database Name\n[Text with no spaces that is only underscore and alphabetical]" \ "vdm_io" 'Enter Database Name') # keep asking if empty or does exist [ ${#vdm_database_name} -ge 1 ] || { showError "You must enter a database name!" } done # get the database user name needed while [ ${#vdm_database_user} -le 1 ]; do # get the value vdm_database_user=$(getInput "Enter Database Username\n[Text with no spaces that is only underscore and alphabetical]" \ "vdm_user" 'Enter Database Username') # keep asking if empty or does exist [ ${#vdm_database_user} -ge 1 ] || { showError "You must enter a database username!" } done # get the database user password needed while [ ${#vdm_database_pass} -le 1 ]; do # get the value vdm_database_pass=$(getPassword "Enter Database User Password" \ "$(getRandomPass 20)" 'Enter Database User Password') # keep asking if empty or does exist [ ${#vdm_database_pass} -ge 1 ] || { showError "You must enter a database user password!" } done # get the database root password while [ ${#vdm_database_rootpass} -le 1 ]; do # get the value vdm_database_rootpass=$(getPassword "Enter Database Root Password" \ "$(getRandomPass 40)" 'Enter Database Root Password') # keep asking if empty or does exist [ ${#vdm_database_rootpass} -ge 1 ] || { showError "You must enter a database root password!" } done } # set persistence VDM_JOOMLA_VOLUMES_MOUNT=$(getYMLine3 "- \"\${VDM_PROJECT_PATH}/${VDM_KEY}/joomla:/var/www/html\"") VDM_DB_VOLUMES_MOUNT=$(getYMLine3 "- \"\${VDM_PROJECT_PATH}/${VDM_KEY}/db:/var/lib/mysql\"") VDM_EXTRA_CONTAINER_STUFF='' VDM_EXTRA_JOOMLA_ENV='' VDM_VOLUMES='volumes:' reset_volume=true # only if in expert mode if isExpert && ! setPersistence 'Joomla website files and folders'; then VDM_JOOMLA_VOLUMES_MOUNT=$(getYMLine3 "- ${VDM_KEY,,}_web:/var/www/html") VDM_VOLUMES+=$(getYMLine1 "${VDM_KEY,,}_web:") reset_volume=false fi # only if in expert mode if isExpert && ! setPersistence 'Joomla database'; then VDM_DB_VOLUMES_MOUNT=$(getYMLine3 "- ${VDM_KEY,,}_db:/var/lib/mysql") VDM_VOLUMES+=$(getYMLine1 "${VDM_KEY,,}_db:") reset_volume=false fi # check if we have to reset the volume value $reset_volume && VDM_VOLUMES='' # add the projects path setContainerEnvVariable "VDM_PROJECT_PATH=\"${VDM_PROJECT_PATH}\"" # ask if we should add mailcatcher if (whiptail --yesno "Would you also like to enable mailcatcher for these ${VDM_CONTAINER_TYPE^} containers" \ --defaultno --title "Enable Mailcatcher" --backtitle "${BACK_TITLE}" 8 112); then VDM_EXTRA_CONTAINER_STUFF=$(getYMLine1 "mailcatcher${VDM_KEY}:") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine2 "image: schickling/mailcatcher") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine2 "container_name: mailcatcher${VDM_KEY}") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine2 "restart: unless-stopped") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine2 "networks:") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "- traefik") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine2 "labels:") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "# mailcatcher") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "- \"traefik.enable=true\"") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "- \"traefik.http.routers.mailcatcher${VDM_KEY}.rule=Host(\`${VDM_SUBDOMAIN}mail.${VDM_DOMAIN}\`)\"") if $VDM_SECURE; then if $VDM_SECURE_CLOUDFLARE; then VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "- \"traefik.http.routers.mailcatcher${VDM_KEY}.entrypoints=web\"") else VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "- \"traefik.http.routers.mailcatcher${VDM_KEY}.entrypoints=${VDM_ENTRY_POINT}\"") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "- \"traefik.http.routers.mailcatcher${VDM_KEY}.tls.certresolver=vdmresolver\"") fi fi VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "- \"traefik.http.routers.mailcatcher${VDM_KEY}.service=mailcatcher${VDM_KEY}\"") VDM_EXTRA_CONTAINER_STUFF+=$(getYMLine3 "- \"traefik.http.services.mailcatcher${VDM_KEY}.loadbalancer.server.port=1080\"") VDM_EXTRA_JOOMLA_ENV+=$(getYMLine3 "- JOOMLA_SMTP_HOST=mailcatcher${VDM_KEY}") fi # only if in expert mode set the container user detail id needed if isExpert; then # if this is our octoleo images we can also set the user ID and user-group ID setContainerUser "$(id -u)" # check that we got the details if [ -n "${VDM_PUID}" ] && [ -n "${VDM_PGID}" ]; then setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_PUID=\"#${VDM_PUID}\"" setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_PGID=\"#${VDM_PGID}\"" fi fi # add this value if not set variable [ ${#vdm_database_name} -ge 1 ] && setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_DB=\"${vdm_database_name}\"" # add this value if not set variable [ ${#vdm_database_user} -ge 1 ] && setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_DB_USER=\"${vdm_database_user}\"" # add this value if not set variable [ ${#vdm_database_pass} -ge 1 ] && setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_DB_PASS=\"${vdm_database_pass}\"" # add this value if not set variable [ ${#vdm_database_rootpass} -ge 1 ] && setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_DB_ROOT=\"${vdm_database_rootpass}\"" # we only do autodeploy for Joomla 4.3 and above if isVersionAbove "$VDM_JV" "4.3"; then # setup the website auto deploy details setJoomlaWebsiteDetails if [[ -n "${VDM_J_EMAIL}" ]]; then # add extra Joomla envs setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_JOOMLA_DB_TYPE=\"mysqli\"" setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_JOOMLA_DB_PREFIX=\"${VDM_KEY}_\"" setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_JOOMLA_SITE_NAME=\"${VDM_J_SITE_NAME}\"" setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_JOOMLA_ADMIN_USERNAME=\"${VDM_J_USERNAME}\"" setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_JOOMLA_ADMIN_USER=\"${VDM_J_USER}\"" setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_JOOMLA_ADMIN_EMAIL=\"${VDM_J_EMAIL}\"" setContainerEnvVariable "VDM_${VDM_ENV_KEY^^}_JOOMLA_ADMIN_PASSWORD=\"${VDM_J_PASSWORD}\"" VDM_EXTRA_JOOMLA_ENV+=$(getYMLine3 "- JOOMLA_DB_TYPE=\${VDM_${VDM_ENV_KEY^^}_JOOMLA_DB_TYPE}") VDM_EXTRA_JOOMLA_ENV+=$(getYMLine3 "- JOOMLA_DB_PREFIX=\${VDM_${VDM_ENV_KEY^^}_JOOMLA_DB_PREFIX}") VDM_EXTRA_JOOMLA_ENV+=$(getYMLine3 "- JOOMLA_SITE_NAME=\${VDM_${VDM_ENV_KEY^^}_JOOMLA_SITE_NAME}") VDM_EXTRA_JOOMLA_ENV+=$(getYMLine3 "- JOOMLA_ADMIN_USERNAME=\${VDM_${VDM_ENV_KEY^^}_JOOMLA_ADMIN_USERNAME}") VDM_EXTRA_JOOMLA_ENV+=$(getYMLine3 "- JOOMLA_ADMIN_USER=\${VDM_${VDM_ENV_KEY^^}_JOOMLA_ADMIN_USER}") VDM_EXTRA_JOOMLA_ENV+=$(getYMLine3 "- JOOMLA_ADMIN_EMAIL=\${VDM_${VDM_ENV_KEY^^}_JOOMLA_ADMIN_EMAIL}") VDM_EXTRA_JOOMLA_ENV+=$(getYMLine3 "- JOOMLA_ADMIN_PASSWORD=\${VDM_${VDM_ENV_KEY^^}_JOOMLA_ADMIN_PASSWORD}") fi fi # add PHP settings VDM_PHP_PROJECT_PATH="${VDM_KEY}" export VDM_PHP_PROJECT_PATH if isExpert && setPHPSettings; then VDM_JOOMLA_VOLUMES_MOUNT+=$(getYMLine3 "- \"\${VDM_PROJECT_PATH}/${VDM_PHP_PROJECT_PATH}/php.ini:/var/www/html/php.ini\"") fi # customize docker-entrypoint.sh VDM_ENTRY_PROJECT_PATH="${VDM_KEY}" export VDM_ENTRY_PROJECT_PATH if isExpert && setDockerEntrypoint; then VDM_JOOMLA_VOLUMES_MOUNT+=$(getYMLine3 "- \"\${VDM_PROJECT_PATH}/${VDM_ENTRY_PROJECT_PATH}/entrypoint.sh:/entrypoint.sh\"") fi ########################## ### export all we need # global export VDM_CONTAINER_TYPE export VDM_REPO_PATH export VDM_PROJECT_PATH # container export VDM_REMOVE_SECURE export VDM_ENTRY_POINT export VDM_HTTP_SCHEME export VDM_PERSISTENCE export VDM_NOT_PERSISTENCE export VDM_VOLUMES export VDM_JOOMLA_VOLUMES_MOUNT export VDM_DB_VOLUMES_MOUNT export VDM_EXTRA_CONTAINER_STUFF export VDM_JOOMLA_SECURE_LABELS export VDM_PHPMYADMIN_SECURE_LABELS export VDM_EXTRA_JOOMLA_ENV # container lower export vdm_database_name export vdm_database_user export vdm_database_pass export vdm_database_rootpass # set host file if needed updateHostFile # also set the database domain updateHostFile "${VDM_SUBDOMAIN}db" # create the directory if it does not yet already exist # shellcheck disable=SC2174 mkdir -p -m 700 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN}.${VDM_DOMAIN}" # place this docker composer file in its place joomlaContainer >"${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN}.${VDM_DOMAIN}/docker-compose.yml" # set permissions chmod 600 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN}.${VDM_DOMAIN}/docker-compose.yml" # saved the file showNotice "Saved ${VDM_CONTAINER_TYPE}:docker-compose.yml file.\nSetup of this container is complete!" # ask if we should continue to enable if (whiptail --yesno "Would you also like to enable this ${VDM_CONTAINER_TYPE^} container" \ --defaultno --title "Enable Container" --backtitle "${BACK_TITLE}" 8 112); then # we set the container details export VDM_CONTAINER="${VDM_SUBDOMAIN}.${VDM_DOMAIN}" # then we enable it enableContainer "${VDM_CONTAINER_TYPE}" fi # display the configurations showJoomlaConfigDetails ########################## ### unset all no longer needed # container unset VDM_SUBDOMAIN unset VDM_J_REPO unset VDM_ENTRY_REPO unset VDM_JV unset VDM_KEY unset VDM_ENV_KEY unset VDM_REMOVE_SECURE unset VDM_ENTRY_POINT unset VDM_HTTP_SCHEME unset VDM_PERSISTENCE unset VDM_NOT_PERSISTENCE unset VDM_VOLUMES unset VDM_JOOMLA_VOLUMES_MOUNT unset VDM_DB_VOLUMES_MOUNT unset VDM_PHP_PROJECT_PATH unset VDM_ENTRY_PROJECT_PATH unset VDM_EXTRA_CONTAINER_STUFF unset VDM_JOOMLA_SECURE_LABELS unset VDM_PHPMYADMIN_SECURE_LABELS unset VDM_EXTRA_JOOMLA_ENV unset VDM_J_SITE_NAME unset VDM_J_USERNAME unset VDM_J_USER unset VDM_J_EMAIL unset VDM_J_PASSWORD # container lower unset vdm_database_name unset vdm_database_user unset vdm_database_pass unset vdm_database_rootpass # return a success return 0 } # return the Joomla Container setup yml function joomlaContainer() { # set the USER if set vdm_container_user='' if [ -n "${VDM_PUID}" ] && [ -n "${VDM_PGID}" ]; then vdm_container_user=" - APACHE_RUN_USER=\${VDM_${VDM_ENV_KEY^^}_PUID} - APACHE_RUN_GROUP=\${VDM_${VDM_ENV_KEY^^}_PGID}" fi # we build the yml file cat <"${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN}.${VDM_DOMAIN}/docker-compose.yml" # set permissions chmod 600 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN}.${VDM_DOMAIN}/docker-compose.yml" # enable the container if $enable_containers; then # we set the container details export VDM_CONTAINER="${VDM_SUBDOMAIN}.${VDM_DOMAIN}" # then we enable it enableContainer "${VDM_CONTAINER_TYPE}" fi done ########################## ### unset all no longer needed # container unset VDM_SUBDOMAIN unset VDM_J_REPO unset VDM_ENTRY_REPO unset VDM_JV unset VDM_KEY unset VDM_ENV_KEY unset VDM_REMOVE_SECURE unset VDM_ENTRY_POINT unset VDM_HTTP_SCHEME unset VDM_PERSISTENCE unset VDM_NOT_PERSISTENCE unset VDM_VOLUMES unset VDM_JOOMLA_VOLUMES_MOUNT unset VDM_DB_VOLUMES_MOUNT unset VDM_NUMBER_CONTAINERS unset VDM_PHP_PROJECT_PATH unset VDM_ENTRY_PROJECT_PATH unset VDM_EXTRA_CONTAINER_STUFF unset VDM_JOOMLA_SECURE_LABELS unset VDM_PHPMYADMIN_SECURE_LABELS unset VDM_J_SITE_NAME unset VDM_J_USERNAME unset VDM_J_USER unset VDM_J_EMAIL unset VDM_J_PASSWORD # container lower unset vdm_database_name unset vdm_database_user unset vdm_database_pass unset vdm_database_rootpass # return a success return 0 } #####################################################################################################################VDM ######################################## SETUP OPENSSH function openssh__TRuST__setup() { # load this container type globals # shellcheck disable=SC1090 [ -f "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" ] && source "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" # get the ssh port if not set while [ ${#VDM_PORT} -le 1 ]; do # get the value VDM_PORT=$(getInput 'Enter ssh port to use\n[do not use 22]' '' 'Enter Port') # keep asking [ ${#VDM_PORT} -ge 1 ] || { showError "You must enter a ssh port for the container" } done # set the main domain if not set setMainDomain # get the username if not set while [ ${#VDM_USER_NAME} -le 1 ] || [ -d "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_USER_NAME}.${VDM_DOMAIN}" ]; do # get the value VDM_USER_NAME=$(getInput 'Enter username of the container\n[Text with no spaces that is only alphabetical]' 'ubuntu' 'Enter Username') # keep asking if empty or does exist if [ ${#VDM_USER_NAME} -ge 1 ] && [ -d "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_USER_NAME}.${VDM_DOMAIN}" ]; then showError "The username:${VDM_USER_NAME} is already used. Select another" elif [ ${#VDM_USER_NAME} -le 1 ]; then showError "You must enter a username for the container" fi done # be sure to set the container USER ID setContainerUser 33 # get the key if not set setUniqueKey "Enter key used for container naming." # get the env key if not set setEnvironmentKey # we check if it was set [ ${#VDM_PUBLIC_KEY_GLOBAL_DIR} -le 1 ] && VDM_PUBLIC_KEY_GLOBAL_DIR="${VDM_PUBLIC_KEY_DIR:-}" # get the global directory of the ssh public keys if not set while [ ${#VDM_PUBLIC_KEY_GLOBAL_DIR} -le 1 ] || [ ! -d "${VDM_PUBLIC_KEY_GLOBAL_DIR}" ]; do # creat the path if it exist if [ ${#VDM_PUBLIC_KEY_GLOBAL_DIR} -ge 1 ] && (whiptail --yesno "Can we create the ${VDM_PUBLIC_KEY_GLOBAL_DIR} ssh folder" \ --title "Create the Path" --backtitle "${BACK_TITLE}" 8 112); then mkdir -p "${VDM_PUBLIC_KEY_GLOBAL_DIR}" else # get the value VDM_PUBLIC_KEY_GLOBAL_DIR=$(getInput "Enter the ssh path where we can select the ssh key folders from." \ "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.ssh" 'Enter SSH Path') # keep asking if empty or does exist if [ ${#VDM_PUBLIC_KEY_GLOBAL_DIR} -ge 1 ] && [ ! -d "${VDM_PUBLIC_KEY_GLOBAL_DIR}" ] && (whiptail --yesno "Can we create the ${VDM_PUBLIC_KEY_GLOBAL_DIR} ssh folder" \ --title "Create the Path" --backtitle "${BACK_TITLE}" 8 112); then # shellcheck disable=SC2174 mkdir -p -m 700 "${VDM_PUBLIC_KEY_GLOBAL_DIR}" elif [ ${#VDM_PUBLIC_KEY_GLOBAL_DIR} -le 1 ]; then showError "You must set a ssh path where we can select the ssh key folders from." fi fi done # set the global key env string for the ssh keys VDM_ENV_PUBLIC_KEY_U_DIR="VDM_${VDM_ENV_KEY^^}_PUBLIC_KEY_DIR" VDM_PUBLIC_KEY_U_DIR=${!VDM_ENV_PUBLIC_KEY_U_DIR} # get the directory of this containers public keys if not set while [ ${#VDM_PUBLIC_KEY_U_DIR} -le 1 ] || [ ! -d "${VDM_PUBLIC_KEY_U_DIR}" ]; do # creat the path if it exist if [ ${#VDM_PUBLIC_KEY_U_DIR} -ge 1 ] && (whiptail --yesno "Can we create this (${VDM_PUBLIC_KEY_U_DIR}) containers' ssh public keys folder" \ --title "Create the Path" --backtitle "${BACK_TITLE}" 8 112); then # shellcheck disable=SC2174 mkdir -p -m 700 "${VDM_PUBLIC_KEY_U_DIR}" else # get the value VDM_PUBLIC_KEY_U_DIR=$(getSelectedDirectory "Select the containers' ssh public keys folder." \ "${VDM_PUBLIC_KEY_GLOBAL_DIR}" "${VDM_KEY,,}" 'Select Folder') # keep asking if empty or does exist if [ ${#VDM_PUBLIC_KEY_U_DIR} -ge 1 ]; then # add the parent dir back VDM_PUBLIC_KEY_U_DIR="${VDM_PUBLIC_KEY_GLOBAL_DIR}/${VDM_PUBLIC_KEY_U_DIR}" # check if this directory exist if [ ! -d "${VDM_PUBLIC_KEY_U_DIR}" ] && (whiptail --yesno "Can we create this (${VDM_PUBLIC_KEY_U_DIR}) containers' ssh public keys folder" \ --title "Create the Path" --backtitle "${BACK_TITLE}" 8 112); then # shellcheck disable=SC2174 mkdir -p -m 700 "${VDM_PUBLIC_KEY_U_DIR}" # TODO add option to add keys? fi else showError "You must set a containers' ssh public keys folder." fi fi done # we must get the project path VDM_ENV_PROJECT_DIR="VDM_${VDM_ENV_KEY^^}_PROJECT_DIR" VDM_PROJECT_U_DIR=${!VDM_ENV_PROJECT_DIR} # get the directory of the ssh public keys if not set while [ ${#VDM_PROJECT_U_DIR} -le 1 ] || [ ! -d "${VDM_PROJECT_U_DIR}" ]; do # creat the path if it exist if [ ${#VDM_PROJECT_U_DIR} -ge 1 ] && (whiptail --yesno "Can we create the ${VDM_PROJECT_U_DIR} parent mounting directory" \ --title "Create the Path" --backtitle "${BACK_TITLE}" 8 112); then mkdir -p "${VDM_PROJECT_U_DIR}" else # get the value VDM_PROJECT_U_DIR=$(getInput "Enter the parent path where we can select the folders to mount to this container." \ "${VDM_PROJECT_PATH}" 'Enter Path') # keep asking if empty or does exist if [ ${#VDM_PROJECT_U_DIR} -ge 1 ] && [ ! -d "${VDM_PROJECT_U_DIR}" ] && (whiptail --yesno "Can we create the ${VDM_PROJECT_U_DIR} parent mounting directory" \ --title "Create the Path" --backtitle "${BACK_TITLE}" 8 112); then # shellcheck disable=SC2174 mkdir -p -m 700 "${VDM_PROJECT_U_DIR}" elif [ ${#VDM_PROJECT_U_DIR} -le 1 ]; then showError "You must set a parent mounting directory path where we can select the folders to mount to this container." fi fi done # now load the mounting volumes while [ ${#VDM_MOUNT_DIRS} -le 1 ]; do # get the value VDM_MOUNT_DIRS=$(getSelectedDirectories "Select the directories to mount to this container." \ "${VDM_PROJECT_U_DIR}" 'Select Folders') # keep asking if empty or does exist if [ ${#VDM_MOUNT_DIRS} -le 1 ]; then showError "You must set the directories to mount to this container." fi done # convert the string to an array IFS=' ' read -r -a vdm_mount_dirs_array <<<"${VDM_MOUNT_DIRS[@]}" # build the mount projects VDM_VOLUMES_MOUNT=$(getYMLine3 "- \${${VDM_ENV_PUBLIC_KEY_U_DIR}}:/config/ssh_public_keys") # loop over the directories to build the for mDir in "${vdm_mount_dirs_array[@]}"; do # set the full path mDir="${mDir//\"/}" # when we mount a joomla volume we may not want to mount the database mFull="${VDM_PROJECT_U_DIR}/${mDir}/joomla" # add to mount projects if [ -d "${mFull}" ] && (whiptail --yesno "Should we ONLY mount the (joomla website files) ${mDir}/joomla directory" \ --title "Mount Joomla" --backtitle "${BACK_TITLE}" 8 112); then VDM_VOLUMES_MOUNT+=$(getYMLine3 "- \${VDM_${VDM_ENV_KEY^^}_PROJECT_DIR}/${mDir}/joomla:/app/${mDir}") else VDM_VOLUMES_MOUNT+=$(getYMLine3 "- \${VDM_${VDM_ENV_KEY^^}_PROJECT_DIR}/${mDir}:/app/${mDir}") fi done # add this value if not set variable setContainerEnvVariable "${VDM_ENV_PROJECT_DIR}=\"${VDM_PROJECT_U_DIR}\"" # add this value if not set variable setContainerEnvVariable "VDM_PUBLIC_KEY_GLOBAL_DIR=\"${VDM_PUBLIC_KEY_GLOBAL_DIR}\"" # add this value if not set variable setContainerEnvVariable "${VDM_ENV_PUBLIC_KEY_U_DIR}=\"${VDM_PUBLIC_KEY_U_DIR}\"" ########################## ### export all we need # global export VDM_CONTAINER_TYPE export VDM_REPO_PATH export VDM_PROJECT_PATH # container export VDM_PORT export VDM_USER_NAME export VDM_PUBLIC_KEY_GLOBAL_DIR export VDM_PUBLIC_KEY_U_DIR export VDM_VOLUMES_MOUNT export VDM_PROJECT_U_DIR # create the directory if it does not yet already exist # shellcheck disable=SC2174 mkdir -p -m 700 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_USER_NAME}.${VDM_DOMAIN}" # place this docker composer file in its place opensshContainer >"${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_USER_NAME}.${VDM_DOMAIN}/docker-compose.yml" # set permissions chmod 600 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${VDM_USER_NAME}.${VDM_DOMAIN}/docker-compose.yml" # saved the file showNotice "Saved ${VDM_CONTAINER_TYPE}:docker-compose.yml file.\nSetup of this container is complete!" # ask if we should continue to enable if (whiptail --yesno "Would you also like to enable this ${VDM_CONTAINER_TYPE^} container" \ --defaultno --title "Enable Container" --backtitle "${BACK_TITLE}" 8 112); then # we set the container details export VDM_CONTAINER="${VDM_USER_NAME}.${VDM_DOMAIN}" # then we enable it enableContainer "${VDM_CONTAINER_TYPE}" fi ########################## ### unset all no longer needed # container unset VDM_PORT unset VDM_USER_NAME unset VDM_KEY unset VDM_ENV_KEY unset VDM_PUBLIC_KEY_GLOBAL_DIR unset VDM_PUBLIC_KEY_U_DIR unset VDM_VOLUMES_MOUNT unset VDM_PROJECT_U_DIR # return a success return 0 } # return the Openssh Container setup yml function opensshContainer() { # we build the yml file cat < action:<${type_migration}> remote:<${remote_system}>\nIs this correct, and can we continue with the migration ${type_migration}?" \ --title "Confirm Migration ${type_migration^}" --backtitle "${BACK_TITLE}" 8 112); then # run migration "${type_migration}ContainerMigration" "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${container}" "${VDM_CONTAINER_TYPE}/available/${container}" "${remote_system}" fi else showError "Migration cancelled since no remote system was selected." fi fi fi fi } #####################################################################################################################VDM ######################################## Migrate OPENSSH function openssh__TRuST__migrate() { # check if this type have available containers if ! hasDirectories "${VDM_CONTAINER_TYPE}/available/"; then showError "The path ${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/ does not exist or is empty, first run a ${VDM_CONTAINER_TYPE^} setup." else # set some local variables local vdm_migrate_me local container # list containers... and allow selection to edit container=$(getSelectedDirectory "Select the container you would like to migrate.\n[Only continue if you know what your doing!] (We will only move docker-composer.yml file)" \ "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/" '' "Migrate ${VDM_CONTAINER_TYPE^} Container") # check that we have something, else return to main menu if [ ${#container} -ge 1 ]; then # add the parent dir back vdm_migrate_me="${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${container}/docker-compose.yml" # check if this docker-composer.yml exist if [ -f "${vdm_migrate_me}" ]; then # select the type of migration type_migration=$(getMigrationType) # get remote system remote_system=$(getRemoteSystem) # make sure we have a remote if [ ${#remote_system} -ge 2 ] && [ "${remote_system}" != "none_selected" ]; then # give little heads-up if (whiptail --yesno "${USER^}, you selected the container:<${container}> action:<${type_migration}> remote:<${remote_system}>\nIs this correct, and can we continue with the migration ${type_migration}?" \ --title "Confirm Migration ${type_migration^}" --backtitle "${BACK_TITLE}" 8 112); then # run migration "${type_migration}ContainerMigration" "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/available/${container}" "${VDM_CONTAINER_TYPE}/available/${container}" "${remote_system}" fi else showError "Migration cancelled since no remote system was selected." fi fi fi fi } #####################################################################################################################VDM ######################################## Migrate Directory function directory__TRuST__migrate() { # set some local variables local vdm_migrate_me local directory # Would you also like to move a project folder if hasDirectories '' "${VDM_PROJECT_PATH}"; then # get project directories to migrate directory=$(getSelectedDirectory "Select project directory to migrate.\n[Only continue if you know what your doing!]" \ "${VDM_PROJECT_PATH}" "Select Project Directory") # check that we have something, else return to main menu if [ ${#directory} -ge 1 ]; then # add the parent dir back vdm_migrate_me="${VDM_PROJECT_PATH}/${directory}" # check that we have something if [ -d "${vdm_migrate_me}" ]; then # select the type of migration type_migration=$(getMigrationType) # get remote system remote_system=$(getRemoteSystem) # make sure we have a remote if [ ${#remote_system} -ge 2 ] && [ "${remote_system}" != "none_selected" ]; then # give little heads-up if (whiptail --yesno "${USER^}, you selected the directory:<${directory}> action:<${type_migration}> remote:<${remote_system}>\nIs this correct, and can we continue with the migration ${type_migration}?" \ --title "Confirm Migration ${type_migration^}" --backtitle "${BACK_TITLE}" 8 112); then # run migration "${type_migration}DirectoryMigration" "${vdm_migrate_me}" "${directory}" "${remote_system}" fi else showError "Migration cancelled since no remote system was selected." fi fi fi fi } #####################################################################################################################VDM ######################################## MENU ACTIONS # setup a container function setupContainer() { # make sure the networks are set setNetworks # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="setup" # execute the task main } # setup multiple containers bulkSetupContainers() { # make sure the networks are set setNetworks # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="bulk" # execute the task main } # edit a container function editContainer() { # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="edit" # execute the task main } # enable a container function enableContainer() { # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="enable" # execute the task main } # disable a container function disableContainer() { # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="disable" # execute the task main } # down containers function downContainers() { # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="down" # execute the task main } # up containers function upContainers() { # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="up" # execute the task main } # delete a container function deleteContainer() { # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="delete" # execute the task main } # migrate a container function migrateContainer() { # make sure of our container type VDM_CONTAINER_TYPE="${1}" VDM_TASK="migrate" # execute the task main } # To fix the permissions of Joomla containers function fixContainersPermissions() { # check if we have persistent volumes if [ ! -d "${VDM_PROJECT_PATH}" ]; then showError "The ${VDM_PROJECT_PATH} does not exist." elif ! hasDirectories '' "${VDM_PROJECT_PATH}"; then showError "There are no persistent volumes in ${VDM_PROJECT_PATH} available." else # set some local variables local vdm_fix_me local persistent # get containers to enable vdm_fix_me=$(getSelectedDirectories "Select persistent volume to fix." \ "${VDM_PROJECT_PATH}" "Fix Joomla Permissions") # check that we have something, else return to main menu if [ ${#vdm_fix_me} -ge 1 ] && (whiptail --yesno "${USER^}, to fix these permissions we need sudo privileges." \ --title "Give sudo Privileges" --backtitle "${BACK_TITLE}" 8 112); then # convert the string to and array IFS=' ' read -r -a vdm_fix_me_array <<<"${vdm_fix_me[@]}" # just to get sudo pass sudo -v # loop over the directories to build the for volume in "${vdm_fix_me_array[@]}"; do # remove the " from the string persistent="${volume//\"/}" # make sure this is a joomla system if [ -d "${VDM_PROJECT_PATH}/${persistent}/joomla" ]; then # show the notice of the volume being fixed showNotice "Fixing ${persistent}/joomla permissions now." # if this is our octoleo images we can also set the user ID and user-group ID setContainerUser "$(id -u)" ### Fix the folder ownership of Joomla folders # id -u www-data # id -g www-data # progressSwitchOn { sudo chown -R "$VDM_PUID":"$VDM_PGID" "${VDM_PROJECT_PATH}/${persistent}/joomla" progressSwitchOff } & showProgress "Setting the ownership of ${persistent} Joomla directory/files.\n(chown -R $VDM_PUID:$VDM_PGID ${persistent}/joomla)" ### Fix the folder permissions for the Joomla websites # # Change the file permissions progressSwitchOn { sudo find "${VDM_PROJECT_PATH}/${persistent}/joomla" -type f -exec chmod 644 {} \; [ -f "${VDM_PROJECT_PATH}/${persistent}/joomla/configuration.php" ] && sudo chmod 444 "${VDM_PROJECT_PATH}/${persistent}/joomla/configuration.php" [ -f "${VDM_PROJECT_PATH}/${persistent}/joomla/.htaccess" ] && sudo chmod 400 "${VDM_PROJECT_PATH}/${persistent}/joomla/.htaccess" [ -f "${VDM_PROJECT_PATH}/${persistent}/joomla/php.ini" ] && sudo chmod 400 "${VDM_PROJECT_PATH}/${persistent}/joomla/php.ini" # at last we are done progressSwitchOff } & showProgress "Setting the files permissions for the ${persistent} Joomla website.\n(find ${persistent}/joomla -type f -exec chmod 644 {} \;)" # Change the folder permissions progressSwitchOn { sudo find /"home/${USER}/Projects/${persistent}/joomla" -type d -exec chmod 755 {} \; # lock jetbrains folder [ -e "${VDM_PROJECT_PATH}/${persistent}/joomla/.idea" ] && { sudo chmod -R 700 "${VDM_PROJECT_PATH}/${persistent}/joomla/.idea" # add a locking .htaccess file and set its access lockFolder >"${VDM_PROJECT_PATH}/${persistent}/joomla/.idea/.htaccess" sudo chmod 400 "${VDM_PROJECT_PATH}/${persistent}/joomla/.idea/.htaccess" } # lock hidden folder [ -e "${VDM_PROJECT_PATH}/${persistent}/joomla/.hidden" ] && { sudo chmod -R 700 "${VDM_PROJECT_PATH}/${persistent}/joomla/.hidden" # add a locking .htaccess file and set its access lockFolder >"${VDM_PROJECT_PATH}/${persistent}/joomla/.hidden/.htaccess" sudo chmod 400 "${VDM_PROJECT_PATH}/${persistent}/joomla/.hidden/.htaccess" } # lock git folder [ -e "${VDM_PROJECT_PATH}/${persistent}/joomla/.git" ] && { sudo chmod -R 700 "${VDM_PROJECT_PATH}/${persistent}/joomla/.git" # add a locking .htaccess file and set its access lockFolder >"${VDM_PROJECT_PATH}/${persistent}/joomla/.git/.htaccess" sudo chmod 400 "${VDM_PROJECT_PATH}/${persistent}/joomla/.git/.htaccess" } # lock the tmp folder [ -e "${VDM_PROJECT_PATH}/${persistent}/joomla/tmp" ] && { sudo chmod -R 700 "${VDM_PROJECT_PATH}/${persistent}/joomla/tmp" # add a locking .htaccess file and set its access # lockFolder > "${VDM_PROJECT_PATH}/${persistent}/joomla/tmp/.htaccess" # sudo chmod 400 "${VDM_PROJECT_PATH}/${persistent}/joomla/tmp/.htaccess" } # at last we are done progressSwitchOff } & showProgress "Setting the directory permissions for the ${persistent} Joomla website.\n(find ${persistent}/joomla -type d -exec chmod 755 {} \;)" # Change the image folder permissions # chmod 707 "${VDM_PROJECT_PATH}/${persistent}/joomla/images" # chmod 707 "${VDM_PROJECT_PATH}/${persistent}/joomla/images/stories" # # get current user ID user_id="$(id -u)" # check if the current user mismatch if [ "$user_id" != "$VDM_PUID" ]; then # Check if the setfacl command is installed # shellcheck disable=SC2015 if command -v setfacl >/dev/null 2>&1; then ### Fix the folder permissions so the active user (1000) can access the files progressSwitchOn { sudo setfacl -R -m u:"$USER":rwx "${VDM_PROJECT_PATH}/${persistent}/joomla" progressSwitchOff } & showProgress "Setting the permissions of ${persistent} Joomla so user:$USER can access and edit them both the files and folders." else showError "[ERROR] Could not fix the permissions of the ${persistent} Joomla so user:$USER can access them.\n\n\ You will need to install: setfacl for this option to work, then run this fix again." fi elif command -v setfacl >/dev/null 2>&1; then # we remove the acl map if it was set before sudo setfacl -R -b "${VDM_PROJECT_PATH}/${persistent}/joomla" fi ### Fix the folder ownership of database folders # id -u systemd-coredump # id -g systemd-coredump # progressSwitchOn { sudo chown -R 999:999 "${VDM_PROJECT_PATH}/${persistent}/db" progressSwitchOff } & showProgress "Setting the ownership of ${persistent} database directory/files.\n(chown -R 999:999 ${persistent}/db)" ### Fix the folder permissions for the database files # # Change the file permissions progressSwitchOn { sudo find "${VDM_PROJECT_PATH}/${persistent}/db" -type f -exec chmod 660 {} \; progressSwitchOff } & showProgress "Setting the file permissions for the ${persistent} database.\n(find ${persistent}/db -type f -exec chmod 660 {} \;)" progressSwitchOn { sudo find "${VDM_PROJECT_PATH}/${persistent}/db" -type d -exec chmod 700 {} \; progressSwitchOff } & showProgress "Setting the directory permissions for the ${persistent} database.\n(find ${persistent}/db -type d -exec chmod 700 {} \;)" # show the completion of this permission fix showNotice "Permissions update completed!" else showError "${VDM_PROJECT_PATH}/${persistent} is not a joomla persistent volume and was skipped." fi done fi fi } # delete persistent volumes function deletePersistentVolumes() { # we first check if we have some volumes if hasDirectories '' "${VDM_PROJECT_PATH}"; then # set some local variables local vdm_delete_volumes local persistent # saved the file showNotice "Only delete persistent volumes of which you have made absolutely sure it's no longer in use!" # get containers to enable vdm_delete_volumes=$(getSelectedDirectories "Select persistent volume\s to delete." \ "${VDM_PROJECT_PATH}" "Select Persistent Volume\s to Delete") # check that we have something, else return to main menu if [ ${#vdm_delete_volumes} -ge 1 ]; then # convert the string to and array IFS=' ' read -r -a vdm_delete_volumes_array <<<"${vdm_delete_volumes[@]}" # loop over the directories to build the for volumes in "${vdm_delete_volumes_array[@]}"; do # remove the " from the string persistent="${volumes//\"/}" # last serious check and then its gone if [ -d "${VDM_PROJECT_PATH}/${persistent}" ] && (whiptail --yesno "Are you absolutely sure you would like to delete this (${persistent}) persistent volume? THIS CAN'T BE UNDONE!\n(we need sudo privileges)" \ --title "Delete Persistent Volume" --backtitle "${BACK_TITLE}" 15 112); then # then remove soft link sudo rm -rf "${VDM_PROJECT_PATH}/${persistent}" fi done fi else showError "There are no persistent volumes found in ${VDM_PROJECT_PATH}." fi } # make an update function runUpdate() { # we need sudo permissions if (whiptail --yesno "${USER^}, to update ${PROGRAM_NAME} we need sudo privileges." \ --title "Give sudo Privileges" --backtitle "${BACK_TITLE}" 8 112); then # remove the current version if [ -f /usr/local/bin/octojoom ]; then # just backup in case of failure sudo mv /usr/local/bin/octojoom /usr/local/bin/octojoom.bak fi # some local values local branch # get the target branch to use in our update isExpert && branch=$(getTargetBranch) # pull the latest version. Master is always the latest if sudo curl --fail -L "https://git.vdm.dev/api/v1/repos/octoleo/octojoom/raw/src/octojoom?ref=${branch:-master}" -o /usr/local/bin/octojoom 2>/dev/null; then # give success message echo "SUCCESS: Update was successful." # do we have a backup if [ -f /usr/local/bin/octojoom.bak ]; then # lets remove it now sudo rm -f /usr/local/bin/octojoom.bak fi else # show the error echo >&2 "ERROR: Update failed! Please try again later." # do we have a backup if [ -f /usr/local/bin/octojoom.bak ]; then # move backup back sudo mv /usr/local/bin/octojoom.bak /usr/local/bin/octojoom fi fi # always set the permission again if we have a file if [ -f /usr/local/bin/octojoom ]; then # the we make sure its executable sudo chmod +x /usr/local/bin/octojoom fi # always exit so the new script can load quitProgram fi } # make an uninstall function runUninstall() { # little notice showNotice "We have three uninstall options: 1) Complete. 2) Just Script. 3) Make your selection." # first safety check if (whiptail --yesno "We are just trying to be careful here, therefore we have a few questions. Please remember there is an auto update option!\n\ Are you absolutely sure you want to continue with the uninstalling process?\n(we need sudo privileges)" \ --defaultno --title "Uninstalling ${PROGRAM_NAME} v${_VERSION} " --backtitle "${BACK_TITLE}" 15 112); then # we have four main options if (whiptail --yesno "Okay, So do you want to completely remove everything? This is the most dangerous option! IT CAN'T BE UNDONE!\ We will remove literally everything like this script was never here." \ --defaultno --title "Complete Uninstall of ${PROGRAM_NAME} v${_VERSION} " --backtitle "${BACK_TITLE}" 12 112); then if [ -d "${VDM_REPO_PATH}/joomla" ]; then # take down all joomla containers downContainers 'joomla' # now completely remove Joomla configurations sudo rm -fr "${VDM_REPO_PATH}/joomla" fi # Remove all of Openssh Docker stuff if [ -d "${VDM_REPO_PATH}/openssh" ]; then # take down all joomla containers downContainers 'openssh' # now completely remove Joomla configurations sudo rm -fr "${VDM_REPO_PATH}/openssh" fi # Remove all of Portainer Docker stuff if [ -d "${VDM_REPO_PATH}/portainer" ]; then # take down all portainer containers disableContainer 'portainer' # now completely remove Portainer configurations sudo rm -fr "${VDM_REPO_PATH}/portainer" fi # Remove all of Traefik Docker stuff if [ -d "${VDM_REPO_PATH}/traefik" ]; then # take down all traefik containers disableContainer 'traefik' # now completely remove Traefik configurations sudo rm -fr "${VDM_REPO_PATH}/traefik" fi # remove config if found if [ -d "${VDM_SRC_PATH}" ]; then sudo rm -fr "${VDM_SRC_PATH}" fi # remove config if found if [ -d "${VDM_REPO_PATH}" ]; then sudo rm -fr "${VDM_REPO_PATH}" fi elif (whiptail --yesno "Would you like to only remove the actual ${PROGRAM_NAME} v${_V} script but keep all the Docker stuff?" \ --title "Keep Docker Stuff" --backtitle "${BACK_TITLE}" 8 112); then # now remove the octojoom script if [ -f /usr/local/bin/octojoom ]; then sudo rm -f /usr/local/bin/octojoom fi # give the final message whiptail --title "${PROGRAM_NAME} v${_VERSION} is UNINSTALLED" --msgbox "\n\ ${PROGRAM_NAME} v${_V} has been uninstalled.\n\n\ We have not touched any of the Docker stuff.\n\ To also remove these you will have to manually pull down the Docker containers (if there are any running) and then remove the following directories.\n\n\ DOCKER: ${VDM_REPO_PATH} \n\ VOLUMES: ${VDM_PROJECT_PATH} \n\ CONFIG: ${VDM_SRC_PATH}" --backtitle "${BACK_TITLE}" 20 112 # we exit here... where done! quitProgram else # little notice showNotice "This option lets you choose what you keep, so read the questions carefully! IT CAN'T BE UNDONE!" # shellcheck disable=SC1090 # ----------------------------------------------------- MULTI CONTAINERS # Remove all of Joomla Docker stuff if [ -d "${VDM_REPO_PATH}/joomla" ] && (whiptail --yesno "Would you like to completely remove the Joomla Docker yaml configurations in (${VDM_REPO_PATH}/joomla)?" \ --defaultno --title "Remove Docker Config" --backtitle "${BACK_TITLE}" 12 112); then # take down all joomla containers downContainers 'joomla' # now completely remove Joomla configurations sudo rm -fr "${VDM_REPO_PATH}/joomla" fi # check if we have possible joomla containers (this will only run if they did not remove it above) if [ -e "${VDM_REPO_PATH}/joomla/enabled" ] && (whiptail --yesno "Would you like to take down Joomla containers?" \ --defaultno --title "Take Down Containers" --backtitle "${BACK_TITLE}" 8 112); then # take down all joomla containers downContainers 'joomla' # remove all enabled sudo rm -fr "${VDM_REPO_PATH}/joomla/enabled" fi # Remove all of Openssh Docker stuff if [ -d "${VDM_REPO_PATH}/openssh" ] && (whiptail --yesno "Would you like to completely remove the Openssh Docker yaml configurations in (${VDM_REPO_PATH}/openssh)?" \ --defaultno --title "Remove Docker Config" --backtitle "${BACK_TITLE}" 12 112); then # take down all joomla containers downContainers 'openssh' # now completely remove Joomla configurations sudo rm -fr "${VDM_REPO_PATH}/openssh" fi # check if we have possible openssh containers (this will only run if they did not remove it above) if [ -e "${VDM_REPO_PATH}/openssh/enabled" ] && (whiptail --yesno "Would you like to take down Openssh containers?" \ --defaultno --title "Take Down Containers" --backtitle "${BACK_TITLE}" 8 112); then # take down all joomla containers downContainers 'openssh' # remove all enabled sudo rm -fr "${VDM_REPO_PATH}/openssh/enabled" fi # ----------------------------------------------------- SINGLE CONTAINER # Remove all of Portainer Docker stuff if [ -d "${VDM_REPO_PATH}/portainer" ] && (whiptail --yesno "Would you like to completely remove the Portainer Docker yaml configurations in (${VDM_REPO_PATH}/portainer)?" \ --defaultno --title "Remove Docker Config" --backtitle "${BACK_TITLE}" 12 112); then # take down all portainer containers disableContainer 'portainer' # now completely remove Portainer configurations sudo rm -fr "${VDM_REPO_PATH}/portainer" fi # check if we have possible portainer container (this will only run if they did not remove it above) if [ -f "${VDM_REPO_PATH}/portainer/docker-compose.yml" ] && (whiptail --yesno "Would you like to take down Portainer container?" \ --defaultno --title "Take Down Container" --backtitle "${BACK_TITLE}" 8 112); then # take down the container disableContainer 'portainer' fi # Remove all of Traefik Docker stuff if [ -d "${VDM_REPO_PATH}/traefik" ] && (whiptail --yesno "Would you like to completely remove the Traefik Docker yaml configurations in (${VDM_REPO_PATH}/traefik)?" \ --defaultno --title "Remove Docker Config" --backtitle "${BACK_TITLE}" 12 112); then # take down all traefik containers disableContainer 'traefik' # now completely remove Traefik configurations sudo rm -fr "${VDM_REPO_PATH}/traefik" fi # check if we have possible traefik container (this will only run if they did not remove it above) if [ -f "${VDM_REPO_PATH}/traefik/docker-compose.yml" ] && (whiptail --yesno "Would you like to take down Traefik container?" \ --defaultno --title "Take Down Container" --backtitle "${BACK_TITLE}" 8 112); then # take down the container disableContainer 'traefik' fi # remove config if found if [ -d "${VDM_SRC_PATH}" ] && (whiptail --yesno "Would you like to remove the global docker configurations?" \ --defaultno --title "Remove Docker Config" --backtitle "${BACK_TITLE}" 8 112); then sudo rm -fr "${VDM_SRC_PATH}" fi fi # now remove the octojoom script if [ -f /usr/local/bin/octojoom ]; then sudo rm -f /usr/local/bin/octojoom fi # last and final question as this is most dangerous and serious issue. if [ -d "${VDM_PROJECT_PATH}" ] && (whiptail --yesno "Last question, would you like to remove all persistent volumes of your containers in (${VDM_PROJECT_PATH})?" \ --defaultno --title "Completely DELETE persistent volumes" --backtitle "${BACK_TITLE}" 12 112); then sudo rm -rf "${VDM_PROJECT_PATH}" fi # give the final message whiptail --title "${PROGRAM_NAME} v${_VERSION} is UNINSTALLED" --msgbox "\n\n\ ${PROGRAM_NAME} v${_V} has been uninstalled." --backtitle "${BACK_TITLE}" 10 112 # exit the program right now quitProgram fi } # we show the octoleo info quietly octojoomQuietly() { local message # now set the message message=" https://git.vdm.dev/octoleo/octojoom Welcome to an Octoleo Program (${PROGRAM_NAME} v$_VERSION) Description With this script we can easily deploy docker containers of Joomla and Openssh. This combination of these tools give rise to a powerful and very secure shared development environment. This program has **command input** options as seen in the menus item called **Show command help**, but these commands are _not the only way_ to set these values. When the values are **omitted** you will be _asked in the terminal_ to manually enter the required values as needed. Furthermore, the use of **env variables** are also heavily used across the script. There are more than one .env file and the script will set those up for you whenever you run a task that make use of env variables. The script will check if those values exist, and if they don't it will ask for them, and store them automatically for future use. That same time the output message to the terminal will show you where the specific .env file can be found. Author Licence Llewellyn van der Merwe GNU GENERAL PUBLIC LICENSE Version 2 --quiet" whiptail --msgbox "${message}" --fb --backtitle "${BACK_TITLE}" 32 112 } # show the commands help menu function showCommandsHelpMenu() { help=$(showHelp) whiptail --msgbox --scrolltext "${help}" --fb --backtitle "${BACK_TITLE}" 30 90 } #####################################################################################################################VDM ######################################## MENUS # show Joomla menu function showJoomla() { # menu for dynamic addition local menu_options=() # our counter local i=3 # load the back menu menu_options+=("back" "<-- Return to the main menu.") # setup new container menu_options+=("setup" "Setup new container") # enable existing container hasDirectories 'joomla/available' && menu_options+=("enable" "Enable existing container") && i=$((i + 1)) # disable enabled container hasDirectories 'joomla/enabled' && menu_options+=("disable" "Disable enabled container") && i=$((i + 1)) # take all enabled containers down hasDirectories 'joomla/enabled' && menu_options+=("down" "Take all enabled containers down") && i=$((i + 1)) # pull up all enabled containers hasDirectories 'joomla/enabled' && menu_options+=("up" "Pull up all enabled containers") && i=$((i + 1)) # edit available container isExpert && hasDirectories 'joomla/available' && menu_options+=("edit" "Edit available container") && i=$((i + 1)) # fix permissions hasDirectories '' "${VDM_PROJECT_PATH}" && menu_options+=("fix" "Fix permissions of folders and files of a container") && i=$((i + 1)) # delete a container hasDirectories 'joomla/available' && menu_options+=("delete" "Delete a container") && i=$((i + 1)) # Get Joomla env details isExpert && hasDirectories 'joomla/available' && menu_options+=("joomla_env" "Open Joomla .env Details") && i=$((i + 1)) # bulk deploy isExpert && menu_options+=("bulk" "Bulk deploy") && i=$((i + 1)) # Quit Octoleo Program menu_options+=("quit" "Quit ${PROGRAM_NAME}") # get the selection CHOICE=$( whiptail --menu "Make your selection" 20 112 $i \ --title "Joomla | ${PROGRAM_NAME} v${_V}" --fb \ --backtitle "${BACK_TITLE}" --nocancel --notags \ "${menu_options[@]}" 3>&2 2>&1 1>&3 ) # menu actions case $CHOICE in "setup") setupContainer 'joomla' ;; "edit") editContainer 'joomla' ;; "enable") enableContainer 'joomla' ;; "disable") disableContainer 'joomla' ;; "down") downContainers 'joomla' ;; "up") upContainers 'joomla' ;; "fix") fixContainersPermissions ;; "delete") deleteContainer 'joomla' ;; "joomla_env") openEnv 'joomla' ;; "bulk") bulkSetupContainers 'joomla' ;; "quit") quitProgram ;; esac # menu loop case $CHOICE in "setup" | "edit" | "enable" | "disable" | "down" | "up" | "fix" | "delete" | "joomla_env" | "bulk") showJoomla ;; esac } # show Openssh menu function showOpenssh() { # menu for dynamic addition local menu_options=() # our counter local i=3 # load the back menu menu_options+=("back" "<-- Return to the main menu.") # setup new container menu_options+=("setup" "Setup new container") # enable existing container hasDirectories 'openssh/available' && menu_options+=("enable" "Enable existing container") && i=$((i + 1)) # disable enabled container hasDirectories 'openssh/enabled' && menu_options+=("disable" "Disable enabled container") && i=$((i + 1)) # take all enabled containers down hasDirectories 'openssh/enabled' && menu_options+=("down" "Take all enabled containers down") && i=$((i + 1)) # pull up all enabled containers hasDirectories 'openssh/enabled' && menu_options+=("up" "Pull up all enabled containers") && i=$((i + 1)) # edit available container isExpert && hasDirectories 'openssh/available' && menu_options+=("edit" "Edit available container") && i=$((i + 1)) # delete a container hasDirectories 'openssh/available' && menu_options+=("delete" "Delete a container") && i=$((i + 1)) # Get Openssh env details isExpert && hasDirectories 'openssh/available' && menu_options+=("openssh_env" "Open Openssh .env Details") && i=$((i + 1)) # Quit Octoleo Program menu_options+=("quit" "Quit ${PROGRAM_NAME}") # get the selection CHOICE=$( whiptail --menu "Make your selection" 20 112 $i \ --title "Openssh | ${PROGRAM_NAME} v${_V}" --fb \ --backtitle "${BACK_TITLE}" --nocancel --notags \ "${menu_options[@]}" 3>&2 2>&1 1>&3 ) case $CHOICE in "setup") setupContainer 'openssh' ;; "edit") editContainer 'openssh' ;; "enable") enableContainer 'openssh' ;; "disable") disableContainer 'openssh' ;; "down") downContainers 'openssh' ;; "up") upContainers 'openssh' ;; "delete") deleteContainer 'openssh' ;; "openssh_env") openEnv 'openssh' ;; "quit") quitProgram ;; esac # menu loop case $CHOICE in "setup" | "edit" | "enable" | "disable" | "down" | "up" | "delete" | "openssh_env" ) showOpenssh ;; esac } # show Traefik menu function showTraefik() { # menu for dynamic addition local menu_options=() # our counter local i=3 # load the back menu menu_options+=("back" "<-- Return to the main menu.") && i=$((i + 1)) # setup new container menu_options+=("setup" "Setup or rebuild Traefik") # enable existing container [ -f "${VDM_REPO_PATH}/traefik/docker-compose.yml" ] && menu_options+=("enable" "Enable Traefik") && i=$((i + 1)) # disable enabled container [ -f "${VDM_REPO_PATH}/traefik/docker-compose.yml" ] && menu_options+=("disable" "Disable Traefik") && i=$((i + 1)) # edit available container isExpert && [ -f "${VDM_REPO_PATH}/traefik/docker-compose.yml" ] && menu_options+=("edit" "Edit Traefik") && i=$((i + 1)) # delete traefik container [ -f "${VDM_REPO_PATH}/traefik/docker-compose.yml" ] && menu_options+=("delete" "Delete Traefik") && i=$((i + 1)) # Quit Octoleo Program menu_options+=("quit" "Quit ${PROGRAM_NAME}") # get the selection CHOICE=$( whiptail --menu "Make your selection" 20 112 $i \ --title "Traefik | ${PROGRAM_NAME} v${_V}" --fb \ --backtitle "${BACK_TITLE}" --nocancel --notags \ "${menu_options[@]}" 3>&2 2>&1 1>&3 ) # menu actions case $CHOICE in "setup") setupContainer 'traefik' ;; "edit") editContainer 'traefik' ;; "enable") enableContainer 'traefik' ;; "disable") disableContainer 'traefik' ;; "delete") deleteContainer 'traefik' ;; "quit") quitProgram ;; esac # menu loop case $CHOICE in "setup" | "edit" | "enable" | "disable" | "delete") showTraefik ;; esac } # show Portainer menu function showPortainer() { # menu for dynamic addition local menu_options=() # our counter local i=3 # load the back menu menu_options+=("back" "<-- Return to the main menu.") # setup new container menu_options+=("setup" "Setup or rebuild Portainer") # enable existing container [ -f "${VDM_REPO_PATH}/portainer/docker-compose.yml" ] && menu_options+=("enable" "Enable Portainer") && i=$((i + 1)) # disable enabled container [ -f "${VDM_REPO_PATH}/portainer/docker-compose.yml" ] && menu_options+=("disable" "Disable Portainer") && i=$((i + 1)) # edit available container isExpert && [ -f "${VDM_REPO_PATH}/portainer/docker-compose.yml" ] && menu_options+=("edit" "Edit Portainer") && i=$((i + 1)) # delete traefik container [ -f "${VDM_REPO_PATH}/portainer/docker-compose.yml" ] && menu_options+=("delete" "Delete Portainer") && i=$((i + 1)) # Quit Octoleo Program menu_options+=("quit" "Quit ${PROGRAM_NAME}") # get the selection CHOICE=$( whiptail --menu "Make your selection" 20 112 $i \ --title "Portainer | ${PROGRAM_NAME} v${_V}" --fb \ --backtitle "${BACK_TITLE}" --nocancel --notags \ "${menu_options[@]}" 3>&2 2>&1 1>&3 ) # menu actions case $CHOICE in "setup") setupContainer 'portainer' ;; "edit") editContainer 'portainer' ;; "enable") enableContainer 'portainer' ;; "disable") disableContainer 'portainer' ;; "delete") deleteContainer 'portainer' ;; "quit") quitProgram ;; esac # menu loop case $CHOICE in "setup" | "edit" | "enable" | "disable" | "delete") showPortainer ;; esac } # show systems help menu function showSettings() { # menu for dynamic addition local menu_options=() # our counter local i=8 # load the back menu menu_options+=("back" "<-- Return to the main menu.") # the mode switch isExpert && menu_options+=("basic-mode" "Switch to basic mode") ! isExpert && menu_options+=("expert-mode" "Switch to expert mode") i=$((i + 1)) # Show command help menu_options+=("command-help" "Help with commands") # Show folder paths menu_options+=("important-paths" "Important paths") # Edit the global config isExpert && [ -f "${VDM_SRC_PATH}/.env" ] && menu_options+=("edit-config" "Edit Config") && i=$((i + 1)) # the remote-control option isExpert && menu_options+=("remote" "Access Remote ${PROGRAM_NAME}") && i=$((i + 1)) # Report an Issue menu_options+=("report-issue" "Report an issue") # Update the whole script menu_options+=("update" "Update ${PROGRAM_NAME,,}") # Uninstall the whole script menu_options+=("uninstall" "Uninstall ${PROGRAM_NAME,,}") # Octoleo details menu_options+=("octojoom" "${PROGRAM_NAME}") # Quit Octoleo Program menu_options+=("quit" "Quit ${PROGRAM_NAME}") # get the selection CHOICE=$( whiptail --menu "Make your selection" 20 112 $i \ --title "Settings Menu | ${PROGRAM_NAME} v${_V}" --fb \ --backtitle "${BACK_TITLE}" --nocancel --notags \ "${menu_options[@]}" 3>&2 2>&1 1>&3 ) # menu actions case $CHOICE in "basic-mode") setMode 'basic' ;; "expert-mode") setMode 'expert' ;; "command-help") showCommandsHelpMenu ;; "important-paths") showImportantPaths ;; "edit-config") editConfigFile ;; "remote") hasRemoteSystemSet && connectToRemoteSystem ;; "report-issue") showLink "https://git.vdm.dev/octoleo/octojoom/issues" "To report an issue go to:" "Report an Issue" ;; "update") runUpdate ;; "uninstall") runUninstall ;; "octojoom") octojoomQuietly ;; "quit") quitProgram ;; esac # menu loop case $CHOICE in "basic-mode" | "expert-mode" | "command-help" | "important-paths" | "edit-config" | "report-issue" | "octojoom" | "remote") showSettings ;; esac } # show domains menu function domainsMenu() { # menu for dynamic addition local menu_options=() # our counter local i=3 # load the back menu menu_options+=("back" "<-- Return to the main menu.") # add more domains allowMultiDomains && menu_options+=("add" "Add Domain") && i=$((i + 1)) # remove existing domains [ -f "${VDM_SRC_PATH}/.domains" ] && menu_options+=("delete" "Delete Domain") && i=$((i + 1)) # edit available container isExpert && allowEditHostFile && menu_options+=("edit" "Edit Host File") && i=$((i + 1)) # Quit Octoleo Program menu_options+=("quit" "Quit ${PROGRAM_NAME}") # get the selection CHOICE=$( whiptail --menu "Make your selection" 20 112 $i \ --title "Domains Menu | ${PROGRAM_NAME} v${_V}" --fb \ --backtitle "${BACK_TITLE}" --nocancel --notags \ "${menu_options[@]}" 3>&2 2>&1 1>&3 ) # menu actions case $CHOICE in "add") setMultiDomains ;; "delete") deleteMultiDomains ;; "edit") editHostFile ;; "quit") quitProgram ;; esac # menu loop case $CHOICE in "add" | "delete" | "edit") domainsMenu ;; esac } # show migration menu function migrateMenu() { # menu for dynamic addition local menu_options=() # our counter local i=4 # load the back menu menu_options+=("back" "<-- Return to the main menu.") # Select a joomla container to migrate menu_options+=("joomla" "Migrate Joomla Container") # Select a openssh container to migrate menu_options+=("openssh" "Migrate Openssh Container") # Select project directories to migrate hasDirectories '' "${VDM_PROJECT_PATH}" && menu_options+=("directory" "Migrate Project Directory") && i=$((i + 1)) # the remote-control option isExpert && menu_options+=("remote" "Access Remote ${PROGRAM_NAME}") && i=$((i + 1)) # Quit Octoleo Program menu_options+=("quit" "Quit ${PROGRAM_NAME}") # get the selection CHOICE=$( whiptail --menu "Make your selection" 20 112 $i \ --title "Migration Menu | ${PROGRAM_NAME} v${_V}" --fb \ --backtitle "${BACK_TITLE}" --nocancel --notags \ "${menu_options[@]}" 3>&2 2>&1 1>&3 ) # menu actions case $CHOICE in "joomla") migrateContainer 'joomla' ;; "openssh") migrateContainer 'openssh' ;; "directory") migrateContainer 'directory' ;; "remote") connectToRemoteSystem ;; "quit") quitProgram ;; esac # menu loop case $CHOICE in "joomla" | "openssh" | "directory" | "remote") migrateMenu ;; esac } # MAIN MENU function mainMenu() { # get the selection while true; do # menu for dynamic addition local menu_options=() # our counter local i=6 # Joomla containers menu_options+=("joomla" "Joomla Containers") # Openssh containers menu_options+=("openssh" "Openssh Containers") # Traefik container menu_options+=("traefik" "Traefik Container") # Portainer container menu_options+=("portainer" "Portainer Container") # Manage Domains if isExpert && (allowEditHostFile || allowMultiDomains); then menu_options+=("domains" "Manage Domains") i=$((i + 1)) fi # Migrate available container isExpert && menu_options+=("migrate" "Migration Options") && i=$((i + 1)) # Delete Persistent Volumes isExpert && hasDirectories '' "${VDM_PROJECT_PATH}" && menu_options+=("delete" "Delete Persistent Volumes") && i=$((i + 1)) # Octoleo settings menu_options+=("settings" "${PROGRAM_NAME} Settings") # Quit Octoleo Program menu_options+=("quit" "Quit ${PROGRAM_NAME}") CHOICE=$( whiptail --menu "Make your selection" 20 112 $i \ --title "${PROGRAM_NAME} v${_V}" --fb \ --backtitle "${BACK_TITLE}" --nocancel --notags \ "${menu_options[@]}" 3>&2 2>&1 1>&3 ) case $CHOICE in "joomla") showJoomla ;; "openssh") showOpenssh ;; "traefik") showTraefik ;; "portainer") showPortainer ;; "domains") domainsMenu ;; "migrate") hasRemoteSystemSet && migrateMenu ;; "delete") deletePersistentVolumes ;; "settings") showSettings ;; "quit") quitProgram ;; esac done } #####################################################################################################################VDM ######################################## GENERAL SHARED FUNCTIONS # show error function showError() { whiptail --title "ERROR | ${PROGRAM_NAME} v${_V}" --backtitle "${BACK_TITLE}" \ --msgbox "${1}" "${2:-12}" 112 } # show info function showInfo() { whiptail --title "INFO | ${PROGRAM_NAME} v${_V}" --backtitle "${BACK_TITLE}" \ --infobox "${1}" "${2:-12}" 112 } # show notice function showNotice() { whiptail --title "NOTICE | ${PROGRAM_NAME} v${_V}" --backtitle "${BACK_TITLE}" \ --msgbox "${1}" "${2:-12}" 112 } # show link function showLink() { whiptail --title "${3:-Open Link} | ${PROGRAM_NAME} v${_V}" --backtitle "${BACK_TITLE}" \ --msgbox "${2:-To open the link click here:} ${1}" 8 112 } # show progress bar function showProgress() { # some locals local title local message local per # get input message="$1" title="${2:-Please wait!}" # our little loop progress { per=1 while (true); do if ! inProgress; then # we are finished, so we slow down to 100% sleep .2 echo 88 sleep .2 echo 90 sleep .$((1 + "$RANDOM" % 6)) echo 98 sleep .$((1 + "$RANDOM" % 5)) echo 100 sleep .$((1 + "$RANDOM" % 5)) break elif [[ "87" -eq "$per" ]]; then # if it takes longer we loop per="33" fi # sleep a little moment sleep .$((1 + "$RANDOM" % 9)) echo $per per=$((per + 1)) done } | whiptail --title "${title}" --gauge "${message}" --backtitle "${BACK_TITLE}" 8 112 0 } # turn on progress function progressSwitchOn() { echo "Progress ON" >"${VDM_SRC_PATH}/.progress" } # switch off the progress function progressSwitchOff() { rm -f "${VDM_SRC_PATH}/.progress" } # get the progress function inProgress() { if [ -f "${VDM_SRC_PATH}/.progress" ]; then # found it return 0 fi # did not find it return 1 } # show important paths function showImportantPaths() { local message # now set the message message=" ${PROGRAM_NAME} v$_VERSION We have a few important paths that you should know, and use to manually manage your setup. DOCKER: ${VDM_REPO_PATH} SOURCE: ${VDM_SRC_PATH}/.images-source DOMAINS: ${VDM_SRC_PATH}/.domains CONFIG: ${VDM_SRC_PATH}/.env SSH: ${VDM_REPO_PATH}/openssh/.ssh VOLUMES: ${VDM_PROJECT_PATH} Then we have some key environment variable files that hold very useful and important information. Since we do not store any passwords or other important details in the docker-compose.yml files they are stored in >>these environment variable files<< and therefore we set these .env file permission to (600) for security. JOOMLA: ${VDM_REPO_PATH}/joomla/.env OPENSSH: ${VDM_REPO_PATH}/openssh/.env TRAEFIK: ${VDM_REPO_PATH}/traefik/.env PORTAINER: ${VDM_REPO_PATH}/portainer/.env Not all these files or folders may exist, they are created only when needed. " whiptail --msgbox "${message}" --fb --backtitle "${BACK_TITLE}" 32 112 } # show show the Joomla config details function showJoomlaConfigDetails() { # load the env back to show if set previously # shellcheck disable=SC1090 # [ -f "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" ] && source "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" # set some locals local message # now set the message message=" ${PROGRAM_NAME} v$_VERSION Running the Joomla container for the first time you will need these details: -------------------------------------------------------------------- URL: ${VDM_HTTP_SCHEME}${VDM_SUBDOMAIN}.${VDM_DOMAIN} DATABASE_HOST: mariadb${VDM_KEY}:3306 -------------------------------------------------------------------- VDM_${VDM_ENV_KEY^^}_DB: ${vdm_database_name} VDM_${VDM_ENV_KEY^^}_DB_USER: ${vdm_database_user} VDM_${VDM_ENV_KEY^^}_DB_PASS: ${vdm_database_pass} VDM_${VDM_ENV_KEY^^}_DB_ROOT: ${vdm_database_rootpass} -------------------------------------------------------------------- These details are securely stored in this environment variable file ${VDM_REPO_PATH}/joomla/.env -------------------------------------------------------------------- Do not remove these details from the file, as it's used to deploy the container. " whiptail --msgbox "${message}" --fb --backtitle "${BACK_TITLE}" 30 112 } # we must get a random key function getRandomPass() { local length="${1:-128}" tr -dc 'A-HJ-NP-Za-km-z2-9' &1 1>&2 2>&3) # return the answer echo "${answer}" } # get the input until is gotten function getInputNow() { local message local default local title local answer="" # get the values message="${1:-Enter a value}" default="${2:-}" title="${3:-value}" # get the value if not already set while [ -z "$answer" ] || [ ${#answer} -le 1 ]; do # get the value answer=$(getInput "${message}" \ "${default}" "${title}") # keep asking if empty or does exist if [ ${#answer} -le 1 ]; then showError "You must enter a $title" fi done # return the answer echo "${answer}" } # get a selected directory function getSelectedDirectory() { # the selected folder local answer # set the local path local path="${2:-}" # set local var local folder="${3:-}" # we start the selection array local selected=() # our counter local i=0 # whiptail box size details local description_lines local height # now check if this dir has sub dirs if hasDirectories '' "$path"; then # loop over the directory for dir_ in "${path%/}/"*; do # remove the full path dir_="${dir_/$path/}" # load the selection [ "$folder" = "${dir_#/}" ] && selected+=("${dir_#/}" "${dir_#/}" "ON") || selected+=("${dir_#/}" "${dir_#/}" "OFF") # increment our pointer if [ "$i" -le 10 ]; then i=$((i + 1)) fi done # Calculate the height based on the description length and directory count description_lines=$(echo -e "${1}" | wc -l) height=$((description_lines + i + 7)) # now make the selection answer=$(whiptail --title "${4:-Make a Selection}" --radiolist \ "${1}" $height 112 $i \ "${selected[@]}" --backtitle "${BACK_TITLE}" --nocancel --notags 3>&1 1>&2 2>&3) fi # return the answer echo "${answer:-$folder}" } # select multiple directories from one directory function getSelectedDirectories() { # the selected folder local answer # set the local path local path="${2:-}" # we start the selection array local selected=() # our counter local i=0 # whiptail box size details local description_lines local height # now check if this dir has sub dirs if hasDirectories '' "$path"; then # loop over the directory for dir_ in "${path%/}/"*; do # remove the full path dir_="${dir_/$path/}" # load the selection selected+=("${dir_#/}" "${dir_#/}" "OFF") # increment our pointer if [ "$i" -le 10 ]; then i=$((i + 1)) fi done # Calculate the height based on the description length and directory count description_lines=$(echo -e "${1}" | wc -l) height=$((description_lines + i + 7)) # now make the selection answer=$(whiptail --title "${3:-Make a Selection}" --checklist\ "${1}" $height 112 $i \ "${selected[@]}" --backtitle "${BACK_TITLE}" --nocancel --notags 3>&1 1>&2 2>&3) # return the answer echo "${answer[@]}" else echo '' fi } # create a yml new line with 3 indents function getYMLine3() { # return line cat <&1 1>&2 2>&3) # return the password echo "${PASSWORD}" } # get the target branch to use in update function getTargetBranch() { # now make the selection answer=$(whiptail --title "Select Update Channel" \ --radiolist --backtitle "${BACK_TITLE}" --nocancel --notags \ "You can select the update channel you would like to use." 10 80 2 \ "master" "Stable Version" "ON" \ "staging" "Developer Version" "OFF" \ 3>&1 1>&2 2>&3) # return the answer (default master) echo "${answer:-master}" } # get the target repository to use in update function getImageSource() { # we set some local stuff local image local image_source local images_source local default local default_octojoom # menu for dynamic addition local menu_options=() # our counter local i=2 # set the defaults default="joomla|https://hub.docker.com/_/joomla?tab=tags;https://raw.githubusercontent.com/joomla-docker/docker-joomla/master/docker-entrypoint.sh" default_octojoom="llewellyn/joomla|https://hub.docker.com/r/llewellyn/joomla/tags;https://git.vdm.dev/octoleo/docker-joomla/raw/branch/octoleo/docker-entrypoint.sh" # we only ask if we are in expert mode if isExpert; then # set the base images source menu_options+=("${default}" "Joomla official image" "OFF") menu_options+=("${default_octojoom}" "Octoleo Joomla image" "OFF") # get existing source if [ -f "${VDM_SRC_PATH}/.images-source" ] && readarray -t images_source <"${VDM_SRC_PATH}/.images-source"; then # build the menus if [ "${#images_source[@]}" -gt 0 ]; then # loop source images for image in "${images_source[@]}"; do # the image name (get before first comma) image_name="${image%%,*}" # the image description (get after last comma) image_desc="${image##*,}" # make sure we have a value, and it is not the defaults already set if [ "${#image_name}" -ge 1 ] && [ "${#image_desc}" -ge 1 ] && [ "${image_name}" != "${default}" ] && [ "${image_name}" != "${default_octojoom}" ]; then # load the source name and description menu_options+=("${image_name}" "${image_desc}" "OFF") # increment our counter i=$((i + 1)) fi done fi fi # now make the selection image_source=$(whiptail --title "Select image source repository" \ --radiolist --backtitle "${BACK_TITLE}" --nocancel --notags \ "You can select the source of your Joomla! image." 10 80 $i \ "${menu_options[@]}" 3>&1 1>&2 2>&3) fi # return the answer (default joomla) echo "${image_source:-$default}" } # get a list of domains locally set function getListDomains() { # get the separator local separator="${1}" # menu for dynamic addition local menu_options=() local domains # get existing domains if [ -f "${VDM_SRC_PATH}/.domains" ] && readarray -t domains <"${VDM_SRC_PATH}/.domains"; then # check that we have values if [ "${#domains[@]}" -gt 0 ]; then # build the menu for domain in "${domains[@]}"; do # load the back menu [ "${#domain}" -ge 1 ] && menu_options+=("${domain}") done # we show a list of options including the option to add another # shellcheck disable=SC2005 echo "$(printf "${separator}%s" "${menu_options[@]}")" fi fi } # to set a global multiple domains file and values function getMultiDomain() { # we set some local stuff local domain_name local domains # menu for dynamic addition local menu_options=() # our counter local i=0 # always add the main domain or (default) first # so we should always have a selection saveMultiDomain "${VDM_DOMAIN}" # now clear the main domain (in case we use getDomain) unset VDM_DOMAIN # get existing domains if [ -f "${VDM_SRC_PATH}/.domains" ] && readarray -t domains <"${VDM_SRC_PATH}/.domains"; then # build the menu if [ "${#domains[@]}" -gt 0 ]; then # loop domains for domain in "${domains[@]}"; do # load the domain if it has a length [ "${#domain}" -ge 1 ] && menu_options+=("${domain}" "${domain}" "OFF") # increment our counter [ "${#domain}" -ge 1 ] && i=$((i + 1)) done # make sure we have domains if [ "${#menu_options[@]}" -gt 0 ]; then # we show a list of options including the option to add another domain_name=$(whiptail --title "Select a Domain" \ --radiolist --backtitle "${BACK_TITLE}" --nocancel --notags \ "Select a domain, or none to add another." 30 80 $i \ "${menu_options[@]}" 3>&1 1>&2 2>&3) fi fi fi # check if we have a selection if [ "${domain_name:-none}" = 'none' ]; then # we must let them enter the new domain name getDomain 'Enter a domain name' 'Enter a Domain Name' else # set the new main domain VDM_DOMAIN="${domain_name}" # update the main domain export VDM_DOMAIN fi # add for next time saveMultiDomain "${VDM_DOMAIN}" } # set the VDM domain value function getDomain() { # set some locals local message="Enter main domain name" local title="Enter Main Domain" local tld="${VDM_CONTAINER_TYPE:-vdm}" # get the arg passed if any message="${1:-$message}" title="${2:-$title}" # get the VDM Domain value if not already set while [ ${#VDM_DOMAIN} -le 1 ] || [[ "${VDM_DOMAIN}" != *.* ]]; do # get the value VDM_DOMAIN=$(getInput "${message}\n[must have at least one dot]" \ "${USER:-octoleo}.${tld,,}" "${title}") # keep asking if empty or does exist if [ ${#VDM_DOMAIN} -le 1 ]; then showError "You must enter a domain name!" elif [[ "${VDM_DOMAIN}" != *.* ]]; then showError "You must enter a domain name with at least one dot" fi done # update the main domain export VDM_DOMAIN } # set the main domain function getMainDomain() { # we first check if we have a main domain passed as command argument if $VDM_ARG_DOMAIN; then # if domain passed by arg we just return it getDomain 'Enter a domain name' 'Enter a Domain Name' else # update the main domain with a selection getMultiDomain fi } # set the container user function setContainerUser() { # ask if a new User ID should be set if one is set if [ -n "${VDM_PUID}" ] && [[ "${VDM_PUID}" =~ ^[0-9]+$ ]] && (whiptail --yesno "The user ID in ${PROGRAM_NAME} memory is ${VDM_PUID}, would you like to change this user ID?" \ --defaultno --title "Set User ID" --backtitle "${BACK_TITLE}" 8 112); then # always first reset unset VDM_PUID fi # give little notice # showNotice "To get the ID of a user in a container:\n\n$ docker exec -it container_name /bin/bash\n$ id -u www-data" # we first get the container user ID while [ -z "${VDM_PUID}" ] || [ ${#VDM_PUID} -le 1 ] || ! [[ "${VDM_PUID}" =~ ^[0-9]+$ ]]; do VDM_PUID=$(getInput "Enter user ID for the ${VDM_CONTAINER_TYPE^} container. Use default:${1:-1000} if you don't know.\n[add only a number]" \ "${1:-1000}" 'Enter User ID') # check if we have success if [ ${#VDM_PUID} -le 1 ] || ! [[ "${VDM_PUID}" =~ ^[0-9]+$ ]]; then showError "You must enter a user ID like: ${1:-1000} (only the number)" fi done # ask if a new Group ID should be set if one is set if [ -n "${VDM_PGID}" ] && [[ "${VDM_PGID}" =~ ^[0-9]+$ ]] && (whiptail --yesno "The user-group ID in ${PROGRAM_NAME} memory is ${VDM_PGID}, would you like to change this user-group ID?" \ --defaultno --title "Set User-Group ID" --backtitle "${BACK_TITLE}"8 112); then # always first reset unset VDM_PGID fi # give little notice # showNotice "To get the ID of a user-group in a container:\n\n$ docker exec -it container_name /bin/bash\n$ id -u www-data" # we get the container user-group ID while [ -z "${VDM_PGID}" ] || [ ${#VDM_PGID} -le 1 ] || ! [[ "${VDM_PGID}" =~ ^[0-9]+$ ]]; do VDM_PGID=$(getInput "Enter user-group ID for the ${VDM_CONTAINER_TYPE^} container. Use default:${1:-1000} if you don't know.\n[add only a number]" \ "${1:-1000}" 'Enter User Group ID') # check if we have success if [ ${#VDM_PGID} -le 1 ] || ! [[ "${VDM_PGID}" =~ ^[0-9]+$ ]]; then showError "You must enter a user-group ID like: ${1:-1000} (only the number)" fi done # make available export VDM_PUID export VDM_PGID } # Set the program mode function setMode() { # Local variable to store the mode local mode="${1:-basic}" # Local variable to store the VDM_EXPERT_MODE value local vdm_expert_mode="false" # Set the VDM_EXPERT_MODE value based on the mode case "$mode" in basic) vdm_expert_mode="false" ;; expert) vdm_expert_mode="true" ;; *) echo "Error: Unsupported mode. Use 'basic' or 'expert'." return 1 ;; esac # Set persistent setUniqueEnvVariable "VDM_EXPERT_MODE=${vdm_expert_mode}" # Make global export VDM_EXPERT_MODE="$vdm_expert_mode" } # set the domain function setDomain() { # allow multi domain setup if allowMultiDomains; then # select a domain name getMainDomain else # set the main domain if not set setMainDomain fi } # set the main domain function setMainDomain() { # set the main domain if not set getDomain # add this value if not set variable setUniqueEnvVariable "VDM_DOMAIN=\"${VDM_DOMAIN}\"" } # set the sub domain function setSubDomain() { # make sure it has not been used before, # also make sure its not a port or a ....db while [ ${#VDM_SUBDOMAIN} -le 1 ] || [[ "${VDM_SUBDOMAIN}" =~ [^a-zA-Z] ]] || [ -d "${VDM_REPO_PATH}/${3:-$VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN}.${VDM_DOMAIN}" ] || [ -d "${VDM_REPO_PATH}/${3:-$VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN%db}.${VDM_DOMAIN}" ] || [ "${VDM_SUBDOMAIN}" = "${2}" ]; do # get the value VDM_SUBDOMAIN=$(getInput "Enter sub-domain used for this ${VDM_CONTAINER_TYPE^} container.\n[Text with no spaces that is only alphabetical]" \ "${1}" 'Enter Sub-Domain') # keep asking if empty or does exist if [ ${#VDM_SUBDOMAIN} -ge 1 ] && [ -d "${VDM_REPO_PATH}/${3:-$VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN}.${VDM_DOMAIN}" ] || [ -d "${VDM_REPO_PATH}/${3:-$VDM_CONTAINER_TYPE}/available/${VDM_SUBDOMAIN%db}.${VDM_DOMAIN}" ]; then showError "The sub-domain:${VDM_SUBDOMAIN} is already used. Select another" elif [[ "${VDM_SUBDOMAIN}" =~ [^a-zA-Z] ]]; then showError "You must enter a sub-domain that is only alphabetical!" elif [ ${#VDM_SUBDOMAIN} -le 1 ]; then showError "You must enter a sub-domain!" fi done # make sure it is available export VDM_SUBDOMAIN } # to set a global multiple domains file and values function setMultiDomains() { # set some locals local domain_name local list_domains local question # allow multi domain setup if allowMultiDomains; then # get the list of domains list_domains=$(getListDomains "\n") # the question based on current state [ -f "${VDM_SRC_PATH}/.domains" ] && question="Would you like to add another domain to ${PROGRAM_NAME}?\nThese Domains already set:\n${list_domains}" || question="Would you like to add a domain to ${PROGRAM_NAME}?" # get the key if not set while (whiptail --yesno "${question}" --scrolltext --defaultno \ --title "Add Domain" --backtitle "${BACK_TITLE}" 20 112); do # now clear the main domain each time unset VDM_DOMAIN # we must let them enter the new domain name getDomain 'Enter a domain name' 'Enter a Domain Name' # add for next time saveMultiDomain "${VDM_DOMAIN}" # get the list of domains list_domains=$(getListDomains "\n") # update the question question="Would you like to add another domain to ${PROGRAM_NAME}?\nThese Domains already set:\n${list_domains}" done fi } # set the multi domain switch function setMultiDomainSwitch() { # Allow multiple domains if [ "${VDM_MULTI_DOMAIN:-not}" = 'not' ]; then # explain the pros and cons... showNotice "Next you should make the selection of using a single or multiple domain setup.\n\n\ With multiple domain setups you will need to select a main domain each time you setup a new container.\n\ With a single domain setup you setup the main domain once, and then only need setup the subdomains\n\ with each new container." # check the security switch if (whiptail --yesno "Would you like to use a multiple domains setup?" \ --defaultno --title "Allow Multi Domains" --backtitle "${BACK_TITLE}" 8 112); then # we set the secure switch VDM_MULTI_DOMAIN=true else VDM_MULTI_DOMAIN=false fi fi # add this value if not set variable setEnvVariable "VDM_MULTI_DOMAIN=${VDM_MULTI_DOMAIN}" # make sure it is available export VDM_MULTI_DOMAIN } # set the unique key function setUniqueKey() { # get the key if not set while [ ${#VDM_KEY} -le 1 ] || [ -d "${VDM_PROJECT_PATH}/${VDM_KEY}" ] || [[ "${VDM_KEY}" =~ [^a-zA-Z] ]]; do # get the value VDM_KEY=$(getInput "${1:-Enter key used for container naming and name of the persistent volume folder.}\n[Text with no spaces that is only alphabetical]" \ '' 'Enter Key') # keep asking if empty or does exist if [ ${#VDM_KEY} -ge 1 ] && [ -d "${VDM_PROJECT_PATH}/${VDM_KEY}" ]; then showError "The key:${VDM_KEY} is already used. Select another" elif [[ "${VDM_KEY}" =~ [^a-zA-Z] ]]; then showError "You must enter a key with no spaces that is only alphabetical!" elif [ ${#VDM_KEY} -le 1 ]; then showError "You must enter a key!" fi done # make sure it is available export VDM_KEY } # set the Joomla website details function setJoomlaWebsiteDetails() { # ask if we should add mailcatcher if (whiptail --yesno "Would you like to enable website autodeploy for ${VDM_CONTAINER_TYPE,}:${VDM_JV}" \ --defaultno --title "Enable Autodeploy" --backtitle "${BACK_TITLE}" 8 112); then # ask for website name setJoomlaSiteName # ask for username setJoomlaUsername # ask for user setJoomlaUser # ask for email setJoomlaEmail # ask for username password setJoomlaPassword fi } # set the website name function setJoomlaSiteName() { # get the key if not set while [ ${#VDM_J_SITE_NAME} -le 2 ]; do # get the value VDM_J_SITE_NAME=$(getInput "Enter a Joomla website name." \ 'Joomla! CMS' 'Enter Site Name') # keep asking if empty if [ ${#VDM_J_SITE_NAME} -le 2 ]; then showError "You must enter a site name longer than 2 characters!" fi done # make sure it is available export VDM_J_SITE_NAME } # set the username function setJoomlaUsername() { # get the key if not set while [ ${#VDM_J_USERNAME} -le 1 ] || [[ "${VDM_J_USERNAME}" =~ [^a-zA-Z] ]]; do # get the value VDM_J_USERNAME=$(getInput "Enter a Joomla username.\n[Text with no spaces that is only alphabetical]" \ 'user' 'Enter Username') # keep asking if empty if [[ "${VDM_J_USERNAME}" =~ [^a-zA-Z] ]]; then showError "You must enter a username with no spaces that is only alphabetical!" elif [ ${#VDM_J_USERNAME} -le 1 ]; then showError "You must enter a username!" fi done # make sure it is available export VDM_J_USERNAME } # set the user function setJoomlaUser() { # get the key if not set while [ ${#VDM_J_USER} -le 2 ]; do # get the value VDM_J_USER=$(getInput "Enter a Joomla user." \ 'John Doe' 'Enter User Name') # keep asking if empty if [ ${#VDM_J_USER} -le 2 ]; then showError "You must enter a user!" fi done # make sure it is available export VDM_J_USER } # set the email function setJoomlaEmail() { # Email regex for basic validation local email_regex="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" # Keep prompting until a valid email is provided while [[ ! "${VDM_J_EMAIL}" =~ $email_regex ]]; do # Get the email VDM_J_EMAIL=$(getInput "Enter a Joomla email address." \ 'admin@example.org' 'Enter Email') # Check if the input matches the basic email regex pattern if [[ ! "${VDM_J_EMAIL}" =~ $email_regex ]]; then showError "You must enter a valid email address!" fi done # Export the variable to make it available for child processes export VDM_J_EMAIL } # set the username function setJoomlaPassword() { # get the key if not set while [ ${#VDM_J_PASSWORD} -le 12 ]; do # get the value VDM_J_PASSWORD=$(getInput "Enter Joomla User Password" \ 'joomla-17082005' 'Enter Joomla User Password') # keep asking if empty if [ ${#VDM_J_PASSWORD} -le 12 ]; then showError "You must enter a password with more than 4 characters!" fi done # make sure it is available export VDM_J_PASSWORD } # set the number of containers to build function setNumberContainers() { local valid=0 while [ "$valid" -eq 0 ]; do # get the value VDM_NUMBER_CONTAINERS=$(getInput "Enter the number of containers you would like to deploy.\n[Just a number!]" \ 10 'Enter Number') # check that the number is above 1 and below 100 if ! [[ $VDM_NUMBER_CONTAINERS =~ ^[0-9]+$ ]]; then showError "You must enter a number!" elif [[ $VDM_NUMBER_CONTAINERS -le 1 ]]; then showError "The number (${VDM_NUMBER_CONTAINERS}) must be above 1" elif [[ $VDM_NUMBER_CONTAINERS -ge 100 ]]; then showError "The number (${VDM_NUMBER_CONTAINERS}) must be below 100" else valid=1 fi done # make sure it is available export VDM_NUMBER_CONTAINERS } # set the Environment key function setEnvironmentKey() { # get the env key if not set while [ ${#VDM_ENV_KEY} -le 1 ] || [[ "${VDM_ENV_KEY}" =~ [^a-zA-Z] ]]; do # get the value VDM_ENV_KEY=$(getInput "Enter environment key used to load the container environment variables.\n[Text with no spaces that is only alphabetical UPPERCASE]" \ "${VDM_KEY^^}" 'Enter ENV Key') # keep asking if empty or does exist if [ ${#VDM_ENV_KEY} -le 1 ]; then showError "You must enter an environment key!" elif [[ "${VDM_ENV_KEY}" =~ [^a-zA-Z] ]]; then showError "You must enter an environment key with no spaces that is only alphabetical!" fi done # make sure it is available export VDM_ENV_KEY } # set the update of host files function setUpdateHostFile() { # check if we have secure switch set if [ "${VDM_UPDATE_HOST:-not}" = 'not' ]; then # check the security switch if (whiptail --yesno "Would you like to update your host file with each new domain you add?" \ --defaultno --title "Update /etc/hosts" --backtitle "${BACK_TITLE}" 8 112); then # we set the secure switch VDM_UPDATE_HOST=true else VDM_UPDATE_HOST=false fi fi # add this value since its not set variable setEnvVariable "VDM_UPDATE_HOST=${VDM_UPDATE_HOST}" # make sure it is available export VDM_UPDATE_HOST } # set the image source values function setImageSource() { # some local values local vdm_image_source local vdm_tag_url # get the Joomla version if not set while [ -z "${VDM_J_REPO}" ] || [ ${#VDM_J_REPO} -le 3 ]; do # get the image source vdm_image_source=$(getImageSource) # build the source key (string before the first | ) VDM_J_REPO="${vdm_image_source%%|*}" # set the source entry-point (url after the last ; ) VDM_ENTRY_REPO="${vdm_image_source##*;}" # remove the J_REPO (remove everything before the first | ) vdm_image_source="${vdm_image_source##*|}" # set the source entry-point (string before the first ; ) vdm_tag_url="${vdm_image_source%%;*}" # keep asking [ ${#VDM_J_REPO} -le 3 ] && { showError "Seems like the source selected is not correctly configured, please select another." } # make sure the source is set [[ "${VDM_ENTRY_REPO}" == *'docker-entrypoint.sh' ]] || { VDM_J_REPO='' showError "Seems like the source selected is not correctly configured, please select another." } done # get the Joomla version if not set while [ ${#VDM_JV} -le 1 ]; do # get the value VDM_JV=$(getInput "Enter Joomla version tag for this container.\n[See available tags here ${vdm_tag_url}]" '5.0' 'Enter Version Tag') # keep asking [ ${#VDM_JV} -ge 1 ] || { showError "You must enter a version tag. See available tags here ${vdm_tag_url}" } done # make the value available globally export VDM_ENTRY_REPO export VDM_J_REPO export VDM_JV } # check version function isVersionAbove() { local tag="$1" local target_version="$2" local tag_major local tag_minor local target_major local target_minor # Extract major and minor versions for the tag and the target_version tag_major=$(echo "$tag" | awk -F'[.-]' '{print $1}') tag_minor=$(echo "$tag" | awk -F'[.-]' '{print $2}') target_major=$(echo "$target_version" | awk -F'.' '{print $1}') target_minor=$(echo "$target_version" | awk -F'.' '{print $2}') # Compare major versions if [[ "$tag_major" -gt "$target_major" ]]; then return 0 elif [[ "$tag_major" -eq "$target_major" && "$tag_minor" -ge "$target_minor" ]]; then return 0 fi return 1 } # set persistence volumes function setPersistence() { # ask the question if (whiptail --yesno "Would you like to enable persistence on this $1." \ --title "Persist Volume" --backtitle "${BACK_TITLE}" 8 112); then # yes persistence return 0 fi # no persistence return 1 } # set the PHP settings function setPHPSettings() { # ask the question if (whiptail --yesno "Would you like to set some PHP overrides for the Joomla container.\n\nTo make these changes we need sudo privileges." \ --defaultno --title "PHP.ini values" --backtitle "${BACK_TITLE}" 9 112); then # max_execution_time VDM_max_execution_time=$(getInputNow "Enter max execution time" \ "${VDM_max_execution_time:-124}" 'max_execution_time') # remember this for next time setUniqueEnvVariable "VDM_max_execution_time=${VDM_max_execution_time}" # max_input_time VDM_max_input_time=$(getInputNow "Enter max input time" \ "${VDM_max_input_time:-1500}" 'max_input_time') # remember this for next time setUniqueEnvVariable "VDM_max_input_time=${VDM_max_input_time}" # max_input_vars VDM_max_input_vars=$(getInputNow "Enter max_input_vars" \ "${VDM_max_input_vars:-3000}" 'max_input_vars') # remember this for next time setUniqueEnvVariable "VDM_max_input_vars=${VDM_max_input_vars}" # error_reporting default_error_reporting="E_ALL & ~E_DEPRECATED & ~E_STRICT" VDM_error_reporting=$(getInputNow "Enter error reporting" \ "${VDM_error_reporting:-$default_error_reporting}" 'error_reporting') # remember this for next time setUniqueEnvVariable "VDM_error_reporting=\"${VDM_error_reporting}\"" # upload_max_filesize VDM_upload_max_filesize=$(getInputNow "Enter upload max filesize" \ "${VDM_upload_max_filesize:-256M}" 'upload_max_filesize') # remember this for next time setUniqueEnvVariable "VDM_upload_max_filesize=\"${VDM_upload_max_filesize}\"" # post_max_size VDM_post_max_size=$(getInputNow "Enter post max size" \ "${VDM_post_max_size:-256M}" 'post_max_size') # remember this for next time setUniqueEnvVariable "VDM_post_max_size=\"${VDM_post_max_size}\"" # memory_limit VDM_memory_limit=$(getInputNow "Enter memory limit" \ "${VDM_memory_limit:-256M}" 'memory_limit') # remember this for next time setUniqueEnvVariable "VDM_memory_limit=\"${VDM_memory_limit}\"" # make sure the directory exist mkdir -p "${VDM_PROJECT_PATH}/${VDM_PHP_PROJECT_PATH}" # push all these values to the php.ini file { echo "max_execution_time = ${VDM_max_execution_time}" echo "max_input_time = ${VDM_max_input_time}" echo "max_input_vars = ${VDM_max_input_vars}" echo "error_reporting = ${VDM_error_reporting}" echo "upload_max_filesize = ${VDM_upload_max_filesize}" echo "post_max_size = ${VDM_post_max_size}" echo "memory_limit = ${VDM_memory_limit}" } > "${VDM_PROJECT_PATH}/${VDM_PHP_PROJECT_PATH}/php.ini" # allow custom edit if isExpert && (whiptail --yesno "Would you like to set some more custom PHP overrides for this Joomla container" \ --defaultno --title "Edit PHP.ini" --backtitle "${BACK_TITLE}" 8 112); then # give little heads-up showNotice "${USER^}, to save the changes you've made or to just close the file again press:\n\n[ctrl+x] with nano on ubuntu." 13 # lets open the file with nano for now sudo "${EDITOR:-nano}" "${VDM_PROJECT_PATH}/${VDM_PHP_PROJECT_PATH}/php.ini" fi # set the ownership if [ -n "${VDM_PUID}" ] && [ -n "${VDM_PGID}" ]; then sudo chown "${VDM_PUID}":"${VDM_PGID}" "${VDM_PROJECT_PATH}/${VDM_PHP_PROJECT_PATH}/php.ini" else sudo chown www-data:www-data "${VDM_PROJECT_PATH}/${VDM_PHP_PROJECT_PATH}/php.ini" fi # make sure the permission are good sudo chmod 600 "${VDM_PROJECT_PATH}/${VDM_PHP_PROJECT_PATH}/php.ini" # yes load php.ini volume to the container return 0 fi return 1 } # set the docker entrypoint function setDockerEntrypoint() { # ask the question if (whiptail --yesno "Would you like to customize the entrypoint script for this Joomla container" \ --defaultno --title "entrypoint.sh values" --backtitle "${BACK_TITLE}" 8 112); then # make sure the directory exist mkdir -p "${VDM_PROJECT_PATH}/${VDM_ENTRY_PROJECT_PATH}" # unattended deploy # if (whiptail --yesno "Would you like to to setup unattended deployment for this Joomla container" --defaultno --title "Unattended Deployment" 8 112); then # echo "soon we will have this ready" # fi # download entrypoint.sh file to the project folder if sudo curl --fail -L "${VDM_ENTRY_REPO}" -o "${VDM_PROJECT_PATH}/${VDM_ENTRY_PROJECT_PATH}/entrypoint.sh" 2>/dev/null; then # give little heads-up showNotice "${USER^}, to save the changes you've made or to just close the file again press:\n\n[ctrl+x] with nano on ubuntu." 13 # lets open the file with nano for now sudo "${EDITOR:-nano}" "${VDM_PROJECT_PATH}/${VDM_ENTRY_PROJECT_PATH}/entrypoint.sh" # set the ownership sudo chown root:root "${VDM_PROJECT_PATH}/${VDM_ENTRY_PROJECT_PATH}/entrypoint.sh" # make sure the permission are good sudo chmod +x "${VDM_PROJECT_PATH}/${VDM_ENTRY_PROJECT_PATH}/entrypoint.sh" # we have the file set return 0 else # show the error showError "The entrypoint.sh could not be set." fi fi return 1 } # set the secure state function setSecureCloudflareState() { # check the security switch if (whiptail --yesno "Will this container be proxied by Cloudflare [ONLY for server proxied in none-strict mode via Cloudflare]" \ --defaultno --title "Cloudflare" --backtitle "${BACK_TITLE}" 8 112); then # we set the secure switch VDM_SECURE_CLOUDFLARE=true else VDM_SECURE_CLOUDFLARE=false fi # make sure it is available export VDM_SECURE_CLOUDFLARE } # set the secure state function setSecureState() { if [ "${VDM_SECURE:-not}" = 'not' ]; then # check the security switch if (whiptail --yesno "Would you like to use Letsencrypt auto setup for your containers [ONLY for server with public static IP]" \ --defaultno --title "Letsencrypt" --backtitle "${BACK_TITLE}" 8 112); then # we set the secure switch VDM_SECURE=true else VDM_SECURE=false fi fi # add this value if not set variable setEnvVariable "VDM_SECURE=${VDM_SECURE}" # make sure it is available export VDM_SECURE } # Create Docker networks if they do not exist function setNetworks() { # Default network names and subnets local traefik_gateway="${VDM_TRAEFIK_GATEWAY:-traefik_webgateway}" local traefik_subnet="${VDM_TRAEFIK_SUBNET:-172.22.0.0/24}" local openssh_gateway="${VDM_OPENSSH_GATEWAY:-openssh_gateway}" local openssh_subnet="${VDM_OPENSSH_SUBNET:-172.23.0.0/24}" # Create traefik network if it does not exist docker network inspect "${traefik_gateway}" >/dev/null 2>&1 || docker network create "${traefik_gateway}" --subnet "${traefik_subnet}" # Create openssh network if it does not exist docker network inspect "${openssh_gateway}" >/dev/null 2>&1 || docker network create "${openssh_gateway}" --subnet "${openssh_subnet}" } # to set a global Env Variable function setUniqueEnvVariable() { # some locals local environment_variable="$1" # always remove previous environment variable deleteEnvVariable "${environment_variable%%=*}" # set the new environment variable setEnvVariable "${environment_variable}" && return 0 # or failed return 12 } # to set a global Env Variable function setEnvVariable() { # check if the env file exist if [ -f "${VDM_SRC_PATH}/.env" ]; then grep -q "${1}" "${VDM_SRC_PATH}/.env" || echo "${1}" >>"${VDM_SRC_PATH}/.env" elif (whiptail --yesno "Can we create the ${VDM_SRC_PATH}/.env file" \ --title "Environment Variable File" --backtitle "${BACK_TITLE}" 8 112); then # make sure the folder exist mkdir -p "${VDM_SRC_PATH}" # so we creat the file echo "${1}" >"${VDM_SRC_PATH}/.env" # make sure the permissions are secured chmod 600 "${VDM_SRC_PATH}/.env" else showError "The ${VDM_SRC_PATH}/.env file does not exist. So ${1} could not be added!" # we return false return 12 fi # we return true return 0 } # to set a container Env Variable function setContainerEnvVariable() { # check if the env file exist if [ -f "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" ]; then grep -q "${1}" "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" || echo "${1}" >>"${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" elif (whiptail --yesno "Can we create the ${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env file" \ --title "Container Environment Variable File" --backtitle "${BACK_TITLE}" 8 112); then # make sure the folder exist mkdir -p "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}" # so we creat the file echo "${1}" >"${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" # make sure the permissions are secured chmod 600 "${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env" else showError "The ${VDM_REPO_PATH}/${VDM_CONTAINER_TYPE}/.env file does not exist. So ${1} could not be added!" # we return false return 12 fi # we return true return 0 } # check if a Directory exist and if it has sub-directories function hasDirectories() { # shellcheck disable=SC2046 # shellcheck disable=SC2012 if [ -d "${2:-$VDM_REPO_PATH}/${1:-}" ] && [ $(ls -A "${2:-$VDM_REPO_PATH}/${1:-}" | wc -l) -ne 0 ]; then return 0 fi return 1 } # take down apache function downApache() { # make sure port 80 is not used by apache command -v apache2 >/dev/null 2>&1 && [[ $(service apache2 status) == *"active (running)"* ]] && { if (whiptail --yesno "Traefik needs port 80/443 and since Apache is also enabled we don't know if is using these ports, so Traefik may not work. Can we disable Apache?\n(we need sudo privileges)" \ --defaultno --title "Disable Apache" --backtitle "${BACK_TITLE}" 12 112); then sudo systemctl stop apache2.service sudo systemctl disable apache2.service fi } } # lock a folder path function lockFolder() { cat < Require all denied # Apache 2.0-2.2 Deny from all EOF } # clear the main environment variables function quitProgram() { ####### CLEAR ALL unset VDM_PUID unset VDM_PGID unset VDM_CONTAINER_TYPE unset VDM_TASK unset VDM_SRC_PATH unset VDM_REPO_PATH unset VDM_PROJECT_PATH unset VDM_DOMAIN unset VDM_MULTI_DOMAIN unset VDM_SECURE unset VDM_SECURE_CLOUDFLARE unset VDM_UPDATE_HOST unset VDM_CONTAINER unset VDM_ACCESS_TOKEN # exit at this point exit 0 } # do we allow multiple domains function allowMultiDomains() { # Allow multiple domains setMultiDomainSwitch # allow multi domain setup if $VDM_MULTI_DOMAIN; then # yes we do return 0 fi # now we don't return 12 } # do we allow multiple domains function allowEditHostFile() { # check if we allow host file updates setUpdateHostFile # allow multi domain setup if $VDM_UPDATE_HOST; then # yes we do return 0 fi # now we don't return 12 } # to set a global multiple domain file and values function saveMultiDomain() { # set some locals local domain_name local src_path_domain # set the domain and remove any whitespace domain_name="$(echo -e "${1}" | tr -d '[:space:]')" src_path_domain="${VDM_SRC_PATH}/.domains" # check that we have a domain string if [ -n "${domain_name}" ]; then # make sure the folder exists mkdir -p "${VDM_SRC_PATH}" # if the file does not exist, create it and set the proper permissions if [ ! -f "${src_path_domain}" ]; then touch "${src_path_domain}" chmod 600 "${src_path_domain}" fi # check if the domain is not already in the file, and if not, add it if ! grep -q "${domain_name}" "${src_path_domain}"; then echo "${domain_name}" >>"${src_path_domain}" fi fi # we return true return 0 } # delete a global Environment Variable function deleteEnvVariable() { # some locals local env_key # check if the env file exist if [ -f "${VDM_SRC_PATH}/.env" ]; then # check that we have the key env_key="${1:-none}" # shellcheck disable=SC2015 grep -vx "${env_key}=.*" "${VDM_SRC_PATH}/.env" >"${VDM_SRC_PATH}/.env_tmp" && mv "${VDM_SRC_PATH}/.env_tmp" "${VDM_SRC_PATH}/.env" || rm "${VDM_SRC_PATH}/.env_tmp" fi } # delete multiple domains function deleteMultiDomains() { local menu_options=() local domains local i=0 local domain_file="${VDM_SRC_PATH}/.domains" if [ -f "${domain_file}" ] && readarray -t domains <"${domain_file}"; then if [ "${#domains[@]}" -gt 0 ]; then for domain in "${domains[@]}"; do menu_options+=("${domain}" "${domain}" "OFF") i=$((i + 1)) done answer=$(whiptail --title "Delete Domains" --checklist \ "Select the domain/s you would like to delete" 20 112 $i \ "${menu_options[@]}" \ --backtitle "${BACK_TITLE}" --nocancel --notags 3>&1 1>&2 2>&3) if [ -n "${answer}" ]; then IFS=' ' read -r -a array_answer <<<"${answer[@]}" for remove in "${array_answer[@]}"; do remove_domain="${remove//\"/}" # shellcheck disable=SC2015 grep -vx "${remove_domain}" "${domain_file}" >"${domain_file}_tmp" && \ mv "${domain_file}_tmp" "${domain_file}" || \ rm "${domain_file}_tmp" done fi fi fi } # check if expert mode is on function isExpert() { # check if its set if [ -n "${VDM_EXPERT_MODE}" ] && [ "${VDM_EXPERT_MODE}" = "true" ]; then # is expert return 0 else # not expert return 1 fi } # check if some thing is a function function isFunc() { declare -F "$1" >/dev/null } # update the host file function updateHostFile() { # check if we should add to host file if allowEditHostFile; then # check if already in host file if grep -q "${1:-$VDM_SUBDOMAIN}.${2:-$VDM_DOMAIN}" /etc/hosts; then showNotice "${USER^}, ${1:-$VDM_SUBDOMAIN}.${2:-$VDM_DOMAIN} is already in the /etc/hosts file." elif (whiptail --yesno "${USER^}, to add the ${1:-$VDM_SUBDOMAIN}.${2:-$VDM_DOMAIN} entry to your host file we need sudo privileges." \ --title "Give sudo Privileges" --backtitle "${BACK_TITLE}" 8 112); then # add the domain to the host file echo "127.0.0.1 ${1:-$VDM_SUBDOMAIN}.${2:-$VDM_DOMAIN}" | sudo tee -a /etc/hosts >/dev/null # show notice showNotice "${USER^}, ${1:-$VDM_SUBDOMAIN}.${2:-$VDM_DOMAIN} was added to the /etc/hosts file." fi fi } # the manually edit the host file function editHostFile() { # check if we should add to host file if allowEditHostFile; then # if this container is enabled ask if it should be redeployed if (whiptail --yesno "To edit the host file we need sudo privileges.\n[Only continue if you know what your doing!]" \ --title "Give sudo Privileges" --backtitle "${BACK_TITLE}" 15 112); then # give little heads-up showNotice "${USER^}, to save the changes you've made or to just close the file again press:\n\n[ctrl+x] with nano on ubuntu." 13 # lets open the file with nano for now sudo "${EDITOR:-nano}" /etc/hosts fi fi } # the manually edit the config file function editConfigFile() { # check if the file exist if [ -f "${VDM_SRC_PATH}/.env" ]; then # give little warning for less command ready people if (whiptail --yesno "Only continue if you know what your doing!\n\n\ ${USER^}, to save the changes you've made or to just close the file again press:\n\n\ [ctrl+x] with nano on ubuntu." --title "Continue to edit ${PROGRAM_NAME} global config." \ --backtitle "${BACK_TITLE}" 15 112); then # all direct edit of the global config file "${EDITOR:-nano}" "${VDM_SRC_PATH}/.env" fi fi } # the manually edit the container env file function openEnv() { # some local values local container_type="$1" # check if the file exist if [ -f "${VDM_REPO_PATH}/${container_type}/.env" ]; then # give little warning for less command ready people if (whiptail --yesno "Only continue if you know what your doing!\n\n\ ${USER^}, to save the changes you've made or to just close the file again press:\n\n\ [ctrl+x] with nano on ubuntu." --title "Continue to edit ${PROGRAM_NAME} ${container_type} env." \ --backtitle "${BACK_TITLE}" 15 112); then # all direct edit of the container env file "${EDITOR:-nano}" "${VDM_REPO_PATH}/${container_type}/.env" fi else showError "${VDM_REPO_PATH}/${container_type}/.env does not exist, make sure you have at least one ${container_type} setup." fi } # Check if any hosts are set in the SSH config file function hasRemoteSystemSet() { local ssh_config="/home/${USER}/.ssh/config" if [ ! -d "/home/${USER}/.ssh" ] || [ ! -f "${ssh_config}" ] || [[ $(grep -i -c '^host' "${ssh_config}") -eq 0 ]]; then showError "To use this feature you must first set up the needed remote host details in (~/.ssh/config) to allow easy and secure SSH access." return 1 fi return 0 } # Get a selected remote system function getRemoteSystem() { # The selected folder local answer if [ -f "/home/${USER}/.ssh/config" ]; then # We start the selection array local selected=() # Our counter local i=0 # Add the remote systems while IFS= read -r remote_system; do # Load the remote systems selected+=("${remote_system}" "${remote_system}" "OFF") # Increment our pointer if [ "$i" -le 10 ]; then i=$((i + 1)) fi done < <(grep -i '^host' "/home/${USER}/.ssh/config" | awk '{print $2}' | sort) # Add manual input option selected+=("Enter manually" "Enter manually" "OFF") # now make the selection answer=$(whiptail --title "Remote Systems" --radiolist \ "Select the remote system you would like to use." 20 112 $i \ "${selected[@]}" --backtitle "${BACK_TITLE}" --notags 3>&1 1>&2 2>&3) if [[ $answer == "Enter manually" ]]; then answer=$(getInputNow "Enter the remote host manually:" "" "Remote Host") fi else answer=$(getInputNow "Enter the remote host:" "" "Remote Host") fi # Return the answer echo "${answer:-none_selected}" } # get a selected migration type function getMigrationType() { # the selected folder local answer # we start the selection array local selected=() # our counter local i=2 # now check if this dir has sub dirs selected+=("push" "Push to Remote System" "ON") selected+=("pull" "Pull from Remote System" "OFF") # now make the selection answer=$(whiptail --title "Migration Type" --radiolist \ "What kind of migration will be preformed?" 20 112 $i \ "${selected[@]}" --backtitle "${BACK_TITLE}" --nocancel --notags 3>&1 1>&2 2>&3) # return the answer echo "${answer:-push}" } # Check if the remote .env file exists function remoteEnvFileExists() { local remote_system="$1" # Check if the file exists on the remote system # shellcheck disable=SC2029 ssh "${remote_system}" "[ -f \"/home/${USER}/.config/octojoom/.env\" ]" } # Get a specific value from the remote .env file function getRemoteEnvValue() { local remote_system="$1" local env_key="$2" local remote_env_value if remoteEnvFileExists "${remote_system}"; then # shellcheck disable=SC2029 remote_env_value=$(ssh "${remote_system}" "grep '^${env_key}=' \"/home/${USER}/.config/octojoom/.env\"" | cut -d '=' -f 2-) fi # Remove any double quotes from the returned value remote_env_value=$(echo "${remote_env_value}" | tr -d '"') echo "${remote_env_value:-none_found}" } # Pull the remote container migration function pullContainerMigration() { # set some local variables local local_path="$1" local container_path="$2" local remote="$3" local remote_repo_path remote_repo_path=$(getRemoteEnvValue "${remote}" "VDM_REPO_PATH") if [ ! "${remote_repo_path}" = 'none_found' ]; then showNotice "PULL<<[NOT READY]>>DETAILS>> ${local_path}<-path ${container_path}<-container name ${remote}<-remote (${remote_repo_path})" 13 else showError "Remote system does not appear to have ${PROGRAM_NAME} installed, or your access was denied. Therefore the migration has been cancelled!" return 1 fi } # Push the remote container migration function pushContainerMigration() { # set some local variables local local_path="$1" local container_path="$2" local remote="$3" local remote_repo_path remote_repo_path=$(getRemoteEnvValue "${remote}" "VDM_REPO_PATH") if [ ! "${remote_repo_path}" = 'none_found' ]; then local remote_path="${remote_repo_path}/${container_path}" # check how we must do this if remoteFolderExists "${remote_path}" "${remote}" && (whiptail --yesno "${USER^}, would you like to backup the remote container?" \ --backtitle "${BACK_TITLE}" --title "Backup Remote" 15 112); then backupRemoteFolder "${remote_path}" "${remote}" fi # sync folder syncWithRemote "${local_path}" "${remote_path}" "${remote}" # check if it was done successfully if transferWasSuccessful "${local_path}" "${remote_path}" "${remote}"; then showNotice "The PUSH of ${local_path} to ${remote}:${remote_path} was a success." 13 return 0 else showError "The PUSH of ${local_path} to ${remote}:${remote_path} failed." return 1 fi else showError "Remote system does not appear to have ${PROGRAM_NAME} installed, or your access was denied. Therefore the migration has been cancelled!" return 1 fi } # Pull the remote directory migration function pullDirectoryMigration() { # set some local variables local local_path="$1" local directory_name="$2" local remote="$3" local remote_project_path remote_project_path=$(getRemoteEnvValue "${remote}" "VDM_PROJECT_PATH") if [ ! "${remote_project_path}" = 'none_found' ]; then showNotice "PULL<<[NOT READY]>>DETAILS>> ${local_path}<-path ${directory_name}<-directory name ${remote}<-remote (${remote_project_path})" 13 else showError "Remote system does not appear to have ${PROGRAM_NAME} installed, or your access was denied. Therefore the migration has been cancelled!" return 1 fi } # Push the remote directory migration function pushDirectoryMigration() { # set some local variables local local_path="$1" local directory_name="$2" local remote="$3" local remote_project_path remote_project_path=$(getRemoteEnvValue "${remote}" "VDM_PROJECT_PATH") if [ ! "${remote_project_path}" = 'none_found' ]; then local backup_dir local remote_path="${remote_project_path}/${directory_name}" # make a backup of this project directory backup_dir=$(createTempBackup "${local_path}") # give current user full ownership of this folder chownBackup "${backup_dir}" # tar the directory tar_path=$(tarAndMoveTempBackup "${local_path}" "${backup_dir}") # clear the backup rm -rf "${backup_dir}" # move zip file to remote moveTarToRemote "${tar_path}" "${remote_project_path}/" "${remote}" # check if it was done successfully if transferTarWasSuccessful "${tar_path}" "${remote_path}.tar.gz" "${remote}"; then # clear the tmp tar rm -rf "${tar_path}" # check how we must do this if remoteFolderExists "${remote_path}" "${remote}" && (whiptail --yesno "${USER^}, would you like to backup the remote directory?" \ --title "Backup Remote" --backtitle "${BACK_TITLE}" 15 112); then backupRemoteFolder "${remote_path}" "${remote}" fi # un-tar the remote file remoteUntarGz "${remote_path}.tar.gz" "${remote_path}" "${remote}" # we are done showNotice "The PUSH of ${local_path} to ${remote}:${remote_path} was a success." 13 # permission fix if (whiptail --yesno "${USER^}, would you like to fix the permissions of this remote directory?\nWe will start the ${PROGRAM_NAME} on the remote server and then you should navigate to the fix, and exit when done." \ --title "Permissions Fix" --backtitle "${BACK_TITLE}" 15 112); then connectToRemoteSystem "${remote}" fi return 0 else showError "The PUSH of ${local_path} to ${remote}:${remote_path} failed." return 1 fi else showError "Remote system does not appear to have ${PROGRAM_NAME} installed, or your access was denied. Therefore the migration has been cancelled!" return 1 fi } # Create a temporary backup of the folder with root access function createTempBackup() { local source_dir="$1" # shellcheck disable=SC2155 local backup_dir="/tmp/backup_$(basename "${source_dir}")_$(date +%Y%m%d%H%M%S)" sudo cp -a "${source_dir}" "${backup_dir}" echo "${backup_dir}" } # Create a tar.gz archive of the temporary backup folder and move the resulting archive to the parent directory of the source directory function tarAndMoveTempBackup() { local source_dir="$1" local backup_dir="$2" local parent_dir local tar_filename local current_dir # Get the parent directory of the source directory parent_dir=$(dirname "${source_dir}") # Create the tar.gz filename tar_filename="$(basename "${source_dir}").tar.gz" # Store the current working directory current_dir=$(pwd) # Change to the backup directory cd "${backup_dir}" || return # Create the tar.gz archive without preserving the full path tar -czf "${parent_dir}/${tar_filename}" . # Change back to the original working directory cd "${current_dir}" || return # Return the path to the moved tar.gz archive echo "${parent_dir}/${tar_filename}" } # Change the ownership of the backup directory to the current user function chownBackup() { local backup_dir="$1" sudo chown -R "${USER}":"${USER}" "${backup_dir}" } # Check if the folder exists on the remote server function remoteFolderExists() { local remote_path="$1" local remote="$2" # Check if the remote folder exists # shellcheck disable=SC2029 if ssh "${remote}" "[ -d '${remote_path}' ]"; then return 0 else return 1 fi } # run Octojoom on a remote system function connectToRemoteSystem() { # set some local variables local remote_system="${1:-none_set}" local remote_repo_path # get remote system if [ "${remote_system}" == "none_set" ]; then remote_system=$(getRemoteSystem) fi # make sure we have a remote if [ ${#remote_system} -ge 2 ] && [ "${remote_system}" != "none_selected" ]; then showNotice "We will first validate that the remote system has ${PROGRAM_NAME,,} config settings in place!" 13 if remoteEnvFileExists "${remote_system}"; then if (whiptail --yesno "${USER^} we found ${PROGRAM_NAME,,} config settings on the ${remote_system}. Please take note of the following important information.\n\nSince we will start ${PROGRAM_NAME,,} on ${remote_system} it will not look that different, seeing that it has the same user graphical interface. Yet, whatever changes you make on ${remote_system} WILL PERMINANTLY effect this remote system, and can not be undone!!!\n\nAre you ready to connect to ${remote_system}:${PROGRAM_NAME,,}?" \ --title "Confirm ${remote_system} Connection" --backtitle "${BACK_TITLE}" 15 112); then showNotice "Connection to ${remote_system} will now be made!" 13 ssh -t "${remote_system}" "bash octojoom" showNotice "You have been disconnected from ${remote_system}!" 13 return 0 else showNotice "The connection to ${remote_system} was cancelled!" 13 return 1 fi else showError "Remote system does not appear to have ${PROGRAM_NAME,,} installed, or your access was denied. Therefore the connection has been closed!" return 1 fi fi showError "Connection to the remote system was cancelled since no remote system was selected." return 1 } # Change the ownership of the remote folder to the current user function chownRemoteFolder() { local remote_path="$1" local remote="$2" ssh -t "${remote}" "sudo chown -R ${USER}:${USER} ${remote_path}" } # Check if the folder exists on the remote server, and if so, rename it for backup purposes function backupRemoteFolder() { local remote_path="$1" local remote="$2" # shellcheck disable=SC2155 local backup_name="${remote_path}_backup_$(date +%Y%m%d%H%M%S)" # shellcheck disable=SC2029 ssh "${remote}" "if [ -d '${remote_path}' ]; then mv '${remote_path}' '${backup_name}'; fi" } # Prep remote folder function prepRemoteFolder() { local remote_path="$1" local remote="$2" # shellcheck disable=SC2029 ssh "${remote}" "mkdir -p ${remote_path}" } # Remotely extract a tar.gz file to a specified folder on the remote host function remoteUntarGz() { local remote_file_path="$1" local remote_extract_path="$2" local remote="$3" # Remote un-tar.gz command # shellcheck disable=SC2029 ssh "${remote}" "mkdir -p '${remote_extract_path}' && tar --overwrite -xzf '${remote_file_path}' -C '${remote_extract_path}'" # shellcheck disable=SC2029 ssh "${remote}" "if [ -f '${remote_file_path}' ]; then rm '${remote_file_path}'; fi" } # Transfer the folder to the remote server using rsync function syncFolder() { local local_path="$1" local remote_path="$2" local remote="$3" rsync -avz --delete "${local_path}/" "${remote}:${remote_path}" } # Move/copy the local tar file to the remote server function moveTarToRemote() { local local_path="$1" local remote_path="$2" local remote="$3" if [ -f "/home/${USER}/.ssh/config" ]; then scp -F "/home/${USER}/.ssh/config" "${local_path}" "${remote}:${remote_path}" else scp "${local_path}" "${remote}:${remote_path}" fi } # Sync the local folder to the remote server and remove the local backup function syncWithRemote() { local local_path="$1" local remote_path="$2" local remote="$3" prepRemoteFolder "${remote_path}" "${remote}" syncFolder "${local_path}" "${remote_path}" "${remote}" } # Check if the transfer was successful by comparing local and remote directory contents function transferWasSuccessful() { local local_path="$1" local remote_path="$2" local remote="$3" # Perform a dry-run with rsync to compare directories rsync_output=$(rsync -avz --dry-run --checksum --delete "${local_path}/" "${remote}:${remote_path}/") # Check if there are any file differences or deletions echo "${rsync_output}" | grep -E -q "^(>|<|deleting)" # Check the exit status of the grep command to determine if there are any differences # shellcheck disable=SC2181 if [ $? -eq 0 ]; then # Differences found, return failure return 1 else # No differences found, return success return 0 fi } # Check if the transfer was successful by comparing local and remote zip file checksums function transferTarWasSuccessful() { local local_path="$1" local remote_path="$2" local remote="$3" # Compute the checksum of the local zip file local_checksum=$(md5sum "${local_path}" | awk '{print $1}') # Compute the checksum of the remote zip file # shellcheck disable=SC2029 remote_checksum=$(ssh "${remote}" "md5sum ${remote_path}" | awk '{print $1}') # Compare the checksums if [ "${local_checksum}" == "${remote_checksum}" ]; then # Checksums match, return success return 0 else # Checksums do not match, return failure return 1 fi } #####################################################################################################################VDM ######################################## CLI MENU ʕ•ᴥ•ʔ # help message function showHelp() { cat < set type you would like to work with example: octojoom --type joomla ====================================================== --task set type of task you would like to perform example: octojoom --task setup ====================================================== --container Directly enabling or disabling a container with the type=joomla and task=enable/disable set The container must exist, which means it was setup previously Used without type and task Joomla-Enable is (default) example: octojoom --container "io.vdm.dev" ====================================================== --update to update your install example: octojoom --update ====================================================== --access-token to update the program you will need an access token from https://git.vdm.dev/user/settings/applications example: octojoom --access-token xxxxxxxxxxx ====================================================== --uninstall to uninstall this script example: octojoom --uninstall ====================================================== AVAILABLE FOR TO ANY CONTAINER ====================================================== -k|--key set key for the docker compose container naming !! no spaces allowed in the key !! example: octojoom -k="vdm" example: octojoom --key="vdm" ====================================================== -e|--env-key set key for the environment variable naming !! no spaces allowed in the key & must be UPPERCASE !! example: octojoom -e="VDM" example: octojoom --env-key="VDM" ====================================================== -d|--domain set key website domain !! must be domain.tld !! example: octojoom -d="joomla.org" example: octojoom --domain="joomla.org" ====================================================== -s|--sub-domain set key website sub domain !! no spaces allowed in the sub domain !! example: octojoom -s="jcb" example: octojoom --sub-domain="jcb" ====================================================== AVAILABLE FOR JOOMLA CONTAINER ====================================================== -j|--joomla-version see available tags here https://hub.docker.com/_/joomla example: octojoom -j=5.0 example: octojoom --joomla-version=5.0 ====================================================== AVAILABLE FOR OPENSSH CONTAINER ====================================================== -u|--username set username of the container example: octojoom -u="ubuntu" example: octojoom --username="ubuntu" ====================================================== --uid set container user id example: octojoom --uid=1000 ====================================================== --gid set container user group id example: octojoom --gid=1000 ====================================================== -p|--port set ssh port to use !! do not use 22 !! example: octojoom -p=2239 example: octojoom --port=2239 ====================================================== --ssh-dir set ssh directory name found in the .ssh dir of this repo for the container keys This directory has separate files for each public key allowed to access the container example: octojoom --ssh-dir="teamname" ====================================================== --sudo switch to add the container user to the sudo group of the container example: octojoom --sudo ====================================================== -t|--time-zone