init2121212

This commit is contained in:
ZOMBIIIIIII
2026-05-28 14:01:10 +03:00
parent e86ff7c063
commit a636bd573a
3 changed files with 271 additions and 9 deletions

74
.env.example Normal file
View 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)

View File

@@ -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
View 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