๐Ÿ“ฆ juspay / hyperswitch-cdk

๐Ÿ“„ install.sh ยท 725 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725#! /usr/bin/env bash

# Setting up color and style variables
bold=$(tput bold)
blue=$(tput setaf 4)
green=$(tput setaf 2)
yellow=$(tput setaf 3)
red=$(tput setaf 1)
reset=$(tput sgr0)
source ./bash/utils.sh
function echoLog() {
    echo "$1" | tee -a $LOG_FILE
}

# Function to display error messages in red
display_error() {
    echo "${bold}${red}$1${reset}"
}

if [[ -z "$AWS_ACCESS_KEY_ID" || -z "$AWS_SECRET_ACCESS_KEY" ]]; then
    display_error "Missing AWS credentials. Please configure the AWS CLI with your credentials."
    exit 1
fi

if [[ -z "$AWS_SESSION_TOKEN" ]]; then
    echo "${bold}${yellow}Missing AWS session token. If you are using 2FA, please export the temporary session token as the AWS_SESSION_TOKEN environment variable."
    echo "Proceeding anyway...${reset}"
    sleep 2
fi
function box_out() {
    local s=("$@") b w padding terminalWidth
    for l in "${s[@]}"; do
        ((w < ${#l})) && {
            b="$l"
            w="${#l}"
        }
    done
    terminalWidth=$(tput cols)               # Get the terminal width
    padding=$(((terminalWidth - w - 4) / 2)) # Calculate padding; subtract 4 for the box borders

    tput bold
    tput setaf 2
    printf "%*s" $padding "" # Add padding before the top line
    echo " ${b//?/ } "
    for l in "${s[@]}"; do
        printf "%*s" $padding "" # Add padding before each line within the box
        printf " %s%*s%s " "$(tput sgr 0)$(tput bold)" "-$w" "$l" "$(tput bold)$(tput setaf 2)"
        echo # New line
    done
    tput sgr 0
}

function box_out_with_hyphen() {
    local s=("$@") b w padding terminalWidth
    for l in "${s[@]}"; do
        ((w < ${#l})) && {
            b="$l"
            w="${#l}"
        }
    done
    terminalWidth=$(tput cols)               # Get the terminal width
    padding=$(((terminalWidth - w - 4) / 2)) # Calculate padding; subtract 4 for the box borders

    tput bold
    tput setaf 2
    printf "%*s" $padding "" # Add padding before the top line
    echo " -${b//?/-}-"
    printf "%*s" $padding "" # Add padding before the second line
    echo "| ${b//?/ } |"
    for l in "${s[@]}"; do
        printf "%*s" $padding "" # Add padding before each line within the box
        printf "| %s%*s%s |" "$(tput sgr 0)$(tput bold)" "-$w" "$l" "$(tput bold)$(tput setaf 2)"
        echo # New line
    done
    printf "%*s" $padding "" # Add padding before the bottom second line
    echo "| ${b//?/ } |"
    printf "%*s" $padding "" # Add padding before the bottom line
    echo " -${b//?/-}-"
    tput sgr 0
}
bash ./bash/deps.sh
echo "Dependency installation completed."

fetch_details() {
    # Trying to retrieve AWS account owner's details
    if ! AWS_ACCOUNT_DETAILS_JSON=$(aws sts get-caller-identity 2>&1); then
        display_error "Unable to obtain AWS caller identity: $AWS_ACCOUNT_DETAILS_JSON"
        display_error "Check if your AWS credentials are expired and you have appropriate permissions."
        exit 1
    fi

    # Extracting and displaying account details
    AWS_ACCOUNT_ID=$(echo "$AWS_ACCOUNT_DETAILS_JSON" | jq -r '.Account')
    AWS_USER_ID=$(echo "$AWS_ACCOUNT_DETAILS_JSON" | jq -r '.UserId')
    AWS_ARN=$(echo "$AWS_ACCOUNT_DETAILS_JSON" | jq -r '.Arn')
    AWS_ROLE=$(aws sts get-caller-identity --query 'Arn' --output text | cut -d '/' -f 2)
}

show_loader "Fetching AWS account details" &
fetch_details

# Waiting for the fetch_details background process to complete
wait

# Check if fetch_details exited with an error
if [ $? -ne 0 ]; then
    echo "Error fetching AWS details. Exiting script."
    exit 1
fi

# Displaying AWS account information in a "box"
echo
box_out_with_hyphen "AWS Account Information:" "" "Account ID: $AWS_ACCOUNT_ID" "User ID: $AWS_USER_ID" "Role: $AWS_ROLE"
echo
# Ask consent to proceed with the aws account
while true; do
    read -r -p "Do you want to proceed with the above AWS account? [y/n]: " yn
    case $yn in
    [Yy]*)
        echo "Proceeding with AWS account $AWS_ACCOUNT_ID"
        break
        ;;
    [Nn]*)
        echo "Exiting..."
        exit
        ;;
    *) echo "Please answer yes or no [y/n]." ;;
    esac
done

# Function to display the header
echo "Checking dependencies..."

# Check for Node.js
if ! command -v node &>/dev/null; then
    echo "Node.js could not be found. Please install node js 18 or above."
    exit 1
fi

# Verify Node.js version
version=$(node -v | cut -d'.' -f1 | tr -d 'v')
if [ "$version" -lt 18 ]; then
    echo "Invalid Node.js version. Expected 18 or above, but got $version."
    exit 1
fi
echo "Node.js version is valid."

# Function to list available services
list_services() {
    box_out_with_hyphen "   Welcome to Hyperswitch Services Installer" "" "" "Hyperswitch Services Available for Installation:" "              1. Backend Services" "              2. Demo Store" "              3. Control Center" "              4. Card Vault" "              5. SDK"
}

INSTALLATION_MODE=1
# Function to show installation options
show_install_options() {
    echo
    echo "${bold}Choose an installation option:${reset}"
    echo "${bold}${green}1. Free Tier ${reset} - ${bold}${blue}Only for prototyping and not scalable. Falls under AWS Free Tier. ${reset}"
    echo "${bold}${green}2. Production Ready ${reset} - ${bold}${blue}Optimized for scalability and performance, leveraging the power of AWS EKS for robust, enterprise-grade deployments.${reset}"
}

declare base_ami,envoy_ami,squid_ami

check_image_builder() {
    ssm=$(aws ssm get-parameters \
            --names base_image_ami squid_image_ami envoy_image_ami \
            --query "Parameters[*].{Name:Name,Value:Value}" --output json)

    length=$(echo "$ssm" | jq -r ".|length")

    if [ "$length" -lt 3 ]; then
        display_error "Unable to find base images for Proxy Servers. Please run the following command: bash deploy_imagebuilder.sh\nIf you have done it already please wait for 15-20 mins until it builds the images"
        exit 1
    fi

    base_ami=$(echo "$ssm" | jq -r '.[]|select(.Name=="base_image_ami")|.Value')
    envoy_ami=$(echo "$ssm" | jq -r '.[]|select(.Name=="envoy_image_ami")|.Value')
    squid_ami=$(echo "$ssm" | jq -r '.[]|select(.Name=="squid_image_ami")|.Value')

}

# Function to read user input until a valid choice is made
get_user_choice() {
    while true; do
        read -r -p "Enter your choice [1-2]: " INSTALLATION_MODE
        case $INSTALLATION_MODE in
        1)
            echo "Free Tier option selected."
            break
            ;;
        2)
            echo "Production Ready option selected."
            break
            ;;
        *) echo "Invalid choice. Please enter 1 or 2." ;;
        esac
    done
}

clear
list_services
echo
show_install_options
get_user_choice

check_if_element_is_preset_in_array() {
    local e match="$1"
    shift
    for e; do [[ "$e" == "$match" ]] && return 0; done
    return 1
}

if [[ -z "$AWS_DEFAULT_REGION" ]]; then
    read -p "Please enter the AWS region to deploy the services: " AWS_DEFAULT_REGION
else
    read -p "Please enter the AWS region to deploy the services (Press enter to keep the current region $blue$bold$AWS_DEFAULT_REGION$reset): " input_region
    if [[ -n "$input_region" ]]; then
        AWS_DEFAULT_REGION=$input_region
    fi
fi

export AWS_DEFAULT_REGION;
# Prompt for region and check if it's enabled
while true; do

    AVAILABLE_REGIONS_JSON=$(aws ec2 describe-regions --query 'Regions[].RegionName' --output text 2>&1)

    if [[ $AVAILABLE_REGIONS_JSON == *"UnauthorizedOperation"* ]]; then
        display_error "Error: Unauthorized operation. You do not have permission to perform 'ec2:DescribeRegions'."
        display_error "Contact your AWS administrator to obtain the necessary permissions."
        exit 1
    elif [[ $AVAILABLE_REGIONS_JSON == *"supported format"* ]]; then
        display_error "Error: Invalid region format. Please enter a valid region code (e.g. us-east-1)."
    else
        # Convert the region list into an array
        AVAILABLE_REGIONS=($AVAILABLE_REGIONS_JSON)

        # Check if AWS_DEFAULT_REGION is in the list of available regions
        if [[ " ${AVAILABLE_REGIONS[*]} " =~ " $AWS_DEFAULT_REGION " ]]; then
            echo "Region $AWS_DEFAULT_REGION is enabled for your account."
            break
        else
            display_error "Error: Region $AWS_DEFAULT_REGION is not enabled for your account or invalid region code."
        fi
    fi

    # Prompt for region again
    read -p "Please enter the AWS region to deploy the services: " AWS_DEFAULT_REGION

done

echo
printf "${bold}Checking neccessary permissions${reset}\n"
echo

check_root_user() {
    AWS_ARN=$(aws sts get-caller-identity --output json | jq -r .Arn)
    if [[ $AWS_ARN == *":root"* ]]; then
        echo "ROOT user is not recommended. Please create a new user with AdministratorAccess and use their Access Token."
        exit 1
    fi
}

REQUIRED_POLICIES=("AdministratorAccess") # Add other necessary policies to this array
# Check if the current user is a root user
echo "Verifying that you're not using the AWS root account..."
echo "(For security reasons, it's best to avoid using the root account.)"
(check_root_user) &
show_loader "Verifying root user status"

check_iam_policies() {
    USER_POLICIES=$(aws iam list-attached-role-policies --role-name "$AWS_ROLE" --output json | jq -r '.AttachedPolicies[].PolicyName')
    for policy in "${REQUIRED_POLICIES[@]}"; do
        if ! echo "$USER_POLICIES" | grep -q "$policy"; then
            echo "Required policy $policy is not attached to your user. Please attach this policy."
            exit 1
        fi
    done
    echo "All necessary permissions are in place."
}

# Check for specific IAM policies
echo "Checking for necessary IAM policies..."
(check_iam_policies) &
show_loader "Verifying IAM policies"

echo
printf "${bold}Configure Credentials of the Application${reset}\n"
echo

validate_password() {
    local password=$1

    # Check length (at least 8 characters)
    if [[ ${#password} -lt 8 ]]; then
        display_error "Error: Password must be at least 8 characters."
        return 1
    fi

    # Check if it starts with an alphabet
    if [[ ! $password =~ ^[A-Za-z] ]]; then
        display_error "Error: Password must start with a letter."
        return 1
    fi

    # Check for at least one uppercase letter and one lowercase letter
    if [[ ! $password =~ [A-Z] || ! $password =~ [a-z] ]]; then
        display_error "Error: Password must include at least one uppercase and one lowercase letter."
        return 1
    fi

    # Check for at least one digit
    if [[ ! $password =~ [0-9] ]]; then
        display_error "Error: Password must include at least one digit."
        return 1
    fi

    # Check for forbidden special characters
    if [[ $password =~ [^A-Za-z0-9] ]]; then
        display_error "Error: Password cannot include special characters."
        return 1
    fi

    # read password again to confirm
    echo "Please re-enter the password: "
    read -r -s password_confirm
    if [[ "$password" != "$password_confirm" ]]; then
        display_error "Error: Passwords do not match."
        return 1
    fi

    return 0
}

# Prompt for DB Password
while true; do
    echo "Please enter the password for your RDS instance (Minimum 8 characters; includes [A-Z], [a-z], [0-9]): "
    read -r -s DB_PASS
    if validate_password "$DB_PASS"; then
        break
    fi
done

validate_api_key() {
    local api_key=$1

    if [[ ! $api_key =~ ^[A-Za-z0-9_]{8,}$ ]]; then
        display_error "Error: API Key must be at least 8 characters long and can include letters, numbers, and underscores."
        return 1
    fi

    # read api_key again to confirm
    echo "Please re-enter the api-key: "
    read -r -s api_key_confirm
    if [[ "$api_key" != "$api_key_confirm" ]]; then
        display_error "Error: Api Keys do not match."
        return 1
    fi
    return 0
}

validate_access_token() {
    local token=$1
    
    # Check minimum length
    if [[ ${#token} -lt 32 ]]; then
        display_error "Error: Access token must be at least 32 characters long."
        return 1
    fi
    
    # Check for valid characters
    if [[ ! $token =~ ^[A-Za-z0-9_+/=-]+$ ]]; then
        display_error "Error: Access token contains invalid characters."
        return 1
    fi
    
    return 0
}

validate_hash_context() {
    local context=$1
    
    # Check if it follows the pattern "service:domain"
    if [[ ! $context =~ ^[a-zA-Z0-9_-]+:[a-zA-Z0-9_-]+$ ]]; then
        display_error "Error: Hash context should follow the pattern 'service:domain' (e.g., 'keymanager:hyperswitch')."
        return 1
    fi
    
    return 0
}

# Prompt for Admin API Key
while true; do
    echo "Please enter the Admin API key (required to access Hyperswitch APIs): "
    read -r -s ADMIN_API_KEY
    if validate_api_key "$ADMIN_API_KEY"; then
        break
    fi
done

validate_opensearch_password() {
    local master_password=$1

    # Check length (at least 8 characters)
    if [[ ${#master_password} -lt 8 ]]; then
        display_error "Error: Password must be at least 8 characters."
        return 1
    fi

    # Check if it starts with an alphabet
    if [[ ! $master_password =~ ^[A-Za-z] ]]; then
        display_error "Error: Password must start with a letter."
        return 1
    fi

    # Check for at least one uppercase letter and one lowercase letter
    if [[ ! $master_password =~ [A-Z] || ! $master_password =~ [a-z] ]]; then
        display_error "Error: Password must include at least one uppercase and one lowercase letter."
        return 1
    fi

    # Check for at least one digit
    if [[ ! $master_password =~ [0-9] ]]; then
        display_error "Error: Password must include at least one digit."
        return 1
    fi

    # Check for special characters
    if [[ $password == [^A-Za-z0-9] ]]; then
        display_error "Error: Password should include special characters."
        return 1
    fi

    # read password again to confirm
    echo "Please re-enter the password: "
    read -r -s master_password_confirm
    if [[ "$master_password" != "$master_password_confirm" ]]; then
        display_error "Error: Passwords do not match."
        return 1
    fi

    return 0

}

generate_keymanager_certificates() {
    rm ca_cert.pem ca_key.pem client.pem rsa_sha256_cert.pem rsa_sha256_key.pem 2> /dev/null
    echo "${bold}${green}Generating certificate for keymanager ...${reset}"
    curl -s https://raw.githubusercontent.com/juspay/hyperswitch-encryption-service/main/scripts/tls/gen_certs.sh --output gen_certs.sh
    bash gen_certs.sh --prod --namespace keymanager --service keymanager > /dev/null 2>&1
    rm gen_certs.sh
    echo "${bold}${green}Generating certificate done${reset}"
}

# Commented out the Open Search Service feature, will be added in future releases
# echo "Do you want to push logs to S3 and Open Search? [y/n]: "
# while true; do
#     read -r OPEN_SEARCH_SERVICE
    
#     if [[ "$OPEN_SEARCH_SERVICE" == "y" ]]; then
#         read -p "Please enter the Master UserName for Open Search Service: " OPEN_SEARCH_MASTER_USER_NAME
#         while true; do
#             echo "Please enter the Master Password for Open Search Service: "
#             read -r -s OPEN_SEARCH_MASTER_PASSWORD
#             if validate_opensearch_password "$OPEN_SEARCH_MASTER_PASSWORD"; then
#                 break
#             fi
#         done
#         break
    
#     elif [[ "$OPEN_SEARCH_SERVICE" == "n" ]]; then
#         echo "Logs will not be pushed to S3 and Open Search."
#         break
    
#     else
#         echo "Invalid input. Please enter 'y' or 'n'."
#     fi
# done 


if [[ "$INSTALLATION_MODE" == 2 ]]; then

    while true; do
        echo "Please enter the AES master encryption key. It must be 64 characters long and consist of hexadecimal digits:"
        echo "${bold}${red}Please create the AES master encryption key as described below.${reset}"
        echo "${bold}${yellow}To generate the master key, run the following command:${reset}${bold}${green} openssl enc -aes-256-cbc -k secret -P -md sha1${reset}"
        echo "${bold}${yellow}Copy the value of 'key' from the output and use it as the master key.${reset}"
        read -r -s MASTER_ENC_KEY
        if [[ ${#MASTER_ENC_KEY} -eq 64 && $MASTER_ENC_KEY =~ ^[0-9a-fA-F]+$ ]]; then
            break
        else
            display_error "Invalid input. The master encryption key must be 64 characters long and consist of hexadecimal digits."
        fi
    done

    echo "Please enter the IP addresses that you want to whitelist for the EKS cluster. If you have multiple IP addresses, separate them with commas."
    echo "If you do not have static ip you can enter${bold}${red} 0.0.0.0, but this will make the cluster open to internet ${reset}."
    read -r VPN_IPS

    echo
    echo "Do you want to deploy the Card Vault? [y/n]: "
    read -r CARD_VAULT

    LOCKER=""
    if [[ "$CARD_VAULT" == "y" ]]; then
        # Instructions for Card Vault Master Key
        echo "${bold}${red}If you require the Card Vault, create a master key as described below.${reset}"
        echo "${bold}${yellow}To generate the master key, use the utility at: https://github.com/juspay/hyperswitch-card-vault${reset}"
        echo "${bold}${yellow}With cargo installed, run: cargo install --git https://github.com/juspay/hyperswitch-card-vault --bin utils --root . && ./bin/utils master-key && rm ./bin/utils && rmdir ./bin${reset}"

        # Prompt for Encrypted Master Key
        echo "Enter your encrypted master key:"
        read -r -s MASTER_KEY
        LOCKER+="-c master_key=$MASTER_KEY "
        # Prompt for Locker DB Password
        while true; do
            echo "Please enter the password for your RDS instance (Minimum 8 characters; includes [A-Z], [a-z], [0-9]): "
            read -r -s LOCKER_DB_PASS
            if validate_password "$LOCKER_DB_PASS"; then
            break
            fi
        done
        LOCKER+="-c locker_pass=$LOCKER_DB_PASS "
    fi

    echo
    echo "Do you want to deploy Keymanager? (Mandatory for non standalone builds) [y/n]: "
    read -r KEYMANAGER

    KEYMANAGER_ENABLED=""
    if [[ "$KEYMANAGER" == "y" ]]; then
        generate_keymanager_certificates
        
        # Prompt for Access Token
        echo "${bold}${blue}Keymanager Access Token Configuration${reset}"
        echo "The access token is a cryptographically secure string used for authenticating requests to the keymanager service."
        echo "${bold}${yellow}To generate a secure access token, you can use:${reset}${bold}${green}  openssl rand -hex 32${reset}"
        
        while true; do
            echo "Please enter the Keymanager Access Token (minimum 32 characters):"
            read -r -s KEYMANAGER_ACCESS_TOKEN
            if validate_access_token "$KEYMANAGER_ACCESS_TOKEN"; then
                break
            fi
        done
        
        # Prompt for Hash Context
        echo "${bold}${blue}Keymanager Hash Context Configuration${reset}"
        echo "The hash context is a domain separation string "
        
        while true; do
            echo "Please enter the Hash Context (or press Enter for default 'keymanager:hyperswitch'):"
            read -r KEYMANAGER_HASH_CONTEXT
            if [[ -z "$KEYMANAGER_HASH_CONTEXT" ]]; then
                KEYMANAGER_HASH_CONTEXT="keymanager:hyperswitch"
                echo "Using default: keymanager:hyperswitch"
                break
            elif validate_hash_context "$KEYMANAGER_HASH_CONTEXT"; then
                break
            fi
        done
        
        KEYMANAGER_ENABLED+="-c keymanager_enabled=true "
        KEYMANAGER_ENABLED+="-c keymanager_access_token=$KEYMANAGER_ACCESS_TOKEN "
        KEYMANAGER_ENABLED+="-c keymanager_hash_context=$KEYMANAGER_HASH_CONTEXT"
    fi

    echo
    echo "Do you want to deploy proxy setup (Envoy/Squid)? [y/n]: "
    read -r APP_PROXY_SETUP

    APP_PROXY_CONTEXT=""
    if [[ "$APP_PROXY_SETUP" == "y" ]]; then
        echo "Checking for required AMI images for proxy setup..."
        check_image_builder
        APP_PROXY_CONTEXT="-c app_proxy_enabled=true -c envoy_ami=$envoy_ami -c squid_ami=$squid_ami"
    fi

    echo
    printf "${bold}Deploying Hyperswitch Services${reset}\n"
    # Deploy the EKS Cluster
    npm install
    export JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION=true
    echo "Bootstrapping CDK environment..."
    if ! cdk bootstrap aws://$AWS_ACCOUNT_ID/$AWS_DEFAULT_REGION -c aws_arn=$AWS_ARN; then
        BUCKET_NAME=cdk-hnb659fds-assets-$AWS_ACCOUNT_ID-$AWS_DEFAULT_REGION
        ROLE_NAME=cdk-hnb659fds-cfn-exec-role-$AWS_ACCOUNT_ID-$AWS_DEFAULT_REGION
        aws s3api delete-objects --bucket $BUCKET_NAME --delete "$(aws s3api list-object-versions --bucket $BUCKET_NAME --query='{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" 2>/dev/null
        aws s3api delete-objects --bucket $BUCKET_NAME --delete "$(aws s3api list-object-versions --bucket $BUCKET_NAME --query='{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}')" 2>/dev/null
        aws s3 rm s3://$BUCKET_NAME --recursive 2>/dev/null
        aws s3api delete-bucket --bucket $BUCKET_NAME 2>/dev/null
        for policy_arn in $(aws iam list-attached-role-policies --role-name $ROLE_NAME --query 'AttachedPolicies[].PolicyArn' --output text); do
            aws iam detach-role-policy --role-name $ROLE_NAME --policy-arn $policy_arn 2>/dev/null
        done
        aws iam delete-role --role-name $ROLE_NAME 2>/dev/null
        cdk bootstrap aws://$AWS_ACCOUNT_ID/$AWS_DEFAULT_REGION -c aws_arn=$AWS_ARN
    fi
    
    echo
    echo "${bold}Deploying Hyperswitch Stack...${reset}"
    echo
    # Single deployment that includes everything based on user choices
    if cdk deploy --require-approval never -c db_pass=$DB_PASS -c admin_api_key=$ADMIN_API_KEY -c aws_arn=$AWS_ARN -c master_enc_key=$MASTER_ENC_KEY -c vpn_ips=$VPN_IPS -c base_ami=$base_ami $LOCKER $KEYMANAGER_ENABLED $APP_PROXY_CONTEXT -c open_search_service=$OPEN_SEARCH_SERVICE -c open_search_master_user_name=$OPEN_SEARCH_MASTER_USER_NAME -c open_search_master_password=$OPEN_SEARCH_MASTER_PASSWORD; then
        echo "Stack deployment successful."
        
        echo "Creating CloudWatch Observability addon..."
        aws eks create-addon --cluster-name hs-eks-cluster --addon-name amazon-cloudwatch-observability --resolve-conflicts PRESERVE > /dev/null 2>&1 || echo "Failed to create/update cloudwatch-observability addon. It might already exist or an error occurred."

        echo "Updating kubeconfig..."
        aws eks update-kubeconfig --region "$AWS_DEFAULT_REGION" --name hs-eks-cluster

        echo "Please wait for the EKS cluster to be initialized. Approximate time is 2 minutes..."
        sleep 120

        # Retrieve ingress hostnames for CloudFront configuration
        echo "Retrieving ingress hostnames..."
        CONTROL_CENTER_HOST=$(kubectl get ingress hyperswitch-control-center-ingress -n hyperswitch -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null)

        # Conditionally get APP_HOST based on whether proxy is enabled
        if [[ "$APP_PROXY_SETUP" == "y" ]]; then

            EXT_ALB_DNS=$(aws elbv2 describe-load-balancers --names external-lb --query 'LoadBalancers[0].DNSName' --output text)
            
            if [ -n "$EXT_ALB_DNS" ] && [ "$EXT_ALB_DNS" != "null" ]; then
                APP_HOST="$EXT_ALB_DNS"
                echo "Using External ALB as App Host: $APP_HOST"
            else
                echo "External ALB DNS not found."
                APP_HOST=$(kubectl get ingress hyperswitch-alb-ingress -n hyperswitch -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null)
                echo "Using ALB ingress as App Host: $APP_HOST"
            fi
        else
            # Default approach for non-proxy setup
            APP_HOST=$(kubectl get ingress hyperswitch-alb-ingress -n hyperswitch -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null)
            echo "Using ALB ingress as App Host: $APP_HOST"
        fi
        
        if [ -n "$CONTROL_CENTER_HOST" ] && [ -n "$APP_HOST" ]; then
            echo "Control Center Host: $CONTROL_CENTER_HOST"
            
            # Update CloudFront distributions with the ingress hostnames
            echo "Updating CloudFront distributions..."
            cdk deploy --require-approval never \
                -c db_pass=$DB_PASS -c admin_api_key=$ADMIN_API_KEY -c aws_arn=$AWS_ARN \
                -c master_enc_key=$MASTER_ENC_KEY -c vpn_ips=$VPN_IPS -c base_ami=$base_ami \
                $LOCKER $KEYMANAGER_ENABLED $APP_PROXY_CONTEXT \
                -c open_search_service=$OPEN_SEARCH_SERVICE \
                -c open_search_master_user_name=$OPEN_SEARCH_MASTER_USER_NAME \
                -c open_search_master_password=$OPEN_SEARCH_MASTER_PASSWORD \
                -c control_center_host="$CONTROL_CENTER_HOST" \
                -c app_host="$APP_HOST"
            
            if [ $? -ne 0 ]; then
                echo "${bold}${red}Error updating CloudFront distributions.${reset}"
            else
                echo "CloudFront distributions updated successfully."
            fi
        else
            echo "${bold}${yellow}Warning: Could not retrieve ingress hostnames. CloudFront configuration may be incomplete.${reset}"
        fi
        
        echo "Finalizing setup..."
        helm get values -n hyperswitch hypers-v1 > values.yaml 2>/dev/null || echo "Failed to get helm values for hypers-v1"
        sh upgrade.sh "$ADMIN_API_KEY" "$CARD_VAULT" "$APP_PROXY_SETUP" "$KEYMANAGER"

        echo "โœ…  All deployments complete!"
        exit 0
    fi

else
    echo
    printf "${bold}Deploying Hyperswitch Services${reset}\n"
    echo
    echo "Hyperswitch is being deployed in standalone mode. Please wait for the deployment to complete."

    npm install
    if ! cdk bootstrap aws://$AWS_ACCOUNT_ID/$AWS_DEFAULT_REGION -c aws_arn=$AWS_ARN; then
        BUCKET_NAME=cdk-hnb659fds-assets-$AWS_ACCOUNT_ID-$AWS_DEFAULT_REGION
        ROLE_NAME=cdk-hnb659fds-cfn-exec-role-$AWS_ACCOUNT_ID-$AWS_DEFAULT_REGION
        aws s3api delete-objects --bucket $BUCKET_NAME --delete "$(aws s3api list-object-versions --bucket $BUCKET_NAME --query='{Objects: Versions[].{Key:Key,VersionId:VersionId}}')" 2>/dev/null
        aws s3api delete-objects --bucket $BUCKET_NAME --delete "$(aws s3api list-object-versions --bucket $BUCKET_NAME --query='{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}')" 2>/dev/null
        aws s3 rm s3://$BUCKET_NAME --recursive 2>/dev/null
        aws s3api delete-bucket --bucket $BUCKET_NAME 2>/dev/null
        for policy_arn in $(aws iam list-attached-role-policies --role-name $ROLE_NAME --query 'AttachedPolicies[].PolicyArn' --output text); do
            aws iam detach-role-policy --role-name $ROLE_NAME --policy-arn $policy_arn 2>/dev/null
        done
        aws iam delete-role --role-name $ROLE_NAME 2>/dev/null
        cdk bootstrap aws://$AWS_ACCOUNT_ID/$AWS_DEFAULT_REGION -c aws_arn=$AWS_ARN
    fi
    if cdk deploy --require-approval never -c aws_arn=$AWS_ARN -c free_tier=true -c db_pass=$DB_PASS -c admin_api_key=$ADMIN_API_KEY; then
        STANDALONE_HOST=$(aws cloudformation describe-stacks --stack-name hyperswitch --query "Stacks[0].Outputs[?OutputKey=='StandaloneURL'].OutputValue" --output text)
        CONTROL_CENTER_HOST=$(aws cloudformation describe-stacks --stack-name hyperswitch --query "Stacks[0].Outputs[?OutputKey=='ControlCenterURL'].OutputValue" --output text)
        SDK_HOST=$(aws cloudformation describe-stacks --stack-name hyperswitch --query "Stacks[0].Outputs[?OutputKey=='SdkAssetsURL'].OutputValue" --output text)
        DEMO_APP=$(aws cloudformation describe-stacks --stack-name hyperswitch --query "Stacks[0].Outputs[?OutputKey=='DemoApp'].OutputValue" --output text)
        echo "Please wait for instances to be initialized. Approximate time is 2 minutes."
        printf "${bold}Initializing Instances${reset} "
        # # Start the spinner in the background
        # (
        #     while :; do
        #         for s in '/' '-' '\\' '|'; do
        #             printf "\r$s"
        #             sleep 1
        #         done
        #     done
        # ) &
        # spinner_pid=$!
        # # Sleep for x seconds to simulate work
        sleep 60
        # Kill the spinner
        # kill $spinner_pid >/dev/null 2>&1
        # wait $spinner_pid 2>/dev/null # Ensures the spinner process is properly terminated before moving on
        # printf "\r"                   # Clear the spinner character
        printf "\nInitialization complete.\n"
        printf "\n"
        echoLog "--------------------------------------------------------------------------------"
        echoLog "$bold Service                           Host$reset"
        echoLog "--------------------------------------------------------------------------------"
        echoLog "$green Standalone Hosted at             $blue"$STANDALONE_HOST"$reset"
        echoLog "$green Control center server running on  $blue"$CONTROL_CENTER_HOST"$reset"
        echoLog "$green SDK Hosted at                    $blue"$SDK_HOST"$reset"
        #echoLog "$green Hyperswitch Demo Store running on $blue"$DEMO_APP"$reset"
        echoLog "--------------------------------------------------------------------------------"
    fi
fi