mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-14 14:17:58 +00:00
docs(aws): bootstrap + ops cheatsheets
- aws/README.md: one-time AWS account setup (account ID, IAM, OIDC) - aws/iam-github-oidc-trust.json: GitHub OIDC trust policy template - docs/deploy-aws.md: steady-state deployment operations guide
This commit is contained in:
1 parent
e5cee5c08f
commit
03c2476616
3 files changed
+299
No files matched your search
+133
@@ -0,0 +1,133 @@
|
||||
# AWS bootstrap commands
|
||||
|
||||
One-time setup steps for Phase 01 of the AWS port. After this is done, every push to `main` deploys via GitHub Actions OIDC; no human-in-loop AWS commands needed.
|
||||
|
||||
> **Region:** `ap-southeast-1` (Singapore). Change in `samconfig.toml` if needed.
|
||||
> **Stack name:** `miti99bot-aws-port`. Change in `samconfig.toml`.
|
||||
|
||||
---
|
||||
|
||||
## 1. AWS account hygiene
|
||||
|
||||
1. Enable MFA on the root user.
|
||||
2. Create an IAM admin user `bootstrap-admin` (CLI access keys). Use only for the first `sam deploy --guided`.
|
||||
3. Set CLI default region:
|
||||
```sh
|
||||
aws configure set region ap-southeast-1 --profile bootstrap-admin
|
||||
aws configure set aws_access_key_id AKIA… --profile bootstrap-admin
|
||||
aws configure set aws_secret_access_key … --profile bootstrap-admin
|
||||
```
|
||||
|
||||
## 2. SSM Parameter Store secrets
|
||||
|
||||
Create the four required secrets. **Names must match `template.yaml`** (`/miti99bot/${StackEnv}/…`).
|
||||
|
||||
```sh
|
||||
aws ssm put-parameter --name /miti99bot/prod/telegram-bot-token \
|
||||
--value "<bot-father-token>" --type SecureString --profile bootstrap-admin
|
||||
|
||||
aws ssm put-parameter --name /miti99bot/prod/telegram-webhook-secret \
|
||||
--value "$(openssl rand -hex 32)" --type SecureString --profile bootstrap-admin
|
||||
|
||||
aws ssm put-parameter --name /miti99bot/prod/gemini-api-key \
|
||||
--value "<google-ai-studio-key>" --type SecureString --profile bootstrap-admin
|
||||
|
||||
aws ssm put-parameter --name /miti99bot/prod/cron-shared-secret \
|
||||
--value "$(openssl rand -hex 32)" --type SecureString --profile bootstrap-admin
|
||||
```
|
||||
|
||||
Save the webhook + cron secrets locally — you'll set them on the Telegram side and on the EventBridge schedule headers.
|
||||
|
||||
## 3. GitHub OIDC identity provider
|
||||
|
||||
One-time per AWS account:
|
||||
|
||||
```sh
|
||||
aws iam create-open-id-connect-provider \
|
||||
--url https://token.actions.githubusercontent.com \
|
||||
--client-id-list sts.amazonaws.com \
|
||||
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1 \
|
||||
--profile bootstrap-admin
|
||||
```
|
||||
|
||||
(GitHub publishes the canonical thumbprint; verify on docs.github.com if rotated.)
|
||||
|
||||
## 4. Deploy IAM role for GitHub Actions
|
||||
|
||||
Edit `aws/iam-github-oidc-trust.json` to set your AWS account ID and GitHub repo, then:
|
||||
|
||||
```sh
|
||||
aws iam create-role \
|
||||
--role-name github-deploy-miti99bot \
|
||||
--assume-role-policy-document file://aws/iam-github-oidc-trust.json \
|
||||
--profile bootstrap-admin
|
||||
|
||||
# Permissions (broad to start; tighten in Phase 06).
|
||||
for arn in \
|
||||
arn:aws:iam::aws:policy/AWSCloudFormationFullAccess \
|
||||
arn:aws:iam::aws:policy/AWSLambda_FullAccess \
|
||||
arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess \
|
||||
arn:aws:iam::aws:policy/AmazonEventBridgeFullAccess \
|
||||
arn:aws:iam::aws:policy/AmazonSQSFullAccess \
|
||||
arn:aws:iam::aws:policy/AmazonSSMFullAccess \
|
||||
arn:aws:iam::aws:policy/CloudWatchLogsFullAccess \
|
||||
arn:aws:iam::aws:policy/AWSBudgetsActionsWithAWSResourceControlAccess \
|
||||
arn:aws:iam::aws:policy/IAMFullAccess \
|
||||
arn:aws:iam::aws:policy/AmazonS3FullAccess; do
|
||||
aws iam attach-role-policy --role-name github-deploy-miti99bot \
|
||||
--policy-arn "$arn" --profile bootstrap-admin
|
||||
done
|
||||
```
|
||||
|
||||
> Yes, this is broad. SAM creates IAM roles for the Lambda, so the deploy role needs `iam:CreateRole`. **Tighten in Phase 06** with custom policies scoped to the stack's resource ARNs.
|
||||
|
||||
## 5. Add GitHub repo secrets
|
||||
|
||||
In GitHub repo settings → Secrets and variables → Actions:
|
||||
|
||||
| Secret | Value |
|
||||
|---|---|
|
||||
| `AWS_ACCOUNT_ID` | 12-digit AWS account ID |
|
||||
| `ALERT_EMAIL` (optional) | Email for the $1 budget alert |
|
||||
|
||||
`AWS_ACCOUNT_ID` is not a credential — it's hidden only to keep the ARN out of the workflow file.
|
||||
|
||||
## 6. First deploy (manual, with bootstrap admin)
|
||||
|
||||
```sh
|
||||
make build-lambda
|
||||
sam build
|
||||
AWS_PROFILE=bootstrap-admin sam deploy --guided
|
||||
```
|
||||
|
||||
Confirm:
|
||||
- Stack name: `miti99bot-aws-port`
|
||||
- Region: `ap-southeast-1`
|
||||
- Capabilities: `CAPABILITY_IAM`
|
||||
- Save to `samconfig.toml`: yes (already committed; this just confirms)
|
||||
|
||||
After `CREATE_COMPLETE`:
|
||||
```sh
|
||||
aws cloudformation describe-stacks --stack-name miti99bot-aws-port \
|
||||
--query "Stacks[0].Outputs" --output table --profile bootstrap-admin
|
||||
```
|
||||
Note the `FunctionUrl` — Phase 07 sets the Telegram webhook to it.
|
||||
|
||||
## 7. Tighten — optional but recommended
|
||||
|
||||
Once the first deploy succeeds:
|
||||
1. Rotate / delete `bootstrap-admin` CLI keys (use only via console for emergencies).
|
||||
2. Trigger a workflow_dispatch deploy via GH Actions to confirm OIDC path works without the bootstrap user.
|
||||
3. Replace the broad managed policies on `github-deploy-miti99bot` with stack-scoped custom policies.
|
||||
|
||||
---
|
||||
|
||||
## Lambda Web Adapter layer ARN
|
||||
|
||||
Pinned in `template.yaml` parameter `LambdaAdapterLayerArn`. Bump by checking:
|
||||
- https://github.com/awslabs/aws-lambda-web-adapter/releases (look at the `Releases` page for the latest layer version)
|
||||
- Format: `arn:aws:lambda:ap-southeast-1:753240598075:layer:LambdaAdapterLayerArm64:<version>`
|
||||
|
||||
## Cost expectations
|
||||
|
||||
After the stack is up but idle, monthly cost should be **$0**. If you ever see >$0.01 in Cost Explorer, investigate — most likely culprits: CloudWatch Logs ingestion volume, DynamoDB writes from a runaway loop, or accidental egress past the 100 GB free tier.
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Federated": "arn:aws:iam::REPLACE_WITH_AWS_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
|
||||
},
|
||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
|
||||
},
|
||||
"StringLike": {
|
||||
"token.actions.githubusercontent.com:sub": [
|
||||
"repo:tiennm99/miti99bot-go:ref:refs/heads/main",
|
||||
"repo:tiennm99/miti99bot-go:ref:refs/heads/dev",
|
||||
"repo:tiennm99/miti99bot-go:pull_request"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
# Deploy: AWS (Lambda + DynamoDB + EventBridge)
|
||||
|
||||
This is the production deploy path for `miti99bot-go`. 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 build && sam deploy`
|
||||
4. Smoke `curl <function-url>/`
|
||||
|
||||
### Manual (emergency / staging)
|
||||
|
||||
```sh
|
||||
make build-lambda
|
||||
make sam-deploy # uses samconfig.toml defaults
|
||||
ALERT_EMAIL=you@example.com make sam-deploy # with budget alert wired
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```sh
|
||||
make logs SINCE=10m
|
||||
|
||||
aws cloudformation describe-stacks --stack-name miti99bot-aws-port \
|
||||
--query "Stacks[0].Outputs[?OutputKey=='FunctionUrl'].OutputValue" --output text
|
||||
|
||||
curl -fsSL "$(...)/" | jq . # health JSON
|
||||
```
|
||||
|
||||
## Set the Telegram webhook (Phase 07)
|
||||
|
||||
```sh
|
||||
URL=$(aws cloudformation describe-stacks --stack-name miti99bot-aws-port \
|
||||
--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:
|
||||
```sh
|
||||
curl "https://api.telegram.org/bot$TOKEN/getWebhookInfo" | jq .
|
||||
```
|
||||
Expect: `url` matches Function URL, `pending_update_count` ≈ 0, `last_error_date` empty.
|
||||
|
||||
## Rotate secrets
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
aws cloudformation update-stack \
|
||||
--stack-name miti99bot-aws-port \
|
||||
--use-previous-template \
|
||||
--capabilities CAPABILITY_IAM
|
||||
```
|
||||
|
||||
Or redeploy from a known-good commit:
|
||||
```sh
|
||||
git checkout <good-sha>
|
||||
make sam-deploy
|
||||
```
|
||||
|
||||
## Operational checks (daily during 7-day soak)
|
||||
|
||||
```sh
|
||||
# Errors / warnings in last 24h
|
||||
aws logs filter-log-events --log-group-name /aws/lambda/miti99bot-aws-port-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-aws-port-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-aws-port-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.
|
||||
Reference in new issue
Block a user