From c16b869f6f3abdecd9107c17ed668ef5d26c555d Mon Sep 17 00:00:00 2001 From: timqiu <9145422+cocoonwind@user.noreply.gitee.com> Date: Fri, 14 Mar 2025 18:10:05 +0800 Subject: [PATCH] Update for start and restart logic --- devbox/cli/devbox | 1199 ++++++++++++++++++++++----------------------- 1 file changed, 584 insertions(+), 615 deletions(-) diff --git a/devbox/cli/devbox b/devbox/cli/devbox index 1f134c2..f4a293b 100644 --- a/devbox/cli/devbox +++ b/devbox/cli/devbox @@ -271,10 +271,15 @@ devbox_stop_usage() { printf "Command\n" printf " devbox stop : Stop the local development environment based on DevBox container.\n\n" printf "Arguments\n" - printf " --component -c [Optional] : Specifies the name of the component to stop (e.g., mongodb, rabbitmq, devbox, devsvc, content, central_storage, notification, chat, authentication).\n\n" + printf " --component -c [Optional] : Specifies the name of the component to stop (e.g., mongodb, rabbitmq, devbox, devsvc, content, central_storage, notification, chat, authentication).\n\n" + printf " --freeleaps-endpoint -e [Optional] : Specifies the Freeleaps.com endpoint backend & frontend to stop in the DevBox container.\n\n" printf "Global Arguments\n" - printf " --help -h : Show this help message and exit.\n\n" + printf " --help -h : Show this help message and exit.\n\n" printf "Examples\n" + printf " stop freeleaps backend service:\n" + printf " devbox stop --freeleaps-endpoint=backend\n" + printf " stop all services:\n" + printf " devbox stop --freeleaps-endpoint=all\n" printf " Stop all components:\n" printf " devbox stop\n\n" printf " Stop a specific component (e.g., backend):\n" @@ -301,7 +306,7 @@ devbox_status_usage() { printf " Display status for all components:\n" printf " devbox status\n\n" printf " Display status for a specific component:\n" - printf " devbox status --component=backend\n" + printf " devbox status --component=content\n" else printf "devbox status - Display the status of the local development environment based on DevBox container.\n" fi @@ -606,6 +611,396 @@ build_local_image() { } +############################################### +# Initialize the development environment +############################################### +init_compile_env() { + +docker exec -i "$DEVBOX_NAME" bash < [INIT] Starting DevBox initialization..." + + # Export environment variables + export WORKING_HOME="${WORKING_HOME}" + export FREELEAPS_USERNAME="${FREELEAPS_USERNAME}" + export FREELEAPS_PASSWORD="${FREELEAPS_PASSWORD}" + export USE_LOCAL_COMPONENT_VAL="${USE_LOCAL_COMPONENT_VAL}" + export DEVBOX_BACKEND_PORT="${DEVBOX_BACKEND_PORT}" + export DEVBOX_FRONTEND_PORT="${DEVBOX_FRONTEND_PORT}" + + echo "==> [INIT] USE_LOCAL_COMPONENT_VAL: ${USE_LOCAL_COMPONENT_VAL}" + # Check if the working home directory exists, if not, create it +if [[ "$USE_LOCAL_COMPONENT_VAL" == "true" ]]; then + # Local component environment variables + echo "==> [INIT] Use local component dev environment." + cat << 'EOFinner' > /home/devbox/freeleaps/apps/.env + export MONGODB_NAME=freeleaps2 + export MONGODB_URI=mongodb://freeleaps2-mongodb:27017/ + export MONGODB_PORT=27017 + export BLOB_STORE_CONNECTION_STR="DefaultEndpointsProtocol=https;AccountName=freeleaps1static;AccountKey=SIk7S3RviJxl1XhGiDZKA3cvzfxNrSbsBMfJ3EbKTsKPeMwhy8FTLpJliRLzQVE6uaSX8giDYw2h+ASt5MmHxQ==;EndpointSuffix=core.windows.net" + export RABBITMQ_HOSTNAME=freeleaps2 + export RABBITMQ_HOST=freeleaps2-rabbitmq + export RABBITMQ_PORT=5672 + export FREELEAPS_ENV=dev + export STRIPE_API_KEY=sk_test_51Ogsw5B0IyqaSJBrwczlr820jnmvA1qQQGoLZ2XxOsIzikpmXo4pRLjw4XVMTEBR8DdVTYySiAv1XX53Zv5xqynF00GfMqttFd + export STRIPE_WEBHOOK_SECRET=whsec_S6ZWjSAdR5Cpsn2USH6ZRBqbdBIENjTC + export STRIPE_ACCOUNT_WEBHOOK_SECRET=whsec_PgPnkWGhEUiQfnV8aIb5Wmruz7XETJLm + export SITE_URL_ROOT=http://localhost + export FREELEAPS_DEVSVC_ENDPOINT=http://devsvc:8007/api/devsvc/ + export FREELEAPS_CHAT_ENDPOINT=http://freeleaps-chat:8012/api/chat/ + export FREELEAPS_CONTENT_ENDPOINT=http://content:8013/api/content/ + export FREELEAPS_NOTIFICATION_ENDPOINT=http://notification:8003/api/notification/ + export FREELEAPS_CENTRAL_STORAGE_ENDPOINT=http://central_storage:8005/api/central_storage/ + export FREELEAPS_AUTHENTICATION_ENDPOINT=http://authentication:8004/api/auth/ + export FREELEAPS_AILAB_ENDPOINT=https://localhost:8009/api/ + export KAFKA_SERVER_URL='' + export JWT_SECRET_KEY=8f87ca8c3c9c3df09a9c78e0adb0927855568f6072d9efc892534aee35f5867b + export EMAIL_FROM=freeleaps@freeleaps.com +EOFinner +else + # Online component environment variables + echo "==> [INIT] Use online component dev environment." + cat << 'EOFinner' > /home/devbox/freeleaps/apps/.env + export MONGODB_NAME=freeleaps2 + export MONGODB_PORT=27017 + export MONGODB_URI='mongodb+srv://jetli:8IHKx6dZK8BfugGp@freeleaps2.hanbj.mongodb.net/' + export RABBITMQ_HOSTNAME=freeleaps2 + export RABBITMQ_HOST=52.149.35.244 + export RABBITMQ_PORT=5672 + export FREELEAPS_ENV=dev + export STRIPE_API_KEY=sk_test_51Ogsw5B0IyqaSJBrwczlr820jnmvA1qQQGoLZ2XxOsIzikpmXo4pRLjw4XVMTEBR8DdVTYySiAv1XX53Zv5xqynF00GfMqttFd + export STRIPE_WEBHOOK_SECRET=whsec_S6ZWjSAdR5Cpsn2USH6ZRBqbdBIENjTC + export STRIPE_ACCOUNT_WEBHOOK_SECRET=whsec_PgPnkWGhEUiQfnV8aIb5Wmruz7XETJLm + export SITE_URL_ROOT=http://localhost/ + export FREELEAPS_DEVSVC_ENDPOINT=http://52.149.3.85:8007/api/devsvc/ + export FREELEAPS_CHAT_ENDPOINT=https://freeleaps-alpha.com/api/chat/ + export FREELEAPS_CONTENT_ENDPOINT=http://52.149.35.244:8013/api/content/ + export FREELEAPS_NOTIFICATION_ENDPOINT=http://52.149.35.244:8003/api/notification/ + export FREELEAPS_CENTRAL_STORAGE_ENDPOINT=http://52.149.35.244:8005/api/central_storage/ + export FREELEAPS_AUTHENTICATION_ENDPOINT=http://52.149.35.244:8004/api/auth/ + export FREELEAPS_AILAB_ENDPOINT=https://as010-w2-re-vm.mathmast.com:8009/api/ + export KAFKA_SERVER_URL='' + export EMAIL_FROM=freeleaps@freeleaps.com + export JWT_SECRET_KEY=ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0 +EOFinner +fi + + # Setting environment variables for frontend compilation + export VITE_API_URL='http://127.0.0.1:8002' + export VITE_WEBSOCKET_URL='http://127.0.0.1:8002' + if [[ "$USE_LOCAL_COMPONENT_VAL" == "true" ]]; then + export VITE_PROXY_WEBSOCKET_CHAT_URL='ws://localhost:8012' + export VITE_PROXY_API_CHAT_URL='http://localhost:8012' + else + export VITE_PROXY_WEBSOCKET_CHAT_URL='wss://freeleaps-alpha.com' + export VITE_PROXY_API_CHAT_URL='https://freeleaps-alpha.com' + fi + +if true ; then + # Load the environment variables + source /home/devbox/freeleaps/apps/.env + + # Create the logs directory if it does not exist + mkdir -p /home/devbox/logs + + # Install the net-tools package for ifconfig + sudo apt install net-tools -y + + ##################################### + # Initialize the backend environment, including Python3.11 and venv + ##################################### + echo "==> [INIT] Check Python3.11 environment..." + sudo apt update + sudo apt install python3.11 python3.11-venv -y + if ! command -v python3.11 &>/dev/null; then + echo "ERROR: Python3.11 is failed to install." + exit 1 + fi + + echo "==> [INIT] Upgrade pip and ensurepip..." + python3.11 -m ensurepip --upgrade + python3.11 -m pip install --upgrade pip + + # If the venv_t directory does not exist, create it + pushd /home/devbox/freeleaps/apps > /dev/null + + if [ ! -d "venv_t" ]; then + echo "==> [INIT] Create Python3.11 virtual environment..." + python3.11 -m venv venv_t || { echo "ERROR: Python3.11 virtual environment creation failed."; exit 1; } + sleep 5 + fi + popd > /dev/null + + ##################################### + # Initialize the frontend environment, including Node.js and npm + ##################################### + + pushd /home/devbox/freeleaps/frontend > /dev/null + + npm update + + # 1️⃣ Install pnpm globally + npm install -g pnpm + + # 2️⃣ Verify installation + pnpm --version + + # 3️⃣ Clean up old dependencies + rm -rf node_modules pnpm-lock.yaml + + # 4️⃣ Install dependencies (ensuring lockfile updates) + pnpm install --no-frozen-lockfile + + # 5️⃣ Build the project + npm run build + + # 6️⃣ Format the code (Optional) + npm run format + + echo "==> [INIT] Backend and frontend environment initialization completed." + +fi +EOF + +} + +stop_backend_service() { + echo "==> [BACKEND] Stopping backend service..." + devbox_container_id_file_path="${WORKING_HOME}/.devbox-instance" + DEVBOX_NAME=$(cat "$devbox_container_id_file_path") + + docker exec -i "$DEVBOX_NAME" bash < [BACKEND] Stopping backend service..." + if [ -f /home/devbox/.backend.pid ]; then + BACKEND_PID=\$(cat /home/devbox/.backend.pid) + echo "==> [BACKEND] Killing backend service PID: \$BACKEND_PID" + kill -9 \$BACKEND_PID + rm -f /home/devbox/.backend.pid + else + echo "==> [BACKEND] Backend service is not running." + fi +EOF + +} + +stop_frontend_service() { + echo "==> [FRONTEND] Stopping frontend service..." + devbox_container_id_file_path="${WORKING_HOME}/.devbox-instance" + DEVBOX_NAME=$(cat "$devbox_container_id_file_path") + + docker exec -i "$DEVBOX_NAME" bash < [FRONTEND] Stopping frontend service..." + if [ -f /home/devbox/.frontend.pid ]; then + FRONTEND_PID=\$(cat /home/devbox/.frontend.pid) + echo "==> [FRONTEND] Killing frontend service PID: \$FRONTEND_PID" + kill -9 \$FRONTEND_PID + rm -f /home/devbox/.frontend.pid + else + echo "==> [FRONTEND] Frontend service is not running." + fi +EOF + +} + + +############################################### +# Backend compilation and startup logic +############################################### +compile_backend_service() { + +echo "==> [BACKEND] start backend service at home path $WORKING_HOME." + +devbox_container_id_file_path="${WORKING_HOME}/.devbox-instance" +DEVBOX_NAME=$(cat "$devbox_container_id_file_path") + +devbox_backend_port_file_path="${WORKING_HOME}/.devbox-backend-port" +DEVBOX_BACKEND_PORT=$(cat "$devbox_backend_port_file_path") + +echo "==> [BACKEND] start backend service from $DEVBOX_NAME." + +docker exec -i "$DEVBOX_NAME" bash < /dev/null; then + echo "==> [BACKEND] Backend service is running with PID: \$backend_pid, if you want to restart, please stop it first or run devbox restart -e backend." + exit 0 + fi + fi + + echo "==> [BACKEND] Starting backend compilation and startup..." + + pushd /home/devbox/freeleaps/apps > /dev/null + + # Record the git status baseline before compilation + baseline_backend=\$(mktemp) + git status -s > "\$baseline_backend" + echo "==> [BACKEND] Recorded baseline before compilation: \$baseline_backend" + + + # CHeck if the virtual environment is created + if [ ! -f "venv_t/bin/activate" ]; then + echo "ERROR: The virtual environment cannot be created" + exit 1 + fi + + echo '============================================' + echo ' Start to activate virtual environment' + echo '============================================' + source venv_t/bin/activate + source /home/devbox/freeleaps/apps/.env + + # Verify the virtual environment is activated + if [[ "\$VIRTUAL_ENV" != "" ]]; then + echo "==> [BACKEND] Virtual environment activate: \$VIRTUAL_ENV" + else + echo "==> [BACKEND] ERROR: The virtual environment cannot be startup \$VIRTUAL_ENV" + exit 1 + fi + + # Check if it's the first time by verifying if the backend dependencies have been installed + if [ ! -f ".backend_deps_installed" ]; then + echo "==> [BACKEND] Install backend dependencies..." + pip install -r /home/devbox/freeleaps/apps/requirements.txt + touch .backend_deps_installed + echo "==> [BACKEND] Run backend service..." + ./start_webapi.sh > /home/devbox/logs/backend.logs 2>&1 & + else + echo "==> [BACKEND] Backend dependencies already installed. Skipping installation." + # Check if the backend service is already running + SERVICE_API_ACCESS_PORT=\$(cat /home/devbox/.devbox-backend-port) + uvicorn freeleaps.webapi.main:app --reload --host 0.0.0.0 --port \$SERVICE_API_ACCESS_PORT > /home/devbox/logs/backend.logs 2>&1 & + fi + + # Check the health of the backend service: poll to detect HTTP status + MAX_ATTEMPTS=30 + ATTEMPT=0 + + echo "==> [BACKEND] Checking backend service startup..." + while [ \$ATTEMPT -lt \$MAX_ATTEMPTS ]; do + echo "==> [BACKEND] backend url http://localhost:${DEVBOX_BACKEND_PORT}/docs" + HTTP_CODE=\$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${DEVBOX_BACKEND_PORT}/docs") + if [ "\$HTTP_CODE" -eq 200 ]; then + echo "==> [BACKEND] Service started successfully (HTTP \$HTTP_CODE)" + # Get the backend service PID by checking the process port with netstat -tulnp | grep ${DEVBOX_BACKEND_PORT} + BACKEND_PID=\$(netstat -tulnp 2>/dev/null | grep "${DEVBOX_BACKEND_PORT}" | awk '{print \$7}' | awk -F'/' '{print \$1}') + + echo "\$BACKEND_PID" > /home/devbox/.backend.pid + break + else + echo "==> [BACKEND] Waiting for backend service... Attempt \$((ATTEMPT+1)) with HTTP \$HTTP_CODE" + ATTEMPT=\$((ATTEMPT+1)) + sleep 5 + fi + done + if [ \$ATTEMPT -eq \$MAX_ATTEMPTS ]; then + echo "ERROR: Backend service startup failed." + exit 1 + fi + + # Restore git changes caused by compilation + current_backend=\$(mktemp) + git status -s > "\$current_backend" + git config --global --add safe.directory /home/devbox/freeleaps + echo "==> [BACKEND] Checking git changes after compilation..." + while read -r line; do + file=\$(echo "\$line" | awk '{print \$2}') + if ! grep -q "[[:space:]]\${file}$" "\$baseline_backend"; then + echo "==> [BACKEND] Restore file \$file" + git reset HEAD "\$file" + git checkout -- "\$file" + fi + done < "\$current_backend" + rm "\$baseline_backend" "\$current_backend" + + popd > /dev/null + echo "==> [BACKEND] Backend compilation and startup completed." +EOF +} + + +############################################### +# Frontend compilation and startup logic +############################################### +compile_frontend_service() { +echo "==> [FRONTEND] start frontend service at home path $WORKING_HOME." + +devbox_container_id_file_path="${WORKING_HOME}/.devbox-instance" +DEVBOX_NAME=$(cat "$devbox_container_id_file_path") + +DEVBOX_FRONTEND_PORT=$(cat "$WORKING_HOME/.devbox-frontend-port") + +docker exec -i "$DEVBOX_NAME" bash < /dev/null; then + echo "==> [FRONTEND] Frontend service is running with PID: \$frontend_pid, if you want to restart, please stop it first or run devbox restart -e frontend." + exit 0 + fi + fi + + echo "==> [FRONTEND] Starting frontend compilation and startup..." + + pushd /home/devbox/freeleaps/frontend > /dev/null + + # Record the git status baseline before compilation + baseline_frontend=\$(mktemp) + git status -s > "\$baseline_frontend" + echo "==> [FRONTEND] Recorded baseline before compilation: \$baseline_frontend" + + # Start the frontend service + echo "==> [FRONTEND] Start frontend service..." + nohup npm run dev > /home/devbox/logs/frontend.logs 2>&1 & + + # Check the health of the frontend service: poll to detect HTTP status + MAX_ATTEMPTS=30 + ATTEMPT=0 + echo "==> [FRONTEND] Checking frontend service startup..." + while [ \$ATTEMPT -lt \$MAX_ATTEMPTS ]; do + HTTP_CODE=\$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${DEVBOX_FRONTEND_PORT}/") + if [ "\$HTTP_CODE" -eq 200 ]; then + echo "==> [FRONTEND] Service started successfully (HTTP \$HTTP_CODE)" + + # Get the backend service PID by checking the process port with netstat -tulnp | grep ${DEVBOX_FRONTEND_PORT} + FRONTEND_PID=\$(netstat -tulnp 2>/dev/null | grep "${DEVBOX_FRONTEND_PORT}" | awk '{print \$7}' | awk -F'/' '{print \$1}') + + echo "\$FRONTEND_PID" > /home/devbox/.frontend.pid + break + else + echo "==> [FRONTEND] Waiting for frontend service... Attempt \$((ATTEMPT+1))" + ATTEMPT=\$((ATTEMPT+1)) + sleep 10 + fi + done + if [ \$ATTEMPT -eq \$MAX_ATTEMPTS ]; then + echo "ERROR: Frontend service startup failed." + exit 1 + fi + + # Restore git changes caused by compilation + current_frontend=\$(mktemp) + + git status -s > "\$current_frontend" + git config --global --add safe.directory /home/devbox/freeleaps + echo "==> [FRONTEND] Checking git changes after compilation..." + while read -r line; do + file=\$(echo "\$line" | awk '{print \$2}') + if ! grep -q "[[:space:]]\${file}$" "\$baseline_frontend"; then + echo "==> [FRONTEND] Restore file \$file" + git reset HEAD "\$file" + git checkout -- "\$file" + fi + done < "\$current_frontend" + rm "\$baseline_frontend" "\$current_frontend" + + popd > /dev/null + echo "==> [FRONTEND] Frontend compilation and startup completed." +EOF +} + + # :command.command_functions # :command.function devbox_init_command() { @@ -818,21 +1213,38 @@ devbox_init_command() { # 3.5 Check if the user has permission to write to WORKING_HOME if [[ -f "$WORKING_HOME/.devbox-instance" && -z "$FORCE_INIT" ]]; then - # Echo all start_components - if [[ "${#start_components[@]}" -gt 0 ]]; then - for component in "${start_components[@]}"; do - if [[ -f "$WORKING_HOME/.${component}-instance" ]]; then - echo "ERROR: Container named $component already exists. Use --force to remove it." - exit 1 - fi - done - else - echo "ERROR: Container named $DEVBOX_NAME already exists. Use --force to remove it." - exit 1 - fi + read -p "DevBox instance already exists. Do you want to force remove it? (y/N): " ans + case "$ans" in + [Yy]* ) + FORCE_INIT=true + ;; + * ) + echo "Aborting initialization." + exit 1 + ;; + esac + fi - + # Check if any component is running on the host when force init is not set + if [ -z "$FORCE_INIT" ]; then + running_containers=$(docker ps --format '{{.Names}}') + components_to_check=("devbox" "freeleaps2-gitea" "freeleaps2-mongodb" "freeleaps2-rabbitmq" "freeleaps2-redis" "devsvc" "notification" "content" "central_storage" "chat" "authentication") + for comp in "${components_to_check[@]}"; do + if echo "$running_containers" | grep -qx "$comp"; then + read -p "Container '$comp' is already running. Do you want to force remove it? (y/N): " ans + case "$ans" in + [Yy]* ) + FORCE_INIT=true + ;; + * ) + echo "ERROR: Container '$comp' is already running. Use --force to override." + exit 1 + ;; + esac + fi + done + fi local devbox_full_image="${DEVBOX_REPO}/${DEVBOX_IMAGE}:${DEVBOX_TAG}" @@ -868,6 +1280,20 @@ devbox_init_command() { fi fi + # If force init is set, remove the existing container + if [[ -n "$FORCE_INIT" ]]; then + echo "==> Removing existing container named $DEVBOX_NAME ..." + docker stop "$DEVBOX_NAME" &>/dev/null || true + docker rm "$DEVBOX_NAME" &>/dev/null || true + + # Remove .devbox-instance file + rm -f "$WORKING_HOME/.devbox-instance" + + # Remove .backend.pid .frontend.pid + rm -f "$WORKING_HOME/.backend.pid" + rm -f "$WORKING_HOME/.frontend.pid" + fi + # Create Docker network for DevBox container. TODO: if the network need to be configured in the docker-compose file add some logic to load it from the file DEVBOX_FREELEAPS2_NETWORK="devbox_freeleaps2-network" @@ -1164,9 +1590,16 @@ pushd $WORKING_HOME IS_START_FRONTEND=false + + # Make a user input (Y/N) to continue pull freeleaps.com code and start if N then exit read -p "Do you want to continue to pull freeleaps.com code and start the services? (Y/N): " user_input + +# Initialize the compile environment +init_compile_env + if [[ "$user_input" == "N" || "$user_input" == "n" ]]; then + # Echo as init job completed and exit echo echo "===========================================================" @@ -1183,262 +1616,14 @@ IS_START_FRONTEND=true # Run banckend service and frontend service in the container +compile_backend_service +compile_frontend_service + docker exec -i "$DEVBOX_NAME" bash < Using local components" - # Local components for Freeleaps services (devsvc, notification, content, central_storage, authentication) - cat << 'EOFinner' > /home/devbox/freeleaps/apps/.env - # Online endpoint info - export MONGODB_NAME=freeleaps2 - export MONGODB_URI=mongodb://freeleaps2-mongodb:27017/ - export MONGODB_PORT=27017 - export BLOB_STORE_CONNECTION_STR="DefaultEndpointsProtocol=https;AccountName=freeleaps1static;AccountKey=SIk7S3RviJxl1XhGiDZKA3cvzfxNrSbsBMfJ3EbKTsKPeMwhy8FTLpJliRLzQVE6uaSX8giDYw2h+ASt5MmHxQ==;EndpointSuffix=core.windows.net" - export RABBITMQ_HOSTNAME=freeleaps2 - export RABBITMQ_HOST=freeleaps2-rabbitmq - export RABBITMQ_PORT=5672 - export FREELEAPS_ENV=dev - export STRIPE_API_KEY=sk_test_51Ogsw5B0IyqaSJBrwczlr820jnmvA1qQQGoLZ2XxOsIzikpmXo4pRLjw4XVMTEBR8DdVTYySiAv1XX53Zv5xqynF00GfMqttFd - export STRIPE_WEBHOOK_SECRET=whsec_S6ZWjSAdR5Cpsn2USH6ZRBqbdBIENjTC - export STRIPE_ACCOUNT_WEBHOOK_SECRET=whsec_PgPnkWGhEUiQfnV8aIb5Wmruz7XETJLm - export SITE_URL_ROOT=http://localhost - export FREELEAPS_DEVSVC_ENDPOINT=http://devsvc:8007/api/devsvc/ - export FREELEAPS_CHAT_ENDPOINT=http://freeleaps-chat:8012/api/chat/ - export FREELEAPS_CONTENT_ENDPOINT=http://content:8013/api/content/ - export FREELEAPS_NOTIFICATION_ENDPOINT=http://notification:8003/api/notification/ - export FREELEAPS_CENTRAL_STORAGE_ENDPOINT=http://central_storage:8005/api/central_storage/ - export FREELEAPS_AUTHENTICATION_ENDPOINT=http://authentication:8004/api/auth/ - export FREELEAPS_AILAB_ENDPOINT=https://localhost:8009/api/ - export KAFKA_SERVER_URL='' - export JWT_SECRET_KEY=8f87ca8c3c9c3df09a9c78e0adb0927855568f6072d9efc892534aee35f5867b - export EMAIL_FROM=freeleaps@freeleaps.com -EOFinner -else - echo "==> Using online components" - cat << 'EOFinner' > /home/devbox/freeleaps/apps/.env - # Online endpoint info - export MONGODB_NAME=freeleaps2 - export MONGODB_PORT=27017 - export MONGODB_URI='mongodb+srv://jetli:8IHKx6dZK8BfugGp@freeleaps2.hanbj.mongodb.net/' - export RABBITMQ_HOSTNAME=freeleaps2 - export RABBITMQ_HOST=52.149.35.244 - export RABBITMQ_PORT=5672 - export FREELEAPS_ENV=dev - export STRIPE_API_KEY=sk_test_51Ogsw5B0IyqaSJBrwczlr820jnmvA1qQQGoLZ2XxOsIzikpmXo4pRLjw4XVMTEBR8DdVTYySiAv1XX53Zv5xqynF00GfMqttFd - export STRIPE_WEBHOOK_SECRET=whsec_S6ZWjSAdR5Cpsn2USH6ZRBqbdBIENjTC - export STRIPE_ACCOUNT_WEBHOOK_SECRET=whsec_PgPnkWGhEUiQfnV8aIb5Wmruz7XETJLm - export SITE_URL_ROOT=http://localhost/ - export FREELEAPS_DEVSVC_ENDPOINT=http://52.149.3.85:8007/api/devsvc/ - export FREELEAPS_CHAT_ENDPOINT=https://freeleaps-alpha.com/api/chat/ - export FREELEAPS_CONTENT_ENDPOINT=http://52.149.35.244:8013/api/content/ - export FREELEAPS_NOTIFICATION_ENDPOINT=http://52.149.35.244:8003/api/notification/ - export FREELEAPS_CENTRAL_STORAGE_ENDPOINT=http://52.149.35.244:8005/api/central_storage/ - export FREELEAPS_AUTHENTICATION_ENDPOINT=http://52.149.35.244:8004/api/auth/ - export FREELEAPS_AILAB_ENDPOINT=https://as010-w2-re-vm.mathmast.com:8009/api/ - export KAFKA_SERVER_URL='' - export EMAIL_FROM=freeleaps@freeleaps.com - export JWT_SECRET_KEY=ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0 -EOFinner -fi - -# Effect the environment variables in the current shell - -source /home/devbox/freeleaps/apps/.env - -# Ensure /home/devbox/logs exists -mkdir -p /home/devbox/logs - -# Start WebAPI service -echo "Starting WebAPI service..." -pushd /home/devbox/freeleaps/apps -cp /home/devbox/freeleaps/backend_env.sh /home/devbox/freeleaps/apps/backend_env.sh - -# 5. Istall python3.11 and venv module -echo "5. Istall python3.11 and venv module" -sudo apt update -sudo apt install python3.11 python3.11-venv -y - -# make sore python3.11 is installed -if ! command -v python3.11 &>/dev/null; then - echo "ERROR: Python3.11 is not installed." - exit 1 -fi - -# Upgrade pip and install virtualenv -echo "7. Upgrade pip and install virtualenv" -python3.11 -m ensurepip --upgrade -python3.11 -m pip install --upgrade pip - -# 8. Create and activate a virtual environment -echo "8. Create and activate a virtual environment" -python3.11 -m venv venv_t - -sleep 5 - -# CHeck if the virtual environment is created -if [ ! -f "venv_t/bin/activate" ]; then - echo "ERROR: The virtual environment cannot be created" - exit 1 -fi - -echo '============================================' -echo ' Start to activate virtual environment' -echo '============================================' -source venv_t/bin/activate -source /home/devbox/freeleaps/apps/.env - -# Verify the virtual environment is activated -if [[ "\$VIRTUAL_ENV" != "" ]]; then - echo "Virtual environment activate: \$VIRTUAL_ENV" -else - echo "ERROR: The virtual environment cannot be startup \$VIRTUAL_ENV" - exit 1 -fi - - -echo '============================================' -echo ' Install requirements' -echo '============================================' -pip install -r /home/devbox/freeleaps/apps/requirements.txt - - - -echo '============================================' -echo 'Start backend service locally' -echo '============================================' -./start_webapi.sh > /home/devbox/logs/backend.logs 2>&1 & -BACKEND_PID=\$! - -# Save BACKEND_PID to a file \${WORKING_HOME}/.backend.pid: Stores the process id of backend process. -echo "\$BACKEND_PID" > /home/devbox/.backend.pid - -echo '============================================' -echo 'Check if the WebAPI service started successfully' -echo '============================================' - -sleep 30 - -# 30 attempts, 5 seconds each, total wait time 2.5 minutes -MAX_ATTEMPTS=30 -ATTEMPT=0 - -echo "Waiting for WebAPI service to become healthy..." - -while [ \$ATTEMPT -lt \$MAX_ATTEMPTS ]; do - HTTP_CODE=\$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\$DEVBOX_BACKEND_PORT/docs") - # Check HTTP Code 200 - if [ "\$HTTP_CODE" -eq 200 ]; then - echo "Backend Swagger UI is available at \$URL (HTTP \$HTTP_CODE)" - break - else - echo "Waiting for Swagger UI to become available... Attempt \$((ATTEMPT+1))" - ATTEMPT=\$((ATTEMPT+1)) - sleep 5 # Wait 5 seconds - fi -done - -if [ \$ATTEMPT -eq \$MAX_ATTEMPTS ]; then - echo "ERROR: WebAPI failed to start after \$MAX_ATTEMPTS attempts" - exit 1 -fi - -rm -rf /home/devbox/freeleaps/apps/backend_env.sh || true - -echo "WebAPI service started successfully" - - -echo '============================================' -echo ' Start frontend service locally' -echo '============================================' -pushd /home/devbox/freeleaps/frontend - -# start the frontend service -export VITE_API_URL='http://127.0.0.1:8002' -export VITE_WEBSOCKET_URL='http://127.0.0.1:8002' - -if [[ \$USE_LOCAL_COMPONENT_VAL == true ]]; then - export VITE_PROXY_WEBSOCKET_CHAT_URL='ws://localhost:8012' - export VITE_PROXY_API_CHAT_URL='http://localhost:8012' -else - export VITE_PROXY_WEBSOCKET_CHAT_URL='wss://freeleaps-alpha.com' - export VITE_PROXY_API_CHAT_URL='https://freeleaps-alpha.com' -fi - - -npm update - -# 1️⃣ Install pnpm globally -npm install -g pnpm - -# 2️⃣ Verify installation -pnpm --version - -# 3️⃣ Clean up old dependencies -rm -rf node_modules pnpm-lock.yaml - -# 4️⃣ Install dependencies (ensuring lockfile updates) -pnpm install --no-frozen-lockfile - -# 5️⃣ Build the project -npm run build - -# 6️⃣ Format the code (Optional) -npm run format -# Start the frontend service with nohup in order to keep it running after the SSH session is closed. Save the process ID of the frontend service -nohup npm run dev > /home/devbox/logs/frontend.logs 2>&1 & -FRONTEND_PID=\$! - -echo "npm run dev has been started with PID: \$FRONTEND_PID" -echo "\$FRONTEND_PID" > /home/devbox/.frontend.pid - -echo '============================================' - -# Wait for the frontend service to start -sleep 30 - -# 30 attempts, 10 seconds each, total wait time 5 minutes -MAX_ATTEMPTS=30 -ATTEMPT=0 - -echo "Waiting for Frontend service to start..." - -while [ \$ATTEMPT -lt \$MAX_ATTEMPTS ]; do - HTTP_CODE=\$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\$DEVBOX_FRONTEND_PORT/") - # Check HTTP Code 200 - if [ "\$HTTP_CODE" -eq 200 ]; then - echo "Frontend is available (HTTP \$HTTP_CODE)" - break - else - echo "Waiting for Frontend to become available... (http://localhost:\$DEVBOX_FRONTEND_PORT), (HTTP \$HTTP_CODE) Attempt \$((ATTEMPT+1))" - ATTEMPT=\$((ATTEMPT+1)) - sleep 10 - fi -done - -if [ \$ATTEMPT -eq \$MAX_ATTEMPTS ]; then - echo "ERROR: Frontend failed to start after \$MAX_ATTEMPTS attempts" - exit 1 -fi - - +echo "==> Reset git changes caused by compilation" pushd /home/devbox/freeleaps git config --global --add safe.directory /home/devbox/freeleaps git reset --hard - - -echo "Freeleaps services started successfully" EOF # ------------------------------------------------------------------- @@ -1636,8 +1821,6 @@ devbox_start_command() { export START_BACKEND=false fi - echo "FREELEAPS_ENDPOINT: $FREELEAPS_ENDPOINT, START_FRONTEND: $START_FRONTEND, START_BACKEND: $START_BACKEND" - # Check if the DevBox container is running local devbox_container_id_file_path="${WORKING_HOME}/.devbox-instance" if [[ ! -f "$devbox_container_id_file_path" ]]; then @@ -1683,7 +1866,6 @@ devbox_start_command() { fi fi - # Start the specified components for comp in "${COMPONENTS[@]}"; do case "$comp" in @@ -1716,194 +1898,104 @@ devbox_start_command() { done -# Check if $FREELEAPS_ENDPOINT is not empty and start the frontend and backend services -if [[ "$FREELEAPS_ENDPOINT" != "" ]]; then - # Sleep for 10 seconds to allow the services to start and echo 10 seconds increase from 1 to 10 in each second - for i in {1..20}; do + # Check if $FREELEAPS_ENDPOINT is not empty and start the frontend and backend services + if [[ "$FREELEAPS_ENDPOINT" != "" ]]; then + # Sleep for 10 seconds to allow the services to start and echo 10 seconds increase from 1 to 10 in each second + for i in {1..20}; do + if docker ps --no-trunc --format '{{.ID}}' | grep -q "^${devbox_container_id}\$"; then + break + fi + echo -n "-" + sleep 1 + done + if docker ps --no-trunc --format '{{.ID}}' | grep -q "^${devbox_container_id}\$"; then - break + echo "==> Starting Freeleaps frontend and backend services..." + # Check if start backend service + if [[ "${START_BACKEND}" == "true" ]]; then + compile_backend_service fi - echo -n "-" - sleep 1 - done - if docker ps --no-trunc --format '{{.ID}}' | grep -q "^${devbox_container_id}\$"; then - echo "==> Starting Freeleaps frontend and backend services..." - # Start the backend and frontend services - docker exec -i "$devbox_container_id" bash < /home/devbox/logs/backend.logs 2>&1 & - BACKEND_PID=\$! - - # Save BACKEND_PID to a file \${WORKING_HOME}/.backend.pid: Stores the process id of backend process. - echo "\$BACKEND_PID" > /home/devbox/.backend.pid - - # Check if the backend service started successfully - sleep 10 - if ! ps -p "\$BACKEND_PID" &>/dev/null; then - echo "ERROR: Backend service failed to start." - exit 1 - fi - - # Test the backend service - echo "Testing backend service..." - - attempt=0 - max_attempts=10 - while [ \$attempt -lt \$max_attempts ]; do - http_code=\$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\$SERVICE_API_ACCESS_PORT/docs") - if [ "\$http_code" -eq 200 ]; then - break - fi - attempt=\$((attempt+1)) - sleep 5 - done - - if [ \$attempt -eq \$max_attempts ]; then - echo "ERROR: Backend service is not available after \$max_attempts attempts." - exit 1 - fi - - echo "Backend service is available (HTTP \$http_code)" -fi - -# Start the frontend service -if [[ "${START_FRONTEND}" == "true" ]]; then - - echo '============================================' - echo ' Start frontend service locally' - echo '============================================' - pushd /home/devbox/freeleaps/frontend - - # echo "==> Starting frontend service..." - baseline_file=\$(mktemp) - echo "==> Creating a baseline file for the frontend service..." - git status -s > "\$baseline_file" - echo "==> Baseline file created: \$baseline_file" - - - # Check if the frontend service is already running according to the package.json and pnpm-lock.yaml files timestamps - if [[ ! -d "node_modules" || "package.json" -nt "node_modules" || "pnpm-lock.yaml" -nt "node_modules" ]]; then - echo "==> Installing/Updating frontend dependencies..." - - # Clean up old dependencies - rm -rf node_modules - - # Install dependencies - pnpm install --prefer-offline || { - echo "ERROR: Failed to install dependencies" - exit 1 - } - fi - - npm run dev > /home/devbox/logs/frontend.logs 2>&1 & - FRONTEND_PID=\$! - - echo "\$FRONTEND_PID" > /home/devbox/.frontend.pid - - # Check if the frontend service started successfully - sleep 10 - if ! ps -p "\$FRONTEND_PID" &>/dev/null; then - echo "ERROR: Frontend service failed to start." - exit 1 - fi - - - # Test the frontend service - WEB_APP_ACCESS_PORT=\$(cat /home/devbox/.devbox-frontend-port) - - - echo "Testing frontend service... PORT: \$WEB_APP_ACCESS_PORT" - attempt=0 - max_attempts=10 - while [ \$attempt -lt \$max_attempts ]; do - HTTP_CODE=\$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\$WEB_APP_ACCESS_PORT/") - echo "HTTP_CODE: \$HTTP_CODE" - # Check HTTP Code 200 - if [ "\$HTTP_CODE" -eq 200 ]; then - echo "Frontend is available (HTTP \$HTTP_CODE)" - break + # Start the frontend service + if [[ "${START_FRONTEND}" == "true" ]]; then + compile_frontend_service + fi else - echo "Attempt \$((attempt+1)): Frontend not available (HTTP \$HTTP_CODE). Waiting..." - attempt=\$((attempt+1)) - sleep 10 + echo "ERROR: DevBox container is not running." + exit 1 fi - done - - if [ \$attempt -eq \$max_attempts ]; then - echo "ERROR: Frontend service is not available after \$max_attempts attempts." - exit 1 fi - current_file=\$(mktemp) - git status -s > "\$current_file" - - echo "==> Checking for modified files in the frontend service..." - while read -r line; do - # Print the file name - echo "\$line" - file=\$(echo "\$line" | awk '{print \$2}') - echo "File: \$file" - # Check if the file is not in the baseline file - if ! grep -q "[[:space:]]\$file\$" "\$baseline_file"; then - echo "==> File \$file has been modified. Resetting..." - git reset HEAD "\$file" - git checkout -- "\$file" - fi - done < \$current_file - - # Remove the temporary files - rm "\$baseline_file" "\$current_file" -fi - -echo "Freeleaps services started successfully" -EOF - else - echo "ERROR: DevBox container is not running." - exit 1 - fi -fi - echo "==> DevBox services started successfully." } # :command.function devbox_stop_command() { - echo "==> Stopping DevBox services..." local COMPONENT="$(get_arg '--component')" local WORKING_HOME="$(get_arg '--working-home' "${HOME}/devbox")" + local FREELEAPS_ENDPOINT="$(get_arg '--freeleaps-endpoint')" + + if [[ "$FREELEAPS_ENDPOINT" == "all" ]]; then + export STOP_FRONTEND=true + export STOP_BACKEND=true + elif [[ "$FREELEAPS_ENDPOINT" == "frontend" ]]; then + export STOP_FRONTEND=true + export STOP_BACKEND=false + elif [[ "$FREELEAPS_ENDPOINT" == "backend" ]]; then + export STOP_FRONTEND=false + export STOP_BACKEND=true + else + # Default behavior can be adjusted if needed + export STOP_FRONTEND=false + export STOP_BACKEND=false + fi + + if [[ -f "${WORKING_HOME}/.devbox-instance" ]]; then + local devbox_container_id + devbox_container_id=$(cat "${WORKING_HOME}/.devbox-instance") + + # If the DevBox container is not running, exit + if ! docker ps --no-trunc --format '{{.ID}}' | grep -q "^${devbox_container_id}\$"; then + echo "==> DevBox container is not running." + exit 1 + fi + + # Check if STOP_BACKEND is true, stop the backend service + if [[ "${STOP_BACKEND}" == "true" ]]; then + if [[ -f "${WORKING_HOME}/.backend.pid" ]]; then + stop_backend_service + else + echo "==> Backend service is not running." + fi + fi + + # Check if STOP_FRONTEND is true, stop the frontend service + if [[ "${STOP_FRONTEND}" == "true" ]]; then + if [[ -f "${WORKING_HOME}/.frontend.pid" ]]; then + stop_frontend_service + else + echo "==> Frontend service is not running." + fi + fi + fi + + # if any of STOP_FRONTEND and STOP_BACKEND is true, then completed stop process + if [[ "${STOP_FRONTEND}" == "true" || "${STOP_BACKEND}" == "true" ]]; then + + stoped_freeleaps_service_names=() + if [[ "${STOP_FRONTEND}" == "true" ]]; then + stoped_freeleaps_service_names+=("frontend") + fi + + if [[ "${STOP_BACKEND}" == "true" ]]; then + stoped_freeleaps_service_names+=("backend") + fi + + echo "==> Stopped Freeleaps services: ${stoped_freeleaps_service_names[@]} successfully." + + exit 0 + fi + # If the DevBox container is not running, exit if [[ -z "$COMPONENT" ]]; then @@ -2063,21 +2155,53 @@ devbox_restart_command() { local WORKING_HOME="$(get_arg '--working-home' "${HOME}/devbox")" local FREELEAPS_ENDPOINT="$(get_arg '--freeleaps-endpoint')" + # Check if --freeleaps-endpoint is not empty and start the frontend and backend services + restart_services=() if [[ "$FREELEAPS_ENDPOINT" == "all" ]]; then export START_FRONTEND=true export START_BACKEND=true + restart_services=("frontend" "backend") elif [[ "$FREELEAPS_ENDPOINT" == "frontend" ]]; then export START_FRONTEND=true export START_BACKEND=false + restart_services=("frontend") elif [[ "$FREELEAPS_ENDPOINT" == "backend" ]]; then export START_FRONTEND=false export START_BACKEND=true + restart_services=("backend") else # Default behavior can be adjusted if needed export START_FRONTEND=false export START_BACKEND=false fi - + + # Check if --freeleaps-endpoint is not empty and start the frontend and backend services + if [[ "$FREELEAPS_ENDPOINT" != "" ]]; then + + devbox_container_id=$(cat "${WORKING_HOME}/.devbox-instance") + echo "docker ps --no-trunc --format ${devbox_container_id} " + if docker ps --no-trunc --format '{{.ID}}' | grep -q "${devbox_container_id}\$"; then + echo "==> Starting Freeleaps frontend and backend services..." + # Check if start backend service + if [[ "${START_BACKEND}" == "true" ]]; then + stop_backend_service + compile_backend_service + fi + + # Start the frontend service + if [[ "${START_FRONTEND}" == "true" ]]; then + stop_frontend_service + compile_frontend_service + fi + else + echo "ERROR: DevBox container is not running." + exit 1 + fi + + echo "==> Freeleaps $restart_services services restarted successfully." + exit 0 + fi + # Check devbox container file path local devbox_container_id_file_path="${WORKING_HOME}/.devbox-instance" if [[ ! -f "$devbox_container_id_file_path" ]]; then @@ -2166,175 +2290,6 @@ devbox_restart_command() { esac done -# Check if $FREELEAPS_ENDPOINT is not empty and start the frontend and backend services -if [[ "$FREELEAPS_ENDPOINT" != "" ]]; then - # Sleep for 10 seconds to allow the services to start and echo 10 seconds increase from 1 to 10 in each second - for i in {1..20}; do - if docker ps --no-trunc --format '{{.ID}}' | grep -q "^${devbox_container_id}\$"; then - break - fi - echo -n "-" - sleep 1 - done - # Start the backend and frontend services - docker exec -i "$devbox_container_id" bash < /home/devbox/logs/backend.logs 2>&1 & - BACKEND_PID=\$! - - # Save BACKEND_PID to a file \${WORKING_HOME}/.backend.pid: Stores the process id of backend process. - echo "\$BACKEND_PID" > /home/devbox/.backend.pid - - # Check if the backend service started successfully - sleep 10 - if ! ps -p "\$BACKEND_PID" &>/dev/null; then - echo "ERROR: Backend service failed to start." - exit 1 - fi - - # Test the backend service - echo "Testing backend service..." - - attempt=0 - max_attempts=10 - while [ \$attempt -lt \$max_attempts ]; do - http_code=\$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\$SERVICE_API_ACCESS_PORT/docs") - if [ "\$http_code" -eq 200 ]; then - break - fi - attempt=\$((attempt+1)) - sleep 5 - done - - if [ \$attempt -eq \$max_attempts ]; then - echo "ERROR: Backend service is not available after \$max_attempts attempts." - exit 1 - fi - - echo "Backend service is available (HTTP \$http_code)" -fi - - # Start the frontend service -if [[ "${START_FRONTEND}" == "true" ]]; then - - echo '============================================' - echo ' Start frontend service locally' - echo '============================================' - pushd /home/devbox/freeleaps/frontend - - # echo "==> Starting frontend service..." - baseline_file=\$(mktemp) - echo "==> Creating a baseline file for the frontend service..." - git status -s > "\$baseline_file" - echo "==> Baseline file created: \$baseline_file" - - # Check if the frontend service is already running according to the package.json and pnpm-lock.yaml files timestamps - if [[ ! -d "node_modules" || "package.json" -nt "node_modules" || "pnpm-lock.yaml" -nt "node_modules" ]]; then - echo "==> Installing/Updating frontend dependencies..." - - # Clean up old dependencies - rm -rf node_modules - - # Install dependencies - pnpm install --prefer-offline || { - echo "ERROR: Failed to install dependencies" - exit 1 - } - fi - - npm run dev > /home/devbox/logs/frontend.logs 2>&1 & - FRONTEND_PID=\$! - - echo "\$FRONTEND_PID" > /home/devbox/.frontend.pid - - # Check if the frontend service started successfully - sleep 10 - if ! ps -p "\$FRONTEND_PID" &>/dev/null; then - echo "ERROR: Frontend service failed to start." - exit 1 - fi - - - # Test the frontend service - WEB_APP_ACCESS_PORT=\$(cat /home/devbox/.devbox-frontend-port) - - echo "Testing frontend service... PORT: \$WEB_APP_ACCESS_PORT" - attempt=0 - max_attempts=10 - while [ \$attempt -lt \$max_attempts ]; do - http_code=\$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:\$WEB_APP_ACCESS_PORT/") - if [ "\$http_code" -eq 200 ]; then - echo "Frontend service is available (HTTP \$http_code)" - break - else - echo "Attempt \$((attempt+1)): Frontend not available (HTTP \$http_code). Waiting..." - attempt=\$((attempt+1)) - sleep 10 - fi - done - - if [ \$attempt -eq \$max_attempts ]; then - echo "ERROR: Frontend service is not available after \$max_attempts attempts." - exit 1 - fi - - current_file=\$(mktemp) - git status -s > "\$current_file" - - echo "==> Checking for modified files in the frontend service..." - while read -r line; do - # Print the file name - echo "\$line" - file=\$(echo "\$line" | awk '{print \$2}') - echo "File: \$file" - # Check if the file is not in the baseline file - if ! grep -q "[[:space:]]\$file\$" "\$baseline_file"; then - echo "==> File \$file has been modified. Resetting..." - git reset HEAD "\$file" - git checkout -- "\$file" - fi - done < \$current_file - - # Remove the temporary files - rm "\$baseline_file" "\$current_file" -fi - -echo "Freeleaps services started successfully" -EOF - fi - echo "==> DevBox services restarted successfully." } @@ -2422,6 +2377,7 @@ parse_requirements() { devbox_init_parse_requirements "$@" shift $# ;; + *) # pass * parameter to devbox_init_guidance devbox_init_guidance $action >&2 @@ -3002,6 +2958,19 @@ devbox_stop_parse_requirements() { fi ;; + --freeleaps-endpoint | -e) + + # :flag.case_arg + if [[ -n ${2+x} ]]; then + add_arg '--freeleaps-endpoint' "$2" + shift + shift + else + printf "%s\n" "--freeleaps-endpoint requires an argument: --freeleaps-endpoint all/backend/frontend" >&2 + exit 1 + fi + ;; + -?*) printf "invalid option: %s\n" "$key" >&2 exit 1