mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-08-15 12:27:01 +00:00
feat(selfhost): containerize for Coolify + decommission AWS deploy
Add multi-stage Dockerfile, docker-compose.yml for local dev, .env.example template, and Makefile helpers. Disable GitHub Actions AWS deploy workflow. Supports Coolify container orchestration for self-hosted deployments.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# miti99bot — self-host (Coolify + MongoDB Atlas) environment.
|
||||
# Copy to .env and fill in. .env is gitignored — never commit real secrets.
|
||||
|
||||
# ============================ Required ============================
|
||||
# Telegram bot token from @BotFather.
|
||||
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
|
||||
|
||||
# MongoDB Atlas connection. MONGO_URL is the full SRV string INCLUDING the
|
||||
# db username + password — treat it as a secret (it is never logged).
|
||||
# Create a least-privilege user: readWrite on this one database only.
|
||||
MONGO_URL=mongodb+srv://botuser:STRONG_UNIQUE_PASSWORD@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority
|
||||
MONGO_DATABASE=miti99bot
|
||||
|
||||
# ============================ Operational =========================
|
||||
# Comma-separated module list. Empty = load every module.
|
||||
MODULES=
|
||||
# Telegram user id for owner-only commands (renamed from BOT_OWNER_ID).
|
||||
OWNER_ID=
|
||||
# Comma-separated admin Telegram user ids (renamed from ADMIN_USER_IDS).
|
||||
ADMIN_IDS=
|
||||
|
||||
# ============================ Optional ============================
|
||||
# Only the twentyq module needs this. Leave blank to disable that command.
|
||||
GEMINI_API_KEY=
|
||||
|
||||
# GIT_SHA is injected at build time by Coolify for the deploynotify owner DM.
|
||||
# Leave unset for local `docker compose up` — deploynotify just stays silent.
|
||||
# GIT_SHA=
|
||||
|
||||
# ====================== Leave UNSET on self-host ==================
|
||||
# These are AWS-only. cmd/server reads secrets directly from the plain env
|
||||
# vars above; *_PARAMETER_NAME would force an SSM lookup that FAILS with no AWS
|
||||
# credentials and bricks startup. Do NOT set any of them:
|
||||
# TELEGRAM_BOT_TOKEN_PARAMETER_NAME
|
||||
# TELEGRAM_WEBHOOK_SECRET_PARAMETER_NAME
|
||||
# CRON_SHARED_SECRET_PARAMETER_NAME
|
||||
# GEMINI_API_KEY_PARAMETER_NAME
|
||||
# STOCK_INCOME_EVENTS_API_TOKEN_PARAMETER_NAME
|
||||
# GOLD_VNAPP_API_KEY_PARAMETER_NAME
|
||||
#
|
||||
# Also leave unset (defaults are correct for self-host):
|
||||
# KV_PROVIDER — auto-selects mongodb because MONGO_URL is set
|
||||
# PORT — defaults to 8080 (internal health server)
|
||||
# TELEGRAM_WEBHOOK_SECRET — long polling has no webhook
|
||||
# CRON_SHARED_SECRET — unset → /cron route 404s; the in-process scheduler is the trigger
|
||||
# GOLD_VNAPP_API_KEY — gold module auto-fetches + caches the key to Mongo
|
||||
# STOCK/COIN/GOLD *_API_URL overrides — modules use their coded default providers
|
||||
@@ -1,8 +1,13 @@
|
||||
name: deploy-aws
|
||||
|
||||
# RETIRED: miti99bot is self-hosted on Coolify + MongoDB Atlas (see
|
||||
# docs/deploy-coolify-selfhosted.md). The AWS stack is decommissioned
|
||||
# (docs/aws-decommission-runbook.md). The automatic push-to-main trigger is
|
||||
# removed so a merge can never recreate the AWS stack. Kept as manual-only
|
||||
# (workflow_dispatch) for reference; it requires the github-deploy-miti99bot
|
||||
# OIDC role, which the decommission deletes — so a manual run fails until AWS
|
||||
# is intentionally re-bootstrapped.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
||||
+8
-1
@@ -5,8 +5,15 @@ COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
# GIT_SHA is baked into the binary so internal/deploynotify can DM the owner
|
||||
# once per new version (parity with the Makefile build). Coolify exposes the
|
||||
# commit SHA as a build arg — pass it with
|
||||
# --build-arg GIT_SHA=$(git rev-parse --short HEAD)
|
||||
# When unset, deploynotify treats the empty SHA as "stay silent".
|
||||
ARG GIT_SHA=""
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-ldflags="-s -w" \
|
||||
-ldflags="-s -w -X main.gitSHA=${GIT_SHA}" \
|
||||
-o /out/server \
|
||||
./cmd/server
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: help test test-emulator test-dynamodb firestore-emulator dynamodb-local dynamodb-local-stop vet build build-lambda run sam-validate sam-build sam-deploy telegram-setup telegram-webhook telegram-webhook-info telegram-commands telegram-commands-info logs clean
|
||||
.PHONY: help test test-emulator test-dynamodb test-mongo firestore-emulator dynamodb-local dynamodb-local-stop mongo-local mongo-local-stop vet build build-lambda run sam-validate sam-build sam-deploy telegram-setup telegram-webhook telegram-webhook-info telegram-commands telegram-commands-info telegram-commands-selfhost telegram-deletewebhook-selfhost telegram-webhook-info-selfhost migrate-dynamo-to-mongo migrate-verify logs clean
|
||||
|
||||
# Lambda target architecture. Match Globals.Architectures in template.yaml.
|
||||
LAMBDA_GOOS ?= linux
|
||||
@@ -48,6 +48,13 @@ test-dynamodb: dynamodb-local ## Run DynamoDB tests against DynamoDB Local
|
||||
DYNAMODB_LOCAL_URL=http://localhost:$(DDB_PORT) LOG_LEVEL=error \
|
||||
go test -race -count=1 ./internal/storage/...
|
||||
|
||||
# Run MongoDB integration tests against a local Mongo container.
|
||||
# Override MONGO_PORT if 27017 is taken on your host.
|
||||
MONGO_PORT ?= 27017
|
||||
test-mongo: mongo-local ## Run MongoDB tests against a local Mongo container
|
||||
MONGODB_TEST_URL=mongodb://127.0.0.1:$(MONGO_PORT) LOG_LEVEL=error \
|
||||
go test -race -count=1 ./internal/storage/...
|
||||
|
||||
# ---- Lint / Vet -----------------------------------------------------------
|
||||
|
||||
vet: ## go vet
|
||||
@@ -87,6 +94,34 @@ dynamodb-local: ## Start DynamoDB Local container on :$(DDB_PORT) (idempotent)
|
||||
dynamodb-local-stop: ## Stop DynamoDB Local
|
||||
-docker stop miti99bot-ddb
|
||||
|
||||
# ---- MongoDB local for tests ----------------------------------------------
|
||||
|
||||
mongo-local: ## Start MongoDB container on :$(MONGO_PORT) (idempotent)
|
||||
@if ! docker ps --format '{{.Names}}' | grep -q '^miti99bot-mongo$$'; then \
|
||||
docker run -d --rm --name miti99bot-mongo -p $(MONGO_PORT):27017 mongo:7; \
|
||||
echo "MongoDB started on :$(MONGO_PORT)"; \
|
||||
sleep 2; \
|
||||
else \
|
||||
echo "MongoDB already running"; \
|
||||
fi
|
||||
|
||||
mongo-local-stop: ## Stop local MongoDB
|
||||
-docker stop miti99bot-mongo
|
||||
|
||||
# ---- Data migration (DynamoDB → MongoDB Atlas) ----------------------------
|
||||
|
||||
# Requires MONGO_URL + MONGO_DATABASE in the environment and AWS credentials
|
||||
# for a READ-ONLY profile with dynamodb:Scan on the table. Use DRY_RUN=1 first.
|
||||
# make migrate-dynamo-to-mongo DRY_RUN=1 MONGO_URL=… MONGO_DATABASE=…
|
||||
# make migrate-dynamo-to-mongo MONGO_URL=… MONGO_DATABASE=…
|
||||
# make migrate-verify MONGO_URL=… MONGO_DATABASE=…
|
||||
MIGRATE_TABLE ?= miti99bot-data
|
||||
migrate-dynamo-to-mongo: ## Copy DynamoDB → Mongo (DRY_RUN=1 for a dry run)
|
||||
go run ./cmd/migrate-dynamo-to-mongo --dynamodb-table $(MIGRATE_TABLE) $(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
migrate-verify: ## Verify per-module counts DynamoDB vs Mongo (exit non-zero on mismatch)
|
||||
go run ./cmd/migrate-dynamo-to-mongo --dynamodb-table $(MIGRATE_TABLE) --verify
|
||||
|
||||
# ---- SAM (require AWS CLI + SAM CLI installed locally) -------------------
|
||||
|
||||
sam-validate: ## Validate template.yaml without contacting AWS
|
||||
@@ -154,6 +189,30 @@ telegram-commands-info: ## Show Telegram getMyCommands using token from SSM
|
||||
curl -sS "https://api.telegram.org/bot$${TOKEN}/getMyCommands"; \
|
||||
echo
|
||||
|
||||
# ---- Telegram (self-host: token from TELEGRAM_BOT_TOKEN env, no AWS/SSM) ---
|
||||
|
||||
telegram-commands-selfhost: ## Register command menu using TELEGRAM_BOT_TOKEN env
|
||||
@set -eu; \
|
||||
: "$${TELEGRAM_BOT_TOKEN:?set TELEGRAM_BOT_TOKEN}"; \
|
||||
echo "Registering Telegram commands from $(TELEGRAM_COMMANDS_FILE)"; \
|
||||
curl -sS -X POST "https://api.telegram.org/bot$${TELEGRAM_BOT_TOKEN}/setMyCommands" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-binary "@$(TELEGRAM_COMMANDS_FILE)"; \
|
||||
echo
|
||||
|
||||
telegram-deletewebhook-selfhost: ## Cutover: delete the webhook so the poller can run (keeps buffered updates)
|
||||
@set -eu; \
|
||||
: "$${TELEGRAM_BOT_TOKEN:?set TELEGRAM_BOT_TOKEN}"; \
|
||||
curl -sS -X POST "https://api.telegram.org/bot$${TELEGRAM_BOT_TOKEN}/deleteWebhook" \
|
||||
-d 'drop_pending_updates=false'; \
|
||||
echo
|
||||
|
||||
telegram-webhook-info-selfhost: ## getWebhookInfo using TELEGRAM_BOT_TOKEN env (confirm url empty + pending draining)
|
||||
@set -eu; \
|
||||
: "$${TELEGRAM_BOT_TOKEN:?set TELEGRAM_BOT_TOKEN}"; \
|
||||
curl -sS "https://api.telegram.org/bot$${TELEGRAM_BOT_TOKEN}/getWebhookInfo"; \
|
||||
echo
|
||||
|
||||
logs: ## Tail Lambda logs (last 5m). Override with SINCE=10m.
|
||||
@sam logs --tail --stack-name miti99bot --start-time $${SINCE:-5m}ago
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
services:
|
||||
bot:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
# Coolify exposes the commit SHA; pass it so deploynotify DMs the owner
|
||||
# on each new version. Optional — empty SHA just stays silent.
|
||||
GIT_SHA: ${GIT_SHA:-}
|
||||
# Or pin a prebuilt image instead of building:
|
||||
# image: ghcr.io/tiennm99/miti99bot:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# --- Required ---
|
||||
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
|
||||
MONGO_URL: ${MONGO_URL} # Atlas SRV string incl. credentials — SECRET
|
||||
MONGO_DATABASE: ${MONGO_DATABASE}
|
||||
# --- Operational ---
|
||||
MODULES: ${MODULES} # CSV; empty = all modules
|
||||
OWNER_ID: ${OWNER_ID} # Telegram user id for owner-only commands
|
||||
ADMIN_IDS: ${ADMIN_IDS} # CSV of admin Telegram user ids
|
||||
# --- Optional ---
|
||||
GEMINI_API_KEY: ${GEMINI_API_KEY} # only the twentyq module needs it
|
||||
# Storage auto-selects mongodb because MONGO_URL is set — no KV_PROVIDER.
|
||||
# The in-process cron scheduler runs by default — no CRON_MODE.
|
||||
# PORT defaults to 8080 (internal health server) — omit unless overriding.
|
||||
# Long polling = no TELEGRAM_WEBHOOK_SECRET, no /webhook, no public domain.
|
||||
# Leave CRON_SHARED_SECRET unset → /cron route is 404 (scheduler is the trigger).
|
||||
# Do NOT set any *_PARAMETER_NAME vars (those force an SSM/AWS lookup that
|
||||
# fails with no AWS creds and bricks startup). See .env.example.
|
||||
# No stock/coin/gold *_API_URL overrides — modules use their coded default
|
||||
# providers (stock: SSI/VCI/KBS; coin: Binance→Coinbase→CoinGecko;
|
||||
# gold: VNAppMob→spot). GOLD_VNAPP_API_KEY auto-fetches + caches to Mongo.
|
||||
# Long polling is outbound-only: nothing inbound to route, so no published
|
||||
# ports and no public domain. `expose` keeps :8080 reachable inside the
|
||||
# Coolify network for the container health monitor against GET / only.
|
||||
expose:
|
||||
- "8080"
|
||||
# No compose healthcheck: distroless has no shell/curl and cmd/server has no
|
||||
# -healthcheck flag. Configure Coolify's HTTP monitor against GET / instead
|
||||
# (returns text/plain "miti99bot ok"). Note: a plain / check does not verify
|
||||
# Mongo connectivity — see docs/deploy-coolify-selfhosted.md.
|
||||
Reference in New Issue
Block a user