init222228

This commit is contained in:
ZOMBIIIIIII
2026-05-28 14:15:37 +03:00
parent a636bd573a
commit 179e05b1e8
4 changed files with 58 additions and 193 deletions

34
.env
View File

@@ -1,34 +0,0 @@
# Local .env for docker compose ${REDIS_PASSWORD} interpolation.
# DO NOT COMMIT (already in .gitignore). На прод-боксе оператор создаёт свой через `cp .env.example .env`.
VAULT_ADDR=
VAULT_ROLE_ID=
VAULT_SECRET_ID=
VAULT_MOUNT_POINT=dev-secrets
VAULT_SECRET_PATH=database
VAULT_JWT_KID_PATH=jwt/kid
VAULT_JWT_KIDS_PREFIX=jwt/kids
VAULT_CSRF_PATH=csrf
VAULT_CRYPTO_KEY_PATH=crypto/master
JWT_ALGORITHM=RS256
JWT_ISSUER=bitok
JWT_AUDIENCE=elcsa
API_PORT=3001
LOG_LEVEL=INFO
CORS_ORIGINS=*
CORS_ALLOW_CREDENTIALS=false
REDIS_HOST=keydb
REDIS_PORT=6379
REDIS_PASSWORD=0O7klMYUvwwR19UORSzEtsRn9kUPnDyfkJ9GDH2yMERYV0vRCU
REDIS_DB=0
# Price oracle (CoinGecko free tier — без ключа работает).
COINGECKO_API_KEY=
# Outbound proxy для swap + bridge endpoints.
# Если задан — Jupiter/Relay/RPC calls идут через proxy. Read-only direct.
OUTBOUND_PROXY_URL=http://37.220.84.34:3128

11
.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
.env
.env.local
node_modules/
**/node_modules/
dist/
**/dist/
*.log
logs/
.DS_Store
.vscode/
.idea/

View File

@@ -28,55 +28,29 @@ 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 (через docker compose) ## Deploy
```bash ```bash
# 1. Залить bundle на сервер # Залить bundle на сервер
scp -P 2222 -r deployserver/ server@<host>:~/cryptowallet/ scp -P 2222 -r deployserver/ server@<host>:~/cryptowallet/
# 2. На сервере: заполнить .env # На сервере: убедись что .env заполнен (VAULT_*, JWT_*, CORS_ORIGINS, REDIS_PASSWORD, ...)
ssh server@<host> -p 2222 ssh server@<host> -p 2222
cd ~/cryptowallet/deployserver cd ~/cryptowallet/deployserver
cp .env.example .env
chmod 600 .env
nano .env # заполни VAULT_*, JWT_*, REDIS_PASSWORD, POSTGRES_PASSWORD, CORS_ORIGINS
# 3. Поднять stack (api + postgres + keydb)
docker compose up -d --build docker compose up -d --build
# 4. Проверить
docker compose ps
docker compose logs -f api docker compose logs -f api
curl http://localhost:3001/api/health curl http://localhost:3001/api/health
``` ```
В `.env` **обязательны**: `VAULT_ADDR`, `VAULT_ROLE_ID`, `VAULT_SECRET_ID`, `JWT_ISSUER=bitok`, `JWT_AUDIENCE`, `CORS_ORIGINS`, `POSTGRES_PASSWORD`, `REDIS_PASSWORD`. API **не делает migrations / DROP / ALTER** при старте — только INSERT/UPDATE/SELECT. Schema (если нужны новые колонки/таблицы для нового функционала) обновляется только руками: `psql -f cryptowallet-schema.sql` (script append-only — `CREATE TABLE IF NOT EXISTS` / `ALTER TABLE ADD COLUMN IF NOT EXISTS`, никаких DROP).
**Что в 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
# Только app (postgres/keydb остаются — данные не теряются) # Залить новый src + rebuild api (keydb данные не теряются)
scp -P 2222 -r deployserver/apps deployserver/Dockerfile deployserver/docker-compose.yml \ scp -P 2222 -r deployserver/apps deployserver/Dockerfile deployserver/docker-compose.yml \
server@<host>:~/cryptowallet/deployserver/ server@<host>:~/cryptowallet/deployserver/
ssh server@<host> -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build api' ssh server@<host> -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build'
# Полный rebuild
ssh server@<host> -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build --force-recreate'
``` ```
## Endpoints ## Endpoints

View File

@@ -1,123 +1,16 @@
# ─────────────────────────────────────────────────────────────────────
# 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: 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: keydb:
condition: service_healthy image: eqalpha/keydb
env_file: container_name: cryptowallet-keydb
- .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 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: expose:
- "6379" - "6379"
volumes:
- keydb_data:/data
command: command:
- keydb-server - keydb-server
- --requirepass - --requirepass
- "${REDIS_PASSWORD:?REDIS_PASSWORD required}" - ${REDIS_PASSWORD}
- --dir - --dir
- /data - /data
- --appendonly - --appendonly
@@ -133,29 +26,50 @@ services:
- --save - --save
- "60" - "60"
- "10000" - "10000"
- --maxmemory
- "256mb"
- --maxmemory-policy
- "allkeys-lru"
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 5s
timeout: 2s
retries: 20
api:
build:
context: .
dockerfile: Dockerfile
container_name: cryptowallet-api
restart: unless-stopped
depends_on:
keydb:
condition: service_healthy
# Production: port открыт на all interfaces. TLS/WAF обязательно на reverse proxy.
ports:
- "3001:3001"
env_file:
- .env
environment:
API_PORT: "3001"
# Container hardening — post-RCE blast radius minimization.
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
pids_limit: 256
mem_limit: 512m
cpus: "1.0"
healthcheck:
test: ["CMD", "wget", "-qO-", "--tries=1", "--timeout=3", "http://localhost:3001/api/health"]
interval: 10s interval: 10s
timeout: 3s timeout: 5s
retries: 5 retries: 5
deploy: start_period: 15s
resources:
limits:
memory: 320M
logging: logging:
driver: json-file driver: json-file
options: options:
max-size: "10m" max-size: "20m"
max-file: "5" max-file: "5"
volumes: volumes:
pgdata:
keydb_data: keydb_data:
networks:
default:
driver: bridge