diff --git a/.env.example b/.env.example index bc4d465f..67eec4ce 100644 --- a/.env.example +++ b/.env.example @@ -22,6 +22,7 @@ DB_PORT=5432 POSTGRES_USER='user' POSTGRES_PASSWORD='password' POSTGRES_DATABASE='socialpredict_db' +POSTGRES_VOLUME='pgdata' FRONTEND_CONTAINER_NAME=socialpredict-frontend-container FRONTEND_IMAGE_NAME=ghcr.io/openpredictionmarkets/socialpredict-frontend @@ -37,6 +38,5 @@ NGINX_PORT=80 DOMAIN='domain.com' DOMAIN_URL='domain.com' API_URL='domain.com' -EMAIL='john@doe.com' TRAEFIK_CONTAINER_NAME=socialpredict-traefik-container diff --git a/.gitignore b/.gitignore index 51a8f0f3..ced8ab43 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ tmp/ # Apple Silicon Docker Compose Override docker-compose.override.yml + +.vscode \ No newline at end of file diff --git a/SocialPredict b/SocialPredict index d4d7463b..6886f6c0 100755 --- a/SocialPredict +++ b/SocialPredict @@ -1,8 +1,25 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e # Stop script on error -set -a # Automatically export all variables +set -euo pipefail +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} # Determine script's directory SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -15,144 +32,100 @@ fi # --- Main SocialPredict Functionality --- -# Ensure .env file exists before resolving path -if [ ! -f "$SCRIPT_DIR/.env" ]; then - echo ".env file not found." - - if [ -f "$SCRIPT_DIR/scripts/dev/env_writer_dev.sh" ]; then - echo "Initializing .env file using env_writer_dev.sh (first-run only)..." - export CALLED_FROM_SOCIALPREDICT=yes - SCRIPT_INTERACTIVE=true source "$SCRIPT_DIR/scripts/dev/env_writer_dev.sh" - unset CALLED_FROM_SOCIALPREDICT - else - echo "Cannot continue. .env file is missing and env_writer_dev.sh was not found." - exit 1 - fi -fi - -# Calculate the absolute path to the .env file -ENV_PATH="$( readlink -f "$SCRIPT_DIR/.env" )" -ENV_FILE="--env-file $ENV_PATH" - # Function to check if a command exists command_exists() { - command -v "$1" &> /dev/null 2>&1 + command -v "$1" &> /dev/null 2>&1 } -# Function to check if Docker && Docker Compose are installed -docker_check() { - if command_exists docker && docker compose version &> /dev/null; then - echo "Found docker compose." - COMPOSE='docker compose' - elif command_exists docker-compose; then - echo "Error: Found docker-compose V1. Please update to V2." - echo "https://docs.docker.com/compose/migrate/" - exit 1 - - else - echo "Error: Docker Compose is not installed." - exit 1 - fi +# Initial Checks +# Check if docker compose is installed. Fails if not found. +# Check if .env file exists. Fails if not found. +check_docker() { + print_status "Checking that docker compose is installed..." + if command_exists docker && docker compose version &> /dev/null; then + print_status "Found docker compose." + elif command_exists docker-compose; then + print_warning "Found docker-compose V1. Please update to V2." + print_warning "https://docs.docker.com/compose/migrate/" + exit 1 + else + print_error "Docker Compose is not installed." + exit 1 + fi } -# Function to Source the .env file -source_env() { - if [ -f "${SCRIPT_DIR}/.env" ]; then - source "${SCRIPT_DIR}/.env" - else - echo "Error: .env file not found." +init() { + check_docker + if [ ! -f "$SCRIPT_DIR/.env" ]; then + print_status "Looks like this is the first time running SocialPredict." + print_warning "Please run './SocialPredict install' to initialize the application." exit 1 + else + print_status "Loading configuration from .env file" + source "$SCRIPT_DIR/.env" fi } -if [ "$1" = "install" ]; then - # Echo initial message - echo "### Building and Deploying SocialPredict ..." - echo - sleep 1; - - # Check that docker is installed - echo "### Checking that docker compose is installed ..." - docker_check - echo - sleep 1; - - # Ask user input for Application Environment - echo "### Select Application Environment: " - PS3='Please enter your choice: ' - options=("Development" "Localhost" "Production" "Quit") - select opt in "${options[@]}" - do - case $opt in - "Development") - echo - echo "Building for Development" - echo - export CALLED_FROM_SOCIALPREDICT=yes - source "$SCRIPT_DIR/scripts/dev.sh" - unset CALLED_FROM_SOCIALPREDICT - break - ;; - "Localhost") - echo - echo "Building for Localhost" - echo - export CALLED_FROM_SOCIALPREDICT=yes - source "$SCRIPT_DIR/scripts/localhost.sh" - unset CALLED_FROM_SOCIALPREDICT - break - ;; - "Production") - echo - echo "Building for Production" - echo - export CALLED_FROM_SOCIALPREDICT=yes - source "$SCRIPT_DIR/scripts/prod.sh" - unset CALLED_FROM_SOCIALPREDICT - break - ;; - "Quit") - break - ;; - *) echo "Invalid option $REPLY";; - esac - done -fi +print_help() { + cat < / --restore-latest - source "$SCRIPT_DIR/scripts/backup/db_backup.sh" "$@" - unset CALLED_FROM_SOCIALPREDICT -fi +COMMAND="${1:-"--help"}" +case "$COMMAND" in + install) + check_docker + shift + export CALLED_FROM_SOCIALPREDICT=yes + source "${SCRIPT_DIR}/scripts/install.sh" + unset CALLED_FROM_SOCIALPREDICT + ;; + up) + init + export CALLED_FROM_SOCIALPREDICT=yes + source "${SCRIPT_DIR}/scripts/docker-commands.sh" up + unset CALLED_FROM_SOCIALPREDICT + ;; + down) + init + export CALLED_FROM_SOCIALPREDICT=yes + source "${SCRIPT_DIR}/scripts/docker-commands.sh" down + unset CALLED_FROM_SOCIALPREDICT + ;; + exec) + init + export CALLED_FROM_SOCIALPREDICT=yes + shift + source "${SCRIPT_DIR}/scripts/docker-commands.sh" exec "$@" + unset CALLED_FROM_SOCIALPREDICT + ;; + backup) + init + export CALLED_FROM_SOCIALPREDICT=yes + shift + # Pass remaining args to the backup script, e.g. --save / --list / --restore + source "${SCRIPT_DIR}/scripts/backup/db_backup.sh" "$@" + unset CALLED_FROM_SOCIALPREDICT + ;; + --help|-h|help) + print_help + ;; + *) + echo "Unknown command: $COMMAND" + echo + print_help + exit 1 + ;; + esac diff --git a/scripts/dev.sh b/scripts/dev.sh deleted file mode 100755 index 0e061cf5..00000000 --- a/scripts/dev.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/bash - -# --- Platform Compatibility, Linux vs. Apple Silicon --- -__SP_DEV_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -__SP_ROOT_DIR="${SOCIALPREDICT_ROOT:-$(cd "${__SP_DEV_DIR}/.." && pwd)}" - -source "${__SP_ROOT_DIR}/scripts/lib/arch.sh" -echo "== dev build platform: ${FORCE_PLATFORM:-default} ==" - -# Cross-platform "sed -i" (GNU vs BSD/macOS) -if sed --version >/dev/null 2>&1; then - # GNU sed - SED_INPLACE=(sed -i -e) -else - # BSD sed (macOS) requires an empty backup extension: -i '' - SED_INPLACE=(sed -i '' -e) -fi - -# (existing) set local image names for dev -export BACKEND_IMAGE_NAME=${BACKEND_IMAGE_NAME:-socialpredict-dev-backend} -export FRONTEND_IMAGE_NAME=${FRONTEND_IMAGE_NAME:-socialpredict-dev-frontend} - -# --- Main SocialPredict Functionality --- - -# Make sure the script can only be run via SocialPredict Script -[ -z "$CALLED_FROM_SOCIALPREDICT" ] && { echo "Not called from SocialPredict"; exit 42; } - -# Make sure the script can only be run via SocialPredict Script -[ -z "$CALLED_FROM_SOCIALPREDICT" ] && { echo "Not called from SocialPredict"; exit 42; } - -init_env() { - # Create .env file - cp "${__SP_ROOT_DIR}/.env.example" "${__SP_ROOT_DIR}/.env" - - # Update APP_ENV - "${SED_INPLACE[@]}" "s|^APP_ENV=.*|APP_ENV='development'|" "${__SP_ROOT_DIR}/.env" - - # Add OS-specific POSTGRES_VOLUME - OS=$(uname -s) - if [[ "$OS" == "Darwin" ]]; then - echo "POSTGRES_VOLUME=pgdata" >> "${__SP_ROOT_DIR}/.env" - else - echo "POSTGRES_VOLUME=../data/postgres" >> "${__SP_ROOT_DIR}/.env" - fi - - # Change the Domain setting: - "${SED_INPLACE[@]}" "s|^DOMAIN=.*|DOMAIN='localhost'|" "${__SP_ROOT_DIR}/.env" - "${SED_INPLACE[@]}" "s|^DOMAIN_URL=.*|DOMAIN_URL='http://localhost'|" "${__SP_ROOT_DIR}/.env" - - # Remove unnecessary lines from .env - "${SED_INPLACE[@]}" "/^TRAEFIK_CONTAINER_NAME=.*/d" "${__SP_ROOT_DIR}/.env" - "${SED_INPLACE[@]}" "/^EMAIL=.*/d" "${__SP_ROOT_DIR}/.env" - - # Update Image Names - "${SED_INPLACE[@]}" "s|^BACKEND_IMAGE_NAME=.*|BACKEND_IMAGE_NAME=${BACKEND_IMAGE_NAME}|" "${__SP_ROOT_DIR}/.env" - "${SED_INPLACE[@]}" "s|^FRONTEND_IMAGE_NAME=.*|FRONTEND_IMAGE_NAME=${FRONTEND_IMAGE_NAME}|" "${__SP_ROOT_DIR}/.env" -} - -if [[ ! -f "${__SP_ROOT_DIR}/.env" ]]; then - echo "### First time running the script ..." - echo "Let's initialize the application ..." - sleep 1 - init_env - echo "Application initialized successfully." -else - read -r -p ".env file found. Do you want to re-create it? (y/N) " DECISION - if [[ "$DECISION" = "Y" || "$DECISION" = "y" ]]; then - sleep 1 - echo "Re-creating env file ..." - sleep 1 - init_env - echo ".env file re-created successfully." - fi -fi - -echo - -sleep 1; - -check_image() { - local image_name=$1 - local dockerfile=$2 - local directory=$3 - - echo "### Checking for $image_name Image ..." - if docker image inspect "$image_name" > /dev/null 2>&1; then - read -r -p "$image_name Image Found. Do you want to re-build it? (y/N) " decision - if [[ "$decision" =~ ^[Yy]$ ]]; then - echo "Deleting Image ..." - docker rmi "$image_name" - echo "Image Deleted." - build_image - else - : - fi - else - echo "$image_name Image Not Found." - build_image - fi -} - -build_image() { - echo "Building $image_name now." - docker build --no-cache -t "$image_name" -f "$dockerfile" "$directory" - echo "$image_name Image Built." -} - -echo "### Searching for Docker Images ..." -sleep 1; - -BACKEND_IMAGE_NAME="${BACKEND_IMAGE_NAME:-socialpredict-dev-backend}" -FRONTEND_IMAGE_NAME="${FRONTEND_IMAGE_NAME:-socialpredict-dev-frontend}" - -DIRECTORY="${__SP_ROOT_DIR}" -BACKEND_DOCKERFILE="${__SP_ROOT_DIR}/docker/backend/Dockerfile.dev" -FRONTEND_DOCKERFILE="${__SP_ROOT_DIR}/docker/frontend/Dockerfile.dev" - -check_image "$BACKEND_IMAGE_NAME" "${BACKEND_DOCKERFILE}" "${DIRECTORY}" -check_image "$FRONTEND_IMAGE_NAME" "${FRONTEND_DOCKERFILE}" "${DIRECTORY}" - -echo -sleep 1; - -echo "Images built." -echo "Use "./SocialPredict up" to start the containers" -echo "And "./SocialPredict down" to stop them." diff --git a/scripts/dev/build_dev.sh b/scripts/dev/build_dev.sh deleted file mode 100755 index c8a17432..00000000 --- a/scripts/dev/build_dev.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash - -# Make sure the script can only be run via SocialPredict Script -[ -z "$CALLED_FROM_SOCIALPREDICT" ] && { echo "Not called from SocialPredict"; exit 42; } - -# Get the list of current docker images and convert to array -IMAGES=$( docker images -a | awk '{print $1}' ) -IMAGES_ARRAY=($IMAGES// / }) - -# Function to build frontend image -build_frontend() { - echo "### Building Frontend Image ..." - - # Get frontend directory - FRONTEND_DIR="$( readlink -f "$SCRIPT_DIR/frontend" )" - - # Build Image - docker build --no-cache -t "${FRONTEND_IMAGE_NAME}" -f "$FRONTEND_DIR/Dockerfile" "$FRONTEND_DIR/." - echo "Frontend Image Built" -} - -# Function to build backend image -build_backend() { - echo "### Building Backend Image ..." - - # Get backend directory - BACKEND_DIR="$( readlink -f "$SCRIPT_DIR/backend" )" - - # Build Image - docker build --no-cache -t "${BACKEND_IMAGE_NAME}" -f "$BACKEND_DIR/Dockerfile" "$BACKEND_DIR/." - echo "Backend Image Built" -} - -check_and_build_image() { - local image_name=$1 - local directory=$2 - local dockerfile=$3 - - echo "### Checking for $image_name Image ..." - if docker image inspect "$image_name" > /dev/null 2>&1; then - read -p "$image_name Image Found. Do you want to re-build it? (y/N) " decision - if [[ "$decision" =~ ^[Yy]$ ]]; then - echo "Deleting Image..." - docker rmi "$image_name" - echo "Image Deleted." - else - return - fi - else - echo "$image_name Image Not Found." - fi - echo "Building $image_name now." - docker build --no-cache -t "$image_name" -f "$directory/$dockerfile" "$directory" - echo "$image_name Image Built" -} - -# Search for images -echo "### Searching for Docker Images ..." -sleep 1; - -# Backend and Frontend Image Names (Defaults can be overridden by environment variables) -BACKEND_IMAGE_NAME="${BACKEND_IMAGE_NAME:-socialpredict-backend:latest}" -FRONTEND_IMAGE_NAME="${FRONTEND_IMAGE_NAME:-socialpredict-frontend:latest}" - -# Backend and Frontend Directory Paths -BACKEND_DIR="$( readlink -f "$SCRIPT_DIR/backend" )" -FRONTEND_DIR="$( readlink -f "$SCRIPT_DIR/frontend" )" - -# Dockerfile Names -BACKEND_DOCKERFILE="Dockerfile" -FRONTEND_DOCKERFILE="Dockerfile" - -# Check and build backend image -check_and_build_image "$BACKEND_IMAGE_NAME" "$BACKEND_DIR" "$BACKEND_DOCKERFILE" - -# Check and build frontend image -check_and_build_image "$FRONTEND_IMAGE_NAME" "$FRONTEND_DIR" "$FRONTEND_DOCKERFILE" - -echo -sleep 1; - -echo "Images built." -echo "Use "./SocialPredict up" to start the containers" -echo "And "./SocialPredict down" to stop them." diff --git a/scripts/dev/env_writer_dev.sh b/scripts/dev/env_writer_dev.sh deleted file mode 100755 index bf70e41b..00000000 --- a/scripts/dev/env_writer_dev.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -# Make sure the script can only be run via SocialPredict Script -[ -z "$CALLED_FROM_SOCIALPREDICT" ] && { echo "Not called from SocialPredict"; exit 42; } - -# Function to create and update .env file -# Updated to be compatible with MacOS Sonoma. -# Uses POSTGRES_VOLUME to deal with MacOS xattrs provenence. -init_env() { - # Create .env file - cp "$SCRIPT_DIR/.env.example" "$SCRIPT_DIR/.env" - - # Update APP_ENV - sed -i -e "s/APP_ENV=.*/APP_ENV='development'/g" "$SCRIPT_DIR/.env" - echo "ENV_PATH=$SCRIPT_DIR/.env" >> "$SCRIPT_DIR/.env" - - # Add OS-specific POSTGRES_VOLUME - OS=$(uname -s) - if [[ "$OS" == "Darwin" ]]; then - echo "POSTGRES_VOLUME=pgdata:/var/lib/postgresql/data" >> "$SCRIPT_DIR/.env" - else - echo "POSTGRES_VOLUME=../data/postgres:/var/lib/postgresql/data" >> "$SCRIPT_DIR/.env" - fi -} - -if [[ ! -f "$SCRIPT_DIR/.env" ]]; then - echo "### First time running the script ..." - echo "Let's initialize the application ..." - sleep 1 - init_env - echo "Application initialized successfully." -else - read -p ".env file found. Do you want to re-create it? (y/N) " DECISION - if [ "$DECISION" != "Y" ] && [ "$DECISION" != "y" ]; then - : - else - sleep 1 - echo "Re-creating env file ..." - sleep 1 - init_env - echo ".env file re-created successfully." - fi -fi - -echo diff --git a/scripts/docker-commands.sh b/scripts/docker-commands.sh index 302d40b4..0c657043 100755 --- a/scripts/docker-commands.sh +++ b/scripts/docker-commands.sh @@ -1,18 +1,10 @@ #!/usr/bin/env bash -set -euo pipefail - -# -------------------------------------------------------------------------------------- -# Path resolution (stable even when this script is "sourced" by ./SocialPredict) -# -------------------------------------------------------------------------------------- -__SP_CMD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -__SP_ROOT_DIR="${SOCIALPREDICT_ROOT:-$(cd "${__SP_CMD_DIR}/.." && pwd)}" # -------------------------------------------------------------------------------------- # Platform handling (Apple Silicon, etc.) – absolute path to avoid CWD issues # -------------------------------------------------------------------------------------- -if [ -f "${__SP_ROOT_DIR}/scripts/lib/arch.sh" ]; then - # shellcheck source=/dev/null - source "${__SP_ROOT_DIR}/scripts/lib/arch.sh" +if [ -f "${SCRIPT_DIR}/scripts/lib/arch.sh" ]; then + source "${SCRIPT_DIR}/scripts/lib/arch.sh" fi # -------------------------------------------------------------------------------------- @@ -33,7 +25,7 @@ case "${APP_ENV}" in *) echo "ERROR: unknown APP_ENV='${APP_ENV}'. Expected one of: development | localhost | production"; exit 1 ;; esac -COMPOSE_MAIN="${__SP_ROOT_DIR}/scripts/${COMPOSE_BASENAME}" +COMPOSE_MAIN="${SCRIPT_DIR}/scripts/${COMPOSE_BASENAME}" if [ ! -f "${COMPOSE_MAIN}" ]; then echo "ERROR: compose file not found for APP_ENV='${APP_ENV}': ${COMPOSE_MAIN}" exit 1 @@ -42,13 +34,10 @@ echo "Using compose file: ${COMPOSE_MAIN}" # optional override (e.g., Apple Silicon platform pinning) COMPOSE_FILES=(-f "${COMPOSE_MAIN}") -if [ -f "${__SP_ROOT_DIR}/docker-compose.override.yml" ]; then - COMPOSE_FILES+=(-f "${__SP_ROOT_DIR}/docker-compose.override.yml") +if [ -f "${SCRIPT_DIR}/scripts/docker-compose.override.yml" ]; then + COMPOSE_FILES+=(-f "${SCRIPT_DIR}/scripts/docker-compose.override.yml") fi -# absolute .env -ENV_FILE="--env-file ${__SP_ROOT_DIR}/.env" - # -------------------------------------------------------------------------------------- # Helpers # -------------------------------------------------------------------------------------- @@ -59,7 +48,7 @@ sp_up() { docker network create --driver bridge socialpredict_external_network # Ensure acme.json exists with correct perms - ACME_FILE="${__SP_ROOT_DIR}/data/traefik/config/acme.json" + ACME_FILE="${SCRIPT_DIR}/data/traefik/config/acme.json" if [ ! -f "${ACME_FILE}" ]; then mkdir -p "$(dirname "${ACME_FILE}")" touch "${ACME_FILE}" @@ -67,38 +56,72 @@ sp_up() { fi fi - docker compose "${COMPOSE_FILES[@]}" ${ENV_FILE} up -d + docker compose --env-file "${SCRIPT_DIR}/.env" "${COMPOSE_FILES[@]}" up -d if [ "${APP_ENV}" = "development" ]; then echo "SocialPredict may be found at http://localhost:${FRONTEND_PORT} ." echo "This may take a few seconds to load initially." echo "Here are the initial settings. These can be changed in setup.yaml" - if [ -f "${__SP_ROOT_DIR}/backend/setup/setup.yaml" ]; then - cat "${__SP_ROOT_DIR}/backend/setup/setup.yaml" + if [ -f "${SCRIPT_DIR}/backend/setup/setup.yaml" ]; then + cat "${SCRIPT_DIR}/backend/setup/setup.yaml" fi fi } sp_down() { - docker compose "${COMPOSE_FILES[@]}" ${ENV_FILE} down -v + docker compose --env-file "${SCRIPT_DIR}/.env" "${COMPOSE_FILES[@]}" down -v } sp_exec() { local target="${1:-}" - local cmd="${2:-/bin/bash}" case "${target}" in nginx) - docker exec -it "${NGINX_CONTAINER_NAME}" ${cmd} + if [ "$#" -lt 2 ]; then + docker exec -it "${NGINX_CONTAINER_NAME}" /bin/bash + else + if [ "$2" == "/bin/bash" ] || [ "$2" == /bin/sh ]; then + docker exec -it "${NGINX_CONTAINER_NAME}" /bin/bash + else + shift + docker exec -i "${NGINX_CONTAINER_NAME}" "$@" + fi + fi ;; backend) - docker exec -it "${BACKEND_CONTAINER_NAME}" ${cmd} + if [ "$#" -lt 2 ]; then + docker exec -it "${BACKEND_CONTAINER_NAME}" /bin/bash + else + if [ "$2" == "/bin/bash" ] || [ "$2" == "/bin/sh" ]; then + docker exec -it "${BACKEND_CONTAINER_NAME}" /bin/bash + else + shift + docker exec -i "${BACKEND_CONTAINER_NAME}" "$@" + fi + fi ;; frontend) - docker exec -it "${FRONTEND_CONTAINER_NAME}" ${cmd} + if [ "$#" -lt 2 ]; then + docker exec -it "${FRONTEND_CONTAINER_NAME}" /bin/bash + else + if [ "$2" == "/bin/bash" ] || [ "$2" == "/bin/sh" ]; then + docker exec -it "${FRONTEND_CONTAINER_NAME}" /bin/bash + else + shift + docker exec -i "${FRONTEND_CONTAINER_NAME}" "$@" + fi + fi ;; postgres|db) - docker exec -it "${POSTGRES_CONTAINER_NAME}" ${cmd} + if [ "$#" -lt 2 ]; then + docker exec -it "${POSTGRES_CONTAINER_NAME}" /bin/bash + else + if [ "$2" == "/bin/bash" ] || [ "$2" == "/bin/sh" ]; then + docker exec -it "${POSTGRES_CONTAINER_NAME}" /bin/bash + else + docker exec -i "${POSTGRES_CONTAINER_NAME}" "$@" + fi + fi ;; *) echo "Unknown service '${target}'. Use one of: nginx | backend | frontend | postgres" @@ -118,15 +141,8 @@ case "${1:-}" in sp_down ;; exec) - # Usage: docker-commands.sh exec [command] - # Example: docker-commands.sh exec backend "bash -lc 'ls -la'" - svc="${2:-}" - shift 2 || true - if [ -z "${svc}" ]; then - echo "Usage: $0 exec [command]" - exit 1 - fi - sp_exec "${svc}" "$*" + shift + sp_exec "$@" ;; *) echo "Usage: $0 {up|down|exec [command]}" diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 00000000..d6302688 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,415 @@ +#!/usr/bin/env bash + +# Make sure the script can only be run via SocialPredict Script +[ -z "${CALLED_FROM_SOCIALPREDICT}" ] && { echo "Not called from SocialPredict"; exit 42; } + +# Set FORCE to false +FORCE="n" + +# Cross-platform "sed -i" (GNU vs BSD/macOS) +if sed --version >/dev/null 2>&1; then + # GNU sed + SED_INPLACE=(sed -i -e) +else + # BSD sed (macOS) requires an empty backup extension: -i '' + SED_INPLACE=(sed -i '' -e) +fi + +# Check if Application is already running +check_if_running() { + if [ -f "${SCRIPT_DIR}/.env" ]; then + local app_env + app_env=$(grep -i 'app_env' "${SCRIPT_DIR}/.env" | awk -F"=" '{print $NF}') + if [ "${app_env}" == 'development' ]; then + if [ -z "$(docker compose --env-file "${SCRIPT_DIR}/.env" -f "${SCRIPT_DIR}/scripts/docker-compose-local.yaml" ps -q)" ]; then + : + elif [ "$FORCE" == "y" ]; then + docker compose --env-file "${SCRIPT_DIR}/.env" -f "${SCRIPT_DIR}/scripts/docker-compose-local.yaml" down -v + else + print_warning 'Application is already running.' + print_error "Please stop it with './SocialPredict down' before proceeding with a new installation" + exit 1 + fi + elif [ "${app_env}" == 'localhost' ]; then + if [ -z "$(docker compose --env-file "${SCRIPT_DIR}/.env" -f "${SCRIPT_DIR}/scripts/docker-compose-local.yaml" ps -q)" ]; then + : + elif [ "$FORCE" == "y" ]; then + docker compose --env-file "${SCRIPT_DIR}/.env" -f "${SCRIPT_DIR}/scripts/docker-compose-local.yaml" down -v + else + print_warning 'Application is already running.' + print_error "Please stop it with './SocialPredict down' before proceeding with a new installation" + exit 1 + fi + elif [ "${app_env}" == 'production' ]; then + if [ -z "$(docker compose --env-file "${SCRIPT_DIR}/.env" -f "${SCRIPT_DIR}/scripts/docker-compose-prod.yaml" ps -q)" ]; then + : + elif [ "$FORCE" == "y" ]; then + docker compose --env-file "${SCRIPT_DIR}/.env" -f "${SCRIPT_DIR}/scripts/docker-compose-local.yaml" down -v + else + print_warning 'Application is already running.' + print_error "Please stop it with './SocialPredict down' before proceeding with a new installation" + exit 1 + fi + fi + else + : + fi +} + +# Check if .env file already exists +check_env() { + if [ -f "${SCRIPT_DIR}/.env" ]; then + if [ "$FORCE" == "y" ]; then + rm "${SCRIPT_DIR}/.env" + cp "${SCRIPT_DIR}/.env.example" "${SCRIPT_DIR}/.env" + else + read -r -p ".env file found. Do you want to re-create it? (y/N) " DECISION + if [[ "${DECISION}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + rm "${SCRIPT_DIR}/.env" + cp "${SCRIPT_DIR}/.env.example" "${SCRIPT_DIR}/.env" + print_status ".env file re-created successfully." + else + print_error "Aborting installation." + exit 1 + fi + fi + else + cp "${SCRIPT_DIR}/.env.example" "${SCRIPT_DIR}/.env" + fi +} + +# Check if Postgres data folder exists +check_postgres() { + if [ -d "${SCRIPT_DIR}/data/postgres" ]; then + if [ "$FORCE" == "y" ]; then + sudo rm -rf "${SCRIPT_DIR}/data/postgres" + else + echo "Postgres Data Folder found." + echo "Make sure to backup your data before proceeding." + read -r -p "Do you want to remove Postgres Data folder? (y/N) " DECISION + if [[ "${DECISION}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + sudo rm -rf "${SCRIPT_DIR}/data/postgres" + print_status "Postgres Data folder deleted successfully." + else + print_error "Aborting installation." + exit 1 + fi + fi + else + : + fi +} + +# Function to generate a random password +generate_password() { + local length=20 + # Define the character set for the password + local char_set="A-Za-z0-9" + + # Generate a random password + tr -dc "$char_set" < /dev/urandom | head -c "$length" + echo +} + +# Check if Docker Image exists on the system +check_image() { + local image_name=$1 + local dockerfile=$2 + local directory=$3 + + echo "### Checking for $image_name Image ..." + if docker image inspect "$image_name" > /dev/null 2>&1; then + read -r -p "$image_name Image Found. Do you want to re-build it? (y/N) " decision + if [[ "$decision" =~ ^[Yy]$ ]]; then + echo "Deleting Image ..." + docker rmi "$image_name" + echo "Image Deleted." + build_image + else + : + fi + else + echo "$image_name Image Not Found." + build_image + fi +} + +# Build Docker Image +build_image() { + echo "Building $image_name now." + docker build --no-cache -t "$image_name" -f "$dockerfile" "$directory" + echo "$image_name Image Built." +} + +# Build procedure for Development Environment +build_dev() { + BACKEND_IMAGE_NAME="${BACKEND_IMAGE_NAME:-socialpredict-dev-backend}" + FRONTEND_IMAGE_NAME="${FRONTEND_IMAGE_NAME:-socialpredict-dev-frontend}" + + # Update APP_ENV + "${SED_INPLACE[@]}" "s|^APP_ENV=.*|APP_ENV=development|" "${SCRIPT_DIR}/.env" + + # Add OS-specific POSTGRES_VOLUME + OS=$(uname -s) + + if [[ "$OS" == "Darwin" ]]; then + "${SED_INPLACE[@]}" "s|^POSTGRES_VOLUME=.*|POSTGRES_VOLUME=pgdata|" "${SCRIPT_DIR}/.env" + else + "${SED_INPLACE[@]}" "s|^POSTGRES_VOLUME=.*|POSTGRES_VOLUME=../data/postgres|" "${SCRIPT_DIR}/.env" + fi + + # Change the Domain settings: + "${SED_INPLACE[@]}" "s|^DOMAIN=.*|DOMAIN='localhost'|" "${SCRIPT_DIR}/.env" + "${SED_INPLACE[@]}" "s|^DOMAIN_URL=.*|DOMAIN_URL='http://localhost'|" "${SCRIPT_DIR}/.env" + + # Remove unnecessary lines from .env + "${SED_INPLACE[@]}" "/^TRAEFIK_CONTAINER_NAME=.*/d" "${SCRIPT_DIR}/.env" + "${SED_INPLACE[@]}" "/^EMAIL=.*/d" "${SCRIPT_DIR}/.env" + + # Update Image Names + "${SED_INPLACE[@]}" "s|^BACKEND_IMAGE_NAME=.*|BACKEND_IMAGE_NAME=${BACKEND_IMAGE_NAME}|" "${SCRIPT_DIR}/.env" + "${SED_INPLACE[@]}" "s|^FRONTEND_IMAGE_NAME=.*|FRONTEND_IMAGE_NAME=${FRONTEND_IMAGE_NAME}|" "${SCRIPT_DIR}/.env" + + print_status "Searching for Docker Images ..." + + DIRECTORY="${SCRIPT_DIR}" + BACKEND_DOCKERFILE="${SCRIPT_DIR}/docker/backend/Dockerfile.dev" + FRONTEND_DOCKERFILE="${SCRIPT_DIR}/docker/frontend/Dockerfile.dev" + + check_image "$BACKEND_IMAGE_NAME" "${BACKEND_DOCKERFILE}" "${DIRECTORY}" + check_image "$FRONTEND_IMAGE_NAME" "${FRONTEND_DOCKERFILE}" "${DIRECTORY}" + + echo + sleep 1; + + echo "Images built." + echo "Use "./SocialPredict up" to start the containers" + echo "And "./SocialPredict down" to stop them." +} + +# Build procedure for Local Environment +build_local() { + # Update APP_ENV + "${SED_INPLACE[@]}" "s|^APP_ENV=.*|APP_ENV=localhost|" "${SCRIPT_DIR}/.env" + + # Add OS-specific POSTGRES_VOLUME + OS=$(uname -s) + if [[ "$OS" == "Darwin" ]]; then + "${SED_INPLACE[@]}" "s|^POSTGRES_VOLUME=.*|POSTGRES_VOLUME=pgdata|" "${SCRIPT_DIR}/.env" + else + "${SED_INPLACE[@]}" "s|^POSTGRES_VOLUME=.*|POSTGRES_VOLUME=../data/postgres|" "${SCRIPT_DIR}/.env" + fi + + # Change the Domain settings: + "${SED_INPLACE[@]}" "s|^DOMAIN=.*|DOMAIN='localhost'|" "${SCRIPT_DIR}/.env" + "${SED_INPLACE[@]}" "s|^DOMAIN_URL=.*|DOMAIN_URL='http://localhost'|" "${SCRIPT_DIR}/.env" + "${SED_INPLACE[@]}" "s|^API_URL=.*|API_URL=http://localhost/api|" "${SCRIPT_DIR}/.env" + + # Remove unnecessary lines from .env + "${SED_INPLACE[@]}" "/^TRAEFIK_CONTAINER_NAME=.*/d" "${SCRIPT_DIR}/.env" + "${SED_INPLACE[@]}" "/^EMAIL=.*/d" "${SCRIPT_DIR}/.env" + + # Pin platform per-service (helpful on Apple Silicon) + cat > "${SCRIPT_DIR}/scripts/docker-compose.override.yml" < "$file" + + echo "Setting EMAIL to: $email_answer" + + # Update Database User Password: + local db_pass + db_pass=$(generate_password) + "${SED_INPLACE[@]}" "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD='${db_pass}'|" "${SCRIPT_DIR}/.env" + echo "Setting Database Password" + + # Update Admin Password: + ADMIN_PASS=$(generate_password) + "${SED_INPLACE[@]}" "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD='${ADMIN_PASS}'|" "${SCRIPT_DIR}/.env" + echo "Setting Admin Password" + + # Pull images + echo "Pulling images ..." + docker compose --env-file "${SCRIPT_DIR}"/.env --file "${SCRIPT_DIR}/scripts/docker-compose-prod.yaml" pull + echo + + echo "Images pulled." + echo + echo "Your admin credentials are:" + echo "Username: admin" + echo "Password: $ADMIN_PASS" +} + +build_production_args() { + # Update APP_ENV + "${SED_INPLACE[@]}" "s|^APP_ENV=.*|APP_ENV=production|" "${SCRIPT_DIR}/.env" + + # Change the Domain settings: + "${SED_INPLACE[@]}" "s|^DOMAIN=.*|DOMAIN='$1'|" "${SCRIPT_DIR}/.env" + "${SED_INPLACE[@]}" "s|^DOMAIN_URL=.*|DOMAIN_URL='https://$1'|" "${SCRIPT_DIR}/.env" + "${SED_INPLACE[@]}" "s|^API_URL=.*|API_URL=https://$1/api|" "${SCRIPT_DIR}/.env" + + echo "Setting DOMAIN to: $1" + + # Update Email Address + template="${SCRIPT_DIR}/data/traefik/config/traefik.template" + file="${SCRIPT_DIR}/data/traefik/config/traefik.yaml" + export EMAIL="$2" + envsubst < "$template" > "$file" + + echo "Setting EMAIL to: $2" + + # Update Database User Password: + local db_pass + db_pass=$(generate_password) + "${SED_INPLACE[@]}" "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD='${db_pass}'|" "${SCRIPT_DIR}/.env" + echo "Setting Database Password" + + # Update Admin Password: + ADMIN_PASS=$(generate_password) + "${SED_INPLACE[@]}" "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD='${ADMIN_PASS}'|" "${SCRIPT_DIR}/.env" + echo "Setting Admin Password" + + # Pull images + echo "Pulling images ..." + docker compose --env-file "${SCRIPT_DIR}"/.env --file "${SCRIPT_DIR}/scripts/docker-compose-prod.yaml" pull + echo + + echo "Images pulled." + echo + echo "Your admin credentials are:" + echo "Username: admin" + echo "Password: $ADMIN_PASS" +} + +if [ "$#" -eq 0 ]; then + + # Check if SocialPredict is running + check_if_running + + # Check if .env file already exists + check_env + + # Check if Postgres Data folder exists + check_postgres + + # Echo initial message + print_status "Building and Deploying SocialPredict ..." + echo + + # Ask user input for Application Environment + echo "### Select Application Environment: " + PS3="Please enter your choice: " + options=("Development" "Localhost" "Production" "Quit") + select opt in "${options[@]}" + do + case $opt in + "Development") + build_dev + break + ;; + "Localhost") + build_local + break + ;; + "Production") + build_production + break + ;; + "Quit") + break + ;; + *) + echo "Invalid option $REPLY" + ;; + esac + done +else + while getopts ":e:d:m:" opt; do + case $opt in + e) + if [ "$OPTARG" != "development" ] && [ "$OPTARG" != "localhost" ] && [ "$OPTARG" != "production" ]; then + print_error "Wrong environment selection." + print_status "Acceptable environments: 'development', 'localhost', 'production'" + exit 1 + else + env="$OPTARG" + fi + ;; + d) + domain="$OPTARG" + ;; + m) + email="$OPTARG" + ;; + \?) + echo "Invalid option: -$OPTARG" + ;; + :) + echo "Option -$OPTARG requires an argument." + ;; + esac + done + FORCE="y" + # Check if SocialPredict is running + check_if_running + + # Check if .env file already exists + check_env + + # Check if Postgres Data folder exists + check_postgres + + if [ "$env" == "development" ]; then + build_dev + elif [ "$env" == "localhost" ]; then + build_local + elif [ "$env" == "production" ]; then + build_production_args "$domain" "$email" + fi +fi diff --git a/scripts/localhost.sh b/scripts/localhost.sh deleted file mode 100755 index d40a1d74..00000000 --- a/scripts/localhost.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Resolve paths (don’t rely on caller CWD) -__SP_LOCAL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -__SP_ROOT_DIR="${SOCIALPREDICT_ROOT:-$(cd "${__SP_LOCAL_DIR}/.." && pwd)}" - -# Apple Silicon handling (sets FORCE_PLATFORM / DOCKER_DEFAULT_PLATFORM as needed) -# shellcheck source=/dev/null -source "${__SP_ROOT_DIR}/scripts/lib/arch.sh" -echo "== localhost platform: ${FORCE_PLATFORM:-default} ==" - -# Cross-platform sed -i -if sed --version >/dev/null 2>&1; then - SED_INPLACE=(sed -i -e) # GNU -else - SED_INPLACE=(sed -i '' -e) # BSD/macOS -fi - -# Guard: only run via ./SocialPredict -[ -z "${CALLED_FROM_SOCIALPREDICT:-}" ] && { echo "Not called from SocialPredict"; exit 42; } - -init_env() { - cp "${__SP_ROOT_DIR}/.env.example" "${__SP_ROOT_DIR}/.env" - - # Mode - "${SED_INPLACE[@]}" "s|^APP_ENV=.*|APP_ENV=localhost|" "${__SP_ROOT_DIR}/.env" - - # GHCR images for localhost pulls - if grep -q '^BACKEND_IMAGE_NAME=' "${__SP_ROOT_DIR}/.env"; then - "${SED_INPLACE[@]}" "s|^BACKEND_IMAGE_NAME=.*|BACKEND_IMAGE_NAME=ghcr.io/openpredictionmarkets/socialpredict-backend:latest|" "${__SP_ROOT_DIR}/.env" - else - printf "\nBACKEND_IMAGE_NAME=ghcr.io/openpredictionmarkets/socialpredict-backend:latest\n" >> "${__SP_ROOT_DIR}/.env" - fi - if grep -q '^FRONTEND_IMAGE_NAME=' "${__SP_ROOT_DIR}/.env"; then - "${SED_INPLACE[@]}" "s|^FRONTEND_IMAGE_NAME=.*|FRONTEND_IMAGE_NAME=ghcr.io/openpredictionmarkets/socialpredict-frontend:latest|" "${__SP_ROOT_DIR}/.env" - else - printf "\nFRONTEND_IMAGE_NAME=ghcr.io/openpredictionmarkets/socialpredict-frontend:latest\n" >> "${__SP_ROOT_DIR}/.env" - fi - - # Postgres (multi-arch tag + named volume) - if grep -q '^POSTGRES_IMAGE=' "${__SP_ROOT_DIR}/.env"; then - "${SED_INPLACE[@]}" "s|^POSTGRES_IMAGE=.*|POSTGRES_IMAGE=postgres:16.6-alpine|" "${__SP_ROOT_DIR}/.env" - else - printf "\nPOSTGRES_IMAGE=postgres:16.6-alpine\n" >> "${__SP_ROOT_DIR}/.env" - fi - if grep -q '^POSTGRES_VOLUME=' "${__SP_ROOT_DIR}/.env"; then - "${SED_INPLACE[@]}" "s|^POSTGRES_VOLUME=.*|POSTGRES_VOLUME=pgdata|" "${__SP_ROOT_DIR}/.env" - else - printf "\nPOSTGRES_VOLUME=pgdata\n" >> "${__SP_ROOT_DIR}/.env" - fi - - # Localhost URLs - "${SED_INPLACE[@]}" "s|^DOMAIN=.*|DOMAIN=localhost|" "${__SP_ROOT_DIR}/.env" - "${SED_INPLACE[@]}" "s|^DOMAIN_URL=.*|DOMAIN_URL=http://localhost|" "${__SP_ROOT_DIR}/.env" - if grep -q '^API_URL=' "${__SP_ROOT_DIR}/.env"; then - "${SED_INPLACE[@]}" "s|^API_URL=.*|API_URL=http://localhost|" "${__SP_ROOT_DIR}/.env" - else - printf "\nAPI_URL=http://localhost\n" >> "${__SP_ROOT_DIR}/.env" - fi - - # Clean prod-only lines - "${SED_INPLACE[@]}" "/^TRAEFIK_CONTAINER_NAME=.*/d" "${__SP_ROOT_DIR}/.env" - "${SED_INPLACE[@]}" "/^EMAIL=.*/d" "${__SP_ROOT_DIR}/.env" - - echo "localhost .env prepared for GHCR images." -} - -# Pin platform per-service (helpful on Apple Silicon) -cat > "${__SP_ROOT_DIR}/docker-compose.override.yml" < "$file" - - sed -i -e "s/SSLEMAIL/$email_answer/g" ./data/traefik/config/traefik.yaml - echo "Setting EMAIL to: $email_answer" - - echo - - # Update Database User Password: - local db_pass - db_pass=$(generate_password) - sed -i -e "s/POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD='${db_pass//&/\\&}'/g" .env - echo "Setting Database Password" - - echo - - # Update Admin Password: - ADMIN_PASS=$(generate_password) - sed -i -e "s/ADMIN_PASSWORD=.*/ADMIN_PASSWORD='${ADMIN_PASS}'/g" .env - echo "Setting Admin Password" -} - -_main() { - if [[ ! -f "$SCRIPT_DIR/.env" ]]; then - echo "### First time running the script ..." - echo "Let's initialize the application ..." - sleep 1 - init_env - echo "Application initialized successfully." - else - read -r -p ".env file found. Do you want to re-create it? (y/N) " DECISION - if [ "$DECISION" != "Y" ] && [ "$DECISION" != "y" ]; then - : - else - sleep 1 - echo "Re-creating env file ..." - sleep 1 - init_env - echo ".env file re-created successfully." - fi - fi - - echo -} - -if ! _is_sourced; then - _main -fi