Files
miti99bot/docs/deploy-aws.md
T
tiennm99 f3b9891a54 refactor: rename module to miti99bot, canonicalize AWS deploy path
Rename:
- Go module github.com/tiennm99/miti99bot-go → github.com/tiennm99/miti99bot
- CloudFormation stack miti99bot-aws-port → miti99bot
- Drop "port", "Cloud Run", "GCP", "cutover", "Phase NN" framing from
  active code and docs — project reads as canonical AWS-Lambda from now on.

AWS deploy guide + flow fix:
- New docs/deploy-aws-free-tier-guide.md — Ubuntu 24.04 ARM64 onboarding
  with project-local venv (pip awscli + sam-cli), SSM secrets via read -s,
  idempotent OIDC provider + role creation, $1 budget alarm.
- Drop sam build from the pipeline — provided.al2023 + makefile builder
  expects a Makefile in CodeUri (build/lambda/, the output dir), so the
  step always fails. sam deploy --template-file template.yaml now reads
  the raw template and zips build/lambda/ directly.
- Rollback section rewritten — use continue-update-rollback /
  cancel-update-stack / git-SHA redeploy. Drop the broken
  --use-previous-template recipe.
- DynamoDB free-tier row corrected (on-demand is 2.5M read / 1M write
  request units, not 25 RCU/WCU).

Updated:
- README.md fully rewritten (drops port/legacy framing, lists modules,
  points new users at the free-tier guide).
- aws/README.md retitled "AWS account setup", phase numbers stripped.
- Makefile / .github/workflows/deploy.yml — sam deploy flow.
- samconfig.toml — stack_name = "miti99bot".
- Go comments — Cloud Run → Lambda, Cloud Scheduler → EventBridge
  Scheduler, Cloud Logging → CloudWatch Logs.
- Struct field GCPProject → FirestoreProject (env GOOGLE_CLOUD_PROJECT
  unchanged).

Plus advisory reports under plans/reports/ from the code-reviewer +
researcher passes that informed the fixes.

Verified: go vet ./..., go build ./..., go test ./... all green.
2026-05-13 22:05:38 +07:00

4.8 KiB

Deploy: AWS (Lambda + DynamoDB + EventBridge)

This is the production deploy path for miti99bot. Strict free-tier targets, region ap-southeast-1.

First-time setup: see aws/README.md. This doc is for steady-state operations.

Architecture (one diagram)

Telegram ──HTTPS──► Lambda Function URL (AuthType: NONE)
                    └─► AWS Lambda Web Adapter ──► localhost:8080
                                                    └─► Go http.Handler (cmd/server)
                                                          ├─► DynamoDB (KV)
                                                          ├─► Gemini API (AI modules)
                                                          └─► Telegram Bot API (replies)

EventBridge Scheduler ──cron──► HTTPS POST <FunctionURL>/cron/{name}
                                + Header X-Cron-Token (from SSM)

Deploy

Via GitHub Actions (canonical)

git push origin main

Triggers .github/workflows/deploy.yml:

  1. OIDC assume github-deploy-miti99bot role
  2. make build-lambda (Go ARM64 ZIP-ready binary)
  3. sam deploy --template-file template.yaml
  4. Smoke curl <function-url>/

Manual (emergency / staging)

make build-lambda
make sam-deploy            # uses samconfig.toml defaults
ALERT_EMAIL=you@example.com make sam-deploy   # with budget alert wired

Verify

make logs SINCE=10m

aws cloudformation describe-stacks --stack-name miti99bot \
  --query "Stacks[0].Outputs[?OutputKey=='FunctionUrl'].OutputValue" --output text

curl -fsSL "$(...)/" | jq .                       # health JSON

Set the Telegram webhook

URL=$(aws cloudformation describe-stacks --stack-name miti99bot \
        --query "Stacks[0].Outputs[?OutputKey=='FunctionUrl'].OutputValue" --output text)
SECRET=$(aws ssm get-parameter --name /miti99bot/prod/telegram-webhook-secret \
        --with-decryption --query 'Parameter.Value' --output text)
TOKEN=$(aws ssm get-parameter --name /miti99bot/prod/telegram-bot-token \
        --with-decryption --query 'Parameter.Value' --output text)

curl -X POST "https://api.telegram.org/bot$TOKEN/setWebhook" \
  -d "url=${URL}webhook" \
  -d "secret_token=$SECRET" \
  -d "drop_pending_updates=false" \
  -d "allowed_updates=[\"message\",\"callback_query\"]"

Verify:

curl "https://api.telegram.org/bot$TOKEN/getWebhookInfo" | jq .

Expect: url matches Function URL, pending_update_count ≈ 0, last_error_date empty.

Rotate secrets

aws ssm put-parameter --name /miti99bot/prod/telegram-webhook-secret \
  --value "$(openssl rand -hex 32)" --type SecureString --overwrite
# template.yaml uses ":1" version pin; redeploy to pick up the new value:
make sam-deploy
# Then re-run setWebhook (above) with the new secret_token.

The :1 in {{resolve:ssm-secure:…:1}} is the parameter version — it pins to the latest version at deploy time, not version 1 forever. To force a refresh after rotation, redeploy.

Rollback

CloudFormation handles failed deploys: a failing sam deploy triggers automatic rollback to the prior version. To roll back a successful-but-bad deploy:

aws cloudformation update-stack \
  --stack-name miti99bot \
  --use-previous-template \
  --capabilities CAPABILITY_IAM

Or redeploy from a known-good commit:

git checkout <good-sha>
make sam-deploy

Operational checks (daily during 7-day soak)

# Errors / warnings in last 24h
aws logs filter-log-events --log-group-name /aws/lambda/miti99bot-bot \
  --start-time $(($(date +%s%3N) - 86400000)) \
  --filter-pattern '{ $.level = "ERROR" }' --max-items 20

# Cold start P95
aws logs start-query --log-group-name /aws/lambda/miti99bot-bot \
  --start-time $(($(date +%s) - 86400)) --end-time $(date +%s) \
  --query-string 'filter @type = "REPORT" | stats avg(@initDuration), pct(@initDuration, 95)'

# DynamoDB throttle
aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB \
  --metric-name ThrottledRequests --dimensions Name=TableName,Value=miti99bot-data \
  --statistics Sum --start-time $(date -u -d '24 hours ago' +%FT%TZ) \
  --end-time $(date -u +%FT%TZ) --period 3600

# Current month spend
aws ce get-cost-and-usage --granularity MONTHLY \
  --time-period Start=$(date -u +%Y-%m-01),End=$(date -u +%F) \
  --metrics UnblendedCost

Free-tier guardrails

Resource Free Watch when
Lambda req / GB-s 1M / 400k Past 50% mid-month
DynamoDB req 200M Past 5% (sign of runaway loop)
DynamoDB storage 25 GiB Past 100 MiB (suspect leaks)
EventBridge invocations 14M Past 1k/mo (suspect mis-config)
CloudWatch Logs ingest 5 GB Past 50% mid-month
Egress 100 GB Past 1 GB (wildly high)

A $1 budget alert at 80%/100% catches all of these via cost-side fallout.