Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
18 changes: 18 additions & 0 deletions Dockerfile.cron
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Dockerfile for cron jobs
FROM alpine:3.19

# 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

# Run supercronic
CMD ["supercronic", "/etc/crontabs/crontab"]
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 12 additions & 0 deletions crontab
Original file line number Diff line number Diff line change
@@ -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)
* * * * * 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 * * * * sh -c 'TOKEN=$(generate-jwt) && curl -X GET "$APP_URL/api/cron/3-minutes-run-interval?token=$TOKEN" -s -o /dev/null'
81 changes: 81 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
services:
# PostgreSQL Database
db:
image: postgres:16-alpine
container_name: nof1-db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: nof1
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: nof1-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/nof1

# 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: nof1-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
38 changes: 38 additions & 0 deletions scripts/generate-jwt.js
Original file line number Diff line number Diff line change
@@ -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}`);