mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-17 13:55:08 +00:00
refactor(easy-docker): split compose and production modules
This commit is contained in:
parent
d2ee473c68
commit
2a9aabbdb3
8 changed files with 906 additions and 834 deletions
|
|
@ -4,420 +4,16 @@ EASY_DOCKER_BUILD_ERROR_DETAIL=""
|
|||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata fails.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL=""
|
||||
|
||||
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=()
|
||||
load_easy_docker_compose_modules() {
|
||||
local compose_dir=""
|
||||
compose_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/compose"
|
||||
|
||||
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
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/compose/render.sh
|
||||
source "${compose_dir}/render.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/compose/start.sh
|
||||
source "${compose_dir}/start.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/compose/build.sh
|
||||
source "${compose_dir}/build.sh"
|
||||
}
|
||||
|
||||
start_stack_with_compose_from_metadata() {
|
||||
local stack_dir="${1}"
|
||||
local metadata_path=""
|
||||
local env_path=""
|
||||
local compose_files_lines=""
|
||||
local compose_file=""
|
||||
local source_compose_path=""
|
||||
local env_erpnext_version=""
|
||||
local fallback_erpnext_version=""
|
||||
local configured_pull_policy=""
|
||||
local runtime_pull_policy=""
|
||||
local custom_image=""
|
||||
local custom_tag=""
|
||||
local image_ref=""
|
||||
local stack_topology=""
|
||||
local repo_root=""
|
||||
local -a compose_args=()
|
||||
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata fails.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL=""
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 31
|
||||
fi
|
||||
|
||||
if [ ! -f "${env_path}" ]; then
|
||||
return 32
|
||||
fi
|
||||
|
||||
stack_topology="$(get_stack_topology "${stack_dir}" || true)"
|
||||
if [ -z "${stack_topology}" ]; then
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata returns 33.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL="metadata.json missing topology"
|
||||
return 33
|
||||
fi
|
||||
|
||||
case "${stack_topology}" in
|
||||
"single-host") ;;
|
||||
*)
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata returns 34.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL="${stack_topology}"
|
||||
return 34
|
||||
;;
|
||||
esac
|
||||
|
||||
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
|
||||
|
||||
configured_pull_policy="$(get_env_file_key_value "${env_path}" "PULL_POLICY" || true)"
|
||||
if [ -z "${configured_pull_policy}" ]; then
|
||||
custom_image="$(get_env_file_key_value "${env_path}" "CUSTOM_IMAGE" || true)"
|
||||
custom_tag="$(get_env_file_key_value "${env_path}" "CUSTOM_TAG" || true)"
|
||||
if [ -n "${custom_image}" ] && [ -n "${custom_tag}" ]; then
|
||||
image_ref="${custom_image}:${custom_tag}"
|
||||
if docker image inspect "${image_ref}" >/dev/null 2>&1; then
|
||||
runtime_pull_policy="if_not_present"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
compose_files_lines="$(get_metadata_compose_files_lines "${metadata_path}" || true)"
|
||||
if [ -z "${compose_files_lines}" ]; then
|
||||
return 35
|
||||
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
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata returns 36.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL="${source_compose_path}"
|
||||
return 36
|
||||
fi
|
||||
|
||||
compose_args+=(-f "${source_compose_path}")
|
||||
done <<EOF
|
||||
${compose_files_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#compose_args[@]}" -eq 0 ]; then
|
||||
return 35
|
||||
fi
|
||||
|
||||
if [ -n "${fallback_erpnext_version}" ] && [ -n "${runtime_pull_policy}" ]; then
|
||||
if ! ERPNEXT_VERSION="${fallback_erpnext_version}" PULL_POLICY="${runtime_pull_policy}" docker compose --env-file "${env_path}" "${compose_args[@]}" up -d; then
|
||||
return 37
|
||||
fi
|
||||
elif [ -n "${fallback_erpnext_version}" ]; then
|
||||
if ! ERPNEXT_VERSION="${fallback_erpnext_version}" docker compose --env-file "${env_path}" "${compose_args[@]}" up -d; then
|
||||
return 37
|
||||
fi
|
||||
elif [ -n "${runtime_pull_policy}" ]; then
|
||||
if ! PULL_POLICY="${runtime_pull_policy}" docker compose --env-file "${env_path}" "${compose_args[@]}" up -d; then
|
||||
return 37
|
||||
fi
|
||||
elif ! docker compose --env-file "${env_path}" "${compose_args[@]}" up -d; then
|
||||
return 37
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
get_stack_compose_runtime_status_label() {
|
||||
local result_var="${1}"
|
||||
local stack_dir="${2}"
|
||||
local metadata_path=""
|
||||
local env_path=""
|
||||
local stack_topology=""
|
||||
local compose_files_lines=""
|
||||
local compose_file=""
|
||||
local source_compose_path=""
|
||||
local env_erpnext_version=""
|
||||
local fallback_erpnext_version=""
|
||||
local running_services_lines=""
|
||||
local compose_status=0
|
||||
local running_services_count=0
|
||||
local repo_root=""
|
||||
local status_label=""
|
||||
local -a compose_args=()
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (metadata missing)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
stack_topology="$(get_stack_topology "${stack_dir}" || true)"
|
||||
if [ -z "${stack_topology}" ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (topology missing)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "${stack_topology}" in
|
||||
"single-host") ;;
|
||||
*)
|
||||
printf -v "${result_var}" "%s" "N/A (${stack_topology})"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -f "${env_path}" ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (env missing)"
|
||||
return 0
|
||||
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
|
||||
printf -v "${result_var}" "%s" "Unknown (compose files missing)"
|
||||
return 0
|
||||
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
|
||||
printf -v "${result_var}" "%s" "Unknown (missing file: ${compose_file})"
|
||||
return 0
|
||||
fi
|
||||
|
||||
compose_args+=(-f "${source_compose_path}")
|
||||
done <<EOF
|
||||
${compose_files_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#compose_args[@]}" -eq 0 ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (compose files missing)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "${fallback_erpnext_version}" ]; then
|
||||
running_services_lines="$(
|
||||
ERPNEXT_VERSION="${fallback_erpnext_version}" docker compose --env-file "${env_path}" "${compose_args[@]}" ps --status running --services 2>/dev/null
|
||||
)"
|
||||
compose_status=$?
|
||||
else
|
||||
running_services_lines="$(
|
||||
docker compose --env-file "${env_path}" "${compose_args[@]}" ps --status running --services 2>/dev/null
|
||||
)"
|
||||
compose_status=$?
|
||||
fi
|
||||
|
||||
if [ "${compose_status}" -ne 0 ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (docker compose status failed)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r compose_file; do
|
||||
if [ -n "${compose_file}" ]; then
|
||||
running_services_count=$((running_services_count + 1))
|
||||
fi
|
||||
done <<EOF
|
||||
${running_services_lines}
|
||||
EOF
|
||||
|
||||
if [ "${running_services_count}" -gt 0 ]; then
|
||||
status_label="Running (${running_services_count} services)"
|
||||
else
|
||||
status_label="Not running"
|
||||
fi
|
||||
|
||||
printf -v "${result_var}" "%s" "${status_label}"
|
||||
return 0
|
||||
}
|
||||
|
||||
build_stack_custom_image() {
|
||||
local stack_dir="${1}"
|
||||
local metadata_path=""
|
||||
local env_path=""
|
||||
local apps_json_path=""
|
||||
local custom_image=""
|
||||
local custom_tag=""
|
||||
local frappe_branch=""
|
||||
local frappe_path="https://github.com/frappe/frappe"
|
||||
local repo_root=""
|
||||
local containerfile_path=""
|
||||
local apps_json_base64=""
|
||||
local apps_refs_lines=""
|
||||
local app_ref_line=""
|
||||
local app_url=""
|
||||
local app_branch=""
|
||||
local git_error=""
|
||||
local image_ref=""
|
||||
|
||||
EASY_DOCKER_BUILD_ERROR_DETAIL=""
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
apps_json_path="${stack_dir}/apps.json"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 11
|
||||
fi
|
||||
if [ ! -f "${env_path}" ]; then
|
||||
return 12
|
||||
fi
|
||||
|
||||
custom_image="$(get_env_file_key_value "${env_path}" "CUSTOM_IMAGE" || true)"
|
||||
custom_tag="$(get_env_file_key_value "${env_path}" "CUSTOM_TAG" || true)"
|
||||
frappe_branch="$(get_stack_frappe_branch "${stack_dir}" || true)"
|
||||
if [ -z "${custom_image}" ]; then
|
||||
return 13
|
||||
fi
|
||||
if [ -z "${custom_tag}" ]; then
|
||||
return 14
|
||||
fi
|
||||
if [ -z "${frappe_branch}" ]; then
|
||||
return 15
|
||||
fi
|
||||
|
||||
# Keep apps.json aligned with current metadata app selection before build.
|
||||
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
||||
return 16
|
||||
fi
|
||||
if [ ! -f "${apps_json_path}" ]; then
|
||||
return 17
|
||||
fi
|
||||
|
||||
if ! command_exists git; then
|
||||
return 22
|
||||
fi
|
||||
|
||||
apps_refs_lines="$(
|
||||
awk '
|
||||
match($0, /"url"[[:space:]]*:[[:space:]]*"([^"]+)"/, url_parts) &&
|
||||
match($0, /"branch"[[:space:]]*:[[:space:]]*"([^"]+)"/, branch_parts) {
|
||||
print url_parts[1] "|" branch_parts[1]
|
||||
}
|
||||
' "${apps_json_path}"
|
||||
)"
|
||||
if [ -z "${apps_refs_lines}" ]; then
|
||||
return 23
|
||||
fi
|
||||
|
||||
while IFS= read -r app_ref_line; do
|
||||
if [ -z "${app_ref_line}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
app_url="${app_ref_line%%|*}"
|
||||
app_branch="${app_ref_line#*|}"
|
||||
if [ -z "${app_url}" ] || [ -z "${app_branch}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if git_error="$(git ls-remote --exit-code --heads "${app_url}" "${app_branch}" 2>&1)"; then
|
||||
:
|
||||
else
|
||||
# shellcheck disable=SC2034 # Read by manage flow after build_stack_custom_image returns 24.
|
||||
EASY_DOCKER_BUILD_ERROR_DETAIL="$(printf '%s@%s :: %s' "${app_url}" "${app_branch}" "${git_error}")"
|
||||
return 24
|
||||
fi
|
||||
done <<EOF
|
||||
${apps_refs_lines}
|
||||
EOF
|
||||
|
||||
if ! command_exists base64; then
|
||||
return 18
|
||||
fi
|
||||
|
||||
apps_json_base64="$(base64 "${apps_json_path}" | tr -d '\r\n')"
|
||||
if [ -z "${apps_json_base64}" ]; then
|
||||
return 19
|
||||
fi
|
||||
|
||||
repo_root="$(get_easy_docker_repo_root)"
|
||||
containerfile_path="${repo_root}/images/layered/Containerfile"
|
||||
if [ ! -f "${containerfile_path}" ]; then
|
||||
return 20
|
||||
fi
|
||||
|
||||
image_ref="${custom_image}:${custom_tag}"
|
||||
|
||||
docker build \
|
||||
-f "${containerfile_path}" \
|
||||
--build-arg "FRAPPE_BRANCH=${frappe_branch}" \
|
||||
--build-arg "FRAPPE_PATH=${frappe_path}" \
|
||||
--build-arg "APPS_JSON_BASE64=${apps_json_base64}" \
|
||||
-t "${image_ref}" \
|
||||
"${repo_root}" || return 21
|
||||
|
||||
return 0
|
||||
}
|
||||
load_easy_docker_compose_modules
|
||||
|
|
|
|||
120
scripts/easy-docker/lib/app/wizard/common/compose/build.sh
Executable file
120
scripts/easy-docker/lib/app/wizard/common/compose/build.sh
Executable file
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
build_stack_custom_image() {
|
||||
local stack_dir="${1}"
|
||||
local metadata_path=""
|
||||
local env_path=""
|
||||
local apps_json_path=""
|
||||
local custom_image=""
|
||||
local custom_tag=""
|
||||
local frappe_branch=""
|
||||
local frappe_path="https://github.com/frappe/frappe"
|
||||
local repo_root=""
|
||||
local containerfile_path=""
|
||||
local apps_json_base64=""
|
||||
local apps_refs_lines=""
|
||||
local app_ref_line=""
|
||||
local app_url=""
|
||||
local app_branch=""
|
||||
local git_error=""
|
||||
local image_ref=""
|
||||
|
||||
EASY_DOCKER_BUILD_ERROR_DETAIL=""
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
apps_json_path="${stack_dir}/apps.json"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 11
|
||||
fi
|
||||
if [ ! -f "${env_path}" ]; then
|
||||
return 12
|
||||
fi
|
||||
|
||||
custom_image="$(get_env_file_key_value "${env_path}" "CUSTOM_IMAGE" || true)"
|
||||
custom_tag="$(get_env_file_key_value "${env_path}" "CUSTOM_TAG" || true)"
|
||||
frappe_branch="$(get_stack_frappe_branch "${stack_dir}" || true)"
|
||||
if [ -z "${custom_image}" ]; then
|
||||
return 13
|
||||
fi
|
||||
if [ -z "${custom_tag}" ]; then
|
||||
return 14
|
||||
fi
|
||||
if [ -z "${frappe_branch}" ]; then
|
||||
return 15
|
||||
fi
|
||||
|
||||
# Keep apps.json aligned with current metadata app selection before build.
|
||||
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
|
||||
return 16
|
||||
fi
|
||||
if [ ! -f "${apps_json_path}" ]; then
|
||||
return 17
|
||||
fi
|
||||
|
||||
if ! command_exists git; then
|
||||
return 22
|
||||
fi
|
||||
|
||||
apps_refs_lines="$(
|
||||
awk '
|
||||
match($0, /"url"[[:space:]]*:[[:space:]]*"([^"]+)"/, url_parts) &&
|
||||
match($0, /"branch"[[:space:]]*:[[:space:]]*"([^"]+)"/, branch_parts) {
|
||||
print url_parts[1] "|" branch_parts[1]
|
||||
}
|
||||
' "${apps_json_path}"
|
||||
)"
|
||||
if [ -z "${apps_refs_lines}" ]; then
|
||||
return 23
|
||||
fi
|
||||
|
||||
while IFS= read -r app_ref_line; do
|
||||
if [ -z "${app_ref_line}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
app_url="${app_ref_line%%|*}"
|
||||
app_branch="${app_ref_line#*|}"
|
||||
if [ -z "${app_url}" ] || [ -z "${app_branch}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if git_error="$(git ls-remote --exit-code --heads "${app_url}" "${app_branch}" 2>&1)"; then
|
||||
:
|
||||
else
|
||||
# shellcheck disable=SC2034 # Read by manage flow after build_stack_custom_image returns 24.
|
||||
EASY_DOCKER_BUILD_ERROR_DETAIL="$(printf '%s@%s :: %s' "${app_url}" "${app_branch}" "${git_error}")"
|
||||
return 24
|
||||
fi
|
||||
done <<EOF
|
||||
${apps_refs_lines}
|
||||
EOF
|
||||
|
||||
if ! command_exists base64; then
|
||||
return 18
|
||||
fi
|
||||
|
||||
apps_json_base64="$(base64 "${apps_json_path}" | tr -d '\r\n')"
|
||||
if [ -z "${apps_json_base64}" ]; then
|
||||
return 19
|
||||
fi
|
||||
|
||||
repo_root="$(get_easy_docker_repo_root)"
|
||||
containerfile_path="${repo_root}/images/layered/Containerfile"
|
||||
if [ ! -f "${containerfile_path}" ]; then
|
||||
return 20
|
||||
fi
|
||||
|
||||
image_ref="${custom_image}:${custom_tag}"
|
||||
|
||||
docker build \
|
||||
-f "${containerfile_path}" \
|
||||
--build-arg "FRAPPE_BRANCH=${frappe_branch}" \
|
||||
--build-arg "FRAPPE_PATH=${frappe_path}" \
|
||||
--build-arg "APPS_JSON_BASE64=${apps_json_base64}" \
|
||||
-t "${image_ref}" \
|
||||
"${repo_root}" || return 21
|
||||
|
||||
return 0
|
||||
}
|
||||
76
scripts/easy-docker/lib/app/wizard/common/compose/render.sh
Executable file
76
scripts/easy-docker/lib/app/wizard/common/compose/render.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
|
||||
}
|
||||
242
scripts/easy-docker/lib/app/wizard/common/compose/start.sh
Executable file
242
scripts/easy-docker/lib/app/wizard/common/compose/start.sh
Executable file
|
|
@ -0,0 +1,242 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
start_stack_with_compose_from_metadata() {
|
||||
local stack_dir="${1}"
|
||||
local metadata_path=""
|
||||
local env_path=""
|
||||
local compose_files_lines=""
|
||||
local compose_file=""
|
||||
local source_compose_path=""
|
||||
local env_erpnext_version=""
|
||||
local fallback_erpnext_version=""
|
||||
local configured_pull_policy=""
|
||||
local runtime_pull_policy=""
|
||||
local custom_image=""
|
||||
local custom_tag=""
|
||||
local image_ref=""
|
||||
local image_inspect_error=""
|
||||
local stack_topology=""
|
||||
local repo_root=""
|
||||
local -a compose_args=()
|
||||
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata fails.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL=""
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 31
|
||||
fi
|
||||
|
||||
if [ ! -f "${env_path}" ]; then
|
||||
return 32
|
||||
fi
|
||||
|
||||
stack_topology="$(get_stack_topology "${stack_dir}" || true)"
|
||||
if [ -z "${stack_topology}" ]; then
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata returns 33.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL="metadata.json missing topology"
|
||||
return 33
|
||||
fi
|
||||
|
||||
case "${stack_topology}" in
|
||||
"single-host") ;;
|
||||
*)
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata returns 34.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL="${stack_topology}"
|
||||
return 34
|
||||
;;
|
||||
esac
|
||||
|
||||
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
|
||||
|
||||
configured_pull_policy="$(get_env_file_key_value "${env_path}" "PULL_POLICY" || true)"
|
||||
if [ -z "${configured_pull_policy}" ]; then
|
||||
custom_image="$(get_env_file_key_value "${env_path}" "CUSTOM_IMAGE" || true)"
|
||||
custom_tag="$(get_env_file_key_value "${env_path}" "CUSTOM_TAG" || true)"
|
||||
if [ -n "${custom_image}" ] && [ -n "${custom_tag}" ]; then
|
||||
image_ref="${custom_image}:${custom_tag}"
|
||||
if image_inspect_error="$(docker image inspect "${image_ref}" 2>&1 >/dev/null)"; then
|
||||
runtime_pull_policy="if_not_present"
|
||||
else
|
||||
case "${image_inspect_error}" in
|
||||
*"No such image"* | *"No such object"*)
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata returns 38.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL="${image_ref}"
|
||||
return 38
|
||||
;;
|
||||
*)
|
||||
if [ -z "${image_inspect_error}" ]; then
|
||||
image_inspect_error="docker image inspect failed for ${image_ref}"
|
||||
fi
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata returns 39.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL="${image_inspect_error}"
|
||||
return 39
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
compose_files_lines="$(get_metadata_compose_files_lines "${metadata_path}" || true)"
|
||||
if [ -z "${compose_files_lines}" ]; then
|
||||
return 35
|
||||
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
|
||||
# shellcheck disable=SC2034 # Read by manage flow after start_stack_with_compose_from_metadata returns 36.
|
||||
EASY_DOCKER_COMPOSE_ERROR_DETAIL="${source_compose_path}"
|
||||
return 36
|
||||
fi
|
||||
|
||||
compose_args+=(-f "${source_compose_path}")
|
||||
done <<EOF
|
||||
${compose_files_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#compose_args[@]}" -eq 0 ]; then
|
||||
return 35
|
||||
fi
|
||||
|
||||
if [ -n "${fallback_erpnext_version}" ] && [ -n "${runtime_pull_policy}" ]; then
|
||||
if ! ERPNEXT_VERSION="${fallback_erpnext_version}" PULL_POLICY="${runtime_pull_policy}" docker compose --env-file "${env_path}" "${compose_args[@]}" up -d; then
|
||||
return 37
|
||||
fi
|
||||
elif [ -n "${fallback_erpnext_version}" ]; then
|
||||
if ! ERPNEXT_VERSION="${fallback_erpnext_version}" docker compose --env-file "${env_path}" "${compose_args[@]}" up -d; then
|
||||
return 37
|
||||
fi
|
||||
elif [ -n "${runtime_pull_policy}" ]; then
|
||||
if ! PULL_POLICY="${runtime_pull_policy}" docker compose --env-file "${env_path}" "${compose_args[@]}" up -d; then
|
||||
return 37
|
||||
fi
|
||||
elif ! docker compose --env-file "${env_path}" "${compose_args[@]}" up -d; then
|
||||
return 37
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
get_stack_compose_runtime_status_label() {
|
||||
local result_var="${1}"
|
||||
local stack_dir="${2}"
|
||||
local metadata_path=""
|
||||
local env_path=""
|
||||
local stack_topology=""
|
||||
local compose_files_lines=""
|
||||
local compose_file=""
|
||||
local source_compose_path=""
|
||||
local env_erpnext_version=""
|
||||
local fallback_erpnext_version=""
|
||||
local running_services_lines=""
|
||||
local compose_status=0
|
||||
local running_services_count=0
|
||||
local repo_root=""
|
||||
local status_label=""
|
||||
local -a compose_args=()
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (metadata missing)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
stack_topology="$(get_stack_topology "${stack_dir}" || true)"
|
||||
if [ -z "${stack_topology}" ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (topology missing)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "${stack_topology}" in
|
||||
"single-host") ;;
|
||||
*)
|
||||
printf -v "${result_var}" "%s" "N/A (${stack_topology})"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -f "${env_path}" ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (env missing)"
|
||||
return 0
|
||||
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
|
||||
printf -v "${result_var}" "%s" "Unknown (compose files missing)"
|
||||
return 0
|
||||
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
|
||||
printf -v "${result_var}" "%s" "Unknown (missing file: ${compose_file})"
|
||||
return 0
|
||||
fi
|
||||
|
||||
compose_args+=(-f "${source_compose_path}")
|
||||
done <<EOF
|
||||
${compose_files_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#compose_args[@]}" -eq 0 ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (compose files missing)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "${fallback_erpnext_version}" ]; then
|
||||
running_services_lines="$(
|
||||
ERPNEXT_VERSION="${fallback_erpnext_version}" docker compose --env-file "${env_path}" "${compose_args[@]}" ps --status running --services 2>/dev/null
|
||||
)"
|
||||
compose_status=$?
|
||||
else
|
||||
running_services_lines="$(
|
||||
docker compose --env-file "${env_path}" "${compose_args[@]}" ps --status running --services 2>/dev/null
|
||||
)"
|
||||
compose_status=$?
|
||||
fi
|
||||
|
||||
if [ "${compose_status}" -ne 0 ]; then
|
||||
printf -v "${result_var}" "%s" "Unknown (docker compose status failed)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r compose_file; do
|
||||
if [ -n "${compose_file}" ]; then
|
||||
running_services_count=$((running_services_count + 1))
|
||||
fi
|
||||
done <<EOF
|
||||
${running_services_lines}
|
||||
EOF
|
||||
|
||||
if [ "${running_services_count}" -gt 0 ]; then
|
||||
status_label="Running (${running_services_count} services)"
|
||||
else
|
||||
status_label="Not running"
|
||||
fi
|
||||
|
||||
printf -v "${result_var}" "%s" "${status_label}"
|
||||
return 0
|
||||
}
|
||||
|
|
@ -1,425 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
get_setup_display_label() {
|
||||
local setup_type="${1}"
|
||||
load_production_screen_modules() {
|
||||
local screen_dir=""
|
||||
screen_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
case "${setup_type}" in
|
||||
development)
|
||||
printf 'Development'
|
||||
;;
|
||||
production | *)
|
||||
printf 'Production'
|
||||
;;
|
||||
esac
|
||||
# shellcheck source=scripts/easy-docker/lib/ui/screens/production/setup.sh
|
||||
source "${screen_dir}/production/setup.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/ui/screens/production/topology.sh
|
||||
source "${screen_dir}/production/topology.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/ui/screens/production/manage.sh
|
||||
source "${screen_dir}/production/manage.sh"
|
||||
}
|
||||
|
||||
show_setup_menu() {
|
||||
local setup_type="${1}"
|
||||
local setup_label=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
setup_label="$(get_setup_display_label "${setup_type}")"
|
||||
status_text="$(printf "%s stack\n\nChoose whether to create a new stack or manage an existing one." "${setup_label}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "${setup_label} stack actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Create new stack" \
|
||||
"Manage existing stacks" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_production_setup_menu() {
|
||||
show_setup_menu "production"
|
||||
}
|
||||
|
||||
show_development_setup_menu() {
|
||||
show_setup_menu "development"
|
||||
}
|
||||
|
||||
prompt_new_stack_name() {
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Create new stack\n\nEnter a stack name.\nType /cancel or press Ctrl+C to abort.")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum input \
|
||||
--header "Stack name (/cancel to abort)" \
|
||||
--prompt "name> " \
|
||||
--placeholder "my-production-stack"
|
||||
}
|
||||
|
||||
show_frappe_version_profile_menu() {
|
||||
local stack_name="${1}"
|
||||
local options_lines="${2:-}"
|
||||
local selected_label="${3:-}"
|
||||
local status_text=""
|
||||
local option_line=""
|
||||
local -a menu_options=()
|
||||
local -a gum_args=()
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Create stack: %s\n\nSelect the Frappe branch profile from frappe.tsv.\nThis sets the stack default for branch suggestions." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
while IFS= read -r option_line; do
|
||||
if [ -z "${option_line}" ]; then
|
||||
continue
|
||||
fi
|
||||
menu_options+=("${option_line}")
|
||||
done <<EOF
|
||||
${options_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#menu_options[@]}" -eq 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
gum_args=(
|
||||
--height 10
|
||||
--header "Frappe branch profile"
|
||||
--cursor.foreground 63
|
||||
--selected.foreground 45
|
||||
)
|
||||
if [ -n "${selected_label}" ]; then
|
||||
gum_args+=(--selected "${selected_label}")
|
||||
fi
|
||||
|
||||
gum choose "${gum_args[@]}" "${menu_options[@]}" "Back"
|
||||
}
|
||||
|
||||
show_stack_topology_menu() {
|
||||
local stack_dir="${1}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack created: %s\nDirectory: %s\n\nChoose the deployment topology.\n\n- Single-host: easiest setup on one server.\n- Split services: separate app and infra stacks for more control." "${stack_name}" "${stack_dir}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Topology" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Single-host (recommended)" \
|
||||
"Split services" \
|
||||
"Abort wizard to main menu"
|
||||
}
|
||||
|
||||
show_single_host_proxy_menu() {
|
||||
local stack_dir="${1}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack: %s\n\nSingle-host setup (step 1/3)\nChoose the proxy mode.\n\n- Traefik and nginx-proxy run inside compose.\n- Caddy is external and uses the no-proxy compose mode." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 11 \
|
||||
--header "Single-host: proxy mode" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Traefik (HTTP, built-in proxy)" \
|
||||
"Traefik (HTTPS + Let's Encrypt)" \
|
||||
"nginx-proxy (HTTP)" \
|
||||
"nginx-proxy + acme-companion (HTTPS)" \
|
||||
"Caddy (external reverse proxy)" \
|
||||
"No reverse proxy (direct :8080)" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_single_host_database_menu() {
|
||||
local stack_dir="${1}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack: %s\n\nSingle-host setup (step 2/3)\nChoose the database service." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Single-host: database" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"MariaDB (recommended)" \
|
||||
"PostgreSQL" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_single_host_redis_menu() {
|
||||
local stack_dir="${1}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack: %s\n\nSingle-host setup (step 3/3)\nChoose whether Redis services should be included." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Single-host: redis" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Include Redis (recommended)" \
|
||||
"Skip Redis (experienced users only)" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_custom_modular_apps_multi_select() {
|
||||
local stack_dir="${1}"
|
||||
local options_lines="${2:-}"
|
||||
local selected_labels_csv="${3:-}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
local option_line=""
|
||||
local selected_label=""
|
||||
local -a menu_options=()
|
||||
local -a selected_labels=()
|
||||
local -a gum_args=()
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack: %s\n\nApps\nUse Space to toggle apps from apps.tsv. Press Enter to continue to branch selection per app.\nUse Ctrl+C to go back." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
while IFS= read -r option_line; do
|
||||
if [ -z "${option_line}" ]; then
|
||||
continue
|
||||
fi
|
||||
menu_options+=("${option_line}")
|
||||
done <<EOF
|
||||
${options_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#menu_options[@]}" -eq 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
gum_args=(
|
||||
--no-limit
|
||||
--height 14
|
||||
--header "Apps"
|
||||
--cursor.foreground 63
|
||||
--selected.foreground 45
|
||||
)
|
||||
if [ -n "${selected_labels_csv}" ]; then
|
||||
IFS=',' read -r -a selected_labels <<<"${selected_labels_csv}"
|
||||
for selected_label in "${selected_labels[@]}"; do
|
||||
trim_predefined_catalog_field selected_label "${selected_label}"
|
||||
if [ -z "${selected_label}" ]; then
|
||||
continue
|
||||
fi
|
||||
gum_args+=(--selected "${selected_label}")
|
||||
done
|
||||
fi
|
||||
|
||||
gum choose "${gum_args[@]}" "${menu_options[@]}"
|
||||
}
|
||||
|
||||
prompt_single_host_env_value() {
|
||||
local stack_dir="${1}"
|
||||
local variable_name="${2}"
|
||||
local guidance_text="${3}"
|
||||
local placeholder="${4:-}"
|
||||
local render_context="${5:-1}"
|
||||
local input_feedback="${6:-}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
if [ "${render_context}" = "1" ]; then
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
guidance_text="${guidance_text//\\n/$'\n'}"
|
||||
status_text="$(printf "Stack: %s\n\nConfigure %s\n\n%s" "${stack_name}" "${variable_name}" "${guidance_text}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
fi
|
||||
|
||||
if [ -n "${input_feedback}" ]; then
|
||||
gum style --foreground 214 "${input_feedback}" >&2
|
||||
fi
|
||||
|
||||
gum input \
|
||||
--header "${variable_name}" \
|
||||
--prompt "value> " \
|
||||
--placeholder "${placeholder}"
|
||||
}
|
||||
|
||||
show_split_services_examples() {
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Split services examples\n\n- DB in a separate stack/project.\n- Proxy in a separate stack/project.\n- One or more app stacks referencing shared infra.")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 7 \
|
||||
--header "Split services" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Use this topology" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_abort_wizard_prompt() {
|
||||
local stack_dir="${1}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Abort wizard\n\nStack directory:\n%s\n\nRollback created files before returning to main menu?" "${stack_dir}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Abort options" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Rollback files and return to main menu" \
|
||||
"Keep files and return to main menu" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_manage_stacks_menu() {
|
||||
local setup_type="${1}"
|
||||
shift
|
||||
local stack_count="${#}"
|
||||
local setup_label=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
setup_label="$(get_setup_display_label "${setup_type}")"
|
||||
if [ "${stack_count}" -eq 1 ]; then
|
||||
status_text="$(printf "Manage existing %s stacks\n\n1 stack found. Select a stack." "${setup_label}")"
|
||||
else
|
||||
status_text="$(printf "Manage existing %s stacks\n\n%s stacks found. Select a stack." "${setup_label}" "${stack_count}")"
|
||||
fi
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 14 \
|
||||
--header "Existing stacks" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"$@" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stacks_placeholder() {
|
||||
local setup_type="${1}"
|
||||
local setup_label=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
setup_label="$(get_setup_display_label "${setup_type}")"
|
||||
status_text="$(printf "Manage existing %s stacks\n\nNo stacks found in .easy-docker/stacks yet." "${setup_label}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 7 \
|
||||
--header "Manage stacks actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stack_actions_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local stack_runtime_status="${3:-Unknown}"
|
||||
local menu_header=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack\n\nStack: %s\nDirectory: %s\nRuntime status: %s\n\nChoose an action for this stack." "${stack_name}" "${stack_dir}" "${stack_runtime_status}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
menu_header="$(printf "Stack actions | %s" "${stack_runtime_status}")"
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "${menu_header}" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Apps" \
|
||||
"Docker" \
|
||||
"Start stack in Docker Compose" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stack_apps_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack apps\n\nStack: %s\nDirectory: %s\n\nChoose an app-related action for this stack." "${stack_name}" "${stack_dir}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Stack apps actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Regenerate apps.json from metadata" \
|
||||
"Select apps and branches" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stack_docker_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack docker\n\nStack: %s\nDirectory: %s\n\nChoose a docker-related action for this stack." "${stack_name}" "${stack_dir}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Stack docker actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Build custom image" \
|
||||
"Generate docker compose from env" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
load_production_screen_modules
|
||||
|
|
|
|||
142
scripts/easy-docker/lib/ui/screens/production/manage.sh
Executable file
142
scripts/easy-docker/lib/ui/screens/production/manage.sh
Executable file
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
show_manage_stacks_menu() {
|
||||
local setup_type="${1}"
|
||||
shift
|
||||
local stack_count="${#}"
|
||||
local setup_label=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
setup_label="$(get_setup_display_label "${setup_type}")"
|
||||
if [ "${stack_count}" -eq 1 ]; then
|
||||
status_text="$(printf "Manage existing %s stacks\n\n1 stack found. Select a stack." "${setup_label}")"
|
||||
else
|
||||
status_text="$(printf "Manage existing %s stacks\n\n%s stacks found. Select a stack." "${setup_label}" "${stack_count}")"
|
||||
fi
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 14 \
|
||||
--header "Existing stacks" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"$@" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stacks_placeholder() {
|
||||
local setup_type="${1}"
|
||||
local setup_label=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
setup_label="$(get_setup_display_label "${setup_type}")"
|
||||
status_text="$(printf "Manage existing %s stacks\n\nNo stacks found in .easy-docker/stacks yet." "${setup_label}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 7 \
|
||||
--header "Manage stacks actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stack_actions_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local stack_runtime_status="${3:-Unknown}"
|
||||
local menu_header=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack\n\nStack: %s\nDirectory: %s\nRuntime status: %s\n\nChoose an action for this stack." "${stack_name}" "${stack_dir}" "${stack_runtime_status}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
menu_header="$(printf "Stack actions | %s" "${stack_runtime_status}")"
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "${menu_header}" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Apps" \
|
||||
"Docker" \
|
||||
"Start stack in Docker Compose" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stack_apps_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack apps\n\nStack: %s\nDirectory: %s\n\nChoose an app-related action for this stack." "${stack_name}" "${stack_dir}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Stack apps actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Regenerate apps.json from metadata" \
|
||||
"Select apps and branches" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stack_docker_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack docker\n\nStack: %s\nDirectory: %s\n\nChoose a docker-related action for this stack." "${stack_name}" "${stack_dir}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Stack docker actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Build custom image" \
|
||||
"Generate docker compose from env" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_missing_custom_image_start_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local image_ref="${3}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Custom image missing\n\nStack: %s\nDirectory: %s\nImage: %s\n\nBuild the custom image now before starting the stack?" "${stack_name}" "${stack_dir}" "${image_ref}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Missing custom image" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Build custom image now" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
100
scripts/easy-docker/lib/ui/screens/production/setup.sh
Executable file
100
scripts/easy-docker/lib/ui/screens/production/setup.sh
Executable file
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
get_setup_display_label() {
|
||||
local setup_type="${1}"
|
||||
|
||||
case "${setup_type}" in
|
||||
development)
|
||||
printf 'Development'
|
||||
;;
|
||||
production | *)
|
||||
printf 'Production'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
show_setup_menu() {
|
||||
local setup_type="${1}"
|
||||
local setup_label=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
setup_label="$(get_setup_display_label "${setup_type}")"
|
||||
status_text="$(printf "%s stack\n\nChoose whether to create a new stack or manage an existing one." "${setup_label}")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "${setup_label} stack actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Create new stack" \
|
||||
"Manage existing stacks" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_production_setup_menu() {
|
||||
show_setup_menu "production"
|
||||
}
|
||||
|
||||
show_development_setup_menu() {
|
||||
show_setup_menu "development"
|
||||
}
|
||||
|
||||
prompt_new_stack_name() {
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Create new stack\n\nEnter a stack name.\nType /cancel or press Ctrl+C to abort.")"
|
||||
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum input \
|
||||
--header "Stack name (/cancel to abort)" \
|
||||
--prompt "name> " \
|
||||
--placeholder "my-production-stack"
|
||||
}
|
||||
|
||||
show_frappe_version_profile_menu() {
|
||||
local stack_name="${1}"
|
||||
local options_lines="${2:-}"
|
||||
local selected_label="${3:-}"
|
||||
local status_text=""
|
||||
local option_line=""
|
||||
local -a menu_options=()
|
||||
local -a gum_args=()
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Create stack: %s\n\nSelect the Frappe branch profile from frappe.tsv.\nThis sets the stack default for branch suggestions." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
while IFS= read -r option_line; do
|
||||
if [ -z "${option_line}" ]; then
|
||||
continue
|
||||
fi
|
||||
menu_options+=("${option_line}")
|
||||
done <<EOF
|
||||
${options_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#menu_options[@]}" -eq 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
gum_args=(
|
||||
--height 10
|
||||
--header "Frappe branch profile"
|
||||
--cursor.foreground 63
|
||||
--selected.foreground 45
|
||||
)
|
||||
if [ -n "${selected_label}" ]; then
|
||||
gum_args+=(--selected "${selected_label}")
|
||||
fi
|
||||
|
||||
gum choose "${gum_args[@]}" "${menu_options[@]}" "Back"
|
||||
}
|
||||
206
scripts/easy-docker/lib/ui/screens/production/topology.sh
Executable file
206
scripts/easy-docker/lib/ui/screens/production/topology.sh
Executable file
|
|
@ -0,0 +1,206 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
show_stack_topology_menu() {
|
||||
local stack_dir="${1}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack created: %s\nDirectory: %s\n\nChoose the deployment topology.\n\n- Single-host: easiest setup on one server.\n- Split services: separate app and infra stacks for more control." "${stack_name}" "${stack_dir}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Topology" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Single-host (recommended)" \
|
||||
"Split services" \
|
||||
"Abort wizard to main menu"
|
||||
}
|
||||
|
||||
show_single_host_proxy_menu() {
|
||||
local stack_dir="${1}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack: %s\n\nSingle-host setup (step 1/3)\nChoose the proxy mode.\n\n- Traefik and nginx-proxy run inside compose.\n- Caddy is external and uses the no-proxy compose mode." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 11 \
|
||||
--header "Single-host: proxy mode" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Traefik (HTTP, built-in proxy)" \
|
||||
"Traefik (HTTPS + Let's Encrypt)" \
|
||||
"nginx-proxy (HTTP)" \
|
||||
"nginx-proxy + acme-companion (HTTPS)" \
|
||||
"Caddy (external reverse proxy)" \
|
||||
"No reverse proxy (direct :8080)" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_single_host_database_menu() {
|
||||
local stack_dir="${1}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack: %s\n\nSingle-host setup (step 2/3)\nChoose the database service." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Single-host: database" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"MariaDB (recommended)" \
|
||||
"PostgreSQL" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_single_host_redis_menu() {
|
||||
local stack_dir="${1}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack: %s\n\nSingle-host setup (step 3/3)\nChoose whether Redis services should be included." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Single-host: redis" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Include Redis (recommended)" \
|
||||
"Skip Redis (experienced users only)" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_custom_modular_apps_multi_select() {
|
||||
local stack_dir="${1}"
|
||||
local options_lines="${2:-}"
|
||||
local selected_labels_csv="${3:-}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
local option_line=""
|
||||
local selected_label=""
|
||||
local -a menu_options=()
|
||||
local -a selected_labels=()
|
||||
local -a gum_args=()
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
status_text="$(printf "Stack: %s\n\nApps\nUse Space to toggle apps from apps.tsv. Press Enter to continue to branch selection per app.\nUse Ctrl+C to go back." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
while IFS= read -r option_line; do
|
||||
if [ -z "${option_line}" ]; then
|
||||
continue
|
||||
fi
|
||||
menu_options+=("${option_line}")
|
||||
done <<EOF
|
||||
${options_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#menu_options[@]}" -eq 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
gum_args=(
|
||||
--no-limit
|
||||
--height 14
|
||||
--header "Apps"
|
||||
--cursor.foreground 63
|
||||
--selected.foreground 45
|
||||
)
|
||||
if [ -n "${selected_labels_csv}" ]; then
|
||||
IFS=',' read -r -a selected_labels <<<"${selected_labels_csv}"
|
||||
for selected_label in "${selected_labels[@]}"; do
|
||||
trim_predefined_catalog_field selected_label "${selected_label}"
|
||||
if [ -z "${selected_label}" ]; then
|
||||
continue
|
||||
fi
|
||||
gum_args+=(--selected "${selected_label}")
|
||||
done
|
||||
fi
|
||||
|
||||
gum choose "${gum_args[@]}" "${menu_options[@]}"
|
||||
}
|
||||
|
||||
prompt_single_host_env_value() {
|
||||
local stack_dir="${1}"
|
||||
local variable_name="${2}"
|
||||
local guidance_text="${3}"
|
||||
local placeholder="${4:-}"
|
||||
local render_context="${5:-1}"
|
||||
local input_feedback="${6:-}"
|
||||
local stack_name=""
|
||||
local status_text=""
|
||||
|
||||
if [ "${render_context}" = "1" ]; then
|
||||
render_main_screen 1 >&2
|
||||
|
||||
stack_name="${stack_dir##*/}"
|
||||
guidance_text="${guidance_text//\\n/$'\n'}"
|
||||
status_text="$(printf "Stack: %s\n\nConfigure %s\n\n%s" "${stack_name}" "${variable_name}" "${guidance_text}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
fi
|
||||
|
||||
if [ -n "${input_feedback}" ]; then
|
||||
gum style --foreground 214 "${input_feedback}" >&2
|
||||
fi
|
||||
|
||||
gum input \
|
||||
--header "${variable_name}" \
|
||||
--prompt "value> " \
|
||||
--placeholder "${placeholder}"
|
||||
}
|
||||
|
||||
show_split_services_examples() {
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Split services examples\n\n- DB in a separate stack/project.\n- Proxy in a separate stack/project.\n- One or more app stacks referencing shared infra.")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 7 \
|
||||
--header "Split services" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Use this topology" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
|
||||
show_abort_wizard_prompt() {
|
||||
local stack_dir="${1}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Abort wizard\n\nStack directory:\n%s\n\nRollback created files before returning to main menu?" "${stack_dir}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Abort options" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Rollback files and return to main menu" \
|
||||
"Keep files and return to main menu" \
|
||||
"Back to topology selection"
|
||||
}
|
||||
Loading…
Reference in a new issue