Compare commits

..

5 Commits

Author SHA1 Message Date
Gauthier
23e959acc1 fix: remove logs 2024-10-31 10:42:39 +01:00
Gauthier
2000c2ddf6 fix: add error handling for proxy creating 2024-10-31 02:08:06 +01:00
Gauthier
4a3fb5e6c8 feat: add bypass list, bypass local addresses and username/password to proxy setting
This PR adds more options to the proxy setting, like username/password authentication, bypass list
of domains and bypass local addresses. The UX is taken from *arrs.
2024-10-31 02:01:55 +01:00
Gauthier
4f14e057c7 fix: add missing merge function of default and current config 2024-10-30 22:00:16 +01:00
Gauthier
d16e399011 fix: use fs/promises for settings
This PR switches from synchronous operations with the 'fs' module to asynchronous operations with
the 'fs/promises' module. It also corrects a small error with hostname migration.
2024-10-29 16:50:24 +01:00
21 changed files with 4401 additions and 3835 deletions

View File

@@ -18,7 +18,7 @@ config/logs/*
config/*.json config/*.json
dist dist
Dockerfile* Dockerfile*
compose.yaml docker-compose.yml
docs docs
LICENSE LICENSE
node_modules node_modules

2
.gitattributes vendored
View File

@@ -40,7 +40,7 @@ docs export-ignore
.all-contributorsrc export-ignore .all-contributorsrc export-ignore
.editorconfig export-ignore .editorconfig export-ignore
Dockerfile.local export-ignore Dockerfile.local export-ignore
compose.yaml export-ignore docker-compose.yml export-ignore
stylelint.config.js export-ignore stylelint.config.js export-ignore
public/os_logo_filled.png export-ignore public/os_logo_filled.png export-ignore

View File

@@ -13,7 +13,7 @@ jobs:
name: Lint & Test Build name: Lint & Test Build
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: node:22-alpine container: node:20-alpine
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4

View File

@@ -17,7 +17,7 @@ jobs:
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 20
- name: Pnpm Setup - name: Pnpm Setup
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:

View File

@@ -20,7 +20,7 @@ jobs:
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 20
- name: Pnpm Setup - name: Pnpm Setup
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4

View File

@@ -16,7 +16,7 @@ jobs:
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 20
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx

View File

@@ -20,7 +20,7 @@ jobs:
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 20
- name: Pnpm Setup - name: Pnpm Setup
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4

View File

@@ -8,7 +8,7 @@ All help is welcome and greatly appreciated! If you would like to contribute to
- HTML/Typescript/Javascript editor - HTML/Typescript/Javascript editor
- [VSCode](https://code.visualstudio.com/) is recommended. Upon opening the project, a few extensions will be automatically recommended for install. - [VSCode](https://code.visualstudio.com/) is recommended. Upon opening the project, a few extensions will be automatically recommended for install.
- [NodeJS](https://nodejs.org/en/download/) (Node 22.x) - [NodeJS](https://nodejs.org/en/download/) (Node 20.x)
- [Pnpm](https://pnpm.io/cli/install) - [Pnpm](https://pnpm.io/cli/install)
- [Git](https://git-scm.com/downloads) - [Git](https://git-scm.com/downloads)
@@ -52,7 +52,7 @@ All help is welcome and greatly appreciated! If you would like to contribute to
pnpm dev pnpm dev
``` ```
- Alternatively, you can use [Docker](https://www.docker.com/) with `docker compose up -d`. This method does not require installing NodeJS or Yarn on your machine directly. - Alternatively, you can use [Docker](https://www.docker.com/) with `docker-compose up -d`. This method does not require installing NodeJS or Yarn on your machine directly.
5. Create your patch and test your changes. 5. Create your patch and test your changes.

View File

@@ -1,4 +1,4 @@
FROM node:22-alpine AS BUILD_IMAGE FROM node:20-alpine AS BUILD_IMAGE
WORKDIR /app WORKDIR /app
@@ -36,7 +36,7 @@ RUN touch config/DOCKER
RUN echo "{\"commitTag\": \"${COMMIT_TAG}\"}" > committag.json RUN echo "{\"commitTag\": \"${COMMIT_TAG}\"}" > committag.json
FROM node:22-alpine FROM node:20-alpine
# Metadata for Github Package Registry # Metadata for Github Package Registry
LABEL org.opencontainers.image.source="https://github.com/Fallenbagel/jellyseerr" LABEL org.opencontainers.image.source="https://github.com/Fallenbagel/jellyseerr"

View File

@@ -1,4 +1,4 @@
FROM node:22-alpine FROM node:20-alpine
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app

View File

@@ -1,3 +1,4 @@
version: '3'
services: services:
jellyseerr: jellyseerr:
build: build:

View File

@@ -190,7 +190,7 @@ Caddy will automatically obtain and renew SSL certificates for your domain.
## Traefik (v2) ## Traefik (v2)
Add the following labels to the Jellyseerr service in your `compose.yaml` file: Add the following labels to the Jellyseerr service in your `docker-compose.yml` file:
```yaml ```yaml
labels: labels:

View File

@@ -71,7 +71,7 @@ You could also use [diun](https://github.com/crazy-max/diun) to receive notifica
For details on how to use Docker Compose, please [review the official Compose documentation](https://docs.docker.com/compose/reference/). For details on how to use Docker Compose, please [review the official Compose documentation](https://docs.docker.com/compose/reference/).
#### Installation: #### Installation:
Define the `jellyseerr` service in your `compose.yaml` as follows: Define the `jellyseerr` service in your `docker-compose.yml` as follows:
```yaml ```yaml
--- ---
services: services:
@@ -94,17 +94,17 @@ If you are using emby, make sure to set the `JELLYFIN_TYPE` environment variable
Then, start all services defined in the Compose file: Then, start all services defined in the Compose file:
```bash ```bash
docker compose up -d docker-compose up -d
``` ```
#### Updating: #### Updating:
Pull the latest image: Pull the latest image:
```bash ```bash
docker compose pull jellyseerr docker-compose pull jellyseerr
``` ```
Then, restart all services defined in the Compose file: Then, restart all services defined in the Compose file:
```bash ```bash
docker compose up -d docker-compose up -d
``` ```
:::tip :::tip
You may alternatively use a third-party mechanism like [dockge](https://github.com/louislam/dockge) to manage your docker compose files. You may alternatively use a third-party mechanism like [dockge](https://github.com/louislam/dockge) to manage your docker compose files.

2
next-env.d.ts vendored
View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. // see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -168,7 +168,7 @@
"typescript": "4.9.5" "typescript": "4.9.5"
}, },
"engines": { "engines": {
"node": "^22.0.0", "node": "^20.0.0",
"pnpm": "^9.0.0" "pnpm": "^9.0.0"
}, },
"overrides": { "overrides": {

8012
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,27 +32,13 @@ class ExternalAPI {
this.fetch = fetch; this.fetch = fetch;
} }
const url = new URL(baseUrl); this.baseUrl = baseUrl;
this.params = params;
this.defaultHeaders = { this.defaultHeaders = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Accept: 'application/json', Accept: 'application/json',
...((url.username || url.password) && {
Authorization: `Basic ${Buffer.from(
`${url.username}:${url.password}`
).toString('base64')}`,
}),
...options.headers, ...options.headers,
}; };
if (url.username || url.password) {
url.username = '';
url.password = '';
baseUrl = url.toString();
}
this.baseUrl = baseUrl;
this.params = params;
this.cache = options.nodeCache; this.cache = options.nodeCache;
} }

View File

@@ -410,7 +410,7 @@ class JellyfinAPI extends ExternalAPI {
).AccessToken; ).AccessToken;
} catch (e) { } catch (e) {
logger.error( logger.error(
`Something went wrong while creating an API key from the Jellyfin server: ${e.message}`, `Something went wrong while creating an API key the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' } { label: 'Jellyfin API' }
); );

View File

@@ -27,14 +27,8 @@ const migrateApiTokens = async (settings: any): Promise<AllSettings> => {
admin.jellyfinDeviceId admin.jellyfinDeviceId
); );
jellyfinClient.setUserId(admin.jellyfinUserId ?? ''); jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
try { const apiKey = await jellyfinClient.createApiToken('Jellyseerr');
const apiKey = await jellyfinClient.createApiToken('Jellyseerr'); settings.jellyfin.apiKey = apiKey;
settings.jellyfin.apiKey = apiKey;
} catch {
throw new Error(
"Failed to create Jellyfin API token from admin account. Please check your network configuration or edit your settings.json by adding an 'apiKey' field inside of the 'jellyfin' section to fix this issue."
);
}
} }
return settings; return settings;
}; };

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-console */
import type { AllSettings } from '@server/lib/settings'; import type { AllSettings } from '@server/lib/settings';
import logger from '@server/logger'; import logger from '@server/logger';
import fs from 'fs/promises'; import fs from 'fs/promises';
@@ -44,20 +45,10 @@ export const runMigrations = async (
} }
migrated = newSettings; migrated = newSettings;
} catch (e) { } catch (e) {
// we stop jellyseerr if the migration failed logger.error(`Error while running migration '${migration}'`, {
logger.error( label: 'Settings Migrator',
`Error while running migration '${migration}': ${e.message}`, });
{ throw e;
label: 'Settings Migrator',
}
);
logger.error(
'A common cause for this error is a permission issue with your configuration folder, a network issue or a corrupted database.',
{
label: 'Settings Migrator',
}
);
process.exit();
} }
} }
@@ -81,18 +72,22 @@ export const runMigrations = async (
await fs.writeFile(BACKUP_PATH, oldBackup.toString()); await fs.writeFile(BACKUP_PATH, oldBackup.toString());
} }
} catch (e) { } catch (e) {
// we stop jellyseerr if the migration failed
logger.error( logger.error(
`Something went wrong while running settings migrations: ${e.message}`, `Something went wrong while running settings migrations: ${e.message}`,
{ { label: 'Settings Migrator' }
label: 'Settings Migrator',
}
); );
logger.error( // we stop jellyseerr if the migration failed
'A common cause for this issue is a permission error of your configuration folder.', console.log(
{ '===================================================================='
label: 'Settings Migrator', );
} console.log(
' SOMETHING WENT WRONG WHILE RUNNING SETTINGS MIGRATIONS '
);
console.log(
' Please check that your configuration folder is properly set up '
);
console.log(
'===================================================================='
); );
process.exit(); process.exit();
} }

View File

@@ -299,84 +299,54 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
where: { jellyfinUserId: account.User.Id }, where: { jellyfinUserId: account.User.Id },
}); });
const missingAdminUser = !user && !(await userRepository.count()); if (!user && !(await userRepository.count())) {
if (
missingAdminUser ||
settings.main.mediaServerType === MediaServerType.NOT_CONFIGURED
) {
// Check if user is admin on jellyfin // Check if user is admin on jellyfin
if (account.User.Policy.IsAdministrator === false) { if (account.User.Policy.IsAdministrator === false) {
throw new ApiError(403, ApiErrorCode.NotAdmin); throw new ApiError(403, ApiErrorCode.NotAdmin);
} }
if ( logger.info(
body.serverType !== MediaServerType.JELLYFIN && 'Sign-in attempt from Jellyfin user with access to the media server; creating initial admin user for Overseerr',
body.serverType !== MediaServerType.EMBY {
) { label: 'API',
throw new Error('select_server_type'); ip: req.ip,
}
settings.main.mediaServerType = body.serverType;
if (missingAdminUser) {
logger.info(
'Sign-in attempt from Jellyfin user with access to the media server; creating initial admin user for Jellyseerr',
{
label: 'API',
ip: req.ip,
jellyfinUsername: account.User.Name,
}
);
// User doesn't exist, and there are no users in the database, we'll create the user
// with admin permissions
user = new User({
id: 1,
email: body.email || account.User.Name,
jellyfinUsername: account.User.Name, jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: `/avatarproxy/${account.User.Id}`,
userType:
body.serverType === MediaServerType.JELLYFIN
? UserType.JELLYFIN
: UserType.EMBY,
});
await userRepository.save(user);
} else {
logger.info(
'Sign-in attempt from Jellyfin user with access to the media server; editing admin user for Jellyseerr',
{
label: 'API',
ip: req.ip,
jellyfinUsername: account.User.Name,
}
);
// User alread exist but settings.json is not configured, we'll edit the admin user
user = await userRepository.findOne({
where: { id: 1 },
});
if (!user) {
throw new Error('Unable to find admin user to edit');
} }
user.email = body.email || account.User.Name; );
user.jellyfinUsername = account.User.Name;
user.jellyfinUserId = account.User.Id;
user.jellyfinDeviceId = deviceId;
user.jellyfinAuthToken = account.AccessToken;
user.permissions = Permission.ADMIN;
user.avatar = `/avatarproxy/${account.User.Id}`;
user.userType =
body.serverType === MediaServerType.JELLYFIN
? UserType.JELLYFIN
: UserType.EMBY;
await userRepository.save(user); // User doesn't exist, and there are no users in the database, we'll create the user
// with admin permissions
switch (body.serverType) {
case MediaServerType.EMBY:
settings.main.mediaServerType = MediaServerType.EMBY;
user = new User({
email: body.email || account.User.Name,
jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: `/avatarproxy/${account.User.Id}`,
userType: UserType.EMBY,
});
break;
case MediaServerType.JELLYFIN:
settings.main.mediaServerType = MediaServerType.JELLYFIN;
user = new User({
email: body.email || account.User.Name,
jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: `/avatarproxy/${account.User.Id}`,
userType: UserType.JELLYFIN,
});
break;
default:
throw new Error('select_server_type');
} }
// Create an API key on Jellyfin from this admin user // Create an API key on Jellyfin from this admin user
@@ -398,6 +368,8 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
settings.jellyfin.apiKey = apiKey; settings.jellyfin.apiKey = apiKey;
await settings.save(); await settings.save();
startJobs(); startJobs();
await userRepository.save(user);
} }
// User already exists, let's update their information // User already exists, let's update their information
else if (account.User.Id === user?.jellyfinUserId) { else if (account.User.Id === user?.jellyfinUserId) {