Compare commits
10 Commits
fe24e8e0fd
...
92aa9b4c22
| Author | SHA1 | Date | |
|---|---|---|---|
| 92aa9b4c22 | |||
|
|
d9a03a5d25 | ||
|
|
700830e37d | ||
|
|
8aaf54c572 | ||
|
|
ffede4f375 | ||
|
|
d4263027db | ||
|
|
d6433a0b84 | ||
|
|
e098e03291 | ||
|
|
db80dfd3b3 | ||
|
|
1e8220bf30 |
44
.env.sample
44
.env.sample
@@ -1,8 +1,50 @@
|
||||
# If you're not sure what timezone you should fill in, you can look at the following list:
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# Use the column that says "TZ database name".
|
||||
# To find your system's preconfigured timezone run: cat /etc/timezone
|
||||
TIMEZONE=Europe/Amsterdam
|
||||
|
||||
# The PleX claim token is used to connect your server to your account. It can be obtained at
|
||||
# https://www.plex.tv/claim/ and is valid for 4 minutes after generating the token.
|
||||
PLEX_CLAIM=
|
||||
ROOT_DIR=/
|
||||
|
||||
# Choose your root directory for keeping all your container configs and data.
|
||||
# This is used as the default base path when the more specific *_DIR variables below are left empty.
|
||||
ROOT_DIR=
|
||||
|
||||
# Optional: override individual locations independently of ROOT_DIR. Leave empty to derive them from ROOT_DIR.
|
||||
# Defaults:
|
||||
# CONFIG_DIR = ${ROOT_DIR}/config (per-service config folders)
|
||||
# DOWNLOADS_DIR = ${ROOT_DIR}/data (contains torrents/ and usenet/)
|
||||
# MEDIA_DIR = ${ROOT_DIR}/data/media (final media library: tv, movies, music, ...)
|
||||
#
|
||||
# IMPORTANT: the TRaSH guide relies on hardlinks and atomic moves between downloads and media.
|
||||
# Hardlinks only work when DOWNLOADS_DIR and MEDIA_DIR are on the SAME filesystem. If you put them
|
||||
# on different drives/mounts, Sonarr/Radarr/etc. will fall back to copying, using extra disk space
|
||||
# and time. Split these only if you understand that trade-off.
|
||||
CONFIG_DIR=
|
||||
DOWNLOADS_DIR=
|
||||
MEDIA_DIR=
|
||||
|
||||
# USER IDs for each container that requires their own user. The default values will most likely suffice, but in case one of these IDs is already in use on your system it will have to be changed. Any value should work as long as there aren't conflicts with other users.
|
||||
# If you've already set up the containers and want to change user IDs after the fact, you have to remove the old users first and re-set permissions. First run remove_old_users.sh and then the steps for manual installation.
|
||||
# If you're doing a manual installation make sure to first change the IDs in this file, and then run setup.sh to set up the directory structure and permissions before you start the docker containers.
|
||||
MEDIACENTER_GID=13000
|
||||
SONARR_UID=13001
|
||||
RADARR_UID=13002
|
||||
LIDARR_UID=13003
|
||||
# Discontinued
|
||||
READARR_UID=13004
|
||||
MYLAR_UID=13005
|
||||
AUDIOBOOKSHELF_UID=13014
|
||||
BAZARR_UID=13013
|
||||
PROWLARR_UID=13006
|
||||
QBITTORRENT_UID=13007
|
||||
JACKETT_UID=13008
|
||||
PLEX_UID=13010
|
||||
OVERSEERR_UID=13009
|
||||
SABNZBD_UID=13011
|
||||
JELLYSEERR_UID=13012
|
||||
|
||||
# The user used to run docker-compose. To find this run: id -u
|
||||
UID=1000
|
||||
|
||||
7
.github/workflows/check_running.yml
vendored
7
.github/workflows/check_running.yml
vendored
@@ -19,8 +19,11 @@ jobs:
|
||||
- name: Copy environment file
|
||||
run: cp .env.sample .env
|
||||
|
||||
- name: Copy docker-compose file
|
||||
run: cp docker-compose.yml.sample docker-compose.yml
|
||||
|
||||
- name: Run setup.sh
|
||||
run: ./setup.sh
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d
|
||||
- name: Run docker compose
|
||||
run: docker compose up -d
|
||||
|
||||
161
README.md
161
README.md
@@ -1,9 +1,10 @@
|
||||
# EZARR
|
||||
# Ezarr
|
||||
[](https://github.com/Luctia/ezarr/actions/workflows/check_running.yml)
|
||||
|
||||
Ezarr is a project built to make it EZ to deploy a Servarr mediacenter on an Ubuntu server. The
|
||||
badge above means that the shell script and docker-compose file in this repository at least *don't
|
||||
crash*. It doesn't necessarily mean it will run well on your system ;) It features:
|
||||
crash*. It doesn't necessarily mean it will run well on your system ;)
|
||||
It's set up to follow the [TRaSH guidelines](https://trash-guides.info/Hardlinks/How-to-setup-for/Docker/) so it should at least perform optimally. It features:
|
||||
- [Sonarr](https://sonarr.tv/) is an application to manage TV shows. It is capable of keeping track
|
||||
of what you'd like to watch, at what quality, in which language and more, and can find a place to
|
||||
download this if connected to Prowlarr and qBittorrent. It can also reorganize the media you
|
||||
@@ -12,18 +13,19 @@ crash*. It doesn't necessarily mean it will run well on your system ;) It featur
|
||||
- [Bazarr](https://www.bazarr.media/) is a companion application to Sonarr and Radarr that manages
|
||||
and downloads subtitles based on your requirements.
|
||||
- [Lidarr](https://lidarr.audio/) is like Sonarr, but for music.
|
||||
- [Readarr](https://readarr.com/) is like Sonarr, but for books.
|
||||
- [Mylar3](https://github.com/mylar3/mylar3) is like Sonarr, but for comic books. This one is a bit
|
||||
tricky to set up, so do so at your own risk. In order to connect this to your Prowlarr container,
|
||||
the process within Prowlarr is the same as for the other containers (add app). You'll have to add
|
||||
an API key within Mylar3, yourself.
|
||||
- [Audiobookshelf](https://www.audiobookshelf.org/) is a self-hosted audiobook and podcast server.
|
||||
- [Homarr](https://homarr.dev/) is _a sleek, modern dashboard that puts all of your apps and services at your fingertips._
|
||||
- [Prowlarr](https://wiki.servarr.com/prowlarr) can keep track of indexers, which are services that
|
||||
keep track of Torrent or UseNet links. One can search an indexer for certain content and find a
|
||||
where to download this. **Note**: when adding an indexer, please do not set the "seed ratio" to
|
||||
less than 1. Less than 1 means that you upload less than you download. Not only is this
|
||||
unfriendly towards your fellow users, but it can also get you banned from certain indexers.
|
||||
- [Jackett](https://github.com/Jackett/Jackett) is an alternative to Prowlarr.
|
||||
- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) is a proxy server to bypass Cloudflare and DDoS-GUARD protection.
|
||||
- [qBittorrent](https://www.qbittorrent.org/) can download torrents and provides a bunch more
|
||||
features for management.
|
||||
- [SABnzbd](https://sabnzbd.org/) can download nzb's
|
||||
@@ -39,35 +41,149 @@ crash*. It doesn't necessarily mean it will run well on your system ;) It featur
|
||||
tool.
|
||||
- [Jellyseerr](https://github.com/Fallenbagel/jellyseerr) is like Overseerr, but for Jellyfin.
|
||||
|
||||
## Requirements
|
||||
Currently, this script only works on Linux. There is a chance that the sample docker compose file will work on Windows,
|
||||
although untested. The only requirements other than that are **Python 3** and **docker** with **docker-compose-v2**.
|
||||
While this script _may_ work on docker-compose-v1 it's made to be and highly recommended to be run using v2.
|
||||
The easiest way to install these dependencies on Ubuntu and other Debian-based distors is by running:
|
||||
```
|
||||
sudo apt-get install python3 docker.io docker-compose-v2
|
||||
```
|
||||
For other Linux distros you may have to use a different package manager or download directly from docker's website.
|
||||
|
||||
## Using
|
||||
### Using the CLI
|
||||
To make things easier, a CLI has been developed. First, clone the repository in a directory of your
|
||||
choosing. You can run it by entering `python main.py` and the CLI will guide you through the
|
||||
process. Please take a look at [important notes](#important-notes) before you continue.
|
||||
choosing. You can run it by entering `python3 main.py` and the CLI will guide you through the
|
||||
process. It will ask which services to enable, your timezone, your root directory, and — if you
|
||||
want — independent absolute paths for your configs, downloads and media trees (press enter at each
|
||||
prompt to keep the `ROOT_DIR`-based default). See [Storage layouts](#storage-layouts) below for the
|
||||
trade-offs when splitting those.
|
||||
This is the recommended method if you're setting this up for the first time on a new system.
|
||||
Please take a look at [important notes](#important-notes) before you continue.
|
||||
**NOTE: This script will create users for each container with IDs ranging from 13001 to 13014.
|
||||
If you want to choose your own IDs (or some of them are occupied) you have to go through the manual install.**
|
||||
|
||||
### Manually
|
||||
1. To get started, clone the repository in a directory of your choosing. **Note: this will be where
|
||||
your installation and media will be as well, so think about this a bit.**
|
||||
If you're installing this for the first time simply follow these steps.
|
||||
If you're coming from an older version or reinstalling with different IDs, run `remove_old_users.sh` to clean up old users and then follow these steps.
|
||||
1. To get started, clone the repository in a directory of your choosing. `git clone https://github.com/Luctia/ezarr.git`
|
||||
2. Copy `.env.sample` to a real `.env` by running `$ cp .env.sample .env`.
|
||||
3. Set the environment variables to your liking. Note that `ROOT_DIR` should be the directory you
|
||||
have cloned this in.
|
||||
4. Run `setup.sh` as superuser. This will set up your users, a system of directories, ensure
|
||||
permissions are set correctly and sets some more environment variables for docker compose.
|
||||
5. Take a look at the `docker-compose.yml` file. If there are services you would like to ignore
|
||||
3. Set the environment variables to your liking. Pay special attention `ROOT_DIR` as this is where everything is going to be stored in.
|
||||
The path in this value needs to be **absolute**. If you leave it empty it's going to install in the directory the .env file is currently in.
|
||||
`UID` should be set to the ID of the user that you want to run docker with. You can find this by running `id -u` from that user's shell.
|
||||
If you want to spread configs, downloads and media across different locations (e.g. SSD for configs, bulk HDD for media), set `CONFIG_DIR`, `DOWNLOADS_DIR` and/or `MEDIA_DIR` to absolute paths. Any of them left empty fall back to `${ROOT_DIR}/config`, `${ROOT_DIR}/data` and `${ROOT_DIR}/data/media` respectively. **Note:** hardlinks/atomic moves between downloads and media only work when `DOWNLOADS_DIR` and `MEDIA_DIR` live on the same filesystem — split them only if you understand that you'll be copying instead of hardlinking.
|
||||
|
||||
The generated folder structure on the host looks like this (the three top-level trees can live under one `ROOT_DIR` or be spread across different drives via the `*_DIR` overrides):
|
||||
```
|
||||
${CONFIG_DIR}/ # default: ${ROOT_DIR}/config
|
||||
├── sonarr-config/
|
||||
├── radarr-config/
|
||||
├── lidarr-config/
|
||||
├── mylar-config/
|
||||
├── prowlarr-config/
|
||||
├── jackett-config/
|
||||
├── qbittorrent-config/
|
||||
├── sabnzbd-config/
|
||||
├── bazarr-config/
|
||||
├── overseerr-config/
|
||||
├── jellyseerr-config/
|
||||
├── plex-config/
|
||||
├── jellyfin-config/
|
||||
├── tautulli-config/
|
||||
└── audiobookshelf-config/
|
||||
|
||||
${DOWNLOADS_DIR}/ # default: ${ROOT_DIR}/data
|
||||
├── torrents/ # qBittorrent save path
|
||||
│ ├── tv/
|
||||
│ ├── movies/
|
||||
│ ├── music/
|
||||
│ ├── comics/
|
||||
│ └── ...
|
||||
└── usenet/ # SABnzbd complete/incomplete
|
||||
├── tv/
|
||||
├── movies/
|
||||
├── music/
|
||||
├── comics/
|
||||
└── ...
|
||||
|
||||
${MEDIA_DIR}/ # default: ${ROOT_DIR}/data/media
|
||||
├── tv/ # Sonarr
|
||||
├── movies/ # Radarr
|
||||
├── music/ # Lidarr
|
||||
├── comics/ # Mylar3
|
||||
├── books/
|
||||
├── audiobooks/ # Audiobookshelf
|
||||
├── podcasts/ # Audiobookshelf
|
||||
└── audiobookshelf-metadata/
|
||||
```
|
||||
Inside the -arr containers these are always exposed under a single `/data` tree — `/data/torrents`, `/data/usenet`, `/data/media/{tv,movies,...}` — regardless of how the host paths are split. This preserves the TRaSH single-`/data`-mount pattern and keeps hardlinks working as long as `DOWNLOADS_DIR` and `MEDIA_DIR` share a filesystem. The compose file achieves this by binding `${DOWNLOADS_DIR}:/data` as the parent and overlaying `${MEDIA_DIR}:/data/media` on top.
|
||||
4. Run `setup.sh` as superuser. This will set up your users, a system of directories and ensure permissions are set correctly.
|
||||
5. Copy `docker-compose.yml.sample` to a real `docker-compose.yml` by running `$ cp docker-compose.yml.sample docker-compose.yml`.
|
||||
6. Take a look at the `docker-compose.yml` file. If there are services you would like to ignore
|
||||
(for example, running PleX and Jellyfin at the same time is a bit unusual), you can comment them
|
||||
out by placing `#` in front of the lines. This ensures they are ignored by Docker compose.
|
||||
6. Run `docker compose up`.
|
||||
Double check that your .env file is set up properly. Also make sure to add a newly generated encryption key to the
|
||||
Homarr section, if you want to use it.
|
||||
7. Run `docker compose up -d` to start the containers. If it complains about permissions run the following commands to add your current user to the docker group and apply changes:
|
||||
```
|
||||
sudo groupadd docker
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker
|
||||
```
|
||||
If it still doesn't work reboot your system.
|
||||
|
||||
That's it! Your containers are now up and you can continue to set up the settings in them. Please
|
||||
take a look at [important notes](#important-notes) before you continue.
|
||||
|
||||
## Storage layouts
|
||||
The -arr services use **hardlinks** to move downloaded files from `torrents/` or `usenet/` into the
|
||||
media library without duplicating bytes. Hardlinks only work within a single filesystem — so
|
||||
`DOWNLOADS_DIR` and `MEDIA_DIR` must resolve to paths on the same filesystem for this to work. If
|
||||
they don't, Sonarr/Radarr/Lidarr/Mylar will silently fall back to copying, which uses twice the
|
||||
disk space and a lot more I/O on every import. `CONFIG_DIR` has no such constraint and can live
|
||||
anywhere.
|
||||
|
||||
Some layouts that work well:
|
||||
|
||||
- **Single-drive / single-filesystem (simplest).** Set `ROOT_DIR` and leave `CONFIG_DIR`,
|
||||
`DOWNLOADS_DIR`, `MEDIA_DIR` empty. Everything lives under one tree, hardlinks just work.
|
||||
- **SSD for configs, HDD for everything else.** Set `CONFIG_DIR=/ssd/ezarr/config` and leave the
|
||||
other two empty (they default under `ROOT_DIR`, which you point at the HDD). Config databases
|
||||
benefit from the faster disk; downloads + media stay together so hardlinks work.
|
||||
- **Pooled storage with [mergerfs](https://github.com/trapexit/mergerfs).** Combine multiple
|
||||
HDDs into one mount point (e.g. `/pool`) and point `ROOT_DIR` or `DOWNLOADS_DIR`+`MEDIA_DIR` at
|
||||
it. With `category.create=epmfs` and `moveonenospc=true`, mergerfs keeps a torrent and its
|
||||
hardlinked media file on the same underlying disk, so `link(2)` succeeds inside the pool.
|
||||
- **CoW filesystems (btrfs / xfs with reflink / bcachefs / ZFS).** Same-filesystem rule still
|
||||
applies, but you also get reflinks (instant, space-sharing copies) as a fallback when hardlinks
|
||||
aren't appropriate.
|
||||
|
||||
Layouts that will **break hardlinks** (they will still run, just less efficiently):
|
||||
- `DOWNLOADS_DIR` on one drive/mount and `MEDIA_DIR` on a different drive/mount.
|
||||
- Downloads on local disk, media on a remote NFS/SMB share (or vice versa).
|
||||
- Either path going through a union filesystem that doesn't co-locate related files.
|
||||
|
||||
If you genuinely need split storage and can't pool it, set "Use Hardlinks" to `false` in each -arr
|
||||
and accept the copy overhead.
|
||||
|
||||
Regardless of how you split the host paths, the compose file always exposes a single `/data` tree
|
||||
inside the -arr containers — `/data/torrents`, `/data/usenet`, `/data/media/{tv,movies,...}` — so
|
||||
the root-folder paths you configure in Sonarr/Radarr/etc. never need to change.
|
||||
|
||||
## Important notes
|
||||
- You probably shouldn't run the python script as root. Ideally you should create a brand new user that's just for these services, but any regular user will do.
|
||||
It will need your password for `sudo` to set up the permissions and folder structures, but you shouldn't run it *as* root.
|
||||
- If you already used this script previously and want to clean up old users, run `remove_old_users.sh`.
|
||||
This is also recommended if you are updating from an earlier version of this script, since there were previously some conflicts in user IDs.
|
||||
- It is recommended to restart your system after script completion, so that newly created users and groups can be loaded properly.
|
||||
- When linking one service to another, remember to use the container name instead of `localhost`.
|
||||
- Please set the settings of the -arr containers as soon as possible to the following (use
|
||||
advanced):
|
||||
- Media management:
|
||||
- Use hardlinks instead of Copy: `true`
|
||||
- Root folder: `/data/media/` and then tv, movies or music depending on service
|
||||
- qBittorrent ships with a default username `admin` and a one-time password that can be viewed by running `docker logs qbittorrent`.
|
||||
- Make sure to set a username and password for all servarr services and qBittorrent!
|
||||
- In qBittorrent, after connecting it to the -arr services, you can indicate it should move
|
||||
torrents in certain categories to certain directories, like torrents in the `radarr` category
|
||||
@@ -77,6 +193,20 @@ take a look at [important notes](#important-notes) before you continue.
|
||||
- You'll have to add indexers in Prowlarr by hand. Use Prowlarrs settings to connect it to the
|
||||
other -arr apps.
|
||||
|
||||
### IMPORTANT IF USING NFS SHARES
|
||||
- NFS shares' permissions are mapped by user IDs. If you want to access a file as a client, your user ID needs to match the user ID of the owner (or group) of that file on the NFS server.
|
||||
Note that if you are a group member (and not the owner), having matching group IDs won't be enough, there also needs to be a corresponding user on the NFS server. The easiest way to make sure
|
||||
the users and groups are set up on both sides correctly is to run `setup.sh` on both your NFS server and your client.
|
||||
On your server:
|
||||
- Copy `.env` and `setup.sh` to your NFS server.
|
||||
- You may have to adjust `.env` so that `ROOT_DIR` (and `CONFIG_DIR` / `DOWNLOADS_DIR` / `MEDIA_DIR`, if you've set any of them) reflect where each tree will be stored on your server, which is most likely different from the mapped location on the client.
|
||||
- Make sure that the `.env` file is not a .sample. Run `setup.sh`.
|
||||
- Now follow all the same steps but on your client machine. Always double-check that `.env` is set correctly, especially the path variables above.
|
||||
You don't have to do this on your server first but it's recommended. If you are running this script on the client **make sure that you temporarily enable -no-root-squash on your NFS server**,
|
||||
as the script needs superuser privileges to run and by default on NFS the root user is mapped to nowhere to prevent abuse.
|
||||
|
||||
**Heads-up on hardlinks over NFS:** if your downloads live on the local disk but media lives on an NFS mount (or vice versa), that's two different filesystems and hardlinks will break — see [Storage layouts](#storage-layouts) for the options. Keeping `DOWNLOADS_DIR` and `MEDIA_DIR` both on the NFS mount is fine, as long as they share the same share/filesystem.
|
||||
|
||||
### SABnzbd External internet access denied message
|
||||
When you're trying to access SABnzbd the first time you'll come across the message `External
|
||||
internet access denied`. To fix this simple modify the `sabnzbd.ini` and change `inet_exposure` to
|
||||
@@ -92,7 +222,10 @@ official SABnzbd website.
|
||||
## FAQ
|
||||
|
||||
### How to update containers
|
||||
If you'd like to update containers, you can move to the directory of your `docker-compose.yml` file
|
||||
There is an `update_containers.sh` script that takes care of this. Simply run it and it updates
|
||||
all containers and removes old images. If you want to keep them, simply comment out the last line of the script.
|
||||
It's essentially the following steps but automated:
|
||||
If you'd like to it manually, go to the directory of your `docker-compose.yml` file
|
||||
and run `(sudo) docker compose pull`. This pulls the newest versions of all images (blueprints for
|
||||
containers) listed in the `docker-compose.yml` file. Then, you can run `(sudo) docker compose up
|
||||
-d`. This will deploy the new versions without losing uptime. Afterwards, you can run `(sudo)
|
||||
|
||||
@@ -1,20 +1,47 @@
|
||||
import getpass
|
||||
import os
|
||||
import pwd
|
||||
|
||||
class ContainerConfig:
|
||||
def __init__(self,
|
||||
root_dir,
|
||||
timezone,
|
||||
plex_claim='',
|
||||
config_dir=None,
|
||||
downloads_dir=None,
|
||||
media_dir=None,
|
||||
):
|
||||
self.root_dir = root_dir
|
||||
self.timezone = timezone
|
||||
self.config_dir = root_dir + '/config'
|
||||
self.plex_claim = plex_claim
|
||||
self.movie_dir = root_dir + '/media/movies'
|
||||
self.tv_dir = root_dir + '/media/tv'
|
||||
self.music_dir = root_dir + '/media/music'
|
||||
self.book_dir = root_dir + '/media/books'
|
||||
self.comic_dir = root_dir + '/media/comics'
|
||||
self.torrent_dir = root_dir + '/data/torrents'
|
||||
self.usenet_dir = root_dir + '/data/usenet'
|
||||
# Allow each of config / downloads / media to be overridden independently.
|
||||
# When not provided, fall back to the ROOT_DIR-based defaults used historically.
|
||||
self.config_dir = config_dir or f'{root_dir}/config'
|
||||
self.downloads_dir = downloads_dir or f'{root_dir}/data'
|
||||
self.media_dir = media_dir or f'{root_dir}/data/media'
|
||||
self.movie_dir = f'{self.media_dir}/movies'
|
||||
self.tv_dir = f'{self.media_dir}/tv'
|
||||
self.music_dir = f'{self.media_dir}/music'
|
||||
self.book_dir = f'{self.media_dir}/books'
|
||||
self.comic_dir = f'{self.media_dir}/comics'
|
||||
self.torrent_dir = f'{self.downloads_dir}/torrents'
|
||||
self.usenet_dir = f'{self.downloads_dir}/usenet'
|
||||
self.homarr_dir = f'{root_dir}/data/homarr/appdata'
|
||||
self.UID = pwd.getpwnam(getpass.getuser()).pw_uid
|
||||
|
||||
def _arr_data_volumes(self):
|
||||
"""Volume lines for services that need the full /data tree (sonarr, radarr, lidarr, mylar3).
|
||||
|
||||
Emits a single ``${downloads_dir}:/data`` parent mount plus a ``${media_dir}:/data/media``
|
||||
overlay. The overlay is a no-op when ``media_dir`` is the default ``${downloads_dir}/media``
|
||||
(Docker just binds the same subpath) and transparently redirects ``/data/media`` when the
|
||||
user has pointed ``media_dir`` at a different location. This preserves TRaSH's single-
|
||||
``/data``-mount semantics while still allowing an independent ``MEDIA_DIR``.
|
||||
"""
|
||||
return (
|
||||
f' - {self.downloads_dir}:/data\n'
|
||||
f' - {self.media_dir}:/data/media\n'
|
||||
)
|
||||
|
||||
def plex(self):
|
||||
return (
|
||||
@@ -26,10 +53,10 @@ class ContainerConfig:
|
||||
' - PUID=13010\n'
|
||||
' - PGID=13000\n'
|
||||
' - VERSION=docker\n'
|
||||
' - PLEX_CLAIM=' + self.plex_claim + '\n'
|
||||
f' - PLEX_CLAIM={self.plex_claim}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/plex-config:/config\n'
|
||||
' - ' + self.root_dir + '/data/media:/media\n'
|
||||
f' - {self.config_dir}/plex-config:/config\n'
|
||||
f' - {self.media_dir}:/media\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
)
|
||||
|
||||
@@ -41,11 +68,11 @@ class ContainerConfig:
|
||||
' depends_on:\n'
|
||||
' - plex\n'
|
||||
' environment:\n'
|
||||
' - PUID=${UID}\n'
|
||||
f' - PUID={self.UID}\n'
|
||||
' - PGID=13000\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/tautulli-config:/config\n'
|
||||
f' - {self.config_dir}/tautulli-config:/config\n'
|
||||
' ports:\n'
|
||||
' - "8181:8181"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -57,13 +84,13 @@ class ContainerConfig:
|
||||
' image: lscr.io/linuxserver/jellyfin:latest\n'
|
||||
' container_name: jellyfin\n'
|
||||
' environment:\n'
|
||||
' - PUID=${UID}\n'
|
||||
f' - PUID={self.UID}\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/jellyfin-config:/config\n'
|
||||
' - ' + self.root_dir + '/data/media:/data\n'
|
||||
f' - {self.config_dir}/jellyfin-config:/config\n'
|
||||
f' - {self.media_dir}:/data\n'
|
||||
' ports:\n'
|
||||
' - "8096:8096"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -78,10 +105,10 @@ class ContainerConfig:
|
||||
' - PUID=13001\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/sonarr-config:/config\n'
|
||||
' - ' + self.root_dir + '/data:/data\n'
|
||||
f' - {self.config_dir}/sonarr-config:/config\n'
|
||||
f'{self._arr_data_volumes()}'
|
||||
' ports:\n'
|
||||
' - "8989:8989"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -96,10 +123,10 @@ class ContainerConfig:
|
||||
' - PUID=13002\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/radarr-config:/config\n'
|
||||
' - ' + self.root_dir + '/data:/data\n'
|
||||
f' - {self.config_dir}/radarr-config:/config\n'
|
||||
f'{self._arr_data_volumes()}'
|
||||
' ports:\n'
|
||||
' - "7878:7878"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -113,10 +140,10 @@ class ContainerConfig:
|
||||
' environment:\n'
|
||||
' - PUID=13013\n'
|
||||
' - PGID=13000\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/bazarr-config:/config\n'
|
||||
' - ' + self.root_dir + '/data/media:/media\n'
|
||||
f' - {self.config_dir}/bazarr-config:/config\n'
|
||||
f' - {self.media_dir}:/media\n'
|
||||
' ports:\n'
|
||||
' - "6767:6767"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -131,33 +158,15 @@ class ContainerConfig:
|
||||
' - PUID=13003\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/lidarr-config:/config\n'
|
||||
' - ' + self.root_dir + '/data:/data\n'
|
||||
f' - {self.config_dir}/lidarr-config:/config\n'
|
||||
f'{self._arr_data_volumes()}'
|
||||
' ports:\n'
|
||||
' - "8686:8686"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
)
|
||||
|
||||
def readarr(self):
|
||||
return (
|
||||
' readarr:\n'
|
||||
' image: lscr.io/linuxserver/readarr:develop\n'
|
||||
' container_name: readarr\n'
|
||||
' environment:\n'
|
||||
' - PUID=13004\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/readarr-config:/config\n'
|
||||
' - ' + self.root_dir + '/data:/data\n'
|
||||
' ports:\n'
|
||||
' - "8787:8787"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
)
|
||||
|
||||
def mylar3(self):
|
||||
return (
|
||||
' mylar3:\n'
|
||||
@@ -168,8 +177,8 @@ class ContainerConfig:
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/mylar-config:/config\n'
|
||||
' - ' + self.root_dir + '/data:/data\n'
|
||||
f' - {self.config_dir}/mylar-config:/config\n'
|
||||
f'{self._arr_data_volumes()}'
|
||||
' ports:\n'
|
||||
' - "8090:8090"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -181,13 +190,13 @@ class ContainerConfig:
|
||||
' image: ghcr.io/advplyr/audiobookshelf:latest\n'
|
||||
' container_name: audiobookshelf\n'
|
||||
' environment:\n'
|
||||
' - AUDIOBOOKSHELF_UID=13009\n'
|
||||
' - AUDIOBOOKSHELF_GID=13000\n'
|
||||
' - user=13014:13000\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/audiobookshelf:/config\n'
|
||||
' - ' + self.root_dir + '/data/audiobooks:/audiobooks\n'
|
||||
' - ' + self.root_dir + '/data/podcasts:/podcasts\n'
|
||||
' - ' + self.root_dir + '/data/metadata:/metadata\n'
|
||||
f' - {self.config_dir}/audiobookshelf:/config\n'
|
||||
f' - {self.media_dir}/audiobooks:/audiobooks\n'
|
||||
f' - {self.media_dir}/podcasts:/podcasts\n'
|
||||
f' - {self.media_dir}/audiobookshelf-metadata:/metadata\n'
|
||||
' ports:\n'
|
||||
' - "13378:80"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -202,14 +211,30 @@ class ContainerConfig:
|
||||
' - PUID=13006\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/prowlarr-config:/config\n'
|
||||
f' - {self.config_dir}/prowlarr-config:/config\n'
|
||||
' ports:\n'
|
||||
' - "9696:9696"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
)
|
||||
|
||||
def homarr(self):
|
||||
key = os.urandom(32).hex() # Because os.urandom(32) generates 32 random bytes, and .hex() converts each byte into two hexadecimal characters.
|
||||
return (
|
||||
' homarr:\n'
|
||||
' image: ghcr.io/homarr-labs/homarr:latest\n'
|
||||
' container_name: homarr\n'
|
||||
' environment:\n'
|
||||
f' - SECRET_ENCRYPTION_KEY={key}\n'
|
||||
' volumes:\n'
|
||||
f' - {self.homarr_dir}:/appdata\n'
|
||||
' - /var/run/docker.sock:/var/run/docker.sock\n'
|
||||
' ports:\n'
|
||||
' - "7575:7575"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
)
|
||||
|
||||
def qbittorrent(self):
|
||||
return (
|
||||
' qbittorrent:\n'
|
||||
@@ -219,11 +244,11 @@ class ContainerConfig:
|
||||
' - PUID=13007\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' - WEBUI_PORT=8080\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/qbittorrent-config:/config\n'
|
||||
' - ' + self.torrent_dir + ':/data/torrents\n'
|
||||
f' - {self.config_dir}/qbittorrent-config:/config\n'
|
||||
f' - {self.torrent_dir}:/data/torrents\n'
|
||||
' ports:\n'
|
||||
' - "8080:8080"\n'
|
||||
' - "6881:6881"\n'
|
||||
@@ -240,9 +265,9 @@ class ContainerConfig:
|
||||
' - PUID=13009\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/overseerr-config:/app/config\n'
|
||||
f' - {self.config_dir}/overseerr-config:/app/config\n'
|
||||
' ports:\n'
|
||||
' - "5055:5055"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -257,9 +282,9 @@ class ContainerConfig:
|
||||
' - PUID=13012\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/jellyseerr-config:/app/config\n'
|
||||
f' - {self.config_dir}/jellyseerr-config:/app/config\n'
|
||||
' ports:\n'
|
||||
' - "5056:5055"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
@@ -274,11 +299,44 @@ class ContainerConfig:
|
||||
' - PUID=13011\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
' - TZ=' + self.timezone + '\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
' - ' + self.config_dir + '/sabnzbd-config:/config\n'
|
||||
' - ' + self.usenet_dir + ':/downloads\n'
|
||||
f' - {self.config_dir}/sabnzbd-config:/config\n'
|
||||
f' - {self.usenet_dir}:/data/usenet\n'
|
||||
' ports:\n'
|
||||
' - "8081:8080"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
)
|
||||
|
||||
def jackett(self):
|
||||
return (
|
||||
' jackett:\n'
|
||||
' image: lscr.io/linuxserver/jackett:latest\n'
|
||||
' container_name: jackett\n'
|
||||
' environment:\n'
|
||||
' - PUID=13008\n'
|
||||
' - PGID=13000\n'
|
||||
' - UMASK=002\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' volumes:\n'
|
||||
f' - {self.config_dir}/jackett-config:/config\n'
|
||||
' ports:\n'
|
||||
' - 9117:9117\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
)
|
||||
|
||||
def flaresolverr(self):
|
||||
return (
|
||||
' flaresolverr:\n'
|
||||
' image: ghcr.io/flaresolverr/flaresolverr:latest\n'
|
||||
' container_name: flaresolverr\n'
|
||||
' environment:\n'
|
||||
' - LOG_LEVEL=${LOG_LEVEL:-info}\n'
|
||||
' - LOG_HTML=${LOG_HTML:-false}\n'
|
||||
' - CAPTCHA_SOLVER=${CAPTCHA_SOLVER:-none}\n'
|
||||
f' - TZ={self.timezone}\n'
|
||||
' ports:\n'
|
||||
' - "8191:8191"\n'
|
||||
' restart: unless-stopped\n\n'
|
||||
)
|
||||
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
---
|
||||
version: "3.1"
|
||||
services:
|
||||
sonarr:
|
||||
image: lscr.io/linuxserver/sonarr:latest
|
||||
container_name: sonarr
|
||||
environment:
|
||||
- PUID=13001
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/sonarr-config:/config
|
||||
- /home/user/ezarr/data:/data
|
||||
ports:
|
||||
- "8989:8989"
|
||||
restart: unless-stopped
|
||||
|
||||
radarr:
|
||||
image: lscr.io/linuxserver/radarr:latest
|
||||
container_name: radarr
|
||||
environment:
|
||||
- PUID=13002
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/radarr-config:/config
|
||||
- /home/user/ezarr/data:/data
|
||||
ports:
|
||||
- "7878:7878"
|
||||
restart: unless-stopped
|
||||
|
||||
bazarr:
|
||||
image: lscr.io/linuxserver/bazarr:latest
|
||||
container_name: bazarr
|
||||
environment:
|
||||
- PUID=13012
|
||||
- PGID=13000
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/bazarr-config:/config
|
||||
- /home/user/ezarr/data/media:/media
|
||||
ports:
|
||||
- "6767:6767"
|
||||
restart: unless-stopped
|
||||
|
||||
lidarr:
|
||||
image: lscr.io/linuxserver/lidarr:latest
|
||||
container_name: lidarr
|
||||
environment:
|
||||
- PUID=13003
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/lidarr-config:/config
|
||||
- /home/user/ezarr/data:/data
|
||||
ports:
|
||||
- "8686:8686"
|
||||
restart: unless-stopped
|
||||
|
||||
readarr:
|
||||
image: lscr.io/linuxserver/readarr:develop
|
||||
container_name: readarr
|
||||
environment:
|
||||
- PUID=13004
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/readarr-config:/config
|
||||
- /home/user/ezarr/data:/data
|
||||
ports:
|
||||
- "8787:8787"
|
||||
restart: unless-stopped
|
||||
|
||||
mylar3:
|
||||
image: lscr.io/linuxserver/mylar3:latest
|
||||
container_name: mylar3
|
||||
environment:
|
||||
- PUID=13005
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
volumes:
|
||||
- /home/user/ezarr/config/mylar-config:/config
|
||||
- /home/user/ezarr/data:/data
|
||||
ports:
|
||||
- "8090:8090"
|
||||
restart: unless-stopped
|
||||
|
||||
audiobookshelf:
|
||||
image: ghcr.io/advplyr/audiobookshelf:latest
|
||||
container_name: audiobookshelf
|
||||
environment:
|
||||
- AUDIOBOOKSHELF_UID=13009
|
||||
- AUDIOBOOKSHELF_GID=13000
|
||||
volumes:
|
||||
- /home/user/ezarr/config/audiobookshelf:/config
|
||||
- /home/user/ezarr/data/audiobooks:/audiobooks
|
||||
- /home/user/ezarr/data/podcasts:/podcasts
|
||||
- /home/user/ezarr/data/metadata:/metadata
|
||||
ports:
|
||||
- "13378:80"
|
||||
restart: unless-stopped
|
||||
|
||||
prowlarr:
|
||||
image: lscr.io/linuxserver/prowlarr:develop
|
||||
container_name: prowlarr
|
||||
environment:
|
||||
- PUID=13006
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/prowlarr-config:/config
|
||||
ports:
|
||||
- "9696:9696"
|
||||
restart: unless-stopped
|
||||
|
||||
plex:
|
||||
image: lscr.io/linuxserver/plex:latest
|
||||
container_name: plex
|
||||
network_mode: host
|
||||
environment:
|
||||
- PUID=13010
|
||||
- PGID=13000
|
||||
- VERSION=docker
|
||||
- PLEX_CLAIM=
|
||||
volumes:
|
||||
- /home/user/ezarr/config/plex-config:/config
|
||||
- /home/user/ezarr/data/media:/media
|
||||
restart: unless-stopped
|
||||
|
||||
tautulli:
|
||||
image: lscr.io/linuxserver/tautulli:latest
|
||||
container_name: tautulli
|
||||
depends_on:
|
||||
- plex
|
||||
environment:
|
||||
- PUID=${UID}
|
||||
- PGID=13000
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/tautulli-config:/config
|
||||
ports:
|
||||
- "8181:8181"
|
||||
restart: unless-stopped
|
||||
|
||||
jellyfin:
|
||||
image: lscr.io/linuxserver/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
environment:
|
||||
- PUID=${UID}
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/jellyfin-config:/config
|
||||
- /home/user/ezarr/data/media:/data
|
||||
ports:
|
||||
- "8096:8096"
|
||||
restart: unless-stopped
|
||||
|
||||
qbittorrent:
|
||||
image: lscr.io/linuxserver/qbittorrent:latest
|
||||
container_name: qbittorrent
|
||||
environment:
|
||||
- PUID=13007
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
- TZ=Europe/Amsterdam
|
||||
- WEBUI_PORT=8080
|
||||
volumes:
|
||||
- /home/user/ezarr/config/qbittorrent-config:/config
|
||||
- /home/user/ezarr/data/torrents:/data/torrents
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "6881:6881"
|
||||
- "6881:6881/udp"
|
||||
restart: unless-stopped
|
||||
|
||||
sabnzbd:
|
||||
image: lscr.io/linuxserver/sabnzbd:latest
|
||||
container_name: sabnzbd
|
||||
environment:
|
||||
- PUID=13011
|
||||
- PGID=13000
|
||||
- UMASK=002
|
||||
- TZ=Europe/Amsterdam
|
||||
volumes:
|
||||
- /home/user/ezarr/config/sabnzbd-config:/config
|
||||
- /home/user/ezarr/data/usenet:/data/usenet
|
||||
ports:
|
||||
- "8081:8080"
|
||||
restart: unless-stopped
|
||||
256
docker-compose.yml.sample
Normal file
256
docker-compose.yml.sample
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
name: ezarr
|
||||
services:
|
||||
sonarr:
|
||||
image: lscr.io/linuxserver/sonarr:latest
|
||||
container_name: sonarr
|
||||
environment:
|
||||
- PUID=${SONARR_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/sonarr-config:/config
|
||||
# Single /data parent mount (TRaSH-style) + MEDIA_DIR overlay at /data/media.
|
||||
# When MEDIA_DIR is left at its default, the overlay is redundant but harmless;
|
||||
# when overridden, it transparently redirects /data/media to the new location.
|
||||
- ${DOWNLOADS_DIR:-${ROOT_DIR}/data}:/data
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}:/data/media
|
||||
ports:
|
||||
- "8989:8989"
|
||||
restart: unless-stopped
|
||||
|
||||
radarr:
|
||||
image: lscr.io/linuxserver/radarr:latest
|
||||
container_name: radarr
|
||||
environment:
|
||||
- PUID=${RADARR_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/radarr-config:/config
|
||||
- ${DOWNLOADS_DIR:-${ROOT_DIR}/data}:/data
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}:/data/media
|
||||
ports:
|
||||
- "7878:7878"
|
||||
restart: unless-stopped
|
||||
|
||||
lidarr:
|
||||
image: lscr.io/linuxserver/lidarr:latest
|
||||
container_name: lidarr
|
||||
environment:
|
||||
- PUID=${LIDARR_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/lidarr-config:/config
|
||||
- ${DOWNLOADS_DIR:-${ROOT_DIR}/data}:/data
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}:/data/media
|
||||
ports:
|
||||
- "8686:8686"
|
||||
restart: unless-stopped
|
||||
|
||||
mylar3:
|
||||
image: lscr.io/linuxserver/mylar3:latest
|
||||
container_name: mylar3
|
||||
environment:
|
||||
- PUID=${MYLAR_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/mylar-config:/config
|
||||
- ${DOWNLOADS_DIR:-${ROOT_DIR}/data}:/data
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}:/data/media
|
||||
ports:
|
||||
- "8090:8090"
|
||||
restart: unless-stopped
|
||||
|
||||
audiobookshelf:
|
||||
image: ghcr.io/advplyr/audiobookshelf:latest
|
||||
container_name: audiobookshelf
|
||||
environment:
|
||||
- user=${AUDIOBOOKSHELF_UID}:${MEDIACENTER_GID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/audiobookshelf:/config
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}/audiobooks:/audiobooks
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}/podcasts:/podcasts
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}/audiobookshelf-metadata:/metadata
|
||||
ports:
|
||||
- "13378:80"
|
||||
restart: unless-stopped
|
||||
|
||||
bazarr:
|
||||
image: lscr.io/linuxserver/bazarr:latest
|
||||
container_name: bazarr
|
||||
environment:
|
||||
- PUID=${BAZARR_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/bazarr-config:/config
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}:/media
|
||||
ports:
|
||||
- "6767:6767"
|
||||
restart: unless-stopped
|
||||
|
||||
overseerr:
|
||||
image: sctx/overseerr:latest
|
||||
container_name: overseerr
|
||||
environment:
|
||||
- PUID=${OVERSEERR_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/overseerr-config:/app/config
|
||||
ports:
|
||||
- "5055:5055"
|
||||
restart: unless-stopped
|
||||
|
||||
jellyseerr:
|
||||
image: fallenbagel/jellyseerr:latest
|
||||
container_name: jellyseerr
|
||||
environment:
|
||||
- PUID=${JELLYSEERR_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/jellyseerr-config:/app/config
|
||||
ports:
|
||||
- "5056:5055"
|
||||
restart: unless-stopped
|
||||
|
||||
prowlarr:
|
||||
image: lscr.io/linuxserver/prowlarr:develop
|
||||
container_name: prowlarr
|
||||
environment:
|
||||
- PUID=${PROWLARR_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/prowlarr-config:/config
|
||||
ports:
|
||||
- "9696:9696"
|
||||
restart: unless-stopped
|
||||
|
||||
homarr:
|
||||
container_name: homarr
|
||||
image: ghcr.io/homarr-labs/homarr:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
|
||||
- ./homarr/appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=
|
||||
ports:
|
||||
- "7575:7575"
|
||||
|
||||
jackett:
|
||||
image: lscr.io/linuxserver/jackett:latest
|
||||
container_name: jackett
|
||||
environment:
|
||||
- PUID=${JACKETT_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/jackett-config:/config
|
||||
ports:
|
||||
- 9117:9117
|
||||
restart: unless-stopped
|
||||
|
||||
flaresolverr:
|
||||
image: ghcr.io/flaresolverr/flaresolverr:latest
|
||||
container_name: flaresolverr
|
||||
environment:
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
- LOG_HTML=${LOG_HTML:-false}
|
||||
- CAPTCHA_SOLVER=${CAPTCHA_SOLVER:-none}
|
||||
- TZ=${TIMEZONE}
|
||||
ports:
|
||||
- "8191:8191"
|
||||
restart: unless-stopped
|
||||
|
||||
plex:
|
||||
image: lscr.io/linuxserver/plex:latest
|
||||
container_name: plex
|
||||
network_mode: host
|
||||
environment:
|
||||
- PUID=${PLEX_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- VERSION=docker
|
||||
- PLEX_CLAIM=${PLEX_CLAIM}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/plex-config:/config
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}:/media
|
||||
restart: unless-stopped
|
||||
|
||||
tautulli:
|
||||
image: lscr.io/linuxserver/tautulli:latest
|
||||
container_name: tautulli
|
||||
# commented out because plex can also run outside of docker
|
||||
#depends_on:
|
||||
# - plex
|
||||
environment:
|
||||
- PUID=${UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/tautulli-config:/config
|
||||
ports:
|
||||
- "8181:8181"
|
||||
restart: unless-stopped
|
||||
|
||||
jellyfin:
|
||||
image: lscr.io/linuxserver/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
environment:
|
||||
- PUID=${UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/jellyfin-config:/config
|
||||
- ${MEDIA_DIR:-${ROOT_DIR}/data/media}:/data
|
||||
ports:
|
||||
- "8096:8096"
|
||||
restart: unless-stopped
|
||||
|
||||
qbittorrent:
|
||||
image: lscr.io/linuxserver/qbittorrent:latest
|
||||
container_name: qbittorrent
|
||||
environment:
|
||||
- PUID=${QBITTORRENT_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
- WEBUI_PORT=8080
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/qbittorrent-config:/config
|
||||
- ${DOWNLOADS_DIR:-${ROOT_DIR}/data}/torrents:/data/torrents
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "6881:6881"
|
||||
- "6881:6881/udp"
|
||||
restart: unless-stopped
|
||||
|
||||
sabnzbd:
|
||||
image: lscr.io/linuxserver/sabnzbd:latest
|
||||
container_name: sabnzbd
|
||||
environment:
|
||||
- PUID=${SABNZBD_UID}
|
||||
- PGID=${MEDIACENTER_GID}
|
||||
- UMASK=002
|
||||
- TZ=${TIMEZONE}
|
||||
volumes:
|
||||
- ${CONFIG_DIR:-${ROOT_DIR}/config}/sabnzbd-config:/config
|
||||
- ${DOWNLOADS_DIR:-${ROOT_DIR}/data}/usenet:/data/usenet
|
||||
ports:
|
||||
- "8081:8080"
|
||||
restart: unless-stopped
|
||||
|
||||
210
main.py
210
main.py
@@ -1,17 +1,10 @@
|
||||
import os
|
||||
from container_configs import ContainerConfig
|
||||
from users_groups_setup import UserGroupSetup
|
||||
|
||||
services_classed = dict()
|
||||
|
||||
|
||||
def take_input(service_name, service_type):
|
||||
choice = input()
|
||||
if choice == 'Y' or choice == 'y' or choice == '':
|
||||
services_classed[service_type].append(service_name)
|
||||
elif choice != 'n' and choice != 'N':
|
||||
print('Invalid input, please enter Y or N. Not adding ' + service_name + ".")
|
||||
|
||||
|
||||
def take_boolean_input(default=True):
|
||||
while True:
|
||||
ans = input()
|
||||
@@ -23,6 +16,13 @@ def take_boolean_input(default=True):
|
||||
return False
|
||||
print('Please answer with y or n.', end=' ')
|
||||
|
||||
def take_input(service_name, service_type, name_to_show=None):
|
||||
print(f'Use {name_to_show or service_name.capitalize()}? [Y/n]', end=" ")
|
||||
choice = take_boolean_input()
|
||||
if choice:
|
||||
services_classed[service_type].append(service_name)
|
||||
else:
|
||||
print('Not adding ' + service_name + ".")
|
||||
|
||||
def take_directory_input():
|
||||
while True:
|
||||
@@ -33,109 +33,161 @@ def take_directory_input():
|
||||
return ans
|
||||
print('Please make sure the path is absolute, meaning it starts at the root of your filesystem and starts with "/":', end=' ')
|
||||
|
||||
def take_optional_directory_input(default):
|
||||
"""Reads a directory override, or returns ``default`` on an empty response."""
|
||||
while True:
|
||||
ans = input()
|
||||
if ans == '':
|
||||
return default
|
||||
if ans[0] == '/':
|
||||
return ans[:-1] if ans[-1] == '/' else ans
|
||||
print(f'Please provide an absolute path (starting with "/") or press enter to use "{default}":', end=' ')
|
||||
|
||||
print('Welcome to the EZarr CLI.')
|
||||
print('This CLI will ask you which services you\'d like to use and more. If you\'d like more information about a '
|
||||
def get_system_timezone():
|
||||
tz_path = '/etc/localtime'
|
||||
|
||||
if os.path.exists(tz_path):
|
||||
if os.path.islink(tz_path):
|
||||
tz = os.readlink(tz_path)
|
||||
return tz.split('zoneinfo/')[-1]
|
||||
return None
|
||||
|
||||
def main():
|
||||
print('Welcome to the EZarr CLI.')
|
||||
print("This CLI will ask you which services you'd like to use and more. If you'd like more information about a "
|
||||
'certain service, look in the README.')
|
||||
|
||||
print('\n===SERVARR===')
|
||||
services_classed['servarr'] = []
|
||||
print('Use Sonarr? [Y/n]', end=" ")
|
||||
take_input('sonarr', 'servarr')
|
||||
print('Use Radarr? [Y/n]', end=" ")
|
||||
take_input('radarr', 'servarr')
|
||||
print('Use Lidarr? [Y/n]', end=" ")
|
||||
take_input('lidarr', 'servarr')
|
||||
print('Use Readarr? [Y/n]', end=" ")
|
||||
take_input('readarr', 'servarr')
|
||||
print('Use Mylar3? [Y/n]', end=" ")
|
||||
take_input('mylar3', 'servarr')
|
||||
print('Use Audiobookshelf? [Y/n]', end=" ")
|
||||
take_input('audiobookshelf', 'servarr')
|
||||
if len(services_classed['servarr']) == 0:
|
||||
print('\n===SERVARR===')
|
||||
services_classed['servarr'] = []
|
||||
take_input('sonarr', 'servarr')
|
||||
take_input('radarr', 'servarr')
|
||||
take_input('lidarr', 'servarr')
|
||||
take_input('mylar3', 'servarr')
|
||||
take_input('audiobookshelf', 'servarr')
|
||||
take_input('homarr', 'servarr')
|
||||
if len(services_classed['servarr']) == 0:
|
||||
print('Warning: no media management services selected.')
|
||||
if services_classed['servarr'].__contains__('sonarr') or services_classed['servarr'].__contains__('radarr'):
|
||||
print('Use Bazarr? [Y/n]', end=" ")
|
||||
if 'sonarr' in services_classed['servarr'] or 'radarr' in services_classed['servarr']:
|
||||
take_input('bazarr', 'servarr')
|
||||
|
||||
print('\n===INDEXERS===')
|
||||
services_classed['indexer'] = []
|
||||
print('Use Prowlarr? [Y/n]', end=" ")
|
||||
take_input('prowlarr', 'indexer')
|
||||
if len(services_classed['indexer']) == 0:
|
||||
print('\n===INDEXERS===')
|
||||
services_classed['indexer'] = []
|
||||
take_input('prowlarr', 'indexer')
|
||||
take_input('jackett', 'indexer')
|
||||
if len(services_classed['indexer']) == 0:
|
||||
print('Warning: no indexing service selected.')
|
||||
|
||||
print('\n===MEDIA SERVERS===')
|
||||
services_classed['ms'] = []
|
||||
print('Use PleX? [Y/n]', end=" ")
|
||||
take_input('plex', 'ms')
|
||||
if services_classed['ms'].__contains__('plex'):
|
||||
print('Use Tautulli? [Y/n]', end=" ")
|
||||
print('\n===CLOUDFLARE BYPASS===')
|
||||
services_classed['bypass'] = []
|
||||
take_input('flaresolverr', 'bypass')
|
||||
|
||||
print('\n===MEDIA SERVERS===')
|
||||
services_classed['ms'] = []
|
||||
take_input('plex', 'ms')
|
||||
if 'plex' in services_classed['ms']:
|
||||
take_input('tautulli', 'ms')
|
||||
if services_classed['servarr'].__contains__('sonarr') or services_classed['servarr'].__contains__('radarr'):
|
||||
print('Use Overseerr? [Y/n]', end=" ")
|
||||
if 'sonarr' in services_classed['servarr'] or 'radarr' in services_classed['servarr']:
|
||||
take_input('overseerr', 'servarr')
|
||||
print('Use Jellyfin? [Y/n]', end=" ")
|
||||
take_input('jellyfin', 'ms')
|
||||
if (services_classed['ms'].__contains__('jellyfin')
|
||||
and (services_classed['servarr'].__contains__('sonarr') or services_classed['servarr'].__contains__('radarr'))):
|
||||
print('Use Jellyseerr? [Y/n]', end=" ")
|
||||
take_input('jellyfin', 'ms')
|
||||
if ('jellyfin' in services_classed['ms']
|
||||
and ('sonarr' in services_classed['servarr'] or 'radarr' in services_classed['servarr'])):
|
||||
take_input('jellyseerr', 'servarr')
|
||||
if len(services_classed['ms']) == 0:
|
||||
if len(services_classed['ms']) == 0:
|
||||
print('Warning: no media servers selected.')
|
||||
|
||||
print('\n===BITTORRENT===')
|
||||
services_classed['torrent'] = []
|
||||
print('Use qBittorrent? [Y/n]', end=" ")
|
||||
take_input('qbittorrent', 'torrent')
|
||||
print('\n===BITTORRENT===')
|
||||
services_classed['torrent'] = []
|
||||
take_input('qbittorrent', 'torrent', 'qBittorrent')
|
||||
|
||||
print('\n===USENET===')
|
||||
services_classed['usenet'] = []
|
||||
print('Use SABnzbd? [Y/n]', end=" ")
|
||||
take_input('sabnzbd', 'usenet')
|
||||
print('\n===USENET===')
|
||||
services_classed['usenet'] = []
|
||||
take_input('sabnzbd', 'usenet', 'SABnzbd')
|
||||
|
||||
if len(services_classed['torrent']) == 0 and len(services_classed['usenet']) == 0:
|
||||
if len(services_classed['torrent']) == 0 and len(services_classed['usenet']) == 0:
|
||||
print('Warning: no usenet or BitTorrent clients selected.')
|
||||
|
||||
services = []
|
||||
for service_class in services_classed.keys():
|
||||
services = []
|
||||
for service_class in services_classed.keys():
|
||||
services.extend(services_classed[service_class])
|
||||
if len(services) == 0:
|
||||
if len(services) == 0:
|
||||
print('No services selected. Terminating.')
|
||||
exit(1)
|
||||
|
||||
print('\n===CONFIGURATION===')
|
||||
print('Please enter your timezone (like "Europe/Amsterdam")', end=' ')
|
||||
timezone = input()
|
||||
if len(timezone) == 0:
|
||||
print('\n===CONFIGURATION===')
|
||||
|
||||
print('Please enter your timezone (like "Europe/Amsterdam") or press enter to use your system\'s configured timezone:', end=' ')
|
||||
timezone = input()
|
||||
if timezone == '':
|
||||
timezone = get_system_timezone()
|
||||
|
||||
if len(timezone) == 0: # if user pressed enter and reading timezone from /etc/localtime failed then default to Amsterdam
|
||||
timezone = 'Europe/Amsterdam'
|
||||
plex_claim = ''
|
||||
if services.__contains__('plex'):
|
||||
|
||||
plex_claim = ''
|
||||
if 'plex' in services:
|
||||
print('If you have a PleX claim token, enter it now. Otherwise, just press enter.', end=' ')
|
||||
plex_claim = input()
|
||||
|
||||
print('Where would you like to keep your files?', end=' ')
|
||||
root_dir = take_directory_input()
|
||||
print('Where would you like to keep your files?', end=' ')
|
||||
root_dir = take_directory_input()
|
||||
|
||||
compose = open('docker-compose.yml', 'w')
|
||||
compose.write(
|
||||
default_config_dir = f'{root_dir}/config'
|
||||
default_downloads_dir = f'{root_dir}/data'
|
||||
default_media_dir = f'{root_dir}/data/media'
|
||||
|
||||
print('\nBy default all configs, downloads and media go under the root directory you just picked.')
|
||||
print('You can override any of these three with an absolute path, or press enter to accept the default.')
|
||||
print('NOTE: hardlinks/atomic moves only work when downloads and media are on the same filesystem.')
|
||||
|
||||
print(f'Config directory [{default_config_dir}]:', end=' ')
|
||||
config_dir = take_optional_directory_input(default_config_dir)
|
||||
print(f'Downloads directory [{default_downloads_dir}]:', end=' ')
|
||||
downloads_dir = take_optional_directory_input(default_downloads_dir)
|
||||
print(f'Media directory [{default_media_dir}]:', end=' ')
|
||||
media_dir = take_optional_directory_input(default_media_dir)
|
||||
|
||||
compose = open('docker-compose.yml', 'w')
|
||||
compose.write(
|
||||
'---\n'
|
||||
'version: "3.1"\n'
|
||||
'services:\n'
|
||||
)
|
||||
)
|
||||
|
||||
container_config = ContainerConfig(root_dir, timezone, plex_claim=plex_claim)
|
||||
permission_setup = UserGroupSetup(root_dir=root_dir)
|
||||
container_config = ContainerConfig(
|
||||
root_dir,
|
||||
timezone,
|
||||
plex_claim=plex_claim,
|
||||
config_dir=config_dir,
|
||||
downloads_dir=downloads_dir,
|
||||
media_dir=media_dir,
|
||||
)
|
||||
|
||||
for service in services:
|
||||
for service in services:
|
||||
compose.write(getattr(container_config, service)())
|
||||
compose.close()
|
||||
print("Docker compose file generated successfully.")
|
||||
|
||||
print("Do you want to also generate the required folder structure and permissions? (this is required for first time setup) [Y/n]: ")
|
||||
generate_permissions = take_boolean_input()
|
||||
if generate_permissions:
|
||||
permission_setup = UserGroupSetup(
|
||||
root_dir=root_dir,
|
||||
config_dir=config_dir,
|
||||
downloads_dir=downloads_dir,
|
||||
media_dir=media_dir,
|
||||
)
|
||||
for service in services:
|
||||
try:
|
||||
getattr(permission_setup, service)()
|
||||
except AttributeError:
|
||||
pass
|
||||
compose.write(getattr(container_config, service)())
|
||||
compose.close()
|
||||
else:
|
||||
print("Permission and folder structure generation skipped by user.")
|
||||
|
||||
print('Process complete. You can now run "docker compose up -d" to start your containers.')
|
||||
print('Thank you for using EZarr. If you experience any issues or have feature requests, add them to our issues.')
|
||||
print('For questions, you can also use the discussions tab.')
|
||||
exit(0)
|
||||
|
||||
print('Process complete. You can now run "docker compose up -d" to start your containers.')
|
||||
print('Thank you for using EZarr. If you experience any issues or have feature requests, add them to our issues.')
|
||||
print('For questions, you can also use the discussions tab.')
|
||||
exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
23
remove_old_users.sh
Executable file
23
remove_old_users.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
# shut down all ezarr containers referenced by docker-compose.yml
|
||||
# If you previously had ezarr installed somewhere else you have to do this manually in the directory where the docker-compose file is.
|
||||
sudo docker compose down
|
||||
|
||||
# Remove old users and group
|
||||
sudo userdel sonarr
|
||||
sudo userdel radarr
|
||||
sudo userdel lidarr
|
||||
sudo userdel readarr
|
||||
sudo userdel mylar
|
||||
sudo userdel audiobookshelf
|
||||
sudo userdel bazarr
|
||||
sudo userdel prowlarr
|
||||
sudo userdel jackett
|
||||
sudo userdel plex
|
||||
sudo userdel overseerr
|
||||
sudo userdel jellyseerr
|
||||
sudo userdel qbittorrent
|
||||
sudo userdel sabnzbd
|
||||
sudo groupdel mediacenter
|
||||
|
||||
101
setup.sh
101
setup.sh
@@ -1,24 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set up your .env file BEFORE running this script!!!
|
||||
# export all variables from .env
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
# This is always going to complain about UID being a read-only variable.
|
||||
# However that is not a problem and it's necessary for UID to be defined in the .env so that docker-compose.yml can take it.
|
||||
# Reminder to set the UID variable in .env if you haven't already. It needs to be the user you want to run docker as. Find what it should be by running "id -u" from that user's shell.
|
||||
|
||||
# Resolve location overrides. If the user didn't set CONFIG_DIR / DOWNLOADS_DIR / MEDIA_DIR in .env,
|
||||
# fall back to the ROOT_DIR-based defaults (which mirror what docker-compose.yml.sample uses).
|
||||
ROOT_DIR=${ROOT_DIR:-.}
|
||||
CONFIG_DIR=${CONFIG_DIR:-${ROOT_DIR}/config}
|
||||
DOWNLOADS_DIR=${DOWNLOADS_DIR:-${ROOT_DIR}/data}
|
||||
MEDIA_DIR=${MEDIA_DIR:-${ROOT_DIR}/data/media}
|
||||
|
||||
# NOTE on hardlinks: Sonarr/Radarr/etc. perform hardlinks and atomic moves from DOWNLOADS_DIR into
|
||||
# MEDIA_DIR. This only works if both paths sit on the SAME filesystem. If you've split them across
|
||||
# different drives or mounts, operations will silently fall back to copying.
|
||||
|
||||
# Make users and group
|
||||
sudo useradd sonarr -u 13001
|
||||
sudo useradd radarr -u 13002
|
||||
sudo useradd lidarr -u 13003
|
||||
sudo useradd readarr -u 13004
|
||||
sudo useradd mylar -u 13005
|
||||
sudo useradd prowlarr -u 13006
|
||||
sudo useradd qbittorrent -u 13007
|
||||
sudo useradd jackett -u 13008
|
||||
sudo useradd overseerr -u 13009
|
||||
sudo useradd plex -u 13010
|
||||
sudo useradd sabnzbd -u 13011
|
||||
sudo useradd jellyseerr -u 13012
|
||||
sudo useradd bazarr -u 13013
|
||||
sudo groupadd mediacenter -g 13000
|
||||
sudo useradd sonarr -u $SONARR_UID
|
||||
sudo useradd radarr -u $RADARR_UID
|
||||
sudo useradd lidarr -u $LIDARR_UID
|
||||
sudo useradd mylar -u $MYLAR_UID
|
||||
sudo useradd prowlarr -u $PROWLARR_UID
|
||||
sudo useradd qbittorrent -u $QBITTORRENT_UID
|
||||
sudo useradd jackett -u $JACKETT_UID
|
||||
sudo useradd overseerr -u $OVERSEERR_UID
|
||||
sudo useradd plex -u $PLEX_UID
|
||||
sudo useradd sabnzbd -u $SABNZBD_UID
|
||||
sudo useradd jellyseerr -u $JELLYSEERR_UID
|
||||
sudo useradd bazarr -u $BAZARR_UID
|
||||
sudo useradd audiobookshelf -u $AUDIOBOOKSHELF_UID
|
||||
sudo groupadd mediacenter -g $MEDIACENTER_GID
|
||||
|
||||
# Adds current user to the mediacenter group. This is recommended so that you can still have access to files inside the ezarr folder structure for manual control.
|
||||
# This is way better than just doing everything as root, especially on NFS shares. Also some services run as the default user anyway (Jellyfin, Tautulli).
|
||||
sudo usermod -a -G mediacenter $USER
|
||||
# When you add the user to the group the changes don't take effect immediately.
|
||||
# You can force them by running "sudo newgrp mediacenter" but that won't always work and it's better to just reboot after the script finishes running.
|
||||
|
||||
# adds all the service users to the group
|
||||
sudo usermod -a -G mediacenter sonarr
|
||||
sudo usermod -a -G mediacenter radarr
|
||||
sudo usermod -a -G mediacenter lidarr
|
||||
sudo usermod -a -G mediacenter readarr
|
||||
sudo usermod -a -G mediacenter mylar
|
||||
sudo usermod -a -G mediacenter prowlarr
|
||||
sudo usermod -a -G mediacenter qbittorrent
|
||||
@@ -28,26 +55,34 @@ sudo usermod -a -G mediacenter plex
|
||||
sudo usermod -a -G mediacenter sabnzbd
|
||||
sudo usermod -a -G mediacenter jellyseerr
|
||||
sudo usermod -a -G mediacenter bazarr
|
||||
sudo usermod -a -G mediacenter audiobookshelf
|
||||
|
||||
# Make directories
|
||||
sudo mkdir -pv config/{sonarr,radarr,lidarr,readarr,mylar,prowlarr,qbittorrent,jackett,audiobookshelf,overseerr,plex,tautulli,sabnzbd,jellyseerr,bazarr}-config
|
||||
sudo mkdir -pv data/{torrents,usenet,media}/{tv,movies,music,books,comics,audiobooks,podcasts,audiobookshelf-metadata}
|
||||
sudo mkdir -pv ${CONFIG_DIR}/{sonarr,radarr,lidarr,mylar,prowlarr,qbittorrent,jackett,audiobookshelf,overseerr,plex,jellyfin,tautulli,sabnzbd,jellyseerr,bazarr}-config
|
||||
sudo mkdir -pv ${DOWNLOADS_DIR}/{torrents,usenet}/{tv,movies,music,books,comics,audiobooks,podcasts}
|
||||
sudo mkdir -pv ${MEDIA_DIR}/{tv,movies,music,books,comics,audiobooks,podcasts,audiobookshelf-metadata}
|
||||
|
||||
# Set permissions
|
||||
sudo chmod -R 775 data/
|
||||
sudo chown -R $(id -u):mediacenter data/
|
||||
sudo chown -R sonarr:mediacenter config/sonarr-config
|
||||
sudo chown -R radarr:mediacenter config/radarr-config
|
||||
sudo chown -R lidarr:mediacenter config/lidarr-config
|
||||
sudo chown -R readarr:mediacenter config/readarr-config
|
||||
sudo chown -R mylar:mediacenter config/mylar-config
|
||||
sudo chown -R prowlarr:mediacenter config/prowlarr-config
|
||||
sudo chown -R qbittorrent:mediacenter config/qbittorrent-config
|
||||
sudo chown -R jackett:mediacenter config/jackett-config
|
||||
sudo chown -R overseerr:mediacenter config/overseerr-config
|
||||
sudo chown -R plex:mediacenter config/plex-config
|
||||
sudo chown -R sabnzbd:mediacenter config/sabnzbd-config
|
||||
sudo chown -R jellyseerr:mediacenter config/jellyseerr-config
|
||||
sudo chown -R bazarr:mediacenter config/bazarr-config
|
||||
sudo chmod -R 775 ${DOWNLOADS_DIR}/
|
||||
sudo chmod -R 775 ${MEDIA_DIR}/
|
||||
sudo chmod -R 775 ${CONFIG_DIR}/
|
||||
sudo chown -R $UID:mediacenter ${DOWNLOADS_DIR}/
|
||||
sudo chown -R $UID:mediacenter ${MEDIA_DIR}/
|
||||
sudo chown -R $UID:mediacenter ${CONFIG_DIR}/
|
||||
sudo chown -R sonarr:mediacenter ${CONFIG_DIR}/sonarr-config
|
||||
sudo chown -R radarr:mediacenter ${CONFIG_DIR}/radarr-config
|
||||
sudo chown -R lidarr:mediacenter ${CONFIG_DIR}/lidarr-config
|
||||
sudo chown -R mylar:mediacenter ${CONFIG_DIR}/mylar-config
|
||||
sudo chown -R prowlarr:mediacenter ${CONFIG_DIR}/prowlarr-config
|
||||
sudo chown -R qbittorrent:mediacenter ${CONFIG_DIR}/qbittorrent-config
|
||||
sudo chown -R jackett:mediacenter ${CONFIG_DIR}/jackett-config
|
||||
sudo chown -R overseerr:mediacenter ${CONFIG_DIR}/overseerr-config
|
||||
sudo chown -R plex:mediacenter ${CONFIG_DIR}/plex-config
|
||||
sudo chown -R $UID:mediacenter ${CONFIG_DIR}/jellyfin-config
|
||||
sudo chown -R $UID:mediacenter ${CONFIG_DIR}/tautulli-config
|
||||
sudo chown -R sabnzbd:mediacenter ${CONFIG_DIR}/sabnzbd-config
|
||||
sudo chown -R jellyseerr:mediacenter ${CONFIG_DIR}/jellyseerr-config
|
||||
sudo chown -R bazarr:mediacenter ${CONFIG_DIR}/bazarr-config
|
||||
sudo chown -R audiobookshelf:mediacenter ${CONFIG_DIR}/audiobookshelf-config
|
||||
|
||||
echo "UID=$(id -u)" >> .env
|
||||
echo "Done! It is recommended to reboot now."
|
||||
|
||||
14
update_containers.sh
Executable file
14
update_containers.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Shutds down all the containers, updates them and removes the old images.
|
||||
# If you want to keep the old images remove or comment the last line.
|
||||
|
||||
# If the following commands throw permission errors, uncomment the following lines
|
||||
#sudo groupadd docker
|
||||
#sudo usermod -aG docker $USER
|
||||
#newgrp docker
|
||||
# if that isnt enough then reboot
|
||||
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
docker image prune -f
|
||||
@@ -2,36 +2,46 @@ import os
|
||||
|
||||
|
||||
class UserGroupSetup:
|
||||
def __init__(self, root_dir='/'):
|
||||
def __init__(self, root_dir='/', config_dir=None, downloads_dir=None, media_dir=None):
|
||||
self.root_dir = root_dir
|
||||
# Individual location overrides with ROOT_DIR-based fallbacks.
|
||||
self.config_dir = config_dir or f'{root_dir}/config'
|
||||
self.downloads_dir = downloads_dir or f'{root_dir}/data'
|
||||
self.media_dir = media_dir or f'{root_dir}/data/media'
|
||||
os.system('sudo groupadd mediacenter -g 13000')
|
||||
os.system('sudo usermod -a -G mediacenter $USER')
|
||||
os.system(
|
||||
f'/bin/bash -c "sudo mkdir -pv {self.downloads_dir}/{{usenet,torrents}} -m 775'
|
||||
f' ; sudo mkdir -pv {self.media_dir} -m 775'
|
||||
f' ; sudo chown $(id -u):mediacenter {self.downloads_dir}'
|
||||
f' ; sudo chown $(id -u):mediacenter {self.downloads_dir}/{{usenet,torrents}}'
|
||||
f' ; sudo chown $(id -u):mediacenter {self.media_dir}"'
|
||||
)
|
||||
|
||||
def create_config_dir(self, service_name):
|
||||
os.system(
|
||||
f'sudo mkdir -p {self.root_dir}/config/{service_name}-config'
|
||||
f' ; sudo chown -R {service_name}:mediacenter {self.root_dir}/config/{service_name}-config'
|
||||
f' ; sudo chown $(id -u):mediacenter {self.root_dir}/config'
|
||||
f'sudo mkdir -p {self.config_dir}/{service_name}-config -m 775' # -m 775 gives read/write access to the whole mediacenter group, change to 755 for only read
|
||||
f' ; sudo chown -R {service_name}:mediacenter {self.config_dir}/{service_name}-config'
|
||||
f' ; sudo chown $(id -u):mediacenter {self.config_dir}'
|
||||
)
|
||||
|
||||
def _make_media_subdir(self, service_name, uid, media_subdir):
|
||||
"""Create matching subdirs under downloads/ and media/ and chown them to the given service user."""
|
||||
os.system(
|
||||
f'/bin/bash -c "sudo useradd {service_name} -u {uid}'
|
||||
f' ; sudo mkdir -pv {self.downloads_dir}/{{usenet,torrents}}/{media_subdir} -m 775'
|
||||
f' ; sudo mkdir -pv {self.media_dir}/{media_subdir} -m 775'
|
||||
f' ; sudo chown -R {service_name}:mediacenter {self.downloads_dir}/{{usenet,torrents}}/{media_subdir}'
|
||||
f' ; sudo chown -R {service_name}:mediacenter {self.media_dir}/{media_subdir}"'
|
||||
)
|
||||
|
||||
def sonarr(self):
|
||||
os.system(
|
||||
'/bin/bash -c "sudo useradd sonarr -u 13001'
|
||||
' ; sudo mkdir -pv ' + self.root_dir + '/data/{media,usenet,torrents}/tv -m 775'
|
||||
' ; sudo chown -R sonarr:mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}/tv'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}"'
|
||||
)
|
||||
self._make_media_subdir('sonarr', 13001, 'tv')
|
||||
self.create_config_dir('sonarr')
|
||||
os.system('sudo usermod -a -G mediacenter sonarr')
|
||||
|
||||
def radarr(self):
|
||||
os.system(
|
||||
'/bin/bash -c "sudo useradd radarr -u 13002'
|
||||
' ; sudo mkdir -pv ' + self.root_dir + '/data/{media,usenet,torrents}/movies -m 775'
|
||||
' ; sudo chown -R radarr:mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}/movies'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}"'
|
||||
)
|
||||
self._make_media_subdir('radarr', 13002, 'movies')
|
||||
self.create_config_dir('radarr')
|
||||
os.system('sudo usermod -a -G mediacenter radarr')
|
||||
|
||||
@@ -41,45 +51,20 @@ class UserGroupSetup:
|
||||
os.system('sudo usermod -a -G mediacenter bazarr')
|
||||
|
||||
def lidarr(self):
|
||||
os.system(
|
||||
'/bin/bash -c "sudo useradd lidarr -u 13003'
|
||||
' ; sudo mkdir -pv ' + self.root_dir + '/data/{media,usenet,torrents}/music -m 775'
|
||||
' ; sudo chown -R lidarr:mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}/music'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}"'
|
||||
)
|
||||
self._make_media_subdir('lidarr', 13003, 'music')
|
||||
self.create_config_dir('lidarr')
|
||||
os.system('sudo usermod -a -G mediacenter lidarr')
|
||||
|
||||
def readarr(self):
|
||||
os.system(
|
||||
'/bin/bash -c "sudo useradd readarr -u 13004'
|
||||
' ; sudo mkdir -pv ' + self.root_dir + '/data/{media,usenet,torrents}/books -m 775'
|
||||
' ; sudo chown -R readarr:mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}/books'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}"'
|
||||
)
|
||||
self.create_config_dir('readarr')
|
||||
os.system('sudo usermod -a -G mediacenter readarr')
|
||||
|
||||
def mylar3(self):
|
||||
os.system(
|
||||
'/bin/bash -c "sudo useradd mylar -u 13005'
|
||||
' ; sudo mkdir -pv ' + self.root_dir + '/data/{media,usenet,torrents}/comics -m 775'
|
||||
' ; sudo chown -R mylar:mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}/comics'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}"'
|
||||
)
|
||||
self._make_media_subdir('mylar', 13005, 'comics')
|
||||
self.create_config_dir('mylar')
|
||||
os.system('sudo usermod -a -G mediacenter mylar')
|
||||
|
||||
def audiobookshelf(self):
|
||||
os.system(
|
||||
'/bin/bash -c "sudo useradd audiobookshelf -u 13009'
|
||||
' ; sudo mkdir -pv ' + self.root_dir + '/data/{media,usenet,torrents}/audiobooks -m 775'
|
||||
' ; sudo chown -R audiobookshelf:mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}/audiobooks'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data'
|
||||
' ; sudo chown $(id -u):mediacenter ' + self.root_dir + '/data/{media,usenet,torrents}"'
|
||||
f'/bin/bash -c "sudo useradd audiobookshelf -u 13014'
|
||||
f' ; sudo mkdir -pv {self.media_dir}/{{audiobooks,podcasts,audiobookshelf-metadata}} -m 775'
|
||||
f' ; sudo chown -R audiobookshelf:mediacenter {self.media_dir}/{{audiobooks,podcasts,audiobookshelf-metadata}}"'
|
||||
)
|
||||
self.create_config_dir('audiobookshelf')
|
||||
os.system('sudo usermod -a -G mediacenter audiobookshelf')
|
||||
@@ -112,3 +97,8 @@ class UserGroupSetup:
|
||||
os.system('sudo useradd jellyseerr -u 13012')
|
||||
self.create_config_dir('jellyseerr')
|
||||
os.system('sudo usermod -a -G mediacenter jellyseerr')
|
||||
|
||||
def jackett(self):
|
||||
os.system('sudo useradd jackett -u 13008')
|
||||
self.create_config_dir('jackett')
|
||||
os.system('sudo usermod -a -G mediacenter jackett')
|
||||
|
||||
Reference in New Issue
Block a user