Added docker production setup, updated README

This commit is contained in:
Aleksi Lassila
2023-08-11 02:06:01 +03:00
parent 2566829e56
commit 053618e4d3
21 changed files with 152 additions and 94 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
node_modules
.svelte-kit
build
.idea
.env

43
Dockerfile Normal file
View File

@@ -0,0 +1,43 @@
FROM node:18-alpine as pre-production
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY . .
#COPY package.json .
#COPY package-lock.json .
RUN npm i
RUN npm run build
FROM --platform=linux/amd64 node:18-alpine as production
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ENV NODE_ENV=production
COPY --from=pre-production /usr/src/app/build ./build
COPY package.json .
COPY package-lock.json .
RUN npm ci --omit dev
CMD [ "PORT", "9494", "node", "build" ]
FROM node:18 as development
ENV NODE_ENV=development
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json .
COPY package-lock.json .
RUN npm i
CMD [ "npm", "run", "dev" ]

View File

@@ -24,7 +24,34 @@ Local Library & Playback
For a list of planned features & known bugs, see [Reiverr Taskboard](https://github.com/users/aleksilassila/projects/5).
# Getting started
# Installation
The easiest and the recommended way to insstall Reiverr is via docker-compose. Make sure to update the api keys and base URLs to match your setup.
Radarr & Sonarr API keys can be found under Settings > General in their respective web UIs. Jellyfin API key is located under Administration > Dashboard > Advanced > API Keys in the Jellyfin Web UI.
```yaml
version: '3.8'
name: reiverr
services:
reiverr-frontend:
container_name: reiverr
image: ghcr.io/aleksilassila/reiverr:latest
restart: unless-stopped
ports:
- 9494:9494
environment:
PUBLIC_RADARR_API_KEY: yourapikeyhere
PUBLIC_RADARR_BASE_URL: http://127.0.0.1:7878
PUBLIC_SONARR_API_KEY: yourapikeyhere
PUBLIC_SONARR_BASE_URL: http://127.0.0.1:8989
PUBLIC_JELLYFIN_API_KEY: yourapikeyhere
PUBLIC_JELLYFIN_URL: http://127.0.0.1:8096
```
### Reiverr will be accessible via port 9494 by default.
# Contributing
@@ -49,22 +76,24 @@ To get started with development:
3. Run `npm install`
4. Run `npm run dev`
Alternatively, you can run `docker-compose up`.
Example .env file:
```env
# The PUBLIC_ prefix is required for SvelteKit to expose the variable to the client.
# The PUBLIC_ prefix is required for SvelteKit to expose the variable to the web browser.
# If you are exposing the server to the internet (not recommended), you should use HTTPS.
# Fill in the blanks and change the base URLs to match your setup.
PUBLIC_RADARR_API_KEY=
PUBLIC_RADARR_BASE_URL=http://192.168.0.129:7878
PUBLIC_RADARR_BASE_URL=http://127.0.0.1:7878
PUBLIC_SONARR_API_KEY=
PUBLIC_SONARR_BASE_URL=http://192.168.0.129:8989
PUBLIC_SONARR_BASE_URL=http://127.0.0.1:8989
PUBLIC_JELLYFIN_API_KEY=
PUBLIC_JELLYFIN_URL=http://192.168.0.129:8096
PUBLIC_JELLYFIN_URL=http://127.0.0.1:8096
```
# Additional Screenshots

View File

@@ -0,0 +1,14 @@
version: '3.8'
name: reiverr
services:
reiverr-frontend:
volumes:
- ./:/usr/src/app/
- /usr/src/app/node_modules
build:
context: .
target: development
ports:
- 5173:5173

12
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,12 @@
version: '3.8'
name: reiverr-prod
services:
reiverr-frontend:
container_name: reiverr-prod
build:
context: .
target: production
ports:
- 9494:3000

9
docker-compose.yml Normal file
View File

@@ -0,0 +1,9 @@
version: '3.8'
name: reiverr-dev
services:
reiverr-frontend:
container_name: reiverr-dev
image: ghcr.io/aleksilassila/reiverr:latest
restart: unless-stopped

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

After

Width:  |  Height:  |  Size: 13 MiB

View File

@@ -2,7 +2,7 @@
"name": "reiverr",
"version": "0.0.1",
"scripts": {
"dev": "vite dev",
"dev": "vite dev --host",
"build": "vite build",
"preview": "vite preview",
"test": "playwright test",

View File

@@ -1,6 +1,6 @@
import createClient from 'openapi-fetch';
import type { components, paths } from '$lib/apis/jellyfin/jellyfin.generated';
import { PUBLIC_JELLYFIN_API_KEY, PUBLIC_JELLYFIN_URL } from '$env/static/public';
import { env } from '$env/dynamic/public';
import { request } from '$lib/utils';
import type { DeviceProfile } from '$lib/apis/jellyfin/playback-profiles';
import { settings } from '$lib/stores/settings.store';
@@ -11,9 +11,9 @@ export type JellyfinItem = components['schemas']['BaseItemDto'];
export const JELLYFIN_DEVICE_ID = 'Reiverr Client';
export const JellyfinApi = createClient<paths>({
baseUrl: PUBLIC_JELLYFIN_URL,
baseUrl: env.PUBLIC_JELLYFIN_URL,
headers: {
Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${PUBLIC_JELLYFIN_API_KEY}"`
Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${env.PUBLIC_JELLYFIN_API_KEY}"`
}
});

View File

@@ -3,7 +3,7 @@ import { log, request } from '$lib/utils';
import type { paths } from '$lib/apis/radarr/radarr.generated';
import type { components } from '$lib/apis/radarr/radarr.generated';
import { getTmdbMovie } from '$lib/apis/tmdb/tmdbApi';
import { PUBLIC_RADARR_API_KEY, PUBLIC_RADARR_BASE_URL } from '$env/static/public';
import { env } from '$env/dynamic/public';
import { settings } from '$lib/stores/settings.store';
import { get } from 'svelte/store';
@@ -27,9 +27,9 @@ export interface RadarrMovieOptions {
}
export const RadarrApi = createClient<paths>({
baseUrl: PUBLIC_RADARR_BASE_URL,
baseUrl: env.PUBLIC_RADARR_BASE_URL,
headers: {
'X-Api-Key': PUBLIC_RADARR_API_KEY
'X-Api-Key': env.PUBLIC_RADARR_API_KEY
}
});

View File

@@ -1,4 +1,4 @@
import { PUBLIC_SONARR_API_KEY, PUBLIC_SONARR_BASE_URL } from '$env/static/public';
import { env } from '$env/dynamic/public';
import type { components, paths } from '$lib/apis/sonarr/sonarr.generated';
import { log } from '$lib/utils';
import createClient from 'openapi-fetch';
@@ -40,9 +40,9 @@ export interface SonarrSeriesOptions {
}
export const SonarrApi = createClient<paths>({
baseUrl: PUBLIC_SONARR_BASE_URL,
baseUrl: env.PUBLIC_SONARR_BASE_URL,
headers: {
'X-Api-Key': PUBLIC_SONARR_API_KEY
'X-Api-Key': env.PUBLIC_SONARR_API_KEY
}
});

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { PUBLIC_RADARR_BASE_URL } from '$env/static/public';
import { env } from '$env/dynamic/public';
import { getDiskSpace } from '$lib/apis/radarr/radarrApi';
import { library } from '$lib/stores/library.store';
import { formatSize } from '$lib/utils.js';
@@ -44,7 +44,7 @@
{large}
title="Radarr"
subtitle="Movies Provider"
href={PUBLIC_RADARR_BASE_URL}
href={env.PUBLIC_RADARR_BASE_URL}
stats={[
{ title: 'Movies', value: String(moviesCount) },
{ title: 'Space Taken', value: formatSize(spaceOccupied) },

View File

@@ -4,7 +4,7 @@
import StatsPlaceholder from './StatsPlaceholder.svelte';
import StatsContainer from './StatsContainer.svelte';
import SonarrIcon from '../svgs/SonarrIcon.svelte';
import { PUBLIC_SONARR_BASE_URL } from '$env/static/public';
import { env } from '$env/dynamic/public';
import { getDiskSpace } from '$lib/apis/sonarr/sonarrApi';
import { library } from '$lib/stores/library.store';
@@ -46,7 +46,7 @@
{large}
title="Sonarr"
subtitle="Shows Provider"
href={PUBLIC_SONARR_BASE_URL}
href={env.PUBLIC_SONARR_BASE_URL}
stats={[
{ title: 'Episodes', value: String(episodesCount) },
{ title: 'Space Taken', value: formatSize(spaceOccupied) },

View File

@@ -10,6 +10,7 @@
import { onMount } from 'svelte';
import { fade, fly } from 'svelte/transition';
import type { TitleType } from '$lib/types';
import { openTitleModal } from '../Modal/Modal';
const TRAILER_TIMEOUT = 3000;
const TRAILER_LOAD_TIME = 1000;
@@ -110,25 +111,6 @@
</span>
{/each}
</div>
<!-- <div
class="flex gap-4"
in:fly|global={{ y: -5, delay: ANIMATION_DURATION, duration: ANIMATION_DURATION }}
out:fly|global={{ y: 5, duration: ANIMATION_DURATION }}
>
<Button type="primary" href={`/${type}/${tmdbId}`}>
<span>Details</span><ChevronRight size={20} />
</Button>
{#if trailerId}
<Button
type="secondary"
href={youtubeUrl}
on:mouseover={() => (focusTrailer = true)}
on:mouseleave={() => (focusTrailer = false)}
>
<span>Watch Trailer</span><ChevronRight size={20} />
</Button>
{/if}
</div> -->
</div>
{/if}
@@ -138,7 +120,7 @@
out:fade|global={{ duration: ANIMATION_DURATION }}
>
<div class="flex gap-4 items-center">
<Button size="lg" type="primary" href={`/${type}/${tmdbId}`}>
<Button size="lg" type="primary" on:click={() => openTitleModal(tmdbId, type)}>
<span>Details</span><ChevronRight size={20} />
</Button>
{#if trailerId}

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { PUBLIC_JELLYFIN_URL } from '$env/static/public';
import { env } from '$env/dynamic/public';
import {
getJellyfinItem,
getJellyfinPlaybackInfo,
@@ -48,10 +48,10 @@
const hls = new Hls();
hls.loadSource(PUBLIC_JELLYFIN_URL + playbackUri);
hls.loadSource(env.PUBLIC_JELLYFIN_URL + playbackUri);
hls.attachMedia(video);
} else {
video.src = PUBLIC_JELLYFIN_URL + playbackUri;
video.src = env.PUBLIC_JELLYFIN_URL + playbackUri;
}
if (item?.UserData?.PlaybackPositionTicks) {

View File

@@ -1,17 +1,12 @@
import type { LayoutLoad } from './$types';
import {
PUBLIC_RADARR_API_KEY,
PUBLIC_RADARR_BASE_URL,
PUBLIC_SONARR_API_KEY,
PUBLIC_SONARR_BASE_URL
} from '$env/static/public';
import { PUBLIC_JELLYFIN_API_KEY, PUBLIC_JELLYFIN_URL } from '$env/static/public';
import { dev } from '$app/environment';
import { env } from '$env/dynamic/public';
// import { dev } from '$app/environment';
// Disable SSR when running the dev server
// This is a fix to vite dev server freezing on mac :(
// https://github.com/vitejs/vite/issues/11468
export const ssr = !dev;
// export const ssr = !dev;
export const ssr = false;
export type MissingEnvironmentVariables = {
PUBLIC_RADARR_API_KEY: boolean;
@@ -24,22 +19,22 @@ export type MissingEnvironmentVariables = {
export const load = (async () => {
const isApplicationSetUp =
!!PUBLIC_RADARR_API_KEY &&
!!PUBLIC_RADARR_BASE_URL &&
!!PUBLIC_SONARR_API_KEY &&
!!PUBLIC_SONARR_BASE_URL &&
!!PUBLIC_JELLYFIN_API_KEY &&
!!PUBLIC_JELLYFIN_URL;
!!env.PUBLIC_RADARR_API_KEY &&
!!env.PUBLIC_RADARR_BASE_URL &&
!!env.PUBLIC_SONARR_API_KEY &&
!!env.PUBLIC_SONARR_BASE_URL &&
!!env.PUBLIC_JELLYFIN_API_KEY &&
!!env.PUBLIC_JELLYFIN_URL;
return {
isApplicationSetUp,
missingEnvironmentVariables: {
PUBLIC_RADARR_API_KEY: !PUBLIC_RADARR_API_KEY,
PUBLIC_RADARR_BASE_URL: !PUBLIC_RADARR_BASE_URL,
PUBLIC_SONARR_API_KEY: !PUBLIC_SONARR_API_KEY,
PUBLIC_SONARR_BASE_URL: !PUBLIC_SONARR_BASE_URL,
PUBLIC_JELLYFIN_API_KEY: !PUBLIC_JELLYFIN_API_KEY,
PUBLIC_JELLYFIN_URL: !PUBLIC_JELLYFIN_URL
PUBLIC_RADARR_API_KEY: !env.PUBLIC_RADARR_API_KEY,
PUBLIC_RADARR_BASE_URL: !env.PUBLIC_RADARR_BASE_URL,
PUBLIC_SONARR_API_KEY: !env.PUBLIC_SONARR_API_KEY,
PUBLIC_SONARR_BASE_URL: !env.PUBLIC_SONARR_BASE_URL,
PUBLIC_JELLYFIN_API_KEY: !env.PUBLIC_JELLYFIN_API_KEY,
PUBLIC_JELLYFIN_URL: !env.PUBLIC_JELLYFIN_URL
}
};
}) satisfies LayoutLoad;

View File

@@ -1,15 +0,0 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error, json } from '@sveltejs/kit';
import { getJellyfinItemByTmdbId } from '$lib/apis/jellyfin/jellyfinApi';
export const GET = (async ({ params, request }) => {
const body = await request.json();
const { tmdbId } = body;
if (!tmdbId) throw error(400, 'NO_TMDB_ID');
const response = await getJellyfinItemByTmdbId(tmdbId);
return json(response);
}) satisfies RequestHandler;

View File

@@ -1,13 +0,0 @@
import { RadarrApi, getRadarrMovies } from '$lib/apis/radarr/radarrApi';
import { json } from '@sveltejs/kit';
import type { RequestHandler } from '@sveltejs/kit';
export type RadarrStatsDto = {
movies: Awaited<ReturnType<typeof getRadarrMovies>>;
};
export const GET = (async () => {
const radarrMovies = await getRadarrMovies();
return json({ movies: radarrMovies });
}) satisfies RequestHandler;

View File

@@ -1,7 +1,4 @@
<script lang="ts">
import RadarrIcon from '$lib/components/svgs/RadarrIcon.svelte';
import SonarrIcon from '$lib/components/svgs/SonarrIcon.svelte';
import { formatSize } from '$lib/utils.js';
import RadarrStats from '$lib/components/SourceStats/RadarrStats.svelte';
import SonarrStats from '$lib/components/SourceStats/SonarrStats.svelte';
</script>