AWSTemplateFormatVersion: 2010-09-09

######## METADATA ########

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "VPC Configuration"
        Parameters:
          - VPCId
          - Subnets
      -
        Label:
          default: "RDS Configuration"
        Parameters:
          - RDSInstanceType
          - DatabaseName
          - DatabaseUsername
          - DatabaseStorage
      -
        Label:
          default: "EC2 Configuration"
        Parameters:
          - InstanceType
          - AllowAPIAccessFrom
      -
        Label:
          default: "HTTPS / SSL Configuration"
        Parameters:
          - EnableHTTPS
          - ACMCertificateARN
          - ALBDeletionProtection
      -
        Label:
          default: "Passwords"
        Parameters:
          - TarPasswordInput
          - ConfirmTarPassword
          - UbuntuPasswordInput
          - ConfirmUbuntuPassword

######## PARAMETERS ########

Parameters:
  VPCId:
    Default: ''
    Type: AWS::EC2::VPC::Id
    Description: "VPC Id of Virtual Private Cloud (VPC)"
  Subnets:
    Default: ''
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: "Select 2 'Public' Subnets belonging to above VPC. Subnets should be in above VPC"
  DatabaseName:
    Default: mpc
    Description: "Database name for MPC. This is the name of mysql Database created inside RDS"
    Type: String
    MinLength: '1'
    MaxLength: '64'
    AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*'
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters.
  DatabaseUsername:
    Default: admin
    Description: "The database admin account username"
    Type: String
    MinLength: '1'
    MaxLength: '16'
    AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*'
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters.
  DatabaseStorage:
    Default: '20'
    Description: "The size of the database (GB), must be atleast 20GB"
    Type: Number
    MinValue: '20'
    MaxValue: '512'
    ConstraintDescription: must be between 20 and 512GB
  RDSInstanceType:
    Description: "RDS(Database) Instance Type, db.t3.medium recommended"
    Type: String
    Default: db.t3.medium
    AllowedValues: [db.t2.medium, db.t2.large, db.t3.medium, db.t3.large]
    ConstraintDescription: must select a valid database instance type.
  InstanceType:
    Description: "EC2 instance type, c5.large recommended"
    Type: String
    Default: c5.large
    AllowedValues: [t3.medium, m5.large, c5.large, r5.large]
    ConstraintDescription: must be a valid EC2 instance type.
  SnapshotTagValue:
    Description: "Tag value for identifying EC2 and snapshots. Used for backup policy and deletion protection. Must be unique per deployment."
    Type: String
    Default: 'liminal-prod-mpc-v62'
    MinLength: '3'
    MaxLength: '64'
    AllowedPattern: '[a-zA-Z0-9-]+'
    ConstraintDescription: Must be 3-64 alphanumeric characters with hyphens allowed.
  AllowAPIAccessFrom:
    Description: "This IP will be whitelisted to access the SDK either via the ALB or through port 8081 (if HTTPS is not enabled)."
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  EnableHTTPS:
    Description: "Enable HTTPS with Application Load Balancer? Select 'Yes' to use an existing ACM certificate for SSL/TLS termination."
    Type: String
    Default: 'No'
    AllowedValues:
      - 'Yes'
      - 'No'
  ACMCertificateARN:
    Description: "ARN of the ACM Certificate (Required if EnableHTTPS is 'Yes'). Example: arn:aws:acm:us-east-1:123456789012:certificate/abc12345-1234-1234-1234-abc123456789"
    Type: String
    Default: ''
  ALBDeletionProtection:
    Description: "Enable deletion protection for the Application Load Balancer? When enabled, the ALB cannot be deleted until protection is disabled. Recommended: 'Yes' for production, 'No' for testing/development."
    Type: String
    Default: 'Yes'
    AllowedValues:
      - 'Yes'
      - 'No'
  TarPasswordInput:
    NoEcho: 'true'
    Description: "Password for the infrastructure config zip file. This will be stored securely in AWS Secrets Manager."
    Type: String
    MinLength: '8'
    MaxLength: '32'
    AllowedPattern: '[a-zA-Z0-9]+'
    ConstraintDescription: Must be 8-32 alphanumeric characters.
  ConfirmTarPassword:
    NoEcho: 'true'
    Description: "Re-enter the TAR password to confirm."
    Type: String
    MinLength: '8'
    MaxLength: '32'
    AllowedPattern: '[a-zA-Z0-9]+'
    ConstraintDescription: Must be 8-32 alphanumeric characters.
  UbuntuPasswordInput:
    NoEcho: 'true'
    Description: "Password for the Ubuntu user on EC2 instance. This will be stored securely in AWS Secrets Manager."
    Type: String
    MinLength: '8'
    MaxLength: '32'
    AllowedPattern: '[a-zA-Z0-9]+'
    ConstraintDescription: Must be 8-32 alphanumeric characters.
  ConfirmUbuntuPassword:
    NoEcho: 'true'
    Description: "Re-enter the Ubuntu password to confirm."
    Type: String
    MinLength: '8'
    MaxLength: '32'
    AllowedPattern: '[a-zA-Z0-9]+'
    ConstraintDescription: Must be 8-32 alphanumeric characters.

######## RULES ########

Rules:
  SubnetsInVPC:
    Assertions:
      - Assert:
          'Fn::EachMemberEquals':
            - 'Fn::ValueOf':
                - Subnets
                - VpcId
            - Ref: VPCId
        AssertDescription: "Important: All subnets must be public and in the selected VPC"
  ValidateACMCertificate:
    RuleCondition: !Equals [!Ref EnableHTTPS, 'Yes']
    Assertions:
      - Assert: !Not [!Equals [!Ref ACMCertificateARN, '']]
        AssertDescription: 'ACM Certificate ARN is required when HTTPS is enabled. Please provide a valid ACM certificate ARN.'
  MatchTarPasswords:
    Assertions:
      - Assert:
          Fn::Equals:
            - !Ref TarPasswordInput
            - !Ref ConfirmTarPassword
        AssertDescription: 'TAR Passwords do not match. Please ensure both entries are identical.'
  MatchUbuntuPasswords:
    Assertions:
      - Assert:
          Fn::Equals:
            - !Ref UbuntuPasswordInput
            - !Ref ConfirmUbuntuPassword
        AssertDescription: 'Ubuntu Passwords do not match. Please ensure both entries are identical.'

######## CONDITIONS ########

Conditions:
  CreateHTTPSResources: !Equals [!Ref EnableHTTPS, 'Yes']
  HTTPSDisabled: !Equals [!Ref EnableHTTPS, 'No']
  EnableALBDeletionProtection: !Equals [!Ref ALBDeletionProtection, 'Yes']

######## MAPPINGS ########

Mappings:
  RegionMap:
    us-east-1:
      ami: 'ami-073981efdb2e8acee'
    us-east-2:
      ami: 'ami-0fb88624e50b31092'
    us-west-1:
      ami: 'ami-0e241715a777b4080'
    us-west-2:
      ami: 'ami-025565316fd0eacdd'
    af-south-1:
      ami: 'ami-07372f0f9c01400db'
    ap-southeast-3:
      ami: 'ami-08627b9dd331bcae9'
    ap-south-1:
      ami: 'ami-02163b070a3ff698f'
    ap-northeast-3:
      ami: 'ami-032879eff246ea215'
    ap-northeast-2:
      ami: 'ami-0822c3b0780c511ba'
    ap-southeast-1:
      ami: 'ami-08e49d90aa5b7c458'
    ap-southeast-2:
      ami: 'ami-0310c1b6b73a722c3'
    ap-northeast-1:
      ami: 'ami-0aa894849346de664'
    ca-central-1:
      ami: 'ami-04d890b3d00166cd6'
    eu-central-1:
      ami: 'ami-0ba6d53027b971798'
    eu-west-1:
      ami: 'ami-0552c80a899793aba'
    eu-west-2:
      ami: 'ami-0f37688cdf99c2392'
    eu-west-3:
      ami: 'ami-0bba295c77bc36532'
    eu-north-1:
      ami: 'ami-04262afad2e98dc3c'
    sa-east-1:
      ami: 'ami-0127f27fa7ab08e36'

######## RESOURCES ########

Resources:
  MPCSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: "mpc-v62-encryptor-password"
      GenerateSecretString:
        ExcludeLowercase: false
        ExcludeNumbers: false
        ExcludePunctuation: true
        ExcludeUppercase: false
        ExcludeCharacters: '"@/\'
        IncludeSpace: false
        PasswordLength: 32
  TarPassword:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: "prod/mpc-v62-tar-password"
      Description: "Password for infrastructure config zip file"
      SecretString: !Ref TarPasswordInput
  UbuntuPassword:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: "prod/mpc-v62-ubuntu-password"
      Description: "Password for Ubuntu user on EC2 instance"
      SecretString: !Ref UbuntuPasswordInput
  RDSSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: "mpc-v62-rds-credentials"
      GenerateSecretString:
        ExcludeLowercase: false
        ExcludeNumbers: false
        ExcludePunctuation: true
        ExcludeUppercase: false
        ExcludeCharacters: '"@/\'
        IncludeSpace: false
        PasswordLength: 32
        SecretStringTemplate: !Join [ '', [ '{"username": "', !Ref DatabaseUsername, '"}' ] ]
        GenerateStringKey: 'password'
  MPCS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true
  MPCKMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: ECC_SECG_P256K1 asymmetric KMS key for signing and verification
      MultiRegion: true
      KeySpec: ECC_SECG_P256K1
      KeyUsage: SIGN_VERIFY
      KeyPolicy:
        Version: '2012-10-17'
        Id: key-default-1
        Statement:
        - Sid: Enable IAM User Permissions
          Effect: Allow
          Principal:
            AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
          Action: 'kms:*'
          Resource: '*'
  MPCKMSKeyAlias:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: alias/liminal-v62-kms
      TargetKeyId: !Ref 'MPCKMSKey'
  EC2KMSIAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: mpc-v62-ec2-iam-role
      Description: "IAM role allowing EC2 to access KMS"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Effect: Allow
  EC2KMSIAMRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: mpc-v62-ec2-policy
      Roles:
      - !Ref 'EC2KMSIAMRole'
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          # KMS - Scoped to specific key, only actions needed for signing
          - Sid: KMSSigningOperations
            Effect: Allow
            Action:
              - 'kms:GetPublicKey'
              - 'kms:Sign'
              - 'kms:Verify'
              - 'kms:DescribeKey'
            Resource: !GetAtt MPCKMSKey.Arn
          # ECR - Read-only for pulling container images
          - Sid: ECRReadOnly
            Effect: Allow
            Action:
              - 'ecr:BatchCheckLayerAvailability'
              - 'ecr:BatchGetImage'
              - 'ecr:GetDownloadUrlForLayer'
            Resource:
              - !Sub 'arn:aws:ecr:${AWS::Region}:*:repository/*'
              - 'arn:aws:ecr:us-west-2:641754881946:repository/liminal-sdk-api'
          - Sid: ECRAuthToken
            Effect: Allow
            Action:
              - 'ecr:GetAuthorizationToken'
            Resource: "*"
          # S3 - Upload config zip to specific bucket
          - Sid: S3UploadConfig
            Effect: Allow
            Action:
              - 's3:PutObject'
            Resource: !Sub 'arn:aws:s3:::${MPCS3Bucket}/*'
          # Secrets Manager - Read stack-created secrets
          - Sid: SecretsManagerReadStackSecrets
            Effect: Allow
            Action:
              - 'secretsmanager:GetSecretValue'
            Resource:
              - !Ref 'MPCSecret'
              - !Ref 'RDSSecret'
          # SSM Session Manager - Required for remote access
          - Sid: SSMSessionManager
            Effect: Allow
            Action:
              - 'ssm:DescribeAssociation'
              - 'ssm:GetDeployablePatchSnapshotForInstance'
              - 'ssm:GetDocument'
              - 'ssm:DescribeDocument'
              - 'ssm:GetManifest'
              - 'ssm:ListAssociations'
              - 'ssm:ListInstanceAssociations'
              - 'ssm:PutInventory'
              - 'ssm:PutComplianceItems'
              - 'ssm:PutConfigurePackageResult'
              - 'ssm:UpdateAssociationStatus'
              - 'ssm:UpdateInstanceAssociationStatus'
              - 'ssm:UpdateInstanceInformation'
            Resource: "*"
          - Sid: SSMMessages
            Effect: Allow
            Action:
              - 'ssmmessages:CreateControlChannel'
              - 'ssmmessages:CreateDataChannel'
              - 'ssmmessages:OpenControlChannel'
              - 'ssmmessages:OpenDataChannel'
            Resource: "*"
          - Sid: EC2Messages
            Effect: Allow
            Action:
              - 'ec2messages:AcknowledgeMessage'
              - 'ec2messages:DeleteMessage'
              - 'ec2messages:FailMessage'
              - 'ec2messages:GetEndpoint'
              - 'ec2messages:GetMessages'
              - 'ec2messages:SendReply'
            Resource: "*"
          # SSM Patch Management
          - Sid: SSMPatchBaseline
            Effect: Allow
            Action:
              - 'ssm:DescribeEffectivePatchesForPatchBaseline'
              - 'ssm:GetPatchBaseline'
            Resource: !Sub 'arn:aws:ssm:${AWS::Region}:*:patchbaseline/*'
          - Sid: SSMDescribePatchBaselines
            Effect: Allow
            Action:
              - 'ssm:DescribePatchBaselines'
            Resource: "*"
          # CloudWatch Logs - Scoped to liminal log groups
          - Sid: CloudWatchLogs
            Effect: Allow
            Action:
              - 'logs:PutLogEvents'
              - 'logs:DescribeLogStreams'
              - 'logs:DescribeLogGroups'
              - 'logs:CreateLogStream'
              - 'logs:CreateLogGroup'
            Resource:
              - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/liminal/*'
              - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/ec2/liminal-*'
          # Secrets Manager - Create and manage SDK credentials
          - Sid: SecretsManagerCreateSDKCreds
            Effect: Allow
            Action:
              - 'secretsmanager:CreateSecret'
            Resource: !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:prod-liminal-sdk-creds*'
          - Sid: SecretsManagerManageSDKCreds
            Effect: Allow
            Action:
              - 'secretsmanager:PutSecretValue'
              - 'secretsmanager:GetSecretValue'
              - 'secretsmanager:DescribeSecret'
            Resource: !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:prod-liminal-sdk-creds*'
         
          - Sid: SecretsManagerAccessMPCSecrets
            Effect: Allow
            Action:
              - 'secretsmanager:GetSecretValue'
              - 'secretsmanager:DescribeSecret'
              - 'secretsmanager:CreateSecret'
              - 'secretsmanager:PutSecretValue'
            Resource:
              - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:prod-mpc-v62-user-creds*'
              - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:prod-mpc-v62-admin-creds*'
              - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:prod-mpc-v62-keys*'

  EC2MPCInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: mpc-v62-instance-profile
      Path: "/"
      Roles:
      - Ref: 'EC2KMSIAMRole'
  MPCIAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: mpc-v62-snapshot-iam-role
      Description: "IAM role for Liminal MPC EC2 auto snapshots"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Principal:
              Service:
                - 'dlm.amazonaws.com'
            Effect: Allow
            Sid: ''
  MPCIAMRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: mpc-v62-snapshot-iam-policy
      Roles:
      - !Ref 'MPCIAMRole'
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          # Describe operations - require Resource: "*" per AWS
          - Sid: DescribeResources
            Effect: Allow
            Action:
              - 'ec2:DescribeInstances'
              - 'ec2:DescribeVolumes'
              - 'ec2:DescribeSnapshots'
            Resource: "*"
          # Create snapshots
          - Sid: CreateSnapshots
            Effect: Allow
            Action:
              - 'ec2:CreateSnapshot'
              - 'ec2:CreateSnapshots'
            Resource: "*"
          - Sid: DeleteTaggedSnapshotsOnly
            Effect: Allow
            Action:
              - 'ec2:DeleteSnapshot'
            Resource: !Sub 'arn:aws:ec2:${AWS::Region}::snapshot/*'
            Condition:
              StringEquals:
                'aws:ResourceTag/SnapshotType': !Sub '${SnapshotTagValue}-backup'
          - Sid: CreateTagsOnSnapshots
            Effect: Allow
            Action:
              - 'ec2:CreateTags'
            Resource: !Sub 'arn:aws:ec2:${AWS::Region}::snapshot/*'
  MpcLifecyclePolicy:
    Type: AWS::DLM::LifecyclePolicy
    Properties:
      Description: Liminal MPC EC2 auto snapshot policy
      ExecutionRoleArn: !GetAtt 'MPCIAMRole.Arn'
      State: ENABLED
      PolicyDetails:
        ResourceTypes:
        - INSTANCE
        Schedules:
        - Name: mpc-v62-ec2-snapshot
          CreateRule:
            Interval: 12
            IntervalUnit: HOURS
            Times:
            - '10:00'
          RetainRule:
            Count: 14
          TagsToAdd:
          - Key: SnapshotType
            Value: !Sub '${SnapshotTagValue}-backup'
          CopyTags: true
        TargetTags:
        - Key: Name
          Value: !Ref SnapshotTagValue
  SubnetGroup:
    Type: "AWS::RDS::DBSubnetGroup"
    Properties:
      DBSubnetGroupName: "mpc-v62-subnet-group"
      DBSubnetGroupDescription: "MPC Subnet Group"
      SubnetIds: !Split [',', !Join [',', !Ref Subnets]]
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable MPC required access
      VpcId: !Ref 'VPCId'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '9000'
        ToPort: '9000'
        CidrIp: '52.34.93.184/32'
  # Direct API access on port 8081 - only when HTTPS is disabled
  DirectAPIAccessIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Condition: HTTPSDisabled
    Properties:
      GroupId: !Ref InstanceSecurityGroup
      IpProtocol: tcp
      FromPort: '8081'
      ToPort: '8081'
      CidrIp: !Ref 'AllowAPIAccessFrom'
  
  ALBToEC2Ingress:
    Type: AWS::EC2::SecurityGroupIngress
    Condition: CreateHTTPSResources
    Properties:
      GroupId: !Ref InstanceSecurityGroup
      IpProtocol: tcp
      FromPort: '8081'
      ToPort: '8081'
      SourceSecurityGroupId: !Ref ALBSecurityGroup

  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Condition: CreateHTTPSResources
    Properties:
      GroupDescription: Security group for Application Load Balancer
      VpcId: !Ref 'VPCId'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '443'
        ToPort: '443'
        CidrIp: !Ref 'AllowAPIAccessFrom'
      - IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: !Ref 'AllowAPIAccessFrom'
      Tags:
      - Key: Name
        Value: 'liminal-prod-mpc-v62-alb-sg'
  DBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Open database for access from MPC server
      VpcId: !Ref 'VPCId'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '3306'
        ToPort: '3306'
        SourceSecurityGroupId: !GetAtt InstanceSecurityGroup.GroupId
  MpcDB:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Retain
    Properties:
      DBName: !Ref 'DatabaseName'
      DBSubnetGroupName: !Ref 'SubnetGroup'
      DBInstanceIdentifier: 'liminal-tsm-rds'
      DBInstanceClass: !Ref 'RDSInstanceType'
      Engine: MySQL
      EngineVersion: "8.0.43"
      MasterUsername: !Ref 'DatabaseUsername'
      MasterUserPassword: !Join [ '', [ '{{resolve:secretsmanager:', !Ref RDSSecret, ':SecretString:password}}' ] ]
      Port: '3306'
      AllocatedStorage: !Ref 'DatabaseStorage'
      StorageType: 'gp3'
      BackupRetentionPeriod: '14'
      StorageEncrypted: 'true'
      PubliclyAccessible: 'false'
      MultiAZ: 'false'
      DeletionProtection: true
      VPCSecurityGroups: 
      - Fn::GetAtt: [DBSecurityGroup, GroupId]
      Tags:
      - Key: Name
        Value: liminal-tsm-rds
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      UserData:
        Fn::Base64: 
          !Sub |
            #!/bin/bash
            set -e
            exec > >(tee -a /var/log/user-data.log | logger -t user-data -s 2>/dev/console) 2>&1
            echo "$(date '+%Y-%m-%d %H:%M:%S') UserData started"

            sudo apt-get update
            echo "$(date '+%Y-%m-%d %H:%M:%S') apt-get update done"

            cd /home/ubuntu
            export DB_HOST="${MpcDB.Endpoint.Address}"
            export DB_USER="${DatabaseUsername}"
            export DB_NAME="${DatabaseName}"
            export REGION="${AWS::Region}"
            export BUCKET_NAME="${MPCS3Bucket}"

            echo "$(date '+%Y-%m-%d %H:%M:%S') Fetching RDS password"
            rdspasswd=$(aws secretsmanager get-secret-value --region "$REGION" --secret-id "${RDSSecret}" --query SecretString --output text | jq -r .password)

            sed -i 's"/tmp/tsmdb"'$DB_USER':'$rdspasswd'@tcp('$DB_HOST:3306')/'$DB_NAME'?parseTime=true"' /home/ubuntu/config/config.toml
            echo "$(date '+%Y-%m-%d %H:%M:%S') DB config updated"

            passwd=$(aws secretsmanager get-secret-value --region "$REGION" --secret-id "${MPCSecret}" --query SecretString --output text)
            sed -i 's"ENCRYPTION_KEY"'$passwd'"' /home/ubuntu/config/config.toml
            echo "$(date '+%Y-%m-%d %H:%M:%S') Encryption key set"

            
            export TAR_PASS="${TarPasswordInput}"
            export UBUNTU_PASS="${UbuntuPasswordInput}"
            echo "$(date '+%Y-%m-%d %H:%M:%S') Passwords configured"

            echo "$(date '+%Y-%m-%d %H:%M:%S') Generating keys"
            /bin/bash generate-key.sh

            echo "$(date '+%Y-%m-%d %H:%M:%S') Running docker"
            /bin/bash run-docker.sh

            sleep 5

            echo "$(date '+%Y-%m-%d %H:%M:%S') Running TSM"
            /bin/bash tsm.sh

            echo "$(date '+%Y-%m-%d %H:%M:%S') Generating credentials"
            /bin/bash generate-creds.sh

            echo "$(date '+%Y-%m-%d %H:%M:%S') Starting express"
            /bin/bash express.sh "$REGION"

            /usr/bin/zip --password "$TAR_PASS" infrastructure-config.zip -r config
            echo "$(date '+%Y-%m-%d %H:%M:%S') Zip created"

            aws s3 cp /home/ubuntu/infrastructure-config.zip s3://"${MPCS3Bucket}"/
            echo "$(date '+%Y-%m-%d %H:%M:%S') Uploaded to S3"

            echo "$(date '+%Y-%m-%d %H:%M:%S') Moving config to secret manager"
            /bin/bash store-config.sh "$REGION"

            /bin/bash restrict-login.sh "$UBUNTU_PASS" "$REGION"
            echo "$(date '+%Y-%m-%d %H:%M:%S') Login restricted"

            rm -f /home/ubuntu/generate-key.sh \
                  /home/ubuntu/run-docker.sh \
                  /home/ubuntu/store-config.sh \
                  /home/ubuntu/config/user.json \
                  /home/ubuntu/config/admin.json \
                  /home/ubuntu/config/private.key \
                  /home/ubuntu/config/public.key \
                  /home/ubuntu/tsm.sh \
                  /home/ubuntu/generate-creds.sh \
                  /home/ubuntu/express.sh \
                  /home/ubuntu/tar.sh \
                  /home/ubuntu/restrict-login.sh
            echo "$(date '+%Y-%m-%d %H:%M:%S') Script cleanup completed"

            # Clear command history
            rm -f /root/.bash_history /home/ubuntu/.bash_history
            history -c
            unset HISTFILE
            
            # Remove config zip and secure files
            rm -f /home/ubuntu/infrastructure-config.zip
            chmod 600 /home/ubuntu/config/* 2>/dev/null || true
            
            echo "$(date '+%Y-%m-%d %H:%M:%S') Security cleanup completed"

            echo "$(date '+%Y-%m-%d %H:%M:%S') UserData completed successfully"

      InstanceType: !Ref 'InstanceType'
      DisableApiTermination: true
      SubnetId: !Select [0, !Ref Subnets]
      Tags:
        - Key: 'Name'
          Value: !Ref SnapshotTagValue
      SecurityGroupIds: 
      - Fn::GetAtt: [InstanceSecurityGroup, GroupId] 
      IamInstanceProfile: !Ref 'EC2MPCInstanceProfile'
      ImageId: !FindInMap
        - RegionMap
        - !Ref 'AWS::Region'
        - ami
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 35
            VolumeType: 'gp3'
            Encrypted: 'true'
            DeleteOnTermination: 'true'
  IPAddress:
    Type: AWS::EC2::EIP
  IPAssoc:
    Type: AWS::EC2::EIPAssociation
    Properties:
      InstanceId: !Ref 'EC2Instance'
      EIP: !Ref 'IPAddress'

  ######## HTTPS / ALB RESOURCES (Conditional) ########

  # Application Load Balancer - only created when HTTPS is enabled
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Condition: CreateHTTPSResources
    Properties:
      Name: 'liminal-prod-mpc-v62-alb'
      Type: application
      Scheme: internet-facing
      IpAddressType: ipv4
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Subnets: !Ref Subnets
      LoadBalancerAttributes:
        - Key: deletion_protection.enabled
          Value: !If [EnableALBDeletionProtection, 'true', 'false']
        - Key: idle_timeout.timeout_seconds
          Value: '60'
      Tags:
        - Key: Name
          Value: 'liminal-prod-mpc-v62-alb'

  # Target Group for EC2 instance
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Condition: CreateHTTPSResources
    Properties:
      Name: 'liminal-prod-mpc-v62-tg'
      Port: 8081
      Protocol: HTTP
      VpcId: !Ref VPCId
      TargetType: instance
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: '/info'
      HealthCheckPort: '8081'
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3
      Matcher:
        HttpCode: '200-399'
      Targets:
        - Id: !Ref EC2Instance
          Port: 8081
      Tags:
        - Key: Name
          Value: 'liminal-prod-mpc-v62-tg'

  # HTTPS Listener (Port 443)
  ALBHTTPSListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Condition: CreateHTTPSResources
    Properties:
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 443
      Protocol: HTTPS
      SslPolicy: 'ELBSecurityPolicy-TLS13-1-2-2021-06'
      Certificates:
        - CertificateArn: !Ref ACMCertificateARN
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup

  # HTTP Listener (Port 80) - Redirects to HTTPS
  ALBHTTPListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Condition: CreateHTTPSResources
    Properties:
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: redirect
          RedirectConfig:
            Protocol: HTTPS
            Port: '443'
            Host: '#{host}'
            Path: '/#{path}'
            Query: '#{query}'
            StatusCode: 'HTTP_301'

Outputs:
  InstanceId:
    Description: InstanceId of the newly created EC2 instance
    Value: !Ref 'EC2Instance'
  InstanceIPAddress:
    Description: IP address of the newly created EC2 instance
    Value: !Ref 'IPAddress'
  Bucket:
    Description: S3 Bucket Name
    Value: !Ref 'MPCS3Bucket'
  ALBDNSName:
    Description: DNS name of the Application Load Balancer (HTTPS endpoint)
    Condition: CreateHTTPSResources
    Value: !GetAtt ApplicationLoadBalancer.DNSName
  ALBFullURL:
    Description: Full HTTPS URL for the API
    Condition: CreateHTTPSResources
    Value: !Sub 'https://${ApplicationLoadBalancer.DNSName}'
  HTTPSEnabled:
    Description: Whether HTTPS is enabled for this deployment
    Value: !Ref EnableHTTPS
