mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-17 13:55:08 +00:00
982 lines
25 KiB
Bash
Executable file
982 lines
25 KiB
Bash
Executable file
#!/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}" </dev/null
|
|
return $?
|
|
fi
|
|
|
|
docker compose --project-name "${compose_project_name}" --env-file "${env_path}" "${compose_args[@]}" exec -T backend bash -lc "${wrapped_backend_command}" </dev/null
|
|
}
|
|
|
|
reset_easy_docker_site_error_state() {
|
|
EASY_DOCKER_SITE_ERROR_DETAIL=""
|
|
EASY_DOCKER_SITE_ERROR_LOG_PATH=""
|
|
}
|
|
|
|
build_stack_site_error_log_relative_path() {
|
|
local result_var="${1}"
|
|
local action_name="${2:-site-error}"
|
|
local raw_timestamp=""
|
|
local safe_timestamp=""
|
|
local relative_path=""
|
|
|
|
raw_timestamp="$(get_current_utc_timestamp)"
|
|
safe_timestamp="$(printf '%s' "${raw_timestamp}" | tr ':' '-')"
|
|
relative_path="$(printf 'logs/%s-%s.log' "${action_name}" "${safe_timestamp}")"
|
|
printf -v "${result_var}" "%s" "${relative_path}"
|
|
}
|
|
|
|
write_stack_site_error_log() {
|
|
local result_var="${1}"
|
|
local stack_dir="${2}"
|
|
local action_name="${3:-site-error}"
|
|
local error_output="${4:-}"
|
|
local relative_path=""
|
|
local log_dir=""
|
|
local absolute_path=""
|
|
|
|
if [ -z "${error_output}" ]; then
|
|
printf -v "${result_var}" "%s" ""
|
|
return 0
|
|
fi
|
|
|
|
build_stack_site_error_log_relative_path relative_path "${action_name}"
|
|
log_dir="${stack_dir}/logs"
|
|
absolute_path="${stack_dir}/${relative_path}"
|
|
|
|
if ! mkdir -p "${log_dir}"; then
|
|
return 1
|
|
fi
|
|
|
|
if ! printf '%s\n' "${error_output}" >"${absolute_path}"; then
|
|
return 1
|
|
fi
|
|
|
|
printf -v "${result_var}" "%s" "${relative_path}"
|
|
return 0
|
|
}
|
|
|
|
run_stack_backend_bash_command_capture() {
|
|
local result_var="${1}"
|
|
local stack_dir="${2}"
|
|
local backend_command="${3}"
|
|
local command_output=""
|
|
local command_status=0
|
|
|
|
reset_easy_docker_site_error_state
|
|
command_output="$(run_stack_backend_bash_command "${stack_dir}" "${backend_command}" 2>&1)"
|
|
command_status=$?
|
|
|
|
if [ -n "${command_output}" ]; then
|
|
printf '%s\n' "${command_output}"
|
|
fi
|
|
|
|
printf -v "${result_var}" "%s" "${command_output}"
|
|
return "${command_status}"
|
|
}
|
|
|
|
capture_stack_site_error_log() {
|
|
local stack_dir="${1}"
|
|
local action_name="${2:-site-error}"
|
|
local error_output="${3:-}"
|
|
local log_path=""
|
|
|
|
EASY_DOCKER_SITE_ERROR_LOG_PATH=""
|
|
if [ -z "${error_output}" ]; then
|
|
return 0
|
|
fi
|
|
|
|
if ! write_stack_site_error_log log_path "${stack_dir}" "${action_name}" "${error_output}"; then
|
|
EASY_DOCKER_SITE_ERROR_DETAIL="${EASY_DOCKER_SITE_ERROR_DETAIL:-Failed to write site error log.}"
|
|
return 1
|
|
fi
|
|
|
|
EASY_DOCKER_SITE_ERROR_LOG_PATH="${log_path}"
|
|
return 0
|
|
}
|
|
|
|
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}"
|
|
}
|
|
|
|
get_stack_site_runtime_app_names_lines() {
|
|
local stack_dir="${1}"
|
|
local site_name="${2}"
|
|
local list_apps_command=""
|
|
|
|
if ! is_safe_stack_site_cleanup_name "${site_name}"; then
|
|
return 61
|
|
fi
|
|
|
|
list_apps_command="$(
|
|
printf "bench --site %s list-apps | awk 'NF { print \$1 }'" \
|
|
"$(shell_quote_site_command_arg "${site_name}")"
|
|
)"
|
|
|
|
run_stack_backend_bash_command "${stack_dir}" "${list_apps_command}"
|
|
}
|
|
|
|
get_stack_runtime_available_app_lines() {
|
|
local stack_dir="${1}"
|
|
|
|
run_stack_backend_bash_command "${stack_dir}" "ls -1 apps"
|
|
}
|
|
|
|
get_stack_site_runtime_selected_apps_lines() {
|
|
local result_var="${1}"
|
|
local stack_dir="${2}"
|
|
local site_name="${3}"
|
|
local selected_app_lines=""
|
|
local runtime_app_lines=""
|
|
local selected_app_name=""
|
|
local installed_app_lines=""
|
|
|
|
if ! get_stack_selected_installable_apps selected_app_lines "${stack_dir}"; then
|
|
printf -v "${result_var}" "%s" ""
|
|
return 0
|
|
fi
|
|
|
|
if [ -z "${selected_app_lines}" ]; then
|
|
printf -v "${result_var}" "%s" ""
|
|
return 0
|
|
fi
|
|
|
|
runtime_app_lines="$(get_stack_site_runtime_app_names_lines "${stack_dir}" "${site_name}" || true)"
|
|
if [ -z "${runtime_app_lines}" ]; then
|
|
printf -v "${result_var}" "%s" ""
|
|
return 1
|
|
fi
|
|
|
|
while IFS= read -r selected_app_name; do
|
|
if [ -z "${selected_app_name}" ]; then
|
|
continue
|
|
fi
|
|
|
|
if ! printf '%s\n' "${runtime_app_lines}" | grep -F -x -- "${selected_app_name}" >/dev/null 2>&1; then
|
|
continue
|
|
fi
|
|
|
|
if [ -z "${installed_app_lines}" ]; then
|
|
installed_app_lines="${selected_app_name}"
|
|
else
|
|
installed_app_lines="${installed_app_lines}"$'\n'"${selected_app_name}"
|
|
fi
|
|
done <<EOF
|
|
${selected_app_lines}
|
|
EOF
|
|
|
|
printf -v "${result_var}" "%s" "${installed_app_lines}"
|
|
return 0
|
|
}
|
|
|
|
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=""
|
|
local create_site_output=""
|
|
|
|
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_capture create_site_output "${stack_dir}" "${create_site_command}"; then
|
|
EASY_DOCKER_SITE_ERROR_DETAIL="bench new-site failed."
|
|
capture_stack_site_error_log "${stack_dir}" "site-create-error" "${create_site_output}" >/dev/null 2>&1 || true
|
|
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=""
|
|
local install_app_output=""
|
|
local available_app_lines=""
|
|
local -a selected_apps=()
|
|
|
|
if ! get_stack_selected_installable_apps selected_app_lines "${stack_dir}"; then
|
|
printf -v "${result_var}" "%s" ""
|
|
return 0
|
|
fi
|
|
|
|
if [ -z "${selected_app_lines}" ]; then
|
|
printf -v "${result_var}" "%s" ""
|
|
return 0
|
|
fi
|
|
|
|
available_app_lines="$(get_stack_runtime_available_app_lines "${stack_dir}" || true)"
|
|
if [ -z "${available_app_lines}" ]; then
|
|
EASY_DOCKER_SITE_ERROR_DETAIL="Could not inspect available apps in the backend image."
|
|
capture_stack_site_error_log "${stack_dir}" "site-install-apps-error" "easy-docker could not list /home/frappe/frappe-bench/apps before install-app." >/dev/null 2>&1 || true
|
|
return 63
|
|
fi
|
|
|
|
mapfile -t selected_apps <<<"${selected_app_lines}"
|
|
for app_name in "${selected_apps[@]}"; do
|
|
if [ -z "${app_name}" ]; then
|
|
continue
|
|
fi
|
|
|
|
if ! printf '%s\n' "${available_app_lines}" | grep -F -x -- "${app_name}" >/dev/null 2>&1; then
|
|
EASY_DOCKER_SITE_ERROR_DETAIL="$(printf "Selected app '%s' is not available in the backend image." "${app_name}")"
|
|
capture_stack_site_error_log "${stack_dir}" "site-install-apps-error" "$(printf "Selected app '%s' was requested in stack metadata but is missing from /home/frappe/frappe-bench/apps.\nAvailable apps:\n%s" "${app_name}" "${available_app_lines}")" >/dev/null 2>&1 || true
|
|
if [ -n "${EASY_DOCKER_SITE_ERROR_LOG_PATH}" ]; then
|
|
printf 'Details written to %s\n' "${stack_dir}/${EASY_DOCKER_SITE_ERROR_LOG_PATH}" >&2
|
|
fi
|
|
printf -v "${result_var}" "%s" "${installed_app_lines}"
|
|
return 63
|
|
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_capture install_app_output "${stack_dir}" "${install_app_command}"; then
|
|
EASY_DOCKER_SITE_ERROR_DETAIL="$(printf "bench install-app failed for '%s'." "${app_name}")"
|
|
capture_stack_site_error_log "${stack_dir}" "site-install-apps-error" "${install_app_output}" >/dev/null 2>&1 || true
|
|
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
|
|
|
|
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." "${EASY_DOCKER_SITE_ERROR_LOG_PATH}" "${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." "${EASY_DOCKER_SITE_ERROR_LOG_PATH}" "${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 | 63)
|
|
if cleanup_partial_stack_site "${stack_dir}" "${site_name}"; then
|
|
mark_stack_site_failed "${stack_dir}" "${site_name}" "${installed_app_lines}" "install-apps" "${EASY_DOCKER_SITE_ERROR_DETAIL:-App installation failed. Partial site data was cleaned up automatically.}" "${EASY_DOCKER_SITE_ERROR_LOG_PATH}" "${created_at}" >/dev/null 2>&1 || true
|
|
else
|
|
cleanup_status=$?
|
|
mark_stack_site_failed "${stack_dir}" "${site_name}" "${installed_app_lines}" "install-apps" "${EASY_DOCKER_SITE_ERROR_DETAIL:-App installation failed and partial site data could not be cleaned up automatically. Manual cleanup is required.}" "${EASY_DOCKER_SITE_ERROR_LOG_PATH}" "${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." "${EASY_DOCKER_SITE_ERROR_LOG_PATH}" "${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
|
|
}
|