#!/bin/bash APP_DIR="$HOME/.db-middleware" CODE_DIR="$APP_DIR/code" CONFIG_DIR="$APP_DIR/configs" CONFIG_FILE="$CONFIG_DIR/app.conf" REPO_URL="ssh://git@gitea.abdulha.de:222/IntegrateGlobals/db-middleware" LOADED_CONFIG=0 # Default values APP_NAME="Database Middleware" IMAGE_NAME="db-middleware" CONTAINER_NAME="con-db-middleware" EXECUTION_MESSAGE="NO-RETURN" print_header() { local HEADER_TEXT="$1" local MAX_LENGTH=0 # Split the header text into lines and find the maximum line length while IFS= read -r LINE; do local LINE_LENGTH=${#LINE} if (( LINE_LENGTH > MAX_LENGTH )); then MAX_LENGTH=$LINE_LENGTH fi done <<< "$HEADER_TEXT" local BORDER_LENGTH=$((MAX_LENGTH + 2)) # Add 4 for padding (2 spaces) echo # Print the top border printf '+%*s+\n' "$BORDER_LENGTH" "" | tr ' ' '-' # Print each line of the header text while IFS= read -r LINE; do printf "| %-*s |\n" "$MAX_LENGTH" "$LINE" done <<< "$HEADER_TEXT" # Print the bottom border printf '+%*s+\n' "$BORDER_LENGTH" "" | tr ' ' '-' echo } build_docker_image() { if ! cd "$CODE_DIR"; then print_header "Failed to navigate to $CODE_DIR." return 1 fi echo "Building Docker image..." # Check if BuildKit is installed if docker buildx version &>/dev/null; then echo "BuildKit detected. Enabling BuildKit..." export DOCKER_BUILDKIT=1 else echo "BuildKit not found. Using legacy builder..." export DOCKER_BUILDKIT=0 fi echo echo echo if docker build -t "$IMAGE_NAME" .; then echo "Docker image built successfully." return 0 else echo "Failed to build Docker image." return 1 fi } set_up_middleware() { print_header "Rebuilding the Docker Container..." if ! build_docker_image; then print_header "Can't build the Docker image." return 1 # Error fi if ! set_up_scripts; then print_header "Failed to set up scripts." return 1 # Error fi print_header "Rebuilt the Docker Container." return 0 } test() { echo "I am here" return 1 } exec_in_container() { # Returns 0 if the command was successful local CONTAINER_NAME=$CONTAINER_NAME local COMMAND="$1" # Check if the container is running if ! docker ps --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then print_header "Error: Container '$CONTAINER_NAME' is not running. We can't execute commands inside it, you can run it with: >>> $0 start" return 1 fi # Execute the command in the container OUTPUT=$(docker exec "$CONTAINER_NAME" bash -c "$COMMAND" 2>&1) local EXIT_CODE=$? # Check if the command succeeded if [[ $EXIT_CODE -eq 0 ]]; then EXECUTION_MESSAGE="$OUTPUT" # Return the output return 0 else print_header "Error: Command failed in container '$CONTAINER_NAME' with exit code $EXIT_CODE. Output: $OUTPUT" return $EXIT_CODE fi } set_up_scripts() { echo "Setting up scripts..." # Copy scripts if ! cp "$CODE_DIR/scripts/"* "$APP_DIR/scripts/"; then print_header "Failed to copy scripts." return 1 fi # Give execution permission if ! chmod +x "$APP_DIR/scripts/"*; then print_header "Failed to set execution permissions." return 1 fi # Create ~/.local/bin if it doesn't exist mkdir -p "$HOME/.local/bin" # Create symlink if ! ln -sf "$APP_DIR/scripts/manager.sh" "$HOME/.local/bin/db-middleware"; then print_header "Failed to create symlink." # TODO Here we should handle the case of having the symlink created previously. return 1 fi # Update PATH if ! grep -q "$HOME/.local/bin" ~/.bashrc; then echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc fi # Reload bashrc if ! source ~/.bashrc; then print_header "Failed to reload ~/.bashrc." return 1 fi echo "Scripts set up successfully." return 0 } create_user(){ read -p "Enter username: " USERNAME read -p "Enter role (admin/user): " ROLE_INPUT local COMMAND="python3 -c 'from utils.scripts import create_user_script_sync;create_user_script_sync(username=\"$USERNAME\", role_input=\"$ROLE_INPUT\")'" exec_in_container "$COMMAND" local RETURN_CODE=$? if [[ $RETURN_CODE -eq 0 ]]; then print_header "$EXECUTION_MESSAGE" else print_header "Couldn't create the user due to the above error." fi } update_config() { print_header "Update configs." echo "Key: API_PORT, Current value: \`$API_PORT\`" echo "This is the internal port on your server's localhost network, where the Middleware will be listening to." read -p " > Enter new value (leave empty to pass): " NEW_API_PORT echo if [[ $NEW_API_PORT == "" ]]; then NEW_API_PORT=$API_PORT fi echo "Key: CONTAINER_NAME, Current value: \`$CONTAINER_NAME\`" echo "This is the name of the Docker Container we will create for this middleware." read -p " > Enter new value (leave empty to pass): " NEW_CONTAINER_NAME echo if [[ $NEW_CONTAINER_NAME == "" ]]; then NEW_CONTAINER_NAME=$CONTAINER_NAME fi echo "Key: HAS_LOCAL_DBS, Current value: \`$HAS_LOCAL_DBS\`" echo "This will change the type of the network of the docker container, between a bridge or a host network." echo "Enter [0] if all your database are on another server(s)" echo "Enter [1] If you have a database on this server" read -p " > Enter new value (leave empty to pass): " NEW_HAS_LOCAL_DBS echo if [[ $NEW_HAS_LOCAL_DBS == "" ]]; then NEW_HAS_LOCAL_DBS=$HAS_LOCAL_DBS fi print_header "Here are the old and new values: API_PORT: \`$API_PORT > $NEW_API_PORT\` CONTAINER_NAME: \`$CONTAINER_NAME > $NEW_CONTAINER_NAME\` HAS_LOCAL_DBS: \`$HAS_LOCAL_DBS > $NEW_HAS_LOCAL_DBS\`" read -p " > To confirm these changes, enter (y) otherwise enter anything: " CONFIRM_CHANGES if [[ $CONFIRM_CHANGES == "y" ]]; then API_PORT=$NEW_API_PORT CONTAINER_NAME=$NEW_CONTAINER_NAME HAS_LOCAL_DBS=$NEW_HAS_LOCAL_DBS write_config print_header "Saved the new configs! You can now rebuild the docker container." return 0 fi print_header "Discarded the changes." } write_config() { CONFIG_TEXT=" # Application settings APP_NAME=\"$APP_NAME\" API_PORT=\"$API_PORT\" # Docker Container settings IMAGE_NAME=\"$IMAGE_NAME\" CONTAINER_NAME=\"$CONTAINER_NAME\" # Databases settings HAS_LOCAL_DBS=\"$HAS_LOCAL_DBS\" " echo "$CONFIG_TEXT" > "$CONFIG_FILE" } load_config() { if [[ -f "$CONFIG_FILE" ]]; then if source "$CONFIG_FILE"; then LOADED_CONFIG=1 echo "Config file loaded successfully." return 0 else print_header "Failed to load config file." return 1 fi else print_header "Config file not found: $CONFIG_FILE" return 1 fi } get_run_command() { local RUN_COMMAND="docker run -d --name $CONTAINER_NAME" if [[ $HAS_LOCAL_DBS -eq 1 || $HAS_LOCAL_DBS == "1" ]]; then RUN_COMMAND+=" --env API_HOST=localhost --env API_PORT=${API_PORT:-8080} --network host" else RUN_COMMAND+=" --env API_HOST=0.0.0.0 --env API_PORT=${API_PORT:-8080} -p ${API_PORT:-8080}:8080" fi RUN_COMMAND+=" -v /home/$USER/.db-middleware/files:/app/files $IMAGE_NAME" echo $RUN_COMMAND } show_config() { if [[ $LOADED_CONFIG -eq 0 ]]; then print_header "Config not loaded. Please load the config file first." return 1 fi local RUN_COMMAND=$(get_run_command) print_header "Current Config: CONTAINER_NAME: ${CONTAINER_NAME:-Not set} API_PORT: ${API_PORT:-Not set} HAS_LOCAL_DBS: ${HAS_LOCAL_DBS:-Not set} Run Command: $RUN_COMMAND" return 0 } convert_ports_to_docker_args() { local ports="$1" local docker_args="" if [[ -z "$ports" ]]; then echo "No ports provided." return 1 fi # Split the ports by comma and trim whitespace IFS=',' read -r -a port_array <<< "$ports" # Loop through the ports and format them as Docker arguments for port in "${port_array[@]}"; do port=$(echo "$port" | xargs) # Trim whitespace if [[ ! "$port" =~ ^[0-9]+$ ]]; then echo "Invalid port: $port" return 1 fi docker_args+=" -p $port:$port" done echo "$docker_args" return 0 } install() { print_header "Installing the Middleware..." if ! cd "$CODE_DIR"; then print_header "Failed to navigate to $CODE_DIR." return 1 fi update_config print_header "Building the Docker Image..." if ! build_docker_image; then print_header "Failed to build Docker image." return 1 fi print_header "Installed the Middleware Successfully! - You can run the middleware simply using the manager: >>> db-middleware start - Or directly by running the docker container: >>> $(get_run_command)" return 0 } pull_clone_repo() { local UP_TO_DATE=-1 # Default: No changes echo "Setting up repository..." # Create directories if they don't exist mkdir -p "$APP_DIR/{code,configs,scripts}" # Navigate to the code directory if ! cd "$CODE_DIR"; then echo "Failed to navigate to $CODE_DIR." return 1 # Error fi # Check if the directory is empty if [[ -z "$(ls -A $CODE_DIR)" ]]; then echo "Directory is empty. Cloning repository..." if ! git clone "$REPO_URL" .; then echo "Failed to clone repository." return 1 # Error fi echo "Repository cloned successfully." UP_TO_DATE=0 # Changes were applied else # Check if the directory contains a Git repository if [[ -d ".git" ]]; then echo "Directory contains a Git repository." # Fetch the latest changes if ! git fetch origin; then echo "Failed to fetch changes from remote." return 1 # Error fi # Get the local and remote HEAD commit hashes echo LOCAL_HEAD=$(git rev-parse HEAD) REMOTE_HEAD=$(git rev-parse origin/main) exec_in_container "git rev-parse HEAD" local RETURN_CODE=$? CONTAINER_HEAD=$EXECUTION_MESSAGE EXECUTION_MESSAGE="NO-RETURN" echo echo "- Commit hash in remote origin: $REMOTE_HEAD" echo "- Commit hash in local repo: $LOCAL_HEAD" if [[ $RETURN_CODE -eq 0 ]]; then echo "- Commit hash in container: $CONTAINER_HEAD" else echo "Failed to get commit hash from container." fi echo if [[ "$CONTAINER_HEAD" == "$LOCAL_HEAD" && "$LOCAL_HEAD" == "$REMOTE_HEAD" ]]; then echo "Repo is up to date." UP_TO_DATE=-1 elif [[ "$LOCAL_HEAD" != "$REMOTE_HEAD" ]]; then echo "Remote repository has new changes. Pulling latest changes..." echo if ! git pull "$REPO_URL"; then echo "Failed to pull changes." return 1 # Error fi echo echo "Local Repository updated successfully." LOCAL_HEAD=$(git rev-parse HEAD) UP_TO_DATE=0 # Changes were applied else echo "Local Repository is already up to date." UP_TO_DATE=-1 # No changes # echo $UP_TO_DATE fi if [[ "$LOCAL_HEAD" != "$CONTAINER_HEAD" ]]; then echo "Container Repository is not up to date." UP_TO_DATE=0 fi else echo "Directory is not empty and does not contain a Git repository." echo "Please ensure the directory is empty or contains a valid Git repository." return 1 # Error fi fi echo "Repository setup completed successfully." return $UP_TO_DATE # -1 = No changes, 0 = Changes applied } update_code() { print_header "Checking for updates..." pull_clone_repo local UP_TO_DATE=$? case $UP_TO_DATE in # 255 is -1, because Bash return codes are unsigned 8-bit integer, limited to the range 0 to 255. -1|255) print_header "No changes detected." return -1 # No changes ;; 0) print_header "Changes were detected and applied to local repo, but not to the container. Need to rebuild the container, run: >>> db-middleware upgrade " return 0 # Changes applied ;; 1) print_header "Failed to update or clone repository." return 1 # Error ;; *) print_header "Wrong return code: \`$UP_TO_DATE\` from pull_clone_repo." return 1 ;; esac } upgrade() { print_header "Upgrading the Middleware..." update_code local UPDATE_RESULT=$? case $UPDATE_RESULT in # 255 is -1, because Bash return codes are unsigned 8-bit integer, limited to the range 0 to 255. -1|255) print_header "No changes detected. Skipping upgrade..." return -1 ;; 0) if ! set_up_middleware; then print_header "Failed to rebuild Docker image." return 1 # Error fi ;; 1) print_header "Failed to update code. Upgrade aborted." return 1 # Error ;; esac print_header "Upgraded the Docker image. We will stop & delete the current container and start a new one..." stop docker remove $CONTAINER_NAME start print_header "Upgraded the Middleware Successfully!" return 0 } status() { print_header "Checking container status..." # Check if the container exists if ! docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then print_header "Container '$CONTAINER_NAME' does not exist." return 1 fi # Get container status local CONTAINER_STATUS CONTAINER_STATUS=$(docker ps --filter "name=$CONTAINER_NAME" --format "{{.Status}}") if [[ -z "$CONTAINER_STATUS" ]]; then print_header "Container '$CONTAINER_NAME' is not running." return 0 fi # Get detailed container information using `docker inspect` local CONTAINER_INFO CONTAINER_INFO=$(docker inspect "$CONTAINER_NAME" 2>/dev/null) if [[ -z "$CONTAINER_INFO" ]]; then print_header "Failed to inspect container '$CONTAINER_NAME'." return 1 fi # Extract useful information local CPU_PERCENT MEM_USAGE MEM_PERCENT NET_IO BLOCK_IO IP_ADDRESS PORTS RUN_COMMAND NETWORK_MODE # Resource usage (CPU, memory, storage) STATS_OUTPUT=$(docker stats --no-stream --format "{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}" "$CONTAINER_NAME") # Split the output into individual variables IFS=$'\t' read -r CPU_PERCENT MEM_USAGE MEM_PERCENT NET_IO BLOCK_IO <<< "$STATS_OUTPUT" # Network information NETWORK_MODE=$(echo "$CONTAINER_INFO" | grep -oP '"NetworkMode": "\K[^"]+') if [[ "$NETWORK_MODE" == "host" ]]; then IP_ADDRESS="Host Network (No separate IP)" PORTS="Host Network (Ports are directly bound to host)" else IP_ADDRESS=$(echo "$CONTAINER_INFO" | grep -oP '"IPAddress": "\K[^"]+') # Extract port mappings from docker ps --no-trunc PORTS=$(docker ps --filter "name=$CONTAINER_NAME" --no-trunc --format "{{.Ports}}") if [[ -z "$PORTS" ]]; then PORTS="No port mappings" fi fi # Run command (from docker ps --no-trunc) RUN_COMMAND=$(docker ps --filter "name=$CONTAINER_NAME" --no-trunc --format "{{.Command}}") # Build the output string local OUTPUT OUTPUT="Database Middleware Status: [Container] Name: $CONTAINER_NAME Status: $CONTAINER_STATUS [Performance] CPU Usage: $CPU_PERCENT Memory Usage: $MEM_USAGE ($MEM_PERCENT) Block I/O: $BLOCK_IO Network I/O: $NET_IO [Network] Network Mode: $NETWORK_MODE IP Address: $IP_ADDRESS Ports: $PORTS [App] Run Command: $RUN_COMMAND" print_header "$OUTPUT" return 0 } start() { RUN_COMMAND=$(get_run_command) print_header "Starting the Container... With run command: >>> $RUN_COMMAND" # Check if a container with the same name already exists if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then # Check if the container is already running if docker ps --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then print_header "Container '$CONTAINER_NAME' is already running. You can restart it with: >>> $0 restart" return 0 else # Start the existing container if docker start "$CONTAINER_NAME"; then print_header "Started the existing container '$CONTAINER_NAME' successfully." return 0 else print_header "Failed to start the existing container '$CONTAINER_NAME'." return 1 fi fi else # Run a new container if eval "$RUN_COMMAND"; then print_header "Started the container '$CONTAINER_NAME' successfully." return 0 else print_header "Failed to start the container '$CONTAINER_NAME'." return 1 fi fi } restart() { print_header "Restarting the Container..." # Check if a container with the same name exists (running or stopped) if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then # Restart the container if docker restart "$CONTAINER_NAME"; then print_header "Restarted the container '$CONTAINER_NAME' successfully." return 0 else print_header "Failed to restart the container '$CONTAINER_NAME'." return 1 fi else print_header "Container '$CONTAINER_NAME' does not exist. Cannot restart. You can start it with: >>> $0 start" return 1 fi } stop() { print_header "Stopping the Container..." # Check if the container exists (running or stopped) if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then # Check if the container is running if docker ps --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then # Stop the container if docker stop "$CONTAINER_NAME"; then print_header "Stopped the container '$CONTAINER_NAME' successfully." return 0 else print_header "Failed to stop the container '$CONTAINER_NAME'." return 1 fi else print_header "Container '$CONTAINER_NAME' is not running." return 0 fi else print_header "Container '$CONTAINER_NAME' does not exist. Cannot stop." return 1 fi } test_setup(){ # Check if the container exists (running or stopped) if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then # Check if the container is running if docker ps --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then # Stop the container RESPONSE=$(wget -qO- "http://127.0.0.1:$API_PORT/ping" 2>/dev/null) if [ "$RESPONSE" = '"Ok"' ]; then print_header "Container '$CONTAINER_NAME' is Found. Container '$CONTAINER_NAME' is running. App returned Ok to ping request." return 0 else print_header "Container Found. Container Running. Error with the return of the App, it is not working right. Response: $RESPONSE" return 1 fi else print_header "Container '$CONTAINER_NAME' is Found. Container '$CONTAINER_NAME' is not running." return 0 fi else print_header "Container '$CONTAINER_NAME' does not exist." return 1 fi } help() { print_header "help: Source Code Management: > install > update_code > upgrade > rebuild App Running: > status > test_setup > start > restart > stop Users Management: > create_user Configurations: > show_config > update_config Help: > help " exit 1 } main() { # Check if an argument is provided if [[ $# -eq 0 ]]; then help fi if ! load_config; then print_header "Failed to load config. Exiting." exit 1 fi # Handle the argument case "$1" in install) install ;; update_code) update_code ;; create_user) create_user ;; upgrade) upgrade ;; rebuild) set_up_middleware;; start) start ;; restart) restart;; status) status ;; stop) stop ;; show_config) show_config ;; update_config) update_config ;; test_setup) test_setup ;; help) help ;; *) echo "Invalid argument: $1"; help ;; esac } # Run the script with the provided arguments main "$@"