#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" DEPLOY_SOURCE_DIR="${REPO_ROOT}/deploy/single-node" require_env() { local key="$1" local value="${!key-}" if [[ -z "${value}" ]]; then echo "Missing required environment variable: ${key}" >&2 exit 1 fi } require_env DEPLOY_HOST require_env DEPLOY_USER require_env DEPLOY_DIR require_env SINGLE_NODE_VPS_SSH_PRIVATE_KEY require_env GHCR_USERNAME require_env GHCR_PASSWORD require_env FRONTEND_IMAGE require_env PRIMARY_DOMAIN require_env SECONDARY_DOMAIN GHCR_REGISTRY="${GHCR_REGISTRY:-ghcr.io}" WORK_DIR="$(mktemp -d)" trap 'rm -rf "${WORK_DIR}"' EXIT RUNTIME_ENV_FILE="${WORK_DIR}/.env.runtime" RELEASE_ARCHIVE="${WORK_DIR}/console-svc-plus-release.tgz" REMOTE_ARCHIVE="/tmp/console-svc-plus-release-${GITHUB_SHA:-manual}.tgz" SSH_KEY_FILE="${WORK_DIR}/deploy.key" KNOWN_HOSTS_FILE="${WORK_DIR}/known_hosts" bash "${SCRIPT_DIR}/render-frontend-runtime-env.sh" "${RUNTIME_ENV_FILE}" cp "${DEPLOY_SOURCE_DIR}/docker-compose.yml" "${WORK_DIR}/docker-compose.yml" cp "${DEPLOY_SOURCE_DIR}/Caddyfile" "${WORK_DIR}/Caddyfile" tar -C "${WORK_DIR}" -czf "${RELEASE_ARCHIVE}" \ docker-compose.yml \ Caddyfile \ .env.runtime printf '%s\n' "${SINGLE_NODE_VPS_SSH_PRIVATE_KEY}" > "${SSH_KEY_FILE}" chmod 600 "${SSH_KEY_FILE}" ssh-keyscan -H "${DEPLOY_HOST}" > "${KNOWN_HOSTS_FILE}" SSH_BASE=( ssh -i "${SSH_KEY_FILE}" -o StrictHostKeyChecking=yes -o UserKnownHostsFile="${KNOWN_HOSTS_FILE}" "${DEPLOY_USER}@${DEPLOY_HOST}" ) SCP_BASE=( scp -i "${SSH_KEY_FILE}" -o StrictHostKeyChecking=yes -o UserKnownHostsFile="${KNOWN_HOSTS_FILE}" ) printf '%s' "${GHCR_PASSWORD}" | "${SSH_BASE[@]}" \ "GHCR_REGISTRY='${GHCR_REGISTRY}' GHCR_USERNAME='${GHCR_USERNAME}' bash -s" <<'EOF' set -euo pipefail require_sudo_prefix() { if [[ "${EUID}" -eq 0 ]]; then return 0 fi if command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then printf 'sudo -n' return 0 fi echo "Remote host requires root or passwordless sudo to install or manage Docker." >&2 exit 1 } ensure_docker() { if command -v docker >/dev/null 2>&1; then return 0 fi local sudo_prefix sudo_prefix="$(require_sudo_prefix)" if command -v apt-get >/dev/null 2>&1; then ${sudo_prefix} apt-get update if ! DEBIAN_FRONTEND=noninteractive ${sudo_prefix} apt-get install -y docker.io docker-compose-plugin; then DEBIAN_FRONTEND=noninteractive ${sudo_prefix} apt-get install -y docker.io docker-compose-v2 fi if command -v systemctl >/dev/null 2>&1; then ${sudo_prefix} systemctl enable --now docker else ${sudo_prefix} service docker start fi return 0 fi echo "Docker is not installed and this script only knows how to install it on apt-based hosts." >&2 exit 1 } docker_runner() { if docker info >/dev/null 2>&1; then return 0 fi local sudo_prefix sudo_prefix="$(require_sudo_prefix)" if ${sudo_prefix} docker info >/dev/null 2>&1; then printf '%s' "${sudo_prefix}" return 0 fi echo "Docker is installed but not accessible for the deploy user." >&2 exit 1 } ensure_docker docker_prefix="$(docker_runner)" printf '%s' "${GHCR_PASSWORD}" | ${docker_prefix:+${docker_prefix} }docker login "${GHCR_REGISTRY}" -u "${GHCR_USERNAME}" --password-stdin EOF "${SCP_BASE[@]}" "${RELEASE_ARCHIVE}" "${DEPLOY_USER}@${DEPLOY_HOST}:${REMOTE_ARCHIVE}" "${SSH_BASE[@]}" \ "DEPLOY_DIR='${DEPLOY_DIR}' REMOTE_ARCHIVE='${REMOTE_ARCHIVE}' PROJECT_NAME='console-svc-plus' bash -s" <<'EOF' set -euo pipefail require_sudo_prefix() { if [[ "${EUID}" -eq 0 ]]; then return 0 fi if command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then printf 'sudo -n' return 0 fi echo "Remote host requires root or passwordless sudo to install or manage Docker." >&2 exit 1 } ensure_docker() { if command -v docker >/dev/null 2>&1; then return 0 fi local sudo_prefix sudo_prefix="$(require_sudo_prefix)" if command -v apt-get >/dev/null 2>&1; then ${sudo_prefix} apt-get update if ! DEBIAN_FRONTEND=noninteractive ${sudo_prefix} apt-get install -y docker.io docker-compose-plugin; then DEBIAN_FRONTEND=noninteractive ${sudo_prefix} apt-get install -y docker.io docker-compose-v2 fi if command -v systemctl >/dev/null 2>&1; then ${sudo_prefix} systemctl enable --now docker else ${sudo_prefix} service docker start fi return 0 fi echo "Docker is not installed and this script only knows how to install it on apt-based hosts." >&2 exit 1 } docker_runner() { if docker info >/dev/null 2>&1; then return 0 fi local sudo_prefix sudo_prefix="$(require_sudo_prefix)" if ${sudo_prefix} docker info >/dev/null 2>&1; then printf '%s' "${sudo_prefix}" return 0 fi echo "Docker is installed but not accessible for the deploy user." >&2 exit 1 } tmp_dir="$(mktemp -d)" trap 'rm -rf "${tmp_dir}" "${REMOTE_ARCHIVE}"' EXIT ensure_docker docker_prefix="$(docker_runner)" mkdir -p "${DEPLOY_DIR}" tar -xzf "${REMOTE_ARCHIVE}" -C "${tmp_dir}" install -m 0644 "${tmp_dir}/docker-compose.yml" "${DEPLOY_DIR}/docker-compose.yml" install -m 0644 "${tmp_dir}/Caddyfile" "${DEPLOY_DIR}/Caddyfile" install -m 0600 "${tmp_dir}/.env.runtime" "${DEPLOY_DIR}/.env.runtime" cd "${DEPLOY_DIR}" ${docker_prefix:+${docker_prefix} }docker compose --project-name "${PROJECT_NAME}" --env-file .env.runtime pull dashboard caddy ${docker_prefix:+${docker_prefix} }docker compose --project-name "${PROJECT_NAME}" --env-file .env.runtime run --rm frontend-assets ${docker_prefix:+${docker_prefix} }docker compose --project-name "${PROJECT_NAME}" --env-file .env.runtime up -d --remove-orphans dashboard caddy ${docker_prefix:+${docker_prefix} }docker compose --project-name "${PROJECT_NAME}" --env-file .env.runtime ps EOF