From e525b7faa3456772a90a2b2190addf3e1ad3d3e1 Mon Sep 17 00:00:00 2001 From: Garret Patti Date: Thu, 26 Mar 2026 23:41:52 -0400 Subject: [PATCH 1/2] add docker config --- .dockerignore | 10 ++++++++ Dockerfile | 57 +++++++++++++++++++++++++++++++++++++++++++ config/libraries.json | 14 +++++++++++ docker-compose.yml | 23 +++++++++++++++++ libraries.json | 6 ----- next.config.ts | 1 + src/app/page.tsx | 2 ++ src/lib/libraries.ts | 2 +- 8 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 config/libraries.json create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2311435 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +node_modules +.next +.thumbnails +medialore.db +medialore.db-shm +medialore.db-wal +data/ +.env* +docker-compose*.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4db3c8c --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/config/libraries.json b/config/libraries.json new file mode 100644 index 0000000..6570921 --- /dev/null +++ b/config/libraries.json @@ -0,0 +1,14 @@ +[ + { + "id": "games", + "name": "Games", + "path": "/data/Games", + "type": "games" + }, + { + "id": "various", + "name": "various", + "path": "/data/Various Media", + "type": "mixed" + } +] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..83b543b --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/libraries.json b/libraries.json index 25200cc..44edecf 100644 --- a/libraries.json +++ b/libraries.json @@ -4,11 +4,5 @@ "name": "Games", "path": "./data/Games", "type": "games" - }, - { - "id": "various", - "name": "Various", - "path": "./data/Various Media", - "type": "mixed" } ] diff --git a/next.config.ts b/next.config.ts index b6992cb..3777626 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,7 @@ import type { NextConfig } from 'next' const nextConfig: NextConfig = { + output: 'standalone', serverExternalPackages: ['better-sqlite3', 'sharp'], } diff --git a/src/app/page.tsx b/src/app/page.tsx index 8d673a0..cd6dcad 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,5 @@ +export const dynamic = 'force-dynamic' + import { redirect } from 'next/navigation' import { getLibraries } from '@/lib/libraries' import LibraryCard from '@/components/LibraryCard' diff --git a/src/lib/libraries.ts b/src/lib/libraries.ts index 0f834ca..b3376f1 100644 --- a/src/lib/libraries.ts +++ b/src/lib/libraries.ts @@ -2,7 +2,7 @@ import path from 'path' import fs from 'fs' 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' export function getLibraries(): Library[] { From 4d9cb537f3ad9281d723ffb39bda6e1a9d8124a2 Mon Sep 17 00:00:00 2001 From: Garret Patti Date: Thu, 26 Mar 2026 23:52:18 -0400 Subject: [PATCH 2/2] add gitea docker build action --- .gitea/workflows/docker.yml | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .gitea/workflows/docker.yml diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml new file mode 100644 index 0000000..b1633cc --- /dev/null +++ b/.gitea/workflows/docker.yml @@ -0,0 +1,42 @@ +name: Build and Push Docker Image + +on: + push: + branches: [ main, dev ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Login to Registry + uses: docker/login-action@v2 + with: + registry: gitea.lan + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Build Docker Image + env: + BRANCH_NAME: ${{ github.ref_name }} + SHORT_HASH: ${{ github.sha }} + run: | + # Build the image with the commit hash tag + docker build --build-arg BUILD_IDENTIFIER=${SHORT_HASH:0:5} -t gitea.lan/gpatti/MediaLore:${BRANCH_NAME}-${SHORT_HASH:0:5} . + + # Tag the same image as "latest" + docker tag gitea.lan/gpatti/MediaLore:${BRANCH_NAME}-${SHORT_HASH:0:5} gitea.lan/gpatti/MediaLore:${BRANCH_NAME}-latest + + - name: Push Docker Images + env: + BRANCH_NAME: ${{ github.ref_name }} + SHORT_HASH: ${{ github.sha }} + run: | + docker push gitea.lan/gpatti/MediaLore:${BRANCH_NAME}-${SHORT_HASH:0:5} + docker push gitea.lan/gpatti/MediaLore:${BRANCH_NAME}-latest + + - name: Log out from registry + if: always() + run: docker logout gitea.lan \ No newline at end of file