dev cicd + tron + docs #377

Merged
tech merged 1 commits from cicd-376 into main 2026-01-30 16:05:02 +00:00
52 changed files with 3695 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
matrix:
include:
- DOCUMENTS_IMAGE_PATH: billing/documents
DOCUMENTS_DOCKERFILE: ci/prod/compose/billing_documents.dockerfile
DOCUMENTS_ENV: prod
when:
- event: push
branch: main
steps:
- name: version
image: alpine:latest
commands:
- set -euo pipefail 2>/dev/null || set -eu
- apk add --no-cache git
- GIT_REV="$(git rev-parse --short HEAD)"
- BUILD_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
- APP_V="$(cat version)"
- BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
- BUILD_USER="${WOODPECKER_MACHINE:-woodpecker}"
- printf "GIT_REV=%s\nBUILD_BRANCH=%s\nAPP_V=%s\nBUILD_DATE=%s\nBUILD_USER=%s\n" \
"$GIT_REV" "$BUILD_BRANCH" "$APP_V" "$BUILD_DATE" "$BUILD_USER" | tee .env.version
- name: proto
image: golang:alpine
depends_on: [ version ]
commands:
- set -eu
- apk add --no-cache bash git build-base protoc protobuf-dev
- go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
- go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh
- name: secrets
image: alpine:latest
depends_on: [ version ]
environment:
VAULT_ADDR: { from_secret: VAULT_ADDR }
VAULT_ROLE_ID: { from_secret: VAULT_APP_ROLE }
VAULT_SECRET_ID: { from_secret: VAULT_SECRET_ID }
commands:
- set -euo pipefail
- apk add --no-cache bash coreutils openssh-keygen curl sed python3
- mkdir -p secrets
- ./ci/vlt kv_to_file kv ops/deploy/ssh_key private_b64 secrets/SSH_KEY.b64 600
- base64 -d secrets/SSH_KEY.b64 > secrets/SSH_KEY
- chmod 600 secrets/SSH_KEY
- ssh-keygen -y -f secrets/SSH_KEY >/dev/null
- ./ci/vlt kv_get kv registry user > secrets/REGISTRY_USER
- ./ci/vlt kv_get kv registry password > secrets/REGISTRY_PASSWORD
- name: build-image
image: gcr.io/kaniko-project/executor:debug
depends_on: [ proto, secrets ]
commands:
- sh ci/scripts/billing_documents/build-image.sh
- name: deploy
image: alpine:latest
depends_on: [ secrets, build-image ]
environment:
VAULT_ADDR: { from_secret: VAULT_ADDR }
VAULT_ROLE_ID: { from_secret: VAULT_APP_ROLE }
VAULT_SECRET_ID: { from_secret: VAULT_SECRET_ID }
commands:
- set -euo pipefail
- apk add --no-cache bash openssh-client rsync coreutils curl sed python3
- mkdir -p /root/.ssh
- install -m 600 secrets/SSH_KEY /root/.ssh/id_rsa
- sh ci/scripts/billing_documents/deploy.sh

View File

@@ -0,0 +1,76 @@
matrix:
include:
- TRON_GATEWAY_IMAGE_PATH: gateway/tron
TRON_GATEWAY_DOCKERFILE: ci/prod/compose/tron_gateway.dockerfile
TRON_GATEWAY_MONGO_SECRET_PATH: sendico/db
TRON_GATEWAY_RPC_SECRET_PATH: sendico/gateway/tron
TRON_GATEWAY_WALLET_SECRET_PATH: sendico/gateway/tron/wallet
TRON_GATEWAY_VAULT_SECRET_PATH: sendico/gateway/tron/vault
TRON_GATEWAY_ENV: prod
when:
- event: push
branch: main
steps:
- name: version
image: alpine:latest
commands:
- set -euo pipefail 2>/dev/null || set -eu
- apk add --no-cache git
- GIT_REV="$(git rev-parse --short HEAD)"
- BUILD_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
- APP_V="$(cat version)"
- BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
- BUILD_USER="${WOODPECKER_MACHINE:-woodpecker}"
- printf "GIT_REV=%s\nBUILD_BRANCH=%s\nAPP_V=%s\nBUILD_DATE=%s\nBUILD_USER=%s\n" \
"$GIT_REV" "$BUILD_BRANCH" "$APP_V" "$BUILD_DATE" "$BUILD_USER" | tee .env.version
- name: proto
image: golang:alpine
depends_on: [ version ]
commands:
- set -eu
- apk add --no-cache bash git build-base protoc protobuf-dev
- go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
- go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh
- name: secrets
image: alpine:latest
depends_on: [ version ]
environment:
VAULT_ADDR: { from_secret: VAULT_ADDR }
VAULT_ROLE_ID: { from_secret: VAULT_APP_ROLE }
VAULT_SECRET_ID: { from_secret: VAULT_SECRET_ID }
commands:
- set -euo pipefail
- apk add --no-cache bash coreutils openssh-keygen curl sed python3
- mkdir -p secrets
- ./ci/vlt kv_to_file kv ops/deploy/ssh_key private_b64 secrets/SSH_KEY.b64 600
- base64 -d secrets/SSH_KEY.b64 > secrets/SSH_KEY
- chmod 600 secrets/SSH_KEY
- ssh-keygen -y -f secrets/SSH_KEY >/dev/null
- ./ci/vlt kv_get kv registry user > secrets/REGISTRY_USER
- ./ci/vlt kv_get kv registry password > secrets/REGISTRY_PASSWORD
- name: build-image
image: gcr.io/kaniko-project/executor:debug
depends_on: [ proto, secrets ]
commands:
- sh ci/scripts/tron_gateway/build-image.sh
- name: deploy
image: alpine:latest
depends_on: [ secrets, build-image ]
environment:
VAULT_ADDR: { from_secret: VAULT_ADDR }
VAULT_ROLE_ID: { from_secret: VAULT_APP_ROLE }
VAULT_SECRET_ID: { from_secret: VAULT_SECRET_ID }
commands:
- set -euo pipefail
- apk add --no-cache bash openssh-client rsync coreutils curl sed python3
- mkdir -p /root/.ssh
- install -m 600 secrets/SSH_KEY /root/.ssh/id_rsa
- sh ci/scripts/tron_gateway/deploy.sh

267
Makefile Normal file
View File

@@ -0,0 +1,267 @@
# Sendico Development Environment - Makefile
# Docker Compose + Makefile build system
.PHONY: help init build up down restart logs rebuild clean vault-init proto
COMPOSE := docker compose -f docker-compose.dev.yml --env-file .env.dev
SERVICE ?=
# Colors
GREEN := \033[0;32m
YELLOW := \033[1;33m
NC := \033[0m
help:
@echo "$(GREEN)Sendico Development Environment$(NC)"
@echo ""
@echo "$(YELLOW)Quick Start:$(NC)"
@echo " make init Initialize dev environment (first time setup)"
@echo " make up Start all services"
@echo " make vault-init Initialize Vault (if services need it)"
@echo ""
@echo "$(YELLOW)Main Commands:$(NC)"
@echo " make build Build all service images"
@echo " make down Stop all services"
@echo " make restart Restart all services"
@echo " make status Show service status"
@echo " make logs [SERVICE=x] View logs (all or specific service)"
@echo " make rebuild SERVICE=x Rebuild specific service"
@echo " make clean Remove all containers and volumes"
@echo ""
@echo "$(YELLOW)Selective Operations:$(NC)"
@echo " make infra-up Start infrastructure only (mongo, nats, vault)"
@echo " make services-up Start application services only"
@echo " make list-services List all available services"
@echo ""
@echo "$(YELLOW)Build Groups:$(NC)"
@echo " make build-core Build core services (discovery, ledger, fees, documents)"
@echo " make build-fx Build FX services (oracle, ingestor)"
@echo " make build-payments Build payment orchestrator"
@echo " make build-gateways Build gateway services (chain, tron, mntx, tgsettle)"
@echo " make build-api Build API services (notification, bff)"
@echo " make build-frontend Build Flutter web frontend"
@echo ""
@echo "$(YELLOW)Development:$(NC)"
@echo " make proto Generate protobuf code"
@echo " make health Check service health"
@echo ""
@echo "Examples:"
@echo " make logs SERVICE=dev-ledger"
@echo " make rebuild SERVICE=dev-ledger"
# First-time initialization
init:
@echo "$(GREEN)Initializing development environment...$(NC)"
@if [ ! -f ci/dev/mongo.key ]; then \
echo "$(YELLOW)Generating MongoDB keyfile...$(NC)"; \
openssl rand -base64 756 | tr -d '\n' > ci/dev/mongo.key; \
chown 999:999 ci/dev/mongo.key; \
chmod 400 ci/dev/mongo.key; \
fi
@if [ ! -f .env.dev ]; then \
echo "$(YELLOW)Creating .env.dev with default values...$(NC)"; \
echo "# Sendico Development Environment Configuration" > .env.dev; \
echo "# Simple plaintext credentials for infrastructure" >> .env.dev; \
echo "" >> .env.dev; \
echo "# MongoDB Configuration" >> .env.dev; \
echo "MONGO_USER=dev_root" >> .env.dev; \
echo "MONGO_PASSWORD=dev_password_123" >> .env.dev; \
echo "" >> .env.dev; \
echo "# NATS Configuration" >> .env.dev; \
echo "NATS_USER=dev_nats" >> .env.dev; \
echo "NATS_PASSWORD=nats_password_123" >> .env.dev; \
echo "" >> .env.dev; \
echo "# Vault Configuration (for application data only)" >> .env.dev; \
echo "VAULT_ADDR=http://dev-vault:8200" >> .env.dev; \
fi
@echo "$(GREEN)Verifying .env.dev...$(NC)"
@cat .env.dev | grep -q "MONGO_USER=" || (echo "$(YELLOW)Error: .env.dev is incomplete$(NC)" && exit 1)
@echo "$(GREEN)Running proto generation...$(NC)"
@./generate_protos.sh
@echo "$(GREEN)Building Docker images...$(NC)"
@$(COMPOSE) build
@echo "$(GREEN)✅ Initialization complete!$(NC)"
@echo ""
@echo "Next steps:"
@echo " 1. make up # Start services"
@echo " 2. make vault-init # Initialize Vault (if needed)"
# Build all images
build:
@echo "$(GREEN)Building all service images...$(NC)"
@./generate_protos.sh
@$(COMPOSE) build
# Start all services
up:
@echo "$(GREEN)Starting development environment...$(NC)"
@$(COMPOSE) up -d
@echo ""
@echo "$(GREEN)✅ Development environment started!$(NC)"
@echo ""
@echo "Services:"
@echo " MongoDB: mongodb://dev_root:dev_password_123@localhost:27017/admin?replicaSet=dev-rs"
@echo " NATS: nats://dev_nats:nats_password_123@localhost:4222"
@echo " NATS UI: http://localhost:8222"
@echo " Vault: http://localhost:8200 (run 'make vault-init' first)"
@echo ""
@echo "View logs: make logs [SERVICE=name]"
@echo "Stop: make down"
# Stop all services
down:
@echo "$(YELLOW)Stopping development environment...$(NC)"
@$(COMPOSE) down
# Restart all services
restart:
@echo "$(YELLOW)Restarting services...$(NC)"
@$(COMPOSE) restart
# View logs
logs:
ifdef SERVICE
@$(COMPOSE) logs -f $(SERVICE)
else
@$(COMPOSE) logs -f
endif
# Rebuild specific service
rebuild:
ifndef SERVICE
$(error SERVICE is required: make rebuild SERVICE=ledger)
endif
@echo "$(GREEN)Rebuilding $(SERVICE)...$(NC)"
@$(COMPOSE) build $(SERVICE)
@$(COMPOSE) up -d --force-recreate $(SERVICE)
@echo "$(GREEN)✅ $(SERVICE) rebuilt$(NC)"
@echo "View logs: make logs SERVICE=$(SERVICE)"
# Generate protobuf code
proto:
@echo "$(GREEN)Generating protobuf code...$(NC)"
@./generate_protos.sh
@echo "$(GREEN)✅ Protobuf generation complete$(NC)"
# Clean everything
clean:
@echo "$(YELLOW)WARNING: This will remove all containers and volumes!$(NC)"
@read -p "Are you sure? [y/N] " -n 1 -r; \
echo; \
if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
$(COMPOSE) down -v --remove-orphans; \
docker system prune -f; \
echo "$(GREEN)✅ Cleanup complete$(NC)"; \
fi
# Initialize Vault (first time only)
vault-init:
@echo "$(GREEN)Initializing Vault...$(NC)"
@echo "$(YELLOW)Make sure Vault is running: make up$(NC)"
@sleep 2
@echo "Checking Vault status..."
@docker exec dev-vault vault status || echo "Vault not initialized yet"
@echo ""
@echo "$(GREEN)Initializing Vault with 1 key share...$(NC)"
@docker exec dev-vault vault operator init -key-shares=1 -key-threshold=1 > vault-keys.txt || echo "Vault already initialized"
@if [ -f vault-keys.txt ]; then \
echo "$(GREEN)✅ Vault initialized!$(NC)"; \
echo ""; \
echo "$(YELLOW)IMPORTANT: Save vault-keys.txt securely!$(NC)"; \
echo "It contains the unseal key and root token."; \
echo ""; \
UNSEAL_KEY=$$(grep 'Unseal Key 1:' vault-keys.txt | awk '{print $$NF}'); \
echo "Unsealing Vault..."; \
docker exec dev-vault vault operator unseal $$UNSEAL_KEY; \
echo ""; \
echo "$(GREEN)✅ Vault is unsealed and ready!$(NC)"; \
echo ""; \
echo "Root token: $$(grep 'Initial Root Token:' vault-keys.txt | awk '{print $$NF}')"; \
echo "UI: http://localhost:8200"; \
fi
# Infrastructure only (mongo + nats + vault)
infra-up:
@echo "$(GREEN)Starting infrastructure only...$(NC)"
@$(COMPOSE) up -d dev-mongo-1 dev-mongo-2 dev-mongo-3 dev-nats dev-vault
@$(COMPOSE) up dev-mongo-init
# Services only (assumes infra is running)
services-up:
@echo "$(GREEN)Starting application services...$(NC)"
@$(COMPOSE) up -d \
dev-discovery \
dev-fx-oracle \
dev-fx-ingestor \
dev-billing-fees \
dev-billing-documents \
dev-ledger \
dev-payments-orchestrator \
dev-chain-gateway \
dev-tron-gateway \
dev-mntx-gateway \
dev-tgsettle-gateway \
dev-notification \
dev-bff \
dev-frontend
# Status check
status:
@$(COMPOSE) ps
# List all services
list-services:
@echo "$(GREEN)Infrastructure Services:$(NC)"
@echo " - dev-mongo-1, dev-mongo-2, dev-mongo-3 (MongoDB Replica Set)"
@echo " - dev-nats (NATS with JetStream)"
@echo " - dev-vault (Vault for application secrets)"
@echo ""
@echo "$(GREEN)Application Services:$(NC)"
@echo " - dev-discovery :9407 (Service Registry)"
@echo " - dev-fx-oracle :50051, :9400 (FX Quotes)"
@echo " - dev-fx-ingestor :9102 (FX Rate Ingestion)"
@echo " - dev-billing-fees :50060, :9402 (Fee Calculation)"
@echo " - dev-billing-documents :50061, :9409 (Billing Documents)"
@echo " - dev-ledger :50052, :9401 (Double-Entry Ledger)"
@echo " - dev-payments-orchestrator :50062, :9403 (Payment Orchestration)"
@echo " - dev-chain-gateway :50070, :9404 (EVM Blockchain Gateway)"
@echo " - dev-tron-gateway :50071, :9408 (TRON Blockchain Gateway)"
@echo " - dev-mntx-gateway :50075, :9405, :8084 (Card Payouts)"
@echo " - dev-tgsettle-gateway :50080, :9406 (Telegram Settlements)"
@echo " - dev-notification :8081 (Notifications)"
@echo " - dev-bff :8080 (Backend for Frontend)"
@echo " - dev-frontend :3000 (Flutter Web UI)"
# Health check all services
health:
@echo "$(GREEN)Checking service health...$(NC)"
@$(COMPOSE) ps --format json | grep -v "State" || echo "Run 'make up' first"
# Build specific service group
build-infra:
@echo "$(GREEN)Building infrastructure images...$(NC)"
# Infrastructure uses official images, nothing to build
build-core:
@echo "$(GREEN)Building core services...$(NC)"
@$(COMPOSE) build dev-discovery dev-ledger dev-billing-fees dev-billing-documents
build-fx:
@echo "$(GREEN)Building FX services...$(NC)"
@$(COMPOSE) build dev-fx-oracle dev-fx-ingestor
build-payments:
@echo "$(GREEN)Building payment services...$(NC)"
@$(COMPOSE) build dev-payments-orchestrator
build-gateways:
@echo "$(GREEN)Building gateway services...$(NC)"
@$(COMPOSE) build dev-chain-gateway dev-tron-gateway dev-mntx-gateway dev-tgsettle-gateway
build-api:
@echo "$(GREEN)Building API services...$(NC)"
@$(COMPOSE) build dev-notification dev-bff
build-frontend:
@echo "$(GREEN)Building frontend...$(NC)"
@$(COMPOSE) build dev-frontend

399
SETUP.md Normal file
View File

@@ -0,0 +1,399 @@
# Sendico Development Environment - Setup Guide
Complete Docker Compose + Makefile build system for local development.
## 🎯 What's Included
**13 Services:**
- ✅ Discovery (Service Registry)
- ✅ FX Oracle & FX Ingestor
- ✅ Billing Fees
- ✅ Ledger (Double-Entry Accounting)
- ✅ Payments Orchestrator
- ✅ Chain Gateway (Blockchain)
- ✅ MNTX Gateway (Card Payouts)
- ✅ TGSettle Gateway (Telegram Settlements)
- ✅ Notification
- ✅ BFF (Backend for Frontend / Server)
- ✅ Frontend (Flutter Web)
**Infrastructure:**
- MongoDB Replica Set (3 nodes)
- NATS with JetStream
- Vault (single node, for application data only)
---
## 🚀 Quick Start
```bash
# 1. First time setup
make init
# 2. Start everything
make up
# 3. Initialize Vault (if services need blockchain keys, external API keys)
make vault-init
# 4. Check status
make status
# 5. View frontend
open http://localhost:3000
```
---
## 📋 Prerequisites
- Docker & Docker Compose
- Make
- Go 1.24+ (for local proto generation)
- protoc, protoc-gen-go, protoc-gen-go-grpc (for proto generation)
---
## 🔧 Configuration
### Environment Variables
Edit `.env.dev` (created after `make init`):
```bash
# MongoDB
MONGO_USER=dev_root
MONGO_PASSWORD=dev_password_123
# NATS
NATS_USER=dev_nats
NATS_PASSWORD=nats_password_123
# Vault (for application data only)
VAULT_ADDR=http://dev-vault:8200
```
**IMPORTANT:** These are plaintext credentials for dev infrastructure only!
---
## 📊 Service Ports
**Infrastructure:**
- MongoDB: `localhost:27017`
- NATS: `localhost:4222`
- NATS Monitoring: `localhost:8222`
- Vault: `localhost:8200`
**Services:**
- Discovery: `localhost:9407`
- FX Oracle: `localhost:50051` (gRPC), `localhost:9400` (metrics)
- FX Ingestor: `localhost:9102`
- Billing Fees: `localhost:50060` (gRPC), `localhost:9402` (metrics)
- Ledger: `localhost:50052` (gRPC), `localhost:9401` (metrics)
- Payments Orchestrator: `localhost:50062` (gRPC), `localhost:9403` (metrics)
- Chain Gateway: `localhost:50070` (gRPC), `localhost:9404` (metrics)
- MNTX Gateway: `localhost:50075` (gRPC), `localhost:9405` (metrics), `localhost:8084` (HTTP)
- TGSettle Gateway: `localhost:50080` (gRPC), `localhost:9406` (metrics)
- Notification: `localhost:8081`
- BFF: `localhost:8080`
- Frontend: `localhost:3000`
---
## 🛠️ Common Commands
### Lifecycle
```bash
make up # Start all services
make down # Stop all services
make restart # Restart all services
make status # Check status
make clean # Remove everything (containers + volumes)
```
### Development
```bash
# View logs
make logs # All services
make logs SERVICE=dev-ledger # Specific service
# Rebuild after code changes
make rebuild SERVICE=dev-ledger
# Regenerate protobuf
make proto
```
### Selective Startup
```bash
# Start infrastructure only
make infra-up
# Then start services
make services-up
# Or start specific service groups
make build-core # discovery, ledger, billing-fees
make build-fx # fx-oracle, fx-ingestor
make build-payments # payments-orchestrator
make build-gateways # chain, mntx, tgsettle
```
---
## 🔐 Vault Setup
Vault is **only for application data** (blockchain keys, external API keys).
Infrastructure credentials (MongoDB, NATS) are in `.env.dev`.
### Initialize Vault
```bash
make vault-init
```
This will:
1. Initialize Vault with 1 key share
2. Unseal Vault automatically
3. Save keys to `vault-keys.txt` (NEVER commit this!)
4. Display root token
### Using Vault in Services
Services that need Vault (e.g., Chain Gateway for blockchain keys):
1. Set `VAULT_ADDR` in service environment
2. Use Vault client to fetch secrets at runtime
3. Store secrets in Vault: `kv/data/chain/ethereum/private_key`
---
## 🏗️ Development Workflow
### Typical Day
```bash
# Morning: Start environment
make up
# Work on ledger service
vim api/ledger/internal/service/ledger/posting.go
# Rebuild ledger
make rebuild SERVICE=dev-ledger
# View logs
make logs SERVICE=dev-ledger
# Test
curl http://localhost:9401/health
# End of day
make down
```
### After Changing Protos
```bash
# Regenerate protos
make proto
# Rebuild affected services
make rebuild SERVICE=dev-ledger
make rebuild SERVICE=dev-payments-orchestrator
```
### Frontend Development
For faster iteration, run frontend locally instead of Docker:
```bash
# Start backend services only
make infra-up
make services-up
# Run frontend locally
cd frontend/pweb
flutter run -d chrome
```
---
## 🔍 Debugging
### MongoDB Replica Set Issues
```bash
# Check replica set status
docker exec -it dev-mongo-1 mongosh -u dev_root -p dev_password_123 --eval "rs.status()"
# Reinitialize
make down
make up
docker logs dev-mongo-init
```
### Service Can't Connect to MongoDB
```bash
# Check if replica set is initialized
docker logs dev-mongo-init
# Check MongoDB is healthy
docker ps | grep mongo
```
### Vault Sealed
```bash
# Get unseal key from vault-keys.txt
UNSEAL_KEY=$(grep 'Unseal Key 1:' vault-keys.txt | awk '{print $NF}')
# Unseal
docker exec dev-vault vault operator unseal $UNSEAL_KEY
```
### Service Crash Loop
```bash
# Check logs
make logs SERVICE=dev-ledger
# Check environment variables
docker inspect dev-ledger | grep -A 20 Env
# Rebuild from scratch
make rebuild SERVICE=dev-ledger
```
---
## 📦 Docker Images
All images tagged as `sendico-dev/<service>:latest`:
- `sendico-dev/discovery:latest`
- `sendico-dev/ledger:latest`
- `sendico-dev/billing-fees:latest`
- `sendico-dev/fx-oracle:latest`
- `sendico-dev/fx-ingestor:latest`
- `sendico-dev/payments-orchestrator:latest`
- `sendico-dev/chain-gateway:latest`
- `sendico-dev/mntx-gateway:latest`
- `sendico-dev/tgsettle-gateway:latest`
- `sendico-dev/notification:latest`
- `sendico-dev/bff:latest`
- `sendico-dev/frontend:latest`
---
## 🧪 Testing Full Flow
```bash
# 1. Start everything
make up
# 2. Check all services are healthy
make status
# 3. Test discovery
curl http://localhost:9407/health
# 4. Test ledger
curl http://localhost:9401/health
# 5. Test BFF
curl http://localhost:8080/api/v1/health
# 6. Test frontend
open http://localhost:3000
```
---
## 🗑️ Cleanup
```bash
# Stop everything
make down
# Remove volumes (fresh start)
make clean
# Remove images
docker rmi $(docker images 'sendico-dev/*' -q)
```
---
## 📂 File Structure
```
sendico/
├── docker-compose.dev.yml # All service definitions
├── .env.dev # Plaintext credentials
├── Makefile # Build commands
├── SETUP.md # This file
├── vault-keys.txt # Vault keys (gitignored)
└── docker/
└── dev/
├── README.md # Detailed docs
├── mongo.key # MongoDB keyfile
├── vault/
│ └── config.hcl # Vault config
└── *.dockerfile # Service builds
```
---
## 🆘 Getting Help
```bash
# Show all available commands
make help
# List all services
make list-services
# Check service health
make health
```
---
## 🎓 Next Steps
1. **Initialize Vault with secrets** for Chain Gateway
2. **Seed test data** into MongoDB
3. **Run integration tests**
4. **Add monitoring** (Prometheus/Grafana - optional)
5. **Document API endpoints** in each service
---
## ⚠️ Important Notes
- **Never commit `.env.dev`** - Contains credentials
- **Never commit `vault-keys.txt`** - Contains Vault keys
- **Vault is for app data only** - Not for infrastructure secrets
- **Use `dev-` prefix** for all service names to avoid conflicts
- **MongoDB keyfile** must have `400` permissions
---
## 📝 Troubleshooting Checklist
- [ ] Docker is running
- [ ] Docker Compose v2 is installed (`docker compose version`)
- [ ] `.env.dev` exists and has correct values
- [ ] Vault is initialized and unsealed (`make vault-init`)
- [ ] MongoDB replica set is initialized (`docker logs dev-mongo-init`)
- [ ] All services are healthy (`make status`)
- [ ] No port conflicts on host machine
- [ ] Proto generation succeeded (`make proto`)
---
**Ready to build? Run `make init && make up`** 🚀

View File

@@ -0,0 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

BIN
api/server/tmp/main Executable file

Binary file not shown.

46
ci/dev/.air.toml Normal file
View File

@@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
entrypoint = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go", "_templ.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

160
ci/dev/CHECKLIST.md Normal file
View File

@@ -0,0 +1,160 @@
# Development Environment - Implementation Checklist
## ✅ Completed
### Infrastructure (3/3)
- [x] MongoDB Replica Set (3 nodes with auto-init)
- [x] NATS with JetStream
- [x] Vault (single node for application data)
### Application Services (12/12)
- [x] Discovery Service
- [x] FX Oracle Service
- [x] FX Ingestor Service
- [x] Billing Fees Service
- [x] Ledger Service
- [x] Payments Orchestrator Service
- [x] Chain Gateway Service
- [x] MNTX Gateway Service
- [x] TGSettle Gateway Service
- [x] Notification Service
- [x] BFF (Server) Service
- [x] Frontend (Flutter Web)
### Configuration Files
- [x] docker-compose.dev.yml (complete with all services)
- [x] .env.dev (plaintext credentials template)
- [x] Makefile (full command suite)
- [x] .gitignore (updated with dev files)
### Dockerfiles (12/12)
- [x] ci/dev/discovery.dockerfile
- [x] ci/dev/ledger.dockerfile
- [x] ci/dev/billing-fees.dockerfile
- [x] ci/dev/fx-oracle.dockerfile
- [x] ci/dev/fx-ingestor.dockerfile
- [x] ci/dev/payments-orchestrator.dockerfile
- [x] ci/dev/chain-gateway.dockerfile
- [x] ci/dev/mntx-gateway.dockerfile
- [x] ci/dev/tgsettle-gateway.dockerfile
- [x] ci/dev/notification.dockerfile
- [x] ci/dev/bff.dockerfile
- [x] ci/dev/frontend.dockerfile
### Scripts & Utilities
- [x] ci/dev/mongo.key (MongoDB keyfile)
- [x] ci/dev/vault/config.hcl (Vault config)
- [x] Vault templates removed (not needed for dev)
- [x] Makefile commands for all operations
### Documentation
- [x] SETUP.md (comprehensive setup guide)
- [x] ci/dev/README.md (quick reference)
- [x] ci/dev/CHECKLIST.md (this file)
- [x] CLAUDE.md (updated with dev commands)
## 🎯 Ready to Test
### Initial Setup
```bash
make init
```
### Start Environment
```bash
make up
```
### Initialize Vault (if needed)
```bash
make vault-init
```
### Check Status
```bash
make status
make list-services
```
## 📊 Service Dependencies
```
Infrastructure Layer:
├─ MongoDB (dev-mongo-1, dev-mongo-2, dev-mongo-3)
├─ NATS (dev-nats)
└─ Vault (dev-vault)
Service Layer:
├─ dev-discovery → [nats]
├─ dev-fx-oracle → [mongo, nats]
├─ dev-fx-ingestor → [mongo, nats]
├─ dev-billing-fees → [mongo, nats, fx-oracle]
├─ dev-ledger → [mongo, nats, discovery, billing-fees]
├─ dev-payments-orchestrator → [mongo, nats, ledger, billing-fees]
├─ dev-chain-gateway → [mongo, nats, discovery, vault]
├─ dev-mntx-gateway → [nats, discovery, vault]
├─ dev-tgsettle-gateway → [mongo, nats, discovery, vault]
├─ dev-notification → [nats]
├─ dev-bff → [mongo, nats, ledger, payments, chain-gateway]
└─ dev-frontend → [bff]
```
## 🔍 Validation Steps
### 1. Infrastructure
- [ ] MongoDB replica set initializes correctly
- [ ] NATS is accessible and healthy
- [ ] Vault starts and can be initialized
### 2. Core Services
- [ ] Discovery service starts and is healthy
- [ ] Ledger service connects to MongoDB and NATS
- [ ] Billing fees service can query FX oracle
- [ ] FX oracle provides quotes
### 3. Payment Flow
- [ ] Payments orchestrator can communicate with ledger
- [ ] Payments orchestrator can calculate fees
- [ ] Payment plan execution works
### 4. Gateways
- [ ] Chain gateway connects to Vault for keys
- [ ] MNTX gateway is reachable
- [ ] TGSettle gateway connects to Telegram
### 5. Frontend
- [ ] BFF serves API endpoints
- [ ] Frontend builds successfully
- [ ] Frontend connects to BFF
## 🐛 Known Considerations
1. **Vault Secrets**: Services using Vault (chain, mntx, tgsettle) need secrets populated
2. **External APIs**: MNTX and TGSettle need API credentials from Vault
3. **Blockchain**: Chain gateway needs blockchain node URLs and private keys
4. **Build Time**: Frontend build takes ~5-10 minutes (Flutter compile)
## 📝 Post-Setup Tasks
- [ ] Populate Vault with blockchain keys for Chain Gateway
- [ ] Add MNTX API credentials to Vault
- [ ] Add TGSettle (Telegram) credentials to Vault
- [ ] Seed test data into MongoDB
- [ ] Configure blockchain RPC endpoints
- [ ] Test full payment flow end-to-end
## 🚀 Next Development Steps
1. Test `make init && make up`
2. Verify all services start successfully
3. Check logs for any errors
4. Test service-to-service communication
5. Add seed data scripts
6. Create integration tests
---
**Status**: ✅ COMPLETE - Ready for testing
**Date**: 2026-01-27
**Build System**: Docker Compose + Makefile
**Total Services**: 12 application + 3 infrastructure = 15 containers

101
ci/dev/Caddyfile.dev Normal file
View File

@@ -0,0 +1,101 @@
########################################
# Development Caddyfile for Sendico
########################################
{
# No ACME email needed for local dev
auto_https off
}
:80 {
vars static_root /usr/share/pweb
encode zstd gzip
route {
########################################
# Backend API - proxy to dev-bff
########################################
handle /api/v1/* {
reverse_proxy dev-bff:8080 {
health_uri /api/v1/health
health_interval 15s
health_timeout 4s
health_status 2xx
}
header Cache-Control "no-cache, no-store, must-revalidate"
}
# Telegram webhook -> notification service
handle /telegram/webhook {
reverse_proxy dev-notification:8081
}
# Monetix callbacks -> mntx gateway
handle /gateway/m/* {
rewrite * /monetix/callback
reverse_proxy dev-mntx-gateway:8084
header Cache-Control "no-cache, no-store, must-revalidate"
}
########################################
# Static assets
########################################
handle /version.json {
root * {vars.static_root}
file_server
header Cache-Control "no-cache"
}
handle /*.js {
root * {vars.static_root}
file_server
header Cache-Control "public, max-age=3600"
}
handle /assets/* {
root * {vars.static_root}
file_server
header Cache-Control "public, max-age=86400"
}
handle /canvaskit/* {
root * {vars.static_root}
file_server
header Cache-Control "public, max-age=86400"
}
handle /icons/* {
root * {vars.static_root}
file_server
header Cache-Control "public, max-age=86400"
}
handle /*.html {
root * {vars.static_root}
file_server
header Cache-Control "no-cache"
}
handle /*.css {
root * {vars.static_root}
file_server
header Cache-Control "public, max-age=3600"
}
handle /*.json {
root * {vars.static_root}
file_server
header Cache-Control "public, max-age=3600"
}
########################################
# SPA fallback - serves index.html for all routes
########################################
handle {
root * {vars.static_root}
try_files {path} /index.html
file_server
header Cache-Control "no-cache"
}
}
}

124
ci/dev/README.md Normal file
View File

@@ -0,0 +1,124 @@
# Sendico Development Environment
Docker Compose + Makefile build system for local development.
## Architecture
**Infrastructure:**
- MongoDB replica set (3 nodes) - credentials in `.env.dev`
- NATS with JetStream - credentials in `.env.dev`
- Vault (single node) - for application data ONLY (blockchain keys, external API keys)
**Services:**
- Discovery, Ledger, Billing Fees, Billing Documents, FX Oracle, Payments Orchestrator
- Chain Gateway, MNTX Gateway, TGSettle Gateway
- FX Ingestor, Notification, BFF (Server), Frontend
## Quick Start
```bash
# 1. Initialize environment (first time)
make init
# 2. Start all services
make up
# 3. Initialize Vault (if services need it for application data)
make vault-init
# 4. View logs
make logs SERVICE=ledger
# 5. Stop everything
make down
```
## Credentials
All in `.env.dev` (plaintext for dev):
- MongoDB: `dev_root` / `dev_password_123`
- NATS: `dev_nats` / `nats_password_123`
- Vault: Run `make vault-init` to get root token
## Common Commands
```bash
make build # Build all images
make up # Start environment
make down # Stop environment
make logs SERVICE=ledger # View service logs
make rebuild SERVICE=ledger # Rebuild specific service
make proto # Regenerate protobuf
make clean # Remove everything
make status # Check service status
```
## Vault Usage
**Vault is ONLY for application data**, not infrastructure secrets!
Examples:
- Blockchain private keys (Chain Gateway)
- External API keys (MNTX, TGSettle)
- Production-like secrets
Infrastructure (MongoDB, NATS) uses plain `.env.dev` credentials.
## Network
All services on `sendico-dev` network. Vault also on `cicd` network to connect to infra Vault if needed.
## Rebuilding Services
After code changes:
```bash
# Rebuild specific service
make rebuild SERVICE=ledger
# Or manually
docker compose -f docker-compose.dev.yml build ledger
docker compose -f docker-compose.dev.yml up -d --force-recreate ledger
```
## Adding New Services
1. Create Dockerfile: `ci/dev/yourservice.dockerfile`
2. Add service to `docker-compose.dev.yml`
3. Use environment variables from `.env.dev`
4. Connect to `sendico-dev` network
## Troubleshooting
**MongoDB replica set not initializing:**
```bash
make down
make up
# Wait for health checks, then:
docker logs dev-mongo-init
```
**Vault sealed:**
```bash
make vault-init
# Save the unseal key and root token from vault-keys.txt
```
**Service can't connect to MongoDB:**
- Check replica set: `docker exec -it dev-mongo-1 mongosh -u dev_root -p dev_password_123 --eval "rs.status()"`
- Ensure `dev-mongo-init` completed: `docker ps -a | grep init`
**Proto generation errors:**
```bash
make proto
# Check output for missing dependencies
```
## Files
- `docker-compose.dev.yml` - All services
- `.env.dev` - Plaintext credentials
- `Makefile` - Build commands
- `ci/dev/*.dockerfile` - Service builds
- `ci/dev/mongo.key` - MongoDB replica set key
- `ci/dev/vault/config.hcl` - Vault config

52
ci/dev/bff.dockerfile Normal file
View File

@@ -0,0 +1,52 @@
# Development Dockerfile for BFF (Server) Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Copy service dependencies (needed for go.mod replace directives)
COPY api/ledger ./api/ledger
COPY api/payments/orchestrator ./api/payments/orchestrator
COPY api/gateway/chain ./api/gateway/chain
COPY api/billing/fees ./api/billing/fees
COPY api/fx/oracle ./api/fx/oracle
COPY api/fx/storage ./api/fx/storage
COPY api/gateway/mntx ./api/gateway/mntx
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Copy service dependencies
COPY --from=builder /src/api/ledger ./api/ledger
COPY --from=builder /src/api/payments/orchestrator ./api/payments/orchestrator
COPY --from=builder /src/api/gateway/chain ./api/gateway/chain
COPY --from=builder /src/api/billing/fees ./api/billing/fees
COPY --from=builder /src/api/fx/oracle ./api/fx/oracle
COPY --from=builder /src/api/fx/storage ./api/fx/storage
COPY --from=builder /src/api/gateway/mntx ./api/gateway/mntx
# Source code will be mounted at runtime
WORKDIR /src/api/server
EXPOSE 8080
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,39 @@
# Development Dockerfile for Billing Documents Service with Air hot reload
FROM golang:alpine AS builder
# Install build dependencies
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy proto and pkg first (changes less frequently)
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
# Generate protos (cached if proto files unchanged)
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Source code will be mounted at runtime to /src/api/billing/documents
WORKDIR /src/api/billing/documents
EXPOSE 50061 9409
# Use Air for hot reload
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,42 @@
# Development Dockerfile for Billing Fees Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Copy service dependencies (needed for go.mod replace directives)
COPY api/fx/oracle ./api/fx/oracle
COPY api/fx/storage ./api/fx/storage
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Copy service dependencies
COPY --from=builder /src/api/fx/oracle ./api/fx/oracle
COPY --from=builder /src/api/fx/storage ./api/fx/storage
# Source code will be mounted at runtime
WORKDIR /src/api/billing/fees
EXPOSE 50060 9402
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,40 @@
# Development Dockerfile for chain-gateway Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Copy dev-specific entrypoint script
COPY ci/dev/entrypoints/chain-gateway.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Source code will be mounted at runtime
WORKDIR /src/api/gateway/chain
EXPOSE 50070 9404
# Use entrypoint wrapper to load vault token
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,34 @@
# Development Dockerfile for discovery Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Source code will be mounted at runtime
WORKDIR /src/api/discovery
EXPOSE 9407
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,17 @@
#!/bin/sh
set -eu
# Load Vault token from file if VAULT_TOKEN_FILE is set
if [ -n "${VAULT_TOKEN_FILE:-}" ] && [ -f "${VAULT_TOKEN_FILE}" ]; then
token="$(cat "${VAULT_TOKEN_FILE}" 2>/dev/null | tr -d '[:space:]')"
if [ -n "${token}" ]; then
export VAULT_TOKEN="${token}"
fi
fi
if [ -z "${VAULT_TOKEN:-}" ]; then
echo "[entrypoint] VAULT_TOKEN is not set; expected Vault Agent sink to write a token to ${VAULT_TOKEN_FILE:-/run/vault/token}" >&2
fi
# Execute the command passed as arguments (e.g., air)
exec "$@"

View File

@@ -0,0 +1,17 @@
#!/bin/sh
set -eu
# Load Vault token from file if VAULT_TOKEN_FILE is set
if [ -n "${VAULT_TOKEN_FILE:-}" ] && [ -f "${VAULT_TOKEN_FILE}" ]; then
token="$(cat "${VAULT_TOKEN_FILE}" 2>/dev/null | tr -d '[:space:]')"
if [ -n "${token}" ]; then
export VAULT_TOKEN="${token}"
fi
fi
if [ -z "${VAULT_TOKEN:-}" ]; then
echo "[entrypoint] VAULT_TOKEN is not set; expected Vault Agent sink to write a token to ${VAULT_TOKEN_FILE:-/run/vault/token}" >&2
fi
# Execute the command passed as arguments (e.g., air)
exec "$@"

View File

@@ -0,0 +1,57 @@
# Development Dockerfile for Frontend (Flutter Web)
FROM dart:stable AS web_builder
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
unzip \
xz-utils \
libglu1-mesa \
ca-certificates \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -ms /bin/bash flutteruser
ENV PATH="/home/flutteruser/flutter/bin:${PATH}"
USER flutteruser
WORKDIR /home/flutteruser
# Install Flutter
RUN git clone --branch stable --depth 1 https://github.com/flutter/flutter.git \
&& flutter config --enable-web \
&& flutter precache --web \
&& flutter upgrade
COPY --chown=flutteruser:flutteruser frontend /home/flutteruser/app
# Replace production config with dev config
WORKDIR /home/flutteruser/app/pshared
RUN cp lib/config/common_dev.dart lib/config/common.dart
# Build shared package with code generation
RUN flutter clean \
&& flutter pub get \
&& dart run build_runner build --delete-conflicting-outputs
# Build the web client
WORKDIR /home/flutteruser/app/pweb
RUN flutter clean \
&& flutter pub get \
&& flutter build web --release --no-tree-shake-icons
# Runtime stage with Caddy
FROM caddy:alpine AS runtime
WORKDIR /usr/share/pweb
COPY --from=web_builder /home/flutteruser/app/pweb/build/web /usr/share/pweb
# Copy Caddy config (will be mounted from host)
# COPY frontend/pweb/caddy/Caddyfile /etc/caddy/Caddyfile
EXPOSE 80
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]

View File

@@ -0,0 +1,40 @@
# Development Dockerfile for fx-ingestor Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Copy service dependencies (needed for go.mod replace directives)
COPY api/fx/storage ./api/fx/storage
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Copy service dependencies
COPY --from=builder /src/api/fx/storage ./api/fx/storage
# Source code will be mounted at runtime
WORKDIR /src/api/fx/ingestor
EXPOSE 9102
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,40 @@
# Development Dockerfile for fx-oracle Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Copy service dependencies (needed for go.mod replace directives)
COPY api/fx/storage ./api/fx/storage
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Copy service dependencies
COPY --from=builder /src/api/fx/storage ./api/fx/storage
# Source code will be mounted at runtime
WORKDIR /src/api/fx/oracle
EXPOSE 50051 9400
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

39
ci/dev/ledger.dockerfile Normal file
View File

@@ -0,0 +1,39 @@
# Development Dockerfile for Ledger Service with Air hot reload
FROM golang:alpine AS builder
# Install build dependencies
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy proto and pkg first (changes less frequently)
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
# Generate protos (cached if proto files unchanged)
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Source code will be mounted at runtime to /src/api/ledger
WORKDIR /src/api/ledger
EXPOSE 50052 9401
# Use Air for hot reload
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,34 @@
# Development Dockerfile for mntx-gateway Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Source code will be mounted at runtime
WORKDIR /src/api/gateway/mntx
EXPOSE 50075 9405 8084
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

1
ci/dev/mongo.key Normal file
View File

@@ -0,0 +1 @@
FyzfUWK+e1eja+tKfkBhLBPHK34pQgsTkgsq8jxocQCt95k5dxbhw8loB+wyZ2LijIXRjGqraurKEHIl4bF12C05pRlazIyiJXA7T2iQzFtEnJW7IQM1rRWcnm5ZKwxlnnjF4t+5E48SUnXUq587iZV+osrHpGxIHu7WKMtRXE6kD+allogQ+6TCcFu8dDg4ruyC1WUkXh/5ivoFyzFE9hK5WDPo8/dURPav8j+rhaP6+ZWKisfzvssVNiLA3jyb+NYiTBgWkVXk9RXH1SpyrQZOHaOon7Kr57KQnksBrv/yJa75FfwZJagpWUkSbqtpz20e8Jimh/1c+fzE8JR0wFd0eprMQQbZ0WM1hadi5FP5I0e883nLh2brUUI9MUC0sO/+qAL7Giv6+eNhrVWfrJBA0TNQ09AygqBrgM0JJGxHe8OBtlQWWcAI58rtSU2eJmNiwMkyzccv3Z7zknjZ4Y/dGTNQ7ckzEyRQE2u+Ujgs1bje/IS+geFieVpwGoRuKyBqQsnH2uKO/M1wrMwGyPo+5AMKhXtYuMCQoz5jNuRFmOxQq/8Jykg07Z2hntoky3nvWAlDgh3B/mXJmZzi4KfXIW1xnNGzxDMnHeuNcCxAe7vlHkLX6xU74DuWUYYi92Sd6oLeyfxZqoqwgO3SEqfErDC3bOIAQBiJHKs+8UwhbfUoEdX/pLenAOPIcq1fcRQIOd/PKE/USlYGFao8mluBqJuXkw8iPeMQEBepuovebTDxIM4whzWQmMqKm/Xn/CZNhv3CISQvGg0IIqNL6q8FxFSRc7aa0ezUMpMFQBt20muL0xz8DGxdzTOqUWXr8ac2JQH0A66UXYW8iBlP3ZPh37BURBVQTB+++uF2p72jGhWHcE873ELV5Xq7LeextIDcz4L8O+C0K8v7v9tsI35PJR93+jNOS8phEkanvN2Yo6v4W1GQ8oBqbFd9R0KIwxgL7iK1H9pfA7lIeBavHjEjz8FqFxltpqz9mHvop41Sybp6

View File

@@ -0,0 +1,34 @@
# Development Dockerfile for notification Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Source code will be mounted at runtime
WORKDIR /src/api/notification
EXPOSE 8081
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,50 @@
# Development Dockerfile for Payments Orchestrator Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Copy service dependencies (needed for go.mod replace directives)
COPY api/ledger ./api/ledger
COPY api/billing/fees ./api/billing/fees
COPY api/fx/oracle ./api/fx/oracle
COPY api/fx/storage ./api/fx/storage
COPY api/gateway/chain ./api/gateway/chain
COPY api/gateway/mntx ./api/gateway/mntx
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Copy service dependencies
COPY --from=builder /src/api/ledger ./api/ledger
COPY --from=builder /src/api/billing/fees ./api/billing/fees
COPY --from=builder /src/api/fx/oracle ./api/fx/oracle
COPY --from=builder /src/api/fx/storage ./api/fx/storage
COPY --from=builder /src/api/gateway/chain ./api/gateway/chain
COPY --from=builder /src/api/gateway/mntx ./api/gateway/mntx
# Source code will be mounted at runtime
WORKDIR /src/api/payments/orchestrator
EXPOSE 50062 9403
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# MongoDB entrypoint wrapper for dev environment
# Reads credentials from Vault secrets and initializes MongoDB
set -euo pipefail
# Read credentials from Vault Agent-rendered files
if [ -s /vault/secrets/MONGO_ROOT_USER ] && [ -s /vault/secrets/MONGO_ROOT_PASSWORD ]; then
export MONGO_INITDB_ROOT_USERNAME="$(cat /vault/secrets/MONGO_ROOT_USER)"
export MONGO_INITDB_ROOT_PASSWORD="$(cat /vault/secrets/MONGO_ROOT_PASSWORD)"
fi
# Execute original MongoDB entrypoint
exec docker-entrypoint.sh "$@"

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# NATS entrypoint wrapper for dev environment
# Reads credentials from Vault secrets and starts NATS
set -euo pipefail
# Read NATS credentials from Vault Agent-rendered files
NATS_USER="$(cat /vault/secrets/NATS_USER)"
NATS_PASSWORD="$(cat /vault/secrets/NATS_PASSWORD)"
# Start NATS with JetStream
exec nats-server \
--jetstream \
--store_dir=/data \
--http_port=8222 \
--port=4222 \
--server_name=dev-nats \
--user="${NATS_USER}" \
--pass="${NATS_PASSWORD}"

View File

@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# Vault Setup Script for Development Environment
# Creates AppRole, policies, and secrets for dev services
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
# Source the vlt helper
VLT="${REPO_ROOT}/ci/vlt"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log() { echo -e "${GREEN}[vault-setup]${NC} $*"; }
warn() { echo -e "${YELLOW}[vault-setup]${NC} $*"; }
error() { echo -e "${RED}[vault-setup]${NC} $*" >&2; }
# Check prerequisites
if [ ! -f "$VLT" ]; then
error "Vault helper not found at $VLT"
exit 1
fi
if [ -z "${VAULT_ADDR:-}" ]; then
error "VAULT_ADDR is not set"
error "Please ensure Vault is running: cd infra/vault && docker compose up -d"
exit 1
fi
log "Checking Vault connection..."
if ! curl -sf "${VAULT_ADDR}/v1/sys/health" > /dev/null; then
error "Cannot connect to Vault at ${VAULT_ADDR}"
error "Please ensure Vault is running: cd infra/vault && docker compose up -d"
exit 1
fi
log "✓ Vault is reachable at ${VAULT_ADDR}"
# You need to be authenticated to Vault to run this script
# Either set VAULT_TOKEN or VAULT_ROLE_ID + VAULT_SECRET_ID
if [ -z "${VAULT_TOKEN:-}" ]; then
warn "VAULT_TOKEN not set. Attempting AppRole login..."
if [ -z "${VAULT_ROLE_ID:-}" ] || [ -z "${VAULT_SECRET_ID:-}" ]; then
error "Neither VAULT_TOKEN nor VAULT_ROLE_ID/VAULT_SECRET_ID are set"
error "Please authenticate to Vault first:"
error " export VAULT_TOKEN=<your-vault-token>"
error "Or use AppRole:"
error " export VAULT_ROLE_ID=<role-id>"
error " export VAULT_SECRET_ID=<secret-id>"
exit 1
fi
"${VLT}" login
VAULT_TOKEN="$(cat .vault_token)"
export VAULT_TOKEN
fi
log "Creating dev environment policy..."
cat <<'EOF' | curl -sf -X PUT -H "X-Vault-Token: ${VAULT_TOKEN}" \
--data-binary @- "${VAULT_ADDR}/v1/sys/policy/dev-services" > /dev/null
{
"policy": "path \"kv/data/sendico/dev\" { capabilities = [\"read\"] }"
}
EOF
log "Creating dev-services AppRole..."
curl -sf -X POST -H "X-Vault-Token: ${VAULT_TOKEN}" \
-d '{"policies": ["dev-services"], "bind_secret_id": true, "token_ttl": "24h", "token_max_ttl": "720h"}' \
"${VAULT_ADDR}/v1/auth/approle/role/dev-services" > /dev/null
log "Fetching AppRole credentials..."
ROLE_ID=$(curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/auth/approle/role/dev-services/role-id" | \
grep -o '"role_id":"[^"]*' | cut -d'"' -f4)
SECRET_ID=$(curl -sf -X POST -H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/auth/approle/role/dev-services/secret-id" | \
grep -o '"secret_id":"[^"]*' | cut -d'"' -f4)
log "Creating/updating dev secrets in Vault..."
# Generate random credentials if they don't exist
MONGO_USER="sendico_dev"
MONGO_PASSWORD="$(openssl rand -base64 32 | tr -d '/+=' | cut -c1-24)"
MONGO_KEYFILE="$(openssl rand -base64 756 | tr -d '\n')"
NATS_USER="sendico_dev"
NATS_PASSWORD="$(openssl rand -base64 32 | tr -d '/+=' | cut -c1-24)"
curl -sf -X POST -H "X-Vault-Token: ${VAULT_TOKEN}" \
-d "{
\"data\": {
\"mongo_user\": \"${MONGO_USER}\",
\"mongo_password\": \"${MONGO_PASSWORD}\",
\"mongo_keyfile\": \"${MONGO_KEYFILE}\",
\"nats_user\": \"${NATS_USER}\",
\"nats_password\": \"${NATS_PASSWORD}\"
}
}" \
"${VAULT_ADDR}/v1/kv/data/sendico/dev" > /dev/null
log "✅ Vault setup complete!"
echo ""
log "AppRole Credentials (add these to .env.dev):"
echo ""
echo "VAULT_ROLE_ID=${ROLE_ID}"
echo "VAULT_SECRET_ID=${SECRET_ID}"
echo ""
warn "IMPORTANT: Save these credentials in .env.dev"
warn "They will be needed to start the dev environment"

View File

@@ -0,0 +1,34 @@
# Development Dockerfile for tgsettle-gateway Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Source code will be mounted at runtime
WORKDIR /src/api/gateway/tgsettle
EXPOSE 50080 9406
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,40 @@
# Development Dockerfile for tron-gateway Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
# Copy dev-specific entrypoint script
COPY ci/dev/entrypoints/tron-gateway.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Source code will be mounted at runtime
WORKDIR /src/api/gateway/tron
EXPOSE 50071 9407
# Use entrypoint wrapper to load vault token
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -0,0 +1,20 @@
vault {
address = "http://dev-vault:8200"
}
auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "/run/vault/role_id"
secret_id_file_path = "/run/vault/secret_id"
}
}
sink "file" {
config = {
path = "/run/vault/token"
mode = 0600
}
}
}

View File

@@ -0,0 +1,20 @@
vault {
address = "http://dev-vault:8200"
}
auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "/run/vault/role_id"
secret_id_file_path = "/run/vault/secret_id"
}
}
sink "file" {
config = {
path = "/run/vault/token"
mode = 0600
}
}
}

49
ci/dev/vault/agent.hcl Normal file
View File

@@ -0,0 +1,49 @@
# Vault Agent for Development Environment
# AppRole credentials are injected as files from environment variables
pid_file = "/tmp/vault-agent.pid"
auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "/vault/secrets/role_id"
secret_id_file_path = "/vault/secrets/secret_id"
}
}
sink "file" {
config = { path = "/vault/token" }
}
}
vault {
address = "{{ env `VAULT_ADDR` }}"
}
# MongoDB root credentials
template {
source = "/etc/vault/templates/mongo/user.ctmpl"
destination = "/vault/secrets/MONGO_ROOT_USER"
}
template {
source = "/etc/vault/templates/mongo/pass.ctmpl"
destination = "/vault/secrets/MONGO_ROOT_PASSWORD"
}
# MongoDB replica set keyFile (strict perms for MongoDB)
template {
source = "/etc/vault/templates/mongo/keyfile.ctmpl"
destination = "/vault/secrets/mongo.kf"
command = "sh -lc 'chmod 0400 /vault/secrets/mongo.kf'"
}
# NATS credentials
template {
source = "/etc/vault/templates/nats/user.ctmpl"
destination = "/vault/secrets/NATS_USER"
}
template {
source = "/etc/vault/templates/nats/pass.ctmpl"
destination = "/vault/secrets/NATS_PASSWORD"
}

17
ci/dev/vault/config.hcl Normal file
View File

@@ -0,0 +1,17 @@
# Vault Configuration for Development
# Single node, file storage backend
storage "file" {
path = "/vault/file"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = true
}
api_addr = "http://0.0.0.0:8200"
ui = true
# Disable mlock for dev (no need for memory protection)
disable_mlock = true

View File

@@ -0,0 +1,3 @@
{{ with secret "kv/data/sendico/dev" -}}
{{ .Data.data.mongo_keyfile }}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{ with secret "kv/data/sendico/dev" -}}
{{ .Data.data.mongo_password }}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{ with secret "kv/data/sendico/dev" -}}
{{ .Data.data.mongo_user }}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{ with secret "kv/data/sendico/dev" -}}
{{ .Data.data.nats_password }}
{{- end }}

View File

@@ -0,0 +1,3 @@
{{ with secret "kv/data/sendico/dev" -}}
{{ .Data.data.nats_user }}
{{- end }}

View File

@@ -90,6 +90,13 @@ FEES_SERVICE_NAME=sendico_billing_fees
FEES_GRPC_PORT=50060
FEES_METRICS_PORT=9402
# Billing documents stack
DOCUMENTS_DIR=billing_documents
DOCUMENTS_COMPOSE_PROJECT=sendico-billing-documents
DOCUMENTS_SERVICE_NAME=sendico_billing_documents
DOCUMENTS_GRPC_PORT=50061
DOCUMENTS_METRICS_PORT=9409
# Billing fees Mongo settings
FEES_MONGO_HOST=sendico_db1
FEES_MONGO_PORT=27017

View File

@@ -0,0 +1,40 @@
# syntax=docker/dockerfile:1.7
ARG TARGETOS=linux
ARG TARGETARCH=amd64
FROM golang:alpine AS build
ARG APP_VERSION=dev
ARG GIT_REV=unknown
ARG BUILD_BRANCH=unknown
ARG BUILD_DATE=unknown
ARG BUILD_USER=ci
ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
WORKDIR /src/api/billing/documents
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags "\
-s -w \
-X github.com/tech/sendico/billing/documents/internal/appversion.Version=${APP_VERSION} \
-X github.com/tech/sendico/billing/documents/internal/appversion.Revision=${GIT_REV} \
-X github.com/tech/sendico/billing/documents/internal/appversion.Branch=${BUILD_BRANCH} \
-X github.com/tech/sendico/billing/documents/internal/appversion.BuildUser=${BUILD_USER} \
-X github.com/tech/sendico/billing/documents/internal/appversion.BuildDate=${BUILD_DATE}" \
-o /out/billing-documents .
FROM alpine:latest AS runtime
RUN apk add --no-cache ca-certificates tzdata wget
WORKDIR /app
COPY api/billing/documents/config.yml /app/config.yml
COPY --from=build /out/billing-documents /app/billing-documents
EXPOSE 50061 9409
ENTRYPOINT ["/app/billing-documents"]
CMD ["--config.file", "/app/config.yml"]

View File

@@ -0,0 +1,39 @@
# Compose v2 - Billing documents
x-common-env: &common-env
env_file:
- ../env/.env.runtime
- ../env/.env.version
networks:
sendico-net:
external: true
name: sendico-net
services:
sendico_billing_documents:
<<: *common-env
container_name: sendico-billing-documents
restart: unless-stopped
image: ${REGISTRY_URL}/billing/documents:${APP_V}
pull_policy: always
environment:
DOCUMENTS_GRPC_PORT: ${DOCUMENTS_GRPC_PORT}
DOCUMENTS_METRICS_PORT: ${DOCUMENTS_METRICS_PORT}
NATS_URL: ${NATS_URL}
NATS_HOST: ${NATS_HOST}
NATS_PORT: ${NATS_PORT}
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
command: ["--config.file", "/app/config.yml"]
ports:
- "0.0.0.0:${DOCUMENTS_GRPC_PORT}:50061"
- "0.0.0.0:${DOCUMENTS_METRICS_PORT}:${DOCUMENTS_METRICS_PORT}"
healthcheck:
test: ["CMD-SHELL","wget -qO- http://localhost:${DOCUMENTS_METRICS_PORT}/health | grep -q '\"status\":\"ok\"'"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- sendico-net

View File

@@ -0,0 +1,43 @@
# syntax=docker/dockerfile:1.7
ARG TARGETOS=linux
ARG TARGETARCH=amd64
FROM golang:alpine AS build
ARG APP_VERSION=dev
ARG GIT_REV=unknown
ARG BUILD_BRANCH=unknown
ARG BUILD_DATE=unknown
ARG BUILD_USER=ci
ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
WORKDIR /src/api/gateway/tron
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags "\
-s -w \
-X github.com/tech/sendico/gateway/tron/internal/appversion.Version=${APP_VERSION} \
-X github.com/tech/sendico/gateway/tron/internal/appversion.Revision=${GIT_REV} \
-X github.com/tech/sendico/gateway/tron/internal/appversion.Branch=${BUILD_BRANCH} \
-X github.com/tech/sendico/gateway/tron/internal/appversion.BuildUser=${BUILD_USER} \
-X github.com/tech/sendico/gateway/tron/internal/appversion.BuildDate=${BUILD_DATE}" \
-o /out/tron-gateway .
FROM alpine:latest AS runtime
RUN apk add --no-cache ca-certificates tzdata wget
WORKDIR /app
COPY api/gateway/tron/config.yml /app/config.yml
COPY api/gateway/tron/env /app/env
COPY api/gateway/tron/entrypoint.sh /app/entrypoint.sh
COPY --from=build /out/tron-gateway /app/tron-gateway
RUN chmod +x /app/entrypoint.sh
EXPOSE 50071 9407
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["/app/tron-gateway","--config.file","/app/config.yml"]

View File

@@ -0,0 +1,20 @@
vault {
address = "https://vault.sendico.io"
}
auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "/run/vault/role_id"
secret_id_file_path = "/run/vault/secret_id"
}
}
sink "file" {
config = {
path = "/run/vault/token"
mode = 0600
}
}
}

View File

@@ -0,0 +1,139 @@
#!/usr/bin/env bash
set -euo pipefail
[[ "${DEBUG_DEPLOY:-0}" = "1" ]] && set -x
trap 'echo "[deploy-billing-documents] error at line $LINENO" >&2' ERR
: "${REMOTE_BASE:?missing REMOTE_BASE}"
: "${SSH_USER:?missing SSH_USER}"
: "${SSH_HOST:?missing SSH_HOST}"
: "${DOCUMENTS_DIR:?missing DOCUMENTS_DIR}"
: "${DOCUMENTS_COMPOSE_PROJECT:?missing DOCUMENTS_COMPOSE_PROJECT}"
: "${DOCUMENTS_SERVICE_NAME:?missing DOCUMENTS_SERVICE_NAME}"
REMOTE_DIR="${REMOTE_BASE%/}/${DOCUMENTS_DIR}"
REMOTE_TARGET="${SSH_USER}@${SSH_HOST}"
COMPOSE_FILE="billing_documents.yml"
SERVICE_NAMES="${DOCUMENTS_SERVICE_NAME}"
REQUIRED_SECRETS=(
NATS_USER
NATS_PASSWORD
NATS_URL
)
for var in "${REQUIRED_SECRETS[@]}"; do
if [[ -z "${!var:-}" ]]; then
echo "missing required secret env: ${var}" >&2
exit 65
fi
done
if [[ ! -s .env.version ]]; then
echo ".env.version is missing; run version step first" >&2
exit 66
fi
b64enc() {
printf '%s' "$1" | base64 | tr -d '\n'
}
NATS_USER_B64="$(b64enc "${NATS_USER}")"
NATS_PASSWORD_B64="$(b64enc "${NATS_PASSWORD}")"
NATS_URL_B64="$(b64enc "${NATS_URL}")"
SSH_OPTS=(
-i /root/.ssh/id_rsa
-o StrictHostKeyChecking=no
-o UserKnownHostsFile=/dev/null
-o LogLevel=ERROR
-q
)
if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then
SSH_OPTS=("${SSH_OPTS[@]/-q/}" -vv)
fi
RSYNC_FLAGS=(-az --delete)
[[ "${DEBUG_DEPLOY:-0}" = "1" ]] && RSYNC_FLAGS=(-avz --delete)
ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "mkdir -p ${REMOTE_DIR}/{compose,env}"
rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/compose/ "$REMOTE_TARGET:${REMOTE_DIR}/compose/"
rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/.env.runtime "$REMOTE_TARGET:${REMOTE_DIR}/env/.env.runtime"
rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" .env.version "$REMOTE_TARGET:${REMOTE_DIR}/env/.env.version"
SERVICES_LINE="${SERVICE_NAMES}"
ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" \
REMOTE_DIR="$REMOTE_DIR" \
COMPOSE_FILE="$COMPOSE_FILE" \
COMPOSE_PROJECT="$DOCUMENTS_COMPOSE_PROJECT" \
SERVICES_LINE="$SERVICES_LINE" \
NATS_USER_B64="$NATS_USER_B64" \
NATS_PASSWORD_B64="$NATS_PASSWORD_B64" \
NATS_URL_B64="$NATS_URL_B64" \
bash -s <<'EOSSH'
set -euo pipefail
cd "${REMOTE_DIR}/compose"
set -a
. ../env/.env.runtime
load_kv_file() {
local file="$1"
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|\#*) continue ;;
esac
if printf '%s' "$line" | grep -Eq '^[[:alpha:]_][[:alnum:]_]*='; then
local key="${line%%=*}"
local value="${line#*=}"
key="$(printf '%s' "$key" | tr -d '[:space:]')"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
if [[ -n "$key" ]]; then
export "$key=$value"
fi
fi
done <"$file"
}
load_kv_file ../env/.env.version
set +a
if base64 -d >/dev/null 2>&1 <<<'AA=='; then
BASE64_DECODE_FLAG='-d'
else
BASE64_DECODE_FLAG='--decode'
fi
decode_b64() {
val="$1"
if [[ -z "$val" ]]; then
printf ''
return
fi
printf '%s' "$val" | base64 "${BASE64_DECODE_FLAG}"
}
NATS_USER="$(decode_b64 "$NATS_USER_B64")"
NATS_PASSWORD="$(decode_b64 "$NATS_PASSWORD_B64")"
NATS_URL="$(decode_b64 "$NATS_URL_B64")"
export NATS_USER NATS_PASSWORD NATS_URL
COMPOSE_PROJECT_NAME="$COMPOSE_PROJECT"
export COMPOSE_PROJECT_NAME
read -r -a SERVICES <<<"${SERVICES_LINE}"
pull_cmd=(docker compose -f "$COMPOSE_FILE" pull)
up_cmd=(docker compose -f "$COMPOSE_FILE" up -d --remove-orphans)
ps_cmd=(docker compose -f "$COMPOSE_FILE" ps)
if [[ "${#SERVICES[@]}" -gt 0 ]]; then
pull_cmd+=("${SERVICES[@]}")
up_cmd+=("${SERVICES[@]}")
ps_cmd+=("${SERVICES[@]}")
fi
"${pull_cmd[@]}"
"${up_cmd[@]}"
"${ps_cmd[@]}"
date -Is > .last_deploy
logger -t "deploy-${COMPOSE_PROJECT_NAME}" "${COMPOSE_PROJECT_NAME} deployed at $(date -Is) in ${REMOTE_DIR}"
EOSSH

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env bash
set -euo pipefail
[[ "${DEBUG_DEPLOY:-0}" = "1" ]] && set -x
trap 'echo "[deploy-tron-gateway] error at line $LINENO" >&2' ERR
: "${REMOTE_BASE:?missing REMOTE_BASE}"
: "${SSH_USER:?missing SSH_USER}"
: "${SSH_HOST:?missing SSH_HOST}"
: "${TRON_GATEWAY_DIR:?missing TRON_GATEWAY_DIR}"
: "${TRON_GATEWAY_COMPOSE_PROJECT:?missing TRON_GATEWAY_COMPOSE_PROJECT}"
: "${TRON_GATEWAY_SERVICE_NAME:?missing TRON_GATEWAY_SERVICE_NAME}"
REMOTE_DIR="${REMOTE_BASE%/}/${TRON_GATEWAY_DIR}"
REMOTE_TARGET="${SSH_USER}@${SSH_HOST}"
COMPOSE_FILE="tron_gateway.yml"
SERVICE_NAMES="${TRON_GATEWAY_SERVICE_NAME}"
REQUIRED_SECRETS=(
TRON_GATEWAY_MONGO_USER
TRON_GATEWAY_MONGO_PASSWORD
TRON_GATEWAY_RPC_URL
TRON_GATEWAY_SERVICE_WALLET_KEY
TRON_GATEWAY_SERVICE_WALLET_ADDRESS
TRON_GATEWAY_VAULT_ROLE_ID
TRON_GATEWAY_VAULT_SECRET_ID
NATS_USER
NATS_PASSWORD
NATS_URL
)
for var in "${REQUIRED_SECRETS[@]}"; do
if [[ -z "${!var:-}" ]]; then
echo "missing required secret env: ${var}" >&2
exit 65
fi
done
if [[ ! -s .env.version ]]; then
echo ".env.version is missing; run version step first" >&2
exit 66
fi
b64enc() {
printf '%s' "$1" | base64 | tr -d '\n'
}
TRON_GATEWAY_MONGO_USER_B64="$(b64enc "${TRON_GATEWAY_MONGO_USER}")"
TRON_GATEWAY_MONGO_PASSWORD_B64="$(b64enc "${TRON_GATEWAY_MONGO_PASSWORD}")"
TRON_GATEWAY_RPC_URL_B64="$(b64enc "${TRON_GATEWAY_RPC_URL}")"
TRON_GATEWAY_GRPC_URL_B64="$(b64enc "${TRON_GATEWAY_GRPC_URL:-}")"
TRON_GATEWAY_SERVICE_WALLET_KEY_B64="$(b64enc "${TRON_GATEWAY_SERVICE_WALLET_KEY}")"
TRON_GATEWAY_SERVICE_WALLET_ADDRESS_B64="$(b64enc "${TRON_GATEWAY_SERVICE_WALLET_ADDRESS}")"
TRON_GATEWAY_VAULT_ROLE_ID_B64="$(b64enc "${TRON_GATEWAY_VAULT_ROLE_ID}")"
TRON_GATEWAY_VAULT_SECRET_ID_B64="$(b64enc "${TRON_GATEWAY_VAULT_SECRET_ID}")"
NATS_USER_B64="$(b64enc "${NATS_USER}")"
NATS_PASSWORD_B64="$(b64enc "${NATS_PASSWORD}")"
NATS_URL_B64="$(b64enc "${NATS_URL}")"
SSH_OPTS=(
-i /root/.ssh/id_rsa
-o StrictHostKeyChecking=no
-o UserKnownHostsFile=/dev/null
-o LogLevel=ERROR
-q
)
if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then
SSH_OPTS=("${SSH_OPTS[@]/-q/}" -vv)
fi
RSYNC_FLAGS=(-az --delete)
[[ "${DEBUG_DEPLOY:-0}" = "1" ]] && RSYNC_FLAGS=(-avz --delete)
ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "mkdir -p ${REMOTE_DIR}/compose/secrets ${REMOTE_DIR}/env"
rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/compose/ "$REMOTE_TARGET:${REMOTE_DIR}/compose/"
rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/.env.runtime "$REMOTE_TARGET:${REMOTE_DIR}/env/.env.runtime"
rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" .env.version "$REMOTE_TARGET:${REMOTE_DIR}/env/.env.version"
SERVICES_LINE="${SERVICE_NAMES}"
ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" \
REMOTE_DIR="$REMOTE_DIR" \
COMPOSE_FILE="$COMPOSE_FILE" \
COMPOSE_PROJECT="$TRON_GATEWAY_COMPOSE_PROJECT" \
SERVICES_LINE="$SERVICES_LINE" \
TRON_GATEWAY_MONGO_USER_B64="$TRON_GATEWAY_MONGO_USER_B64" \
TRON_GATEWAY_MONGO_PASSWORD_B64="$TRON_GATEWAY_MONGO_PASSWORD_B64" \
TRON_GATEWAY_RPC_URL_B64="$TRON_GATEWAY_RPC_URL_B64" \
TRON_GATEWAY_GRPC_URL_B64="$TRON_GATEWAY_GRPC_URL_B64" \
TRON_GATEWAY_SERVICE_WALLET_KEY_B64="$TRON_GATEWAY_SERVICE_WALLET_KEY_B64" \
TRON_GATEWAY_SERVICE_WALLET_ADDRESS_B64="$TRON_GATEWAY_SERVICE_WALLET_ADDRESS_B64" \
TRON_GATEWAY_VAULT_ROLE_ID_B64="$TRON_GATEWAY_VAULT_ROLE_ID_B64" \
TRON_GATEWAY_VAULT_SECRET_ID_B64="$TRON_GATEWAY_VAULT_SECRET_ID_B64" \
NATS_USER_B64="$NATS_USER_B64" \
NATS_PASSWORD_B64="$NATS_PASSWORD_B64" \
NATS_URL_B64="$NATS_URL_B64" \
bash -s <<'EOSSH'
set -euo pipefail
cd "${REMOTE_DIR}/compose"
set -a
. ../env/.env.runtime
load_kv_file() {
local file="$1"
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|\#*) continue ;;
esac
if printf '%s' "$line" | grep -Eq '^[[:alpha:]_][[:alnum:]_]*='; then
local key="${line%%=*}"
local value="${line#*=}"
key="$(printf '%s' "$key" | tr -d '[:space:]')"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
if [[ -n "$key" ]]; then
export "$key=$value"
fi
fi
done <"$file"
}
load_kv_file ../env/.env.version
set +a
if base64 -d >/dev/null 2>&1 <<<'AA=='; then
BASE64_DECODE_FLAG='-d'
else
BASE64_DECODE_FLAG='--decode'
fi
decode_b64() {
val="$1"
if [[ -z "$val" ]]; then
printf ''
return
fi
printf '%s' "$val" | base64 "${BASE64_DECODE_FLAG}"
}
TRON_GATEWAY_MONGO_USER="$(decode_b64 "$TRON_GATEWAY_MONGO_USER_B64")"
TRON_GATEWAY_MONGO_PASSWORD="$(decode_b64 "$TRON_GATEWAY_MONGO_PASSWORD_B64")"
TRON_GATEWAY_RPC_URL="$(decode_b64 "$TRON_GATEWAY_RPC_URL_B64")"
TRON_GATEWAY_GRPC_URL="$(decode_b64 "$TRON_GATEWAY_GRPC_URL_B64")"
TRON_GATEWAY_SERVICE_WALLET_KEY="$(decode_b64 "$TRON_GATEWAY_SERVICE_WALLET_KEY_B64")"
TRON_GATEWAY_SERVICE_WALLET_ADDRESS="$(decode_b64 "$TRON_GATEWAY_SERVICE_WALLET_ADDRESS_B64")"
TRON_GATEWAY_VAULT_ROLE_ID="$(decode_b64 "$TRON_GATEWAY_VAULT_ROLE_ID_B64")"
TRON_GATEWAY_VAULT_SECRET_ID="$(decode_b64 "$TRON_GATEWAY_VAULT_SECRET_ID_B64")"
NATS_USER="$(decode_b64 "$NATS_USER_B64")"
NATS_PASSWORD="$(decode_b64 "$NATS_PASSWORD_B64")"
NATS_URL="$(decode_b64 "$NATS_URL_B64")"
export TRON_GATEWAY_MONGO_USER TRON_GATEWAY_MONGO_PASSWORD
export TRON_GATEWAY_RPC_URL TRON_GATEWAY_GRPC_URL
export TRON_GATEWAY_SERVICE_WALLET_KEY TRON_GATEWAY_SERVICE_WALLET_ADDRESS
export TRON_GATEWAY_VAULT_ROLE_ID TRON_GATEWAY_VAULT_SECRET_ID
export NATS_USER NATS_PASSWORD NATS_URL
COMPOSE_PROJECT_NAME="$COMPOSE_PROJECT"
export COMPOSE_PROJECT_NAME
read -r -a SERVICES <<<"${SERVICES_LINE}"
pull_cmd=(docker compose -f "$COMPOSE_FILE" pull)
up_cmd=(docker compose -f "$COMPOSE_FILE" up -d --remove-orphans)
ps_cmd=(docker compose -f "$COMPOSE_FILE" ps)
if [[ "${#SERVICES[@]}" -gt 0 ]]; then
pull_cmd+=("${SERVICES[@]}")
up_cmd+=("${SERVICES[@]}")
ps_cmd+=("${SERVICES[@]}")
fi
"${pull_cmd[@]}"
"${up_cmd[@]}"
"${ps_cmd[@]}"
date -Is > .last_deploy
logger -t "deploy-${COMPOSE_PROJECT_NAME}" "${COMPOSE_PROJECT_NAME} deployed at $(date -Is) in ${REMOTE_DIR}"
EOSSH

View File

@@ -0,0 +1,85 @@
#!/bin/sh
set -eu
if ! set -o pipefail 2>/dev/null; then
:
fi
REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
cd "${REPO_ROOT}"
sh ci/scripts/common/ensure_env_version.sh
normalize_env_file() {
file="$1"
tmp="${file}.tmp.$$"
tr -d '\r' <"$file" >"$tmp"
mv "$tmp" "$file"
}
load_env_file() {
file="$1"
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|\#*) continue ;;
esac
key="${line%%=*}"
value="${line#*=}"
key="$(printf '%s' "$key" | tr -d '[:space:]')"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
export "$key=$value"
done <"$file"
}
DOCUMENTS_ENV_NAME="${DOCUMENTS_ENV:-prod}"
RUNTIME_ENV_FILE="./ci/${DOCUMENTS_ENV_NAME}/.env.runtime"
if [ ! -f "${RUNTIME_ENV_FILE}" ]; then
echo "[billing-documents-build] runtime env file not found: ${RUNTIME_ENV_FILE}" >&2
exit 1
fi
normalize_env_file "${RUNTIME_ENV_FILE}"
normalize_env_file ./.env.version
load_env_file "${RUNTIME_ENV_FILE}"
load_env_file ./.env.version
REGISTRY_URL="${REGISTRY_URL:?missing REGISTRY_URL}"
APP_V="${APP_V:?missing APP_V}"
DOCUMENTS_DOCKERFILE="${DOCUMENTS_DOCKERFILE:?missing DOCUMENTS_DOCKERFILE}"
DOCUMENTS_IMAGE_PATH="${DOCUMENTS_IMAGE_PATH:?missing DOCUMENTS_IMAGE_PATH}"
REGISTRY_HOST="${REGISTRY_URL#http://}"
REGISTRY_HOST="${REGISTRY_HOST#https://}"
REGISTRY_USER="$(cat secrets/REGISTRY_USER)"
REGISTRY_PASSWORD="$(cat secrets/REGISTRY_PASSWORD)"
: "${REGISTRY_USER:?missing registry user}"
: "${REGISTRY_PASSWORD:?missing registry password}"
mkdir -p /kaniko/.docker
AUTH_B64="$(printf '%s:%s' "$REGISTRY_USER" "$REGISTRY_PASSWORD" | base64 | tr -d '\n')"
cat <<EOF_JSON >/kaniko/.docker/config.json
{
"auths": {
"https://${REGISTRY_HOST}": { "auth": "${AUTH_B64}" }
}
}
EOF_JSON
BUILD_CONTEXT="${DOCUMENTS_BUILD_CONTEXT:-${WOODPECKER_WORKSPACE:-${CI_WORKSPACE:-${PWD:-/workspace}}}}"
if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${DOCUMENTS_DOCKERFILE}" \
--destination "${REGISTRY_URL}/${DOCUMENTS_IMAGE_PATH}:${APP_V}" \
--build-arg APP_VERSION="${APP_V}" \
--build-arg GIT_REV="${GIT_REV}" \
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot

View File

@@ -0,0 +1,54 @@
#!/bin/sh
set -eu
if ! set -o pipefail 2>/dev/null; then
:
fi
REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
cd "${REPO_ROOT}"
sh ci/scripts/common/ensure_env_version.sh
normalize_env_file() {
file="$1"
tmp="${file}.tmp.$$"
tr -d '\r' <"$file" >"$tmp"
mv "$tmp" "$file"
}
load_env_file() {
file="$1"
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|\#*) continue ;;
esac
key="${line%%=*}"
value="${line#*=}"
key="$(printf '%s' "$key" | tr -d '[:space:]')"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
export "$key=$value"
done <"$file"
}
. ci/scripts/common/nats_env.sh
DOCUMENTS_ENV_NAME="${DOCUMENTS_ENV:-prod}"
RUNTIME_ENV_FILE="./ci/${DOCUMENTS_ENV_NAME}/.env.runtime"
if [ ! -f "${RUNTIME_ENV_FILE}" ]; then
echo "[billing-documents-deploy] runtime env file not found: ${RUNTIME_ENV_FILE}" >&2
exit 1
fi
normalize_env_file "${RUNTIME_ENV_FILE}"
normalize_env_file ./.env.version
load_env_file "${RUNTIME_ENV_FILE}"
load_env_file ./.env.version
load_nats_env
bash ci/prod/scripts/bootstrap/network.sh
bash ci/prod/scripts/deploy/billing_documents.sh

View File

@@ -134,4 +134,10 @@ if [ -f "${PROTO_DIR}/billing/fees/v1/fees.proto" ]; then
generate_go_with_grpc "${PROTO_DIR}/billing/fees/v1/fees.proto"
fi
if [ -f "${PROTO_DIR}/billing/documents/v1/documents.proto" ]; then
info "Compiling billing documents protos"
clean_pb_files "./pkg/proto/billing/documents"
generate_go_with_grpc "${PROTO_DIR}/billing/documents/v1/documents.proto"
fi
info "Protobuf generation completed"

View File

@@ -0,0 +1,85 @@
#!/bin/sh
set -eu
if ! set -o pipefail 2>/dev/null; then
:
fi
REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
cd "${REPO_ROOT}"
sh ci/scripts/common/ensure_env_version.sh
normalize_env_file() {
file="$1"
tmp="${file}.tmp.$$"
tr -d '\r' <"$file" >"$tmp"
mv "$tmp" "$file"
}
load_env_file() {
file="$1"
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|\#*) continue ;;
esac
key="${line%%=*}"
value="${line#*=}"
key="$(printf '%s' "$key" | tr -d '[:space:]')"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
export "$key=$value"
done <"$file"
}
TRON_GATEWAY_ENV_NAME="${TRON_GATEWAY_ENV:-prod}"
RUNTIME_ENV_FILE="./ci/${TRON_GATEWAY_ENV_NAME}/.env.runtime"
if [ ! -f "${RUNTIME_ENV_FILE}" ]; then
echo "[tron-gateway-build] runtime env file not found: ${RUNTIME_ENV_FILE}" >&2
exit 1
fi
normalize_env_file "${RUNTIME_ENV_FILE}"
normalize_env_file ./.env.version
load_env_file "${RUNTIME_ENV_FILE}"
load_env_file ./.env.version
REGISTRY_URL="${REGISTRY_URL:?missing REGISTRY_URL}"
APP_V="${APP_V:?missing APP_V}"
TRON_GATEWAY_DOCKERFILE="${TRON_GATEWAY_DOCKERFILE:?missing TRON_GATEWAY_DOCKERFILE}"
TRON_GATEWAY_IMAGE_PATH="${TRON_GATEWAY_IMAGE_PATH:?missing TRON_GATEWAY_IMAGE_PATH}"
REGISTRY_HOST="${REGISTRY_URL#http://}"
REGISTRY_HOST="${REGISTRY_HOST#https://}"
REGISTRY_USER="$(cat secrets/REGISTRY_USER)"
REGISTRY_PASSWORD="$(cat secrets/REGISTRY_PASSWORD)"
: "${REGISTRY_USER:?missing registry user}"
: "${REGISTRY_PASSWORD:?missing registry password}"
mkdir -p /kaniko/.docker
AUTH_B64="$(printf '%s:%s' "$REGISTRY_USER" "$REGISTRY_PASSWORD" | base64 | tr -d '\n')"
cat <<EOF >/kaniko/.docker/config.json
{
"auths": {
"https://${REGISTRY_HOST}": { "auth": "${AUTH_B64}" }
}
}
EOF
BUILD_CONTEXT="${TRON_GATEWAY_BUILD_CONTEXT:-${WOODPECKER_WORKSPACE:-${CI_WORKSPACE:-${PWD:-/workspace}}}}"
if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${TRON_GATEWAY_DOCKERFILE}" \
--destination "${REGISTRY_URL}/${TRON_GATEWAY_IMAGE_PATH}:${APP_V}" \
--build-arg APP_VERSION="${APP_V}" \
--build-arg GIT_REV="${GIT_REV}" \
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot

View File

@@ -0,0 +1,75 @@
#!/bin/sh
set -eu
if ! set -o pipefail 2>/dev/null; then
:
fi
REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
cd "${REPO_ROOT}"
sh ci/scripts/common/ensure_env_version.sh
normalize_env_file() {
file="$1"
tmp="${file}.tmp.$$"
tr -d '\r' <"$file" >"$tmp"
mv "$tmp" "$file"
}
load_env_file() {
file="$1"
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|\#*) continue ;;
esac
key="${line%%=*}"
value="${line#*=}"
key="$(printf '%s' "$key" | tr -d '[:space:]')"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
export "$key=$value"
done <"$file"
}
. ci/scripts/common/nats_env.sh
TRON_GATEWAY_ENV_NAME="${TRON_GATEWAY_ENV:-prod}"
RUNTIME_ENV_FILE="./ci/${TRON_GATEWAY_ENV_NAME}/.env.runtime"
if [ ! -f "${RUNTIME_ENV_FILE}" ]; then
echo "[tron-gateway-deploy] runtime env file not found: ${RUNTIME_ENV_FILE}" >&2
exit 1
fi
normalize_env_file "${RUNTIME_ENV_FILE}"
normalize_env_file ./.env.version
load_env_file "${RUNTIME_ENV_FILE}"
load_env_file ./.env.version
TRON_GATEWAY_MONGO_SECRET_PATH="${TRON_GATEWAY_MONGO_SECRET_PATH:?missing TRON_GATEWAY_MONGO_SECRET_PATH}"
TRON_GATEWAY_RPC_SECRET_PATH="${TRON_GATEWAY_RPC_SECRET_PATH:?missing TRON_GATEWAY_RPC_SECRET_PATH}"
TRON_GATEWAY_WALLET_SECRET_PATH="${TRON_GATEWAY_WALLET_SECRET_PATH:?missing TRON_GATEWAY_WALLET_SECRET_PATH}"
TRON_GATEWAY_VAULT_SECRET_PATH="${TRON_GATEWAY_VAULT_SECRET_PATH:?missing TRON_GATEWAY_VAULT_SECRET_PATH}"
export TRON_GATEWAY_MONGO_USER="$(./ci/vlt kv_get kv "${TRON_GATEWAY_MONGO_SECRET_PATH}" user)"
export TRON_GATEWAY_MONGO_PASSWORD="$(./ci/vlt kv_get kv "${TRON_GATEWAY_MONGO_SECRET_PATH}" password)"
export TRON_GATEWAY_RPC_URL="$(./ci/vlt kv_get kv "${TRON_GATEWAY_RPC_SECRET_PATH}" tron_rpc_url)"
export TRON_GATEWAY_GRPC_URL="$(./ci/vlt kv_get kv "${TRON_GATEWAY_RPC_SECRET_PATH}" tron_grpc_url || true)"
export TRON_GATEWAY_SERVICE_WALLET_KEY="$(./ci/vlt kv_get kv "${TRON_GATEWAY_WALLET_SECRET_PATH}" private_key)"
export TRON_GATEWAY_SERVICE_WALLET_ADDRESS="$(./ci/vlt kv_get kv "${TRON_GATEWAY_WALLET_SECRET_PATH}" address || true)"
export TRON_GATEWAY_VAULT_ROLE_ID="$(./ci/vlt kv_get kv "${TRON_GATEWAY_VAULT_SECRET_PATH}" role_id)"
export TRON_GATEWAY_VAULT_SECRET_ID="$(./ci/vlt kv_get kv "${TRON_GATEWAY_VAULT_SECRET_PATH}" secret_id)"
if [ -z "${TRON_GATEWAY_VAULT_ROLE_ID}" ] || [ -z "${TRON_GATEWAY_VAULT_SECRET_ID}" ]; then
echo "[tron-gateway-deploy] vault approle creds are empty for path ${TRON_GATEWAY_VAULT_SECRET_PATH}" >&2
exit 1
fi
load_nats_env
bash ci/prod/scripts/bootstrap/network.sh
bash ci/prod/scripts/deploy/tron_gateway.sh

807
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,807 @@
# Sendico Development Environment
# Vault is ONLY used for application data (blockchain keys, external API keys)
# NOT for infrastructure credentials (MongoDB, NATS use plain .env)
x-common-env: &common-env
env_file:
- .env.dev
networks:
sendico-dev:
name: sendico-dev
driver: bridge
volumes:
dev-mongo1-data:
dev-mongo2-data:
dev-mongo3-data:
dev-nats-data:
dev-vault-data:
dev-chain-gateway-vault-run:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: size=8m,uid=0,gid=0,mode=0700
dev-tron-gateway-vault-run:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: size=8m,uid=0,gid=0,mode=0700
# ============================================================================
# INFRASTRUCTURE SERVICES
# ============================================================================
services:
# --------------------------------------------------------------------------
# Vault (Single Node) - for application data ONLY
# --------------------------------------------------------------------------
dev-vault:
image: hashicorp/vault:latest
container_name: dev-vault
restart: unless-stopped
cap_add: [IPC_LOCK]
environment:
VAULT_ADDR: http://0.0.0.0:8200
volumes:
- dev-vault-data:/vault/file
- ./ci/dev/vault/config.hcl:/vault/config/vault.hcl:ro
command: vault server -config=/vault/config/vault.hcl
ports:
- "8200:8200"
networks:
- sendico-dev
healthcheck:
test: ["CMD-SHELL", "wget --spider --proxy off http://127.0.0.1:8200/v1/sys/health || [ $? -eq 1 ]"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# --------------------------------------------------------------------------
# MongoDB Replica Set (3 nodes) - plain credentials from .env
# --------------------------------------------------------------------------
dev-mongo-1:
<<: *common-env
image: mongo:latest
container_name: dev-mongo-1
restart: unless-stopped
command: >
mongod --replSet dev-rs --bind_ip_all --auth
--keyFile /data/mongo.key --port 27017
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
volumes:
- dev-mongo1-data:/data/db
- ./ci/dev/mongo.key:/data/mongo.key:ro
ports:
- "27017:27017"
networks:
- sendico-dev
healthcheck:
test: ["CMD-SHELL", "mongosh --quiet --host localhost --port 27017 --eval 'db.runCommand({ ping: 1 }).ok' || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
dev-mongo-2:
<<: *common-env
image: mongo:latest
container_name: dev-mongo-2
restart: unless-stopped
command: >
mongod --replSet dev-rs --bind_ip_all --auth
--keyFile /data/mongo.key --port 27017
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
volumes:
- dev-mongo2-data:/data/db
- ./ci/dev/mongo.key:/data/mongo.key:ro
networks:
- sendico-dev
healthcheck:
test: ["CMD-SHELL", "mongosh --quiet --host localhost --port 27017 --eval 'db.runCommand({ ping: 1 }).ok' || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
dev-mongo-3:
<<: *common-env
image: mongo:latest
container_name: dev-mongo-3
restart: unless-stopped
command: >
mongod --replSet dev-rs --bind_ip_all --auth
--keyFile /data/mongo.key --port 27017
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
volumes:
- dev-mongo3-data:/data/db
- ./ci/dev/mongo.key:/data/mongo.key:ro
networks:
- sendico-dev
healthcheck:
test: ["CMD-SHELL", "mongosh --quiet --host localhost --port 27017 --eval 'db.runCommand({ ping: 1 }).ok' || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
# --------------------------------------------------------------------------
# MongoDB Replica Set Initialization
# --------------------------------------------------------------------------
dev-mongo-init:
<<: *common-env
image: mongo:latest
container_name: dev-mongo-init
depends_on:
dev-mongo-1: { condition: service_healthy }
dev-mongo-2: { condition: service_healthy }
dev-mongo-3: { condition: service_healthy }
networks:
- sendico-dev
entrypoint: |
bash -c '
set -euo pipefail
echo "Waiting for MongoDB nodes..."
until mongosh --quiet --host dev-mongo-1 --port 27017 --eval "db.adminCommand({ ping: 1 })"; do
sleep 2
done
echo "Initializing replica set..."
mongosh --host dev-mongo-1 --port 27017 -u "${MONGO_USER}" -p "${MONGO_PASSWORD}" --authenticationDatabase admin <<EOF || echo "Replica set already initialized"
try {
rs.status()
print("Replica set already initialized")
} catch (e) {
rs.initiate({
_id: "dev-rs",
members: [
{ _id: 0, host: "dev-mongo-1:27017", priority: 2 },
{ _id: 1, host: "dev-mongo-2:27017", priority: 1 },
{ _id: 2, host: "dev-mongo-3:27017", priority: 1 }
]
})
print("Replica set initialized")
}
EOF
echo "✅ MongoDB replica set ready"
'
restart: "no"
# --------------------------------------------------------------------------
# NATS with JetStream - plain credentials from .env
# --------------------------------------------------------------------------
dev-nats:
<<: *common-env
image: nats:latest
container_name: dev-nats
restart: unless-stopped
command:
- --jetstream
- --store_dir=/data
- --http_port=8222
- --port=4222
- --server_name=dev-nats
- --user=${NATS_USER}
- --pass=${NATS_PASSWORD}
volumes:
- dev-nats-data:/data
ports:
- "4222:4222"
- "8222:8222"
networks:
- sendico-dev
# ============================================================================
# APPLICATION SERVICES
# ============================================================================
# --------------------------------------------------------------------------
# Discovery Service
# --------------------------------------------------------------------------
dev-discovery:
<<: *common-env
build:
context: .
dockerfile: ci/dev/discovery.dockerfile
image: sendico-dev/discovery:latest
container_name: dev-discovery
restart: unless-stopped
depends_on:
dev-nats: { condition: service_started }
volumes:
- ./api/discovery:/src/api/discovery
- ./api/discovery/config.dev.yml:/app/config.yml:ro
ports:
- "9407:9407"
networks:
- sendico-dev
environment:
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
DISCOVERY_METRICS_PORT: 9407
# --------------------------------------------------------------------------
# FX Oracle Service
# --------------------------------------------------------------------------
dev-fx-oracle:
<<: *common-env
build:
context: .
dockerfile: ci/dev/fx-oracle.dockerfile
image: sendico-dev/fx-oracle:latest
container_name: dev-fx-oracle
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
volumes:
- ./api/fx/oracle:/src/api/fx/oracle
- ./api/fx/oracle/config.dev.yml:/app/config.yml:ro
ports:
- "50051:50051"
- "9400:9400"
networks:
- sendico-dev
environment:
FX_MONGO_HOST: dev-mongo-1
FX_MONGO_PORT: 27017
FX_MONGO_DATABASE: fx
FX_MONGO_USER: ${MONGO_USER}
FX_MONGO_PASSWORD: ${MONGO_PASSWORD}
FX_MONGO_AUTH_SOURCE: admin
FX_MONGO_REPLICA_SET: dev-rs
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
FX_ORACLE_GRPC_PORT: 50051
FX_ORACLE_METRICS_PORT: 9400
# --------------------------------------------------------------------------
# Billing Fees Service
# --------------------------------------------------------------------------
dev-billing-fees:
<<: *common-env
build:
context: .
dockerfile: ci/dev/billing-fees.dockerfile
image: sendico-dev/billing-fees:latest
container_name: dev-billing-fees
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
dev-fx-oracle: { condition: service_started }
volumes:
- ./api/billing/fees:/src/api/billing/fees
- ./api/billing/fees/config.dev.yml:/app/config.yml:ro
ports:
- "50060:50060"
- "9402:9402"
networks:
- sendico-dev
environment:
FEES_MONGO_HOST: dev-mongo-1
FEES_MONGO_PORT: 27017
FEES_MONGO_DATABASE: billing_fees
FEES_MONGO_USER: ${MONGO_USER}
FEES_MONGO_PASSWORD: ${MONGO_PASSWORD}
FEES_MONGO_AUTH_SOURCE: admin
FEES_MONGO_REPLICA_SET: dev-rs
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
FEES_GRPC_PORT: 50060
FEES_METRICS_PORT: 9402
# --------------------------------------------------------------------------
# Billing Documents Service
# --------------------------------------------------------------------------
dev-billing-documents:
<<: *common-env
build:
context: .
dockerfile: ci/dev/billing-documents.dockerfile
image: sendico-dev/billing-documents:latest
container_name: dev-billing-documents
restart: unless-stopped
depends_on:
dev-nats: { condition: service_started }
volumes:
- ./api/billing/documents:/src/api/billing/documents
- ./api/billing/documents/config.dev.yml:/app/config.yml:ro
ports:
- "50061:50061"
- "9409:9409"
networks:
- sendico-dev
environment:
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
DOCUMENTS_GRPC_PORT: 50061
DOCUMENTS_METRICS_PORT: 9409
# --------------------------------------------------------------------------
# Ledger Service
# --------------------------------------------------------------------------
dev-ledger:
<<: *common-env
build:
context: .
dockerfile: ci/dev/ledger.dockerfile
image: sendico-dev/ledger:latest
container_name: dev-ledger
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
dev-discovery: { condition: service_started }
dev-billing-fees: { condition: service_started }
volumes:
- ./api/ledger:/src/api/ledger
- ./api/ledger/config.dev.yml:/app/config.yml:ro
ports:
- "50052:50052"
- "9401:9401"
networks:
- sendico-dev
environment:
LEDGER_MONGO_HOST: dev-mongo-1
LEDGER_MONGO_PORT: 27017
LEDGER_MONGO_DATABASE: ledger
LEDGER_MONGO_USER: ${MONGO_USER}
LEDGER_MONGO_PASSWORD: ${MONGO_PASSWORD}
LEDGER_MONGO_AUTH_SOURCE: admin
LEDGER_MONGO_REPLICA_SET: dev-rs
MONGO_HOSTS_0: dev-mongo-1
MONGO_PORTS_0: 27017
MONGO_HOSTS_1: dev-mongo-2
MONGO_PORTS_1: 27017
MONGO_HOSTS_2: dev-mongo-3
MONGO_PORTS_2: 27017
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
LEDGER_GRPC_PORT: 50052
LEDGER_METRICS_PORT: 9401
# --------------------------------------------------------------------------
# FX Ingestor Service
# --------------------------------------------------------------------------
dev-fx-ingestor:
<<: *common-env
build:
context: .
dockerfile: ci/dev/fx-ingestor.dockerfile
image: sendico-dev/fx-ingestor:latest
container_name: dev-fx-ingestor
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
volumes:
- ./api/fx/ingestor:/src/api/fx/ingestor
- ./api/fx/ingestor/config.dev.yml:/app/config.yml:ro
ports:
- "9102:9102"
networks:
- sendico-dev
environment:
FX_MONGO_HOST: dev-mongo-1
FX_MONGO_PORT: 27017
FX_MONGO_DATABASE: fx
FX_MONGO_USER: ${MONGO_USER}
FX_MONGO_PASSWORD: ${MONGO_PASSWORD}
FX_MONGO_AUTH_SOURCE: admin
FX_MONGO_REPLICA_SET: dev-rs
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
FX_INGESTOR_METRICS_PORT: 9102
# --------------------------------------------------------------------------
# Payments Orchestrator Service
# --------------------------------------------------------------------------
dev-payments-orchestrator:
<<: *common-env
build:
context: .
dockerfile: ci/dev/payments-orchestrator.dockerfile
image: sendico-dev/payments-orchestrator:latest
container_name: dev-payments-orchestrator
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
dev-ledger: { condition: service_started }
dev-billing-fees: { condition: service_started }
volumes:
- ./api/payments/orchestrator:/src/api/payments/orchestrator
- ./api/payments/orchestrator/config.dev.yml:/app/config.yml:ro
ports:
- "50062:50062"
- "9403:9403"
networks:
- sendico-dev
environment:
PAYMENTS_MONGO_HOST: dev-mongo-1
PAYMENTS_MONGO_PORT: 27017
PAYMENTS_MONGO_DATABASE: payments_orchestrator
PAYMENTS_MONGO_USER: ${MONGO_USER}
PAYMENTS_MONGO_PASSWORD: ${MONGO_PASSWORD}
PAYMENTS_MONGO_AUTH_SOURCE: admin
PAYMENTS_MONGO_REPLICA_SET: dev-rs
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
PAYMENTS_GRPC_PORT: 50062
PAYMENTS_METRICS_PORT: 9403
# --------------------------------------------------------------------------
# Chain Gateway Vault Agent (sidecar for AppRole authentication)
# --------------------------------------------------------------------------
dev-chain-gateway-vault-agent:
<<: *common-env
image: hashicorp/vault:latest
container_name: dev-chain-gateway-vault-agent
restart: unless-stopped
cap_add: ["IPC_LOCK"]
environment:
VAULT_ADDR: ${VAULT_ADDR}
CHAIN_GATEWAY_VAULT_ROLE_ID: ${CHAIN_GATEWAY_VAULT_ROLE_ID}
CHAIN_GATEWAY_VAULT_SECRET_ID: ${CHAIN_GATEWAY_VAULT_SECRET_ID}
command: >
sh -c 'set -eu; umask 077;
echo "$$CHAIN_GATEWAY_VAULT_ROLE_ID" > /run/vault/role_id;
echo "$$CHAIN_GATEWAY_VAULT_SECRET_ID" > /run/vault/secret_id;
unset CHAIN_GATEWAY_VAULT_ROLE_ID CHAIN_GATEWAY_VAULT_SECRET_ID;
exec vault agent -config=/etc/vault/agent/chain-gateway.hcl'
volumes:
- ./ci/dev/vault-agent/chain-gateway.hcl:/etc/vault/agent/chain-gateway.hcl:ro
- dev-chain-gateway-vault-run:/run/vault
depends_on:
dev-vault: { condition: service_healthy }
healthcheck:
test: ["CMD", "test", "-s", "/run/vault/token"]
interval: 10s
timeout: 5s
retries: 6
networks:
- sendico-dev
# --------------------------------------------------------------------------
# Chain Gateway Service (blockchain - connects to Vault for keys)
# --------------------------------------------------------------------------
dev-chain-gateway:
<<: *common-env
build:
context: .
dockerfile: ci/dev/chain-gateway.dockerfile
image: sendico-dev/chain-gateway:latest
container_name: dev-chain-gateway
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
dev-discovery: { condition: service_started }
dev-vault: { condition: service_healthy }
dev-chain-gateway-vault-agent: { condition: service_healthy }
volumes:
- ./api/gateway/chain:/src/api/gateway/chain
- ./api/gateway/chain/config.dev.yml:/app/config.yml:ro
- dev-chain-gateway-vault-run:/run/vault:ro
ports:
- "50070:50070"
- "9404:9404"
networks:
- sendico-dev
environment:
CHAIN_GATEWAY_MONGO_HOST: dev-mongo-1
CHAIN_GATEWAY_MONGO_PORT: 27017
CHAIN_GATEWAY_MONGO_DATABASE: chain_gateway
CHAIN_GATEWAY_MONGO_USER: ${MONGO_USER}
CHAIN_GATEWAY_MONGO_PASSWORD: ${MONGO_PASSWORD}
CHAIN_GATEWAY_MONGO_AUTH_SOURCE: admin
CHAIN_GATEWAY_MONGO_REPLICA_SET: dev-rs
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
CHAIN_GATEWAY_GRPC_PORT: 50070
CHAIN_GATEWAY_METRICS_PORT: 9404
VAULT_ADDR: ${VAULT_ADDR}
VAULT_TOKEN_FILE: /run/vault/token
CHAIN_GATEWAY_RPC_URL: ${CHAIN_GATEWAY_RPC_URL}
# --------------------------------------------------------------------------
# TRON Gateway Vault Agent (sidecar for AppRole authentication)
# --------------------------------------------------------------------------
dev-tron-gateway-vault-agent:
<<: *common-env
image: hashicorp/vault:latest
container_name: dev-tron-gateway-vault-agent
restart: unless-stopped
cap_add: ["IPC_LOCK"]
environment:
VAULT_ADDR: ${VAULT_ADDR}
TRON_GATEWAY_VAULT_ROLE_ID: ${TRON_GATEWAY_VAULT_ROLE_ID}
TRON_GATEWAY_VAULT_SECRET_ID: ${TRON_GATEWAY_VAULT_SECRET_ID}
command: >
sh -c 'set -eu; umask 077;
echo "$$TRON_GATEWAY_VAULT_ROLE_ID" > /run/vault/role_id;
echo "$$TRON_GATEWAY_VAULT_SECRET_ID" > /run/vault/secret_id;
unset TRON_GATEWAY_VAULT_ROLE_ID TRON_GATEWAY_VAULT_SECRET_ID;
exec vault agent -config=/etc/vault/agent/tron-gateway.hcl'
volumes:
- ./ci/dev/vault-agent/tron-gateway.hcl:/etc/vault/agent/tron-gateway.hcl:ro
- dev-tron-gateway-vault-run:/run/vault
depends_on:
dev-vault: { condition: service_healthy }
healthcheck:
test: ["CMD", "test", "-s", "/run/vault/token"]
interval: 10s
timeout: 5s
retries: 6
networks:
- sendico-dev
# --------------------------------------------------------------------------
# TRON Gateway Service (TRON blockchain - connects to Vault for keys)
# --------------------------------------------------------------------------
dev-tron-gateway:
<<: *common-env
build:
context: .
dockerfile: ci/dev/tron-gateway.dockerfile
image: sendico-dev/tron-gateway:latest
container_name: dev-tron-gateway
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
dev-discovery: { condition: service_started }
dev-vault: { condition: service_healthy }
dev-tron-gateway-vault-agent: { condition: service_healthy }
volumes:
- ./api/gateway/tron:/src/api/gateway/tron
- ./api/gateway/tron/config.dev.yml:/app/config.yml:ro
- dev-tron-gateway-vault-run:/run/vault:ro
ports:
- "50071:50071"
- "9408:9407"
networks:
- sendico-dev
environment:
TRON_GATEWAY_MONGO_HOST: dev-mongo-1
TRON_GATEWAY_MONGO_PORT: 27017
TRON_GATEWAY_MONGO_DATABASE: tron_gateway
TRON_GATEWAY_MONGO_USER: ${MONGO_USER}
TRON_GATEWAY_MONGO_PASSWORD: ${MONGO_PASSWORD}
TRON_GATEWAY_MONGO_AUTH_SOURCE: admin
TRON_GATEWAY_MONGO_REPLICA_SET: dev-rs
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
TRON_GATEWAY_GRPC_PORT: 50071
TRON_GATEWAY_METRICS_PORT: 9407
VAULT_ADDR: ${VAULT_ADDR}
VAULT_TOKEN_FILE: /run/vault/token
TRON_GATEWAY_RPC_URL: ${TRON_GATEWAY_RPC_URL:-}
TRON_GATEWAY_GRPC_URL: ${TRON_GATEWAY_GRPC_URL:-}
TRON_GATEWAY_GRPC_TOKEN: ${TRON_GATEWAY_GRPC_TOKEN:-}
# --------------------------------------------------------------------------
# MNTX Gateway Service (card payouts)
# --------------------------------------------------------------------------
dev-mntx-gateway:
<<: *common-env
build:
context: .
dockerfile: ci/dev/mntx-gateway.dockerfile
image: sendico-dev/mntx-gateway:latest
container_name: dev-mntx-gateway
restart: unless-stopped
depends_on:
dev-nats: { condition: service_started }
dev-discovery: { condition: service_started }
dev-vault: { condition: service_healthy }
volumes:
- ./api/gateway/mntx:/src/api/gateway/mntx
- ./api/gateway/mntx/config.dev.yml:/app/config.yml:ro
ports:
- "50075:50075"
- "9405:9405"
- "8084:8084"
networks:
- sendico-dev
environment:
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
MNTX_GATEWAY_GRPC_PORT: 50075
MNTX_GATEWAY_METRICS_PORT: 9405
MNTX_GATEWAY_HTTP_PORT: 8084
VAULT_ADDR: ${VAULT_ADDR}
# --------------------------------------------------------------------------
# TGSettle Gateway Service (Telegram settlements)
# --------------------------------------------------------------------------
dev-tgsettle-gateway:
<<: *common-env
build:
context: .
dockerfile: ci/dev/tgsettle-gateway.dockerfile
image: sendico-dev/tgsettle-gateway:latest
container_name: dev-tgsettle-gateway
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
dev-discovery: { condition: service_started }
dev-vault: { condition: service_healthy }
volumes:
- ./api/gateway/tgsettle:/src/api/gateway/tgsettle
- ./api/gateway/tgsettle/config.dev.yml:/app/config.yml:ro
ports:
- "50080:50080"
- "9406:9406"
networks:
- sendico-dev
environment:
TGSETTLE_GATEWAY_MONGO_HOST: dev-mongo-1
TGSETTLE_GATEWAY_MONGO_PORT: 27017
TGSETTLE_GATEWAY_MONGO_DATABASE: tgsettle_gateway
TGSETTLE_GATEWAY_MONGO_USER: ${MONGO_USER}
TGSETTLE_GATEWAY_MONGO_PASSWORD: ${MONGO_PASSWORD}
TGSETTLE_GATEWAY_MONGO_AUTH_SOURCE: admin
TGSETTLE_GATEWAY_MONGO_REPLICA_SET: dev-rs
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
TGSETTLE_GATEWAY_GRPC_PORT: 50080
TGSETTLE_GATEWAY_METRICS_PORT: 9406
VAULT_ADDR: ${VAULT_ADDR}
# --------------------------------------------------------------------------
# Notification Service
# --------------------------------------------------------------------------
dev-notification:
<<: *common-env
build:
context: .
dockerfile: ci/dev/notification.dockerfile
image: sendico-dev/notification:latest
container_name: dev-notification
restart: unless-stopped
depends_on:
dev-nats: { condition: service_started }
volumes:
- ./api/notification:/src/api/notification
- ./api/notification/config.dev.yml:/app/config.yml:ro
ports:
- "8081:8081"
networks:
- sendico-dev
environment:
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
NOTIFICATION_HTTP_PORT: 8081
MONGO_HOST: dev-mongo-1
MONGO_PORT: 27017
MONGO_DATABASE: sendico
MONGO_USER: ${MONGO_USER}
MONGO_PASSWORD: ${MONGO_PASSWORD}
MONGO_AUTH_SOURCE: admin
MONGO_REPLICA_SET: dev-rs
# --------------------------------------------------------------------------
# BFF (Backend for Frontend / Server) Service
# --------------------------------------------------------------------------
dev-bff:
<<: *common-env
build:
context: .
dockerfile: ci/dev/bff.dockerfile
image: sendico-dev/bff:latest
container_name: dev-bff
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
dev-nats: { condition: service_started }
dev-ledger: { condition: service_started }
dev-payments-orchestrator: { condition: service_started }
dev-chain-gateway: { condition: service_started }
volumes:
- ./api/server:/src/api/server
- ./api/server/config.dev.yml:/app/config.yml:ro
ports:
- "8080:8080"
networks:
- sendico-dev
environment:
MONGO_HOST: dev-mongo-1
MONGO_PORT: 27017
MONGO_DATABASE: sendico
MONGO_USER: ${MONGO_USER}
MONGO_PASSWORD: ${MONGO_PASSWORD}
MONGO_AUTH_SOURCE: admin
MONGO_REPLICA_SET: dev-rs
MONGO_HOSTS_0: dev-mongo-1
MONGO_PORTS_0: 27017
MONGO_HOSTS_1: dev-mongo-2
MONGO_PORTS_1: 27017
MONGO_HOSTS_2: dev-mongo-3
MONGO_PORTS_2: 27017
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
LEDGER_ADDRESS: dev-ledger:50052
PAYMENTS_ADDRESS: dev-payments-orchestrator:50062
CHAIN_GATEWAY_ADDRESS: dev-chain-gateway:50070
BFF_HTTP_PORT: 8080
API_PROTOCOL: http
SERVICE_HOST: localhost
API_ENDPOINT: /api/v1
# --------------------------------------------------------------------------
# Frontend (Flutter Web)
# --------------------------------------------------------------------------
dev-frontend:
<<: *common-env
build:
context: .
dockerfile: ci/dev/frontend.dockerfile
image: sendico-dev/frontend:latest
container_name: dev-frontend
restart: unless-stopped
depends_on:
dev-bff: { condition: service_started }
volumes:
- ./ci/dev/Caddyfile.dev:/etc/caddy/Caddyfile:ro
ports:
- "3000:80"
networks:
- sendico-dev
environment:
BFF_URL: http://dev-bff:8080