#!/usr/bin/env bash
set -euo pipefail

usage() {
    cat <<'EOF'
Usage:
  install-docker.sh [options]

Options:
  --image <image>               Eulr Docker image tag loaded from the archive, default: eulr:latest
  --image-archive-url <url>     gzipped docker-save archive, default: https://eulr.notisee.com/images/eulr-linux-amd64.tar.gz
  --image-sha256-url <url>      sha256 checksum URL, default: <image-archive-url>.sha256
  --container <name>            control-plane container name, default: eulr-paas
  --network <name>              control-plane Docker network, default: eulr-control
  --data-dir <path>             persistent host data dir, default: /data/paas
  --projects-dir <path>         host project workspace mounted into the control plane, default: /srv/eulr/projects
  --api-port <port>             host API/dashboard port, default: 8000
  --api-bind <ip>               host API/dashboard bind address, default: 0.0.0.0
  --http-port <port>            host Caddy HTTP port, default: 80
  --https-port <port>           host Caddy HTTPS port, default: 443
  --caddy-admin-port <port>     host Caddy admin port, default: 2019
  --domain <domain>             dashboard domain stored in the install state
  --email <email>               first admin email for fresh installs; prompted when omitted
  --admin-name <name>           first admin display name, default: Eulr Admin
  --admin-password-file <path>  read the first admin password from the first line of this file
  --generate-admin-auth         generate a CLI/recovery bootstrap token during first install
  --install-docker              explicitly install/start Docker Engine when it is missing
  --no-image-download           use an already-present local image instead of downloading the R2 archive
  --dry-run                     print the planned Docker operations without changing the host
  -h, --help                    show help

Environment:
  EULR_IMAGE                    default --image
  EULR_IMAGE_ARCHIVE_URL        default --image-archive-url
  EULR_IMAGE_SHA256_URL         default --image-sha256-url
  EULR_CONTAINER                default --container
  EULR_NETWORK                  default --network
  EULR_DATA_DIR                 default --data-dir
  EULR_PROJECTS_DIR             default --projects-dir
  EULR_API_PORT                 default --api-port
  EULR_API_BIND                 default --api-bind
  EULR_HTTP_PORT                default --http-port
  EULR_HTTPS_PORT               default --https-port
  EULR_CADDY_ADMIN_PORT         default --caddy-admin-port
  EULR_DASHBOARD_DOMAIN         default --domain
  EULR_ADMIN_EMAIL              default --email
  EULR_ADMIN_NAME               default --admin-name
  EULR_ADMIN_PASSWORD_FILE      default --admin-password-file
EOF
}

error() {
    printf 'error: %s\n' "$*" >&2
    exit 1
}

info() {
    printf '==> %s\n' "$*" >&2
}

require_command() {
    command -v "$1" >/dev/null 2>&1 || error "$1 is required"
}

require_option_value() {
    local option="$1" value="${2:-}"
    [[ -n "$value" ]] || error "$option requires a value"
    printf '%s\n' "$value"
}

print_command() {
    {
        printf '+'
        local arg
        for arg in "$@"; do
            printf ' %q' "$arg"
        done
        printf '\n'
    } >&2
}

prompt_tty_line() {
    local label="$1" value
    [[ -r /dev/tty ]] || error "$label is required but no TTY is available"
    printf '%s: ' "$label" >/dev/tty
    IFS= read -r value </dev/tty || error "interactive input ended before $label was provided"
    [[ -n "$value" ]] || error "$label cannot be empty"
    printf '%s\n' "$value"
}

run() {
    if [[ "$dry_run" = true ]]; then
        print_command "$@"
    else
        "$@"
    fi
}

image_tmp_dir=""
cleanup_image_tmp_dir() {
    if [[ -n "$image_tmp_dir" && -d "$image_tmp_dir" ]]; then
        rm -rf "$image_tmp_dir"
    fi
}
trap cleanup_image_tmp_dir EXIT

is_absolute_path() {
    [[ "$1" = /* ]]
}

ensure_absolute_path() {
    local value="$1" label="$2"
    is_absolute_path "$value" || error "$label must be an absolute path, got $value"
}

ensure_port() {
    local value="$1" label="$2"
    if ! [[ "$value" =~ ^[0-9]+$ ]] || (( value < 1 || value > 65535 )); then
        error "$label must be a TCP port from 1 to 65535, got $value"
    fi
}

ensure_distinct_ports() {
    if [[ "$http_port" = "$https_port" || "$http_port" = "$caddy_admin_port" || "$https_port" = "$caddy_admin_port" ]]; then
        error "Caddy HTTP, HTTPS and Admin host ports must be distinct"
    fi
}

ensure_host_port_available() {
    local port="$1" label="$2"
    if [[ "$dry_run" = true ]]; then
        print_command ss -H -ltn "sport = :$port"
        return
    fi
    if ss -H -ltn "sport = :$port" | awk 'NF { found = 1 } END { exit found ? 0 : 1 }'; then
        {
            printf 'error: %s host port %s is already in use\n' "$label" "$port"
            ss -ltnp "sport = :$port" || true
            printf '\nPass --%s-port with a free port or stop the process that owns this port.\n' "$label"
        } >&2
        exit 1
    fi
}

ensure_supported_image_arch() {
    local machine
    machine=$(uname -m)
    case "$machine" in
    x86_64 | amd64) ;;
    *)
        error "default Eulr Docker image artifact supports linux/amd64 hosts only; build an artifact for $machine and pass --image-archive-url"
        ;;
    esac
}

detect_os_release() {
    local key="$1"
    if [[ -r /etc/os-release ]]; then
        awk -F= -v key="$key" '
            $1 == key {
                gsub(/^"/, "", $2)
                gsub(/"$/, "", $2)
                print $2
                exit
            }
        ' /etc/os-release
    fi
}

docker_repo_family() {
    local os_id="$1"
    case "$os_id" in
    ubuntu | debian) printf '%s\n' "$os_id" ;;
    fedora) printf 'fedora\n' ;;
    centos | rocky | almalinux) printf 'centos\n' ;;
    rhel) printf 'rhel\n' ;;
    *) return 1 ;;
    esac
}

docker_manual_commands() {
    local os_id os_version_codename ubuntu_codename repo_family codename
    os_id=$(detect_os_release ID)
    os_version_codename=$(detect_os_release VERSION_CODENAME)
    ubuntu_codename=$(detect_os_release UBUNTU_CODENAME)
    repo_family=$(docker_repo_family "$os_id" 2>/dev/null || true)

    case "$os_id" in
    ubuntu | debian)
        codename="${ubuntu_codename:-$os_version_codename}"
        if [[ -z "$codename" ]]; then
            cat <<'EOF'
Manual Docker prerequisite:
  Follow https://docs.docker.com/engine/install/ for this host, then run:
  sudo systemctl enable --now docker
EOF
            return
        fi
        cat <<EOF
Manual Docker prerequisite:
  sudo apt-get update
  sudo apt-get install -y ca-certificates curl
  sudo install -m 0755 -d /etc/apt/keyrings
  sudo curl -fsSL https://download.docker.com/linux/$repo_family/gpg -o /etc/apt/keyrings/docker.asc
  sudo chmod a+r /etc/apt/keyrings/docker.asc
  echo "deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$repo_family $codename stable" | sudo tee /etc/apt/sources.list.d/docker.list
  sudo apt-get update
  sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  sudo systemctl enable --now docker
EOF
        ;;
    fedora | centos | rocky | almalinux | rhel)
        cat <<EOF
Manual Docker prerequisite:
  sudo dnf -y install dnf-plugins-core
  sudo dnf config-manager --add-repo https://download.docker.com/linux/$repo_family/docker-ce.repo
  sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  sudo systemctl enable --now docker
EOF
        ;;
    alpine)
        cat <<'EOF'
Manual Docker prerequisite:
  sudo apk add docker docker-cli-compose
  sudo rc-update add docker default
  sudo service docker start
EOF
        ;;
    *)
        cat <<'EOF'
Manual Docker prerequisite:
  This Linux distribution is not supported by the automatic installer yet.
  Install Docker Engine from https://docs.docker.com/engine/install/ and start the daemon.
EOF
        ;;
    esac
}

download_and_load_image_archive() {
    [[ -n "$image_archive_url" ]] || error "--image-archive-url cannot be empty when image download is enabled"
    [[ -n "$image_sha256_url" ]] || error "--image-sha256-url cannot be empty when image download is enabled"

    if [[ "$dry_run" = true ]]; then
        info "Dry run: Eulr image archive would be downloaded and loaded"
        print_command curl -fsSL --retry 3 --retry-delay 2 -o /tmp/eulr-image.tar.gz "$image_archive_url"
        print_command curl -fsSL --retry 3 --retry-delay 2 -o /tmp/eulr-image.tar.gz.sha256 "$image_sha256_url"
        print_command sha256sum /tmp/eulr-image.tar.gz
        printf '+ gzip -dc %q | docker load\n' /tmp/eulr-image.tar.gz >&2
        print_command docker image inspect "$image"
        return
    fi

    ensure_supported_image_arch
    require_command gzip
    require_command mktemp
    require_command sha256sum

    image_tmp_dir=$(mktemp -d)
    local archive_path checksum_path expected actual
    archive_path="$image_tmp_dir/eulr-image.tar.gz"
    checksum_path="$image_tmp_dir/eulr-image.tar.gz.sha256"

    info "Downloading Eulr image archive from $image_archive_url"
    curl -fsSL --retry 3 --retry-delay 2 -o "$archive_path" "$image_archive_url"

    info "Downloading Eulr image checksum from $image_sha256_url"
    curl -fsSL --retry 3 --retry-delay 2 -o "$checksum_path" "$image_sha256_url"
    expected=$(awk '{ print tolower($1); exit }' "$checksum_path")
    [[ "$expected" =~ ^[0-9a-f]{64}$ ]] || error "invalid sha256 checksum from $image_sha256_url"
    actual=$(sha256sum "$archive_path" | awk '{ print tolower($1); exit }')
    [[ "$actual" = "$expected" ]] || error "image archive checksum mismatch: expected $expected, got $actual"

    info "Loading Eulr Docker image as $image"
    gzip -dc "$archive_path" | docker load >/dev/null
    docker image inspect "$image" >/dev/null 2>&1 || error "loaded image archive did not provide Docker image tag $image"
}

ensure_image_available() {
    if [[ "$download_image" = true ]]; then
        download_and_load_image_archive
        return
    fi

    if [[ "$dry_run" = true ]]; then
        info "Dry run: Eulr image download disabled; local image would be required"
        print_command docker image inspect "$image"
        return
    fi

    docker image inspect "$image" >/dev/null 2>&1 || error "Docker image $image is not present; remove --no-image-download or load the image before running the installer"
}

docker_prerequisite_error() {
    local reason="$1"
    {
        printf 'error: %s\n\n' "$reason"
        docker_manual_commands
        printf '\nRerun this installer after Docker is reachable, or pass --install-docker to allow the installer to install Docker on a supported Linux distribution.\n'
    } >&2
    exit 1
}

reject_snap_docker() {
    if command -v snap >/dev/null 2>&1 && snap list docker >/dev/null 2>&1; then
        docker_prerequisite_error "Docker installed through snap is not supported; remove it with: sudo snap remove docker"
    fi
}

start_docker_daemon() {
    if command -v systemctl >/dev/null 2>&1; then
        systemctl enable --now docker
        return
    fi
    if command -v rc-update >/dev/null 2>&1 && command -v service >/dev/null 2>&1; then
        rc-update add docker default
        service docker start
        return
    fi
    if command -v service >/dev/null 2>&1; then
        service docker start
        return
    fi
    docker_prerequisite_error "Docker is installed but no supported service manager was found to start it"
}

install_docker_engine() {
    local os_id repo_family codename arch
    os_id=$(detect_os_release ID)
    repo_family=$(docker_repo_family "$os_id" 2>/dev/null || true)

    info "Installing Docker Engine for $os_id"
    case "$os_id" in
    ubuntu | debian)
        [[ -n "$repo_family" ]] || docker_prerequisite_error "unsupported Linux distribution for automatic Docker install: ${os_id:-unknown}"
        require_command apt-get
        require_command dpkg
        codename=$(detect_os_release UBUNTU_CODENAME)
        codename="${codename:-$(detect_os_release VERSION_CODENAME)}"
        [[ -n "$codename" ]] || docker_prerequisite_error "could not detect apt codename for automatic Docker install"
        apt-get update
        apt-get install -y ca-certificates curl
        install -m 0755 -d /etc/apt/keyrings
        curl -fsSL "https://download.docker.com/linux/$repo_family/gpg" -o /etc/apt/keyrings/docker.asc
        chmod a+r /etc/apt/keyrings/docker.asc
        arch=$(dpkg --print-architecture)
        printf 'deb [arch=%s signed-by=%s] https://download.docker.com/linux/%s %s stable\n' "$arch" "/etc/apt/keyrings/docker.asc" "$repo_family" "$codename" >/etc/apt/sources.list.d/docker.list
        apt-get update
        apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
        ;;
    fedora | centos | rocky | almalinux | rhel)
        [[ -n "$repo_family" ]] || docker_prerequisite_error "unsupported Linux distribution for automatic Docker install: ${os_id:-unknown}"
        require_command dnf
        dnf -y install dnf-plugins-core
        dnf config-manager --add-repo "https://download.docker.com/linux/$repo_family/docker-ce.repo"
        dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
        ;;
    alpine)
        require_command apk
        apk add docker docker-cli-compose
        ;;
    *)
        docker_prerequisite_error "unsupported Linux distribution for automatic Docker install: ${os_id:-unknown}"
        ;;
    esac
    start_docker_daemon
}

ensure_docker_prerequisite() {
    reject_snap_docker

    if command -v docker >/dev/null 2>&1; then
        if docker info >/dev/null 2>&1; then
            info "Docker daemon is reachable"
            return
        fi
        if [[ "$install_docker" = true ]]; then
            info "Docker is installed but not reachable; trying to start it"
            start_docker_daemon
            docker info >/dev/null 2>&1 || docker_prerequisite_error "Docker daemon is still not reachable after start"
            return
        fi
        docker_prerequisite_error "Docker is installed but the daemon is not reachable"
    fi

    if [[ "$install_docker" = true ]]; then
        install_docker_engine
        command -v docker >/dev/null 2>&1 || docker_prerequisite_error "Docker command was not installed"
        docker info >/dev/null 2>&1 || docker_prerequisite_error "Docker daemon is not reachable after installation"
        info "Docker Engine installed and reachable"
        return
    fi

    docker_prerequisite_error "Docker is required for Eulr Docker installs"
}

dir_has_entries() {
    local dir="$1"
    [[ -d "$dir" ]] || return 1
    [[ -n "$(find "$dir" -mindepth 1 -maxdepth 1 -print -quit)" ]]
}

docker_container_label() {
    local object_name="$1" key="$2"
    docker inspect --type container --format "{{index .Config.Labels \"$key\"}}" "$object_name" 2>/dev/null || true
}

docker_network_label() {
    local object_name="$1" key="$2"
    docker inspect --type network --format "{{index .Labels \"$key\"}}" "$object_name" 2>/dev/null || true
}

ensure_owned_container_absent_or_replaceable() {
    if [[ "$dry_run" = true ]]; then
        info "Dry run: existing $container would be replaced only if it has Eulr control-plane ownership labels"
        return
    fi

    if ! docker container inspect "$container" >/dev/null 2>&1; then
        return
    fi

    local managed kind component
    managed=$(docker_container_label "$container" "paas.managed")
    kind=$(docker_container_label "$container" "paas.kind")
    component=$(docker_container_label "$container" "paas.component")
    if [[ "$managed" != "true" || "$kind" != "control-plane" || "$component" != "eulr-server" ]]; then
        error "Docker container $container already exists but is not an Eulr-owned control-plane container"
    fi
    run docker rm --force "$container"
}

ensure_owned_network() {
    if [[ "$dry_run" = true ]]; then
        info "Dry run: $network would be created if it does not already have Eulr control-network ownership labels"
        print_command docker network create \
            --driver bridge \
            --label paas.managed=true \
            --label paas.kind=control-network \
            --label paas.component=eulr-control \
            "$network"
        return
    fi

    if ! docker network inspect "$network" >/dev/null 2>&1; then
        run docker network create \
            --driver bridge \
            --label paas.managed=true \
            --label paas.kind=control-network \
            --label paas.component=eulr-control \
            "$network"
        return
    fi

    local managed kind component driver
    managed=$(docker_network_label "$network" "paas.managed")
    kind=$(docker_network_label "$network" "paas.kind")
    component=$(docker_network_label "$network" "paas.component")
    driver=$(docker network inspect --format '{{.Driver}}' "$network")
    if [[ "$managed" != "true" || "$kind" != "control-network" || "$component" != "eulr-control" ]]; then
        error "Docker network $network already exists but is not an Eulr-owned control network"
    fi
    [[ "$driver" = "bridge" ]] || error "Docker network $network must use the bridge driver, got $driver"
}

read_admin_password() {
    if [[ -n "$admin_password_file" ]]; then
        [[ -f "$admin_password_file" ]] || error "admin password file $admin_password_file does not exist"
        IFS= read -r admin_password <"$admin_password_file" || error "failed to read admin password file $admin_password_file"
        [[ -n "$admin_password" ]] || error "admin password file $admin_password_file is empty"
        printf '%s\n' "$admin_password"
        return
    fi

    [[ -t 0 || -r /dev/tty ]] || error "fresh install requires --admin-password-file when no TTY is available"

    local first second
    printf 'Admin password: ' >/dev/tty
    IFS= read -r -s first </dev/tty || error "interactive input ended before admin password was provided"
    printf '\nConfirm admin password: ' >/dev/tty
    IFS= read -r -s second </dev/tty || error "interactive input ended before admin password confirmation was provided"
    printf '\n' >/dev/tty
    [[ -n "$first" ]] || error "admin password cannot be empty"
    [[ "$first" = "$second" ]] || error "admin password confirmation mismatch"
    printf '%s\n' "$first"
}

write_fresh_install_state() {
    if [[ -z "$admin_email" ]]; then
        admin_email=$(prompt_tty_line "Admin email")
    fi

    local install_args=(
        docker run --rm -i
        --entrypoint /usr/local/bin/eulr
        -v "$data_dir:/host$data_dir"
        "$image"
        server install
        --root /host
        --data-dir "$data_dir"
        --bin-dir "$data_dir/bin"
        --env-file "$data_dir/.paas/docker.env"
        --systemd-unit "$data_dir/.paas/paasd.service"
        --eulr-binary /usr/local/bin/eulr
        --paasd-binary /usr/local/bin/eulr-server
        --dashboard-dist-source /opt/eulr/dashboard/dist
        --email "$admin_email"
        --admin-name "$admin_name"
        --admin-password-stdin
        --api-addr 0.0.0.0:8080
        --caddy-http-port "$http_port"
        --caddy-https-port "$https_port"
        --caddy-admin-port "$caddy_admin_port"
        --no-systemd
    )
    if [[ -n "$dashboard_domain" ]]; then
        install_args+=(--domain "$dashboard_domain")
    fi
    if [[ "$generate_admin_auth" = true ]]; then
        install_args+=(--generate-admin-auth)
    fi

    if [[ "$dry_run" = true ]]; then
        print_command "${install_args[@]}"
        return
    fi

    info "Creating first admin and install state in $data_dir"
    read_admin_password | "${install_args[@]}"
}

ensure_install_state() {
    local users_dir="$data_dir/.paas/auth/users"
    local env_file="$data_dir/.paas/docker.env"
    if dir_has_entries "$users_dir"; then
        [[ -f "$env_file" ]] || error "existing Eulr users were found but $env_file is missing"
        info "Existing Eulr install state found in $data_dir"
        return
    fi
    write_fresh_install_state
}

wait_for_api() {
    local url="http://127.0.0.1:$api_port/api/server/doctor"
    local attempt status
    for attempt in $(seq 1 60); do
        status=$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' "$url" 2>/dev/null || true)
        if [[ "$status" = "200" || "$status" = "401" ]]; then
            info "Eulr API is reachable at http://127.0.0.1:$api_port"
            return
        fi
        sleep 1
    done
    {
        printf '\nEulr container state:\n'
        docker ps -a --filter "name=^/${container}$" --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' || true
        printf '\nEulr container inspect:\n'
        docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}} started={{.State.StartedAt}} finished={{.State.FinishedAt}}' "$container" || true
        printf '\nHost listeners on port %s:\n' "$api_port"
        ss -ltnp "sport = :$api_port" || true
        printf '\nEulr container logs:\n'
    } >&2
    docker logs --tail 120 "$container" >&2 || true
    error "Eulr API did not become reachable at $url"
}

image="${EULR_IMAGE:-eulr:latest}"
image_archive_url="${EULR_IMAGE_ARCHIVE_URL:-https://eulr.notisee.com/images/eulr-linux-amd64.tar.gz}"
image_sha256_url="${EULR_IMAGE_SHA256_URL:-}"
container="${EULR_CONTAINER:-eulr-paas}"
network="${EULR_NETWORK:-eulr-control}"
data_dir="${EULR_DATA_DIR:-/data/paas}"
projects_dir="${EULR_PROJECTS_DIR:-/srv/eulr/projects}"
api_port="${EULR_API_PORT:-8000}"
api_bind="${EULR_API_BIND:-0.0.0.0}"
http_port="${EULR_HTTP_PORT:-80}"
https_port="${EULR_HTTPS_PORT:-443}"
caddy_admin_port="${EULR_CADDY_ADMIN_PORT:-2019}"
dashboard_domain="${EULR_DASHBOARD_DOMAIN:-}"
admin_email="${EULR_ADMIN_EMAIL:-}"
admin_name="${EULR_ADMIN_NAME:-Eulr Admin}"
admin_password_file="${EULR_ADMIN_PASSWORD_FILE:-}"
install_docker=false
download_image=true
dry_run=false
generate_admin_auth=false

while [[ $# -gt 0 ]]; do
    case "$1" in
    --image)
        image=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --image-archive-url)
        image_archive_url=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --image-sha256-url)
        image_sha256_url=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --container)
        container=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --network)
        network=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --data-dir)
        data_dir=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --projects-dir)
        projects_dir=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --api-port)
        api_port=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --api-bind)
        api_bind=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --http-port)
        http_port=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --https-port)
        https_port=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --caddy-admin-port)
        caddy_admin_port=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --domain)
        dashboard_domain=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --email)
        admin_email=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --admin-name)
        admin_name=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --admin-password-file)
        admin_password_file=$(require_option_value "$1" "${2:-}")
        shift 2
        ;;
    --generate-admin-auth)
        generate_admin_auth=true
        shift
        ;;
    --install-docker)
        install_docker=true
        shift
        ;;
    --no-image-download)
        download_image=false
        shift
        ;;
    --dry-run)
        dry_run=true
        shift
        ;;
    -h | --help)
        usage
        exit 0
        ;;
    *)
        error "unknown option: $1"
        ;;
    esac
done

[[ -n "$image" ]] || error "--image cannot be empty"
if [[ "$download_image" = true && -z "$image_sha256_url" ]]; then
    image_sha256_url="$image_archive_url.sha256"
fi
[[ -n "$container" ]] || error "--container cannot be empty"
[[ -n "$network" ]] || error "--network cannot be empty"
[[ -n "$api_bind" ]] || error "--api-bind cannot be empty"
[[ -n "$admin_name" ]] || error "--admin-name cannot be empty"
ensure_absolute_path "$data_dir" "--data-dir"
ensure_absolute_path "$projects_dir" "--projects-dir"
ensure_port "$api_port" "--api-port"
ensure_port "$http_port" "--http-port"
ensure_port "$https_port" "--https-port"
ensure_port "$caddy_admin_port" "--caddy-admin-port"
ensure_distinct_ports

if [[ "$dry_run" = false ]]; then
    [[ "$(uname -s)" = "Linux" ]] || error "install-docker.sh is intended for Linux VPS hosts"
    [[ "${EUID:-$(id -u)}" -eq 0 ]] || error "run as root, for example: curl -fsSL <url> | sudo bash"
    require_command awk
    require_command curl
    require_command find
    require_command seq
    require_command ss
    ensure_docker_prerequisite
fi

if [[ "$dry_run" = true ]]; then
    info "Dry run for Eulr Docker install"
fi

run install -d -m 0750 "$data_dir"
run install -d -m 0750 "$projects_dir"

ensure_image_available

ensure_install_state
ensure_owned_network
ensure_owned_container_absent_or_replaceable
ensure_host_port_available "$api_port" "api"

docker_run_args=(
    docker run -d
    --name "$container"
    --restart unless-stopped
    --label paas.managed=true
    --label paas.kind=control-plane
    --label paas.component=eulr-server
    --network "$network"
    --network-alias "$container"
    --add-host host.docker.internal:host-gateway
    -p "$api_bind:$api_port:8080"
    -v /var/run/docker.sock:/var/run/docker.sock
    -v "$data_dir:$data_dir"
    -v "$projects_dir:$projects_dir"
    --env-file "$data_dir/.paas/docker.env"
    -e "PAAS_DASHBOARD_DIST_DIR=/opt/eulr/dashboard/dist"
    -e "PAAS_CADDY_ADMIN_URL=http://host.docker.internal:$caddy_admin_port"
    -e "PAAS_CADDY_CONTROL_NETWORK=$network"
    -e "PAAS_CADDY_HTTP_HOST=host.docker.internal"
    -e "PAAS_WORKER_UPSTREAM_HOST=$container"
    -e "PAAS_ESBUILD_BIN=/opt/eulr/node_modules/.bin/esbuild"
    -e "PAAS_WORKERD_BIN=/opt/eulr/node_modules/.bin/workerd"
    "$image"
)

run "${docker_run_args[@]}"

if [[ "$dry_run" = false ]]; then
    wait_for_api
    info "Dashboard/API: http://127.0.0.1:$api_port"
    info "HTTP edge: 0.0.0.0:$http_port"
    info "HTTPS edge: 0.0.0.0:$https_port"
    info "Mounted deploy workspace: $projects_dir"
fi
