mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-26 09:05:10 +00:00
feat(easy-docker-wizard): persist apps in metadata and modularize scripts
This commit is contained in:
parent
759e0822a8
commit
1f1d5c7133
17 changed files with 2761 additions and 429 deletions
|
|
@ -1,434 +1,17 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
readonly FLOW_CONTINUE=0
|
load_easy_docker_app_modules() {
|
||||||
readonly FLOW_BACK_TO_MAIN=10
|
local app_dir=""
|
||||||
readonly FLOW_EXIT_APP=11
|
app_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
readonly FLOW_ABORT_INPUT=12
|
|
||||||
|
|
||||||
get_easy_docker_repo_root() {
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common.sh
|
||||||
local app_lib_dir=""
|
source "${app_dir}/wizard/common.sh"
|
||||||
app_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/env.sh
|
||||||
(cd "${app_lib_dir}/../../../.." && pwd)
|
source "${app_dir}/wizard/env.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/single_host.sh
|
||||||
|
source "${app_dir}/wizard/single_host.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows.sh
|
||||||
|
source "${app_dir}/wizard/flows.sh"
|
||||||
}
|
}
|
||||||
|
|
||||||
get_easy_docker_stacks_dir() {
|
load_easy_docker_app_modules
|
||||||
printf '%s/.easy-docker/stacks\n' "$(get_easy_docker_repo_root)"
|
|
||||||
}
|
|
||||||
|
|
||||||
get_current_utc_timestamp() {
|
|
||||||
date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_valid_stack_name() {
|
|
||||||
local stack_name="${1}"
|
|
||||||
|
|
||||||
if [ -z "${stack_name}" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "${stack_name}" in
|
|
||||||
*[!A-Za-z0-9._-]*)
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
create_stack_directory_with_metadata() {
|
|
||||||
local stack_dir_var="${1}"
|
|
||||||
local stack_name="${2}"
|
|
||||||
local stacks_dir=""
|
|
||||||
local created_stack_dir=""
|
|
||||||
local metadata_path=""
|
|
||||||
local created_at=""
|
|
||||||
|
|
||||||
stacks_dir="$(get_easy_docker_stacks_dir)"
|
|
||||||
created_stack_dir="${stacks_dir}/${stack_name}"
|
|
||||||
metadata_path="${created_stack_dir}/metadata.json"
|
|
||||||
|
|
||||||
if ! mkdir -p "${stacks_dir}"; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -e "${created_stack_dir}" ]; then
|
|
||||||
return 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! mkdir -p "${created_stack_dir}"; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
created_at="$(get_current_utc_timestamp)"
|
|
||||||
if ! cat >"${metadata_path}" <<EOF; then
|
|
||||||
{
|
|
||||||
"schema_version": 1,
|
|
||||||
"stack_name": "${stack_name}",
|
|
||||||
"created_at": "${created_at}"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
rollback_stack_directory "${created_stack_dir}" >/dev/null 2>&1 || true
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf -v "${stack_dir_var}" "%s" "${created_stack_dir}"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
rollback_stack_directory() {
|
|
||||||
local stack_dir="${1}"
|
|
||||||
local stacks_dir=""
|
|
||||||
|
|
||||||
if [ -z "${stack_dir}" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
stacks_dir="$(get_easy_docker_stacks_dir)"
|
|
||||||
case "${stack_dir}" in
|
|
||||||
"${stacks_dir}"/*) ;;
|
|
||||||
*)
|
|
||||||
return 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ ! -d "${stack_dir}" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf -- "${stack_dir}"
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt_stack_name_with_cancel() {
|
|
||||||
local result_var="${1}"
|
|
||||||
local input_value=""
|
|
||||||
local input_status=0
|
|
||||||
|
|
||||||
input_value="$(prompt_new_stack_name)"
|
|
||||||
input_status=$?
|
|
||||||
if [ "${input_status}" -ne 0 ]; then
|
|
||||||
return "${FLOW_ABORT_INPUT}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
input_value="$(printf '%s' "${input_value}" | tr -d '\r\n')"
|
|
||||||
|
|
||||||
case "${input_value}" in
|
|
||||||
/cancel | /CANCEL | /Cancel)
|
|
||||||
return "${FLOW_ABORT_INPUT}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
printf -v "${result_var}" "%s" "${input_value}"
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
}
|
|
||||||
|
|
||||||
show_warning_and_wait() {
|
|
||||||
local message="${1}"
|
|
||||||
local seconds="${2:-1}"
|
|
||||||
|
|
||||||
show_warning_message "${message}"
|
|
||||||
sleep "${seconds}"
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_topology_examples_flow() {
|
|
||||||
local topology_name="${1}"
|
|
||||||
local detail_action=""
|
|
||||||
|
|
||||||
case "${topology_name}" in
|
|
||||||
"Single-host")
|
|
||||||
detail_action="$(show_single_host_examples || true)"
|
|
||||||
;;
|
|
||||||
"Split services")
|
|
||||||
detail_action="$(show_split_services_examples || true)"
|
|
||||||
;;
|
|
||||||
"Advanced")
|
|
||||||
detail_action="$(show_advanced_examples || true)"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_and_wait "Unknown topology: ${topology_name}"
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "${detail_action}" in
|
|
||||||
"Use this topology")
|
|
||||||
show_warning_and_wait "Topology '${topology_name}' selected. Next wizard step is coming soon." 2
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
"Back to topology selection" | "")
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_and_wait "Unknown topology action: ${detail_action}"
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_abort_wizard_flow() {
|
|
||||||
local stack_dir="${1}"
|
|
||||||
local abort_action=""
|
|
||||||
local rollback_status=0
|
|
||||||
|
|
||||||
abort_action="$(show_abort_wizard_prompt "${stack_dir}" || true)"
|
|
||||||
case "${abort_action}" in
|
|
||||||
"Rollback files and return to main menu")
|
|
||||||
if rollback_stack_directory "${stack_dir}"; then
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rollback_status=$?
|
|
||||||
if [ "${rollback_status}" -eq 2 ]; then
|
|
||||||
show_warning_and_wait "Refused rollback for unsafe path: ${stack_dir}" 2
|
|
||||||
else
|
|
||||||
show_warning_and_wait "Could not rollback stack files: ${stack_dir}" 2
|
|
||||||
fi
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
"Keep files and return to main menu")
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
;;
|
|
||||||
"Back to topology selection" | "")
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_and_wait "Unknown abort action: ${abort_action}"
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_stack_topology_flow() {
|
|
||||||
local stack_dir="${1}"
|
|
||||||
local topology_action=""
|
|
||||||
local abort_status=0
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
topology_action="$(show_stack_topology_menu "${stack_dir}" || true)"
|
|
||||||
case "${topology_action}" in
|
|
||||||
"Single-host" | "Split services" | "Advanced")
|
|
||||||
handle_topology_examples_flow "${topology_action}"
|
|
||||||
;;
|
|
||||||
"Abort wizard to main menu")
|
|
||||||
handle_abort_wizard_flow "${stack_dir}"
|
|
||||||
abort_status=$?
|
|
||||||
case "${abort_status}" in
|
|
||||||
"${FLOW_BACK_TO_MAIN}")
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
;;
|
|
||||||
*) ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
"")
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_and_wait "Unknown topology selection: ${topology_action}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_create_new_stack_flow() {
|
|
||||||
local stack_name=""
|
|
||||||
local stack_dir=""
|
|
||||||
local create_stack_status=0
|
|
||||||
local stack_input_status=0
|
|
||||||
local topology_status=0
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
stack_name=""
|
|
||||||
if prompt_stack_name_with_cancel stack_name; then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
stack_input_status=$?
|
|
||||||
if [ "${stack_input_status}" -eq "${FLOW_ABORT_INPUT}" ]; then
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
show_warning_and_wait "Input canceled."
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${stack_name}" ]; then
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! is_valid_stack_name "${stack_name}"; then
|
|
||||||
show_warning_and_wait "Invalid stack name. Use letters, numbers, dot, underscore, or hyphen." 2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
stack_dir=""
|
|
||||||
if create_stack_directory_with_metadata stack_dir "${stack_name}"; then
|
|
||||||
handle_stack_topology_flow "${stack_dir}"
|
|
||||||
topology_status=$?
|
|
||||||
case "${topology_status}" in
|
|
||||||
"${FLOW_BACK_TO_MAIN}")
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
;;
|
|
||||||
"${FLOW_EXIT_APP}")
|
|
||||||
return "${FLOW_EXIT_APP}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
|
||||||
create_stack_status=$?
|
|
||||||
if [ "${create_stack_status}" -eq 2 ]; then
|
|
||||||
show_warning_and_wait "Stack already exists: ${stack_name}" 2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
show_warning_and_wait "Could not create stack directory for: ${stack_name}" 2
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_manage_existing_stacks_flow() {
|
|
||||||
local manage_action=""
|
|
||||||
|
|
||||||
manage_action="$(show_manage_stacks_placeholder || true)"
|
|
||||||
case "${manage_action}" in
|
|
||||||
"Back to production setup")
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
"Back to main menu" | "")
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
;;
|
|
||||||
"Exit and close easy-docker")
|
|
||||||
return "${FLOW_EXIT_APP}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_and_wait "Unknown manage-stacks action: ${manage_action}"
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_production_setup_flow() {
|
|
||||||
local production_action=""
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
production_action="$(show_production_setup_menu || true)"
|
|
||||||
|
|
||||||
case "${production_action}" in
|
|
||||||
"Create new stack")
|
|
||||||
if handle_create_new_stack_flow; then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
case "$?" in
|
|
||||||
"${FLOW_BACK_TO_MAIN}")
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
;;
|
|
||||||
"${FLOW_EXIT_APP}")
|
|
||||||
return "${FLOW_EXIT_APP}"
|
|
||||||
;;
|
|
||||||
*) ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
"Manage existing stacks")
|
|
||||||
if handle_manage_existing_stacks_flow; then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
case "$?" in
|
|
||||||
"${FLOW_BACK_TO_MAIN}")
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
;;
|
|
||||||
"${FLOW_EXIT_APP}")
|
|
||||||
return "${FLOW_EXIT_APP}"
|
|
||||||
;;
|
|
||||||
*) ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
"Back to main menu" | "")
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
;;
|
|
||||||
"Exit and close easy-docker")
|
|
||||||
return "${FLOW_EXIT_APP}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_and_wait "Unknown production action: ${production_action}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_environment_check_flow() {
|
|
||||||
local environment_action=""
|
|
||||||
|
|
||||||
environment_action="$(show_environment_status || true)"
|
|
||||||
case "${environment_action}" in
|
|
||||||
"Back to main menu" | "")
|
|
||||||
return "${FLOW_BACK_TO_MAIN}"
|
|
||||||
;;
|
|
||||||
"Exit and close easy-docker")
|
|
||||||
return "${FLOW_EXIT_APP}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_and_wait "Unknown environment action: ${environment_action}"
|
|
||||||
return "${FLOW_CONTINUE}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
run_easy_docker_app() {
|
|
||||||
local action=""
|
|
||||||
local handler_status=0
|
|
||||||
|
|
||||||
enter_alt_screen
|
|
||||||
render_main_screen 1
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
action="$(show_main_menu || true)"
|
|
||||||
|
|
||||||
if [ -z "${action}" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "${action}" in
|
|
||||||
"Production setup")
|
|
||||||
if handle_production_setup_flow; then
|
|
||||||
handler_status="${FLOW_CONTINUE}"
|
|
||||||
else
|
|
||||||
handler_status=$?
|
|
||||||
fi
|
|
||||||
case "${handler_status}" in
|
|
||||||
"${FLOW_BACK_TO_MAIN}")
|
|
||||||
render_main_screen 1
|
|
||||||
;;
|
|
||||||
"${FLOW_EXIT_APP}")
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
*) ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
"Environment check")
|
|
||||||
if handle_environment_check_flow; then
|
|
||||||
handler_status="${FLOW_CONTINUE}"
|
|
||||||
else
|
|
||||||
handler_status=$?
|
|
||||||
fi
|
|
||||||
case "${handler_status}" in
|
|
||||||
"${FLOW_BACK_TO_MAIN}")
|
|
||||||
render_main_screen 1
|
|
||||||
;;
|
|
||||||
"${FLOW_EXIT_APP}")
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
*) ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
"Exit")
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
show_warning_and_wait "Unknown action: ${action}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
|
||||||
19
scripts/easy-docker/lib/app/wizard/common.sh
Executable file
19
scripts/easy-docker/lib/app/wizard/common.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
load_easy_docker_wizard_common_modules() {
|
||||||
|
local wizard_dir=""
|
||||||
|
wizard_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/core.sh
|
||||||
|
source "${wizard_dir}/common/core.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/helpers.sh
|
||||||
|
source "${wizard_dir}/common/helpers.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/compose.sh
|
||||||
|
source "${wizard_dir}/common/compose.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/apps.sh
|
||||||
|
source "${wizard_dir}/common/apps.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/ux.sh
|
||||||
|
source "${wizard_dir}/common/ux.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_easy_docker_wizard_common_modules
|
||||||
274
scripts/easy-docker/lib/app/wizard/common/apps.sh
Executable file
274
scripts/easy-docker/lib/app/wizard/common/apps.sh
Executable file
|
|
@ -0,0 +1,274 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
persist_stack_apps_json_content() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local apps_json_content="${2}"
|
||||||
|
local apps_json_path=""
|
||||||
|
local apps_json_tmp_path=""
|
||||||
|
|
||||||
|
apps_json_path="${stack_dir}/apps.json"
|
||||||
|
apps_json_tmp_path="${apps_json_path}.tmp"
|
||||||
|
|
||||||
|
if ! printf '%s\n' "${apps_json_content}" >"${apps_json_tmp_path}"; then
|
||||||
|
rm -f -- "${apps_json_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -- "${apps_json_tmp_path}" "${apps_json_path}"; then
|
||||||
|
rm -f -- "${apps_json_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get_metadata_apps_predefined_csv() {
|
||||||
|
local metadata_path="${1}"
|
||||||
|
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk '
|
||||||
|
/"apps"[[:space:]]*:[[:space:]]*{/ {
|
||||||
|
in_apps = 1
|
||||||
|
}
|
||||||
|
in_apps && /"predefined"[[:space:]]*:[[:space:]]*\[/ {
|
||||||
|
in_predefined = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_predefined && /\]/ {
|
||||||
|
in_predefined = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_predefined {
|
||||||
|
if (match($0, /"([^"]+)"/, parts)) {
|
||||||
|
if (csv == "") {
|
||||||
|
csv = parts[1]
|
||||||
|
} else {
|
||||||
|
csv = csv "," parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (csv != "") {
|
||||||
|
print csv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "${metadata_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_metadata_apps_custom_lines() {
|
||||||
|
local metadata_path="${1}"
|
||||||
|
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk '
|
||||||
|
/"apps"[[:space:]]*:[[:space:]]*{/ {
|
||||||
|
in_apps = 1
|
||||||
|
}
|
||||||
|
in_apps && /"custom"[[:space:]]*:[[:space:]]*\[/ {
|
||||||
|
in_custom = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_custom && /\]/ {
|
||||||
|
in_custom = 0
|
||||||
|
repo = ""
|
||||||
|
branch = ""
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_custom {
|
||||||
|
if (match($0, /"repo"[[:space:]]*:[[:space:]]*"([^"]+)"/, repo_parts)) {
|
||||||
|
repo = repo_parts[1]
|
||||||
|
}
|
||||||
|
if (match($0, /"branch"[[:space:]]*:[[:space:]]*"([^"]+)"/, branch_parts)) {
|
||||||
|
branch = branch_parts[1]
|
||||||
|
}
|
||||||
|
if (repo != "" && branch != "") {
|
||||||
|
print repo "|" branch
|
||||||
|
repo = ""
|
||||||
|
branch = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "${metadata_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_stack_apps_json_content_from_metadata_apps() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local stack_dir="${2}"
|
||||||
|
local metadata_path=""
|
||||||
|
local preset_apps_csv=""
|
||||||
|
local custom_apps_lines=""
|
||||||
|
local preset_branch=""
|
||||||
|
local app=""
|
||||||
|
local line=""
|
||||||
|
local repo=""
|
||||||
|
local branch=""
|
||||||
|
local url=""
|
||||||
|
local escaped_url=""
|
||||||
|
local escaped_branch=""
|
||||||
|
local entry_json=""
|
||||||
|
local entries_json=""
|
||||||
|
local -a preset_apps=()
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
preset_apps_csv="$(get_metadata_apps_predefined_csv "${metadata_path}" || true)"
|
||||||
|
custom_apps_lines="$(get_metadata_apps_custom_lines "${metadata_path}" || true)"
|
||||||
|
preset_branch="$(get_default_frappe_branch)"
|
||||||
|
|
||||||
|
if [ -n "${preset_apps_csv}" ]; then
|
||||||
|
IFS=',' read -r -a preset_apps <<<"${preset_apps_csv}"
|
||||||
|
for app in "${preset_apps[@]}"; do
|
||||||
|
case "${app}" in
|
||||||
|
erpnext)
|
||||||
|
url="https://github.com/frappe/erpnext"
|
||||||
|
;;
|
||||||
|
crm)
|
||||||
|
url="https://github.com/frappe/crm"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
escaped_url="$(json_escape_string "${url}")"
|
||||||
|
escaped_branch="$(json_escape_string "${preset_branch}")"
|
||||||
|
entry_json="$(printf ' {"url": "%s", "branch": "%s"}' "${escaped_url}" "${escaped_branch}")"
|
||||||
|
if [ -z "${entries_json}" ]; then
|
||||||
|
entries_json="${entry_json}"
|
||||||
|
else
|
||||||
|
entries_json="${entries_json}"$',\n'"${entry_json}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [ -z "${line}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo="${line%%|*}"
|
||||||
|
branch="${line#*|}"
|
||||||
|
if [ -z "${repo}" ] || [ -z "${branch}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
escaped_url="$(json_escape_string "${repo}")"
|
||||||
|
escaped_branch="$(json_escape_string "${branch}")"
|
||||||
|
entry_json="$(printf ' {"url": "%s", "branch": "%s"}' "${escaped_url}" "${escaped_branch}")"
|
||||||
|
if [ -z "${entries_json}" ]; then
|
||||||
|
entries_json="${entry_json}"
|
||||||
|
else
|
||||||
|
entries_json="${entries_json}"$',\n'"${entry_json}"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${custom_apps_lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ -z "${entries_json}" ]; then
|
||||||
|
printf -v "${result_var}" "[\n]\n"
|
||||||
|
else
|
||||||
|
printf -v "${result_var}" "[\n%s\n]\n" "${entries_json}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_stack_apps_json_from_metadata_apps() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local apps_json_content=""
|
||||||
|
|
||||||
|
if ! build_stack_apps_json_content_from_metadata_apps apps_json_content "${stack_dir}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! persist_stack_apps_json_content "${stack_dir}" "${apps_json_content}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_stack_metadata_apps_object() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local apps_json_object="${2}"
|
||||||
|
local metadata_path=""
|
||||||
|
local metadata_tmp_path=""
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
metadata_tmp_path="${metadata_path}.tmp"
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${apps_json_object}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! awk -v apps_object="${apps_json_object}" '
|
||||||
|
BEGIN {
|
||||||
|
in_top_level_apps = 0
|
||||||
|
apps_depth = 0
|
||||||
|
inserted = 0
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (!in_top_level_apps && $0 ~ /^ "apps"[[:space:]]*:/) {
|
||||||
|
print " \"apps\": " apps_object ","
|
||||||
|
in_top_level_apps = 1
|
||||||
|
inserted = 1
|
||||||
|
if ($0 ~ /{/) {
|
||||||
|
apps_depth += gsub(/{/, "{", $0)
|
||||||
|
apps_depth -= gsub(/}/, "}", $0)
|
||||||
|
} else {
|
||||||
|
apps_depth = 0
|
||||||
|
}
|
||||||
|
if (apps_depth <= 0) {
|
||||||
|
in_top_level_apps = 0
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_top_level_apps) {
|
||||||
|
apps_depth += gsub(/{/, "{", $0)
|
||||||
|
apps_depth -= gsub(/}/, "}", $0)
|
||||||
|
if (apps_depth <= 0) {
|
||||||
|
in_top_level_apps = 0
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inserted && $0 ~ /^ "wizard"[[:space:]]*:/) {
|
||||||
|
print " \"apps\": " apps_object ","
|
||||||
|
inserted = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inserted && $0 ~ /^}/) {
|
||||||
|
print " \"apps\": " apps_object
|
||||||
|
inserted = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (!inserted) {
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "${metadata_path}" >"${metadata_tmp_path}"; then
|
||||||
|
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -- "${metadata_tmp_path}" "${metadata_path}"; then
|
||||||
|
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
76
scripts/easy-docker/lib/app/wizard/common/compose.sh
Executable file
76
scripts/easy-docker/lib/app/wizard/common/compose.sh
Executable file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
render_stack_compose_from_metadata() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local metadata_path=""
|
||||||
|
local env_path=""
|
||||||
|
local generated_compose_path=""
|
||||||
|
local generated_compose_tmp_path=""
|
||||||
|
local compose_files_lines=""
|
||||||
|
local compose_file=""
|
||||||
|
local source_compose_path=""
|
||||||
|
local env_erpnext_version=""
|
||||||
|
local fallback_erpnext_version=""
|
||||||
|
local repo_root=""
|
||||||
|
local -a compose_args=()
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||||
|
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
|
||||||
|
generated_compose_tmp_path="${generated_compose_path}.tmp"
|
||||||
|
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "${env_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
env_erpnext_version="$(get_env_file_key_value "${env_path}" "ERPNEXT_VERSION" || true)"
|
||||||
|
if [ -z "${env_erpnext_version}" ]; then
|
||||||
|
fallback_erpnext_version="$(get_default_erpnext_version || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
compose_files_lines="$(get_metadata_compose_files_lines "${metadata_path}" || true)"
|
||||||
|
if [ -z "${compose_files_lines}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo_root="$(get_easy_docker_repo_root)"
|
||||||
|
while IFS= read -r compose_file; do
|
||||||
|
if [ -z "${compose_file}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
source_compose_path="${repo_root}/${compose_file}"
|
||||||
|
if [ ! -f "${source_compose_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
compose_args+=(-f "${source_compose_path}")
|
||||||
|
done <<EOF
|
||||||
|
${compose_files_lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "${#compose_args[@]}" -eq 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${fallback_erpnext_version}" ]; then
|
||||||
|
if ! ERPNEXT_VERSION="${fallback_erpnext_version}" docker compose --env-file "${env_path}" "${compose_args[@]}" config >"${generated_compose_tmp_path}"; then
|
||||||
|
rm -f -- "${generated_compose_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
elif ! docker compose --env-file "${env_path}" "${compose_args[@]}" config >"${generated_compose_tmp_path}"; then
|
||||||
|
rm -f -- "${generated_compose_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -- "${generated_compose_tmp_path}" "${generated_compose_path}"; then
|
||||||
|
rm -f -- "${generated_compose_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
314
scripts/easy-docker/lib/app/wizard/common/core.sh
Executable file
314
scripts/easy-docker/lib/app/wizard/common/core.sh
Executable file
|
|
@ -0,0 +1,314 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly FLOW_CONTINUE=0
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly FLOW_ABORT_INPUT=12
|
||||||
|
|
||||||
|
get_easy_docker_repo_root() {
|
||||||
|
local app_lib_dir=""
|
||||||
|
app_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
(cd "${app_lib_dir}/../../../../.." && pwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
get_easy_docker_stacks_dir() {
|
||||||
|
printf '%s/.easy-docker/stacks\n' "$(get_easy_docker_repo_root)"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_current_utc_timestamp() {
|
||||||
|
date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_stack_name() {
|
||||||
|
local stack_name="${1}"
|
||||||
|
|
||||||
|
if [ -z "${stack_name}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${stack_name}" in
|
||||||
|
*[!A-Za-z0-9._-]*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
create_stack_directory_with_metadata() {
|
||||||
|
local stack_dir_var="${1}"
|
||||||
|
local stack_name="${2}"
|
||||||
|
local setup_type="${3:-production}"
|
||||||
|
local stacks_dir=""
|
||||||
|
local created_stack_dir=""
|
||||||
|
local metadata_path=""
|
||||||
|
local created_at=""
|
||||||
|
|
||||||
|
stacks_dir="$(get_easy_docker_stacks_dir)"
|
||||||
|
created_stack_dir="${stacks_dir}/${stack_name}"
|
||||||
|
metadata_path="${created_stack_dir}/metadata.json"
|
||||||
|
|
||||||
|
if ! mkdir -p "${stacks_dir}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "${created_stack_dir}" ]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mkdir -p "${created_stack_dir}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
created_at="$(get_current_utc_timestamp)"
|
||||||
|
if ! cat >"${metadata_path}" <<EOF; then
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"stack_name": "${stack_name}",
|
||||||
|
"setup_type": "${setup_type}",
|
||||||
|
"created_at": "${created_at}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
rollback_stack_directory "${created_stack_dir}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${stack_dir_var}" "%s" "${created_stack_dir}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rollback_stack_directory() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local stacks_dir=""
|
||||||
|
|
||||||
|
if [ -z "${stack_dir}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
stacks_dir="$(get_easy_docker_stacks_dir)"
|
||||||
|
case "${stack_dir}" in
|
||||||
|
"${stacks_dir}"/*) ;;
|
||||||
|
*)
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ ! -d "${stack_dir}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf -- "${stack_dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_metadata_string_field() {
|
||||||
|
local metadata_path="${1}"
|
||||||
|
local field_name="${2}"
|
||||||
|
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk -v field_name="${field_name}" '
|
||||||
|
match($0, "\"" field_name "\"[[:space:]]*:[[:space:]]*\"([^\"]*)\"", parts) {
|
||||||
|
print parts[1]
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "${metadata_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_metadata_number_field() {
|
||||||
|
local metadata_path="${1}"
|
||||||
|
local field_name="${2}"
|
||||||
|
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk -v field_name="${field_name}" '
|
||||||
|
match($0, "\"" field_name "\"[[:space:]]*:[[:space:]]*([0-9]+)", parts) {
|
||||||
|
print parts[1]
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "${metadata_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_env_file_key_value() {
|
||||||
|
local env_file="${1}"
|
||||||
|
local key="${2}"
|
||||||
|
local value=""
|
||||||
|
|
||||||
|
if [ ! -f "${env_file}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
value="$(
|
||||||
|
awk -v key="${key}" '
|
||||||
|
/^[[:space:]]*#/ { next }
|
||||||
|
$0 !~ /=/ { next }
|
||||||
|
{
|
||||||
|
k = $1
|
||||||
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", k)
|
||||||
|
if (k != key) {
|
||||||
|
next
|
||||||
|
}
|
||||||
|
v = substr($0, index($0, "=") + 1)
|
||||||
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", v)
|
||||||
|
print v
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "${env_file}"
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
\"*\")
|
||||||
|
value="${value#\"}"
|
||||||
|
value="${value%\"}"
|
||||||
|
;;
|
||||||
|
\'*\')
|
||||||
|
value="${value#\'}"
|
||||||
|
value="${value%\'}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf '%s\n' "${value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_default_erpnext_version() {
|
||||||
|
local repo_root=""
|
||||||
|
local source_env_file=""
|
||||||
|
local value=""
|
||||||
|
|
||||||
|
if [ -n "${ERPNEXT_VERSION:-}" ]; then
|
||||||
|
printf '%s\n' "${ERPNEXT_VERSION}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo_root="$(get_easy_docker_repo_root)"
|
||||||
|
for source_env_file in "${repo_root}/.env" "${repo_root}/example.env"; do
|
||||||
|
value="$(get_env_file_key_value "${source_env_file}" "ERPNEXT_VERSION" || true)"
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
printf '%s\n' "${value}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_default_frappe_branch() {
|
||||||
|
local repo_root=""
|
||||||
|
local source_env_file=""
|
||||||
|
local value=""
|
||||||
|
local erpnext_version=""
|
||||||
|
local major_version=""
|
||||||
|
|
||||||
|
if [ -n "${FRAPPE_BRANCH:-}" ]; then
|
||||||
|
printf '%s\n' "${FRAPPE_BRANCH}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo_root="$(get_easy_docker_repo_root)"
|
||||||
|
for source_env_file in "${repo_root}/.env" "${repo_root}/example.env"; do
|
||||||
|
value="$(get_env_file_key_value "${source_env_file}" "FRAPPE_BRANCH" || true)"
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
printf '%s\n' "${value}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
erpnext_version="$(get_default_erpnext_version || true)"
|
||||||
|
case "${erpnext_version}" in
|
||||||
|
v[0-9]*)
|
||||||
|
major_version="${erpnext_version#v}"
|
||||||
|
major_version="${major_version%%.*}"
|
||||||
|
if [[ "${major_version}" =~ ^[0-9]+$ ]]; then
|
||||||
|
printf 'version-%s\n' "${major_version}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf 'version-15\n'
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get_stack_env_path() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local metadata_path=""
|
||||||
|
local stack_name=""
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
stack_name="$(get_metadata_string_field "${metadata_path}" "stack_name" || true)"
|
||||||
|
if [ -z "${stack_name}" ]; then
|
||||||
|
stack_name="${stack_dir##*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s/%s.env\n' "${stack_dir}" "${stack_name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_stack_generated_compose_path() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
|
||||||
|
printf '%s/compose.generated.yaml\n' "${stack_dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_stack_dir_by_name() {
|
||||||
|
local stack_name="${1}"
|
||||||
|
local stacks_dir=""
|
||||||
|
local stack_dir=""
|
||||||
|
local metadata_path=""
|
||||||
|
local candidate_name=""
|
||||||
|
|
||||||
|
stacks_dir="$(get_easy_docker_stacks_dir)"
|
||||||
|
if [ ! -d "${stacks_dir}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for stack_dir in "${stacks_dir}"/*; do
|
||||||
|
if [ ! -d "${stack_dir}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
candidate_name="$(get_metadata_string_field "${metadata_path}" "stack_name" || true)"
|
||||||
|
if [ "${candidate_name}" = "${stack_name}" ]; then
|
||||||
|
printf '%s\n' "${stack_dir}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_metadata_compose_files_lines() {
|
||||||
|
local metadata_path="${1}"
|
||||||
|
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk '
|
||||||
|
/"compose_files"[[:space:]]*:[[:space:]]*\[/ {
|
||||||
|
in_compose_files = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
in_compose_files && /\]/ {
|
||||||
|
in_compose_files = 0
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
in_compose_files {
|
||||||
|
if (match($0, /"([^"]+)"/, parts)) {
|
||||||
|
print parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "${metadata_path}"
|
||||||
|
}
|
||||||
92
scripts/easy-docker/lib/app/wizard/common/helpers.sh
Executable file
92
scripts/easy-docker/lib/app/wizard/common/helpers.sh
Executable file
|
|
@ -0,0 +1,92 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
json_escape_string() {
|
||||||
|
local raw_value="${1}"
|
||||||
|
|
||||||
|
raw_value="${raw_value//\\/\\\\}"
|
||||||
|
raw_value="${raw_value//\"/\\\"}"
|
||||||
|
raw_value="${raw_value//$'\n'/\\n}"
|
||||||
|
raw_value="${raw_value//$'\r'/\\r}"
|
||||||
|
raw_value="${raw_value//$'\t'/\\t}"
|
||||||
|
|
||||||
|
printf '%s' "${raw_value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_compose_files_json_array() {
|
||||||
|
local compose_files_lines="${1}"
|
||||||
|
local line=""
|
||||||
|
local first=1
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [ -z "${line}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${first}" -eq 1 ]; then
|
||||||
|
printf ' "%s"' "${line}"
|
||||||
|
first=0
|
||||||
|
else
|
||||||
|
printf ',\n "%s"' "${line}"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${compose_files_lines}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
build_env_json_object() {
|
||||||
|
local env_lines="${1}"
|
||||||
|
local line=""
|
||||||
|
local key=""
|
||||||
|
local value=""
|
||||||
|
local escaped_key=""
|
||||||
|
local escaped_value=""
|
||||||
|
local first=1
|
||||||
|
|
||||||
|
printf '{'
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [ -z "${line}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${line}" in
|
||||||
|
*=*) ;;
|
||||||
|
*)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
key="${line%%=*}"
|
||||||
|
value="${line#*=}"
|
||||||
|
escaped_key="$(json_escape_string "${key}")"
|
||||||
|
escaped_value="$(json_escape_string "${value}")"
|
||||||
|
|
||||||
|
if [ "${first}" -eq 1 ]; then
|
||||||
|
printf '\n "%s": "%s"' "${escaped_key}" "${escaped_value}"
|
||||||
|
first=0
|
||||||
|
else
|
||||||
|
printf ',\n "%s": "%s"' "${escaped_key}" "${escaped_value}"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${env_lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "${first}" -eq 1 ]; then
|
||||||
|
printf '}'
|
||||||
|
else
|
||||||
|
printf '\n }'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
append_env_line() {
|
||||||
|
local existing_lines="${1}"
|
||||||
|
local key="${2}"
|
||||||
|
local value="${3}"
|
||||||
|
|
||||||
|
if [ -z "${existing_lines}" ]; then
|
||||||
|
printf '%s=%s' "${key}" "${value}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n%s=%s' "${existing_lines}" "${key}" "${value}"
|
||||||
|
}
|
||||||
92
scripts/easy-docker/lib/app/wizard/common/ux.sh
Executable file
92
scripts/easy-docker/lib/app/wizard/common/ux.sh
Executable file
|
|
@ -0,0 +1,92 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
list_existing_stack_names() {
|
||||||
|
local setup_type_filter="${1:-}"
|
||||||
|
local stacks_dir=""
|
||||||
|
local stack_dir=""
|
||||||
|
local metadata_path=""
|
||||||
|
local stack_name=""
|
||||||
|
local stack_setup_type=""
|
||||||
|
|
||||||
|
stacks_dir="$(get_easy_docker_stacks_dir)"
|
||||||
|
if [ ! -d "${stacks_dir}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for stack_dir in "${stacks_dir}"/*; do
|
||||||
|
if [ ! -d "${stack_dir}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
stack_name="$(get_metadata_string_field "${metadata_path}" "stack_name" || true)"
|
||||||
|
if [ -z "${stack_name}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
stack_setup_type="$(get_metadata_string_field "${metadata_path}" "setup_type" || true)"
|
||||||
|
if [ -z "${stack_setup_type}" ]; then
|
||||||
|
stack_setup_type="production"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${setup_type_filter}" in
|
||||||
|
"" | all) ;;
|
||||||
|
*)
|
||||||
|
if [ "${stack_setup_type}" != "${setup_type_filter}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf '%s\n' "${stack_name}"
|
||||||
|
done | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
stack_name_in_array() {
|
||||||
|
local stack_name="${1}"
|
||||||
|
shift
|
||||||
|
local candidate=""
|
||||||
|
|
||||||
|
for candidate in "$@"; do
|
||||||
|
if [ "${candidate}" = "${stack_name}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_stack_name_with_cancel() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local input_value=""
|
||||||
|
local input_status=0
|
||||||
|
|
||||||
|
input_value="$(prompt_new_stack_name)"
|
||||||
|
input_status=$?
|
||||||
|
if [ "${input_status}" -ne 0 ]; then
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
input_value="$(printf '%s' "${input_value}" | tr -d '\r\n')"
|
||||||
|
|
||||||
|
case "${input_value}" in
|
||||||
|
/cancel | /CANCEL | /Cancel)
|
||||||
|
return "${FLOW_ABORT_INPUT}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${input_value}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_warning_and_wait() {
|
||||||
|
local message="${1}"
|
||||||
|
local seconds="${2:-1}"
|
||||||
|
|
||||||
|
show_warning_message "${message}"
|
||||||
|
sleep "${seconds}"
|
||||||
|
}
|
||||||
15
scripts/easy-docker/lib/app/wizard/env.sh
Executable file
15
scripts/easy-docker/lib/app/wizard/env.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
load_easy_docker_wizard_env_modules() {
|
||||||
|
local wizard_dir=""
|
||||||
|
wizard_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/env/validation.sh
|
||||||
|
source "${wizard_dir}/env/validation.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/env/apps.sh
|
||||||
|
source "${wizard_dir}/env/apps.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/env/collect.sh
|
||||||
|
source "${wizard_dir}/env/collect.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_easy_docker_wizard_env_modules
|
||||||
339
scripts/easy-docker/lib/app/wizard/env/apps.sh
vendored
Executable file
339
scripts/easy-docker/lib/app/wizard/env/apps.sh
vendored
Executable file
|
|
@ -0,0 +1,339 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
is_valid_git_repo_source() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
https://* | http://* | ssh://* | git://* | git@*:* | file://*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_git_branch_name() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if [ -z "${value}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${value}" =~ [[:space:]] ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
-* | *..* | *~* | *^* | *:* | *\?* | *\[* | *\\* | */ | /* | *.)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "${value}" == *"@{"* ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get_predefined_app_repo_url() {
|
||||||
|
local app_name="${1}"
|
||||||
|
|
||||||
|
case "${app_name}" in
|
||||||
|
erpnext)
|
||||||
|
printf 'https://github.com/frappe/erpnext\n'
|
||||||
|
;;
|
||||||
|
crm)
|
||||||
|
printf 'https://github.com/frappe/crm\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_custom_git_apps_data() {
|
||||||
|
local result_apps_entries_var="${1}"
|
||||||
|
local result_metadata_custom_var="${2}"
|
||||||
|
local stack_dir="${3}"
|
||||||
|
local app_index=1
|
||||||
|
local app_count=0
|
||||||
|
local repo_value=""
|
||||||
|
local branch_value=""
|
||||||
|
local repo_feedback=""
|
||||||
|
local branch_feedback=""
|
||||||
|
local repo_render_context=1
|
||||||
|
local branch_render_context=1
|
||||||
|
local prompt_status=0
|
||||||
|
local escaped_repo=""
|
||||||
|
local escaped_branch=""
|
||||||
|
local apps_entry=""
|
||||||
|
local metadata_entry=""
|
||||||
|
local built_apps_entries=""
|
||||||
|
local built_metadata_custom_entries=""
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if [ -n "${repo_feedback}" ]; then
|
||||||
|
repo_render_context=0
|
||||||
|
else
|
||||||
|
repo_render_context=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo_value="$(prompt_single_host_env_value "${stack_dir}" "CUSTOM_APP_${app_index}_REPO" "Enter git repository URL for custom app #${app_index}.\nType /done when finished or /back to return." "https://github.com/frappe/erpnext" "${repo_render_context}" "${repo_feedback}")"
|
||||||
|
prompt_status=$?
|
||||||
|
repo_feedback=""
|
||||||
|
if [ "${prompt_status}" -ne 0 ]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
repo_value="$(printf '%s' "${repo_value}" | tr -d '\r\n')"
|
||||||
|
|
||||||
|
case "${repo_value}" in
|
||||||
|
/back | /BACK | /Back | /cancel | /CANCEL | /Cancel)
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
/done | /DONE | /Done)
|
||||||
|
if [ "${app_count}" -eq 0 ]; then
|
||||||
|
repo_feedback="At least one custom app is required when 'Custom Git app(s)' is selected."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ! is_valid_git_repo_source "${repo_value}"; then
|
||||||
|
repo_feedback="Invalid git repository URL for custom app #${app_index}."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
branch_feedback=""
|
||||||
|
while true; do
|
||||||
|
if [ -n "${branch_feedback}" ]; then
|
||||||
|
branch_render_context=0
|
||||||
|
else
|
||||||
|
branch_render_context=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
branch_value="$(prompt_single_host_env_value "${stack_dir}" "CUSTOM_APP_${app_index}_BRANCH" "Enter git branch for custom app #${app_index}.\nType /back to return." "main" "${branch_render_context}" "${branch_feedback}")"
|
||||||
|
prompt_status=$?
|
||||||
|
branch_feedback=""
|
||||||
|
if [ "${prompt_status}" -ne 0 ]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
branch_value="$(printf '%s' "${branch_value}" | tr -d '\r\n')"
|
||||||
|
|
||||||
|
case "${branch_value}" in
|
||||||
|
/back | /BACK | /Back | /cancel | /CANCEL | /Cancel)
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ! is_valid_git_branch_name "${branch_value}"; then
|
||||||
|
branch_feedback="Invalid git branch for custom app #${app_index}."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
escaped_repo="$(json_escape_string "${repo_value}")"
|
||||||
|
escaped_branch="$(json_escape_string "${branch_value}")"
|
||||||
|
apps_entry="$(printf ' {"url": "%s", "branch": "%s"}' "${escaped_repo}" "${escaped_branch}")"
|
||||||
|
metadata_entry="$(printf ' {"repo": "%s", "branch": "%s"}' "${escaped_repo}" "${escaped_branch}")"
|
||||||
|
|
||||||
|
if [ -z "${built_apps_entries}" ]; then
|
||||||
|
built_apps_entries="${apps_entry}"
|
||||||
|
else
|
||||||
|
built_apps_entries="${built_apps_entries}"$',\n'"${apps_entry}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${built_metadata_custom_entries}" ]; then
|
||||||
|
built_metadata_custom_entries="${metadata_entry}"
|
||||||
|
else
|
||||||
|
built_metadata_custom_entries="${built_metadata_custom_entries}"$',\n'"${metadata_entry}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
app_count=$((app_count + 1))
|
||||||
|
app_index=$((app_index + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf -v "${result_apps_entries_var}" "%s" "${built_apps_entries}"
|
||||||
|
printf -v "${result_metadata_custom_var}" "%s" "${built_metadata_custom_entries}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_custom_modular_apps_data() {
|
||||||
|
local result_apps_metadata_var="${1}"
|
||||||
|
local stack_dir="${2}"
|
||||||
|
local back_option_label="${3:-Back to topology selection}"
|
||||||
|
local selection_raw=""
|
||||||
|
local selection=""
|
||||||
|
local preset_apps_csv=""
|
||||||
|
local has_custom_apps=0
|
||||||
|
local prompt_status=0
|
||||||
|
local preset_app=""
|
||||||
|
local preset_repo_url=""
|
||||||
|
local preset_branch=""
|
||||||
|
local escaped_url=""
|
||||||
|
local escaped_branch=""
|
||||||
|
local apps_entry=""
|
||||||
|
local metadata_predefined_entry=""
|
||||||
|
local custom_apps_entries=""
|
||||||
|
local metadata_custom_entries=""
|
||||||
|
local apps_entries=""
|
||||||
|
local metadata_predefined_entries=""
|
||||||
|
local built_apps_metadata_json_object=""
|
||||||
|
local -a selections=()
|
||||||
|
local -a preset_apps=()
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
selection_raw="$(show_custom_modular_apps_multi_select "${stack_dir}" "${back_option_label}" || true)"
|
||||||
|
prompt_status=$?
|
||||||
|
if [ "${prompt_status}" -ne 0 ]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${selection_raw}" ]; then
|
||||||
|
show_warning_message "Select at least one app option."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
preset_apps_csv=""
|
||||||
|
has_custom_apps=0
|
||||||
|
custom_apps_entries=""
|
||||||
|
metadata_custom_entries=""
|
||||||
|
apps_entries=""
|
||||||
|
metadata_predefined_entries=""
|
||||||
|
|
||||||
|
mapfile -t selections <<<"${selection_raw}"
|
||||||
|
for selection in "${selections[@]}"; do
|
||||||
|
case "${selection}" in
|
||||||
|
"ERPNext")
|
||||||
|
if [ -z "${preset_apps_csv}" ]; then
|
||||||
|
preset_apps_csv="erpnext"
|
||||||
|
else
|
||||||
|
preset_apps_csv="${preset_apps_csv},erpnext"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"CRM")
|
||||||
|
if [ -z "${preset_apps_csv}" ]; then
|
||||||
|
preset_apps_csv="crm"
|
||||||
|
else
|
||||||
|
preset_apps_csv="${preset_apps_csv},crm"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"Custom Git app(s)")
|
||||||
|
has_custom_apps=1
|
||||||
|
;;
|
||||||
|
"${back_option_label}")
|
||||||
|
if [ "${#selections[@]}" -eq 1 ]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
show_warning_message "Do not combine '${back_option_label}' with app selections."
|
||||||
|
preset_apps_csv=""
|
||||||
|
has_custom_apps=0
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_message "Unknown app selection: ${selection}"
|
||||||
|
preset_apps_csv=""
|
||||||
|
has_custom_apps=0
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${preset_apps_csv}" ] && [ "${has_custom_apps}" -eq 0 ]; then
|
||||||
|
show_warning_message "Select at least one app (ERPNext, CRM, or Custom Git app)."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
preset_branch="$(get_default_frappe_branch)"
|
||||||
|
if [ -n "${preset_apps_csv}" ]; then
|
||||||
|
IFS=',' read -r -a preset_apps <<<"${preset_apps_csv}"
|
||||||
|
for preset_app in "${preset_apps[@]}"; do
|
||||||
|
preset_repo_url="$(get_predefined_app_repo_url "${preset_app}" || true)"
|
||||||
|
if [ -z "${preset_repo_url}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
escaped_url="$(json_escape_string "${preset_repo_url}")"
|
||||||
|
escaped_branch="$(json_escape_string "${preset_branch}")"
|
||||||
|
apps_entry="$(printf ' {"url": "%s", "branch": "%s"}' "${escaped_url}" "${escaped_branch}")"
|
||||||
|
metadata_predefined_entry="$(printf ' "%s"' "${preset_app}")"
|
||||||
|
|
||||||
|
if [ -z "${apps_entries}" ]; then
|
||||||
|
apps_entries="${apps_entry}"
|
||||||
|
else
|
||||||
|
apps_entries="${apps_entries}"$',\n'"${apps_entry}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${metadata_predefined_entries}" ]; then
|
||||||
|
metadata_predefined_entries="${metadata_predefined_entry}"
|
||||||
|
else
|
||||||
|
metadata_predefined_entries="${metadata_predefined_entries}"$',\n'"${metadata_predefined_entry}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${has_custom_apps}" -eq 1 ]; then
|
||||||
|
if ! prompt_custom_git_apps_data custom_apps_entries metadata_custom_entries "${stack_dir}"; then
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${custom_apps_entries}" ]; then
|
||||||
|
if [ -z "${apps_entries}" ]; then
|
||||||
|
apps_entries="${custom_apps_entries}"
|
||||||
|
else
|
||||||
|
apps_entries="${apps_entries}"$',\n'"${custom_apps_entries}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${apps_entries}" ]; then
|
||||||
|
show_warning_message "No apps selected. Please choose at least one app."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
built_apps_metadata_json_object="$(printf '{\n "predefined": [\n%s\n ],\n "custom": [\n%s\n ]\n }' "${metadata_predefined_entries}" "${metadata_custom_entries}")"
|
||||||
|
|
||||||
|
printf -v "${result_apps_metadata_var}" "%s" "${built_apps_metadata_json_object}"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
update_stack_custom_modular_apps() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local metadata_path=""
|
||||||
|
local apps_metadata_json_object=""
|
||||||
|
local prompt_status=0
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
if [ ! -f "${metadata_path}" ]; then
|
||||||
|
return 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! prompt_custom_modular_apps_data apps_metadata_json_object "${stack_dir}" "Back"; then
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${apps_metadata_json_object}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! persist_stack_metadata_apps_object "${stack_dir}" "${apps_metadata_json_object}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
228
scripts/easy-docker/lib/app/wizard/env/collect.sh
vendored
Executable file
228
scripts/easy-docker/lib/app/wizard/env/collect.sh
vendored
Executable file
|
|
@ -0,0 +1,228 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
collect_single_host_env_lines() {
|
||||||
|
local result_env_var="${1}"
|
||||||
|
local result_apps_metadata_var="${2}"
|
||||||
|
local stack_dir="${3}"
|
||||||
|
local proxy_mode_id="${4}"
|
||||||
|
local database_id="${5}"
|
||||||
|
local collected_env_lines=""
|
||||||
|
local value=""
|
||||||
|
local domains_value=""
|
||||||
|
local domain_lines=""
|
||||||
|
local site_domains_value=""
|
||||||
|
local custom_image_value=""
|
||||||
|
local custom_tag_value=""
|
||||||
|
local selected_apps_metadata_json_object=""
|
||||||
|
local sites_rule_value=""
|
||||||
|
local nginx_proxy_hosts_value=""
|
||||||
|
local prompt_status=0
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation custom_image_value "${stack_dir}" "CUSTOM_IMAGE" "Required for custom modular image mode.\nExample: ghcr.io/acme/frappe-custom\nType /back to return." "ghcr.io/acme/frappe-custom" "required" "none"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "CUSTOM_IMAGE" "${custom_image_value}")"
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation custom_tag_value "${stack_dir}" "CUSTOM_TAG" "Required for custom modular image mode.\nExample: v1.0.0\nType /back to return." "v1.0.0" "required" "none"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "CUSTOM_TAG" "${custom_tag_value}")"
|
||||||
|
|
||||||
|
if ! prompt_custom_modular_apps_data selected_apps_metadata_json_object "${stack_dir}"; then
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${selected_apps_metadata_json_object}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${proxy_mode_id}" in
|
||||||
|
traefik-https)
|
||||||
|
if prompt_env_value_with_validation domains_value "${stack_dir}" "SITE_DOMAINS" "Required for Traefik HTTPS routing.\nUse only domains in format sub.domain.tld or sub.sub.domain.tld.\nEnter multiple domains separated by comma or space.\nType /back to return." "erp.example.com crm.eu.example.com" "required" "domains"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! parse_domains_input_to_lines domain_lines "${domains_value}"; then
|
||||||
|
show_warning_message "Could not parse SITE_DOMAINS."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
site_domains_value="$(domain_lines_to_csv "${domain_lines}")"
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "SITE_DOMAINS" "${site_domains_value}")"
|
||||||
|
|
||||||
|
sites_rule_value="$(domain_lines_to_sites_rule "${domain_lines}")"
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "SITES_RULE" "${sites_rule_value}")"
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "LETSENCRYPT_EMAIL" "Required for Let's Encrypt certificate registration.\nType /back to return." "admin@example.com" "required" "email"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "LETSENCRYPT_EMAIL" "${value}")"
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "HTTP_PUBLISH_PORT" "Optional. Press Enter to keep default 80.\nType /back to return." "80" "optional" "port"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "HTTP_PUBLISH_PORT" "${value}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "HTTPS_PUBLISH_PORT" "Optional. Press Enter to keep default 443.\nType /back to return." "443" "optional" "port"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "HTTPS_PUBLISH_PORT" "${value}")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
nginxproxy-https)
|
||||||
|
if prompt_env_value_with_validation domains_value "${stack_dir}" "SITE_DOMAINS" "Required for nginx-proxy routing.\nUse only domains in format sub.domain.tld or sub.sub.domain.tld.\nEnter multiple domains separated by comma or space.\nType /back to return." "erp.example.com crm.eu.example.com" "required" "domains"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! parse_domains_input_to_lines domain_lines "${domains_value}"; then
|
||||||
|
show_warning_message "Could not parse SITE_DOMAINS."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
site_domains_value="$(domain_lines_to_csv "${domain_lines}")"
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "SITE_DOMAINS" "${site_domains_value}")"
|
||||||
|
|
||||||
|
nginx_proxy_hosts_value="$(domain_lines_to_csv "${domain_lines}")"
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "NGINX_PROXY_HOSTS" "${nginx_proxy_hosts_value}")"
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "LETSENCRYPT_EMAIL" "Required for Let's Encrypt certificate registration.\nType /back to return." "admin@example.com" "required" "email"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "LETSENCRYPT_EMAIL" "${value}")"
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "HTTP_PUBLISH_PORT" "Optional. Press Enter to keep default 80.\nType /back to return." "80" "optional" "port"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "HTTP_PUBLISH_PORT" "${value}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "HTTPS_PUBLISH_PORT" "Optional. Press Enter to keep default 443.\nType /back to return." "443" "optional" "port"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "HTTPS_PUBLISH_PORT" "${value}")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
nginxproxy-http)
|
||||||
|
if prompt_env_value_with_validation domains_value "${stack_dir}" "SITE_DOMAINS" "Required for nginx-proxy routing.\nUse only domains in format sub.domain.tld or sub.sub.domain.tld.\nEnter multiple domains separated by comma or space.\nType /back to return." "erp.example.com crm.eu.example.com" "required" "domains"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! parse_domains_input_to_lines domain_lines "${domains_value}"; then
|
||||||
|
show_warning_message "Could not parse SITE_DOMAINS."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
site_domains_value="$(domain_lines_to_csv "${domain_lines}")"
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "SITE_DOMAINS" "${site_domains_value}")"
|
||||||
|
|
||||||
|
nginx_proxy_hosts_value="$(domain_lines_to_csv "${domain_lines}")"
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "NGINX_PROXY_HOSTS" "${nginx_proxy_hosts_value}")"
|
||||||
|
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "HTTP_PUBLISH_PORT" "Optional. Press Enter to keep default 80.\nType /back to return." "80" "optional" "port"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "HTTP_PUBLISH_PORT" "${value}")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
traefik-http)
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "HTTP_PUBLISH_PORT" "Optional. Press Enter to keep default 80.\nType /back to return." "80" "optional" "port"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "HTTP_PUBLISH_PORT" "${value}")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
caddy-external | no-proxy)
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "HTTP_PUBLISH_PORT" "Optional. Press Enter to keep default 8080 for no-proxy frontend publishing.\nType /back to return." "8080" "optional" "port"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "HTTP_PUBLISH_PORT" "${value}")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown proxy mode id: ${proxy_mode_id}" 2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "${database_id}" in
|
||||||
|
postgres)
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "DB_PASSWORD" "Required for PostgreSQL database service.\nType /back to return." "changeit" "required" "none"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "DB_PASSWORD" "${value}")"
|
||||||
|
;;
|
||||||
|
mariadb)
|
||||||
|
if prompt_env_value_with_validation value "${stack_dir}" "DB_PASSWORD" "Optional but recommended for MariaDB.\nPress Enter to use default from override.\nType /back to return." "changeit" "optional" "none"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
prompt_status=$?
|
||||||
|
return "${prompt_status}"
|
||||||
|
fi
|
||||||
|
if [ -n "${value}" ]; then
|
||||||
|
collected_env_lines="$(append_env_line "${collected_env_lines}" "DB_PASSWORD" "${value}")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown database id: ${database_id}" 2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf -v "${result_env_var}" "%s" "${collected_env_lines}"
|
||||||
|
printf -v "${result_apps_metadata_var}" "%s" "${selected_apps_metadata_json_object}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
418
scripts/easy-docker/lib/app/wizard/env/validation.sh
vendored
Executable file
418
scripts/easy-docker/lib/app/wizard/env/validation.sh
vendored
Executable file
|
|
@ -0,0 +1,418 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
is_valid_email_address() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
*@*.*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_port_number() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if ! [[ "${value}" =~ ^[0-9]+$ ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${value}" -lt 1 ] || [ "${value}" -gt 65535 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
EASY_DOCKER_LAST_INVALID_DOMAIN=""
|
||||||
|
|
||||||
|
reset_domain_validation_feedback() {
|
||||||
|
EASY_DOCKER_LAST_INVALID_DOMAIN=""
|
||||||
|
}
|
||||||
|
|
||||||
|
trim_domain_token() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local value="${2}"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "${value}" in
|
||||||
|
' '*)
|
||||||
|
value="${value# }"
|
||||||
|
;;
|
||||||
|
*' ')
|
||||||
|
value="${value% }"
|
||||||
|
;;
|
||||||
|
$'\t'*)
|
||||||
|
value="${value#$'\t'}"
|
||||||
|
;;
|
||||||
|
*$'\t')
|
||||||
|
value="${value%$'\t'}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${value}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_domain_token() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local raw_token="${2}"
|
||||||
|
local token="${raw_token}"
|
||||||
|
|
||||||
|
trim_domain_token token "${token}"
|
||||||
|
if [ -z "${token}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "${token}" in
|
||||||
|
\"*\")
|
||||||
|
token="${token#\"}"
|
||||||
|
token="${token%\"}"
|
||||||
|
;;
|
||||||
|
\'*\')
|
||||||
|
token="${token#\'}"
|
||||||
|
token="${token%\'}"
|
||||||
|
;;
|
||||||
|
\`*\`)
|
||||||
|
token="${token#\`}"
|
||||||
|
token="${token%\`}"
|
||||||
|
;;
|
||||||
|
\[*\])
|
||||||
|
token="${token#\[}"
|
||||||
|
token="${token%\]}"
|
||||||
|
;;
|
||||||
|
\(*\))
|
||||||
|
token="${token#(}"
|
||||||
|
token="${token%)}"
|
||||||
|
;;
|
||||||
|
\"*)
|
||||||
|
token="${token#\"}"
|
||||||
|
;;
|
||||||
|
\'*)
|
||||||
|
token="${token#\'}"
|
||||||
|
;;
|
||||||
|
\`*)
|
||||||
|
token="${token#\`}"
|
||||||
|
;;
|
||||||
|
\[*)
|
||||||
|
token="${token#\[}"
|
||||||
|
;;
|
||||||
|
\(*)
|
||||||
|
token="${token#(}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "${token}" in
|
||||||
|
*\")
|
||||||
|
token="${token%\"}"
|
||||||
|
;;
|
||||||
|
*\')
|
||||||
|
token="${token%\'}"
|
||||||
|
;;
|
||||||
|
*\`)
|
||||||
|
token="${token%\`}"
|
||||||
|
;;
|
||||||
|
*\])
|
||||||
|
token="${token%\]}"
|
||||||
|
;;
|
||||||
|
*\))
|
||||||
|
token="${token%)}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
trim_domain_token token "${token}"
|
||||||
|
if [ -z "${token}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${token}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_domain_name() {
|
||||||
|
local domain="${1}"
|
||||||
|
local normalized_domain=""
|
||||||
|
local label=""
|
||||||
|
local tld=""
|
||||||
|
local last_index=0
|
||||||
|
local -a labels=()
|
||||||
|
|
||||||
|
if ! normalize_domain_token normalized_domain "${domain}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${normalized_domain}" in
|
||||||
|
*[[:space:],:/?#!@]* | *";"* | .* | *. | *..* | *\**)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "${#normalized_domain}" -lt 5 ] || [ "${#normalized_domain}" -gt 253 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local IFS='.'
|
||||||
|
read -r -a labels <<<"${normalized_domain}"
|
||||||
|
if [ "${#labels[@]}" -ne 3 ] && [ "${#labels[@]}" -ne 4 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for label in "${labels[@]}"; do
|
||||||
|
if [ -z "${label}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#label}" -gt 63 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${label}" in
|
||||||
|
[A-Za-z0-9]*) ;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "${label}" in
|
||||||
|
*[A-Za-z0-9]) ;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "${label}" in
|
||||||
|
*[!A-Za-z0-9-]*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
last_index=$((${#labels[@]} - 1))
|
||||||
|
tld="${labels[last_index]}"
|
||||||
|
if ! [[ "${tld}" =~ ^[A-Za-z]{2,63}$ ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_domains_input_to_lines() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local raw_value="${2}"
|
||||||
|
local sanitized_value=""
|
||||||
|
local token=""
|
||||||
|
local normalized_token=""
|
||||||
|
local parsed_domain_lines=""
|
||||||
|
local -a tokens=()
|
||||||
|
local IFS=$' \t\n'
|
||||||
|
|
||||||
|
reset_domain_validation_feedback
|
||||||
|
|
||||||
|
sanitized_value="${raw_value//$'\r'/ }"
|
||||||
|
sanitized_value="${sanitized_value//$'\n'/ }"
|
||||||
|
sanitized_value="${sanitized_value//$'\t'/ }"
|
||||||
|
sanitized_value="${sanitized_value//,/ }"
|
||||||
|
sanitized_value="${sanitized_value//;/ }"
|
||||||
|
|
||||||
|
read -r -a tokens <<<"${sanitized_value}"
|
||||||
|
if [ "${#tokens[@]}" -eq 0 ]; then
|
||||||
|
EASY_DOCKER_LAST_INVALID_DOMAIN="${raw_value}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for token in "${tokens[@]}"; do
|
||||||
|
if ! normalize_domain_token normalized_token "${token}"; then
|
||||||
|
EASY_DOCKER_LAST_INVALID_DOMAIN="${token}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_domain_name "${normalized_token}"; then
|
||||||
|
EASY_DOCKER_LAST_INVALID_DOMAIN="${normalized_token}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${parsed_domain_lines}" ]; then
|
||||||
|
parsed_domain_lines="${normalized_token}"
|
||||||
|
else
|
||||||
|
parsed_domain_lines="${parsed_domain_lines}"$'\n'"${normalized_token}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${parsed_domain_lines}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${parsed_domain_lines}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
domain_lines_to_csv() {
|
||||||
|
local domain_lines="${1}"
|
||||||
|
local domain=""
|
||||||
|
local csv_value=""
|
||||||
|
|
||||||
|
while IFS= read -r domain; do
|
||||||
|
if [ -z "${domain}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${csv_value}" ]; then
|
||||||
|
csv_value="${domain}"
|
||||||
|
else
|
||||||
|
csv_value="${csv_value},${domain}"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${domain_lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
printf '%s' "${csv_value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
domain_lines_to_sites_rule() {
|
||||||
|
local domain_lines="${1}"
|
||||||
|
local domain=""
|
||||||
|
local sites_rule=""
|
||||||
|
local rule_part=""
|
||||||
|
|
||||||
|
while IFS= read -r domain; do
|
||||||
|
if [ -z "${domain}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
rule_part="$(printf "Host(\`%s\`)" "${domain}")"
|
||||||
|
if [ -z "${sites_rule}" ]; then
|
||||||
|
sites_rule="${rule_part}"
|
||||||
|
else
|
||||||
|
sites_rule="${sites_rule} || ${rule_part}"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${domain_lines}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
printf '%s' "${sites_rule}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_domain_list_value() {
|
||||||
|
local value="${1}"
|
||||||
|
local domain_lines=""
|
||||||
|
|
||||||
|
if ! parse_domains_input_to_lines domain_lines "${value}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${domain_lines}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_domains_value() {
|
||||||
|
local value="${1}"
|
||||||
|
|
||||||
|
if ! is_valid_domain_list_value "${value}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_env_value_with_validation() {
|
||||||
|
local result_var="${1}"
|
||||||
|
local stack_dir="${2}"
|
||||||
|
local variable_name="${3}"
|
||||||
|
local guidance_text="${4}"
|
||||||
|
local placeholder="${5}"
|
||||||
|
local required_mode="${6}"
|
||||||
|
local validation_kind="${7}"
|
||||||
|
local input_value=""
|
||||||
|
local normalized_value=""
|
||||||
|
local invalid_domain_input=""
|
||||||
|
local validation_feedback=""
|
||||||
|
local prompt_status=0
|
||||||
|
local is_first_prompt=1
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
input_value="$(prompt_single_host_env_value "${stack_dir}" "${variable_name}" "${guidance_text}" "${placeholder}" "${is_first_prompt}" "${validation_feedback}")"
|
||||||
|
prompt_status=$?
|
||||||
|
is_first_prompt=0
|
||||||
|
validation_feedback=""
|
||||||
|
if [ "${prompt_status}" -ne 0 ]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
normalized_value="$(printf '%s' "${input_value}" | tr -d '\r\n')"
|
||||||
|
|
||||||
|
case "${normalized_value}" in
|
||||||
|
/back | /BACK | /Back | /cancel | /CANCEL | /Cancel)
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "${normalized_value}" ]; then
|
||||||
|
if [ "${required_mode}" = "required" ]; then
|
||||||
|
validation_feedback="Value required for ${variable_name}."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${validation_kind}" in
|
||||||
|
email)
|
||||||
|
if ! is_valid_email_address "${normalized_value}"; then
|
||||||
|
validation_feedback="Invalid email format for ${variable_name}."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
port)
|
||||||
|
if ! is_valid_port_number "${normalized_value}"; then
|
||||||
|
validation_feedback="Invalid port for ${variable_name}. Use 1-65535."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
domains)
|
||||||
|
if ! is_valid_domains_value "${normalized_value}"; then
|
||||||
|
invalid_domain_input="${EASY_DOCKER_LAST_INVALID_DOMAIN}"
|
||||||
|
if [ -z "${invalid_domain_input}" ]; then
|
||||||
|
invalid_domain_input="${normalized_value}"
|
||||||
|
fi
|
||||||
|
validation_feedback="Domain '${invalid_domain_input}' cannot be used for ${variable_name}. Use sub.domain.tld or sub.sub.domain.tld."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
nginx_hosts)
|
||||||
|
if ! is_valid_domains_value "${normalized_value}"; then
|
||||||
|
validation_feedback="Invalid ${variable_name}. Use domains separated by comma or space."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
none | "") ;;
|
||||||
|
*)
|
||||||
|
show_warning_message "Unknown validation rule: ${validation_kind}"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf -v "${result_var}" "%s" "${normalized_value}"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
}
|
||||||
17
scripts/easy-docker/lib/app/wizard/flows.sh
Executable file
17
scripts/easy-docker/lib/app/wizard/flows.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
load_easy_docker_wizard_flow_modules() {
|
||||||
|
local wizard_dir=""
|
||||||
|
wizard_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/single_host.sh
|
||||||
|
source "${wizard_dir}/flows/single_host.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/manage.sh
|
||||||
|
source "${wizard_dir}/flows/manage.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/navigation.sh
|
||||||
|
source "${wizard_dir}/flows/navigation.sh"
|
||||||
|
# shellcheck source=scripts/easy-docker/lib/app/wizard/flows/setup.sh
|
||||||
|
source "${wizard_dir}/flows/setup.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_easy_docker_wizard_flow_modules
|
||||||
116
scripts/easy-docker/lib/app/wizard/flows/manage.sh
Executable file
116
scripts/easy-docker/lib/app/wizard/flows/manage.sh
Executable file
|
|
@ -0,0 +1,116 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
handle_manage_selected_stack_flow() {
|
||||||
|
local stack_name="${1}"
|
||||||
|
local stack_dir=""
|
||||||
|
local stack_action=""
|
||||||
|
local apps_action=""
|
||||||
|
local docker_action=""
|
||||||
|
local stack_metadata_path=""
|
||||||
|
local stack_apps_path=""
|
||||||
|
local custom_apps_update_status=0
|
||||||
|
local persist_apps_status=0
|
||||||
|
local render_compose_status=0
|
||||||
|
local generated_compose_path=""
|
||||||
|
|
||||||
|
stack_dir="$(get_stack_dir_by_name "${stack_name}" || true)"
|
||||||
|
if [ -z "${stack_dir}" ]; then
|
||||||
|
show_warning_and_wait "Could not resolve stack directory for '${stack_name}'." 2
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
stack_action="$(show_manage_stack_actions_menu "${stack_name}" "${stack_dir}" || true)"
|
||||||
|
case "${stack_action}" in
|
||||||
|
"Apps")
|
||||||
|
while true; do
|
||||||
|
apps_action="$(show_manage_stack_apps_menu "${stack_name}" "${stack_dir}" || true)"
|
||||||
|
case "${apps_action}" in
|
||||||
|
"Generate apps.json")
|
||||||
|
stack_metadata_path="${stack_dir}/metadata.json"
|
||||||
|
stack_apps_path="${stack_dir}/apps.json"
|
||||||
|
if [ ! -f "${stack_metadata_path}" ]; then
|
||||||
|
show_warning_and_wait "Cannot generate apps.json because metadata is missing: ${stack_metadata_path}" 3
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
||||||
|
persist_apps_status=$?
|
||||||
|
show_warning_and_wait "Could not generate ${stack_apps_path} (${persist_apps_status})." 3
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_warning_and_wait "apps.json generated successfully: ${stack_apps_path}" 3
|
||||||
|
;;
|
||||||
|
"Update custom image apps")
|
||||||
|
if ! update_stack_custom_modular_apps "${stack_dir}"; then
|
||||||
|
custom_apps_update_status=$?
|
||||||
|
case "${custom_apps_update_status}" in
|
||||||
|
2)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
stack_metadata_path="${stack_dir}/metadata.json"
|
||||||
|
show_warning_and_wait "Cannot update custom image apps because metadata is missing: ${stack_metadata_path}" 3
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Could not update custom image apps (${custom_apps_update_status}) for stack: ${stack_name}" 3
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
stack_apps_path="${stack_dir}/apps.json"
|
||||||
|
show_warning_and_wait "Custom image apps updated in ${stack_dir}/metadata.json and ${stack_apps_path}." 3
|
||||||
|
;;
|
||||||
|
"Back" | "")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
"Exit and close easy-docker")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown apps action: ${apps_action}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
"Docker")
|
||||||
|
while true; do
|
||||||
|
docker_action="$(show_manage_stack_docker_menu "${stack_name}" "${stack_dir}" || true)"
|
||||||
|
case "${docker_action}" in
|
||||||
|
"Generate docker compose from env")
|
||||||
|
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
|
||||||
|
if ! render_stack_compose_from_metadata "${stack_dir}"; then
|
||||||
|
render_compose_status=$?
|
||||||
|
show_warning_and_wait "Could not generate docker compose (${render_compose_status}) for ${generated_compose_path}." 3
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_warning_and_wait "Docker compose generated successfully: ${generated_compose_path}" 3
|
||||||
|
;;
|
||||||
|
"Back" | "")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
"Exit and close easy-docker")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown docker action: ${docker_action}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
"Back" | "")
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
"Exit and close easy-docker")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown stack action: ${stack_action}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
68
scripts/easy-docker/lib/app/wizard/flows/navigation.sh
Executable file
68
scripts/easy-docker/lib/app/wizard/flows/navigation.sh
Executable file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
handle_abort_wizard_flow() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local abort_action=""
|
||||||
|
local rollback_status=0
|
||||||
|
|
||||||
|
abort_action="$(show_abort_wizard_prompt "${stack_dir}" || true)"
|
||||||
|
case "${abort_action}" in
|
||||||
|
"Rollback files and return to main menu")
|
||||||
|
if rollback_stack_directory "${stack_dir}"; then
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rollback_status=$?
|
||||||
|
if [ "${rollback_status}" -eq 2 ]; then
|
||||||
|
show_warning_and_wait "Refused rollback for unsafe path: ${stack_dir}" 2
|
||||||
|
else
|
||||||
|
show_warning_and_wait "Could not rollback stack files: ${stack_dir}" 2
|
||||||
|
fi
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
"Keep files and return to main menu")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
"Back to topology selection" | "")
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown abort action: ${abort_action}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_stack_topology_flow() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local topology_action=""
|
||||||
|
local abort_status=0
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
topology_action="$(show_stack_topology_menu "${stack_dir}" || true)"
|
||||||
|
case "${topology_action}" in
|
||||||
|
"Single-host" | "Single-host (recommended)")
|
||||||
|
handle_single_host_stack_flow "${stack_dir}"
|
||||||
|
;;
|
||||||
|
"Split services")
|
||||||
|
handle_topology_examples_flow "${topology_action}"
|
||||||
|
;;
|
||||||
|
"Abort wizard to main menu")
|
||||||
|
handle_abort_wizard_flow "${stack_dir}"
|
||||||
|
abort_status=$?
|
||||||
|
case "${abort_status}" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown topology selection: ${topology_action}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
281
scripts/easy-docker/lib/app/wizard/flows/setup.sh
Executable file
281
scripts/easy-docker/lib/app/wizard/flows/setup.sh
Executable file
|
|
@ -0,0 +1,281 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
handle_create_new_stack_flow() {
|
||||||
|
local setup_type="${1:-production}"
|
||||||
|
local stack_name=""
|
||||||
|
local stack_dir=""
|
||||||
|
local create_stack_status=0
|
||||||
|
local stack_input_status=0
|
||||||
|
local topology_status=0
|
||||||
|
|
||||||
|
case "${setup_type}" in
|
||||||
|
production | development) ;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown setup type: ${setup_type}" 2
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
stack_name=""
|
||||||
|
if prompt_stack_name_with_cancel stack_name; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
stack_input_status=$?
|
||||||
|
if [ "${stack_input_status}" -eq "${FLOW_ABORT_INPUT}" ]; then
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_warning_and_wait "Input canceled."
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${stack_name}" ]; then
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_valid_stack_name "${stack_name}"; then
|
||||||
|
show_warning_and_wait "Invalid stack name. Use letters, numbers, dot, underscore, or hyphen." 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
stack_dir=""
|
||||||
|
if create_stack_directory_with_metadata stack_dir "${stack_name}" "${setup_type}"; then
|
||||||
|
handle_stack_topology_flow "${stack_dir}"
|
||||||
|
topology_status=$?
|
||||||
|
case "${topology_status}" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
create_stack_status=$?
|
||||||
|
if [ "${create_stack_status}" -eq 2 ]; then
|
||||||
|
show_warning_and_wait "Stack already exists: ${stack_name}" 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_warning_and_wait "Could not create stack directory for: ${stack_name}" 2
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_manage_existing_stacks_flow() {
|
||||||
|
local setup_type="${1:-production}"
|
||||||
|
local manage_action=""
|
||||||
|
local selected_stack_status=0
|
||||||
|
local stack_names_raw=""
|
||||||
|
local -a stack_names=()
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
stack_names_raw="$(list_existing_stack_names "${setup_type}")"
|
||||||
|
if [ -z "${stack_names_raw}" ]; then
|
||||||
|
manage_action="$(show_manage_stacks_placeholder "${setup_type}" || true)"
|
||||||
|
else
|
||||||
|
mapfile -t stack_names <<<"${stack_names_raw}"
|
||||||
|
manage_action="$(show_manage_stacks_menu "${setup_type}" "${stack_names[@]}" || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${manage_action}" in
|
||||||
|
"Back" | "")
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
"Exit and close easy-docker")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ -n "${stack_names_raw}" ] && stack_name_in_array "${manage_action}" "${stack_names[@]}"; then
|
||||||
|
if handle_manage_selected_stack_flow "${manage_action}"; then
|
||||||
|
selected_stack_status="${FLOW_CONTINUE}"
|
||||||
|
else
|
||||||
|
selected_stack_status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${selected_stack_status}" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
show_warning_and_wait "Unknown manage-stacks action: ${manage_action}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_setup_flow() {
|
||||||
|
local setup_type="${1}"
|
||||||
|
local setup_action=""
|
||||||
|
|
||||||
|
case "${setup_type}" in
|
||||||
|
production | development) ;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown setup type: ${setup_type}" 2
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "${setup_type}" in
|
||||||
|
production)
|
||||||
|
setup_action="$(show_production_setup_menu || true)"
|
||||||
|
;;
|
||||||
|
development)
|
||||||
|
setup_action="$(show_development_setup_menu || true)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "${setup_action}" in
|
||||||
|
"Create new stack")
|
||||||
|
if handle_create_new_stack_flow "${setup_type}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
case "$?" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"Manage existing stacks")
|
||||||
|
if handle_manage_existing_stacks_flow "${setup_type}"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
case "$?" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"Back" | "")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
"Exit and close easy-docker")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown ${setup_type} action: ${setup_action}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_production_setup_flow() {
|
||||||
|
handle_setup_flow "production"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_development_setup_flow() {
|
||||||
|
handle_setup_flow "development"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_environment_check_flow() {
|
||||||
|
local environment_action=""
|
||||||
|
|
||||||
|
environment_action="$(show_environment_status || true)"
|
||||||
|
case "${environment_action}" in
|
||||||
|
"Back to main menu" | "")
|
||||||
|
return "${FLOW_BACK_TO_MAIN}"
|
||||||
|
;;
|
||||||
|
"Exit and close easy-docker")
|
||||||
|
return "${FLOW_EXIT_APP}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown environment action: ${environment_action}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
run_easy_docker_app() {
|
||||||
|
local action=""
|
||||||
|
local handler_status=0
|
||||||
|
|
||||||
|
enter_alt_screen
|
||||||
|
render_main_screen 1
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
action="$(show_main_menu || true)"
|
||||||
|
|
||||||
|
if [ -z "${action}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${action}" in
|
||||||
|
"Production Stack")
|
||||||
|
if handle_production_setup_flow; then
|
||||||
|
handler_status="${FLOW_CONTINUE}"
|
||||||
|
else
|
||||||
|
handler_status=$?
|
||||||
|
fi
|
||||||
|
case "${handler_status}" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
render_main_screen 1
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
"Development Stack")
|
||||||
|
if handle_development_setup_flow; then
|
||||||
|
handler_status="${FLOW_CONTINUE}"
|
||||||
|
else
|
||||||
|
handler_status=$?
|
||||||
|
fi
|
||||||
|
case "${handler_status}" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
render_main_screen 1
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
"Environment check")
|
||||||
|
if handle_environment_check_flow; then
|
||||||
|
handler_status="${FLOW_CONTINUE}"
|
||||||
|
else
|
||||||
|
handler_status=$?
|
||||||
|
fi
|
||||||
|
case "${handler_status}" in
|
||||||
|
"${FLOW_BACK_TO_MAIN}")
|
||||||
|
render_main_screen 1
|
||||||
|
;;
|
||||||
|
"${FLOW_EXIT_APP}")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
"Exit")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown action: ${action}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
111
scripts/easy-docker/lib/app/wizard/flows/single_host.sh
Executable file
111
scripts/easy-docker/lib/app/wizard/flows/single_host.sh
Executable file
|
|
@ -0,0 +1,111 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly FLOW_BACK_TO_MAIN=10
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
readonly FLOW_EXIT_APP=11
|
||||||
|
|
||||||
|
handle_single_host_stack_flow() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local proxy_mode=""
|
||||||
|
local database_choice=""
|
||||||
|
local redis_choice=""
|
||||||
|
local stack_env_path=""
|
||||||
|
local stack_apps_path=""
|
||||||
|
local generated_compose_path=""
|
||||||
|
local save_selection_status=0
|
||||||
|
local render_compose_status=0
|
||||||
|
|
||||||
|
proxy_mode="$(show_single_host_proxy_menu "${stack_dir}" || true)"
|
||||||
|
case "${proxy_mode}" in
|
||||||
|
"Back to topology selection" | "")
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if ! get_single_host_proxy_mode_id "${proxy_mode}" >/dev/null; then
|
||||||
|
show_warning_and_wait "Unknown proxy mode: ${proxy_mode}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
database_choice="$(show_single_host_database_menu "${stack_dir}" || true)"
|
||||||
|
case "${database_choice}" in
|
||||||
|
"Back to topology selection" | "")
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if ! get_single_host_database_id "${database_choice}" >/dev/null; then
|
||||||
|
show_warning_and_wait "Unknown database choice: ${database_choice}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
redis_choice="$(show_single_host_redis_menu "${stack_dir}" || true)"
|
||||||
|
case "${redis_choice}" in
|
||||||
|
"Back to topology selection" | "")
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if ! get_single_host_redis_id "${redis_choice}" >/dev/null; then
|
||||||
|
show_warning_and_wait "Unknown Redis choice: ${redis_choice}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ! save_single_host_selection "${stack_dir}" "${proxy_mode}" "${database_choice}" "${redis_choice}"; then
|
||||||
|
save_selection_status=$?
|
||||||
|
if [ "${save_selection_status}" -eq 2 ]; then
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_warning_and_wait "Could not save single-host selection for stack: ${stack_dir}" 2
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! render_stack_compose_from_metadata "${stack_dir}"; then
|
||||||
|
render_compose_status=$?
|
||||||
|
stack_env_path="$(get_stack_env_path "${stack_dir}")"
|
||||||
|
stack_apps_path="${stack_dir}/apps.json"
|
||||||
|
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
|
||||||
|
show_warning_and_wait "Selection saved in ${stack_dir}/metadata.json, ${stack_env_path}, and ${stack_apps_path}, but compose rendering failed (${render_compose_status}) for ${generated_compose_path}." 3
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
stack_env_path="$(get_stack_env_path "${stack_dir}")"
|
||||||
|
stack_apps_path="${stack_dir}/apps.json"
|
||||||
|
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
|
||||||
|
show_warning_and_wait "Single-host selection saved in ${stack_dir}/metadata.json, ${stack_env_path}, and ${stack_apps_path}. Rendered compose: ${generated_compose_path}." 3
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_topology_examples_flow() {
|
||||||
|
local topology_name="${1}"
|
||||||
|
local detail_action=""
|
||||||
|
|
||||||
|
case "${topology_name}" in
|
||||||
|
"Split services")
|
||||||
|
detail_action="$(show_split_services_examples || true)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown topology: ${topology_name}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "${detail_action}" in
|
||||||
|
"Use this topology")
|
||||||
|
show_warning_and_wait "Topology '${topology_name}' selected. Next wizard step is coming soon." 2
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
"Back to topology selection" | "")
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_warning_and_wait "Unknown topology action: ${detail_action}"
|
||||||
|
return "${FLOW_CONTINUE}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
289
scripts/easy-docker/lib/app/wizard/single_host.sh
Executable file
289
scripts/easy-docker/lib/app/wizard/single_host.sh
Executable file
|
|
@ -0,0 +1,289 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
get_single_host_proxy_mode_id() {
|
||||||
|
local proxy_mode="${1}"
|
||||||
|
|
||||||
|
case "${proxy_mode}" in
|
||||||
|
"Traefik (HTTP, built-in proxy)")
|
||||||
|
printf 'traefik-http\n'
|
||||||
|
;;
|
||||||
|
"Traefik (HTTPS + Let's Encrypt)")
|
||||||
|
printf 'traefik-https\n'
|
||||||
|
;;
|
||||||
|
"nginx-proxy (HTTP)")
|
||||||
|
printf 'nginxproxy-http\n'
|
||||||
|
;;
|
||||||
|
"nginx-proxy + acme-companion (HTTPS)")
|
||||||
|
printf 'nginxproxy-https\n'
|
||||||
|
;;
|
||||||
|
"Caddy (external reverse proxy)")
|
||||||
|
printf 'caddy-external\n'
|
||||||
|
;;
|
||||||
|
"No reverse proxy (direct :8080)")
|
||||||
|
printf 'no-proxy\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_single_host_proxy_overrides() {
|
||||||
|
local proxy_mode="${1}"
|
||||||
|
|
||||||
|
case "${proxy_mode}" in
|
||||||
|
"Traefik (HTTP, built-in proxy)")
|
||||||
|
printf 'overrides/compose.proxy.yaml\n'
|
||||||
|
;;
|
||||||
|
"Traefik (HTTPS + Let's Encrypt)")
|
||||||
|
printf 'overrides/compose.https.yaml\n'
|
||||||
|
;;
|
||||||
|
"nginx-proxy (HTTP)")
|
||||||
|
printf 'overrides/compose.nginxproxy.yaml\n'
|
||||||
|
;;
|
||||||
|
"nginx-proxy + acme-companion (HTTPS)")
|
||||||
|
printf 'overrides/compose.nginxproxy.yaml\noverrides/compose.nginxproxy-ssl.yaml\n'
|
||||||
|
;;
|
||||||
|
"Caddy (external reverse proxy)" | "No reverse proxy (direct :8080)")
|
||||||
|
printf 'overrides/compose.noproxy.yaml\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_single_host_database_id() {
|
||||||
|
local database_choice="${1}"
|
||||||
|
|
||||||
|
case "${database_choice}" in
|
||||||
|
"MariaDB (recommended)")
|
||||||
|
printf 'mariadb\n'
|
||||||
|
;;
|
||||||
|
"PostgreSQL")
|
||||||
|
printf 'postgres\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_single_host_database_override() {
|
||||||
|
local database_choice="${1}"
|
||||||
|
|
||||||
|
case "${database_choice}" in
|
||||||
|
"MariaDB (recommended)")
|
||||||
|
printf 'overrides/compose.mariadb.yaml\n'
|
||||||
|
;;
|
||||||
|
"PostgreSQL")
|
||||||
|
printf 'overrides/compose.postgres.yaml\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_single_host_redis_id() {
|
||||||
|
local redis_choice="${1}"
|
||||||
|
|
||||||
|
case "${redis_choice}" in
|
||||||
|
"Include Redis (recommended)")
|
||||||
|
printf 'enabled\n'
|
||||||
|
;;
|
||||||
|
"Skip Redis (experienced users only)")
|
||||||
|
printf 'disabled\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_single_host_redis_override() {
|
||||||
|
local redis_choice="${1}"
|
||||||
|
|
||||||
|
case "${redis_choice}" in
|
||||||
|
"Include Redis (recommended)")
|
||||||
|
printf 'overrides/compose.redis.yaml\n'
|
||||||
|
;;
|
||||||
|
"Skip Redis (experienced users only)")
|
||||||
|
printf ''
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_single_host_env_file() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local env_lines="${2}"
|
||||||
|
local env_path=""
|
||||||
|
local env_tmp_path=""
|
||||||
|
local generated_at=""
|
||||||
|
|
||||||
|
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||||
|
env_tmp_path="${env_path}.tmp"
|
||||||
|
generated_at="$(get_current_utc_timestamp)"
|
||||||
|
|
||||||
|
if ! {
|
||||||
|
printf '# Generated by easy-docker wizard at %s\n' "${generated_at}"
|
||||||
|
printf '# Adjust values as needed for this stack.\n'
|
||||||
|
if [ -n "${env_lines}" ]; then
|
||||||
|
printf '\n%s\n' "${env_lines}"
|
||||||
|
else
|
||||||
|
printf '\n# No additional environment variables configured by the wizard.\n'
|
||||||
|
fi
|
||||||
|
} >"${env_tmp_path}"; then
|
||||||
|
rm -f -- "${env_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -- "${env_tmp_path}" "${env_path}"; then
|
||||||
|
rm -f -- "${env_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_single_host_selection_metadata() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local proxy_mode_id="${2}"
|
||||||
|
local database_id="${3}"
|
||||||
|
local redis_id="${4}"
|
||||||
|
local compose_files_lines="${5}"
|
||||||
|
local apps_json_object="${6}"
|
||||||
|
local env_lines="${7}"
|
||||||
|
local metadata_path=""
|
||||||
|
local metadata_tmp_path=""
|
||||||
|
local schema_version=""
|
||||||
|
local stack_name=""
|
||||||
|
local setup_type=""
|
||||||
|
local created_at=""
|
||||||
|
local updated_at=""
|
||||||
|
local compose_files_json=""
|
||||||
|
local env_json_object=""
|
||||||
|
|
||||||
|
metadata_path="${stack_dir}/metadata.json"
|
||||||
|
metadata_tmp_path="${metadata_path}.tmp"
|
||||||
|
|
||||||
|
schema_version="$(get_metadata_number_field "${metadata_path}" "schema_version" || true)"
|
||||||
|
if [ -z "${schema_version}" ]; then
|
||||||
|
schema_version="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
stack_name="$(get_metadata_string_field "${metadata_path}" "stack_name" || true)"
|
||||||
|
if [ -z "${stack_name}" ]; then
|
||||||
|
stack_name="${stack_dir##*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
setup_type="$(get_metadata_string_field "${metadata_path}" "setup_type" || true)"
|
||||||
|
if [ -z "${setup_type}" ]; then
|
||||||
|
setup_type="production"
|
||||||
|
fi
|
||||||
|
|
||||||
|
created_at="$(get_metadata_string_field "${metadata_path}" "created_at" || true)"
|
||||||
|
if [ -z "${created_at}" ]; then
|
||||||
|
created_at="$(get_current_utc_timestamp)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
updated_at="$(get_current_utc_timestamp)"
|
||||||
|
compose_files_json="$(build_compose_files_json_array "${compose_files_lines}")"
|
||||||
|
env_json_object="$(build_env_json_object "${env_lines}")"
|
||||||
|
if [ -z "${apps_json_object}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! cat >"${metadata_tmp_path}" <<EOF; then
|
||||||
|
{
|
||||||
|
"schema_version": ${schema_version},
|
||||||
|
"stack_name": "${stack_name}",
|
||||||
|
"setup_type": "${setup_type}",
|
||||||
|
"created_at": "${created_at}",
|
||||||
|
"apps": ${apps_json_object},
|
||||||
|
"wizard": {
|
||||||
|
"topology": "single-host",
|
||||||
|
"selection": {
|
||||||
|
"proxy_mode_id": "${proxy_mode_id}",
|
||||||
|
"database_id": "${database_id}",
|
||||||
|
"redis_id": "${redis_id}"
|
||||||
|
},
|
||||||
|
"env": ${env_json_object},
|
||||||
|
"compose_files": [
|
||||||
|
${compose_files_json}
|
||||||
|
],
|
||||||
|
"updated_at": "${updated_at}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mv -- "${metadata_tmp_path}" "${metadata_path}"; then
|
||||||
|
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
save_single_host_selection() {
|
||||||
|
local stack_dir="${1}"
|
||||||
|
local proxy_mode="${2}"
|
||||||
|
local database_choice="${3}"
|
||||||
|
local redis_choice="${4}"
|
||||||
|
local proxy_mode_id=""
|
||||||
|
local database_id=""
|
||||||
|
local redis_id=""
|
||||||
|
local database_override=""
|
||||||
|
local redis_override=""
|
||||||
|
local proxy_overrides=""
|
||||||
|
local compose_files_lines=""
|
||||||
|
local env_lines=""
|
||||||
|
local apps_metadata_json_object=""
|
||||||
|
local collect_env_status=0
|
||||||
|
|
||||||
|
proxy_mode_id="$(get_single_host_proxy_mode_id "${proxy_mode}")" || return 1
|
||||||
|
database_id="$(get_single_host_database_id "${database_choice}")" || return 1
|
||||||
|
redis_id="$(get_single_host_redis_id "${redis_choice}")" || return 1
|
||||||
|
|
||||||
|
database_override="$(get_single_host_database_override "${database_choice}")" || return 1
|
||||||
|
redis_override="$(get_single_host_redis_override "${redis_choice}")" || return 1
|
||||||
|
proxy_overrides="$(get_single_host_proxy_overrides "${proxy_mode}")" || return 1
|
||||||
|
|
||||||
|
compose_files_lines="$(printf 'compose.yaml\n%s' "${database_override}")"
|
||||||
|
if [ -n "${redis_override}" ]; then
|
||||||
|
compose_files_lines="$(printf '%s\n%s' "${compose_files_lines}" "${redis_override}")"
|
||||||
|
fi
|
||||||
|
compose_files_lines="$(printf '%s\n%s' "${compose_files_lines}" "${proxy_overrides}")"
|
||||||
|
|
||||||
|
if ! collect_single_host_env_lines env_lines apps_metadata_json_object "${stack_dir}" "${proxy_mode_id}" "${database_id}"; then
|
||||||
|
collect_env_status=$?
|
||||||
|
return "${collect_env_status}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! persist_single_host_env_file "${stack_dir}" "${env_lines}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! persist_single_host_selection_metadata \
|
||||||
|
"${stack_dir}" \
|
||||||
|
"${proxy_mode_id}" \
|
||||||
|
"${database_id}" \
|
||||||
|
"${redis_id}" \
|
||||||
|
"${compose_files_lines}" \
|
||||||
|
"${apps_metadata_json_object}" \
|
||||||
|
"${env_lines}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue