mysystem-cli 1.0.0 → 1.0.1

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 ADDED
@@ -0,0 +1,66 @@
1
+ # MySystem Agent Guidelines (AGENTS.md)
2
+
3
+ This file contains critical production-readiness instructions for AI coding agents (Cursor, Claude Code, Lovable, Bolt, Antigravity, etc.).
4
+
5
+ > [!IMPORTANT]
6
+ > **To the AI Agent**: You MUST read and follow these rules when editing or creating code in this codebase.
7
+
8
+ ---
9
+
10
+ ## 1. Core Production Architecture Rules
11
+
12
+ ### 1.1 Port & Port Binding
13
+ * Always bind the application web server to the port specified in the `PORT` environment variable.
14
+ * If `PORT` is not defined, fall back to a standard default (e.g., `3000` or `8080`), but never hardcode the listen port without checking `process.env.PORT` (Node) or `os.environ.get("PORT")` (Python).
15
+
16
+ ### 1.2 Health Check Endpoint
17
+ * The application must expose a GET `/health` or `/healthz` endpoint.
18
+ * It must return a `200 OK` response (preferably JSON, e.g., `{"status": "healthy"}`) with no authentication required.
19
+ * The AWS Load Balancer uses this endpoint to verify if the container is running. If it fails, the container will be terminated and replaced.
20
+
21
+ ### 1.3 Statelessness & File Storage
22
+ * Do NOT write persistent files to the local container filesystem. The container instances are ephemeral and can be destroyed at any time.
23
+ * For file uploads, use an external object storage service (e.g., AWS S3).
24
+ * For temporary processing, use the system `/tmp` directory, but expect it to disappear.
25
+
26
+ ### 1.4 Environment Variables & Secrets
27
+ * Never hardcode API keys, database credentials, passwords, or secrets.
28
+ * Always read configurations from environment variables.
29
+ * Generate a sample `.env.example` file when adding new environment variables.
30
+
31
+ ### 1.5 Database Migrations
32
+ * Ensure database migrations are safe to run concurrently or executed as a single-run step during deployment (never inside the web server's startup command if scaling to multiple container instances).
33
+ * Design database changes to be backward-compatible (e.g., add new columns as nullable, deprecate columns in a two-stage deployment).
34
+
35
+ ### 1.6 Logging & Error Tracking (Sentry & Pino)
36
+ * **Structured Logging**: For Node.js/TypeScript applications, format production logs in JSON using `pino` (or standard JSON formatters in Python/Go) for easy querying in CloudWatch. Avoid plain-text print statements in production.
37
+ * **Error Tracking**: If the `SENTRY_DSN` environment variable is present:
38
+ 1. Install the appropriate Sentry SDK (e.g., `@sentry/nextjs` for Next.js, `@sentry/node` for Node, or `sentry-sdk` for Python).
39
+ 2. Initialize the SDK in the application entrypoint.
40
+ 3. Ensure unhandled exceptions and runtime errors are automatically captured and reported to Sentry.
41
+
42
+ ### 1.7 EC2 Instance Sizing (Hobbyist Tier)
43
+ If the project is configured to use the **Hobbyist Tier** (single EC2 + Docker Compose), you must select and manage the `instance_type` parameter in `terraform/terraform.tfvars` according to the application footprint:
44
+ * **Micro-services / Static Frontends (Vite/React)**: Use `t4g.nano` (ARM64, 0.5GB RAM, ~$3.20/month) for maximum savings.
45
+ * **Standard Frameworks (Express, Next.js, FastAPI + Postgres)**: Use `t3.micro` (x86, 1GB RAM, **AWS Free Tier eligible**).
46
+ * **Medium Apps (App + Postgres + Redis or memory-intensive runtimes)**: Use `t3.small` (2GB RAM, ~$16/month) to prevent Out-Of-Memory (OOM) crashes.
47
+ * **Large Monoliths**: Scale to `t3.medium` (4GB RAM, ~$32/month) or larger when active traffic warrants it.
48
+ * *Note: When deploying on t4g (ARM) instances, ensure the Dockerfile is compiled for arm64.*
49
+
50
+ ---
51
+
52
+ ## 2. Docker & Container Rules
53
+ * Do not modify the `Dockerfile` in a way that breaks multi-stage optimization.
54
+ * Keep container images small by using minimal base images (e.g., alpine or slim variants).
55
+ * Always run the container application under a non-root user (e.g., `node` in Node.js) for security.
56
+
57
+ ---
58
+
59
+ ## 3. Automated Git Push Workflow (Vibecoder Deployment)
60
+ If the user tells you: **"I have set up the OIDC role / GitHub secrets"** or **"Deploy the app now"**:
61
+
62
+ 1. **Verify Git Status**: Run `git status` via terminal to verify the changes.
63
+ 2. **Stage files**: Run `git add .` to stage the changes.
64
+ 3. **Commit**: Commit with a clean message: `git commit -m "chore: configure MySystem deployment assets"`
65
+ 4. **Push**: Identify the active branch (usually `main` or `master`) and push it: `git push origin main`
66
+ 5. **Confirm**: Let the user know the push was successful and the GitHub Actions deployment pipeline has been triggered.
@@ -0,0 +1,47 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const rootDir = path.join(__dirname, '../..');
5
+ const cliDir = __dirname;
6
+
7
+ const srcTemplates = path.join(rootDir, 'templates');
8
+ const destTemplates = path.join(cliDir, 'templates');
9
+
10
+ const srcAgents = path.join(rootDir, 'AGENTS.md');
11
+ const destAgents = path.join(cliDir, 'AGENTS.md');
12
+
13
+ // Helper to recursively copy directories
14
+ function copyDirSync(src, dest) {
15
+ fs.mkdirSync(dest, { recursive: true });
16
+ const entries = fs.readdirSync(src, { withFileTypes: true });
17
+
18
+ for (let entry of entries) {
19
+ const srcPath = path.join(src, entry.name);
20
+ const destPath = path.join(dest, entry.name);
21
+
22
+ if (entry.isDirectory()) {
23
+ copyDirSync(srcPath, destPath);
24
+ } else {
25
+ fs.copyFileSync(srcPath, destPath);
26
+ }
27
+ }
28
+ }
29
+
30
+ console.log('Copying templates to CLI package...');
31
+ if (fs.existsSync(srcTemplates)) {
32
+ if (fs.existsSync(destTemplates)) {
33
+ fs.rmSync(destTemplates, { recursive: true, force: true });
34
+ }
35
+ copyDirSync(srcTemplates, destTemplates);
36
+ console.log(' ✅ Templates copied.');
37
+ } else {
38
+ console.error(' ❌ Source templates folder not found at:', srcTemplates);
39
+ }
40
+
41
+ console.log('Copying AGENTS.md to CLI package...');
42
+ if (fs.existsSync(srcAgents)) {
43
+ fs.copyFileSync(srcAgents, destAgents);
44
+ console.log(' ✅ AGENTS.md copied.');
45
+ } else {
46
+ console.error(' ❌ Source AGENTS.md not found at:', srcAgents);
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mysystem-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Zero-config deployment standard and CLI for AI-generated applications on AWS Fargate",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  "mysystem-cli": "dist/index.js"
9
9
  },
10
10
  "scripts": {
11
- "build": "tsc",
11
+ "build": "tsc && node copy-templates.js",
12
12
  "prepublishOnly": "npm run build"
13
13
  },
14
14
  "keywords": [
@@ -0,0 +1,32 @@
1
+ # Multi-stage build for Python FastAPI Applications
2
+
3
+ # --- Build Stage ---
4
+ FROM python:3.11-slim AS builder
5
+ WORKDIR /app
6
+ RUN python -m venv /opt/venv
7
+ ENV PATH="/opt/venv/bin:$PATH"
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # --- Production Stage ---
12
+ FROM python:3.11-slim AS runner
13
+ WORKDIR /app
14
+ COPY --from=builder /opt/venv /opt/venv
15
+ ENV PATH="/opt/venv/bin:$PATH"
16
+ ENV PYTHONDONTWRITEBYTECODE=1
17
+ ENV PYTHONUNBUFFERED=1
18
+
19
+ COPY . .
20
+
21
+ # Run as non-privileged user
22
+ RUN adduser --disabled-password --gecos "" appuser && chown -R appuser:appuser /app
23
+ USER appuser
24
+
25
+ # Healthcheck
26
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
27
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:' + str(os.environ.get('PORT', 8000)) + '/health', timeout=5)" || exit 1
28
+
29
+ EXPOSE 8000
30
+ ENV PORT=8000
31
+
32
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
@@ -0,0 +1,45 @@
1
+ # Multi-stage build for Next.js (using Standalone output)
2
+
3
+ # --- Dependencies Stage ---
4
+ FROM node:20-alpine AS deps
5
+ RUN apk add --no-cache libc6-compat
6
+ WORKDIR /app
7
+ COPY package*.json ./
8
+ RUN npm ci
9
+
10
+ # --- Build Stage ---
11
+ FROM node:20-alpine AS builder
12
+ WORKDIR /app
13
+ COPY --from=deps /app/node_modules ./node_modules
14
+ COPY . .
15
+ # Set Next.js telemetry disable
16
+ ENV NEXT_TELEMETRY_DISABLED=1
17
+ RUN npm run build
18
+
19
+ # --- Production Stage ---
20
+ FROM node:20-alpine AS runner
21
+ WORKDIR /app
22
+
23
+ ENV NODE_ENV=production
24
+ ENV NEXT_TELEMETRY_DISABLED=1
25
+
26
+ # Create non-root user
27
+ RUN addgroup --system --gid 1001 nodejs
28
+ RUN adduser --system --uid 1001 nextjs
29
+
30
+ # Copy standalone build and static assets
31
+ COPY --from=builder /app/public ./public
32
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
33
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
34
+
35
+ USER nextjs
36
+
37
+ EXPOSE 3000
38
+ ENV PORT=3000
39
+ ENV HOSTNAME="0.0.0.0"
40
+
41
+ # Healthcheck
42
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
43
+ CMD node -e "fetch('http://localhost:3000/api/health').then(r => r.status === 200 ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"
44
+
45
+ CMD ["node", "server.js"]
@@ -0,0 +1,32 @@
1
+ # Multi-stage build for Node.js Applications
2
+
3
+ # --- Build Stage ---
4
+ FROM node:20-alpine AS builder
5
+ WORKDIR /app
6
+ COPY package*.json ./
7
+ RUN npm ci
8
+ COPY . .
9
+ RUN npm run build --if-present
10
+
11
+ # --- Production Stage ---
12
+ FROM node:20-alpine AS runner
13
+ WORKDIR /app
14
+ ENV NODE_ENV=production
15
+ COPY package*.json ./
16
+ RUN npm ci --only=production
17
+ COPY --from=builder /app/dist ./dist --keep-directory-structure --if-present
18
+ COPY --from=builder /app/build ./build --keep-directory-structure --if-present
19
+ COPY --from=builder /app/server.js ./server.js --if-present
20
+ COPY --from=builder /app/index.js ./index.js --if-present
21
+
22
+ # Set safe, non-root user
23
+ USER node
24
+
25
+ # Health check setup
26
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
27
+ CMD node -e "fetch('http://localhost:' + (process.env.PORT || 3000) + '/health').then(r => r.status === 200 ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"
28
+
29
+ EXPOSE 3000
30
+ ENV PORT=3000
31
+
32
+ CMD ["node", "dist/index.js"]
@@ -0,0 +1,38 @@
1
+ # Multi-stage build for React/Vite Single Page Applications (served via Nginx)
2
+
3
+ # --- Build Stage ---
4
+ FROM node:20-alpine AS builder
5
+ WORKDIR /app
6
+ COPY package*.json ./
7
+ RUN npm ci
8
+ COPY . .
9
+ RUN npm run build
10
+
11
+ # --- Production Stage ---
12
+ FROM nginx:alpine AS runner
13
+ COPY --from=builder /app/dist /usr/share/nginx/html
14
+
15
+ # Custom Nginx configuration to support SPA routing (redirect all fallback routes to index.html)
16
+ RUN echo $'\n\
17
+ server {\n\
18
+ listen 80;\n\
19
+ location / {\n\
20
+ root /usr/share/nginx/html;\n\
21
+ index index.html index.htm;\n\
22
+ try_files $uri $uri/ /index.html;\n\
23
+ }\n\
24
+ location /health {\n\
25
+ access_log off;\n\
26
+ add_header Content-Type text/plain;\n\
27
+ return 200 "healthy\\n";\n\
28
+ }\n\
29
+ }' > /etc/nginx/conf.d/default.conf
30
+
31
+ # Healthcheck
32
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
33
+ CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1
34
+
35
+ EXPOSE 80
36
+ ENV PORT=80
37
+
38
+ CMD ["nginx", "-g", "daemon off;"]
@@ -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
+ }