Allow splitting config, downloads and media paths via .env
Some checks failed
Check running / run (push) Has been cancelled

Adds optional CONFIG_DIR, DOWNLOADS_DIR and MEDIA_DIR env variables so
users can spread configs, downloads and media across different drives
without restructuring ROOT_DIR. Each falls back to its ROOT_DIR-based
default when left empty, so existing setups keep working unchanged.

The -arr services (sonarr, radarr, lidarr, mylar3) now use a single
${DOWNLOADS_DIR}:/data parent bind with a ${MEDIA_DIR}:/data/media
overlay. This preserves TRaSH's single-/data-mount semantics inside
the container (hardlinks still work on one filesystem) while letting
the host-side media tree live wherever the user wants.

README documents the new variables, the generated folder structure,
recommended storage layouts, and the same-filesystem requirement for
hardlinks between downloads and media.
This commit is contained in:
2026-04-17 10:40:44 +02:00
parent d9a03a5d25
commit 92aa9b4c22
7 changed files with 292 additions and 112 deletions

41
main.py
View File

@@ -33,6 +33,16 @@ 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=' ')
def get_system_timezone():
tz_path = '/etc/localtime'
@@ -121,13 +131,35 @@ def main():
print('Where would you like to keep your files?', end=' ')
root_dir = take_directory_input()
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'
'services:\n'
)
container_config = ContainerConfig(root_dir, timezone, plex_claim=plex_claim)
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:
compose.write(getattr(container_config, service)())
@@ -137,7 +169,12 @@ def main():
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)
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)()