feat: project scaffold — Docker, NestJS, Nuxt 3, Prisma config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
99f3c5caab
commit
88dbb99f9d
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
DB_PASSWORD=changeme
|
||||||
|
JWT_SECRET=generate-a-64-char-hex-string
|
||||||
|
ENCRYPTION_KEY=generate-a-64-char-hex-string
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.output/
|
||||||
|
.nuxt/
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
backend/prisma/*.db
|
||||||
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Stage 1: Frontend build
|
||||||
|
FROM node:20-alpine AS frontend
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY frontend/ ./
|
||||||
|
RUN npx nuxi generate
|
||||||
|
|
||||||
|
# Stage 2: Backend build
|
||||||
|
FROM node:20-alpine AS backend
|
||||||
|
WORKDIR /app/backend
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY backend/ ./
|
||||||
|
RUN npx prisma generate
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 3: Production
|
||||||
|
FROM node:20-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=backend /app/backend/dist ./dist
|
||||||
|
COPY --from=backend /app/backend/node_modules ./node_modules
|
||||||
|
COPY --from=backend /app/backend/package.json ./
|
||||||
|
COPY --from=backend /app/backend/prisma ./prisma
|
||||||
|
COPY --from=frontend /app/frontend/.output/public ./public
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "dist/main.js"]
|
||||||
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Wraith
|
||||||
|
|
||||||
|
Self-hosted MobaXterm replacement — SSH + SFTP + RDP in a browser.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Backend:** NestJS 10, Prisma 6, PostgreSQL 16, ssh2, guacd
|
||||||
|
- **Frontend:** Nuxt 3 (SPA), PrimeVue 4, Tailwind CSS, xterm.js 5
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with real secrets
|
||||||
|
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Default credentials: `admin@wraith.local` / `wraith`
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backend
|
||||||
|
cd backend && npm install && npm run dev
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
cd frontend && npm install && npm run dev
|
||||||
|
```
|
||||||
8
backend/nest-cli.json
Normal file
8
backend/nest-cli.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true
|
||||||
|
}
|
||||||
|
}
|
||||||
63
backend/package.json
Normal file
63
backend/package.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"name": "wraith-backend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"start": "node dist/main.js",
|
||||||
|
"dev": "nest start --watch",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"prisma:migrate": "prisma migrate dev",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:seed": "ts-node prisma/seed.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/common": "^10.0.0",
|
||||||
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/jwt": "^10.0.0",
|
||||||
|
"@nestjs/mapped-types": "^2.0.0",
|
||||||
|
"@nestjs/passport": "^10.0.0",
|
||||||
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
|
"@nestjs/platform-ws": "^10.0.0",
|
||||||
|
"@nestjs/serve-static": "^4.0.0",
|
||||||
|
"@nestjs/websockets": "^10.0.0",
|
||||||
|
"@prisma/client": "^6.0.0",
|
||||||
|
"bcrypt": "^5.1.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.0",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"reflect-metadata": "^0.2.0",
|
||||||
|
"rxjs": "^7.8.0",
|
||||||
|
"ssh2": "^1.15.0",
|
||||||
|
"ws": "^8.16.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^10.0.0",
|
||||||
|
"@nestjs/testing": "^10.0.0",
|
||||||
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
"@types/node": "^20.0.0",
|
||||||
|
"@types/passport-jwt": "^4.0.0",
|
||||||
|
"@types/ssh2": "^1.15.0",
|
||||||
|
"@types/ws": "^8.5.0",
|
||||||
|
"jest": "^29.0.0",
|
||||||
|
"prisma": "^6.0.0",
|
||||||
|
"ts-jest": "^29.0.0",
|
||||||
|
"ts-node": "^10.9.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
},
|
||||||
|
"prisma": {
|
||||||
|
"seed": "ts-node prisma/seed.ts"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": ["js", "json", "ts"],
|
||||||
|
"rootDir": ".",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": { "^.+\\.ts$": "ts-jest" },
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^@/(.*)$": "<rootDir>/src/$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
backend/tsconfig.build.json
Normal file
4
backend/tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*.spec.ts"]
|
||||||
|
}
|
||||||
22
backend/tsconfig.json
Normal file
22
backend/tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "ES2021",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"incremental": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"paths": { "@/*": ["src/*"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
36
docker-compose.yml
Normal file
36
docker-compose.yml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
ports: ["3000:3000"]
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql://wraith:${DB_PASSWORD}@postgres:5432/wraith
|
||||||
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
|
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||||
|
GUACD_HOST: guacd
|
||||||
|
GUACD_PORT: "4822"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
guacd:
|
||||||
|
condition: service_started
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
guacd:
|
||||||
|
image: guacamole/guacd
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
volumes: [pgdata:/var/lib/postgresql/data]
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: wraith
|
||||||
|
POSTGRES_USER: wraith
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U wraith"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
5
frontend/app.vue
Normal file
5
frontend/app.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<NuxtLayout>
|
||||||
|
<NuxtPage />
|
||||||
|
</NuxtLayout>
|
||||||
|
</template>
|
||||||
7
frontend/assets/css/main.css
Normal file
7
frontend/assets/css/main.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html, body, #__nuxt {
|
||||||
|
@apply h-full bg-gray-900 text-gray-100;
|
||||||
|
}
|
||||||
5
frontend/layouts/auth.vue
Normal file
5
frontend/layouts/auth.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-gray-950">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
29
frontend/nuxt.config.ts
Normal file
29
frontend/nuxt.config.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export default defineNuxtConfig({
|
||||||
|
ssr: false,
|
||||||
|
devtools: { enabled: false },
|
||||||
|
modules: [
|
||||||
|
'@pinia/nuxt',
|
||||||
|
'@nuxtjs/tailwindcss',
|
||||||
|
'@primevue/nuxt-module',
|
||||||
|
],
|
||||||
|
css: ['~/assets/css/main.css'],
|
||||||
|
primevue: {
|
||||||
|
options: {
|
||||||
|
theme: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
runtimeConfig: {
|
||||||
|
public: {
|
||||||
|
apiBase: process.env.API_BASE || 'http://localhost:3000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
port: 3001,
|
||||||
|
},
|
||||||
|
nitro: {
|
||||||
|
devProxy: {
|
||||||
|
'/api': { target: 'http://localhost:3000/api', ws: true },
|
||||||
|
'/ws': { target: 'ws://localhost:3000/ws', ws: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
30
frontend/package.json
Normal file
30
frontend/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "wraith-frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nuxi dev",
|
||||||
|
"build": "nuxi generate",
|
||||||
|
"preview": "nuxi preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@pinia/nuxt": "^0.5.0",
|
||||||
|
"@primevue/themes": "^4.0.0",
|
||||||
|
"guacamole-common-js": "^1.5.0",
|
||||||
|
"lucide-vue-next": "^0.300.0",
|
||||||
|
"monaco-editor": "^0.45.0",
|
||||||
|
"pinia": "^2.1.0",
|
||||||
|
"primevue": "^4.0.0",
|
||||||
|
"@xterm/xterm": "^5.4.0",
|
||||||
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
"@xterm/addon-search": "^0.15.0",
|
||||||
|
"@xterm/addon-web-links": "^0.11.0",
|
||||||
|
"@xterm/addon-webgl": "^0.18.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nuxtjs/tailwindcss": "^6.0.0",
|
||||||
|
"@primevue/nuxt-module": "^4.0.0",
|
||||||
|
"nuxt": "^3.10.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
frontend/tailwind.config.ts
Normal file
32
frontend/tailwind.config.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import type { Config } from 'tailwindcss'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
'./components/**/*.vue',
|
||||||
|
'./layouts/**/*.vue',
|
||||||
|
'./pages/**/*.vue',
|
||||||
|
'./composables/**/*.ts',
|
||||||
|
'./app.vue',
|
||||||
|
],
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
wraith: {
|
||||||
|
50: '#f0f4ff',
|
||||||
|
100: '#dbe4ff',
|
||||||
|
200: '#bac8ff',
|
||||||
|
300: '#91a7ff',
|
||||||
|
400: '#748ffc',
|
||||||
|
500: '#5c7cfa',
|
||||||
|
600: '#4c6ef5',
|
||||||
|
700: '#4263eb',
|
||||||
|
800: '#3b5bdb',
|
||||||
|
900: '#364fc7',
|
||||||
|
950: '#1e3a8a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
} satisfies Config
|
||||||
Loading…
Reference in New Issue
Block a user