Added docker production setup, updated README
This commit is contained in:
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.svelte-kit
|
||||
build
|
||||
.idea
|
||||
.env
|
||||
43
Dockerfile
Normal file
43
Dockerfile
Normal 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" ]
|
||||
39
README.md
39
README.md
@@ -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
|
||||
|
||||
14
docker-compose.override.yml
Normal file
14
docker-compose.override.yml
Normal 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
12
docker-compose.prod.yml
Normal 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
9
docker-compose.yml
Normal 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 |
@@ -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",
|
||||
|
||||
@@ -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}"`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user