From b1c72a06d925856db6846d207f33a7e2f9dd6df1 Mon Sep 17 00:00:00 2001 From: Daniel Naranjo Date: Fri, 24 Oct 2025 10:54:58 -0500 Subject: [PATCH 1/4] feat: add optional Docker to run project --- .dockerignore | 21 ++++++++++++ Dockerfile | 32 ++++++++++++++++++ Dockerfile.cron | 18 +++++++++++ crontab | 12 +++++++ docker-compose.yml | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 164 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 Dockerfile.cron create mode 100644 crontab create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4c730af --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +node_modules +.next +.env +.env.local +.env.*.local +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.DS_Store +*.pem +.vercel +.git +.gitignore +README.md +.vscode +.idea +coverage +*.log +dist +build +.cache diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a0b62b8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Dockerfile for the Next.js application in development mode +FROM oven/bun:1 + +# Install OpenSSL for Prisma +RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy package files first to leverage Docker cache +COPY package.json bun.lockb* ./ + +# Install dependencies +RUN bun install + +# Copy Prisma schema to generate the client +COPY prisma ./prisma + +# Generate Prisma Client +RUN bunx prisma generate + +# Copy the rest of the application +COPY . . + +ENV NEXT_TELEMETRY_DISABLED=1 + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Run in development mode +CMD ["bun", "dev"] diff --git a/Dockerfile.cron b/Dockerfile.cron new file mode 100644 index 0000000..2f22665 --- /dev/null +++ b/Dockerfile.cron @@ -0,0 +1,18 @@ +# Dockerfile for cron jobs +FROM alpine:3.19 + +# Install curl, ca-certificates and supercronic +RUN apk add --no-cache curl ca-certificates tzdata && \ + curl -fsSLO https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 && \ + chmod +x supercronic-linux-amd64 && \ + mv supercronic-linux-amd64 /usr/local/bin/supercronic + +# Copy the crontab file +COPY crontab /etc/crontabs/crontab + +# Environment variables for the app URL and cron secret +ENV APP_URL=http://app:3000 +ENV CRON_SECRET_KEY="" + +# Run supercronic +CMD ["supercronic", "/etc/crontabs/crontab"] diff --git a/crontab b/crontab new file mode 100644 index 0000000..ea20f78 --- /dev/null +++ b/crontab @@ -0,0 +1,12 @@ +# Crontab to run automated jobs +# Note: standard crontab has minute granularity. The following entries use 'sleep' to approximate three executions per minute (every ~20 seconds). +# +# Format: minute hour day month weekday command + +# Metrics every ~20 seconds (three executions per minute) +* * * * * curl -X POST "$APP_URL/api/cron/20-seconds-metrics-interval" -H "Authorization: Bearer $CRON_SECRET_KEY" -s -o /dev/null +* * * * * sleep 20 && curl -X POST "$APP_URL/api/cron/20-seconds-metrics-interval" -H "Authorization: Bearer $CRON_SECRET_KEY" -s -o /dev/null +* * * * * sleep 40 && curl -X POST "$APP_URL/api/cron/20-seconds-metrics-interval" -H "Authorization: Bearer $CRON_SECRET_KEY" -s -o /dev/null + +# Trading every 3 minutes +*/3 * * * * curl -X POST "$APP_URL/api/cron/3-minutes-run-interval" -H "Authorization: Bearer $CRON_SECRET_KEY" -s -o /dev/null diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..652cdeb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,81 @@ +services: + # PostgreSQL Database + db: + image: postgres:16-alpine + container_name: ares-trader-db + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} + POSTGRES_DB: ares_trader + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + # Next.js Application + app: + build: + context: . + dockerfile: Dockerfile + container_name: ares-trader-app + restart: unless-stopped + ports: + - "3000:3000" + environment: + # Application + NEXT_PUBLIC_URL: ${NEXT_PUBLIC_URL:-http://localhost:3000} + + # Database + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@db:5432/ares_trader + + # AI Models + DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} + OPENROUTER_API_KEY: ${OPENROUTER_API_KEY} + + # Market Research + EXA_API_KEY: ${EXA_API_KEY} + + # Trading (Binance) + BINANCE_API_KEY: ${BINANCE_API_KEY} + BINANCE_API_SECRET: ${BINANCE_API_SECRET} + + # Trading Configuration + START_MONEY: ${START_MONEY:-10000} + + # Cron Job Authentication + CRON_SECRET_KEY: ${CRON_SECRET_KEY} + + # Node Environment + NODE_ENV: development + volumes: + - .:/app + - /app/node_modules + - /app/.next + depends_on: + db: + condition: service_healthy + command: sh -c "bunx prisma db push && bun dev" + + # Cron Jobs + cron: + build: + context: . + dockerfile: Dockerfile.cron + container_name: ares-trader-cron + restart: unless-stopped + environment: + APP_URL: http://app:3000 + CRON_SECRET_KEY: ${CRON_SECRET_KEY} + TZ: ${TZ:-UTC} + depends_on: + - app + +volumes: + postgres_data: + driver: local From fab4c51d3c0f6046bd17c59551330c5c5edc73a9 Mon Sep 17 00:00:00 2001 From: Daniel Naranjo Date: Fri, 24 Oct 2025 10:58:06 -0500 Subject: [PATCH 2/4] docs: update README with Docker usage --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index c8840a2..a9c0565 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,29 @@ This open-source implementation currently focuses on running the **DeepSeek** tr bun dev ``` +Optional: Run with Docker + +If you prefer to run the application with Docker and Docker Compose, you can use the included `docker-compose.yml` to start the app, PostgreSQL, and the cron service. Make sure you have a `.env` file with the required variables (see step 3). + +Example (recommended): + +```bash +# Build and start services in the background +docker compose up --build -d + +# Initialize the database (run once or whenever schema changes) +docker compose exec app bunx prisma db push + +# Follow app logs +docker compose logs -f app +``` + +To start only the cron worker (if you want cron jobs running separately): + +```bash +docker compose up -d cron +``` + 6. **Set up cron jobs** (for automated trading) You'll need to set up external cron jobs or use a service like [Vercel Cron](https://vercel.com/docs/cron-jobs) to call these endpoints: From 03057bb42c5356f036d64ba15c37d6584f9b3340 Mon Sep 17 00:00:00 2001 From: Daniel Naranjo Date: Fri, 24 Oct 2025 11:02:51 -0500 Subject: [PATCH 3/4] use similar name "nof1" --- docker-compose.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 652cdeb..e46775d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,12 +2,12 @@ services: # PostgreSQL Database db: image: postgres:16-alpine - container_name: ares-trader-db + container_name: nof1-db restart: unless-stopped environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} - POSTGRES_DB: ares_trader + POSTGRES_DB: nof1 volumes: - postgres_data:/var/lib/postgresql/data ports: @@ -23,7 +23,7 @@ services: build: context: . dockerfile: Dockerfile - container_name: ares-trader-app + container_name: nof1-app restart: unless-stopped ports: - "3000:3000" @@ -32,7 +32,7 @@ services: NEXT_PUBLIC_URL: ${NEXT_PUBLIC_URL:-http://localhost:3000} # Database - DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@db:5432/ares_trader + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@db:5432/nof1 # AI Models DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} @@ -67,7 +67,7 @@ services: build: context: . dockerfile: Dockerfile.cron - container_name: ares-trader-cron + container_name: nof1-cron restart: unless-stopped environment: APP_URL: http://app:3000 From c6e471b6c6927554da2231560089fdfc50188913 Mon Sep 17 00:00:00 2001 From: Daniel Naranjo Date: Fri, 24 Oct 2025 16:27:18 -0500 Subject: [PATCH 4/4] Fix jwt token generation for cron --- Dockerfile.cron | 12 ++++++------ crontab | 8 ++++---- scripts/generate-jwt.js | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 scripts/generate-jwt.js diff --git a/Dockerfile.cron b/Dockerfile.cron index 2f22665..530b662 100644 --- a/Dockerfile.cron +++ b/Dockerfile.cron @@ -1,18 +1,18 @@ # Dockerfile for cron jobs FROM alpine:3.19 -# Install curl, ca-certificates and supercronic -RUN apk add --no-cache curl ca-certificates tzdata && \ +# Install curl, ca-certificates, tzdata, nodejs and supercronic +RUN apk add --no-cache curl ca-certificates tzdata nodejs && \ curl -fsSLO https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 && \ chmod +x supercronic-linux-amd64 && \ mv supercronic-linux-amd64 /usr/local/bin/supercronic +# Add JWT generator script +COPY scripts/generate-jwt.js /usr/local/bin/generate-jwt +RUN chmod +x /usr/local/bin/generate-jwt + # Copy the crontab file COPY crontab /etc/crontabs/crontab -# Environment variables for the app URL and cron secret -ENV APP_URL=http://app:3000 -ENV CRON_SECRET_KEY="" - # Run supercronic CMD ["supercronic", "/etc/crontabs/crontab"] diff --git a/crontab b/crontab index ea20f78..7879a49 100644 --- a/crontab +++ b/crontab @@ -4,9 +4,9 @@ # Format: minute hour day month weekday command # Metrics every ~20 seconds (three executions per minute) -* * * * * curl -X POST "$APP_URL/api/cron/20-seconds-metrics-interval" -H "Authorization: Bearer $CRON_SECRET_KEY" -s -o /dev/null -* * * * * sleep 20 && curl -X POST "$APP_URL/api/cron/20-seconds-metrics-interval" -H "Authorization: Bearer $CRON_SECRET_KEY" -s -o /dev/null -* * * * * sleep 40 && curl -X POST "$APP_URL/api/cron/20-seconds-metrics-interval" -H "Authorization: Bearer $CRON_SECRET_KEY" -s -o /dev/null +* * * * * sh -c 'TOKEN=$(generate-jwt) && curl -X GET "$APP_URL/api/cron/20-seconds-metrics-interval?token=$TOKEN" -s -o /dev/null' +* * * * * sh -c 'sleep 20 && TOKEN=$(generate-jwt) && curl -X GET "$APP_URL/api/cron/20-seconds-metrics-interval?token=$TOKEN" -s -o /dev/null' +* * * * * sh -c 'sleep 40 && TOKEN=$(generate-jwt) && curl -X GET "$APP_URL/api/cron/20-seconds-metrics-interval?token=$TOKEN" -s -o /dev/null' # Trading every 3 minutes -*/3 * * * * curl -X POST "$APP_URL/api/cron/3-minutes-run-interval" -H "Authorization: Bearer $CRON_SECRET_KEY" -s -o /dev/null +*/3 * * * * sh -c 'TOKEN=$(generate-jwt) && curl -X GET "$APP_URL/api/cron/3-minutes-run-interval?token=$TOKEN" -s -o /dev/null' diff --git a/scripts/generate-jwt.js b/scripts/generate-jwt.js new file mode 100644 index 0000000..b21c55a --- /dev/null +++ b/scripts/generate-jwt.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node +// Minimal JWT (HS256) generator without external deps +// Reads secret from env CRON_SECRET_KEY and prints a token to stdout + +const crypto = require('crypto'); + +function base64url(input) { + return Buffer.from(input) + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); +} + +const secret = process.env.CRON_SECRET_KEY || ''; +if (!secret) { + console.error('CRON_SECRET_KEY is not set'); + process.exit(1); +} + +const header = { alg: 'HS256', typ: 'JWT' }; +const payload = { + sub: 'cron-token', + iat: Math.floor(Date.now() / 1000), +}; + +const headerB64 = base64url(JSON.stringify(header)); +const payloadB64 = base64url(JSON.stringify(payload)); +const data = `${headerB64}.${payloadB64}`; +const signature = crypto + .createHmac('sha256', secret) + .update(data) + .digest('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + +process.stdout.write(`${data}.${signature}`);