diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2e71340 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.git +.jj +node_modules +**/node_modules +.turbo +.cache +coverage +**/dist +**/build +**/*.tsbuildinfo +.env* +**/.env* +!.env.example +!**/.env.example +logs +*.log +docs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2e78d98 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +POSTGRES_DB=minmon +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_PORT=5432 + +DATABASE_URL=postgresql://postgres:postgres@postgres:5432/minmon + +BETTER_AUTH_SECRET=minmon-compose-secret-change-me-123456 +BETTER_AUTH_URL=http://localhost:3000 +CORS_ORIGIN=http://localhost:3001 +NODE_ENV=development + +SERVER_PORT=3000 +WEB_PORT=3001 +VITE_SERVER_URL=http://localhost:3000 diff --git a/README.md b/README.md index e06f18a..228bcd9 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,25 @@ bun run dev - Web: `http://localhost:5173` - API: `http://localhost:3000` +## Docker Compose Deployment + +Run the full stack with PostgreSQL, database initialization, API, and web UI: + +```bash +cp .env.example .env +docker compose up -d +``` + +Default endpoints: + +- Web: `http://localhost:3001` +- API: `http://localhost:3000` + +On first startup, the database initializer applies the schema and seeds the sample admin only when the users table is empty. + +Docker Compose reads the root `.env` file automatically. Edit it before startup if you need different ports, public URLs, credentials, or secrets. + + ## Project Structure ```text @@ -186,5 +205,5 @@ See: ## Notes - DNS checking is limited to A record / IPv4 as required. -- This is not a deployment platform and does not include SSH, Docker orchestration, or reverse proxy automation. +- Docker Compose is available for running the app stack; the app itself does not include SSH, reverse proxy, or host orchestration automation. - Full workspace typecheck is still blocked by a pre-existing desktop typing issue for `three`, but the web app and server app are verified. diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile new file mode 100644 index 0000000..0c49b3b --- /dev/null +++ b/apps/server/Dockerfile @@ -0,0 +1,33 @@ +# syntax=docker/dockerfile:1 + +FROM oven/bun:1.3.12 AS base +WORKDIR /app +ENV CI=1 + +COPY package.json bun.lock turbo.json tsconfig.json bts.jsonc ./ +COPY apps ./apps +COPY packages ./packages + +FROM base AS deps +RUN bun install --frozen-lockfile + +FROM deps AS tools +ENV NODE_ENV=development +RUN touch apps/server/.env + +FROM deps AS build +ENV NODE_ENV=production +RUN bun run --filter server build + +FROM base AS prod-deps +RUN bun install --frozen-lockfile --production + +FROM oven/bun:1.3.12 AS runtime +WORKDIR /app +ENV NODE_ENV=production + +COPY --from=prod-deps /app/node_modules ./node_modules +COPY --from=build /app/apps/server/dist ./apps/server/dist + +EXPOSE 3000 +CMD ["bun", "apps/server/dist/index.mjs"] diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..f3e637b --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +FROM oven/bun:1.3.12 AS build +WORKDIR /app +ENV CI=1 + +ARG VITE_SERVER_URL=http://localhost:3000 +ENV VITE_SERVER_URL=${VITE_SERVER_URL} + +COPY package.json bun.lock turbo.json tsconfig.json bts.jsonc ./ +COPY apps ./apps +COPY packages ./packages + +RUN bun install --frozen-lockfile +RUN bun run --filter web build + +FROM nginx:1.27-alpine AS runtime +COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/apps/web/dist /usr/share/nginx/html + +EXPOSE 80 diff --git a/apps/web/nginx.conf b/apps/web/nginx.conf new file mode 100644 index 0000000..79fd959 --- /dev/null +++ b/apps/web/nginx.conf @@ -0,0 +1,11 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/apps/web/src/components/dashboard-shell.tsx b/apps/web/src/components/dashboard-shell.tsx index a0147e4..a3ddfee 100644 --- a/apps/web/src/components/dashboard-shell.tsx +++ b/apps/web/src/components/dashboard-shell.tsx @@ -15,6 +15,7 @@ import { LayoutGrid, MenuIcon, Server, + Users, Wrench, } from "lucide-react"; import { useEffect, useState } from "react"; @@ -25,30 +26,42 @@ const dashboardLinks = [ label: "Overview", description: "Health, alerts, and recent checks", icon: LayoutGrid, + permission: "dashboard.read", }, { to: "/dashboard/servers", label: "Servers", description: "Inventory and host details", icon: Server, + permission: "servers.read", }, { to: "/dashboard/services", label: "Services", description: "Processes and uptime targets", icon: Wrench, + permission: "services.read", }, { to: "/dashboard/domains", label: "Domains", description: "DNS, SSL, and expiry status", icon: Globe, + permission: "domains.read", }, { to: "/dashboard/notes", label: "Notes", description: "Runbooks and operational handover", icon: BookText, + permission: "notes.read", + }, + { + to: "/dashboard/users", + label: "Users", + description: "Roles and permission assignments", + icon: Users, + permission: "users.read", }, ] as const; @@ -60,14 +73,18 @@ function isActive(pathname: string, to: string): boolean { function NavItems({ pathname, + permissions, onNavigate, }: { pathname: string; + permissions: string[]; onNavigate?: () => void; }) { + const allowedPermissions = new Set(permissions); + return (