#!/usr/bin/env bash set -Eeuo pipefail APP_NAME="corp-address-book" CONTAINER_NAME="corporate-address-book" INSTALL_DIR="/opt/corp-address-book" REPO_URL="https://git.h0melab.ru/fabritsky/corp-address-book.git" HOST_PORT="8180" CONTAINER_PORT="3000" IMAGE_NAME="corp-address-book:latest" ENV_CREATED="false" GENERATED_ADMIN_PASSWORD="" log() { printf '\n[%s] %s\n' "$(date +'%H:%M:%S')" "$*" } die() { printf '\nERROR: %s\n' "$*" >&2 exit 1 } need_root_or_sudo() { if [ "${EUID:-$(id -u)}" -ne 0 ] && ! command -v sudo >/dev/null 2>&1; then die "Run as root or install sudo first." fi } as_root() { if [ "${EUID:-$(id -u)}" -eq 0 ]; then "$@" else sudo "$@" fi } install_base_packages() { log "Installing required base packages" as_root apt-get update as_root DEBIAN_FRONTEND=noninteractive apt-get install -y \ ca-certificates \ curl \ git \ gnupg \ openssl } install_docker_if_needed() { if command -v docker >/dev/null 2>&1; then log "Docker is already installed" else log "Installing Docker Engine from the official Docker repository" as_root install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | as_root gpg --dearmor -o /etc/apt/keyrings/docker.gpg as_root chmod a+r /etc/apt/keyrings/docker.gpg . /etc/os-release arch="$(dpkg --print-architecture)" codename="${VERSION_CODENAME:-}" [ -n "$codename" ] || die "Cannot determine Ubuntu codename." printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu %s stable\n' "$arch" "$codename" | as_root tee /etc/apt/sources.list.d/docker.list >/dev/null as_root apt-get update as_root DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io fi if ! docker compose version >/dev/null 2>&1; then log "Installing Docker Compose plugin" as_root apt-get update as_root DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin else log "Docker Compose plugin is already installed" fi as_root systemctl enable --now docker } prepare_repo() { log "Preparing repository in ${INSTALL_DIR}" as_root mkdir -p "$INSTALL_DIR" if [ -d "${INSTALL_DIR}/.git" ]; then as_root git -C "$INSTALL_DIR" remote set-url origin "$REPO_URL" as_root git -C "$INSTALL_DIR" fetch --prune origin as_root git -C "$INSTALL_DIR" pull --ff-only origin main elif [ -z "$(as_root find "$INSTALL_DIR" -mindepth 1 -maxdepth 1 -print -quit)" ]; then as_root git clone "$REPO_URL" "$INSTALL_DIR" else die "${INSTALL_DIR} exists and is not a git repository. Move it aside or initialize it manually." fi } prepare_env_and_data() { log "Preparing persistent data and environment" as_root mkdir -p "${INSTALL_DIR}/data" if [ ! -f "${INSTALL_DIR}/.env" ]; then jwt_secret="$(openssl rand -hex 32 2>/dev/null || date +%s%N)" admin_password="$(openssl rand -base64 24 2>/dev/null | tr -d '\n' || date +%s%N)" GENERATED_ADMIN_PASSWORD="$admin_password" ENV_CREATED="true" as_root tee "${INSTALL_DIR}/.env" >/dev/null </dev/null </dev/null 2>&1; then old_name="${CONTAINER_NAME}-old-$(date +%Y%m%d-%H%M%S)" as_root docker stop "$CONTAINER_NAME" as_root docker rename "$CONTAINER_NAME" "$old_name" log "Previous container renamed to ${old_name}" fi if ! as_root docker run -d \ --name "$CONTAINER_NAME" \ --restart unless-stopped \ --env-file "${INSTALL_DIR}/.env" \ -p "${HOST_PORT}:${CONTAINER_PORT}" \ -v "${INSTALL_DIR}/data:/app/data" \ "$IMAGE_NAME"; then if [ -n "$old_name" ]; then log "New container failed to start; attempting rollback to ${old_name}" as_root docker start "$old_name" || true fi die "Failed to start ${CONTAINER_NAME}." fi sleep 5 if ! curl -fsSI "http://127.0.0.1:${HOST_PORT}/" >/dev/null; then as_root docker logs --tail=80 "$CONTAINER_NAME" || true if [ -n "$old_name" ]; then log "Health check failed; attempting rollback to ${old_name}" as_root docker stop "$CONTAINER_NAME" || true as_root docker rename "$CONTAINER_NAME" "${CONTAINER_NAME}-failed-$(date +%Y%m%d-%H%M%S)" || true as_root docker start "$old_name" || true fi die "Health check failed on http://127.0.0.1:${HOST_PORT}/." fi } print_result() { server_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" if [ -z "${server_ip:-}" ]; then server_ip="SERVER_IP" fi log "Installation completed" printf 'URL: http://%s:%s/\n' "$server_ip" "$HOST_PORT" printf 'Local check: http://127.0.0.1:%s/\n' "$HOST_PORT" printf 'Admin login: admin\n' if [ "$ENV_CREATED" = "true" ]; then printf 'Admin password: %s\n' "$GENERATED_ADMIN_PASSWORD" printf 'The password was saved in %s/.env. JWT_SECRET was not printed.\n' "$INSTALL_DIR" else printf 'Admin password: unchanged; read it from %s/.env on the server.\n' "$INSTALL_DIR" fi } main() { need_root_or_sudo install_base_packages install_docker_if_needed prepare_repo prepare_env_and_data build_image replace_container print_result } main "$@"