Changed project name to be IPTV Manager Service
All checks were successful
AWS Deploy on Push / build (push) Successful in 8m29s
All checks were successful
AWS Deploy on Push / build (push) Successful in 8m29s
This commit is contained in:
@@ -4,7 +4,7 @@ MOCK_AUTH=true/false
|
|||||||
DB_USER=MyDBUser
|
DB_USER=MyDBUser
|
||||||
DB_PASSWORD=MyDBPassword
|
DB_PASSWORD=MyDBPassword
|
||||||
DB_HOST=MyDBHost
|
DB_HOST=MyDBHost
|
||||||
DB_NAME=iptv_updater
|
DB_NAME=iptv_manager
|
||||||
|
|
||||||
FREEDNS_User=MyFreeDNSUsername
|
FREEDNS_User=MyFreeDNSUsername
|
||||||
FREEDNS_Password=MyFreeDNSPassword
|
FREEDNS_Password=MyFreeDNSPassword
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
INSTANCE_IDS=$(aws ec2 describe-instances \
|
INSTANCE_IDS=$(aws ec2 describe-instances \
|
||||||
--region us-east-2 \
|
--region us-east-2 \
|
||||||
--filters "Name=tag:Name,Values=IptvUpdaterStack/IptvUpdaterInstance" \
|
--filters "Name=tag:Name,Values=IptvManagerStack/IptvManagerInstance" \
|
||||||
"Name=instance-state-name,Values=running" \
|
"Name=instance-state-name,Values=running" \
|
||||||
--query "Reservations[].Instances[].InstanceId" \
|
--query "Reservations[].Instances[].InstanceId" \
|
||||||
--output text)
|
--output text)
|
||||||
@@ -69,11 +69,11 @@ jobs:
|
|||||||
--instance-ids "$INSTANCE_ID" \
|
--instance-ids "$INSTANCE_ID" \
|
||||||
--document-name "AWS-RunShellScript" \
|
--document-name "AWS-RunShellScript" \
|
||||||
--parameters 'commands=[
|
--parameters 'commands=[
|
||||||
"cd /home/ec2-user/iptv-updater-aws",
|
"cd /home/ec2-user/iptv-manager-service",
|
||||||
"git pull",
|
"git pull",
|
||||||
"pip3 install -r requirements.txt",
|
"pip3 install -r requirements.txt",
|
||||||
"alembic upgrade head",
|
"alembic upgrade head",
|
||||||
"sudo systemctl restart iptv-updater"
|
"sudo systemctl restart iptv-manager"
|
||||||
]'
|
]'
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.2
|
rev: v0.11.12
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|||||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"python.terminal.activateEnvironment": true,
|
||||||
|
"python.terminal.activateEnvInCurrentTerminal": true,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||||
"ruff.importStrategy": "fromEnvironment",
|
"ruff.importStrategy": "fromEnvironment",
|
||||||
@@ -31,10 +33,13 @@
|
|||||||
"cluflogo",
|
"cluflogo",
|
||||||
"clulogo",
|
"clulogo",
|
||||||
"cpulogo",
|
"cpulogo",
|
||||||
|
"crond",
|
||||||
|
"cronie",
|
||||||
"cuflgo",
|
"cuflgo",
|
||||||
"CUNF",
|
"CUNF",
|
||||||
"cunflogo",
|
"cunflogo",
|
||||||
"cuulogo",
|
"cuulogo",
|
||||||
|
"datname",
|
||||||
"deadstreams",
|
"deadstreams",
|
||||||
"delenv",
|
"delenv",
|
||||||
"delogo",
|
"delogo",
|
||||||
@@ -50,6 +55,7 @@
|
|||||||
"freedns",
|
"freedns",
|
||||||
"fullchain",
|
"fullchain",
|
||||||
"gitea",
|
"gitea",
|
||||||
|
"httpx",
|
||||||
"iptv",
|
"iptv",
|
||||||
"isort",
|
"isort",
|
||||||
"KHTML",
|
"KHTML",
|
||||||
@@ -61,7 +67,9 @@
|
|||||||
"ondelete",
|
"ondelete",
|
||||||
"onupdate",
|
"onupdate",
|
||||||
"passlib",
|
"passlib",
|
||||||
|
"PGPASSWORD",
|
||||||
"poolclass",
|
"poolclass",
|
||||||
|
"psql",
|
||||||
"psycopg",
|
"psycopg",
|
||||||
"pycache",
|
"pycache",
|
||||||
"pycodestyle",
|
"pycodestyle",
|
||||||
@@ -82,6 +90,7 @@
|
|||||||
"testdb",
|
"testdb",
|
||||||
"testpass",
|
"testpass",
|
||||||
"testpaths",
|
"testpaths",
|
||||||
|
"testuser",
|
||||||
"uflogo",
|
"uflogo",
|
||||||
"umlogo",
|
"umlogo",
|
||||||
"usefixtures",
|
"usefixtures",
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -1,6 +1,6 @@
|
|||||||
# IPTV Updater AWS
|
# IPTV Manager Service
|
||||||
|
|
||||||
An automated IPTV playlist and EPG updater service deployed on AWS infrastructure using CDK.
|
An automated IPTV playlist and EPG manager service deployed on AWS infrastructure using CDK.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ This project provides a service for automatically updating IPTV playlists and El
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone <repo-url>
|
git clone <repo-url>
|
||||||
cd iptv-updater-aws
|
cd iptv-manager-service
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Copy the example environment file:
|
2. Copy the example environment file:
|
||||||
@@ -144,13 +144,13 @@ scripts/ # Utility scripts for deployment and management
|
|||||||
|
|
||||||
The following environment variables are required:
|
The following environment variables are required:
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|----------|-------------|
|
| ----------------- | ------------------------------------ |
|
||||||
| FREEDNS_User | FreeDNS username |
|
| FREEDNS_User | FreeDNS username |
|
||||||
| FREEDNS_Password | FreeDNS password |
|
| FREEDNS_Password | FreeDNS password |
|
||||||
| DOMAIN_NAME | Your domain name |
|
| DOMAIN_NAME | Your domain name |
|
||||||
| SSH_PUBLIC_KEY | SSH public key for EC2 access |
|
| SSH_PUBLIC_KEY | SSH public key for EC2 access |
|
||||||
| REPO_URL | Repository URL |
|
| REPO_URL | Repository URL |
|
||||||
| LETSENCRYPT_EMAIL | Email for Let's Encrypt certificates |
|
| LETSENCRYPT_EMAIL | Email for Let's Encrypt certificates |
|
||||||
|
|
||||||
## Security Notes
|
## Security Notes
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ config = context.config
|
|||||||
if config.config_file_name is not None:
|
if config.config_file_name is not None:
|
||||||
fileConfig(config.config_file_name)
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
# Setup target metadata for autogenerate support
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
target_metadata = Base.metadata
|
target_metadata = Base.metadata
|
||||||
|
|
||||||
# Override sqlalchemy.url with dynamic credentials
|
# Override sqlalchemy.url with dynamic credentials
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
"""Add priority and in_use fields
|
|
||||||
|
|
||||||
Revision ID: 036879e47172
|
|
||||||
Revises:
|
|
||||||
Create Date: 2025-05-26 19:21:32.285656
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '036879e47172'
|
|
||||||
down_revision: Union[str, None] = None
|
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
"""Upgrade schema."""
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
# 1. Create priorities table if not exists
|
|
||||||
if not op.get_bind().engine.dialect.has_table(op.get_bind(), 'priorities'):
|
|
||||||
op.create_table('priorities',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('description', sa.String(), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. Insert default priorities (skip if already exists)
|
|
||||||
op.execute("""
|
|
||||||
INSERT INTO priorities (id, description)
|
|
||||||
VALUES (100, 'High'), (200, 'Medium'), (300, 'Low')
|
|
||||||
ON CONFLICT (id) DO NOTHING
|
|
||||||
""")
|
|
||||||
# Add new columns with temporary nullable=True
|
|
||||||
op.add_column('channels_urls', sa.Column('in_use', sa.Boolean(), nullable=True))
|
|
||||||
op.add_column('channels_urls', sa.Column('priority_id', sa.Integer(), nullable=True))
|
|
||||||
|
|
||||||
# Set default values
|
|
||||||
op.execute("UPDATE channels_urls SET in_use = false, priority_id = 100")
|
|
||||||
|
|
||||||
# Convert to NOT NULL
|
|
||||||
op.alter_column('channels_urls', 'in_use', nullable=False)
|
|
||||||
op.alter_column('channels_urls', 'priority_id', nullable=False)
|
|
||||||
op.create_foreign_key(None, 'channels_urls', 'priorities', ['priority_id'], ['id'])
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
"""Downgrade schema."""
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_constraint('channels_urls_priority_id_fkey', 'channels_urls', type_='foreignkey')
|
|
||||||
op.drop_column('channels_urls', 'priority_id')
|
|
||||||
op.drop_column('channels_urls', 'in_use')
|
|
||||||
op.drop_table('priorities')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
79
alembic/versions/95b61a92455a_create_initial_tables.py
Normal file
79
alembic/versions/95b61a92455a_create_initial_tables.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"""create initial tables
|
||||||
|
|
||||||
|
Revision ID: 95b61a92455a
|
||||||
|
Revises:
|
||||||
|
Create Date: 2025-05-29 14:42:16.239587
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '95b61a92455a'
|
||||||
|
down_revision: Union[str, None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('channels',
|
||||||
|
sa.Column('id', sa.UUID(), nullable=False),
|
||||||
|
sa.Column('tvg_id', sa.String(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(), nullable=False),
|
||||||
|
sa.Column('group_title', sa.String(), nullable=False),
|
||||||
|
sa.Column('tvg_name', sa.String(), nullable=True),
|
||||||
|
sa.Column('tvg_logo', sa.String(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('group_title', 'name', name='uix_group_title_name')
|
||||||
|
)
|
||||||
|
op.create_table('priorities',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('description', sa.String(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('channels_urls',
|
||||||
|
sa.Column('id', sa.UUID(), nullable=False),
|
||||||
|
sa.Column('channel_id', sa.UUID(), nullable=False),
|
||||||
|
sa.Column('url', sa.String(), nullable=False),
|
||||||
|
sa.Column('in_use', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('priority_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['channel_id'], ['channels.id'], ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['priority_id'], ['priorities.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
# Seed initial priorities
|
||||||
|
op.bulk_insert(
|
||||||
|
sa.Table(
|
||||||
|
'priorities',
|
||||||
|
sa.MetaData(),
|
||||||
|
sa.Column('id', sa.Integer),
|
||||||
|
sa.Column('description', sa.String),
|
||||||
|
),
|
||||||
|
[
|
||||||
|
{'id': 100, 'description': 'High'},
|
||||||
|
{'id': 200, 'description': 'Medium'},
|
||||||
|
{'id': 300, 'description': 'Low'},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# Remove seeded priorities
|
||||||
|
op.execute("DELETE FROM priorities WHERE id IN (100, 200, 300);")
|
||||||
|
|
||||||
|
# Drop tables
|
||||||
|
op.drop_table('channels_urls')
|
||||||
|
op.drop_table('priorities')
|
||||||
|
op.drop_table('channels')
|
||||||
6
app.py
6
app.py
@@ -3,7 +3,7 @@ import os
|
|||||||
|
|
||||||
import aws_cdk as cdk
|
import aws_cdk as cdk
|
||||||
|
|
||||||
from infrastructure.stack import IptvUpdaterStack
|
from infrastructure.stack import IptvManagerStack
|
||||||
|
|
||||||
app = cdk.App()
|
app = cdk.App()
|
||||||
|
|
||||||
@@ -31,9 +31,9 @@ if missing_vars:
|
|||||||
f"Missing required environment variables: {', '.join(missing_vars)}"
|
f"Missing required environment variables: {', '.join(missing_vars)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
IptvUpdaterStack(
|
IptvManagerStack(
|
||||||
app,
|
app,
|
||||||
"IptvUpdaterStack",
|
"IptvManagerStack",
|
||||||
freedns_user=freedns_user,
|
freedns_user=freedns_user,
|
||||||
freedns_password=freedns_password,
|
freedns_password=freedns_password,
|
||||||
domain_name=domain_name,
|
domain_name=domain_name,
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
title="IPTV Updater API",
|
title="IPTV Manager API",
|
||||||
description="API for IPTV Updater service",
|
description="API for IPTV Manager service",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ app.openapi = custom_openapi
|
|||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
return {"message": "IPTV Updater API"}
|
return {"message": "IPTV Manager API"}
|
||||||
|
|
||||||
|
|
||||||
# Include routers
|
# Include routers
|
||||||
|
|||||||
@@ -19,16 +19,16 @@ def get_db_credentials():
|
|||||||
|
|
||||||
ssm = boto3.client("ssm", region_name=AWS_REGION)
|
ssm = boto3.client("ssm", region_name=AWS_REGION)
|
||||||
try:
|
try:
|
||||||
host = ssm.get_parameter(Name="/iptv-updater/DB_HOST", WithDecryption=True)[
|
host = ssm.get_parameter(Name="/iptv-manager/DB_HOST", WithDecryption=True)[
|
||||||
"Parameter"
|
"Parameter"
|
||||||
]["Value"]
|
]["Value"]
|
||||||
user = ssm.get_parameter(Name="/iptv-updater/DB_USER", WithDecryption=True)[
|
user = ssm.get_parameter(Name="/iptv-manager/DB_USER", WithDecryption=True)[
|
||||||
"Parameter"
|
"Parameter"
|
||||||
]["Value"]
|
]["Value"]
|
||||||
password = ssm.get_parameter(
|
password = ssm.get_parameter(
|
||||||
Name="/iptv-updater/DB_PASSWORD", WithDecryption=True
|
Name="/iptv-manager/DB_PASSWORD", WithDecryption=True
|
||||||
)["Parameter"]["Value"]
|
)["Parameter"]["Value"]
|
||||||
dbname = ssm.get_parameter(Name="/iptv-updater/DB_NAME", WithDecryption=True)[
|
dbname = ssm.get_parameter(Name="/iptv-manager/DB_NAME", WithDecryption=True)[
|
||||||
"Parameter"
|
"Parameter"
|
||||||
]["Value"]
|
]["Value"]
|
||||||
return f"postgresql://{user}:{password}@{host}/{dbname}"
|
return f"postgresql://{user}:{password}@{host}/{dbname}"
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ version: '3.8'
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13
|
image: postgres:13
|
||||||
|
container_name: postgres
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: iptv_updater
|
POSTGRES_DB: iptv_manager
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: iptv_updater
|
POSTGRES_DB: iptv_manager
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -20,7 +20,7 @@ services:
|
|||||||
DB_USER: postgres
|
DB_USER: postgres
|
||||||
DB_PASSWORD: postgres
|
DB_PASSWORD: postgres
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
DB_NAME: iptv_updater
|
DB_NAME: iptv_manager
|
||||||
MOCK_AUTH: "true"
|
MOCK_AUTH: "true"
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from aws_cdk import aws_ssm as ssm
|
|||||||
from constructs import Construct
|
from constructs import Construct
|
||||||
|
|
||||||
|
|
||||||
class IptvUpdaterStack(Stack):
|
class IptvManagerStack(Stack):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
scope: Construct,
|
scope: Construct,
|
||||||
@@ -27,7 +27,7 @@ class IptvUpdaterStack(Stack):
|
|||||||
# Create VPC
|
# Create VPC
|
||||||
vpc = ec2.Vpc(
|
vpc = ec2.Vpc(
|
||||||
self,
|
self,
|
||||||
"IptvUpdaterVPC",
|
"IptvManagerVPC",
|
||||||
max_azs=2, # Need at least 2 AZs for RDS subnet group
|
max_azs=2, # Need at least 2 AZs for RDS subnet group
|
||||||
nat_gateways=0, # No NAT Gateway to stay in free tier
|
nat_gateways=0, # No NAT Gateway to stay in free tier
|
||||||
subnet_configuration=[
|
subnet_configuration=[
|
||||||
@@ -44,7 +44,7 @@ class IptvUpdaterStack(Stack):
|
|||||||
|
|
||||||
# Security Group
|
# Security Group
|
||||||
security_group = ec2.SecurityGroup(
|
security_group = ec2.SecurityGroup(
|
||||||
self, "IptvUpdaterSG", vpc=vpc, allow_all_outbound=True
|
self, "IptvManagerSG", vpc=vpc, allow_all_outbound=True
|
||||||
)
|
)
|
||||||
|
|
||||||
security_group.add_ingress_rule(
|
security_group.add_ingress_rule(
|
||||||
@@ -66,18 +66,18 @@ class IptvUpdaterStack(Stack):
|
|||||||
"Allow PostgreSQL traffic for tunneling",
|
"Allow PostgreSQL traffic for tunneling",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Key pair for IPTV Updater instance
|
# Key pair for IPTV Manager instance
|
||||||
key_pair = ec2.KeyPair(
|
key_pair = ec2.KeyPair(
|
||||||
self,
|
self,
|
||||||
"IptvUpdaterKeyPair",
|
"IptvManagerKeyPair",
|
||||||
key_pair_name="iptv-updater-key",
|
key_pair_name="iptv-manager-key",
|
||||||
public_key_material=ssh_public_key,
|
public_key_material=ssh_public_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create IAM role for EC2
|
# Create IAM role for EC2
|
||||||
role = iam.Role(
|
role = iam.Role(
|
||||||
self,
|
self,
|
||||||
"IptvUpdaterRole",
|
"IptvManagerRole",
|
||||||
assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
|
assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ class IptvUpdaterStack(Stack):
|
|||||||
# EC2 Instance
|
# EC2 Instance
|
||||||
instance = ec2.Instance(
|
instance = ec2.Instance(
|
||||||
self,
|
self,
|
||||||
"IptvUpdaterInstance",
|
"IptvManagerInstance",
|
||||||
vpc=vpc,
|
vpc=vpc,
|
||||||
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC),
|
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PUBLIC),
|
||||||
instance_type=ec2.InstanceType.of(
|
instance_type=ec2.InstanceType.of(
|
||||||
@@ -132,7 +132,7 @@ class IptvUpdaterStack(Stack):
|
|||||||
|
|
||||||
# Option: 2: Create Elastic IP (not free tier compatible)
|
# Option: 2: Create Elastic IP (not free tier compatible)
|
||||||
# eip = ec2.CfnEIP(
|
# eip = ec2.CfnEIP(
|
||||||
# self, "IptvUpdaterEIP",
|
# self, "IptvManagerEIP",
|
||||||
# domain="vpc",
|
# domain="vpc",
|
||||||
# instance_id=instance.instance_id
|
# instance_id=instance.instance_id
|
||||||
# )
|
# )
|
||||||
@@ -140,8 +140,8 @@ class IptvUpdaterStack(Stack):
|
|||||||
# Add Cognito User Pool
|
# Add Cognito User Pool
|
||||||
user_pool = cognito.UserPool(
|
user_pool = cognito.UserPool(
|
||||||
self,
|
self,
|
||||||
"IptvUpdaterUserPool",
|
"IptvManagerUserPool",
|
||||||
user_pool_name="iptv-updater-users",
|
user_pool_name="iptv-manager-users",
|
||||||
self_sign_up_enabled=False, # Only admins can create users
|
self_sign_up_enabled=False, # Only admins can create users
|
||||||
password_policy=cognito.PasswordPolicy(
|
password_policy=cognito.PasswordPolicy(
|
||||||
min_length=8,
|
min_length=8,
|
||||||
@@ -156,7 +156,7 @@ class IptvUpdaterStack(Stack):
|
|||||||
|
|
||||||
# Add App Client with the correct callback URL
|
# Add App Client with the correct callback URL
|
||||||
client = user_pool.add_client(
|
client = user_pool.add_client(
|
||||||
"IptvUpdaterClient",
|
"IptvManagerClient",
|
||||||
access_token_validity=Duration.minutes(60),
|
access_token_validity=Duration.minutes(60),
|
||||||
id_token_validity=Duration.minutes(60),
|
id_token_validity=Duration.minutes(60),
|
||||||
refresh_token_validity=Duration.days(1),
|
refresh_token_validity=Duration.days(1),
|
||||||
@@ -171,8 +171,8 @@ class IptvUpdaterStack(Stack):
|
|||||||
|
|
||||||
# Add domain for hosted UI
|
# Add domain for hosted UI
|
||||||
domain = user_pool.add_domain(
|
domain = user_pool.add_domain(
|
||||||
"IptvUpdaterDomain",
|
"IptvManagerDomain",
|
||||||
cognito_domain=cognito.CognitoDomainOptions(domain_prefix="iptv-updater"),
|
cognito_domain=cognito.CognitoDomainOptions(domain_prefix="iptv-manager"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read the userdata script with proper path resolution
|
# Read the userdata script with proper path resolution
|
||||||
@@ -226,7 +226,7 @@ class IptvUpdaterStack(Stack):
|
|||||||
# Create RDS PostgreSQL instance (free tier compatible - db.t3.micro)
|
# Create RDS PostgreSQL instance (free tier compatible - db.t3.micro)
|
||||||
db = rds.DatabaseInstance(
|
db = rds.DatabaseInstance(
|
||||||
self,
|
self,
|
||||||
"IptvUpdaterDB",
|
"IptvManagerDB",
|
||||||
engine=rds.DatabaseInstanceEngine.postgres(
|
engine=rds.DatabaseInstanceEngine.postgres(
|
||||||
version=rds.PostgresEngineVersion.VER_13
|
version=rds.PostgresEngineVersion.VER_13
|
||||||
),
|
),
|
||||||
@@ -240,7 +240,7 @@ class IptvUpdaterStack(Stack):
|
|||||||
security_groups=[rds_sg],
|
security_groups=[rds_sg],
|
||||||
allocated_storage=10,
|
allocated_storage=10,
|
||||||
max_allocated_storage=10,
|
max_allocated_storage=10,
|
||||||
database_name="iptv_updater",
|
database_name="iptv_manager",
|
||||||
removal_policy=RemovalPolicy.DESTROY,
|
removal_policy=RemovalPolicy.DESTROY,
|
||||||
deletion_protection=False,
|
deletion_protection=False,
|
||||||
publicly_accessible=False, # Avoid public IPv4 charges
|
publicly_accessible=False, # Avoid public IPv4 charges
|
||||||
@@ -255,25 +255,25 @@ class IptvUpdaterStack(Stack):
|
|||||||
ssm.StringParameter(
|
ssm.StringParameter(
|
||||||
self,
|
self,
|
||||||
"DBHostParam",
|
"DBHostParam",
|
||||||
parameter_name="/iptv-updater/DB_HOST",
|
parameter_name="/iptv-manager/DB_HOST",
|
||||||
string_value=db.db_instance_endpoint_address,
|
string_value=db.db_instance_endpoint_address,
|
||||||
)
|
)
|
||||||
ssm.StringParameter(
|
ssm.StringParameter(
|
||||||
self,
|
self,
|
||||||
"DBNameParam",
|
"DBNameParam",
|
||||||
parameter_name="/iptv-updater/DB_NAME",
|
parameter_name="/iptv-manager/DB_NAME",
|
||||||
string_value="iptv_updater",
|
string_value="iptv_manager",
|
||||||
)
|
)
|
||||||
ssm.StringParameter(
|
ssm.StringParameter(
|
||||||
self,
|
self,
|
||||||
"DBUserParam",
|
"DBUserParam",
|
||||||
parameter_name="/iptv-updater/DB_USER",
|
parameter_name="/iptv-manager/DB_USER",
|
||||||
string_value=db.secret.secret_value_from_json("username").to_string(),
|
string_value=db.secret.secret_value_from_json("username").to_string(),
|
||||||
)
|
)
|
||||||
ssm.StringParameter(
|
ssm.StringParameter(
|
||||||
self,
|
self,
|
||||||
"DBPassParam",
|
"DBPassParam",
|
||||||
parameter_name="/iptv-updater/DB_PASSWORD",
|
parameter_name="/iptv-manager/DB_PASSWORD",
|
||||||
string_value=db.secret.secret_value_from_json("password").to_string(),
|
string_value=db.secret.secret_value_from_json("password").to_string(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Update system and install required packages
|
# Update system and install required packages
|
||||||
dnf update -y
|
dnf update -y
|
||||||
dnf install -y python3-pip git cronie nginx certbot python3-certbot-nginx
|
dnf install -y python3-pip git cronie nginx certbot python3-certbot-nginx postgresql awscli
|
||||||
|
|
||||||
# Start and enable crond service
|
# Start and enable crond service
|
||||||
systemctl start crond
|
systemctl start crond
|
||||||
@@ -11,27 +11,48 @@ systemctl enable crond
|
|||||||
cd /home/ec2-user
|
cd /home/ec2-user
|
||||||
|
|
||||||
git clone ${REPO_URL}
|
git clone ${REPO_URL}
|
||||||
cd iptv-updater-aws
|
cd iptv-manager-service
|
||||||
|
|
||||||
# Install Python packages with --ignore-installed to prevent conflicts with RPM packages
|
# Install Python packages with --ignore-installed to prevent conflicts with RPM packages
|
||||||
pip3 install --ignore-installed -r requirements.txt
|
pip3 install --ignore-installed -r requirements.txt
|
||||||
|
|
||||||
|
# Retrieve DB credentials from SSM Parameter Store
|
||||||
|
export DB_HOST=$(aws ssm get-parameter --name "/iptv-manager/DB_HOST" --query "Parameter.Value" --output text)
|
||||||
|
export DB_NAME=$(aws ssm get-parameter --name "/iptv-manager/DB_NAME" --query "Parameter.Value" --output text)
|
||||||
|
export DB_USER=$(aws ssm get-parameter --name "/iptv-manager/DB_USER" --query "Parameter.Value" --output text)
|
||||||
|
export DB_PASSWORD=$(aws ssm get-parameter --name "/iptv-manager/DB_PASSWORD" --query "Parameter.Value" --output text)
|
||||||
|
|
||||||
|
# Set PGPASSWORD for psql to use
|
||||||
|
export PGPASSWORD=$DB_PASSWORD
|
||||||
|
|
||||||
|
# Wait for PostgreSQL to be ready
|
||||||
|
echo "Waiting for PostgreSQL to start..."
|
||||||
|
until psql -h $DB_HOST -U $DB_USER -d postgres -c '\q'; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "PostgreSQL is ready."
|
||||||
|
|
||||||
|
# Create database if it does not exist
|
||||||
|
DB_EXISTS=$(psql -h $DB_HOST -U $DB_USER -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = '$DB_NAME';")
|
||||||
|
if [ -z "$DB_EXISTS" ]; then
|
||||||
|
echo "Creating database $DB_NAME..."
|
||||||
|
psql -h $DB_HOST -U $DB_USER -d postgres -c "CREATE DATABASE $DB_NAME;"
|
||||||
|
echo "Database $DB_NAME created."
|
||||||
|
fi
|
||||||
|
|
||||||
# Run database migrations
|
# Run database migrations
|
||||||
alembic upgrade head
|
alembic upgrade head
|
||||||
|
|
||||||
# Seed initial priorities
|
|
||||||
python3 -c "from app.utils.database import SessionLocal; from app.models.db import Priority; db = SessionLocal(); db.add_all([Priority(id=100, description='High'), Priority(id=200, description='Medium'), Priority(id=300, description='Low')]); db.commit()"
|
|
||||||
|
|
||||||
# Create systemd service file
|
# Create systemd service file
|
||||||
cat << 'EOF' > /etc/systemd/system/iptv-updater.service
|
cat << 'EOF' > /etc/systemd/system/iptv-manager.service
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=IPTV Updater Service
|
Description=IPTV Manager Service
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=ec2-user
|
User=ec2-user
|
||||||
WorkingDirectory=/home/ec2-user/iptv-updater-aws
|
WorkingDirectory=/home/ec2-user/iptv-manager-service
|
||||||
ExecStart=/usr/local/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
|
ExecStart=/usr/local/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||||||
EnvironmentFile=/etc/environment
|
EnvironmentFile=/etc/environment
|
||||||
Restart=always
|
Restart=always
|
||||||
@@ -56,7 +77,7 @@ sudo mkdir -p /etc/nginx/ssl
|
|||||||
--reloadcmd "service nginx force-reload"
|
--reloadcmd "service nginx force-reload"
|
||||||
|
|
||||||
# Create nginx config
|
# Create nginx config
|
||||||
cat << EOF > /etc/nginx/conf.d/iptvUpdater.conf
|
cat << EOF > /etc/nginx/conf.d/iptvManager.conf
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name ${DOMAIN_NAME} *.${DOMAIN_NAME};
|
server_name ${DOMAIN_NAME} *.${DOMAIN_NAME};
|
||||||
@@ -83,5 +104,5 @@ EOF
|
|||||||
# Start nginx service
|
# Start nginx service
|
||||||
systemctl enable nginx
|
systemctl enable nginx
|
||||||
systemctl start nginx
|
systemctl start nginx
|
||||||
systemctl enable iptv-updater
|
systemctl enable iptv-manager
|
||||||
systemctl start iptv-updater
|
systemctl start iptv-manager
|
||||||
@@ -5,12 +5,21 @@ python_functions = test_*
|
|||||||
asyncio_mode = auto
|
asyncio_mode = auto
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
ignore::DeprecationWarning:botocore.auth
|
ignore::DeprecationWarning:botocore.auth
|
||||||
|
ignore:The 'app' shortcut is now deprecated:DeprecationWarning:httpx._client
|
||||||
|
|
||||||
# Coverage configuration
|
# Coverage configuration
|
||||||
addopts =
|
addopts =
|
||||||
--cov=app
|
--cov=app
|
||||||
--cov-report=term-missing
|
--cov-report=term-missing
|
||||||
|
|
||||||
|
# Test environment variables
|
||||||
|
env =
|
||||||
|
MOCK_AUTH=true
|
||||||
|
DB_USER=test_user
|
||||||
|
DB_PASSWORD=test_password
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_NAME=iptv_manager_test
|
||||||
|
|
||||||
# Test markers
|
# Test markers
|
||||||
markers =
|
markers =
|
||||||
slow: mark tests as slow running
|
slow: mark tests as slow running
|
||||||
|
|||||||
@@ -14,4 +14,8 @@ psycopg2-binary==2.9.9
|
|||||||
alembic==1.16.1
|
alembic==1.16.1
|
||||||
pytest==8.1.1
|
pytest==8.1.1
|
||||||
pytest-asyncio==0.23.6
|
pytest-asyncio==0.23.6
|
||||||
pytest-mock==3.12.0
|
pytest-mock==3.12.0
|
||||||
|
pytest-cov==4.1.0
|
||||||
|
pytest-env==1.1.1
|
||||||
|
httpx==0.27.0
|
||||||
|
pre-commit
|
||||||
@@ -25,7 +25,7 @@ cdk deploy --app="python3 ${PWD}/app.py"
|
|||||||
# Update application on running instances
|
# Update application on running instances
|
||||||
INSTANCE_IDS=$(aws ec2 describe-instances \
|
INSTANCE_IDS=$(aws ec2 describe-instances \
|
||||||
--region us-east-2 \
|
--region us-east-2 \
|
||||||
--filters "Name=tag:Name,Values=IptvUpdaterStack/IptvUpdaterInstance" \
|
--filters "Name=tag:Name,Values=IptvManagerStack/IptvManagerInstance" \
|
||||||
"Name=instance-state-name,Values=running" \
|
"Name=instance-state-name,Values=running" \
|
||||||
--query "Reservations[].Instances[].InstanceId" \
|
--query "Reservations[].Instances[].InstanceId" \
|
||||||
--output text)
|
--output text)
|
||||||
@@ -35,7 +35,7 @@ for INSTANCE_ID in $INSTANCE_IDS; do
|
|||||||
aws ssm send-command \
|
aws ssm send-command \
|
||||||
--instance-ids "$INSTANCE_ID" \
|
--instance-ids "$INSTANCE_ID" \
|
||||||
--document-name "AWS-RunShellScript" \
|
--document-name "AWS-RunShellScript" \
|
||||||
--parameters '{"commands":["cd /home/ec2-user/iptv-updater-aws && git pull && pip3 install -r requirements.txt && alembic upgrade head && sudo systemctl restart iptv-updater"]}' \
|
--parameters '{"commands":["cd /home/ec2-user/iptv-manager-service && git pull && pip3 install -r requirements.txt && alembic upgrade head && sudo systemctl restart iptv-manager"]}' \
|
||||||
--no-cli-pager \
|
--no-cli-pager \
|
||||||
--no-paginate
|
--no-paginate
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -10,10 +10,4 @@ pre-commit install-hooks
|
|||||||
pre-commit autoupdate
|
pre-commit autoupdate
|
||||||
|
|
||||||
# Verify pytest setup
|
# Verify pytest setup
|
||||||
python3 -m pytest
|
python3 -m pytest
|
||||||
|
|
||||||
# Initialize and run database migrations
|
|
||||||
alembic upgrade head
|
|
||||||
|
|
||||||
# Seed initial data
|
|
||||||
python3 -c "from app.utils.database import SessionLocal; from app.models.db import Priority; db = SessionLocal(); db.add_all([Priority(id=100, description='High'), Priority(id=200, description='Medium'), Priority(id=300, description='Low')]); db.commit()"
|
|
||||||
@@ -1,21 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
# Start PostgreSQL
|
# Start PostgreSQL
|
||||||
docker-compose -f docker/docker-compose-db.yml up -d
|
docker-compose -f docker/docker-compose-db.yml up -d
|
||||||
|
|
||||||
# Set mock auth and database environment variables
|
# Set environment variables
|
||||||
export MOCK_AUTH=true
|
export MOCK_AUTH=true
|
||||||
|
export DB_HOST=localhost
|
||||||
export DB_USER=postgres
|
export DB_USER=postgres
|
||||||
export DB_PASSWORD=postgres
|
export DB_PASSWORD=postgres
|
||||||
export DB_HOST=localhost
|
export DB_NAME=iptv_manager
|
||||||
export DB_NAME=iptv_updater
|
|
||||||
|
echo "Ensuring database $DB_NAME exists using conditional DDL..."
|
||||||
|
PGPASSWORD=$DB_PASSWORD docker exec -i postgres psql -U $DB_USER <<< "SELECT 'CREATE DATABASE $DB_NAME' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$DB_NAME')\gexec"
|
||||||
|
echo "Database $DB_NAME check complete."
|
||||||
|
|
||||||
# Run database migrations
|
# Run database migrations
|
||||||
alembic upgrade head
|
alembic upgrade head
|
||||||
|
|
||||||
# Start FastAPI
|
# Start FastAPI
|
||||||
nohup uvicorn app.main:app --host 127.0.0.1 --port 8000 > app.log 2>&1 &
|
nohup uvicorn app.main:app --host 127.0.0.1 --port 8000 > app.log 2>&1 &
|
||||||
echo $! > iptv-updater.pid
|
echo $! > iptv-manager.pid
|
||||||
|
|
||||||
echo "Services started:"
|
echo "Services started:"
|
||||||
echo "- PostgreSQL running on localhost:5432"
|
echo "- PostgreSQL running on localhost:5432"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Stop FastAPI
|
# Stop FastAPI
|
||||||
if [ -f iptv-updater.pid ]; then
|
if [ -f iptv-manager.pid ]; then
|
||||||
kill $(cat iptv-updater.pid)
|
kill $(cat iptv-manager.pid)
|
||||||
rm iptv-updater.pid
|
rm iptv-manager.pid
|
||||||
echo "Stopped FastAPI"
|
echo "Stopped FastAPI"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def test_root_endpoint(client):
|
|||||||
"""Test root endpoint returns expected message"""
|
"""Test root endpoint returns expected message"""
|
||||||
response = client.get("/")
|
response = client.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"message": "IPTV Updater API"}
|
assert response.json() == {"message": "IPTV Manager API"}
|
||||||
|
|
||||||
|
|
||||||
def test_openapi_schema_generation(client):
|
def test_openapi_schema_generation(client):
|
||||||
|
|||||||
Reference in New Issue
Block a user