#!/usr/bin/env bash # MiroTalk Call-Me installer for Ubuntu 22.04 and 24.04. # Run from the repository root with: sudo ./install.sh set -Eeuo pipefail readonly NODE_MAJOR=24 readonly DOCKER_IMAGE='mirotalk/cme:latest' readonly CONFIG_FILE='public/config.js' readonly CONFIG_TEMPLATE='public/config.template.js' readonly ENV_FILE='.env' readonly ENV_TEMPLATE='.env.template' readonly COMPOSE_FILE='docker-compose.yml' readonly COMPOSE_TEMPLATE='docker-compose.template.yml' if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ -n "${TERM:-}" ]]; then readonly RED="$(tput setaf 1)" readonly YELLOW="$(tput setaf 3)" readonly MAGENTA="$(tput setaf 5)" readonly RESET="$(tput sgr0)" else readonly RED='' readonly YELLOW='' readonly MAGENTA='' readonly RESET='' fi log() { local level="$1" shift local color="$MAGENTA" case "$level" in warning) color="$YELLOW" ;; error) color="$RED" ;; esac printf '%s :: %b%s%b\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$color" "$*" "$RESET" } die() { log error "$*" >&2 exit 1 } on_error() { local exit_code=$? log error "Installation failed near line ${BASH_LINENO[0]} (exit ${exit_code})." >&2 exit "$exit_code" } trap on_error ERR confirm() { local prompt="$1" local default_answer="${2:-y}" local answer local hint='[y/N]' [[ "$default_answer" == 'y' ]] && hint='[Y/n]' while true; do read -r -p "$prompt $hint " answer answer="${answer:-$default_answer}" case "$answer" in [Yy]|[Yy][Ee][Ss]) return 0 ;; [Nn]|[Nn][Oo]) return 1 ;; *) printf 'Please answer yes or no.\n' ;; esac done } require_command() { command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1" } copy_if_missing() { local source_file="$1" local destination_file="$2" if [[ -e "$destination_file" ]]; then log warning "Keeping existing ${destination_file}" return fi cp "$source_file" "$destination_file" if [[ -n "${SUDO_UID:-}" && -n "${SUDO_GID:-}" ]]; then chown "$SUDO_UID:$SUDO_GID" "$destination_file" fi log info "Created ${destination_file} from ${source_file}" } run_as_project_user() { if [[ -n "${SUDO_USER:-}" && "$SUDO_USER" != 'root' ]]; then sudo -u "$SUDO_USER" -- "$@" else "$@" fi } install_nodejs() { local installed_major=0 if command -v node >/dev/null 2>&1; then installed_major="$(node --version | sed -E 's/^v([0-9]+).*/\1/')" fi if (( installed_major >= NODE_MAJOR )); then log info "Node.js $(node --version) already satisfies the requirement" return fi log info "Installing Node.js ${NODE_MAJOR}.x" apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl gnupg install -m 0755 -d /etc/apt/keyrings curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg printf 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_%s.x nodistro main\n' "$NODE_MAJOR" \ > /etc/apt/sources.list.d/nodesource.list apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs } install_docker() { if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then log info 'Docker Engine and Compose v2 are already installed' return fi log info 'Installing Docker Engine and Compose v2' apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl gnupg install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu %s stable\n' \ "$(dpkg --print-architecture)" "$VERSION_CODENAME" > /etc/apt/sources.list.d/docker.list apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y \ containerd.io docker-buildx-plugin docker-ce docker-ce-cli docker-compose-plugin systemctl enable --now docker } [[ "$(uname -s)" == 'Linux' ]] || die 'This installer supports Linux only.' [[ "$EUID" -eq 0 ]] || die 'Run this installer as root: sudo ./install.sh' [[ -r /etc/os-release ]] || die 'Unable to identify this Linux distribution.' . /etc/os-release [[ "${ID:-}" == 'ubuntu' ]] || die 'This installer currently supports Ubuntu only.' [[ "${VERSION_ID:-}" == '22.04' || "${VERSION_ID:-}" == '24.04' ]] || \ log warning "Ubuntu ${VERSION_ID:-unknown} has not been tested; continuing anyway." cd "$(dirname "${BASH_SOURCE[0]}")" [[ -f package.json && -f package-lock.json && -f "$CONFIG_TEMPLATE" && -f "$ENV_TEMPLATE" ]] || \ die 'Run this script from a complete MiroTalk Call-Me checkout.' log info "MiroTalk Call-Me installer on Ubuntu ${VERSION_ID:-unknown}" if confirm 'Use Docker?' y; then if confirm 'Install or update Docker dependencies?' y; then install_docker fi require_command docker docker compose version >/dev/null 2>&1 || \ die "Docker Compose v2 is required (the command is 'docker compose')." copy_if_missing "$ENV_TEMPLATE" "$ENV_FILE" copy_if_missing "$COMPOSE_TEMPLATE" "$COMPOSE_FILE" if confirm 'Use the official Docker image?' y; then log info "Pulling ${DOCKER_IMAGE}" docker pull "$DOCKER_IMAGE" else log info "Building ${DOCKER_IMAGE} from this checkout" docker build --tag "$DOCKER_IMAGE" . fi log info 'Starting MiroTalk Call-Me in the background' docker compose up -d docker compose ps else if confirm 'Install or update Node.js?' y; then install_nodejs fi require_command node require_command npm node_major="$(node --version | sed -E 's/^v([0-9]+).*/\1/')" (( node_major >= NODE_MAJOR )) || \ die "Node.js ${NODE_MAJOR} or newer is required; found $(node --version)." copy_if_missing "$CONFIG_TEMPLATE" "$CONFIG_FILE" copy_if_missing "$ENV_TEMPLATE" "$ENV_FILE" log info 'Installing npm dependencies from the lockfile' run_as_project_user npm ci log info 'Starting MiroTalk Call-Me (press Ctrl+C to stop)' run_as_project_user npm start fi