Rework backend #1
+11
-3
@@ -4,28 +4,36 @@
|
|||||||
|
|
||||||
# Ignore Node.js modules (they will be installed in the Docker image)
|
# Ignore Node.js modules (they will be installed in the Docker image)
|
||||||
node_modules
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
|
||||||
# Ignore build output (it will be generated in the Docker image)
|
# Ignore build output (it will be generated in the Docker image)
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
|
|
||||||
# Ignore Docker files themselves
|
# Ignore Docker files themselves (Caddyfile MUST stay in context for frontend build)
|
||||||
Dockerfile
|
Dockerfile
|
||||||
|
Dockerfile.ws
|
||||||
.dockerignore
|
.dockerignore
|
||||||
|
docker-compose.yml
|
||||||
|
|
||||||
# Ignore any local environment files if you have them
|
# Ignore any local environment files if you have them
|
||||||
.env
|
.env
|
||||||
# .env.local
|
.env.local
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
|
||||||
# Ignore IDE and OS-specific files
|
# Ignore IDE and OS-specific files
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Ignore local sqlite data + scratch diagnostics (never bake into image)
|
||||||
|
data/
|
||||||
|
scratch/
|
||||||
|
tmp/
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Caddyfile — serve static frontend, proxy /api + /ws to backend
|
||||||
|
# handle blocks are mutually exclusive + ordered: API/WS first, static last.
|
||||||
|
# (try_files at site level would rewrite /api/* → /index.html before proxy.)
|
||||||
|
|
||||||
|
{
|
||||||
|
# admin off for docker
|
||||||
|
admin off
|
||||||
|
}
|
||||||
|
|
||||||
|
:80 {
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
# REST API → backend service (path preserved: /api/doc etc.)
|
||||||
|
handle /api/* {
|
||||||
|
reverse_proxy backend:4001 {
|
||||||
|
header_up Host {host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# WebSocket upgrade → backend
|
||||||
|
handle /ws {
|
||||||
|
reverse_proxy backend:4001
|
||||||
|
}
|
||||||
|
|
||||||
|
# Everything else: static SPA with client-side routing fallback
|
||||||
|
handle {
|
||||||
|
root * /srv
|
||||||
|
try_files {path} /index.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP basic auth (in-house only). Uncomment + set CADDY_BASIC_AUTH env.
|
||||||
|
# basic_auth {
|
||||||
|
# {$CADDY_BASIC_AUTH}
|
||||||
|
# }
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Dockerfile.ws — frontend build (STORAGE=ws) served by Caddy
|
||||||
|
# Same-origin: Caddy proxies /api + /ws to backend. No backend URL baked at build.
|
||||||
|
|
||||||
|
FROM node:18-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# workspaces root
|
||||||
|
COPY package*.json ./
|
||||||
|
COPY shared/package.json ./shared/
|
||||||
|
COPY server/package.json ./server/
|
||||||
|
RUN npm install --include-workspace-root
|
||||||
|
|
||||||
|
COPY shared/ ./shared/
|
||||||
|
COPY src/ ./src/
|
||||||
|
COPY public/ ./public/
|
||||||
|
|
||||||
|
# Build with ws storage (no backend URL — same-origin via Caddy proxy)
|
||||||
|
ARG REACT_APP_TRACKER_APP_ID=ttrpg-initiative-tracker-default
|
||||||
|
ENV REACT_APP_STORAGE=ws
|
||||||
|
ENV REACT_APP_TRACKER_APP_ID=$REACT_APP_TRACKER_APP_ID
|
||||||
|
RUN NODE_OPTIONS=--openssl-legacy-provider npm run build
|
||||||
|
|
||||||
|
# Stage 2: Caddy serves static + proxies API/WS
|
||||||
|
FROM caddy:2-alpine
|
||||||
|
|
||||||
|
COPY --from=build /app/build /srv
|
||||||
|
COPY Caddyfile /etc/caddy/Caddyfile
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# docker-compose.yml — two profiles:
|
||||||
|
# firebase: existing Dockerfile (nginx + firebase build), upstream path
|
||||||
|
# backend: full stack (caddy frontend + node backend + sqlite volume)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# docker compose --profile backend up --build # full self-hosted stack
|
||||||
|
# docker compose --profile firebase up --build # firebase-only (upstream)
|
||||||
|
#
|
||||||
|
# Run local in OrbStack; remote docker context later (just change context).
|
||||||
|
|
||||||
|
services:
|
||||||
|
# ---- full self-hosted stack (STORAGE=ws) ----
|
||||||
|
backend:
|
||||||
|
profiles: ["backend"]
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: server/Dockerfile
|
||||||
|
image: ttrpg-backend:local
|
||||||
|
volumes:
|
||||||
|
- backend-data:/data
|
||||||
|
environment:
|
||||||
|
- DB_PATH=/data/tracker.sqlite
|
||||||
|
- PORT=4001
|
||||||
|
# - CORS_ORIGIN=* # Caddy same-origin, cors not strictly needed
|
||||||
|
expose:
|
||||||
|
- "4001"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
profiles: ["backend"]
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.ws
|
||||||
|
args:
|
||||||
|
- REACT_APP_TRACKER_APP_ID=${TRACKER_APP_ID:-ttrpg-initiative-tracker-default}
|
||||||
|
image: ttrpg-frontend:local
|
||||||
|
ports:
|
||||||
|
- "${FRONTEND_PORT:-8080}:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
# Optional basic auth: set in .env as CADDY_BASIC_AUTH="user <hashed-pass>"
|
||||||
|
# Generate hash: caddy hash-password
|
||||||
|
environment:
|
||||||
|
- CADDY_BASIC_AUTH=${CADDY_BASIC_AUTH:-}
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# ---- firebase-only path (upstream, existing Dockerfile) ----
|
||||||
|
firebase:
|
||||||
|
profiles: ["firebase"]
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: ttrpg-firebase:local
|
||||||
|
ports:
|
||||||
|
- "${FRONTEND_PORT:-8080}:80"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
backend-data:
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# server/Dockerfile — backend (Express + ws + better-sqlite3)
|
||||||
|
FROM node:18-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# workspaces root needed: shared/ is a dependency (@ttrpg/shared)
|
||||||
|
COPY package*.json ./
|
||||||
|
COPY shared/package.json ./shared/
|
||||||
|
COPY server/package.json ./server/
|
||||||
|
RUN npm install --workspaces --include-workspace-root
|
||||||
|
|
||||||
|
COPY shared/ ./shared/
|
||||||
|
COPY server/ ./server/
|
||||||
|
|
||||||
|
# better-sqlite3 builds native; rebuild for alpine musl
|
||||||
|
RUN cd server && npm rebuild better-sqlite3
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=4001
|
||||||
|
ENV DB_PATH=/data/tracker.sqlite
|
||||||
|
|
||||||
|
EXPOSE 4001
|
||||||
|
WORKDIR /app/server
|
||||||
|
CMD ["node", "index.js"]
|
||||||
+13
-2
@@ -13,8 +13,19 @@ if (typeof WebSocket !== 'undefined') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createWsStorage({ baseUrl, wsUrl } = {}) {
|
function createWsStorage({ baseUrl, wsUrl } = {}) {
|
||||||
const API = (baseUrl || 'http://127.0.0.1:4001').replace(/\/$/, '');
|
// Same-origin by default: empty baseUrl = relative fetch (Caddy/proxy).
|
||||||
const WS = wsUrl || (API.replace(/^http/, 'ws') + '/ws');
|
// Fallback to localhost for bare `npm start` dev without proxy.
|
||||||
|
const API = (baseUrl || (typeof window !== 'undefined' && window.location ? '' : 'http://127.0.0.1:4001')).replace(/\/$/, '');
|
||||||
|
let WS;
|
||||||
|
if (wsUrl) {
|
||||||
|
WS = wsUrl;
|
||||||
|
} else if (typeof window !== 'undefined' && window.location) {
|
||||||
|
// derive from current origin (http→ws, https→wss), same host/port.
|
||||||
|
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
WS = `${proto}//${window.location.host}/ws`;
|
||||||
|
} else {
|
||||||
|
WS = 'ws://127.0.0.1:4001/ws';
|
||||||
|
}
|
||||||
|
|
||||||
// App passes firebase-prefixed paths: artifacts/{APP_ID}/public/data/campaigns/...
|
// App passes firebase-prefixed paths: artifacts/{APP_ID}/public/data/campaigns/...
|
||||||
// Backend uses canonical paths. Strip prefix.
|
// Backend uses canonical paths. Strip prefix.
|
||||||
|
|||||||
Reference in New Issue
Block a user