diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e075137 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.vscode/ +.gitignore +.git +README.md +config.example.json + diff --git a/.gitignore b/.gitignore index 8c9383a..03985d6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ config.json blocklist.json proxy.db -proxy.db-journal \ No newline at end of file +proxy.db-journal +redis/ +docker-compose.yml +data/ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..100fb27 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +ARG NODE_TAG=node:21-bullseye + +FROM --platform=$BUILDPLATFORM $NODE_TAG AS base + +ADD . /app +WORKDIR /app + +# Enable yarn and install system packages +RUN corepack enable && apt install -y libssl1.1 + +FROM base AS builder + +# Resolve packages +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install + +# Build sources +RUN yarn docker:build + +FROM base AS prod + +# Resolve packages, without devDependencies +RUN --mount=type=cache,target=/root.yarn YARN_CACHE_FOLDER=/root.yarn yarn install --production + +COPY --from=builder /app/dist /app/dist +ENV VERSION "docker" + +CMD [ "yarn", "docker:start" ] + diff --git a/docker-compose_example.yml b/docker-compose_example.yml new file mode 100644 index 0000000..3721861 --- /dev/null +++ b/docker-compose_example.yml @@ -0,0 +1,54 @@ +version: '3.8' +networks: + external: + internal: + internal: true + +services: + # You can configure reverse proxy to establish https connection + # Consider, use caddy + # caddy: + # networks: + # - external + # - internal + # ports: + # - 80:80 + # ... + + webhook-proxy: + links: + - webhook-proxy-redis:redis + depends_on: + webhook-proxy-redis: + condition: service_healthy + build: . + restart: unless-stopped + networks: + - external + - internal + environment: + PORT: 80 + TRUST_PROXY: false + AUTO_BLOCK: false + QUEUE_ENABLED: false + QUEUE_RABBITMQ: "amqp://localhost" + QUEUE_QUEUE: "webhook-proxy" + REDIS: "redis://redis" + ABUSE_THRESHOLD: 12 + ports: + - 80:80 + volumes: + - ./data:/data + + webhook-proxy-redis: + image: redis:7-alpine + restart: unless-stopped + networks: + - internal + volumes: + - ./redis:/data + healthcheck: + test: "redis-cli ping" + interval: 10s + retries: 20 + diff --git a/package.json b/package.json index ce2c142..e5e210d 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,10 @@ { "scripts": { "update": "git pull && yarn && yarn build && pm2 reload webhook-proxy webhook-proxy-processor || true", - "build": "prisma migrate deploy && prisma generate && rimraf dist && tsc", - "start": "yarn build && node dist" + "build": "DATABASE_URL=file:./proxy.db prisma migrate deploy && prisma generate && rimraf dist && tsc", + "docker:build": "prisma generate && rimraf dist && tsc", + "docker:start": "DATABASE_URL=file:/data/proxy.db prisma migrate deploy && DATABASE_URL=file:/data/proxy.db node dist", + "start": "yarn build && DATABASE_URL=file:./proxy.db node dist" }, "dependencies": { "@prisma/client": "^3.14.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7283428..89ae637 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,7 +3,7 @@ datasource db { provider = "sqlite" - url = "file:./proxy.db" + url = env("DATABASE_URL") } generator client { @@ -12,7 +12,6 @@ generator client { model BannedWebhook { id String @id - reason String } @@ -31,4 +30,4 @@ model BannedGame { model WebhooksSeen { id String @id -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index f958402..88828b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,7 @@ import { robloxRanges } from './robloxRanges'; import 'express-async-errors'; import { setup } from './rmq'; -const VERSION = (() => { +const VERSION = process.env.VERSION || (() => { const rev = fs.readFileSync('.git/HEAD').toString().trim(); if (rev.indexOf(':') === -1) { return rev; @@ -33,11 +33,11 @@ const VERSION = (() => { })(); const app = Express(); -const config = JSON.parse(fs.readFileSync('./config.json', 'utf8')) as { +let config = {} as { port: number; trustProxy: boolean; autoBlock: boolean; - queue: { + queue?: { enabled: boolean; rabbitmq: string; queue: string; @@ -46,6 +46,38 @@ const config = JSON.parse(fs.readFileSync('./config.json', 'utf8')) as { abuseThreshold: number; }; +if (fs.existsSync('./config.json')) { + config = JSON.parse(fs.readFileSync('./config.json', 'utf8')); +} + +function parseConfigBoolean(value:string|any):boolean { + if (value === "1") return true; + if (value === "0") return false; + if (value.toLowerCase() === "true") return true; + if (value.toLowerCase() === "false") return false; + if (value.toLowerCase() === "yes") return true; + if (value.toLowerCase() === "no") return false; + if (value.toLowerCase() === "y") return true; + if (value.toLowerCase() === "n") return false; +} + +// Read configuration from env variable +for (const envItem of [ + [ 'PORT', (value:string) => { config.port = parseInt(value) } ], + [ 'TRUST_PROXY', (value:string) => { config.trustProxy = parseConfigBoolean(value) } ], + [ 'AUTO_BLOCK', (value:string) => { config.autoBlock = parseConfigBoolean(value) } ], + [ 'QUEUE_ENABLED', (value:string) => { (config.queue ?? ( config.queue = {} as typeof config["queue"] )).enabled = parseConfigBoolean(value) } ], + [ 'QUEUE_RABBITMQ', (value:string) => { (config.queue ?? ( config.queue = {} as typeof config["queue"] )).rabbitmq = value } ], + [ 'QUEUE_QUEUE', (value:string) => { (config.queue ?? ( config.queue = {} as typeof config["queue"] )).queue = value } ], + [ 'REDIS', (value:string) => { config.redis = value } ], + [ 'ABUSE_THRESHOLD', (value:string) => { config.abuseThreshold = parseInt(value) } ], +] as Array<[string, (value:string)=>undefined ]>) { + const value = process.env[envItem[0]]; + if (value !== undefined) { + envItem[1](value); + } +} + const db = new PrismaClient(); const redis = new Redis(config.redis); beforeShutdown(async () => {