mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-17 13:55:08 +00:00
feat(easy-docker): add guided single-site management
This commit is contained in:
parent
bba9e70dd8
commit
bfa70da36e
7 changed files with 1536 additions and 1 deletions
|
|
@ -16,6 +16,8 @@ load_easy_docker_wizard_common_modules() {
|
|||
source "${wizard_dir}/common/frappe.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/site.sh
|
||||
source "${wizard_dir}/common/site.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/ux.sh
|
||||
source "${wizard_dir}/common/ux.sh"
|
||||
}
|
||||
|
|
|
|||
15
scripts/easy-docker/lib/app/wizard/common/site.sh
Executable file
15
scripts/easy-docker/lib/app/wizard/common/site.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
load_easy_docker_site_modules() {
|
||||
local site_dir=""
|
||||
site_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/site"
|
||||
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/site/metadata.sh
|
||||
source "${site_dir}/metadata.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/site/bootstrap.sh
|
||||
source "${site_dir}/bootstrap.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/site/apps.sh
|
||||
source "${site_dir}/apps.sh"
|
||||
}
|
||||
|
||||
load_easy_docker_site_modules
|
||||
76
scripts/easy-docker/lib/app/wizard/common/site/apps.sh
Executable file
76
scripts/easy-docker/lib/app/wizard/common/site/apps.sh
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
append_stack_installable_app_line() {
|
||||
local result_var="${1}"
|
||||
local existing_lines="${2:-}"
|
||||
local app_name="${3:-}"
|
||||
|
||||
if [ -z "${app_name}" ]; then
|
||||
printf -v "${result_var}" "%s" "${existing_lines}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r existing_app; do
|
||||
if [ "${existing_app}" = "${app_name}" ]; then
|
||||
printf -v "${result_var}" "%s" "${existing_lines}"
|
||||
return 0
|
||||
fi
|
||||
done <<EOF
|
||||
${existing_lines}
|
||||
EOF
|
||||
|
||||
if [ -z "${existing_lines}" ]; then
|
||||
printf -v "${result_var}" "%s" "${app_name}"
|
||||
else
|
||||
printf -v "${result_var}" "%s\n%s" "${existing_lines}" "${app_name}"
|
||||
fi
|
||||
}
|
||||
|
||||
get_stack_selected_installable_apps() {
|
||||
local result_var="${1}"
|
||||
local stack_dir="${2}"
|
||||
local metadata_path=""
|
||||
local predefined_apps_csv=""
|
||||
local app_name=""
|
||||
local installable_app_lines=""
|
||||
local deferred_erpnext=""
|
||||
local ordered_app_lines=""
|
||||
local -a predefined_apps=()
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
predefined_apps_csv="$(get_metadata_apps_predefined_csv "${metadata_path}" || true)"
|
||||
if [ -z "${predefined_apps_csv}" ]; then
|
||||
printf -v "${result_var}" "%s" ""
|
||||
return 0
|
||||
fi
|
||||
|
||||
IFS=',' read -r -a predefined_apps <<<"${predefined_apps_csv}"
|
||||
for app_name in "${predefined_apps[@]}"; do
|
||||
if [ -z "${app_name}" ] || [ "${app_name}" = "frappe" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "${app_name}" = "erpnext" ]; then
|
||||
deferred_erpnext="${app_name}"
|
||||
continue
|
||||
fi
|
||||
|
||||
append_stack_installable_app_line installable_app_lines "${installable_app_lines}" "${app_name}"
|
||||
done
|
||||
|
||||
if [ -n "${deferred_erpnext}" ]; then
|
||||
ordered_app_lines="${deferred_erpnext}"
|
||||
if [ -n "${installable_app_lines}" ]; then
|
||||
ordered_app_lines="${ordered_app_lines}"$'\n'"${installable_app_lines}"
|
||||
fi
|
||||
else
|
||||
ordered_app_lines="${installable_app_lines}"
|
||||
fi
|
||||
|
||||
printf -v "${result_var}" "%s" "${ordered_app_lines}"
|
||||
return 0
|
||||
}
|
||||
795
scripts/easy-docker/lib/app/wizard/common/site/bootstrap.sh
Executable file
795
scripts/easy-docker/lib/app/wizard/common/site/bootstrap.sh
Executable file
|
|
@ -0,0 +1,795 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
is_valid_stack_site_name() {
|
||||
local site_name="${1}"
|
||||
|
||||
if [ -z "${site_name}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "${site_name}" in
|
||||
*[!A-Za-z0-9._-]*)
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_safe_stack_site_cleanup_name() {
|
||||
local site_name="${1}"
|
||||
|
||||
if ! is_valid_stack_site_name "${site_name}"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "${site_name}" in
|
||||
"." | ".." | "/" | "")
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
shell_quote_site_command_arg() {
|
||||
local raw_value="${1}"
|
||||
|
||||
printf "'%s'" "$(printf '%s' "${raw_value}" | sed "s/'/'\"'\"'/g")"
|
||||
}
|
||||
|
||||
get_stack_primary_site_name_suggestion() {
|
||||
local stack_dir="${1}"
|
||||
local env_path=""
|
||||
local site_domains=""
|
||||
local primary_domain=""
|
||||
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
site_domains="$(get_env_file_key_value "${env_path}" "SITE_DOMAINS" || true)"
|
||||
primary_domain="${site_domains%%,*}"
|
||||
primary_domain="${primary_domain%% *}"
|
||||
|
||||
if [ -n "${primary_domain}" ]; then
|
||||
printf '%s\n' "${primary_domain}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf '%s.localhost\n' "${stack_dir##*/}"
|
||||
return 0
|
||||
}
|
||||
|
||||
get_stack_database_id() {
|
||||
local stack_dir="${1}"
|
||||
|
||||
get_metadata_string_field "${stack_dir}/metadata.json" "database_id"
|
||||
}
|
||||
|
||||
get_stack_redis_id() {
|
||||
local stack_dir="${1}"
|
||||
|
||||
get_metadata_string_field "${stack_dir}/metadata.json" "redis_id"
|
||||
}
|
||||
|
||||
get_stack_database_root_password() {
|
||||
local stack_dir="${1}"
|
||||
local env_path=""
|
||||
local db_password=""
|
||||
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
db_password="$(get_env_file_key_value "${env_path}" "DB_PASSWORD" || true)"
|
||||
if [ -z "${db_password}" ]; then
|
||||
db_password="123"
|
||||
fi
|
||||
|
||||
printf '%s\n' "${db_password}"
|
||||
return 0
|
||||
}
|
||||
|
||||
stack_site_bootstrap_supports_database() {
|
||||
local stack_dir="${1}"
|
||||
local database_id=""
|
||||
|
||||
database_id="$(get_stack_database_id "${stack_dir}" || true)"
|
||||
case "${database_id}" in
|
||||
mariadb)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
stack_supports_single_site_management() {
|
||||
local stack_dir="${1}"
|
||||
local stack_topology=""
|
||||
|
||||
stack_topology="$(get_stack_topology "${stack_dir}" || true)"
|
||||
case "${stack_topology}" in
|
||||
single-host)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_stack_backend_bash_command() {
|
||||
local stack_dir="${1}"
|
||||
local backend_command="${2}"
|
||||
local wrapped_backend_command=""
|
||||
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 compose_project_name=""
|
||||
local stack_topology=""
|
||||
local repo_root=""
|
||||
local -a compose_args=()
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
env_path="$(get_stack_env_path "${stack_dir}")"
|
||||
compose_project_name="$(get_stack_compose_project_name "${stack_dir}")"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 54
|
||||
fi
|
||||
|
||||
if [ ! -f "${env_path}" ]; then
|
||||
return 54
|
||||
fi
|
||||
|
||||
stack_topology="$(get_stack_topology "${stack_dir}" || true)"
|
||||
if [ -z "${stack_topology}" ]; then
|
||||
return 54
|
||||
fi
|
||||
|
||||
case "${stack_topology}" in
|
||||
single-host) ;;
|
||||
*)
|
||||
return 52
|
||||
;;
|
||||
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
|
||||
|
||||
compose_files_lines="$(get_metadata_compose_files_lines "${metadata_path}" || true)"
|
||||
if [ -z "${compose_files_lines}" ]; then
|
||||
return 54
|
||||
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 54
|
||||
fi
|
||||
|
||||
compose_args+=(-f "${source_compose_path}")
|
||||
done <<EOF
|
||||
${compose_files_lines}
|
||||
EOF
|
||||
|
||||
if [ "${#compose_args[@]}" -eq 0 ]; then
|
||||
return 54
|
||||
fi
|
||||
|
||||
wrapped_backend_command="$(printf "cd /home/frappe/frappe-bench && %s" "${backend_command}")"
|
||||
|
||||
if [ -n "${fallback_erpnext_version}" ]; then
|
||||
ERPNEXT_VERSION="${fallback_erpnext_version}" docker compose --project-name "${compose_project_name}" --env-file "${env_path}" "${compose_args[@]}" exec -T backend bash -lc "${wrapped_backend_command}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
docker compose --project-name "${compose_project_name}" --env-file "${env_path}" "${compose_args[@]}" exec -T backend bash -lc "${wrapped_backend_command}"
|
||||
}
|
||||
|
||||
stack_backend_service_is_running() {
|
||||
local stack_dir="${1}"
|
||||
local backend_ready_status=0
|
||||
|
||||
if run_stack_backend_bash_command "${stack_dir}" "true" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
backend_ready_status=$?
|
||||
if [ "${backend_ready_status}" -eq 54 ] || [ "${backend_ready_status}" -eq 52 ]; then
|
||||
return "${backend_ready_status}"
|
||||
fi
|
||||
|
||||
# If exec fails, the backend service is not ready for site actions yet.
|
||||
return 1
|
||||
}
|
||||
|
||||
stack_database_service_is_reachable() {
|
||||
local stack_dir="${1}"
|
||||
local reachability_command=""
|
||||
local db_ready_status=0
|
||||
|
||||
IFS= read -r -d '' reachability_command <<'EOF' || true
|
||||
python - <<'PY'
|
||||
import json
|
||||
import socket
|
||||
from pathlib import Path
|
||||
|
||||
config_path = Path("/home/frappe/frappe-bench/sites/common_site_config.json")
|
||||
with config_path.open(encoding="utf-8") as handle:
|
||||
config = json.load(handle)
|
||||
|
||||
db_host = config.get("db_host")
|
||||
db_port = int(config.get("db_port", 3306))
|
||||
socket.create_connection((db_host, db_port), 5).close()
|
||||
PY
|
||||
EOF
|
||||
|
||||
if run_stack_backend_bash_command "${stack_dir}" "${reachability_command}" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
db_ready_status=$?
|
||||
if [ "${db_ready_status}" -eq 54 ] || [ "${db_ready_status}" -eq 52 ]; then
|
||||
return "${db_ready_status}"
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
stack_site_exists_in_bench() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
local exists_command=""
|
||||
local exists_status=0
|
||||
|
||||
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
||||
return 61
|
||||
fi
|
||||
|
||||
exists_command="$(
|
||||
printf "bench list-sites | grep -F -x -- %s >/dev/null" "$(shell_quote_site_command_arg "${site_name}")"
|
||||
)"
|
||||
if run_stack_backend_bash_command "${stack_dir}" "${exists_command}" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
exists_status=$?
|
||||
if [ "${exists_status}" -eq 54 ] || [ "${exists_status}" -eq 52 ]; then
|
||||
return "${exists_status}"
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
stack_site_directory_exists() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
local exists_command=""
|
||||
local exists_status=0
|
||||
|
||||
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
||||
return 61
|
||||
fi
|
||||
|
||||
exists_command="$(
|
||||
printf "test -d sites/%s" "$(shell_quote_site_command_arg "${site_name}")"
|
||||
)"
|
||||
if run_stack_backend_bash_command "${stack_dir}" "${exists_command}" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
exists_status=$?
|
||||
if [ "${exists_status}" -eq 54 ] || [ "${exists_status}" -eq 52 ]; then
|
||||
return "${exists_status}"
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
stack_site_config_exists() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
local exists_command=""
|
||||
local exists_status=0
|
||||
|
||||
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
||||
return 61
|
||||
fi
|
||||
|
||||
exists_command="$(
|
||||
printf "test -f sites/%s/site_config.json" "$(shell_quote_site_command_arg "${site_name}")"
|
||||
)"
|
||||
if run_stack_backend_bash_command "${stack_dir}" "${exists_command}" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
exists_status=$?
|
||||
if [ "${exists_status}" -eq 54 ] || [ "${exists_status}" -eq 52 ]; then
|
||||
return "${exists_status}"
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
get_stack_site_database_name() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
local read_command=""
|
||||
|
||||
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
||||
return 61
|
||||
fi
|
||||
|
||||
read_command="$(
|
||||
printf "python - <<'PY'\nimport json\nfrom pathlib import Path\npath = Path('sites') / %s / 'site_config.json'\nwith path.open(encoding='utf-8') as handle:\n print(json.load(handle).get('db_name', ''))\nPY" \
|
||||
"$(shell_quote_site_command_arg "${site_name}")"
|
||||
)"
|
||||
|
||||
run_stack_backend_bash_command "${stack_dir}" "${read_command}"
|
||||
}
|
||||
|
||||
get_stack_common_db_endpoint() {
|
||||
local stack_dir="${1}"
|
||||
local read_command=""
|
||||
|
||||
read_command="$(
|
||||
cat <<'EOF'
|
||||
python - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
path = Path("sites/common_site_config.json")
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
config = json.load(handle)
|
||||
print(f"{config.get('db_host', '')}|{config.get('db_port', 3306)}")
|
||||
PY
|
||||
EOF
|
||||
)"
|
||||
|
||||
run_stack_backend_bash_command "${stack_dir}" "${read_command}"
|
||||
}
|
||||
|
||||
repair_stack_site_runtime_state() {
|
||||
local stack_dir="${1}"
|
||||
local database_id=""
|
||||
local redis_id=""
|
||||
local db_host=""
|
||||
local db_port=""
|
||||
local repair_command=""
|
||||
|
||||
database_id="$(get_stack_database_id "${stack_dir}" || true)"
|
||||
redis_id="$(get_stack_redis_id "${stack_dir}" || true)"
|
||||
|
||||
case "${database_id}" in
|
||||
mariadb)
|
||||
db_host="db"
|
||||
db_port="3306"
|
||||
;;
|
||||
postgres)
|
||||
db_host="db"
|
||||
db_port="5432"
|
||||
;;
|
||||
*)
|
||||
return 57
|
||||
;;
|
||||
esac
|
||||
|
||||
repair_command="$(
|
||||
cat <<EOF
|
||||
mkdir -p sites
|
||||
test -f sites/common_site_config.json || printf '{}' > sites/common_site_config.json
|
||||
ls -1 apps > sites/apps.txt
|
||||
bench set-config -g db_host ${db_host}
|
||||
bench set-config -gp db_port ${db_port}
|
||||
EOF
|
||||
)"
|
||||
|
||||
case "${redis_id}" in
|
||||
enabled)
|
||||
repair_command="${repair_command}"$'\n'"bench set-config -g redis_cache redis://redis-cache:6379"
|
||||
repair_command="${repair_command}"$'\n'"bench set-config -g redis_queue redis://redis-queue:6379"
|
||||
repair_command="${repair_command}"$'\n'"bench set-config -g redis_socketio redis://redis-queue:6379"
|
||||
;;
|
||||
"" | disabled)
|
||||
:
|
||||
;;
|
||||
*)
|
||||
return 62
|
||||
;;
|
||||
esac
|
||||
|
||||
repair_command="${repair_command}"$'\n'"bench set-config -gp socketio_port 9000"
|
||||
repair_command="${repair_command}"$'\n'"bench set-config -g chromium_path /usr/bin/chromium-headless-shell"
|
||||
|
||||
if ! run_stack_backend_bash_command "${stack_dir}" "${repair_command}"; then
|
||||
return 62
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
stack_site_has_partial_artifacts() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
|
||||
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
||||
return 61
|
||||
fi
|
||||
|
||||
if stack_site_exists_in_bench "${stack_dir}" "${site_name}"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case $? in
|
||||
61)
|
||||
return 61
|
||||
;;
|
||||
54 | 52)
|
||||
return $?
|
||||
;;
|
||||
esac
|
||||
|
||||
if stack_site_directory_exists "${stack_dir}" "${site_name}"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case $? in
|
||||
61)
|
||||
return 61
|
||||
;;
|
||||
54 | 52)
|
||||
return $?
|
||||
;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
drop_stack_site_database() {
|
||||
local stack_dir="${1}"
|
||||
local db_name="${2}"
|
||||
local db_password=""
|
||||
local db_endpoint=""
|
||||
local db_host=""
|
||||
local db_port=""
|
||||
local drop_db_command=""
|
||||
|
||||
db_password="$(get_stack_database_root_password "${stack_dir}")"
|
||||
db_endpoint="$(get_stack_common_db_endpoint "${stack_dir}" || true)"
|
||||
db_host="${db_endpoint%%|*}"
|
||||
db_port="${db_endpoint#*|}"
|
||||
|
||||
if [ -z "${db_host}" ] || [ -z "${db_port}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
drop_db_command="$(
|
||||
printf "mysql --protocol=TCP -h %s -P %s -u root -p%s -e %s" \
|
||||
"$(shell_quote_site_command_arg "${db_host}")" \
|
||||
"$(shell_quote_site_command_arg "${db_port}")" \
|
||||
"$(printf '%s' "${db_password}" | sed "s/'/'\"'\"'/g")" \
|
||||
"$(shell_quote_site_command_arg "DROP DATABASE IF EXISTS \`${db_name}\`; DROP USER IF EXISTS '${db_name}'@'%'; DROP USER IF EXISTS '${db_name}'@'localhost'; FLUSH PRIVILEGES;")"
|
||||
)"
|
||||
|
||||
if ! run_stack_backend_bash_command "${stack_dir}" "${drop_db_command}"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
remove_stack_site_directory() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
local remove_command=""
|
||||
|
||||
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
||||
return 61
|
||||
fi
|
||||
|
||||
remove_command="$(
|
||||
printf "rm -rf -- sites/%s archived_sites/%s" \
|
||||
"$(shell_quote_site_command_arg "${site_name}")" \
|
||||
"$(shell_quote_site_command_arg "${site_name}")"
|
||||
)"
|
||||
|
||||
if ! run_stack_backend_bash_command "${stack_dir}" "${remove_command}"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
cleanup_partial_stack_site() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
local artifact_status=0
|
||||
local db_name=""
|
||||
local has_site_config=1
|
||||
|
||||
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
||||
return 61
|
||||
fi
|
||||
|
||||
if stack_site_has_partial_artifacts "${stack_dir}" "${site_name}"; then
|
||||
:
|
||||
else
|
||||
artifact_status=$?
|
||||
case "${artifact_status}" in
|
||||
61)
|
||||
return 61
|
||||
;;
|
||||
54 | 52)
|
||||
return "${artifact_status}"
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if stack_site_config_exists "${stack_dir}" "${site_name}"; then
|
||||
:
|
||||
else
|
||||
artifact_status=$?
|
||||
case "${artifact_status}" in
|
||||
61)
|
||||
return 61
|
||||
;;
|
||||
54 | 52)
|
||||
return "${artifact_status}"
|
||||
;;
|
||||
*)
|
||||
has_site_config=0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ "${has_site_config}" -eq 1 ]; then
|
||||
db_name="$(get_stack_site_database_name "${stack_dir}" "${site_name}" || true)"
|
||||
if [ -z "${db_name}" ]; then
|
||||
return 60
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${has_site_config}" -eq 1 ] && ! drop_stack_site_database "${stack_dir}" "${db_name}"; then
|
||||
return 60
|
||||
fi
|
||||
|
||||
if ! remove_stack_site_directory "${stack_dir}" "${site_name}"; then
|
||||
return 60
|
||||
fi
|
||||
|
||||
if stack_site_has_partial_artifacts "${stack_dir}" "${site_name}"; then
|
||||
return 60
|
||||
fi
|
||||
|
||||
artifact_status=$?
|
||||
case "${artifact_status}" in
|
||||
54 | 52)
|
||||
return "${artifact_status}"
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
create_first_stack_site() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
local admin_password="${3}"
|
||||
local create_site_command=""
|
||||
|
||||
create_site_command="$(
|
||||
printf "bench new-site %s --mariadb-user-host-login-scope='%%' --admin-password %s --db-root-username root --db-root-password %s" \
|
||||
"$(shell_quote_site_command_arg "${site_name}")" \
|
||||
"$(shell_quote_site_command_arg "${admin_password}")" \
|
||||
"$(shell_quote_site_command_arg "$(get_stack_database_root_password "${stack_dir}")")"
|
||||
)"
|
||||
|
||||
if ! run_stack_backend_bash_command "${stack_dir}" "${create_site_command}"; then
|
||||
return 55
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
install_stack_apps_on_site() {
|
||||
local result_var="${1}"
|
||||
local stack_dir="${2}"
|
||||
local site_name="${3}"
|
||||
local selected_app_lines=""
|
||||
local installed_app_lines=""
|
||||
local app_name=""
|
||||
local install_app_command=""
|
||||
|
||||
if ! get_stack_selected_installable_apps selected_app_lines "${stack_dir}"; then
|
||||
printf -v "${result_var}" "%s" ""
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r app_name; do
|
||||
if [ -z "${app_name}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
install_app_command="$(
|
||||
printf "bench --site %s install-app %s" \
|
||||
"$(shell_quote_site_command_arg "${site_name}")" \
|
||||
"$(shell_quote_site_command_arg "${app_name}")"
|
||||
)"
|
||||
|
||||
if ! run_stack_backend_bash_command "${stack_dir}" "${install_app_command}"; then
|
||||
printf -v "${result_var}" "%s" "${installed_app_lines}"
|
||||
return 56
|
||||
fi
|
||||
|
||||
if [ -z "${installed_app_lines}" ]; then
|
||||
installed_app_lines="${app_name}"
|
||||
else
|
||||
installed_app_lines="${installed_app_lines}"$'\n'"${app_name}"
|
||||
fi
|
||||
|
||||
if ! persist_stack_site_metadata \
|
||||
"${stack_dir}" \
|
||||
"single-site" \
|
||||
"${site_name}" \
|
||||
"apps_installing" \
|
||||
"${installed_app_lines}" \
|
||||
"install-apps" \
|
||||
"" \
|
||||
"$(get_stack_site_created_at "${stack_dir}" || true)" \
|
||||
"$(get_current_utc_timestamp)"; then
|
||||
return 58
|
||||
fi
|
||||
done <<EOF
|
||||
${selected_app_lines}
|
||||
EOF
|
||||
|
||||
printf -v "${result_var}" "%s" "${installed_app_lines}"
|
||||
return 0
|
||||
}
|
||||
|
||||
bootstrap_first_stack_site() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2}"
|
||||
local admin_password="${3}"
|
||||
local created_at=""
|
||||
local updated_at=""
|
||||
local installed_app_lines=""
|
||||
local site_create_status=0
|
||||
local app_install_status=0
|
||||
local cleanup_status=0
|
||||
|
||||
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
||||
return 61
|
||||
fi
|
||||
|
||||
if ! stack_supports_single_site_management "${stack_dir}"; then
|
||||
return 52
|
||||
fi
|
||||
|
||||
if ! stack_site_bootstrap_supports_database "${stack_dir}"; then
|
||||
return 57
|
||||
fi
|
||||
|
||||
if stack_has_site_configured "${stack_dir}"; then
|
||||
return 53
|
||||
fi
|
||||
|
||||
if ! stack_backend_service_is_running "${stack_dir}"; then
|
||||
return 51
|
||||
fi
|
||||
|
||||
if ! repair_stack_site_runtime_state "${stack_dir}"; then
|
||||
return $?
|
||||
fi
|
||||
|
||||
if ! stack_database_service_is_reachable "${stack_dir}"; then
|
||||
return 59
|
||||
fi
|
||||
|
||||
created_at="$(get_current_utc_timestamp)"
|
||||
updated_at="${created_at}"
|
||||
if ! persist_stack_site_metadata "${stack_dir}" "single-site" "${site_name}" "requested" "" "create-site" "" "${created_at}" "${updated_at}"; then
|
||||
return 58
|
||||
fi
|
||||
|
||||
if cleanup_partial_stack_site "${stack_dir}" "${site_name}"; then
|
||||
:
|
||||
else
|
||||
cleanup_status=$?
|
||||
case "${cleanup_status}" in
|
||||
54 | 52)
|
||||
return "${cleanup_status}"
|
||||
;;
|
||||
60)
|
||||
mark_stack_site_failed "${stack_dir}" "${site_name}" "" "cleanup-partial-site" "Partial site artifacts could not be removed automatically. Manual cleanup is required." "" >/dev/null 2>&1 || true
|
||||
return 60
|
||||
;;
|
||||
*)
|
||||
mark_stack_site_failed "${stack_dir}" "${site_name}" "" "cleanup-partial-site" "Unexpected cleanup failure before create-site." "${created_at}" >/dev/null 2>&1 || true
|
||||
return 60
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
updated_at="${created_at}"
|
||||
if ! persist_stack_site_metadata "${stack_dir}" "single-site" "${site_name}" "creating" "" "create-site" "" "${created_at}" "${updated_at}"; then
|
||||
return 58
|
||||
fi
|
||||
|
||||
if create_first_stack_site "${stack_dir}" "${site_name}" "${admin_password}"; then
|
||||
:
|
||||
else
|
||||
site_create_status=$?
|
||||
if cleanup_partial_stack_site "${stack_dir}" "${site_name}"; then
|
||||
mark_stack_site_failed "${stack_dir}" "${site_name}" "" "create-site" "bench new-site failed. Partial site data was cleaned up automatically." "${created_at}" >/dev/null 2>&1 || true
|
||||
return "${site_create_status}"
|
||||
fi
|
||||
|
||||
cleanup_status=$?
|
||||
mark_stack_site_failed "${stack_dir}" "${site_name}" "" "create-site" "bench new-site failed and partial site data could not be cleaned up automatically. Manual cleanup is required." "${created_at}" >/dev/null 2>&1 || true
|
||||
case "${cleanup_status}" in
|
||||
54 | 52)
|
||||
return "${cleanup_status}"
|
||||
;;
|
||||
*)
|
||||
return 60
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
updated_at="$(get_current_utc_timestamp)"
|
||||
if ! persist_stack_site_metadata "${stack_dir}" "single-site" "${site_name}" "created" "" "create-site" "" "${created_at}" "${updated_at}"; then
|
||||
return 58
|
||||
fi
|
||||
|
||||
if install_stack_apps_on_site installed_app_lines "${stack_dir}" "${site_name}"; then
|
||||
:
|
||||
else
|
||||
app_install_status=$?
|
||||
case "${app_install_status}" in
|
||||
56)
|
||||
if cleanup_partial_stack_site "${stack_dir}" "${site_name}"; then
|
||||
mark_stack_site_failed "${stack_dir}" "${site_name}" "${installed_app_lines}" "install-apps" "App installation failed. Partial site data was cleaned up automatically." "${created_at}" >/dev/null 2>&1 || true
|
||||
else
|
||||
cleanup_status=$?
|
||||
mark_stack_site_failed "${stack_dir}" "${site_name}" "${installed_app_lines}" "install-apps" "App installation failed and partial site data could not be cleaned up automatically. Manual cleanup is required." "${created_at}" >/dev/null 2>&1 || true
|
||||
case "${cleanup_status}" in
|
||||
54 | 52)
|
||||
return "${cleanup_status}"
|
||||
;;
|
||||
*)
|
||||
return 60
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
58)
|
||||
return 58
|
||||
;;
|
||||
*)
|
||||
mark_stack_site_failed "${stack_dir}" "${site_name}" "${installed_app_lines}" "install-apps" "Unknown app installation failure." "${created_at}" >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
return "${app_install_status}"
|
||||
fi
|
||||
|
||||
updated_at="$(get_current_utc_timestamp)"
|
||||
if ! persist_stack_site_metadata "${stack_dir}" "single-site" "${site_name}" "ready" "${installed_app_lines}" "install-apps" "" "${created_at}" "${updated_at}"; then
|
||||
return 58
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
350
scripts/easy-docker/lib/app/wizard/common/site/metadata.sh
Executable file
350
scripts/easy-docker/lib/app/wizard/common/site/metadata.sh
Executable file
|
|
@ -0,0 +1,350 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
get_metadata_site_string_field() {
|
||||
local metadata_path="${1}"
|
||||
local field_name="${2}"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
awk -v field_name="${field_name}" '
|
||||
/"site"[[:space:]]*:[[:space:]]*{/ {
|
||||
in_site = 1
|
||||
site_depth = 1
|
||||
next
|
||||
}
|
||||
in_site {
|
||||
if (match($0, "\"" field_name "\"[[:space:]]*:[[:space:]]*\"([^\"]*)\"", parts)) {
|
||||
print parts[1]
|
||||
exit
|
||||
}
|
||||
|
||||
line = $0
|
||||
open_count = gsub(/{/, "{", line)
|
||||
close_count = gsub(/}/, "}", line)
|
||||
site_depth += open_count - close_count
|
||||
if (site_depth <= 0) {
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "${metadata_path}"
|
||||
}
|
||||
|
||||
get_metadata_site_apps_installed_lines() {
|
||||
local metadata_path="${1}"
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
awk '
|
||||
/"site"[[:space:]]*:[[:space:]]*{/ {
|
||||
in_site = 1
|
||||
site_depth = 1
|
||||
next
|
||||
}
|
||||
in_site && /"apps_installed"[[:space:]]*:[[:space:]]*\[/ {
|
||||
in_apps_installed = 1
|
||||
next
|
||||
}
|
||||
in_apps_installed && /\]/ {
|
||||
in_apps_installed = 0
|
||||
next
|
||||
}
|
||||
in_apps_installed {
|
||||
if (match($0, /"([^"]+)"/, parts)) {
|
||||
print parts[1]
|
||||
}
|
||||
}
|
||||
in_site {
|
||||
line = $0
|
||||
open_count = gsub(/{/, "{", line)
|
||||
close_count = gsub(/}/, "}", line)
|
||||
site_depth += open_count - close_count
|
||||
if (site_depth <= 0) {
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "${metadata_path}"
|
||||
}
|
||||
|
||||
get_stack_site_name() {
|
||||
local stack_dir="${1}"
|
||||
|
||||
get_metadata_site_string_field "${stack_dir}/metadata.json" "name"
|
||||
}
|
||||
|
||||
get_stack_site_state() {
|
||||
local stack_dir="${1}"
|
||||
|
||||
get_metadata_site_string_field "${stack_dir}/metadata.json" "state"
|
||||
}
|
||||
|
||||
get_stack_site_created_at() {
|
||||
local stack_dir="${1}"
|
||||
|
||||
get_metadata_site_string_field "${stack_dir}/metadata.json" "created_at"
|
||||
}
|
||||
|
||||
get_stack_site_apps_installed_lines() {
|
||||
local stack_dir="${1}"
|
||||
|
||||
get_metadata_site_apps_installed_lines "${stack_dir}/metadata.json"
|
||||
}
|
||||
|
||||
stack_has_site_record() {
|
||||
local stack_dir="${1}"
|
||||
local site_name=""
|
||||
|
||||
site_name="$(get_stack_site_name "${stack_dir}" || true)"
|
||||
if [ -n "${site_name}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
stack_has_site_configured() {
|
||||
local stack_dir="${1}"
|
||||
local site_state=""
|
||||
|
||||
site_state="$(get_stack_site_state "${stack_dir}" || true)"
|
||||
case "${site_state}" in
|
||||
created | apps_installing | ready)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_stack_site_status_label() {
|
||||
local result_var="${1}"
|
||||
local stack_dir="${2}"
|
||||
local site_state=""
|
||||
local site_name=""
|
||||
local site_status_label=""
|
||||
|
||||
site_state="$(get_stack_site_state "${stack_dir}" || true)"
|
||||
site_name="$(get_stack_site_name "${stack_dir}" || true)"
|
||||
|
||||
case "${site_state}" in
|
||||
"")
|
||||
site_status_label="Not configured"
|
||||
;;
|
||||
requested)
|
||||
site_status_label="Requested"
|
||||
;;
|
||||
creating)
|
||||
site_status_label="Creating"
|
||||
;;
|
||||
created)
|
||||
site_status_label="Created"
|
||||
;;
|
||||
apps_installing)
|
||||
site_status_label="Installing apps"
|
||||
;;
|
||||
ready)
|
||||
site_status_label="Ready"
|
||||
;;
|
||||
failed)
|
||||
site_status_label="Failed"
|
||||
;;
|
||||
*)
|
||||
site_status_label="${site_state}"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "${site_name}" ]; then
|
||||
site_status_label="${site_status_label} (${site_name})"
|
||||
fi
|
||||
|
||||
printf -v "${result_var}" "%s" "${site_status_label}"
|
||||
return 0
|
||||
}
|
||||
|
||||
get_stack_site_menu_entry() {
|
||||
local result_var="${1}"
|
||||
local stack_dir="${2}"
|
||||
local site_name=""
|
||||
local site_status_label=""
|
||||
local menu_entry=""
|
||||
|
||||
site_name="$(get_stack_site_name "${stack_dir}" || true)"
|
||||
if [ -z "${site_name}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
get_stack_site_status_label site_status_label "${stack_dir}"
|
||||
menu_entry="$(printf "%s | %s" "${site_name}" "${site_status_label}")"
|
||||
printf -v "${result_var}" "%s" "${menu_entry}"
|
||||
return 0
|
||||
}
|
||||
|
||||
build_stack_site_apps_installed_json_array() {
|
||||
local result_var="${1}"
|
||||
local apps_installed_lines="${2:-}"
|
||||
local app_name=""
|
||||
local escaped_app_name=""
|
||||
local entries_json=""
|
||||
|
||||
while IFS= read -r app_name; do
|
||||
if [ -z "${app_name}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
escaped_app_name="$(json_escape_string "${app_name}")"
|
||||
if [ -z "${entries_json}" ]; then
|
||||
entries_json="$(printf ' "%s"' "${escaped_app_name}")"
|
||||
else
|
||||
entries_json="${entries_json}"$',\n'"$(printf ' "%s"' "${escaped_app_name}")"
|
||||
fi
|
||||
done <<EOF
|
||||
${apps_installed_lines}
|
||||
EOF
|
||||
|
||||
if [ -z "${entries_json}" ]; then
|
||||
printf -v "${result_var}" '[\n ]'
|
||||
else
|
||||
printf -v "${result_var}" '[\n%s\n ]' "${entries_json}"
|
||||
fi
|
||||
}
|
||||
|
||||
build_stack_site_metadata_json_object() {
|
||||
local result_var="${1}"
|
||||
local site_mode="${2:-single-site}"
|
||||
local site_name="${3:-}"
|
||||
local site_state="${4:-not_created}"
|
||||
local apps_installed_lines="${5:-}"
|
||||
local last_action="${6:-}"
|
||||
local last_error="${7:-}"
|
||||
local created_at="${8:-}"
|
||||
local updated_at="${9:-}"
|
||||
local apps_installed_json_array=""
|
||||
|
||||
build_stack_site_apps_installed_json_array apps_installed_json_array "${apps_installed_lines}"
|
||||
|
||||
printf -v "${result_var}" '{\n "mode": "%s",\n "name": "%s",\n "state": "%s",\n "apps_installed": %s,\n "last_action": "%s",\n "last_error": "%s",\n "created_at": "%s",\n "updated_at": "%s"\n }' \
|
||||
"$(json_escape_string "${site_mode}")" \
|
||||
"$(json_escape_string "${site_name}")" \
|
||||
"$(json_escape_string "${site_state}")" \
|
||||
"${apps_installed_json_array}" \
|
||||
"$(json_escape_string "${last_action}")" \
|
||||
"$(json_escape_string "${last_error}")" \
|
||||
"$(json_escape_string "${created_at}")" \
|
||||
"$(json_escape_string "${updated_at}")"
|
||||
}
|
||||
|
||||
persist_stack_site_metadata() {
|
||||
local stack_dir="${1}"
|
||||
local site_mode="${2:-single-site}"
|
||||
local site_name="${3:-}"
|
||||
local site_state="${4:-not_created}"
|
||||
local apps_installed_lines="${5:-}"
|
||||
local last_action="${6:-}"
|
||||
local last_error="${7:-}"
|
||||
local created_at="${8:-}"
|
||||
local updated_at="${9:-}"
|
||||
local metadata_path=""
|
||||
local metadata_tmp_path=""
|
||||
local site_json_object=""
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
metadata_tmp_path="${metadata_path}.tmp"
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
build_stack_site_metadata_json_object site_json_object "${site_mode}" "${site_name}" "${site_state}" "${apps_installed_lines}" "${last_action}" "${last_error}" "${created_at}" "${updated_at}"
|
||||
|
||||
if ! awk -v site_object="${site_json_object}" '
|
||||
BEGIN {
|
||||
in_site = 0
|
||||
inserted = 0
|
||||
site_depth = 0
|
||||
prev = ""
|
||||
}
|
||||
function flush_prev() {
|
||||
if (prev != "") {
|
||||
print prev
|
||||
prev = ""
|
||||
}
|
||||
}
|
||||
{
|
||||
if (!in_site && $0 ~ /^ "site"[[:space:]]*:[[:space:]]*{/) {
|
||||
in_site = 1
|
||||
site_depth = 1
|
||||
next
|
||||
}
|
||||
|
||||
if (in_site) {
|
||||
line = $0
|
||||
open_count = gsub(/{/, "{", line)
|
||||
close_count = gsub(/}/, "}", line)
|
||||
site_depth += open_count - close_count
|
||||
if (site_depth <= 0) {
|
||||
in_site = 0
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
if (!inserted && $0 ~ /^}$/) {
|
||||
if (prev != "") {
|
||||
if (prev ~ /,$/) {
|
||||
print prev
|
||||
} else {
|
||||
print prev ","
|
||||
}
|
||||
prev = ""
|
||||
}
|
||||
print " \"site\": " site_object
|
||||
print "}"
|
||||
inserted = 1
|
||||
next
|
||||
}
|
||||
|
||||
flush_prev()
|
||||
prev = $0
|
||||
}
|
||||
END {
|
||||
if (!inserted) {
|
||||
if (prev != "") {
|
||||
if (prev ~ /,$/) {
|
||||
print prev
|
||||
} else {
|
||||
print prev ","
|
||||
}
|
||||
}
|
||||
print " \"site\": " site_object
|
||||
print "}"
|
||||
} else if (prev != "") {
|
||||
print prev
|
||||
}
|
||||
}
|
||||
' "${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
|
||||
}
|
||||
|
||||
mark_stack_site_failed() {
|
||||
local stack_dir="${1}"
|
||||
local site_name="${2:-}"
|
||||
local apps_installed_lines="${3:-}"
|
||||
local last_action="${4:-bootstrap-site}"
|
||||
local last_error="${5:-Unknown site bootstrap failure}"
|
||||
local created_at="${6:-}"
|
||||
local updated_at=""
|
||||
|
||||
updated_at="$(get_current_utc_timestamp)"
|
||||
persist_stack_site_metadata "${stack_dir}" "single-site" "${site_name}" "failed" "${apps_installed_lines}" "${last_action}" "${last_error}" "${created_at}" "${updated_at}"
|
||||
}
|
||||
|
|
@ -63,6 +63,198 @@ run_build_stack_custom_image_with_feedback() {
|
|||
return "${build_image_status}"
|
||||
}
|
||||
|
||||
prompt_manage_stack_site_name_with_cancel() {
|
||||
local result_var="${1}"
|
||||
local stack_name="${2}"
|
||||
local stack_dir="${3}"
|
||||
local input_site_name=""
|
||||
local suggestion=""
|
||||
local prompt_status=0
|
||||
|
||||
suggestion="$(get_stack_primary_site_name_suggestion "${stack_dir}" || true)"
|
||||
while true; do
|
||||
input_site_name="$(prompt_stack_site_name "${stack_name}" "${suggestion}")"
|
||||
prompt_status=$?
|
||||
if [ "${prompt_status}" -ne 0 ]; then
|
||||
return "${FLOW_ABORT_INPUT}"
|
||||
fi
|
||||
|
||||
input_site_name="$(printf '%s' "${input_site_name}" | tr -d '\r\n')"
|
||||
case "${input_site_name}" in
|
||||
"")
|
||||
show_warning_and_wait "Site name is required." 2
|
||||
;;
|
||||
/back | /Back | /BACK)
|
||||
return "${FLOW_ABORT_INPUT}"
|
||||
;;
|
||||
*)
|
||||
if ! is_valid_stack_site_name "${input_site_name}"; then
|
||||
show_warning_and_wait "Site name may only contain letters, numbers, dots, dashes, and underscores." 3
|
||||
continue
|
||||
fi
|
||||
printf -v "${result_var}" "%s" "${input_site_name}"
|
||||
return "${FLOW_CONTINUE}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
prompt_manage_stack_site_admin_password_with_cancel() {
|
||||
local result_var="${1}"
|
||||
local stack_name="${2}"
|
||||
local input_admin_password=""
|
||||
local prompt_status=0
|
||||
|
||||
while true; do
|
||||
input_admin_password="$(prompt_stack_site_admin_password "${stack_name}")"
|
||||
prompt_status=$?
|
||||
if [ "${prompt_status}" -ne 0 ]; then
|
||||
return "${FLOW_ABORT_INPUT}"
|
||||
fi
|
||||
|
||||
input_admin_password="$(printf '%s' "${input_admin_password}" | tr -d '\r\n')"
|
||||
case "${input_admin_password}" in
|
||||
"")
|
||||
show_warning_and_wait "Administrator password is required." 2
|
||||
;;
|
||||
/back | /Back | /BACK)
|
||||
return "${FLOW_ABORT_INPUT}"
|
||||
;;
|
||||
*)
|
||||
printf -v "${result_var}" "%s" "${input_admin_password}"
|
||||
return "${FLOW_CONTINUE}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
handle_manage_stack_site_flow() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local site_status_label=""
|
||||
local site_action=""
|
||||
local site_name=""
|
||||
local admin_password=""
|
||||
local site_flow_status=0
|
||||
local existing_site_entry=""
|
||||
local existing_site_name=""
|
||||
local existing_site_created_at=""
|
||||
local existing_site_apps_lines=""
|
||||
local existing_site_apps_csv=""
|
||||
local existing_site_details_action=""
|
||||
|
||||
while true; do
|
||||
get_stack_site_status_label site_status_label "${stack_dir}"
|
||||
existing_site_entry=""
|
||||
get_stack_site_menu_entry existing_site_entry "${stack_dir}" || true
|
||||
|
||||
site_action="$(show_manage_stack_site_menu "${stack_name}" "${stack_dir}" "${site_status_label}" "${existing_site_entry}" || true)"
|
||||
case "${site_action}" in
|
||||
"Create new site")
|
||||
if ! prompt_manage_stack_site_name_with_cancel site_name "${stack_name}" "${stack_dir}"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! prompt_manage_stack_site_admin_password_with_cancel admin_password "${stack_name}"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
show_warning_message "Creating the first site for stack: ${stack_name}"
|
||||
if bootstrap_first_stack_site "${stack_dir}" "${site_name}" "${admin_password}"; then
|
||||
show_warning_and_wait "Site created successfully and selected stack apps were installed: ${site_name}" 3
|
||||
continue
|
||||
else
|
||||
site_flow_status=$?
|
||||
fi
|
||||
|
||||
case "${site_flow_status}" in
|
||||
51)
|
||||
show_warning_and_wait "Cannot manage site: backend service is not running yet. Start the stack first." 4
|
||||
;;
|
||||
52)
|
||||
show_warning_and_wait "Cannot manage site for this topology yet. Only single-host stacks are supported." 4
|
||||
;;
|
||||
53)
|
||||
show_warning_and_wait "A site is already configured for this stack. Phase 1 supports one site per stack." 4
|
||||
;;
|
||||
54)
|
||||
show_warning_and_wait "Cannot manage site because stack metadata, env, or compose inputs are incomplete." 4
|
||||
;;
|
||||
55)
|
||||
show_warning_and_wait "Could not create the site. Check the output above for bench new-site details." 4
|
||||
;;
|
||||
56)
|
||||
show_warning_and_wait "The site was created, but app installation failed. Check the output above." 4
|
||||
;;
|
||||
57)
|
||||
show_warning_and_wait "Site bootstrap currently supports only MariaDB-backed single-host stacks." 4
|
||||
;;
|
||||
58)
|
||||
show_warning_and_wait "The site state could not be written to metadata.json." 4
|
||||
;;
|
||||
59)
|
||||
show_warning_and_wait "Cannot create site: stack services are not ready yet. Wait and try again." 4
|
||||
;;
|
||||
60)
|
||||
show_warning_and_wait "Site creation failed and automatic cleanup could not remove all partial data. Manual cleanup is required." 5
|
||||
;;
|
||||
61)
|
||||
show_warning_and_wait "Cannot create site because the site name was empty or unsafe for cleanup operations." 4
|
||||
;;
|
||||
62)
|
||||
show_warning_and_wait "Cannot prepare the stack for site creation because the bench runtime files could not be repaired." 4
|
||||
;;
|
||||
*)
|
||||
show_warning_and_wait "Site bootstrap failed (${site_flow_status})." 4
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
"Back" | "")
|
||||
return "${FLOW_CONTINUE}"
|
||||
;;
|
||||
"Exit and close easy-docker")
|
||||
return "${FLOW_EXIT_APP}"
|
||||
;;
|
||||
*)
|
||||
if [ -n "${existing_site_entry}" ] && [ "${site_action}" = "${existing_site_entry}" ]; then
|
||||
existing_site_name="$(get_stack_site_name "${stack_dir}" || true)"
|
||||
existing_site_created_at="$(get_stack_site_created_at "${stack_dir}" || true)"
|
||||
existing_site_apps_lines="$(get_stack_site_apps_installed_lines "${stack_dir}" || true)"
|
||||
if [ -n "${existing_site_apps_lines}" ]; then
|
||||
existing_site_apps_csv="$(printf '%s' "${existing_site_apps_lines}" | tr '\n' ',' | sed 's/,$//')"
|
||||
else
|
||||
existing_site_apps_csv="None"
|
||||
fi
|
||||
|
||||
existing_site_details_action="$(
|
||||
show_manage_stack_site_details \
|
||||
"${stack_name}" \
|
||||
"${stack_dir}" \
|
||||
"${existing_site_name}" \
|
||||
"${site_status_label}" \
|
||||
"${existing_site_created_at}" \
|
||||
"${existing_site_apps_csv}" || true
|
||||
)"
|
||||
case "${existing_site_details_action}" in
|
||||
"Back" | "")
|
||||
continue
|
||||
;;
|
||||
"Exit and close easy-docker")
|
||||
return "${FLOW_EXIT_APP}"
|
||||
;;
|
||||
*)
|
||||
show_warning_and_wait "Unknown site details action: ${existing_site_details_action}" 2
|
||||
;;
|
||||
esac
|
||||
continue
|
||||
fi
|
||||
|
||||
show_warning_and_wait "Unknown site action: ${site_action}" 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
handle_manage_selected_stack_flow() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir=""
|
||||
|
|
@ -289,6 +481,21 @@ handle_manage_selected_stack_flow() {
|
|||
esac
|
||||
done
|
||||
;;
|
||||
"Site")
|
||||
if handle_manage_stack_site_flow "${stack_name}" "${stack_dir}"; then
|
||||
:
|
||||
else
|
||||
compose_start_status=$?
|
||||
case "${compose_start_status}" in
|
||||
"${FLOW_EXIT_APP}")
|
||||
return "${FLOW_EXIT_APP}"
|
||||
;;
|
||||
*)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
"Back" | "")
|
||||
return "${FLOW_CONTINUE}"
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -65,12 +65,13 @@ show_manage_stack_actions_menu() {
|
|||
menu_header="$(printf "Stack actions | %s" "${stack_runtime_status}")"
|
||||
|
||||
gum choose \
|
||||
--height 9 \
|
||||
--height 10 \
|
||||
--header "${menu_header}" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Apps" \
|
||||
"Docker" \
|
||||
"Site" \
|
||||
"Start stack in Docker Compose" \
|
||||
"Stop stack in Docker Compose" \
|
||||
"Back" \
|
||||
|
|
@ -121,6 +122,95 @@ show_manage_stack_docker_menu() {
|
|||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_manage_stack_site_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local site_status="${3:-Not configured}"
|
||||
local existing_site_entry="${4:-}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack site\n\nStack: %s\nDirectory: %s\nSite status: %s\n\nCreate a new site or select an existing site for this stack." "${stack_name}" "${stack_dir}" "${site_status}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
if [ -n "${existing_site_entry}" ]; then
|
||||
gum choose \
|
||||
--height 10 \
|
||||
--header "Stack site actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Create new site" \
|
||||
"${existing_site_entry}" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
return 0
|
||||
fi
|
||||
|
||||
gum choose \
|
||||
--height 8 \
|
||||
--header "Stack site actions" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Create new site" \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
prompt_stack_site_name() {
|
||||
local stack_name="${1}"
|
||||
local placeholder="${2:-}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack site\n\nStack: %s\n\nEnter the site name for the first site.\nType /back or press Ctrl+C to cancel." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum input \
|
||||
--header "Site name" \
|
||||
--prompt "site> " \
|
||||
--placeholder "${placeholder}"
|
||||
}
|
||||
|
||||
prompt_stack_site_admin_password() {
|
||||
local stack_name="${1}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage stack site\n\nStack: %s\n\nEnter the Administrator password for the new site.\nType /back or press Ctrl+C to cancel." "${stack_name}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum input \
|
||||
--header "Administrator password" \
|
||||
--prompt "password> " \
|
||||
--password
|
||||
}
|
||||
|
||||
show_manage_stack_site_details() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
local site_name="${3}"
|
||||
local site_status="${4:-Unknown}"
|
||||
local created_at="${5:-}"
|
||||
local installed_apps="${6:-None}"
|
||||
local status_text=""
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Manage existing site\n\nStack: %s\nDirectory: %s\nSite: %s\nStatus: %s\nCreated at: %s\nInstalled apps: %s" "${stack_name}" "${stack_dir}" "${site_name}" "${site_status}" "${created_at:-n/a}" "${installed_apps}")"
|
||||
render_box_message "${status_text}" "0 2" >&2
|
||||
|
||||
gum choose \
|
||||
--height 7 \
|
||||
--header "Site details" \
|
||||
--cursor.foreground 63 \
|
||||
--selected.foreground 45 \
|
||||
"Back" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_missing_custom_image_start_menu() {
|
||||
local stack_name="${1}"
|
||||
local stack_dir="${2}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue