diff --git a/Makefile b/Makefile index 49134440..2867c304 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ -.PHONY: up-local up-lan down restart-local restart-lan logs status help +.PHONY: up-local up-lan down restart-local restart-lan dev dev-lan logs status help COMPOSE = docker compose # Detect LAN IP (tries Wi-Fi first, falls back to Ethernet) LAN_IP := $(shell ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null) +LAN_IP_MSG = "\nShadowbroker is now running and can be accessed by LAN devices at http://$(LAN_IP):3000" ## Default target — print help help: @@ -14,9 +15,12 @@ help: @echo "" @echo " up-local Start with loopback binding (local access only)" @echo " up-lan Start with 0.0.0.0 binding (LAN accessible)" + @echo " up-lan-log Start LAN-accessible with live logging" @echo " down Stop all containers" @echo " restart-local Bounce and restart in local mode" @echo " restart-lan Bounce and restart in LAN mode" + @echo " dev Start in watch mode (local only)" + @echo " dev-lan Start in watch mode (LAN accessible)" @echo " logs Tail logs for all services" @echo " status Show container status" @echo "" @@ -32,9 +36,18 @@ up-lan: exit 1; \ fi @echo "Detected LAN IP: $(LAN_IP)" - BIND=0.0.0.0 CORS_ORIGINS=http://$(LAN_IP):3000 $(COMPOSE) up -d - @echo "" - @echo "Shadowbroker is now running and can be accessed by LAN devices at http://$(LAN_IP):3000" + BIND=0.0.0.0 HOST=0.0.0.0 CORS_ORIGINS=http://$(LAN_IP):3000 $(COMPOSE) up -d + @echo "$(LAN_IP_MSG)" + +## Start in LAN mode with live logging (accessible to other hosts on the network) +up-lan-log: + @if [ -z "$(LAN_IP)" ]; then \ + echo "ERROR: Could not detect LAN IP. Check your network connection."; \ + exit 1; \ + fi + @echo "Detected LAN IP: $(LAN_IP)" + BIND=0.0.0.0 HOST=0.0.0.0 CORS_ORIGINS=http://$(LAN_IP):3000 $(COMPOSE) up + @echo "$(LAN_IP_MSG)" ## Stop all containers down: @@ -46,6 +59,20 @@ restart-local: down up-local ## Restart in LAN mode restart-lan: down up-lan +## Start in watch mode (local only, foreground) +dev: + BIND=127.0.0.1 $(COMPOSE) up --watch + +## Start in watch mode (LAN accessible, foreground) +dev-lan: + @if [ -z "$(LAN_IP)" ]; then \ + echo "ERROR: Could not detect LAN IP. Check your network connection."; \ + exit 1; \ + fi + @echo "Detected LAN IP: $(LAN_IP)" + @echo "$(LAN_IP_MSG)" + BIND=0.0.0.0 HOST=0.0.0.0 CORS_ORIGINS=http://$(LAN_IP):3000 $(COMPOSE) up --watch + ## Tail logs for all services logs: $(COMPOSE) logs -f diff --git a/README.md b/README.md index 1f96b85a..d9109019 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ --- +###UPDATE: + +**The primary repository is no longer hosted on github and has moved to gitlab. It can be found here:** + +**https://gitlab.com/bigbodycobain/Shadowbroker** + + +--- ![ShadowBroker](/uploads/46f99d19fa141a2efba37feee9de8aab/Title.jpg) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 00000000..41d72c4c --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,16 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile.dev + ports: + - "3000:3000" + volumes: + - .:/app + - /app/node_modules + - /app/.next + environment: + - HOST=0.0.0.0 + - NODE_ENV=development + command: next dev --hostname 0.0.0.0 --port 3000 + diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 00000000..e9727348 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,6 @@ +# docker-compose.override.yml (automatically loaded) +services: + frontend: + build: + context: ./frontend + dockerfile: Dockerfile.dev diff --git a/docker-compose.yml b/docker-compose.yml index 934634aa..aa46277e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,24 @@ services: volumes: - backend_data:/app/data restart: unless-stopped + develop: + watch: + - action: sync+restart + path: ./backend + target: /app + ignore: + - __pycache__/ + - "*.pyc" + - "*.pyc" + - node_modules/ + - action: rebuild + path: pyproject.toml + - action: rebuild + path: uv.lock + - action: rebuild + path: backend/package.json + - action: rebuild + path: backend/package-lock.json healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"] interval: 15s @@ -45,10 +63,21 @@ services: # Points the Next.js server-side proxy at the backend container via Docker networking. # Change this if your backend runs on a different host or port. - BACKEND_URL=http://backend:8000 + - HOST=${HOST:-127.0.0.1} depends_on: backend: condition: service_healthy restart: unless-stopped + develop: + watch: + - action: sync + path: ./frontend + target: /app + ignore: + - node_modules/ + - .next/ + - action: rebuild + path: ./frontend/package.json healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/"] interval: 30s diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev new file mode 100644 index 00000000..c11670b6 --- /dev/null +++ b/frontend/Dockerfile.dev @@ -0,0 +1,9 @@ +FROM node:20-alpine +RUN apk add --no-cache libc6-compat +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +EXPOSE 3000 +CMD ["npx", "next", "dev", "--hostname", "0.0.0.0", "--port", "3000"] diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 691d4270..a9088b9e 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,10 +1,30 @@ -import type { NextConfig } from 'next'; +import type { NextConfig } from "next"; +import os from "os"; // /api/* requests are proxied to the backend by the catch-all route handler at // src/app/api/[...path]/route.ts, which reads BACKEND_URL at request time. // Do NOT add rewrites for /api/* here — next.config is evaluated at build time, // so any URL baked in here ignores the runtime BACKEND_URL env var. +function getLanOrigins(): string[] { + if (process.env.HOST !== "0.0.0.0") return []; + + const origins = new Set(); + + for (const ifaces of Object.values(os.networkInterfaces())) { + for (const iface of ifaces ?? []) { + if (iface.family === "IPv4" && !iface.internal) { + const subnet = iface.address.split(".").slice(0, 3).join("."); + origins.add(`${subnet}.*`); + } + } + } + + return [...origins]; +} + +const lanOrigins = getLanOrigins(); + const skipTypecheck = process.env.NEXT_SKIP_TYPECHECK === '1'; const isDev = process.env.NODE_ENV !== 'production'; const securityHeaders = [ @@ -44,7 +64,7 @@ const securityHeaders = [ ]; const nextConfig: NextConfig = { - transpilePackages: ['react-map-gl', 'maplibre-gl'], + transpilePackages: ['react-map-gl', 'mapbox-gl', 'maplibre-gl'], output: 'standalone', devIndicators: false, images: { @@ -68,6 +88,7 @@ const nextConfig: NextConfig = { }, ]; }, + ...(lanOrigins.length > 0 && { allowedDevOrigins: lanOrigins }), }; export default nextConfig;