#!/bin/bash set -e # Constants NOCO_HOME="./nocodb" CURRENT_PATH=$(pwd) REQUIRED_PORTS=(80 443) # Color definitions RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' ORANGE='\033[0;33m' BOLD='\033[1m' NC='\033[0m' # Global variables CONFIG_DOMAIN_NAME="" CONFIG_SSL_ENABLED="" CONFIG_EDITION="" CONFIG_LICENSE_KEY="" CONFIG_REDIS_ENABLED="" CONFIG_MINIO_ENABLED="" CONFIG_MINIO_DOMAIN_NAME="" CONFIG_MINIO_SSL_ENABLED="" CONFIG_WATCHTOWER_ENABLED="" CONFIG_NUM_INSTANCES="" CONFIG_POSTGRES_PASSWORD="" CONFIG_REDIS_PASSWORD="" CONFIG_MINIO_ACCESS_KEY="" CONFIG_MINIO_ACCESS_SECRET="" CONFIG_DOCKER_COMMAND="" declare -a message_arr # Utility functions print_color() { printf "${1}%s${NC}\n" "$2"; } print_info() { print_color "$BLUE" "INFO: $1"; } print_success() { print_color "$GREEN" "SUCCESS: $1"; } print_warning() { print_color "$YELLOW" "WARNING: $1"; } print_error() { print_color "$RED" "ERROR: $1"; } print_box_message() { local message=("$@") local edge="======================================" local padding=" " echo "$edge" for element in "${message[@]}"; do echo "${padding}${element}" done echo "$edge" } command_exists() { command -v "$1" >/dev/null 2>&1; } is_valid_domain() { local domain_regex="^([a-zA-Z0-9]([-a-zA-Z0-9]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$" [[ "$1" =~ $domain_regex ]] } urlencode() { local string="$1" local strlen=${#string} local encoded="" local pos c o for (( pos=0 ; pos /dev/null if sudo mv "$TEMP_HOSTS_FILE" "$HOSTS_FILE"; then print_info "Added ${CONFIG_MINIO_DOMAIN_NAME} to $HOSTS_FILE" else print_error "Failed to update $HOSTS_FILE. Please check your permissions." return 1 fi fi } check_for_docker_sudo() { if docker ps >/dev/null 2>&1; then echo "n" else echo "y" fi } read_number() { local prompt="$1" local default="$2" local number while true; do if [ -n "$default" ]; then read -rp "$prompt [$default]: " number number=${number:-$default} else read -rp "$prompt: " number fi if [ -z "$number" ]; then echo "Input cannot be empty. Please enter a number." elif ! [[ $number =~ ^[0-9]+$ ]]; then echo "Invalid input. Please enter a valid number." else echo "$number" return fi done } read_number_range() { local prompt="$1" local min="$2" local max="$3" local default="$4" local number while true; do if [ -n "$default" ]; then number=$(read_number "$prompt ($min-$max)" "$default") else number=$(read_number "$prompt ($min-$max)") fi if [ -z "$number" ]; then continue elif [ "$number" -lt "$min" ] || [ "$number" -gt "$max" ]; then echo "Please enter a number between $min and $max." else echo "$number" return fi done } check_if_docker_is_running() { if ! $CONFIG_DOCKER_COMMAND ps >/dev/null 2>&1; then print_warning "Docker is not running. Most of the commands will not work without Docker." print_info "Use the following command to start Docker:" print_color "$BLUE" " sudo systemctl start docker" fi } # Main functions check_existing_installation() { NOCO_FOUND=false # Check if $NOCO_HOME exists as directory if [ -d "$NOCO_HOME" ]; then NOCO_FOUND=true elif $CONFIG_DOCKER_COMMAND ps --format '{{.Names}}' | grep -q "nocodb"; then NOCO_ID=$($CONFIG_DOCKER_COMMAND ps | grep "nocodb/nocodb" | cut -d ' ' -f 1) CUSTOM_HOME=$($CONFIG_DOCKER_COMMAND inspect --format='{{index .Mounts 0}}' "$NOCO_ID" | cut -d ' ' -f 3) PARENT_DIR=$(dirname "$CUSTOM_HOME") ln -s "$PARENT_DIR" "$NOCO_HOME" basename "$PARENT_DIR" > "$NOCO_HOME/.COMPOSE_PROJECT_NAME" NOCO_FOUND=true else mkdir -p "$NOCO_HOME" fi cd "$NOCO_HOME" || exit 1 # Check if nocodb is already installed if [ "$NOCO_FOUND" = true ]; then echo "NocoDB is already installed. And running." echo "Do you want to reinstall NocoDB? [Y/N] (default: N): " read -r REINSTALL if [ -f "$NOCO_HOME/.COMPOSE_PROJECT_NAME" ]; then COMPOSE_PROJECT_NAME=$(cat "$NOCO_HOME/.COMPOSE_PROJECT_NAME") export COMPOSE_PROJECT_NAME fi if [ "$REINSTALL" != "Y" ] && [ "$REINSTALL" != "y" ]; then management_menu exit 0 else echo "Reinstalling NocoDB..." $CONFIG_DOCKER_COMMAND compose down unset COMPOSE_PROJECT_NAME cd /tmp || exit 1 rm -rf "$NOCO_HOME" cd "$CURRENT_PATH" || exit 1 mkdir -p "$NOCO_HOME" cd "$NOCO_HOME" || exit 1 fi fi } check_system_requirements() { print_info "Performing NocoDB system check and setup" for tool in docker wget lsof openssl; do if ! command_exists "$tool"; then print_warning "$tool is not installed. Setting up for installation..." if [ "$tool" = "docker" ]; then wget -qO- https://get.docker.com/ | sh else install_package "$tool" fi fi done for port in "${REQUIRED_PORTS[@]}"; do if lsof -Pi :"$port" -sTCP:LISTEN -t >/dev/null; then print_warning "Port $port is in use. Please make sure it is free." else print_info "Port $port is free." fi done print_success "System check completed successfully" } get_user_inputs() { CONFIG_DOMAIN_NAME=$(prompt "Enter the IP address or domain name for the NocoDB instance" "$(get_public_ip)") if is_valid_domain "$CONFIG_DOMAIN_NAME"; then if confirm "Do you want to configure SSL for $CONFIG_DOMAIN_NAME?"; then CONFIG_SSL_ENABLED="Y" else CONFIG_SSL_ENABLED="N" fi else CONFIG_SSL_ENABLED="N" fi if confirm "Show Advanced Options?"; then get_advanced_options else set_default_options fi } get_advanced_options() { CONFIG_EDITION=$(prompt "Choose Community or Enterprise Edition [CE/EE]" "CE") if [ "$CONFIG_EDITION" = "EE" ] || [ "$CONFIG_EDITION" = "ee" ]; then CONFIG_LICENSE_KEY=$(prompt_required "Enter the NocoDB license key") fi CONFIG_REDIS_ENABLED=$(confirm "Do you want to enable Redis for caching?" "Y" && echo "Y" || echo "N" "Y") CONFIG_MINIO_ENABLED=$(confirm "Do you want to enable Minio for file storage?" "Y" && echo "Y" || echo "N" "Y") if [ "$CONFIG_MINIO_ENABLED" = "Y" ] || [ "$CONFIG_MINIO_ENABLED" = "y" ]; then CONFIG_MINIO_DOMAIN_NAME=$(prompt "Enter the MinIO domain name" "minio") if is_valid_domain "CONFIG_MINIO_DOMAIN_NAME"; then if confirm "Do you want to configure SSL for $CONFIG_MINIO_DOMAIN_NAME?"; then CONFIG_MINIO_SSL_ENABLED="Y" else CONFIG_MINIO_SSL_ENABLED="N" fi else CONFIG_MINIO_SSL_ENABLED="N" fi fi CONFIG_WATCHTOWER_ENABLED=$(confirm "Do you want to enable Watchtower for automatic updates?" "Y" && echo "Y" || echo "N") NUM_CORES=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) CONFIG_NUM_INSTANCES=$(read_number_range "How many instances of NocoDB do you want to run?" 1 "$NUM_CORES" 1) } set_default_options() { CONFIG_SSL_ENABLED="N" CONFIG_EDITION="CE" CONFIG_REDIS_ENABLED="Y" CONFIG_MINIO_ENABLED="Y" CONFIG_MINIO_DOMAIN_NAME="minio" CONFIG_MINIO_SSL_ENABLED="N" CONFIG_WATCHTOWER_ENABLED="Y" CONFIG_NUM_INSTANCES=1 } generate_credentials() { CONFIG_POSTGRES_PASSWORD=$(generate_password) CONFIG_REDIS_PASSWORD=$(generate_password) CONFIG_MINIO_ACCESS_KEY=$(generate_password) CONFIG_MINIO_ACCESS_SECRET=$(generate_password) } create_docker_compose_file() { image="nocodb/nocodb:latest" if [ "${CONFIG_EDITION}" = "EE" ] || [ "${CONFIG_EDITION}" = "ee" ]; then image="nocodb/ee:latest" fi local compose_file="docker-compose.yml" cat > "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" < "$env_file" <> "$env_file" echo "NC_LICENSE_KEY=${CONFIG_LICENSE_KEY}" >> "$env_file" else echo "NC_DB=pg://db:5432?d=nocodb&user=postgres&password=${encoded_password}" >> "$env_file" fi if [ "${CONFIG_REDIS_ENABLED}" = "Y" ]; then cat >> "$env_file" <> "$env_file" <> "$env_file" else echo "NC_S3_ENDPOINT=http://${CONFIG_MINIO_DOMAIN_NAME}:9000" >> "$env_file" fi fi } create_update_script() { cat > ./update.sh <