From 179e05b1e8c911ff56ad6ab88f0fb2b4ebcd5396 Mon Sep 17 00:00:00 2001 From: ZOMBIIIIIII <120676065+Metaton241@users.noreply.github.com> Date: Thu, 28 May 2026 14:15:37 +0300 Subject: [PATCH] init222228 --- .env | 34 --------- .gitignore | 11 +++ README.md | 38 ++-------- docker-compose.yml | 168 +++++++++++---------------------------------- 4 files changed, 58 insertions(+), 193 deletions(-) delete mode 100644 .env create mode 100644 .gitignore diff --git a/.env b/.env deleted file mode 100644 index f649749..0000000 --- a/.env +++ /dev/null @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d3e999 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.env +.env.local +node_modules/ +**/node_modules/ +dist/ +**/dist/ +*.log +logs/ +.DS_Store +.vscode/ +.idea/ diff --git a/README.md b/README.md index 81bcacd..b939f17 100644 --- a/README.md +++ b/README.md @@ -28,55 +28,29 @@ vault kv put dev-secrets/jwt/kids/ \ ⚠️ **Master-key менять нельзя** — все existing `encrypted_mnemonic` станут нерасшифровываемыми. API на старте делает self-test: пытается декриптить любой существующий mnemonic и фейлится если ключ не подошёл. -## Deploy (через docker compose) +## Deploy ```bash -# 1. Залить bundle на сервер +# Залить bundle на сервер scp -P 2222 -r deployserver/ server@:~/cryptowallet/ -# 2. На сервере: заполнить .env +# На сервере: убедись что .env заполнен (VAULT_*, JWT_*, CORS_ORIGINS, REDIS_PASSWORD, ...) ssh server@ -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@:~/cryptowallet/deployserver/ -ssh server@ -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build api' - -# Полный rebuild -ssh server@ -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build --force-recreate' +ssh server@ -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build' ``` ## Endpoints diff --git a/docker-compose.yml b/docker-compose.yml index e0fbc9f..d7d92ea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 - 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 + image: eqalpha/keydb + container_name: cryptowallet-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