init222228
This commit is contained in:
34
.env
34
.env
@@ -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
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.env
|
||||
.env.local
|
||||
node_modules/
|
||||
**/node_modules/
|
||||
dist/
|
||||
**/dist/
|
||||
*.log
|
||||
logs/
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.idea/
|
||||
38
README.md
38
README.md
@@ -28,55 +28,29 @@ vault kv put dev-secrets/jwt/kids/<kid-from-bitok> \
|
||||
|
||||
⚠️ **Master-key менять нельзя** — все existing `encrypted_mnemonic` станут нерасшифровываемыми. API на старте делает self-test: пытается декриптить любой существующий mnemonic и фейлится если ключ не подошёл.
|
||||
|
||||
## Deploy (через docker compose)
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
# 1. Залить bundle на сервер
|
||||
# Залить bundle на сервер
|
||||
scp -P 2222 -r deployserver/ server@<host>:~/cryptowallet/
|
||||
|
||||
# 2. На сервере: заполнить .env
|
||||
# На сервере: убедись что .env заполнен (VAULT_*, JWT_*, CORS_ORIGINS, REDIS_PASSWORD, ...)
|
||||
ssh server@<host> -p 2222
|
||||
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
|
||||
|
||||
# 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`, `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`
|
||||
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).
|
||||
|
||||
## Update / Rebuild
|
||||
|
||||
```bash
|
||||
# Только app (postgres/keydb остаются — данные не теряются)
|
||||
# Залить новый src + rebuild api (keydb данные не теряются)
|
||||
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'
|
||||
ssh server@<host> -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build'
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
@@ -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:
|
||||
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
|
||||
image: eqalpha/keydb
|
||||
container_name: cryptowallet-keydb
|
||||
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"
|
||||
volumes:
|
||||
- keydb_data:/data
|
||||
command:
|
||||
- keydb-server
|
||||
- --requirepass
|
||||
- "${REDIS_PASSWORD:?REDIS_PASSWORD required}"
|
||||
- ${REDIS_PASSWORD}
|
||||
- --dir
|
||||
- /data
|
||||
- --appendonly
|
||||
@@ -133,29 +26,50 @@ services:
|
||||
- --save
|
||||
- "60"
|
||||
- "10000"
|
||||
- --maxmemory
|
||||
- "256mb"
|
||||
- --maxmemory-policy
|
||||
- "allkeys-lru"
|
||||
healthcheck:
|
||||
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
|
||||
timeout: 3s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 320M
|
||||
start_period: 15s
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-size: "20m"
|
||||
max-file: "5"
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
keydb_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
|
||||
Reference in New Issue
Block a user