mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-17 13:55:08 +00:00
refactor(easy-docker): modularize bootstrap and remove shared shell globals
This commit is contained in:
parent
ce750515f2
commit
89ab6a6658
16 changed files with 709 additions and 490 deletions
|
|
@ -10,15 +10,18 @@ bash easy-docker.sh
|
|||
|
||||
## Dependencies
|
||||
|
||||
- `gum` is used for the TUI
|
||||
- The script checks required dependencies on startup
|
||||
- Missing dependencies are installed automatically when possible
|
||||
- `gum` is used for the TUI and is installed automatically when possible
|
||||
- `docker` CLI is required and checked on startup
|
||||
- `docker compose` (Compose v2 command) is required and checked on startup
|
||||
- Docker Desktop includes Compose v2 by default; on Linux Engine-only setups you may need the `docker-compose-plugin` package
|
||||
- Docker daemon must be running before the TUI starts
|
||||
- Required docker commands are validated (`docker ps/exec/inspect/cp` and `docker compose config/up/down/logs/exec/pull/ps`)
|
||||
- If package manager installation for `gum` fails, the script can use a GitHub binary fallback
|
||||
|
||||
## Options
|
||||
|
||||
- `-h`, `--help`
|
||||
- Shows usage and exits without starting the TUI
|
||||
- `--no-github-binary-fallback`
|
||||
- `--no-installation-fallback`
|
||||
- Disables GitHub binary fallback for `gum`
|
||||
- If package manager installation fails, the script exits with manual installation guidance
|
||||
|
|
|
|||
38
scripts/easy-docker/lib/app/options.sh
Executable file
38
scripts/easy-docker/lib/app/options.sh
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
print_usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: bash easy-docker.sh [options]
|
||||
|
||||
Options:
|
||||
--no-installation-fallback Disable installation fallback prompt
|
||||
-h, --help Show this help
|
||||
USAGE
|
||||
}
|
||||
|
||||
parse_cli_options() {
|
||||
local result_var="${1}"
|
||||
local disable_installation_fallback=0
|
||||
shift
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--no-installation-fallback)
|
||||
disable_installation_fallback=1
|
||||
;;
|
||||
-h | --help)
|
||||
print_usage
|
||||
return 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
print_usage
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
printf -v "${result_var}" "%s" "${disable_installation_fallback}"
|
||||
return 0
|
||||
}
|
||||
43
scripts/easy-docker/lib/app/run.sh
Executable file
43
scripts/easy-docker/lib/app/run.sh
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
run_easy_docker_app() {
|
||||
local action=""
|
||||
local local_env_action=""
|
||||
|
||||
enter_alt_screen
|
||||
render_main_screen 1
|
||||
|
||||
while true; do
|
||||
local_env_action=""
|
||||
action="$(show_main_menu || true)"
|
||||
|
||||
if [ -z "${action}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "${action}" in
|
||||
"Environment check")
|
||||
local_env_action="$(show_environment_status || true)"
|
||||
case "${local_env_action}" in
|
||||
"Back to main menu" | "")
|
||||
render_main_screen 1
|
||||
;;
|
||||
"Exit and close easy-docker")
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
show_warning_message "Unknown environment action: ${local_env_action}"
|
||||
sleep 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
"Exit")
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
show_warning_message "Unknown action: ${action}"
|
||||
sleep 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
19
scripts/easy-docker/lib/app/screen.sh
Executable file
19
scripts/easy-docker/lib/app/screen.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
ALT_SCREEN_ACTIVE=0
|
||||
|
||||
enter_alt_screen() {
|
||||
if [ -t 1 ] && command_exists tput; then
|
||||
tput smcup || true
|
||||
tput civis || true
|
||||
ALT_SCREEN_ACTIVE=1
|
||||
fi
|
||||
}
|
||||
|
||||
leave_alt_screen() {
|
||||
if [ "${ALT_SCREEN_ACTIVE}" = "1" ] && command_exists tput; then
|
||||
tput cnorm || true
|
||||
tput rmcup || true
|
||||
ALT_SCREEN_ACTIVE=0
|
||||
fi
|
||||
}
|
||||
79
scripts/easy-docker/lib/checks/docker.sh
Executable file
79
scripts/easy-docker/lib/checks/docker.sh
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
docker_compose_available() {
|
||||
docker compose version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
docker_daemon_running() {
|
||||
docker info >/dev/null 2>&1
|
||||
}
|
||||
|
||||
docker_supports_command() {
|
||||
docker "$@" --help >/dev/null 2>&1
|
||||
}
|
||||
|
||||
get_missing_docker_commands() {
|
||||
local missing=()
|
||||
local subcommand=""
|
||||
|
||||
for subcommand in ps exec inspect cp; do
|
||||
if ! docker_supports_command "${subcommand}"; then
|
||||
missing+=("docker ${subcommand}")
|
||||
fi
|
||||
done
|
||||
|
||||
for subcommand in config up down logs exec pull ps; do
|
||||
if ! docker_supports_command compose "${subcommand}"; then
|
||||
missing+=("docker compose ${subcommand}")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#missing[@]}" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf '%s\n' "${missing[@]}"
|
||||
return 1
|
||||
}
|
||||
|
||||
format_missing_commands_list() {
|
||||
local missing_commands="${1}"
|
||||
local missing_list=""
|
||||
|
||||
missing_list="$(printf '%s' "${missing_commands}" | tr '\n' ',')"
|
||||
missing_list="${missing_list%,}"
|
||||
missing_list="${missing_list#,}"
|
||||
printf '%s\n' "${missing_list}"
|
||||
}
|
||||
|
||||
ensure_docker() {
|
||||
local missing_commands=""
|
||||
local missing_list=""
|
||||
|
||||
if ! command_exists docker; then
|
||||
echo "docker is not installed."
|
||||
print_docker_install_guidance
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! docker_compose_available; then
|
||||
echo "docker compose (Compose v2 command) is not available."
|
||||
print_docker_compose_install_guidance
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! docker_daemon_running; then
|
||||
echo "docker daemon is not running."
|
||||
print_docker_daemon_start_guidance
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! missing_commands="$(get_missing_docker_commands)"; then
|
||||
missing_list="$(format_missing_commands_list "${missing_commands}")"
|
||||
echo "Missing required docker commands: ${missing_list}"
|
||||
print_docker_command_support_guidance
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
39
scripts/easy-docker/lib/core/commands.sh
Executable file
39
scripts/easy-docker/lib/core/commands.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
command_exists() {
|
||||
command -v "${1}" >/dev/null 2>&1 || command -v "${1}.exe" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
run_with_privileges() {
|
||||
if command_exists sudo; then
|
||||
sudo "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
"$@"
|
||||
}
|
||||
|
||||
copy_binary() {
|
||||
local source_path="${1}"
|
||||
local target_path="${2}"
|
||||
|
||||
if command_exists install; then
|
||||
install -m 0755 "${source_path}" "${target_path}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
cp "${source_path}" "${target_path}" && chmod +x "${target_path}"
|
||||
}
|
||||
|
||||
copy_binary_with_privileges() {
|
||||
local source_path="${1}"
|
||||
local target_path="${2}"
|
||||
|
||||
if command_exists install; then
|
||||
run_with_privileges install -m 0755 "${source_path}" "${target_path}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
run_with_privileges cp "${source_path}" "${target_path}" &&
|
||||
run_with_privileges chmod +x "${target_path}"
|
||||
}
|
||||
29
scripts/easy-docker/lib/core/messages.sh
Executable file
29
scripts/easy-docker/lib/core/messages.sh
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
print_manual_gum_install_guidance() {
|
||||
echo "Install gum manually: https://github.com/charmbracelet/gum#installation"
|
||||
echo "If installed into ~/.local/bin, add it to PATH first."
|
||||
}
|
||||
|
||||
print_docker_install_guidance() {
|
||||
echo "Install Docker first: https://docs.docker.com/get-started/get-docker/"
|
||||
}
|
||||
|
||||
print_docker_compose_install_guidance() {
|
||||
echo "This script requires Docker Compose v2 via the 'docker compose' command."
|
||||
echo "Docker Desktop includes it by default."
|
||||
echo "On Linux Engine-only setups, install the Docker Compose CLI plugin package (commonly 'docker-compose-plugin')."
|
||||
echo "Setup docs:"
|
||||
echo "https://docs.docker.com/compose/install/"
|
||||
echo "Note: this script uses 'docker compose' (Compose v2), not the old standalone 'docker-compose'."
|
||||
}
|
||||
|
||||
print_docker_daemon_start_guidance() {
|
||||
echo "Start the Docker daemon/service and retry."
|
||||
echo "If you use Docker Desktop, ensure it is running."
|
||||
}
|
||||
|
||||
print_docker_command_support_guidance() {
|
||||
echo "Update Docker to a recent version and ensure Compose v2 is available as 'docker compose'."
|
||||
echo "Standard 'docker' and 'docker compose' commands are required."
|
||||
}
|
||||
|
|
@ -1,387 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
command_exists() {
|
||||
command -v "${1}" >/dev/null 2>&1 || command -v "${1}.exe" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
run_with_privileges() {
|
||||
if command_exists sudo; then
|
||||
sudo "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
"$@"
|
||||
}
|
||||
|
||||
copy_binary() {
|
||||
local source_path="${1}"
|
||||
local target_path="${2}"
|
||||
|
||||
if command_exists install; then
|
||||
install -m 0755 "${source_path}" "${target_path}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
cp "${source_path}" "${target_path}" && chmod +x "${target_path}"
|
||||
}
|
||||
|
||||
copy_binary_with_privileges() {
|
||||
local source_path="${1}"
|
||||
local target_path="${2}"
|
||||
|
||||
if command_exists install; then
|
||||
run_with_privileges install -m 0755 "${source_path}" "${target_path}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
run_with_privileges cp "${source_path}" "${target_path}" &&
|
||||
run_with_privileges chmod +x "${target_path}"
|
||||
}
|
||||
|
||||
detect_gum_platform() {
|
||||
local raw_os=""
|
||||
local raw_arch=""
|
||||
|
||||
raw_os="$(uname -s 2>/dev/null || echo unknown)"
|
||||
raw_arch="$(uname -m 2>/dev/null || echo unknown)"
|
||||
|
||||
case "${raw_os}" in
|
||||
Linux*)
|
||||
GUM_OS="Linux"
|
||||
;;
|
||||
Darwin*)
|
||||
GUM_OS="Darwin"
|
||||
;;
|
||||
MINGW* | MSYS* | CYGWIN* | Windows_NT)
|
||||
GUM_OS="Windows"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${raw_arch}" in
|
||||
x86_64 | amd64)
|
||||
GUM_ARCH="x86_64"
|
||||
;;
|
||||
aarch64 | arm64)
|
||||
GUM_ARCH="arm64"
|
||||
;;
|
||||
armv7l | armv7)
|
||||
GUM_ARCH="armv7"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
get_os_aliases() {
|
||||
local os_name="${1}"
|
||||
local os_lower=""
|
||||
|
||||
os_lower="$(printf '%s' "${os_name}" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [ "${os_lower}" = "${os_name}" ]; then
|
||||
printf '%s\n' "${os_name}"
|
||||
return
|
||||
fi
|
||||
|
||||
printf '%s\n%s\n' "${os_name}" "${os_lower}"
|
||||
}
|
||||
|
||||
get_arch_aliases() {
|
||||
case "${1}" in
|
||||
x86_64)
|
||||
printf '%s\n%s\n' "x86_64" "amd64"
|
||||
;;
|
||||
arm64)
|
||||
printf '%s\n%s\n' "arm64" "aarch64"
|
||||
;;
|
||||
armv7)
|
||||
printf '%s\n%s\n' "armv7" "armv7l"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "${1}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_gum_asset_candidates() {
|
||||
local release_version="${1}"
|
||||
local os_alias=""
|
||||
local arch_alias=""
|
||||
local ext=""
|
||||
|
||||
while IFS= read -r os_alias; do
|
||||
while IFS= read -r arch_alias; do
|
||||
for ext in tar.gz zip; do
|
||||
printf 'gum_%s_%s_%s.%s\n' "${release_version}" "${os_alias}" "${arch_alias}" "${ext}"
|
||||
done
|
||||
done < <(get_arch_aliases "${GUM_ARCH}")
|
||||
done < <(get_os_aliases "${GUM_OS}")
|
||||
}
|
||||
|
||||
extract_gum_asset() {
|
||||
local asset_path="${1}"
|
||||
local extract_dir="${2}"
|
||||
|
||||
mkdir -p "${extract_dir}"
|
||||
|
||||
case "${asset_path}" in
|
||||
*.tar.gz)
|
||||
if ! command_exists tar; then
|
||||
echo "tar is required to extract gum tar.gz assets."
|
||||
return 1
|
||||
fi
|
||||
tar -xzf "${asset_path}" -C "${extract_dir}"
|
||||
;;
|
||||
*.zip)
|
||||
if ! command_exists unzip; then
|
||||
echo "unzip is required to extract gum zip assets."
|
||||
return 1
|
||||
fi
|
||||
unzip -q "${asset_path}" -d "${extract_dir}"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_gum_binary() {
|
||||
local search_dir="${1}"
|
||||
local found_path=""
|
||||
|
||||
found_path="$(
|
||||
find "${search_dir}" -type f \( -name "gum" -o -name "gum.exe" \) 2>/dev/null |
|
||||
head -n 1
|
||||
)"
|
||||
|
||||
if [ -n "${found_path}" ]; then
|
||||
printf '%s\n' "${found_path}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
install_gum_from_github_release() {
|
||||
local release_version=""
|
||||
local asset_name=""
|
||||
local downloaded_asset_path=""
|
||||
local download_url=""
|
||||
local tmp_dir=""
|
||||
local extract_dir=""
|
||||
local target_dir=""
|
||||
local gum_binary_path=""
|
||||
local target_binary_name="gum"
|
||||
|
||||
if ! command_exists curl; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! detect_gum_platform; then
|
||||
echo "Unsupported platform for automatic GitHub fallback."
|
||||
return 1
|
||||
fi
|
||||
|
||||
release_version="$(
|
||||
curl -fsSL "https://api.github.com/repos/charmbracelet/gum/releases/latest" |
|
||||
sed -n 's/.*"tag_name":[[:space:]]*"v\([^"]*\)".*/\1/p' |
|
||||
head -n 1
|
||||
)"
|
||||
|
||||
if [ -z "${release_version}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
extract_dir="${tmp_dir}/extract"
|
||||
|
||||
while IFS= read -r asset_name; do
|
||||
download_url="https://github.com/charmbracelet/gum/releases/download/v${release_version}/${asset_name}"
|
||||
if curl -fsSL "${download_url}" -o "${tmp_dir}/${asset_name}"; then
|
||||
downloaded_asset_path="${tmp_dir}/${asset_name}"
|
||||
break
|
||||
fi
|
||||
done < <(get_gum_asset_candidates "${release_version}")
|
||||
|
||||
if [ -z "${downloaded_asset_path}" ]; then
|
||||
rm -rf "${tmp_dir}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! extract_gum_asset "${downloaded_asset_path}" "${extract_dir}"; then
|
||||
rm -rf "${tmp_dir}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
gum_binary_path="$(find_gum_binary "${extract_dir}" || true)"
|
||||
|
||||
if [ -z "${gum_binary_path}" ]; then
|
||||
rm -rf "${tmp_dir}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "${gum_binary_path}" == *.exe ]]; then
|
||||
target_binary_name="gum.exe"
|
||||
fi
|
||||
|
||||
if [ "${GUM_OS}" != "Windows" ] && [ -w "/usr/local/bin" ]; then
|
||||
target_dir="/usr/local/bin"
|
||||
if copy_binary "${gum_binary_path}" "${target_dir}/${target_binary_name}"; then
|
||||
rm -rf "${tmp_dir}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${GUM_OS}" != "Windows" ] && command_exists sudo; then
|
||||
if copy_binary_with_privileges "${gum_binary_path}" "/usr/local/bin/${target_binary_name}"; then
|
||||
rm -rf "${tmp_dir}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
target_dir="${HOME}/.local/bin"
|
||||
mkdir -p "${target_dir}"
|
||||
if copy_binary "${gum_binary_path}" "${target_dir}/${target_binary_name}"; then
|
||||
rm -rf "${tmp_dir}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -rf "${tmp_dir}"
|
||||
return 1
|
||||
}
|
||||
|
||||
install_gum_with_package_manager() {
|
||||
local pm_attempted=0
|
||||
|
||||
if command_exists brew; then
|
||||
pm_attempted=1
|
||||
if brew install gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists apt-get; then
|
||||
pm_attempted=1
|
||||
if run_with_privileges apt-get update && run_with_privileges apt-get install -y gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists dnf; then
|
||||
pm_attempted=1
|
||||
if run_with_privileges dnf install -y gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists pacman; then
|
||||
pm_attempted=1
|
||||
if run_with_privileges pacman -Sy --noconfirm gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists zypper; then
|
||||
pm_attempted=1
|
||||
if run_with_privileges zypper --non-interactive install gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists winget; then
|
||||
pm_attempted=1
|
||||
if winget install --id Charmbracelet.Gum -e --accept-source-agreements --accept-package-agreements; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists choco; then
|
||||
pm_attempted=1
|
||||
if choco install gum -y; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${pm_attempted}" -eq 0 ]; then
|
||||
echo "No supported package manager was found."
|
||||
else
|
||||
echo "Package manager installation did not succeed."
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
should_use_github_fallback() {
|
||||
local answer=""
|
||||
|
||||
if [ ! -t 0 ]; then
|
||||
echo "GitHub fallback prompt requires an interactive terminal."
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf "Use GitHub binary fallback for gum? [y/N]: "
|
||||
read -r answer
|
||||
|
||||
case "${answer}" in
|
||||
y | Y | yes | YES)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_gum() {
|
||||
local disable_github_binary_fallback="${1:-0}"
|
||||
|
||||
if command_exists gum; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "gum is not installed. Trying package manager installation..."
|
||||
|
||||
if install_gum_with_package_manager; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
if command_exists gum; then
|
||||
echo "gum was installed successfully."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "${disable_github_binary_fallback}" = "1" ]; then
|
||||
echo "GitHub binary fallback is disabled."
|
||||
echo "Install gum manually: https://github.com/charmbracelet/gum#installation"
|
||||
echo "If installed into ~/.local/bin, add it to PATH first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if should_use_github_fallback; then
|
||||
echo "Trying GitHub release fallback..."
|
||||
if install_gum_from_github_release; then
|
||||
hash -r
|
||||
fi
|
||||
else
|
||||
echo "GitHub fallback was not selected."
|
||||
echo "Install gum manually: https://github.com/charmbracelet/gum#installation"
|
||||
echo "If installed into ~/.local/bin, add it to PATH first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command_exists gum; then
|
||||
echo "gum was installed successfully."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Automatic installation failed."
|
||||
echo "Install gum manually: https://github.com/charmbracelet/gum#installation"
|
||||
echo "If installed into ~/.local/bin, add it to PATH first."
|
||||
exit 1
|
||||
}
|
||||
82
scripts/easy-docker/lib/install/gum/assets.sh
Executable file
82
scripts/easy-docker/lib/install/gum/assets.sh
Executable file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
get_gum_asset_candidates() {
|
||||
local release_version="${1}"
|
||||
local gum_os="${2}"
|
||||
local gum_arch="${3}"
|
||||
local os_alias=""
|
||||
local arch_alias=""
|
||||
local ext=""
|
||||
|
||||
while IFS= read -r os_alias; do
|
||||
while IFS= read -r arch_alias; do
|
||||
for ext in tar.gz zip; do
|
||||
printf 'gum_%s_%s_%s.%s\n' "${release_version}" "${os_alias}" "${arch_alias}" "${ext}"
|
||||
done
|
||||
done < <(get_arch_aliases "${gum_arch}")
|
||||
done < <(get_os_aliases "${gum_os}")
|
||||
}
|
||||
|
||||
extract_gum_asset() {
|
||||
local asset_path="${1}"
|
||||
local extract_dir="${2}"
|
||||
|
||||
mkdir -p "${extract_dir}"
|
||||
|
||||
case "${asset_path}" in
|
||||
*.tar.gz)
|
||||
if ! command_exists tar; then
|
||||
echo "tar is required to extract gum tar.gz assets."
|
||||
return 1
|
||||
fi
|
||||
tar -xzf "${asset_path}" -C "${extract_dir}"
|
||||
;;
|
||||
*.zip)
|
||||
if ! command_exists unzip; then
|
||||
echo "unzip is required to extract gum zip assets."
|
||||
return 1
|
||||
fi
|
||||
unzip -q "${asset_path}" -d "${extract_dir}"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_gum_binary() {
|
||||
local search_dir="${1}"
|
||||
local found_path=""
|
||||
|
||||
found_path="$(
|
||||
find "${search_dir}" -type f \( -name "gum" -o -name "gum.exe" \) 2>/dev/null |
|
||||
head -n 1
|
||||
)"
|
||||
|
||||
if [ -n "${found_path}" ]; then
|
||||
printf '%s\n' "${found_path}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_latest_gum_release_version() {
|
||||
local api_payload=""
|
||||
local tag_name=""
|
||||
|
||||
api_payload="$(curl -fsSL "https://api.github.com/repos/charmbracelet/gum/releases/latest")" || return 1
|
||||
|
||||
if command_exists jq; then
|
||||
tag_name="$(printf '%s' "${api_payload}" | jq -r '.tag_name // empty')"
|
||||
else
|
||||
tag_name="$(printf '%s' "${api_payload}" | sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)"
|
||||
fi
|
||||
|
||||
tag_name="${tag_name#v}"
|
||||
if [ -z "${tag_name}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "${tag_name}"
|
||||
}
|
||||
67
scripts/easy-docker/lib/install/gum/ensure.sh
Executable file
67
scripts/easy-docker/lib/install/gum/ensure.sh
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
should_use_github_fallback() {
|
||||
local answer=""
|
||||
|
||||
if [ ! -t 0 ]; then
|
||||
echo "GitHub fallback prompt requires an interactive terminal."
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf "Use GitHub binary fallback for gum? [y/N]: "
|
||||
read -r answer
|
||||
|
||||
case "${answer}" in
|
||||
y | Y | yes | YES)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_gum() {
|
||||
local disable_installation_fallback="${1:-0}"
|
||||
|
||||
if command_exists gum; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "gum is not installed. Trying package manager installation..."
|
||||
|
||||
if install_gum_with_package_manager; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
if command_exists gum; then
|
||||
echo "gum was installed successfully."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "${disable_installation_fallback}" = "1" ]; then
|
||||
echo "Installation fallback is disabled."
|
||||
print_manual_gum_install_guidance
|
||||
return 1
|
||||
fi
|
||||
|
||||
if should_use_github_fallback; then
|
||||
echo "Trying GitHub release fallback..."
|
||||
if install_gum_from_github_release; then
|
||||
hash -r
|
||||
fi
|
||||
else
|
||||
echo "GitHub fallback was not selected."
|
||||
print_manual_gum_install_guidance
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command_exists gum; then
|
||||
echo "gum was installed successfully."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Automatic installation failed."
|
||||
print_manual_gum_install_guidance
|
||||
return 1
|
||||
}
|
||||
105
scripts/easy-docker/lib/install/gum/github_release.sh
Executable file
105
scripts/easy-docker/lib/install/gum/github_release.sh
Executable file
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
cleanup_gum_tmp_dir() {
|
||||
local tmp_dir="${1:-}"
|
||||
|
||||
if [ -n "${tmp_dir}" ] && [ -d "${tmp_dir}" ]; then
|
||||
rm -rf "${tmp_dir}"
|
||||
fi
|
||||
}
|
||||
|
||||
install_gum_from_github_release() {
|
||||
local release_version=""
|
||||
local asset_name=""
|
||||
local asset_path=""
|
||||
local download_url=""
|
||||
local tmp_dir=""
|
||||
local extract_dir=""
|
||||
local target_dir=""
|
||||
local gum_binary_path=""
|
||||
local target_binary_name="gum"
|
||||
local gum_os=""
|
||||
local gum_arch=""
|
||||
|
||||
if ! command_exists curl; then
|
||||
echo "curl is required for the GitHub fallback."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! read -r gum_os gum_arch < <(detect_gum_platform); then
|
||||
echo "Unsupported platform for automatic GitHub fallback."
|
||||
return 1
|
||||
fi
|
||||
|
||||
release_version="$(fetch_latest_gum_release_version || true)"
|
||||
if [ -z "${release_version}" ]; then
|
||||
echo "Could not determine latest gum release version."
|
||||
return 1
|
||||
fi
|
||||
|
||||
tmp_dir="$(mktemp -d 2>/dev/null || true)"
|
||||
if [ -z "${tmp_dir}" ] || [ ! -d "${tmp_dir}" ]; then
|
||||
echo "Failed to create temporary directory for gum installation."
|
||||
return 1
|
||||
fi
|
||||
extract_dir="${tmp_dir}/extract"
|
||||
|
||||
while IFS= read -r asset_name; do
|
||||
asset_path="${tmp_dir}/${asset_name}"
|
||||
download_url="https://github.com/charmbracelet/gum/releases/download/v${release_version}/${asset_name}"
|
||||
|
||||
if ! curl -fsSL "${download_url}" -o "${asset_path}"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
rm -rf "${extract_dir}"
|
||||
mkdir -p "${extract_dir}"
|
||||
|
||||
if ! extract_gum_asset "${asset_path}" "${extract_dir}"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
gum_binary_path="$(find_gum_binary "${extract_dir}" || true)"
|
||||
if [ -n "${gum_binary_path}" ]; then
|
||||
break
|
||||
fi
|
||||
done < <(get_gum_asset_candidates "${release_version}" "${gum_os}" "${gum_arch}")
|
||||
|
||||
if [ -z "${gum_binary_path}" ]; then
|
||||
cleanup_gum_tmp_dir "${tmp_dir}"
|
||||
echo "No compatible gum binary was found in GitHub release assets."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "${gum_binary_path}" == *.exe ]]; then
|
||||
target_binary_name="gum.exe"
|
||||
fi
|
||||
|
||||
if [ "${gum_os}" != "Windows" ] && [ -w "/usr/local/bin" ]; then
|
||||
target_dir="/usr/local/bin"
|
||||
if copy_binary "${gum_binary_path}" "${target_dir}/${target_binary_name}"; then
|
||||
cleanup_gum_tmp_dir "${tmp_dir}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${gum_os}" != "Windows" ] && command_exists sudo; then
|
||||
if copy_binary_with_privileges "${gum_binary_path}" "/usr/local/bin/${target_binary_name}"; then
|
||||
cleanup_gum_tmp_dir "${tmp_dir}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "${HOME:-}" ]; then
|
||||
target_dir="${HOME}/.local/bin"
|
||||
mkdir -p "${target_dir}"
|
||||
if copy_binary "${gum_binary_path}" "${target_dir}/${target_binary_name}"; then
|
||||
cleanup_gum_tmp_dir "${tmp_dir}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
cleanup_gum_tmp_dir "${tmp_dir}"
|
||||
echo "Failed to install gum binary from GitHub release."
|
||||
return 1
|
||||
}
|
||||
19
scripts/easy-docker/lib/install/gum/load.sh
Executable file
19
scripts/easy-docker/lib/install/gum/load.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
load_gum_install_modules() {
|
||||
local gum_lib_dir=""
|
||||
gum_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# shellcheck source=scripts/easy-docker/lib/install/gum/platform.sh
|
||||
source "${gum_lib_dir}/platform.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/install/gum/assets.sh
|
||||
source "${gum_lib_dir}/assets.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/install/gum/package_manager.sh
|
||||
source "${gum_lib_dir}/package_manager.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/install/gum/github_release.sh
|
||||
source "${gum_lib_dir}/github_release.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/install/gum/ensure.sh
|
||||
source "${gum_lib_dir}/ensure.sh"
|
||||
}
|
||||
|
||||
load_gum_install_modules
|
||||
62
scripts/easy-docker/lib/install/gum/package_manager.sh
Executable file
62
scripts/easy-docker/lib/install/gum/package_manager.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
install_gum_with_package_manager() {
|
||||
local pm_attempted=0
|
||||
|
||||
if command_exists brew; then
|
||||
pm_attempted=1
|
||||
if brew install gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists apt-get; then
|
||||
pm_attempted=1
|
||||
if run_with_privileges apt-get update && run_with_privileges apt-get install -y gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists dnf; then
|
||||
pm_attempted=1
|
||||
if run_with_privileges dnf install -y gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists pacman; then
|
||||
pm_attempted=1
|
||||
if run_with_privileges pacman -Sy --noconfirm gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists zypper; then
|
||||
pm_attempted=1
|
||||
if run_with_privileges zypper --non-interactive install gum; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists winget; then
|
||||
pm_attempted=1
|
||||
if winget install --id Charmbracelet.Gum -e --accept-source-agreements --accept-package-agreements; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists choco; then
|
||||
pm_attempted=1
|
||||
if choco install gum -y; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${pm_attempted}" -eq 0 ]; then
|
||||
echo "No supported package manager was found."
|
||||
else
|
||||
echo "Package manager installation did not succeed."
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
75
scripts/easy-docker/lib/install/gum/platform.sh
Executable file
75
scripts/easy-docker/lib/install/gum/platform.sh
Executable file
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
detect_gum_platform() {
|
||||
local raw_os=""
|
||||
local raw_arch=""
|
||||
local gum_os=""
|
||||
local gum_arch=""
|
||||
|
||||
raw_os="$(uname -s 2>/dev/null || echo unknown)"
|
||||
raw_arch="$(uname -m 2>/dev/null || echo unknown)"
|
||||
|
||||
case "${raw_os}" in
|
||||
Linux*)
|
||||
gum_os="Linux"
|
||||
;;
|
||||
Darwin*)
|
||||
gum_os="Darwin"
|
||||
;;
|
||||
MINGW* | MSYS* | CYGWIN* | Windows_NT)
|
||||
gum_os="Windows"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${raw_arch}" in
|
||||
x86_64 | amd64)
|
||||
gum_arch="x86_64"
|
||||
;;
|
||||
aarch64 | arm64)
|
||||
gum_arch="arm64"
|
||||
;;
|
||||
armv7l | armv7)
|
||||
gum_arch="armv7"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
printf '%s %s\n' "${gum_os}" "${gum_arch}"
|
||||
return 0
|
||||
}
|
||||
|
||||
get_os_aliases() {
|
||||
local os_name="${1}"
|
||||
local os_lower=""
|
||||
|
||||
os_lower="$(printf '%s' "${os_name}" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [ "${os_lower}" = "${os_name}" ]; then
|
||||
printf '%s\n' "${os_name}"
|
||||
return
|
||||
fi
|
||||
|
||||
printf '%s\n%s\n' "${os_name}" "${os_lower}"
|
||||
}
|
||||
|
||||
get_arch_aliases() {
|
||||
case "${1}" in
|
||||
x86_64)
|
||||
printf '%s\n%s\n' "x86_64" "amd64"
|
||||
;;
|
||||
arm64)
|
||||
printf '%s\n%s\n' "arm64" "aarch64"
|
||||
;;
|
||||
armv7)
|
||||
printf '%s\n%s\n' "armv7" "armv7l"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "${1}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ render_main_screen() {
|
|||
clear
|
||||
fi
|
||||
|
||||
header_text="$(printf "Easy Frappe Docker\nMinimal TUI bootstrap")"
|
||||
header_text="$(printf "Easy Frappe Docker\nManage Docker setups quickly and easily")"
|
||||
|
||||
gum style \
|
||||
--border rounded \
|
||||
|
|
@ -31,16 +31,20 @@ show_main_menu() {
|
|||
|
||||
show_environment_status() {
|
||||
local docker_status="not installed"
|
||||
local gum_status="installed"
|
||||
local docker_daemon_status="not running"
|
||||
local status_text=""
|
||||
|
||||
if command_exists docker; then
|
||||
docker_status="installed"
|
||||
|
||||
if docker_daemon_running; then
|
||||
docker_daemon_status="running"
|
||||
fi
|
||||
fi
|
||||
|
||||
render_main_screen 1 >&2
|
||||
|
||||
status_text="$(printf "Environment status\n\n- gum: %s\n- docker: %s" "${gum_status}" "${docker_status}")"
|
||||
status_text="$(printf "Environment status\n\n- docker: %s\n- docker daemon: %s" "${docker_status}" "${docker_daemon_status}")"
|
||||
|
||||
gum style \
|
||||
--border rounded \
|
||||
|
|
@ -58,3 +62,8 @@ show_environment_status() {
|
|||
"Back to main menu" \
|
||||
"Exit and close easy-docker"
|
||||
}
|
||||
|
||||
show_warning_message() {
|
||||
local message="${1}"
|
||||
gum style --foreground 214 "${message}"
|
||||
}
|
||||
|
|
@ -3,106 +3,43 @@ set -euo pipefail
|
|||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# shellcheck source=scripts/easy-docker/lib/env.sh
|
||||
source "${SCRIPT_DIR}/lib/env.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/ui.sh
|
||||
source "${SCRIPT_DIR}/lib/ui.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/core/commands.sh
|
||||
source "${SCRIPT_DIR}/lib/core/commands.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/core/messages.sh
|
||||
source "${SCRIPT_DIR}/lib/core/messages.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/install/gum/load.sh
|
||||
source "${SCRIPT_DIR}/lib/install/gum/load.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/checks/docker.sh
|
||||
source "${SCRIPT_DIR}/lib/checks/docker.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/ui/screens.sh
|
||||
source "${SCRIPT_DIR}/lib/ui/screens.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/screen.sh
|
||||
source "${SCRIPT_DIR}/lib/app/screen.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/options.sh
|
||||
source "${SCRIPT_DIR}/lib/app/options.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/run.sh
|
||||
source "${SCRIPT_DIR}/lib/app/run.sh"
|
||||
|
||||
print_usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: bash easy-docker.sh [options]
|
||||
|
||||
Options:
|
||||
--no-github-binary-fallback Disable GitHub binary fallback prompt
|
||||
-h, --help Show this help
|
||||
USAGE
|
||||
}
|
||||
|
||||
DISABLE_GITHUB_BINARY_FALLBACK=0
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--no-github-binary-fallback)
|
||||
DISABLE_GITHUB_BINARY_FALLBACK=1
|
||||
;;
|
||||
-h | --help)
|
||||
print_usage
|
||||
disable_installation_fallback=0
|
||||
if parse_cli_options disable_installation_fallback "$@"; then
|
||||
:
|
||||
else
|
||||
parse_status=$?
|
||||
if [ "${parse_status}" -eq 2 ]; then
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
print_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
ensure_gum "${DISABLE_GITHUB_BINARY_FALLBACK}"
|
||||
|
||||
ALT_SCREEN_ACTIVE=0
|
||||
|
||||
enter_alt_screen() {
|
||||
if [ -t 1 ] && command -v tput >/dev/null 2>&1; then
|
||||
tput smcup || true
|
||||
tput civis || true
|
||||
ALT_SCREEN_ACTIVE=1
|
||||
fi
|
||||
}
|
||||
exit "${parse_status}"
|
||||
fi
|
||||
|
||||
leave_alt_screen() {
|
||||
if [ "${ALT_SCREEN_ACTIVE}" = "1" ] && command -v tput >/dev/null 2>&1; then
|
||||
tput cnorm || true
|
||||
tput rmcup || true
|
||||
ALT_SCREEN_ACTIVE=0
|
||||
fi
|
||||
}
|
||||
if ! ensure_gum "${disable_installation_fallback}"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup_screen() {
|
||||
leave_alt_screen
|
||||
}
|
||||
if ! ensure_docker; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup_and_exit() {
|
||||
exit 0
|
||||
}
|
||||
trap 'leave_alt_screen; exit 0' INT TERM
|
||||
trap 'leave_alt_screen' EXIT
|
||||
|
||||
trap cleanup_and_exit INT TERM
|
||||
trap cleanup_screen EXIT
|
||||
|
||||
enter_alt_screen
|
||||
|
||||
render_main_screen 1
|
||||
|
||||
while true; do
|
||||
local_env_action=""
|
||||
action="$(show_main_menu || true)"
|
||||
|
||||
if [ -z "${action}" ]; then
|
||||
cleanup_and_exit
|
||||
fi
|
||||
|
||||
case "${action}" in
|
||||
"Environment check")
|
||||
local_env_action="$(show_environment_status || true)"
|
||||
case "${local_env_action}" in
|
||||
"Back to main menu" | "")
|
||||
render_main_screen 1
|
||||
;;
|
||||
"Exit and close easy-docker")
|
||||
cleanup_and_exit
|
||||
;;
|
||||
*)
|
||||
gum style --foreground 214 "Unknown environment action: ${local_env_action}"
|
||||
sleep 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
"Exit")
|
||||
cleanup_and_exit
|
||||
;;
|
||||
*)
|
||||
gum style --foreground 214 "Unknown action: ${action}"
|
||||
sleep 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
run_easy_docker_app
|
||||
|
|
|
|||
Loading…
Reference in a new issue