mysystem-cli 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +66 -0
- package/copy-templates.js +47 -0
- package/dist/commands/init.js +15 -2
- package/dist/utils/detector.js +84 -32
- package/package.json +2 -2
- package/src/commands/init.ts +15 -2
- package/src/utils/detector.ts +86 -29
- package/templates/docker/fastapi.Dockerfile +32 -0
- package/templates/docker/nextjs.Dockerfile +45 -0
- package/templates/docker/node.Dockerfile +32 -0
- package/templates/docker/react.Dockerfile +38 -0
- package/templates/github/deploy-ec2.yml +163 -0
- package/templates/github/deploy.yml +94 -0
- package/templates/github/destroy.yml +43 -0
- package/templates/terraform/alb.tf +69 -0
- package/templates/terraform/bootstrap-oidc.yaml +69 -0
- package/templates/terraform/budget.tf +40 -0
- package/templates/terraform/db.tf +46 -0
- package/templates/terraform/dns.tf +89 -0
- package/templates/terraform/ecs.tf +110 -0
- package/templates/terraform/outputs.tf +14 -0
- package/templates/terraform/provider.tf +21 -0
- package/templates/terraform/rds_proxy.tf +157 -0
- package/templates/terraform/redis.tf +69 -0
- package/templates/terraform/security.tf +156 -0
- package/templates/terraform/variables.tf +46 -0
- package/templates/terraform/vpc.tf +68 -0
- package/templates/terraform/waf.tf +97 -0
- package/templates/terraform-ec2/budget.tf +40 -0
- package/templates/terraform-ec2/dns.tf +85 -0
- package/templates/terraform-ec2/ec2.tf +124 -0
- package/templates/terraform-ec2/ecr.tf +10 -0
- package/templates/terraform-ec2/outputs.tf +19 -0
- package/templates/terraform-ec2/provider.tf +22 -0
- package/templates/terraform-ec2/variables.tf +58 -0
- package/templates/terraform-ec2/vpc.tf +50 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
name: MySystem EC2 Deployment
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
id-token: write
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
deploy:
|
|
15
|
+
name: Deploy to AWS EC2 (Hobbyist Tier)
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout Code
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
# --- Step 1: Parse MySystem Config File ---
|
|
23
|
+
- name: Parse MySystem Config
|
|
24
|
+
id: config
|
|
25
|
+
run: |
|
|
26
|
+
echo "APP_NAME=$(jq -r .name mysystem.json)" >> $GITHUB_ENV
|
|
27
|
+
echo "AWS_REGION=$(jq -r .region mysystem.json)" >> $GITHUB_ENV
|
|
28
|
+
echo "CONTAINER_PORT=$(jq -r .port mysystem.json)" >> $GITHUB_ENV
|
|
29
|
+
echo "BILLING_EMAIL=$(jq -r .billingEmail mysystem.json)" >> $GITHUB_ENV
|
|
30
|
+
echo "ENABLE_CUSTOM_DOMAIN=$(jq -r .customDomain mysystem.json)" >> $GITHUB_ENV
|
|
31
|
+
echo "DOMAIN_NAME=$(jq -r .domainName mysystem.json)" >> $GITHUB_ENV
|
|
32
|
+
echo "DNS_PROVIDER=$(jq -r .dnsProvider mysystem.json)" >> $GITHUB_ENV
|
|
33
|
+
|
|
34
|
+
# --- Step 2: Authenticate with AWS via OIDC ---
|
|
35
|
+
- name: Configure AWS Credentials
|
|
36
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
37
|
+
with:
|
|
38
|
+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
|
39
|
+
aws-region: ${{ env.AWS_REGION }}
|
|
40
|
+
audience: sts.amazonaws.com
|
|
41
|
+
|
|
42
|
+
# --- Step 3: Provision EC2 & ECR via Terraform ---
|
|
43
|
+
- name: Setup Terraform
|
|
44
|
+
uses: hashicorp/setup-terraform@v3
|
|
45
|
+
with:
|
|
46
|
+
terraform_version: 1.6.0
|
|
47
|
+
|
|
48
|
+
- name: Terraform Init & Apply
|
|
49
|
+
working-directory: ./terraform
|
|
50
|
+
env:
|
|
51
|
+
TF_VAR_aws_region: ${{ env.AWS_REGION }}
|
|
52
|
+
TF_VAR_app_name: ${{ env.APP_NAME }}
|
|
53
|
+
TF_VAR_container_port: ${{ env.CONTAINER_PORT }}
|
|
54
|
+
TF_VAR_billing_email: ${{ env.BILLING_EMAIL }}
|
|
55
|
+
TF_VAR_enable_custom_domain: ${{ env.ENABLE_CUSTOM_DOMAIN }}
|
|
56
|
+
TF_VAR_domain_name: ${{ env.DOMAIN_NAME }}
|
|
57
|
+
TF_VAR_dns_provider: ${{ env.DNS_PROVIDER }}
|
|
58
|
+
run: |
|
|
59
|
+
terraform init
|
|
60
|
+
terraform apply -auto-approve
|
|
61
|
+
|
|
62
|
+
# --- Step 4: Extract Terraform Outputs ---
|
|
63
|
+
- name: Get Outputs
|
|
64
|
+
id: tf_outputs
|
|
65
|
+
working-directory: ./terraform
|
|
66
|
+
run: |
|
|
67
|
+
echo "ECR_REPO=$(terraform output -raw ecr_repository_url)" >> $GITHUB_OUTPUT
|
|
68
|
+
echo "INSTANCE_ID=$(terraform output -raw instance_id)" >> $GITHUB_OUTPUT
|
|
69
|
+
echo "APP_URL=$(terraform output -raw application_url)" >> $GITHUB_OUTPUT
|
|
70
|
+
|
|
71
|
+
# --- Step 5: Login to Amazon ECR ---
|
|
72
|
+
- name: Login to Amazon ECR
|
|
73
|
+
id: login-ecr
|
|
74
|
+
uses: aws-actions/amazon-ecr-login@v2
|
|
75
|
+
|
|
76
|
+
# --- Step 6: Build and Push Docker Image ---
|
|
77
|
+
- name: Build, Tag, and Push Image to ECR
|
|
78
|
+
id: build-image
|
|
79
|
+
env:
|
|
80
|
+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
|
81
|
+
ECR_REPOSITORY: ${{ steps.tf_outputs.outputs.ECR_REPO }}
|
|
82
|
+
IMAGE_TAG: ${{ github.sha }}
|
|
83
|
+
run: |
|
|
84
|
+
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
|
|
85
|
+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
|
86
|
+
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
|
|
87
|
+
|
|
88
|
+
# --- Step 7: Deploy to EC2 via AWS SSM ---
|
|
89
|
+
- name: Deploy to EC2 via AWS SSM
|
|
90
|
+
env:
|
|
91
|
+
INSTANCE_ID: ${{ steps.tf_outputs.outputs.INSTANCE_ID }}
|
|
92
|
+
IMAGE_URL: ${{ steps.build-image.outputs.image }}
|
|
93
|
+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
|
94
|
+
AWS_DEFAULT_REGION: ${{ env.AWS_REGION }}
|
|
95
|
+
CONTAINER_PORT: ${{ env.CONTAINER_PORT }}
|
|
96
|
+
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
97
|
+
run: |
|
|
98
|
+
# Generate a secure password for Postgres
|
|
99
|
+
DB_PASSWORD=$(openssl rand -base64 12)
|
|
100
|
+
|
|
101
|
+
# Write the deployment script
|
|
102
|
+
cat <<EOF > deploy.sh
|
|
103
|
+
#!/bin/bash
|
|
104
|
+
# 1. Login to ECR
|
|
105
|
+
aws ecr get-login-password --region ${AWS_DEFAULT_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY}
|
|
106
|
+
|
|
107
|
+
# 2. Create app directory
|
|
108
|
+
mkdir -p /home/ec2-user/app
|
|
109
|
+
|
|
110
|
+
# 3. Create docker-compose.yml
|
|
111
|
+
cat <<'COMPOSE' > /home/ec2-user/app/docker-compose.yml
|
|
112
|
+
version: '3.8'
|
|
113
|
+
services:
|
|
114
|
+
web:
|
|
115
|
+
image: ${IMAGE_URL}
|
|
116
|
+
restart: always
|
|
117
|
+
ports:
|
|
118
|
+
- "80:${CONTAINER_PORT}"
|
|
119
|
+
environment:
|
|
120
|
+
- PORT=${CONTAINER_PORT}
|
|
121
|
+
- NODE_ENV=production
|
|
122
|
+
- DATABASE_URL=postgresql://mysystem_admin:${DB_PASSWORD}@db:5432/mysystem_db
|
|
123
|
+
- SENTRY_DSN=${SENTRY_DSN}
|
|
124
|
+
depends_on:
|
|
125
|
+
- db
|
|
126
|
+
|
|
127
|
+
db:
|
|
128
|
+
image: postgres:15-alpine
|
|
129
|
+
restart: always
|
|
130
|
+
environment:
|
|
131
|
+
- POSTGRES_USER=mysystem_admin
|
|
132
|
+
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
|
133
|
+
- POSTGRES_DB=mysystem_db
|
|
134
|
+
volumes:
|
|
135
|
+
- pgdata:/var/lib/postgresql/data
|
|
136
|
+
|
|
137
|
+
volumes:
|
|
138
|
+
pgdata:
|
|
139
|
+
COMPOSE
|
|
140
|
+
|
|
141
|
+
# 4. Pull and run containers
|
|
142
|
+
cd /home/ec2-user/app
|
|
143
|
+
docker compose pull
|
|
144
|
+
docker compose up -d
|
|
145
|
+
EOF
|
|
146
|
+
|
|
147
|
+
# Base64 encode the script to avoid all command-line escaping issues
|
|
148
|
+
B64_SCRIPT=\$(base64 -w 0 deploy.sh)
|
|
149
|
+
|
|
150
|
+
# Send run command to AWS SSM
|
|
151
|
+
CMD_ID=\$(aws ssm send-command \
|
|
152
|
+
--instance-ids "${INSTANCE_ID}" \
|
|
153
|
+
--document-name "AWS-RunShellScript" \
|
|
154
|
+
--parameters "commands=[\"echo '\$B64_SCRIPT' | base64 -d > /tmp/deploy.sh\", \"chmod +x /tmp/deploy.sh\", \"/tmp/deploy.sh\"]" \
|
|
155
|
+
--query "Command.CommandId" \
|
|
156
|
+
--region "${AWS_DEFAULT_REGION}" \
|
|
157
|
+
--output text)
|
|
158
|
+
|
|
159
|
+
echo "Deployment initiated via SSM. Command ID: \$CMD_ID"
|
|
160
|
+
|
|
161
|
+
- name: Show App URL
|
|
162
|
+
run: |
|
|
163
|
+
echo "🎉 App successfully deployed to: ${{ steps.tf_outputs.outputs.APP_URL }}"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: MySystem Deployment
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
id-token: write # Required for AWS OIDC authentication
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
deploy:
|
|
15
|
+
name: Deploy to AWS Fargate
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout Code
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
# --- Step 1: Authenticate with AWS via OIDC ---
|
|
23
|
+
- name: Configure AWS Credentials
|
|
24
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
25
|
+
with:
|
|
26
|
+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
|
27
|
+
aws-region: us-east-1 # Will be customized by CLI on setup
|
|
28
|
+
audience: sts.amazonaws.com
|
|
29
|
+
|
|
30
|
+
# --- Step 2: Provision Infrastructure using Terraform ---
|
|
31
|
+
- name: Setup Terraform
|
|
32
|
+
uses: hashicorp/setup-terraform@v3
|
|
33
|
+
with:
|
|
34
|
+
terraform_version: 1.6.0
|
|
35
|
+
|
|
36
|
+
- name: Terraform Init & Apply
|
|
37
|
+
working-directory: ./terraform
|
|
38
|
+
run: |
|
|
39
|
+
terraform init
|
|
40
|
+
terraform apply -auto-approve
|
|
41
|
+
|
|
42
|
+
# --- Step 3: Extract Terraform Outputs ---
|
|
43
|
+
- name: Get Outputs
|
|
44
|
+
id: tf_outputs
|
|
45
|
+
working-directory: ./terraform
|
|
46
|
+
run: |
|
|
47
|
+
echo "ECR_REPO=$(terraform output -raw ecr_repository_url)" >> $GITHUB_OUTPUT
|
|
48
|
+
echo "APP_URL=$(terraform output -raw application_url)" >> $GITHUB_OUTPUT
|
|
49
|
+
|
|
50
|
+
# --- Step 4: Login to Amazon ECR ---
|
|
51
|
+
- name: Login to Amazon ECR
|
|
52
|
+
id: login-ecr
|
|
53
|
+
uses: aws-actions/amazon-ecr-login@v2
|
|
54
|
+
|
|
55
|
+
# --- Step 5: Build and Push Docker Image ---
|
|
56
|
+
- name: Build, Tag, and Push Image to ECR
|
|
57
|
+
id: build-image
|
|
58
|
+
env:
|
|
59
|
+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
|
60
|
+
ECR_REPOSITORY: ${{ steps.tf_outputs.outputs.ECR_REPO }}
|
|
61
|
+
IMAGE_TAG: ${{ github.sha }}
|
|
62
|
+
run: |
|
|
63
|
+
# Build a local Docker image
|
|
64
|
+
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
|
|
65
|
+
# Push it to ECR
|
|
66
|
+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
|
|
67
|
+
# Save image URL as output
|
|
68
|
+
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
|
|
69
|
+
|
|
70
|
+
# --- Step 6: Deploy to ECS Fargate ---
|
|
71
|
+
# This pulls down the task definition template, updates it with the new image, and deploys it.
|
|
72
|
+
- name: Download Task Definition
|
|
73
|
+
run: |
|
|
74
|
+
aws ecs describe-task-definition --task-definition ${{ github.event.repository.name }} --query taskDefinition > task-definition.json
|
|
75
|
+
|
|
76
|
+
- name: Fill in the new image ID in the Amazon ECS task definition
|
|
77
|
+
id: render-task-def
|
|
78
|
+
uses: aws-actions/amazon-ecs-render-task-definition@v1
|
|
79
|
+
with:
|
|
80
|
+
task-definition: task-definition.json
|
|
81
|
+
container-name: ${{ github.event.repository.name }}
|
|
82
|
+
image: ${{ steps.build-image.outputs.image }}
|
|
83
|
+
|
|
84
|
+
- name: Deploy Amazon ECS task definition
|
|
85
|
+
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
|
86
|
+
with:
|
|
87
|
+
task-definition: ${{ steps.render-task-def.outputs.task-definition }}
|
|
88
|
+
service: ${{ github.event.repository.name }}
|
|
89
|
+
cluster: ${{ github.event.repository.name }}-cluster
|
|
90
|
+
wait-for-service-stability: true
|
|
91
|
+
|
|
92
|
+
- name: Show App URL
|
|
93
|
+
run: |
|
|
94
|
+
echo "🎉 App successfully deployed to: ${{ steps.tf_outputs.outputs.APP_URL }}"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: MySystem Teardown (Destroy)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch: # Allows manual trigger from the GitHub Actions UI
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
id-token: write
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
destroy:
|
|
12
|
+
name: Teardown AWS Infrastructure
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout Code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
# --- Step 1: Authenticate with AWS via OIDC ---
|
|
20
|
+
- name: Configure AWS Credentials
|
|
21
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
22
|
+
with:
|
|
23
|
+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
|
24
|
+
aws-region: us-east-1 # Will be customized by CLI on setup
|
|
25
|
+
audience: sts.amazonaws.com
|
|
26
|
+
|
|
27
|
+
# --- Step 2: Set up Terraform ---
|
|
28
|
+
- name: Setup Terraform
|
|
29
|
+
uses: hashicorp/setup-terraform@v3
|
|
30
|
+
with:
|
|
31
|
+
terraform_version: 1.6.0
|
|
32
|
+
|
|
33
|
+
# --- Step 3: Run Terraform Destroy ---
|
|
34
|
+
- name: Terraform Init & Destroy
|
|
35
|
+
working-directory: ./terraform
|
|
36
|
+
run: |
|
|
37
|
+
terraform init
|
|
38
|
+
terraform destroy -auto-approve
|
|
39
|
+
|
|
40
|
+
- name: Success Message
|
|
41
|
+
run: |
|
|
42
|
+
echo "🔥 All AWS resources for this application have been successfully destroyed."
|
|
43
|
+
echo "Your AWS billing for this project has been stopped."
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
resource "aws_lb" "main" {
|
|
2
|
+
name = "${var.app_name}-alb"
|
|
3
|
+
internal = false
|
|
4
|
+
load_balancer_type = "application"
|
|
5
|
+
security_groups = [aws_security_group.alb.id]
|
|
6
|
+
subnets = aws_subnet.public[*].id
|
|
7
|
+
|
|
8
|
+
tags = {
|
|
9
|
+
Name = "${var.app_name}-alb"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
resource "aws_lb_target_group" "app" {
|
|
14
|
+
name = "${var.app_name}-tg"
|
|
15
|
+
port = var.container_port
|
|
16
|
+
protocol = "HTTP"
|
|
17
|
+
vpc_id = aws_vpc.main.id
|
|
18
|
+
target_type = "ip"
|
|
19
|
+
|
|
20
|
+
health_check {
|
|
21
|
+
healthy_threshold = 3
|
|
22
|
+
unhealthy_threshold = 3
|
|
23
|
+
timeout = 5
|
|
24
|
+
interval = 30
|
|
25
|
+
path = "/health" # Compliant with AGENTS.md rule
|
|
26
|
+
protocol = "HTTP"
|
|
27
|
+
matcher = "200"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
tags = {
|
|
31
|
+
Name = "${var.app_name}-tg"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# HTTP Listener (Port 80)
|
|
36
|
+
resource "aws_lb_listener" "http" {
|
|
37
|
+
load_balancer_arn = aws_lb.main.arn
|
|
38
|
+
port = "80"
|
|
39
|
+
protocol = "HTTP"
|
|
40
|
+
|
|
41
|
+
default_action {
|
|
42
|
+
type = var.enable_custom_domain ? "redirect" : "forward"
|
|
43
|
+
target_group_arn = var.enable_custom_domain ? null : aws_lb_target_group.app.arn
|
|
44
|
+
|
|
45
|
+
dynamic "redirect" {
|
|
46
|
+
for_each = var.enable_custom_domain ? [1] : []
|
|
47
|
+
content {
|
|
48
|
+
port = "443"
|
|
49
|
+
protocol = "HTTPS"
|
|
50
|
+
status_code = "HTTP_301"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# HTTPS Listener (Port 443) - Created only if custom domain is enabled
|
|
57
|
+
resource "aws_lb_listener" "https" {
|
|
58
|
+
count = var.enable_custom_domain ? 1 : 0
|
|
59
|
+
load_balancer_arn = aws_lb.main.arn
|
|
60
|
+
port = "443"
|
|
61
|
+
protocol = "HTTPS"
|
|
62
|
+
ssl_policy = "ELBSecurityPolicy-2016-08"
|
|
63
|
+
certificate_arn = aws_acm_certificate.cert[0].arn
|
|
64
|
+
|
|
65
|
+
default_action {
|
|
66
|
+
type = "forward"
|
|
67
|
+
target_group_arn = aws_lb_target_group.app.arn
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
AWSTemplateFormatVersion: '2010-09-09'
|
|
2
|
+
Description: Bootstrap IAM Role for GitHub Actions OIDC connection to AWS (MySystem).
|
|
3
|
+
|
|
4
|
+
Parameters:
|
|
5
|
+
GitHubOrg:
|
|
6
|
+
Type: String
|
|
7
|
+
Description: Your GitHub Organization or Username (case-sensitive)
|
|
8
|
+
GitHubRepo:
|
|
9
|
+
Type: String
|
|
10
|
+
Description: Your GitHub Repository Name (case-sensitive)
|
|
11
|
+
|
|
12
|
+
Resources:
|
|
13
|
+
# OIDC Provider for GitHub (Created only if it doesn't exist, we assume it's created or we create it here)
|
|
14
|
+
# Note: Since there can only be one OIDC provider per account, we handle it. If it fails, users can use an existing one.
|
|
15
|
+
GithubOidcProvider:
|
|
16
|
+
Type: AWS::IAM::OIDCProvider
|
|
17
|
+
Properties:
|
|
18
|
+
Url: https://token.actions.githubusercontent.com
|
|
19
|
+
ClientIdList:
|
|
20
|
+
- sts.amazonaws.com
|
|
21
|
+
ThumbprintList:
|
|
22
|
+
- 6938fd4d98bab03faadb97b34396831e3780aea1 # GitHub Actions Thumbprint
|
|
23
|
+
|
|
24
|
+
# IAM Role that GitHub Actions will assume to deploy the app
|
|
25
|
+
MySystemDeployRole:
|
|
26
|
+
Type: AWS::IAM::Role
|
|
27
|
+
Properties:
|
|
28
|
+
RoleName: !Sub 'MySystemDeployRole-${GitHubRepo}'
|
|
29
|
+
Description: IAM Role assumed by GitHub Actions to deploy infrastructure and code.
|
|
30
|
+
AssumeRolePolicyDocument:
|
|
31
|
+
Version: '2012-10-17'
|
|
32
|
+
Statement:
|
|
33
|
+
- Effect: Allow
|
|
34
|
+
Principal:
|
|
35
|
+
Federated: !Ref GithubOidcProvider
|
|
36
|
+
Action: sts:AssumeRoleWithWebIdentity
|
|
37
|
+
Condition:
|
|
38
|
+
StringEquals:
|
|
39
|
+
token.actions.githubusercontent.com:aud: sts.amazonaws.com
|
|
40
|
+
StringLike:
|
|
41
|
+
token.actions.githubusercontent.com:sub: !Sub 'repo:${GitHubOrg}/${GitHubRepo}:*'
|
|
42
|
+
# Attach Policies to allow Terraform to provision VPC, ECS, RDS, ALB, ECR etc.
|
|
43
|
+
ManagedPolicyArns:
|
|
44
|
+
- arn:aws:iam::aws:policy/PowerUserAccess
|
|
45
|
+
Policies:
|
|
46
|
+
- PolicyName: MySystemAdditionalIAMPolicy
|
|
47
|
+
PolicyDocument:
|
|
48
|
+
Version: '2012-10-17'
|
|
49
|
+
Statement:
|
|
50
|
+
# PowerUserAccess does not allow managing IAM roles. Terraform needs to create ECS Task Execution Roles.
|
|
51
|
+
- Effect: Allow
|
|
52
|
+
Action:
|
|
53
|
+
- iam:CreateRole
|
|
54
|
+
- iam:DeleteRole
|
|
55
|
+
- iam:GetRole
|
|
56
|
+
- iam:PassRole
|
|
57
|
+
- iam:PutRolePolicy
|
|
58
|
+
- iam:DeleteRolePolicy
|
|
59
|
+
- iam:GetRolePolicy
|
|
60
|
+
- iam:AttachRolePolicy
|
|
61
|
+
- iam:DetachRolePolicy
|
|
62
|
+
Resource:
|
|
63
|
+
- !Sub 'arn:aws:iam::${AWS::AccountId}:role/*-ecs-execution-role'
|
|
64
|
+
- !Sub 'arn:aws:iam::${AWS::AccountId}:role/*-ecs-task-role'
|
|
65
|
+
|
|
66
|
+
Outputs:
|
|
67
|
+
RoleARN:
|
|
68
|
+
Description: The ARN of the IAM Role for GitHub Actions. Copy this to your GitHub Repository Secret `AWS_ROLE_ARN`.
|
|
69
|
+
Value: !GetAtt MySystemDeployRole.Arn
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# AWS Cost Budget Alert
|
|
2
|
+
# Sends an email notification if the forecasted or actual AWS monthly spend exceeds the specified threshold.
|
|
3
|
+
|
|
4
|
+
variable "billing_email" {
|
|
5
|
+
type = string
|
|
6
|
+
description = "Email address to send AWS budget and billing alerts"
|
|
7
|
+
default = ""
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
variable "budget_limit" {
|
|
11
|
+
type = string
|
|
12
|
+
description = "Monthly budget limit in USD"
|
|
13
|
+
default = "20"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
resource "aws_budgets_budget" "monthly_cost" {
|
|
17
|
+
count = var.billing_email != "" ? 1 : 0
|
|
18
|
+
name = "${var.app_name}-monthly-budget"
|
|
19
|
+
budget_type = "COST"
|
|
20
|
+
limit_amount = var.budget_limit
|
|
21
|
+
limit_unit = "USD"
|
|
22
|
+
time_period_start = "2026-01-01_00:00" # Arbitrary start date in the past/present
|
|
23
|
+
time_unit = "MONTHLY"
|
|
24
|
+
|
|
25
|
+
notification {
|
|
26
|
+
comparison_operator = "GREATER_THAN"
|
|
27
|
+
threshold = 80
|
|
28
|
+
threshold_type = "PERCENTAGE"
|
|
29
|
+
notification_type = "ACTUAL"
|
|
30
|
+
subscriber_email_addresses = [var.billing_email]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
notification {
|
|
34
|
+
comparison_operator = "GREATER_THAN"
|
|
35
|
+
threshold = 100
|
|
36
|
+
threshold_type = "PERCENTAGE"
|
|
37
|
+
notification_type = "FORECASTED"
|
|
38
|
+
subscriber_email_addresses = [var.billing_email]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
resource "random_password" "db_password" {
|
|
2
|
+
count = var.enable_database ? 1 : 0
|
|
3
|
+
length = 16
|
|
4
|
+
special = false
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
resource "aws_db_subnet_group" "main" {
|
|
8
|
+
count = var.enable_database ? 1 : 0
|
|
9
|
+
name = "${var.app_name}-db-subnet-group"
|
|
10
|
+
subnet_ids = aws_subnet.private[*].id
|
|
11
|
+
|
|
12
|
+
tags = {
|
|
13
|
+
Name = "${var.app_name}-db-subnet-group"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
resource "aws_db_instance" "postgres" {
|
|
18
|
+
count = var.enable_database ? 1 : 0
|
|
19
|
+
identifier = "${var.app_name}-db"
|
|
20
|
+
allocated_storage = 20
|
|
21
|
+
max_allocated_storage = 100
|
|
22
|
+
engine = "postgres"
|
|
23
|
+
engine_version = "15"
|
|
24
|
+
instance_class = "db.t4g.micro" # cost-effective burstable Graviton instance
|
|
25
|
+
db_name = replace(var.app_name, "-", "_")
|
|
26
|
+
username = "mysystem_admin"
|
|
27
|
+
password = random_password.db_password[0].result
|
|
28
|
+
db_subnet_group_name = aws_db_subnet_group.main[0].name
|
|
29
|
+
vpc_security_group_ids = [aws_security_group.db[0].id]
|
|
30
|
+
skip_final_snapshot = true
|
|
31
|
+
publicly_accessible = false
|
|
32
|
+
|
|
33
|
+
tags = {
|
|
34
|
+
Name = "${var.app_name}-db-postgres"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Save password in SSM Parameter Store so the developer can retrieve it if needed,
|
|
39
|
+
# and so ECS tasks can fetch it securely.
|
|
40
|
+
resource "aws_ssm_parameter" "db_url" {
|
|
41
|
+
count = var.enable_database ? 1 : 0
|
|
42
|
+
name = "/${var.app_name}/database_url"
|
|
43
|
+
type = "SecureString"
|
|
44
|
+
description = "Database URL for the application"
|
|
45
|
+
value = "postgresql://${aws_db_instance.postgres[0].username}:${random_password.db_password[0].result}@${var.enable_rds_proxy ? aws_db_proxy.postgres[0].endpoint : aws_db_instance.postgres[0].endpoint}/${aws_db_instance.postgres[0].db_name}"
|
|
46
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# AWS ACM (SSL Certificate) and Custom Domain Routing
|
|
2
|
+
# Supports automated Route 53 setups or external DNS providers (GoDaddy, Cloudflare, Namecheap)
|
|
3
|
+
|
|
4
|
+
variable "enable_custom_domain" {
|
|
5
|
+
type = bool
|
|
6
|
+
description = "Enable custom domain and HTTPS SSL certificate"
|
|
7
|
+
default = false
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
variable "domain_name" {
|
|
11
|
+
type = string
|
|
12
|
+
description = "The custom domain name (e.g., app.example.com)"
|
|
13
|
+
default = ""
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
variable "dns_provider" {
|
|
17
|
+
type = string
|
|
18
|
+
description = "DNS provider for domain validation ('route53' or 'external')"
|
|
19
|
+
default = "external"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# 1. SSL/TLS Certificate via ACM
|
|
23
|
+
resource "aws_acm_certificate" "cert" {
|
|
24
|
+
count = var.enable_custom_domain ? 1 : 0
|
|
25
|
+
domain_name = var.domain_name
|
|
26
|
+
validation_method = "DNS"
|
|
27
|
+
|
|
28
|
+
lifecycle {
|
|
29
|
+
create_before_destroy = true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
tags = {
|
|
33
|
+
Name = "${var.app_name}-cert"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# --- Route 53 DNS Configuration (Automated Setup) ---
|
|
38
|
+
data "aws_route53_zone" "primary" {
|
|
39
|
+
count = var.enable_custom_domain && var.dns_provider == "route53" ? 1 : 0
|
|
40
|
+
name = join(".", slice(split(".", var.domain_name), length(split(".", var.domain_name)) - 2, length(split(".", var.domain_name))))
|
|
41
|
+
private_zone = false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Create DNS validation record in Route53
|
|
45
|
+
resource "aws_route53_record" "cert_validation" {
|
|
46
|
+
count = var.enable_custom_domain && var.dns_provider == "route53" ? 1 : 0
|
|
47
|
+
name = tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_name
|
|
48
|
+
type = tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_type
|
|
49
|
+
zone_id = data.aws_route53_zone.primary[0].zone_id
|
|
50
|
+
records = [tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_value]
|
|
51
|
+
ttl = 60
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Validate certificate in ACM (Wait for validation to complete)
|
|
55
|
+
resource "aws_acm_certificate_validation" "cert" {
|
|
56
|
+
count = var.enable_custom_domain && var.dns_provider == "route53" ? 1 : 0
|
|
57
|
+
certificate_arn = aws_acm_certificate.cert[0].arn
|
|
58
|
+
validation_record_fqdns = [aws_route53_record.cert_validation[0].fqdn]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Create A record in Route53 pointing to the Load Balancer
|
|
62
|
+
resource "aws_route53_record" "app" {
|
|
63
|
+
count = var.enable_custom_domain && var.dns_provider == "route53" ? 1 : 0
|
|
64
|
+
zone_id = data.aws_route53_zone.primary[0].zone_id
|
|
65
|
+
name = var.domain_name
|
|
66
|
+
type = "A"
|
|
67
|
+
|
|
68
|
+
alias {
|
|
69
|
+
name = aws_lb.main.dns_name
|
|
70
|
+
zone_id = aws_lb.main.zone_id
|
|
71
|
+
evaluate_target_health = true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# --- Outputs for External DNS Setup (GoDaddy, Cloudflare, etc.) ---
|
|
76
|
+
output "dns_validation_name" {
|
|
77
|
+
value = var.enable_custom_domain ? tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_name : "None"
|
|
78
|
+
description = "CNAME Name to add to your DNS provider for SSL certificate validation"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
output "dns_validation_value" {
|
|
82
|
+
value = var.enable_custom_domain ? tolist(aws_acm_certificate.cert[0].domain_validation_options)[0].resource_record_value : "None"
|
|
83
|
+
description = "CNAME Value/Alias to add to your DNS provider for SSL certificate validation"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
output "app_cname_alias" {
|
|
87
|
+
value = aws_lb.main.dns_name
|
|
88
|
+
description = "Point your domain CNAME record to this target to route traffic to the app"
|
|
89
|
+
}
|