Skip to content
Merged
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
99 changes: 99 additions & 0 deletions .github/workflows/api-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: API Tests

on:
push:
branches: [main, develop, beta_developer]
paths:
- 'apps/api/**'
- '.github/workflows/api-test.yml'
- 'pnpm-lock.yaml'
pull_request:
branches: [main, develop, beta_developer]
paths:
- 'apps/api/**'
- '.github/workflows/api-test.yml'
- 'pnpm-lock.yaml'

jobs:
test:
name: Run API Tests
runs-on: ubuntu-latest

permissions:
contents: read
pull-requests: write

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: nginx_waf_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Setup test environment
working-directory: apps/api
run: |
cp .env.test .env
echo "DATABASE_URL=postgresql://postgres:postgres@localhost:5432/nginx_waf_test?schema=public" >> .env

- name: Generate Prisma Client
working-directory: apps/api
run: pnpm prisma generate

- name: Run database migrations
working-directory: apps/api
run: pnpm prisma migrate deploy
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/nginx_waf_test?schema=public

- name: Run tests
working-directory: apps/api
run: pnpm test:coverage
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/nginx_waf_test?schema=public
NODE_ENV: test
JWT_ACCESS_SECRET: test-access-secret-key-12345
JWT_REFRESH_SECRET: test-refresh-secret-key-12345
JWT_ACCESS_EXPIRES_IN: 15m
JWT_REFRESH_EXPIRES_IN: 7d
SESSION_SECRET: test-session-secret-12345
CORS_ORIGIN: http://localhost:5173,http://localhost:3000
BCRYPT_ROUNDS: 4
TWO_FACTOR_APP_NAME: Nginx WAF Admin Test
BACKUP_DIR: ./test-backups
SSL_DIR: ./test-ssl
PORT: 3001

- name: 'Report Coverage'
# Set if: always() to also generate the report if tests are failing
# Only works if you set `reportOnFailure: true` in your vite config as specified above
if: always()
uses: davelosert/vitest-coverage-report-action@v2
with:
working-directory: apps/api
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ yarn.lock
*.sw?
landing/*
.env
.pnpm-store/
.pnpm-store/
.seeded
*.md
/docs/*
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ Comprehensive Nginx management system with ModSecurity WAF, Domain Management, S



# Project Goal
This project began as a private service built for a company. Later, my client and I decided to make it open source and free for the community to meet the personal or organizational needs of providing users with an easy way to configure Loadbalancer for server systems with SSL termination, Web Application Firewall, and it should be so easy that even a monkey could do it. This goal remains the same Although there may be advanced options, they are optional and the project should be as simple as possible to minimize the barrier to entry. The software will have all the features of an application for a digital business that is needed in the context of technological development to rapidly develop the system along with ensuring system security.

Recommendations: The software is developed with the support of AI so it cannot be absolutely secure, so please protect the Portal and API with a firewall to ensure safety. If you find any problems, please notify us and we will handle it..

## ✨ Key Features

Expand All @@ -30,6 +31,7 @@ Recommendations: The software is developed with the support of AI so it cannot b
|----------|--------|-------------|
| **New Server (Production)** | `./scripts/deploy.sh` | Full installation of Nginx + ModSecurity + Backend + Frontend with systemd services |
| **Development/Testing** | `./scripts/quickstart.sh` | Quick run in dev mode (no Nginx installation, no root required) |
| **Upgrade New Version** | `./scripts/update.sh` | Full update to new version |

### 🖥️ Production Deployment (New Server)

Expand All @@ -42,6 +44,17 @@ cd nginx-love
bash scripts/deploy.sh
```

### 🖥️ Production Upgrade Deployment (Upgrade New Version)

```bash
# Run Upgrade script (requires root)
cd nginx-love
git pull
bash scripts/update.sh
```



**Minimum Requirements:**
- Ubuntu/Debian server (22.04+ recommended)
- Root access
Expand Down
30 changes: 30 additions & 0 deletions apps/api/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Test Environment Configuration
NODE_ENV=test

# Test Database (PostgreSQL in Docker)
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/nginx_waf_test?schema=public"

# JWT Secrets (test values)
JWT_ACCESS_SECRET="test-access-secret-key-12345"
JWT_REFRESH_SECRET="test-refresh-secret-key-12345"
JWT_ACCESS_EXPIRES_IN="15m"
JWT_REFRESH_EXPIRES_IN="7d"

# Session
SESSION_SECRET="test-session-secret-12345"

# CORS
CORS_ORIGIN="http://localhost:5173,http://localhost:3000"

# BCrypt (lower rounds for faster tests)
BCRYPT_ROUNDS="4"

# 2FA
TWO_FACTOR_APP_NAME="Nginx WAF Admin Test"

# Paths (test paths)
BACKUP_DIR="./test-backups"
SSL_DIR="./test-ssl"

# Server
PORT=3001
1 change: 1 addition & 0 deletions apps/api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist/
coverage/
.vscode/
.idea/
!src/domains/logs/
21 changes: 18 additions & 3 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
"clean": "rm -rf dist node_modules",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
Expand All @@ -16,7 +20,12 @@
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"keywords": ["nginx", "waf", "modsecurity", "admin"],
"keywords": [
"nginx",
"waf",
"modsecurity",
"admin"
],
"author": "",
"license": "MIT",
"dependencies": {
Expand All @@ -32,8 +41,8 @@
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"nodemailer": "^6.9.14",
"speakeasy": "^2.0.0",
"qrcode": "^1.5.4",
"speakeasy": "^2.0.0",
"winston": "^3.13.1"
},
"devDependencies": {
Expand All @@ -47,9 +56,15 @@
"@types/nodemailer": "^6.4.15",
"@types/qrcode": "^1.5.5",
"@types/speakeasy": "^2.0.10",
"@types/supertest": "^6.0.3",
"@vitest/coverage-v8": "3.2.4",
"@vitest/ui": "^3.2.4",
"prisma": "^5.18.0",
"supertest": "^7.1.4",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.5.4"
"typescript": "^5.5.4",
"vitest": "^3.2.4",
"zod": "^4.1.11"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
-- CreateEnum
CREATE TYPE "BackupStatus" AS ENUM ('success', 'failed', 'running', 'pending');

-- CreateTable
CREATE TABLE "backup_schedules" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"schedule" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"lastRun" TIMESTAMP(3),
"nextRun" TIMESTAMP(3),
"status" "BackupStatus" NOT NULL DEFAULT 'pending',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "backup_schedules_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "backup_files" (
"id" TEXT NOT NULL,
"scheduleId" TEXT,
"filename" TEXT NOT NULL,
"filepath" TEXT NOT NULL,
"size" BIGINT NOT NULL,
"status" "BackupStatus" NOT NULL DEFAULT 'success',
"type" TEXT NOT NULL DEFAULT 'full',
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "backup_files_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "backup_files_scheduleId_idx" ON "backup_files"("scheduleId");

-- CreateIndex
CREATE INDEX "backup_files_createdAt_idx" ON "backup_files"("createdAt");

-- AddForeignKey
ALTER TABLE "backup_files" ADD CONSTRAINT "backup_files_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "backup_schedules"("id") ON DELETE SET NULL ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
-- CreateEnum
CREATE TYPE "SlaveNodeStatus" AS ENUM ('online', 'offline', 'syncing', 'error');

-- CreateEnum
CREATE TYPE "SyncLogStatus" AS ENUM ('success', 'failed', 'partial', 'running');

-- CreateEnum
CREATE TYPE "SyncLogType" AS ENUM ('full_sync', 'incremental_sync', 'health_check');

-- CreateTable
CREATE TABLE "slave_nodes" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"host" TEXT NOT NULL,
"port" INTEGER NOT NULL DEFAULT 3001,
"apiKey" TEXT NOT NULL,
"status" "SlaveNodeStatus" NOT NULL DEFAULT 'offline',
"lastSeen" TIMESTAMP(3),
"version" TEXT,
"syncEnabled" BOOLEAN NOT NULL DEFAULT true,
"syncInterval" INTEGER NOT NULL DEFAULT 60,
"configHash" TEXT,
"lastSyncAt" TIMESTAMP(3),
"latency" INTEGER,
"cpuUsage" DOUBLE PRECISION,
"memoryUsage" DOUBLE PRECISION,
"diskUsage" DOUBLE PRECISION,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "slave_nodes_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "sync_logs" (
"id" TEXT NOT NULL,
"nodeId" TEXT NOT NULL,
"type" "SyncLogType" NOT NULL,
"status" "SyncLogStatus" NOT NULL DEFAULT 'running',
"configHash" TEXT,
"changesCount" INTEGER,
"errorMessage" TEXT,
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completedAt" TIMESTAMP(3),
"duration" INTEGER,

CONSTRAINT "sync_logs_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "config_versions" (
"id" TEXT NOT NULL,
"version" SERIAL NOT NULL,
"configHash" TEXT NOT NULL,
"configData" JSONB NOT NULL,
"createdBy" TEXT,
"description" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "config_versions_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "slave_nodes_name_key" ON "slave_nodes"("name");

-- CreateIndex
CREATE UNIQUE INDEX "slave_nodes_apiKey_key" ON "slave_nodes"("apiKey");

-- CreateIndex
CREATE INDEX "slave_nodes_status_idx" ON "slave_nodes"("status");

-- CreateIndex
CREATE INDEX "slave_nodes_lastSeen_idx" ON "slave_nodes"("lastSeen");

-- CreateIndex
CREATE INDEX "sync_logs_nodeId_startedAt_idx" ON "sync_logs"("nodeId", "startedAt");

-- CreateIndex
CREATE UNIQUE INDEX "config_versions_configHash_key" ON "config_versions"("configHash");

-- CreateIndex
CREATE INDEX "config_versions_createdAt_idx" ON "config_versions"("createdAt");

-- AddForeignKey
ALTER TABLE "sync_logs" ADD CONSTRAINT "sync_logs_nodeId_fkey" FOREIGN KEY ("nodeId") REFERENCES "slave_nodes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- CreateEnum
CREATE TYPE "NodeMode" AS ENUM ('master', 'slave');

-- CreateTable
CREATE TABLE "system_configs" (
"id" TEXT NOT NULL,
"nodeMode" "NodeMode" NOT NULL DEFAULT 'master',
"masterApiEnabled" BOOLEAN NOT NULL DEFAULT true,
"slaveApiEnabled" BOOLEAN NOT NULL DEFAULT false,
"masterHost" TEXT,
"masterPort" INTEGER,
"masterApiKey" TEXT,
"syncInterval" INTEGER NOT NULL DEFAULT 60,
"lastSyncHash" TEXT,
"connected" BOOLEAN NOT NULL DEFAULT false,
"lastConnectedAt" TIMESTAMP(3),
"connectionError" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "system_configs_pkey" PRIMARY KEY ("id")
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterEnum
ALTER TYPE "ActivityType" ADD VALUE 'system';

-- AlterTable
ALTER TABLE "activity_logs" ALTER COLUMN "userId" DROP NOT NULL;
Loading