claude/compassionate-feistel #4

Merged
gpatti merged 4 commits from claude/compassionate-feistel into main 2026-03-27 03:57:16 +00:00
8 changed files with 108 additions and 7 deletions
Showing only changes of commit e525b7faa3 - Show all commits

10
.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
.git
node_modules
.next
.thumbnails
medialore.db
medialore.db-shm
medialore.db-wal
data/
.env*
docker-compose*.yml

57
Dockerfile Normal file
View File

@@ -0,0 +1,57 @@
# Stage 1: Install dependencies (with build tools for native modules)
FROM node:22-bookworm-slim AS deps
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci
# Stage 2: Build the Next.js application
FROM node:22-bookworm-slim AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Stage 3: Production image
FROM node:22-bookworm-slim AS runner
WORKDIR /app
# Install ffmpeg for video thumbnail generation
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_ENV=production
ENV PORT=3000
# Bind to all interfaces so the container port is reachable
ENV HOSTNAME=0.0.0.0
# Store libraries.json in /config so it can be bind-mounted as a directory.
# Mounting a directory (not a single file) ensures the atomic rename in
# writeLibraries() works — both .tmp and the target are on the same filesystem.
ENV LIBRARIES_CONFIG_PATH=/config/libraries.json
RUN mkdir -p /config
# Copy standalone Next.js output
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
# Copy native modules — Next.js standalone's file tracer does not follow
# .node binary files, so we copy these manually from the deps stage.
# Both are listed in serverExternalPackages and resolved at runtime via require().
COPY --from=deps /app/node_modules/better-sqlite3 ./node_modules/better-sqlite3
COPY --from=deps /app/node_modules/sharp ./node_modules/sharp
COPY --from=deps /app/node_modules/@img ./node_modules/@img
# Create thumbnail cache directory (mounted as a volume in production)
RUN mkdir -p /app/.thumbnails
EXPOSE 3000
CMD ["node", "server.js"]

14
config/libraries.json Normal file
View File

@@ -0,0 +1,14 @@
[
{
"id": "games",
"name": "Games",
"path": "/data/Games",
"type": "games"
},
{
"id": "various",
"name": "various",
"path": "/data/Various Media",
"type": "mixed"
}
]

23
docker-compose.yml Normal file
View File

@@ -0,0 +1,23 @@
services:
app:
build: .
ports:
- 3000:3000
environment:
PORT: 3000
NODE_ENV: production
volumes:
# Runtime data — must map to /app/ since process.cwd() = /app in the container
- ./medialore.db:/app/medialore.db
- ./.thumbnails:/app/.thumbnails
# Library config — mounted as a directory so the atomic rename in the API works.
# A single-file bind-mount causes EBUSY on rename() because .tmp and the target
# end up on different devices. Initialize before first run:
# mkdir -p config && echo '[]' > config/libraries.json
- ./config:/config
# Mount your media folders and reference them in libraries.json using
# absolute /data/ paths, e.g. { "path": "/data/Games" }
- ./data:/data:ro
# - /path/to/your/movies:/data/Movies:ro
restart: unless-stopped

View File

@@ -4,11 +4,5 @@
"name": "Games", "name": "Games",
"path": "./data/Games", "path": "./data/Games",
"type": "games" "type": "games"
},
{
"id": "various",
"name": "Various",
"path": "./data/Various Media",
"type": "mixed"
} }
] ]

View File

@@ -1,6 +1,7 @@
import type { NextConfig } from 'next' import type { NextConfig } from 'next'
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: 'standalone',
serverExternalPackages: ['better-sqlite3', 'sharp'], serverExternalPackages: ['better-sqlite3', 'sharp'],
} }

View File

@@ -1,3 +1,5 @@
export const dynamic = 'force-dynamic'
import { redirect } from 'next/navigation' import { redirect } from 'next/navigation'
import { getLibraries } from '@/lib/libraries' import { getLibraries } from '@/lib/libraries'
import LibraryCard from '@/components/LibraryCard' import LibraryCard from '@/components/LibraryCard'

View File

@@ -2,7 +2,7 @@ import path from 'path'
import fs from 'fs' import fs from 'fs'
import type { Library, LibraryType } from '@/types' import type { Library, LibraryType } from '@/types'
const CONFIG_PATH = path.resolve(process.cwd(), 'libraries.json') const CONFIG_PATH = process.env.LIBRARIES_CONFIG_PATH ?? path.resolve(process.cwd(), 'libraries.json')
const CONFIG_TMP = CONFIG_PATH + '.tmp' const CONFIG_TMP = CONFIG_PATH + '.tmp'
export function getLibraries(): Library[] { export function getLibraries(): Library[] {