import os from aws_cdk import ( Duration, RemovalPolicy, Stack, aws_ec2 as ec2, aws_iam as iam, aws_cognito as cognito, aws_rds as rds, CfnOutput ) from constructs import Construct class IptvUpdaterStack(Stack): def __init__( self, scope: Construct, construct_id: str, freedns_user: str, freedns_password: str, domain_name: str, ssh_public_key: str, repo_url: str, letsencrypt_email: str, **kwargs ) -> None: super().__init__(scope, construct_id, **kwargs) # Create VPC vpc = ec2.Vpc(self, "IptvUpdaterVPC", max_azs=1, # Use only one AZ for free tier nat_gateways=0, # No NAT Gateway to stay in free tier subnet_configuration=[ ec2.SubnetConfiguration( name="public", subnet_type=ec2.SubnetType.PUBLIC, cidr_mask=24 ) ] ) # Security Group security_group = ec2.SecurityGroup( self, "IptvUpdaterSG", vpc=vpc, allow_all_outbound=True ) security_group.add_ingress_rule( ec2.Peer.any_ipv4(), ec2.Port.tcp(443), "Allow HTTPS traffic" ) security_group.add_ingress_rule( ec2.Peer.any_ipv4(), ec2.Port.tcp(80), "Allow HTTP traffic" ) security_group.add_ingress_rule( ec2.Peer.any_ipv4(), ec2.Port.tcp(22), "Allow SSH traffic" ) # Key pair for IPTV Updater instance key_pair = ec2.KeyPair( self, "IptvUpdaterKeyPair", key_pair_name="iptv-updater-key", public_key_material=ssh_public_key ) # Create IAM role for EC2 role = iam.Role( self, "IptvUpdaterRole", assumed_by=iam.ServicePrincipal("ec2.amazonaws.com") ) # Add SSM managed policy role.add_managed_policy( iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonSSMManagedInstanceCore" ) ) # Add Cognito permissions to instance role role.add_managed_policy( iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonCognitoReadOnly" ) ) # EC2 Instance instance = ec2.Instance( self, "IptvUpdaterInstance", vpc=vpc, instance_type=ec2.InstanceType.of( ec2.InstanceClass.T2, ec2.InstanceSize.MICRO ), machine_image=ec2.AmazonLinuxImage( generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 ), security_group=security_group, key_pair=key_pair, role=role ) # Create Elastic IP eip = ec2.CfnEIP( self, "IptvUpdaterEIP", domain="vpc", instance_id=instance.instance_id ) # Add Cognito User Pool user_pool = cognito.UserPool( self, "IptvUpdaterUserPool", user_pool_name="iptv-updater-users", self_sign_up_enabled=False, # Only admins can create users password_policy=cognito.PasswordPolicy( min_length=8, require_lowercase=True, require_digits=True, require_symbols=True, require_uppercase=True ), account_recovery=cognito.AccountRecovery.EMAIL_ONLY, removal_policy=RemovalPolicy.DESTROY ) # Add App Client with the correct callback URL client = user_pool.add_client("IptvUpdaterClient", access_token_validity=Duration.minutes(60), id_token_validity=Duration.minutes(60), refresh_token_validity=Duration.days(1), auth_flows=cognito.AuthFlow( user_password=True ), o_auth=cognito.OAuthSettings( flows=cognito.OAuthFlows( implicit_code_grant=True ) ), prevent_user_existence_errors=True, generate_secret=True, enable_token_revocation=True ) # Add domain for hosted UI domain = user_pool.add_domain("IptvUpdaterDomain", cognito_domain=cognito.CognitoDomainOptions( domain_prefix="iptv-updater" ) ) # Read the userdata script with proper path resolution script_dir = os.path.dirname(os.path.abspath(__file__)) userdata_path = os.path.join(script_dir, "userdata.sh") userdata_file = open(userdata_path, "rb").read() # Creates a userdata object for Linux hosts userdata = ec2.UserData.for_linux() # Add environment variables for acme.sh from parameters userdata.add_commands( f'export FREEDNS_User="{freedns_user}"', f'export FREEDNS_Password="{freedns_password}"', f'export DOMAIN_NAME="{domain_name}"', f'export REPO_URL="{repo_url}"', f'export LETSENCRYPT_EMAIL="{letsencrypt_email}"' ) # Adds one or more commands to the userdata object. userdata.add_commands( f'echo "COGNITO_USER_POOL_ID={user_pool.user_pool_id}" >> /etc/environment', f'echo "COGNITO_CLIENT_ID={client.user_pool_client_id}" >> /etc/environment', f'echo "COGNITO_CLIENT_SECRET={client.user_pool_client_secret.to_string()}" >> /etc/environment', f'echo "DOMAIN_NAME={domain_name}" >> /etc/environment' ) userdata.add_commands(str(userdata_file, 'utf-8')) # Create RDS Security Group rds_sg = ec2.SecurityGroup( self, "RdsSecurityGroup", vpc=vpc, description="Security group for RDS PostgreSQL" ) rds_sg.add_ingress_rule( security_group, ec2.Port.tcp(5432), "Allow PostgreSQL access from EC2 instance" ) # Create RDS PostgreSQL instance (free tier compatible - db.t3.micro) db = rds.DatabaseInstance( self, "IptvUpdaterDB", engine=rds.DatabaseInstanceEngine.postgres( version=rds.PostgresEngineVersion.VER_13 ), instance_type=ec2.InstanceType.of( ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO ), vpc=vpc, security_groups=[rds_sg], allocated_storage=10, max_allocated_storage=10, database_name="iptv_updater", removal_policy=RemovalPolicy.DESTROY, deletion_protection=False, publicly_accessible=False ) # Add RDS permissions to instance role role.add_managed_policy( iam.ManagedPolicy.from_aws_managed_policy_name( "AmazonRDSFullAccess" ) ) # Update instance with userdata and DB connection info userdata.add_commands( f'echo "DB_HOST={db.db_instance_endpoint_address}" >> /etc/environment', f'echo "DB_NAME=iptv_updater" >> /etc/environment', f'echo "DB_USER={db.secret.secret_value_from_json("username").to_string()}" >> /etc/environment', f'echo "DB_PASSWORD={db.secret.secret_value_from_json("password").to_string()}" >> /etc/environment' ) instance.add_user_data(userdata.render()) # Outputs CfnOutput(self, "DBEndpoint", value=db.db_instance_endpoint_address) CfnOutput(self, "InstancePublicIP", value=eip.attr_public_ip) CfnOutput(self, "UserPoolId", value=user_pool.user_pool_id) CfnOutput(self, "UserPoolClientId", value=client.user_pool_client_id) CfnOutput(self, "CognitoDomainUrl", value=f"https://{domain.domain_name}.auth.{self.region}.amazoncognito.com" )