init2121212
This commit is contained in:
74
.env.example
Normal file
74
.env.example
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Production .env template для CryptoWallet API.
|
||||||
|
# Скопируй: cp .env.example .env && chmod 600 .env && nano .env
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# ─── HashiCorp Vault (для master-key, JWT public keys, CSRF secret) ──
|
||||||
|
VAULT_ADDR=https://vault.your-domain.com
|
||||||
|
VAULT_ROLE_ID=00000000-0000-0000-0000-000000000000
|
||||||
|
VAULT_SECRET_ID=00000000-0000-0000-0000-000000000000
|
||||||
|
VAULT_MOUNT_POINT=dev-secrets
|
||||||
|
VAULT_SECRET_PATH=crypto/master
|
||||||
|
VAULT_JWT_KID_PATH=jwt/kid
|
||||||
|
VAULT_JWT_KIDS_PREFIX=jwt/kids/
|
||||||
|
VAULT_CSRF_PATH=csrf
|
||||||
|
VAULT_CRYPTO_KEY_PATH=crypto/master
|
||||||
|
|
||||||
|
# ─── JWT (приём от bitok external issuer) ────────────────────────────
|
||||||
|
JWT_ALGORITHM=RS256
|
||||||
|
JWT_ISSUER=bitok
|
||||||
|
JWT_AUDIENCE=cryptowallet-api
|
||||||
|
|
||||||
|
# ─── API runtime ─────────────────────────────────────────────────────
|
||||||
|
API_PORT=3001
|
||||||
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
# CORS — comma-separated list разрешённых origins (фронтенд hosts)
|
||||||
|
CORS_ORIGINS=https://app.your-domain.com,https://admin.your-domain.com
|
||||||
|
CORS_ALLOW_CREDENTIALS=true
|
||||||
|
|
||||||
|
# ─── Postgres (для docker-compose) ───────────────────────────────────
|
||||||
|
# Эти переменные используются compose'ом для создания/connect к Postgres контейнеру.
|
||||||
|
# Если оператор использует external managed Postgres — игнорь POSTGRES_* и впиши
|
||||||
|
# connection string в DATABASE_URL ниже.
|
||||||
|
POSTGRES_USER=cryptowallet
|
||||||
|
POSTGRES_PASSWORD=__GENERATE_STRONG_PASSWORD__
|
||||||
|
POSTGRES_DB=cryptowallet
|
||||||
|
|
||||||
|
# Если используешь managed/external Postgres — раскомментируй и заполни:
|
||||||
|
# DATABASE_URL=postgres://user:pass@host:5432/dbname
|
||||||
|
|
||||||
|
# ─── KeyDB / Redis (idempotency cache + NearIntents asset map cache) ─
|
||||||
|
# REDIS_HOST=keydb имя service в compose — НЕ меняй если работаешь через compose
|
||||||
|
REDIS_HOST=keydb
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=__GENERATE_STRONG_PASSWORD__
|
||||||
|
REDIS_DB=0
|
||||||
|
|
||||||
|
# ─── Внешние API ─────────────────────────────────────────────────────
|
||||||
|
# CoinGecko — для prices/dynamics (без ключа работает с rate limits)
|
||||||
|
COINGECKO_API_KEY=
|
||||||
|
|
||||||
|
# Jupiter — для SOL custodial swap (без ключа = lower rate limits)
|
||||||
|
JUPITER_API_KEY=
|
||||||
|
|
||||||
|
# Jupiter referral — если хочешь чтобы SOL swap fees шли через Jupiter feeAccount.
|
||||||
|
# Сейчас у нас атомарный 0.7% fee atomic в swap-orchestrator (на APP_FEE_WALLET_SOL),
|
||||||
|
# referral отдельный механизм. Можно оставить пустым.
|
||||||
|
JUPITER_REFERRAL_ACCOUNT=
|
||||||
|
|
||||||
|
# TronGrid — для TRX queries (без ключа 3 req/sec, с ключом 100/sec)
|
||||||
|
# Получить: https://www.trongrid.io/dashboard
|
||||||
|
TRON_API_KEY=
|
||||||
|
|
||||||
|
# Outbound HTTP proxy (опц.) — если хотите чтобы Jupiter/Relay/NearIntents calls
|
||||||
|
# шли через proxy (rotation IP для rate limits). Формат: http://user:pass@host:port
|
||||||
|
OUTBOUND_PROXY_URL=
|
||||||
|
|
||||||
|
# ─── App fee wallets ─────────────────────────────────────────────────
|
||||||
|
# Эти адреса HARDCODED в backend (apps/api/src/lib/app-fee.ts), НЕ настраиваются
|
||||||
|
# через env. Если нужно изменить — code review + rebuild.
|
||||||
|
# EVM (ETH+BSC): 0xeb9fbf0d137ef5ea7b9959044c2ed44ec1206c68
|
||||||
|
# SOL: DQkQegoX698XkcXZ6VX9P1qUpbV64Sgjz1BCPFgfWpjD
|
||||||
|
# TRX: TRwpFjnfMBe4aDJbHYEqeUVCG1auF8wFXP
|
||||||
|
# BTC: — (not collectable)
|
||||||
45
README.md
45
README.md
@@ -28,28 +28,55 @@ vault kv put dev-secrets/jwt/kids/<kid-from-bitok> \
|
|||||||
|
|
||||||
⚠️ **Master-key менять нельзя** — все existing `encrypted_mnemonic` станут нерасшифровываемыми. API на старте делает self-test: пытается декриптить любой существующий mnemonic и фейлится если ключ не подошёл.
|
⚠️ **Master-key менять нельзя** — все existing `encrypted_mnemonic` станут нерасшифровываемыми. API на старте делает self-test: пытается декриптить любой существующий mnemonic и фейлится если ключ не подошёл.
|
||||||
|
|
||||||
## Deploy
|
## Deploy (через docker compose)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Залить bundle на сервер
|
# 1. Залить bundle на сервер
|
||||||
scp -P 2222 -r deployserver/ server@<host>:~/cryptowallet/
|
scp -P 2222 -r deployserver/ server@<host>:~/cryptowallet/
|
||||||
|
|
||||||
# На сервере: заполнить .env, поднять
|
# 2. На сервере: заполнить .env
|
||||||
ssh server@<host> -p 2222
|
ssh server@<host> -p 2222
|
||||||
cd ~/cryptowallet
|
cd ~/cryptowallet/deployserver
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
chmod 600 .env
|
chmod 600 .env
|
||||||
nano .env # заполни VAULT_*, JWT_*, CORS_ORIGINS
|
nano .env # заполни VAULT_*, JWT_*, REDIS_PASSWORD, POSTGRES_PASSWORD, CORS_ORIGINS
|
||||||
./start.sh
|
|
||||||
|
# 3. Поднять stack (api + postgres + keydb)
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# 4. Проверить
|
||||||
|
docker compose ps
|
||||||
|
docker compose logs -f api
|
||||||
|
curl http://localhost:3001/api/health
|
||||||
```
|
```
|
||||||
|
|
||||||
В `.env` **обязательны**: `VAULT_ADDR`, `VAULT_ROLE_ID`, `VAULT_SECRET_ID`, `JWT_ISSUER=bitok`, `JWT_AUDIENCE`, `CORS_ORIGINS`.
|
В `.env` **обязательны**: `VAULT_ADDR`, `VAULT_ROLE_ID`, `VAULT_SECRET_ID`, `JWT_ISSUER=bitok`, `JWT_AUDIENCE`, `CORS_ORIGINS`, `POSTGRES_PASSWORD`, `REDIS_PASSWORD`.
|
||||||
|
|
||||||
|
**Что в compose:**
|
||||||
|
- `api` — наш Node API из multi-stage Dockerfile (read-only fs, uid 1001, port 127.0.0.1:3001)
|
||||||
|
- `postgres` — хранилище (internal only, без exposed ports)
|
||||||
|
- `keydb` — Redis-compatible для idempotency + asset map cache (internal only)
|
||||||
|
|
||||||
|
**Чего НЕТ в compose** (production не использует — оператор настраивает отдельно):
|
||||||
|
- HashiCorp Vault — production использует HA cluster (URL в `VAULT_ADDR`)
|
||||||
|
- JWT issuer — production принимает JWT от bitok external service
|
||||||
|
- Web UI — frontend деплоится отдельно (Vercel / nginx / CDN)
|
||||||
|
- Nginx reverse proxy + TLS — оператор сам ставит перед `127.0.0.1:3001`
|
||||||
|
|
||||||
|
**Если используешь managed Postgres** (e.g. AWS RDS):
|
||||||
|
- Раскомментируй `DATABASE_URL` в `.env`
|
||||||
|
- Закомментируй `postgres` service в `docker-compose.yml` + убери `depends_on.postgres` из `api`
|
||||||
|
|
||||||
## Update / Rebuild
|
## Update / Rebuild
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scp -P 2222 -r deployserver/apps server@<host>:~/cryptowallet/
|
# Только app (postgres/keydb остаются — данные не теряются)
|
||||||
ssh server@<host> -p 2222 'cd cryptowallet && docker compose up -d --build'
|
scp -P 2222 -r deployserver/apps deployserver/Dockerfile deployserver/docker-compose.yml \
|
||||||
|
server@<host>:~/cryptowallet/deployserver/
|
||||||
|
ssh server@<host> -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build api'
|
||||||
|
|
||||||
|
# Полный rebuild
|
||||||
|
ssh server@<host> -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build --force-recreate'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|||||||
161
docker-compose.yml
Normal file
161
docker-compose.yml
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Production docker-compose для CryptoWallet API.
|
||||||
|
#
|
||||||
|
# Что в этом stack:
|
||||||
|
# - api — наш Node.js API (multi-stage build из ./Dockerfile)
|
||||||
|
# - postgres — хранилище encrypted_mnemonic + audit_log
|
||||||
|
# - keydb — Redis-compatible для idempotency cache + asset map cache
|
||||||
|
#
|
||||||
|
# Что НЕ включено (production не использует — оператор настраивает отдельно):
|
||||||
|
# - Vault — production использует HashiCorp Vault HA cluster (VAULT_ADDR в .env)
|
||||||
|
# - JWT signer— production принимает JWT от bitok (внешний сервис, JWT_ISSUER в .env)
|
||||||
|
# - web-ui — фронтенд деплоится отдельно (Vercel / nginx CDN / etc.)
|
||||||
|
# - vault-init — production Vault уже инициализирован оператором (см. README pre-deploy)
|
||||||
|
#
|
||||||
|
# Security hardening:
|
||||||
|
# - api запускается под uid 1001, read-only fs, cap_drop ALL, no-new-privileges
|
||||||
|
# - api порт 3001 биндится на 127.0.0.1 (наружу через nginx + TLS, оператор)
|
||||||
|
# - postgres + keydb без exposed ports (только internal docker network)
|
||||||
|
# - .env с secrets — 600 permission, не в репо
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# cp .env.example .env # заполнить все VAULT_*, JWT_*, REDIS_PASSWORD, CORS_ORIGINS, etc.
|
||||||
|
# chmod 600 .env
|
||||||
|
# docker compose up -d --build
|
||||||
|
# docker compose logs -f api
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
# Context = parent dir (мы внутри deployserver/, апи берёт apps/api/ из родителя)
|
||||||
|
context: ..
|
||||||
|
dockerfile: deployserver/Dockerfile
|
||||||
|
image: cryptowallet-api:latest
|
||||||
|
container_name: cw-api
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
keydb:
|
||||||
|
condition: service_healthy
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
# Override DB/Redis host для docker network (если в .env стоят prod hosts —
|
||||||
|
# внутри compose их replace на internal service names)
|
||||||
|
POSTGRES_HOST: postgres
|
||||||
|
POSTGRES_PORT: "5432"
|
||||||
|
REDIS_HOST: keydb
|
||||||
|
REDIS_PORT: "6379"
|
||||||
|
ports:
|
||||||
|
# Loopback only — наружу через nginx reverse proxy + TLS
|
||||||
|
- "127.0.0.1:3001:3001"
|
||||||
|
read_only: true
|
||||||
|
tmpfs:
|
||||||
|
- /tmp
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512M
|
||||||
|
cpus: "1.0"
|
||||||
|
pids: 200
|
||||||
|
reservations:
|
||||||
|
memory: 256M
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 15s
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "5"
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: cw-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER required}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-cryptowallet}
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
# Bind-mount schema.sql если оператор хочет авто-init на свежей БД.
|
||||||
|
# На existing — pg ignores 01-schema.sql (initdb запускается только если /var/lib/postgresql/data пуст).
|
||||||
|
# См. README — лучше прогонять schema руками: psql -f cryptowallet-schema.sql
|
||||||
|
# - ./cryptowallet-schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
|
||||||
|
# Internal only — никаких ports на host
|
||||||
|
expose:
|
||||||
|
- "5432"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB:-cryptowallet}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "5"
|
||||||
|
|
||||||
|
keydb:
|
||||||
|
image: eqalpha/keydb:alpine
|
||||||
|
container_name: cw-keydb
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- keydb_data:/data
|
||||||
|
expose:
|
||||||
|
- "6379"
|
||||||
|
command:
|
||||||
|
- keydb-server
|
||||||
|
- --requirepass
|
||||||
|
- "${REDIS_PASSWORD:?REDIS_PASSWORD required}"
|
||||||
|
- --dir
|
||||||
|
- /data
|
||||||
|
- --appendonly
|
||||||
|
- "yes"
|
||||||
|
- --appendfsync
|
||||||
|
- everysec
|
||||||
|
- --save
|
||||||
|
- "900"
|
||||||
|
- "1"
|
||||||
|
- --save
|
||||||
|
- "300"
|
||||||
|
- "10"
|
||||||
|
- --save
|
||||||
|
- "60"
|
||||||
|
- "10000"
|
||||||
|
- --maxmemory
|
||||||
|
- "256mb"
|
||||||
|
- --maxmemory-policy
|
||||||
|
- "allkeys-lru"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 320M
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "5"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
keydb_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
driver: bridge
|
||||||
Reference in New Issue
Block a user