#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=wp13-common.sh source "${SCRIPT_DIR}/wp13-common.sh" usage() { cat <<'EOF' Usage: install_offline.sh [options] Verify, load, and start a DataOps offline Compose bundle. Options: --bundle-root PATH Extracted bundle root (default: parent of operations/). --env-file PATH Optional environment file. --environment-profile PATH Validated JSON profile; selects matching .env and project. --project-name NAME Compose project (default: dataops-test). --timeout SECONDS Health timeout (default: 600). --help Show this help. EOF } BUNDLE_ROOT="$(wp13_default_root)" ENV_FILE="" ENVIRONMENT_PROFILE="" PROJECT_NAME="" TIMEOUT_SECONDS=600 while [[ $# -gt 0 ]]; do case "$1" in --bundle-root) BUNDLE_ROOT="$2"; shift 2 ;; --env-file) ENV_FILE="$2"; shift 2 ;; --environment-profile) ENVIRONMENT_PROFILE="$2"; shift 2 ;; --project-name) PROJECT_NAME="$2"; shift 2 ;; --timeout) TIMEOUT_SECONDS="$2"; shift 2 ;; --help|-h) usage; exit 0 ;; *) wp13_die "unknown argument: $1" ;; esac done BUNDLE_ROOT="$(cd "${BUNDLE_ROOT}" && pwd)" COMPOSE_FILE="${BUNDLE_ROOT}/deploy/docker/docker-compose.yml" if [[ -n "${ENVIRONMENT_PROFILE}" ]]; then if [[ "${ENVIRONMENT_PROFILE}" != /* ]]; then ENVIRONMENT_PROFILE="${BUNDLE_ROOT}/${ENVIRONMENT_PROFILE}" fi [[ -r "${ENVIRONMENT_PROFILE}" ]] \ || wp13_die "environment profile is not readable: ${ENVIRONMENT_PROFILE}" wp13_require_command python3 PROFILE_PROJECT="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["project_name"])' "${ENVIRONMENT_PROFILE}")" PROFILE_ENV_FILE="${ENVIRONMENT_PROFILE%.json}.env" if [[ -n "${PROJECT_NAME}" && "${PROJECT_NAME}" != "${PROFILE_PROJECT}" ]]; then wp13_die "project name does not match environment profile: ${PROJECT_NAME} != ${PROFILE_PROJECT}" fi PROJECT_NAME="${PROFILE_PROJECT}" ENV_FILE="${PROFILE_ENV_FILE}" fi PROJECT_NAME="${PROJECT_NAME:-dataops-test}" wp13_verify_checksums "${BUNDLE_ROOT}" wp13_require_command docker if [[ -f "${BUNDLE_ROOT}/images/dataops-images.tar" ]]; then wp13_log "loading offline images" docker load -i "${BUNDLE_ROOT}/images/dataops-images.tar" >/dev/null else wp13_log "image archive not present; using preloaded local images" fi preflight_args=( --bundle-root "${BUNDLE_ROOT}" --compose-file "${COMPOSE_FILE}" --project-name "${PROJECT_NAME}" ) [[ -z "${ENV_FILE}" ]] || preflight_args+=(--env-file "${ENV_FILE}") [[ -z "${ENVIRONMENT_PROFILE}" ]] || preflight_args+=(--environment-profile "${ENVIRONMENT_PROFILE}") bash "${SCRIPT_DIR}/preflight.sh" "${preflight_args[@]}" wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \ up -d --no-build wp13_wait_for_project "${PROJECT_NAME}" "${TIMEOUT_SECONDS}" current="$( wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \ exec -T backend alembic -c alembic.ini current \ | awk 'NF {print $1; exit}' )" head="$( wp13_compose "${COMPOSE_FILE}" "${PROJECT_NAME}" "${ENV_FILE}" \ exec -T backend alembic -c alembic.ini heads \ | awk 'NF {print $1; exit}' )" [[ -n "${current}" && "${current}" == "${head}" ]] \ || wp13_die "post-install migration mismatch: current=${current:-unknown}, head=${head:-unknown}" expected_head="$(wp13_expected_migration_head "${BUNDLE_ROOT}")" [[ -n "${expected_head}" ]] \ || wp13_die "release manifest migration head is missing" [[ "${head}" == "${expected_head}" ]] \ || wp13_die "installed image migration head does not match release manifest: image=${head}, release=${expected_head}" cp "${BUNDLE_ROOT}/release-manifest.txt" \ "${BUNDLE_ROOT}/installed-${PROJECT_NAME}.manifest" cp "${BUNDLE_ROOT}/release-manifest.json" \ "${BUNDLE_ROOT}/installed-${PROJECT_NAME}.manifest.json" wp13_log "offline install completed at migration ${current}"