diff --git a/.dockerignore b/.dockerignore index de07d6bc..fbe3249e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,6 +9,7 @@ htmlcov/ coverage.xml .coverage* *.svg +frontend/node_modules/ .vscode/ .ruff_cache/ diff --git a/.gitignore b/.gitignore index fab6149b..c632bebd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ makefile .ruff_cache/ *.dat profile.svg +*.gz +*.zip # Python bytecode / Byte-compiled / optimized / DLL files __pycache__/ diff --git a/Dockerfile b/Dockerfile index 52eefbc6..e5f4ecfe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,53 +1,96 @@ +# Builder Image for Python Dependencies +FROM python:3.11-alpine AS builder +RUN apk add --no-cache build-base curl +RUN pip install --upgrade pip && pip install poetry==1.4.2 + +ENV POETRY_NO_INTERACTION=1 \ + POETRY_VIRTUALENVS_IN_PROJECT=1 \ + POETRY_VIRTUALENVS_CREATE=1 \ + POETRY_CACHE_DIR=/tmp/poetry_cache + +WORKDIR /app + +COPY pyproject.toml poetry.lock ./ +RUN touch README.md +RUN poetry install --without dev --no-root && rm -rf $POETRY_CACHE_DIR + # Frontend Builder FROM node:20-alpine AS frontend WORKDIR /app COPY frontend/package*.json ./ -RUN npm install +RUN npm install -g pnpm && pnpm install COPY frontend/ . -RUN npm run build && npm prune --production +RUN pnpm run build && pnpm prune --prod # Final Image -FROM node:20-alpine +FROM python:3.11-alpine LABEL name="Iceberg" \ description="Iceberg Debrid Downloader" \ url="https://github.com/dreulavelle/iceberg" -# Install system dependencies -RUN apk --update add --no-cache python3 curl bash shadow && \ - rm -rf /var/cache/apk/* +# Install system dependencies and Node.js +ENV PYTHONUNBUFFERED=1 +RUN apk add --no-cache \ + curl \ + fish \ + shadow \ + nodejs \ + npm \ + rclone \ + fontconfig \ + unzip && \ + npm install -g pnpm -# Install Poetry globally -ENV POETRY_HOME="/etc/poetry" -ENV PATH="$POETRY_HOME/bin:$PATH" -RUN curl -sSL https://install.python-poetry.org | python3 - --yes +# Install Nerd Fonts +RUN mkdir -p /usr/share/fonts/nerd-fonts && \ + curl -fLo "/usr/share/fonts/nerd-fonts/FiraCode.zip" \ + https://github.com/ryanoasis/nerd-fonts/releases/download/v2.1.0/FiraCode.zip && \ + unzip /usr/share/fonts/nerd-fonts/FiraCode.zip -d /usr/share/fonts/nerd-fonts && \ + rm /usr/share/fonts/nerd-fonts/FiraCode.zip && \ + fc-cache -fv -# Setup the application directory -WORKDIR /iceberg +# Install Poetry +RUN pip install poetry==1.4.2 + +# Create user and group +RUN addgroup -g 1000 iceberg && \ + adduser -u 1000 -G iceberg -h /home/iceberg -s /usr/bin/fish -D iceberg + +# Create fish config directory +RUN mkdir -p /home/iceberg/.config/fish # Expose ports -EXPOSE 3000 8080 +EXPOSE 3000 8080 5572 # Set environment variable to force color output ENV FORCE_COLOR=1 ENV TERM=xterm-256color -# Copy frontend build from the previous stage -COPY --from=frontend --chown=node:node /app/build /iceberg/frontend/build -COPY --from=frontend --chown=node:node /app/node_modules /iceberg/frontend/node_modules -COPY --from=frontend --chown=node:node /app/package.json /iceberg/frontend/package.json - -# Copy the Python project files -COPY pyproject.toml poetry.lock* /iceberg/ +# Set working directory +WORKDIR /iceberg -# Install Python dependencies -RUN poetry config virtualenvs.create false && \ - poetry install --no-dev +# Copy the virtual environment from the builder stage +COPY --from=builder /app/.venv /app/.venv +ENV VIRTUAL_ENV=/app/.venv +ENV PATH="/app/.venv/bin:$PATH" -# Copy backend code and other necessary files +# Copy the rest of the application code COPY backend/ /iceberg/backend +COPY pyproject.toml poetry.lock /iceberg/backend/ COPY VERSION entrypoint.sh /iceberg/ +# Copy frontend build from the previous stage +COPY --from=frontend --chown=iceberg:iceberg /app/build /iceberg/frontend/build +COPY --from=frontend --chown=iceberg:iceberg /app/node_modules /iceberg/frontend/node_modules +COPY --from=frontend --chown=iceberg:iceberg /app/package.json /iceberg/frontend/package.json + # Ensure entrypoint script is executable -RUN chmod +x ./entrypoint.sh +RUN chmod +x /iceberg/entrypoint.sh + +# Set correct permissions for the iceberg user +RUN chown -R iceberg:iceberg /home/iceberg/.config /iceberg + +# Switch to fish shell +SHELL ["fish", "--login"] -ENTRYPOINT ["/bin/sh", "-c", "/iceberg/entrypoint.sh"] +ENTRYPOINT ["fish", "/iceberg/entrypoint.sh"] \ No newline at end of file diff --git a/VERSION b/VERSION index b1d7abc0..a0a15177 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.2 \ No newline at end of file +0.6.3 \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index ba5d0f6c..c2734a13 100644 --- a/backend/main.py +++ b/backend/main.py @@ -67,6 +67,8 @@ def run_in_thread(self): app.program.start() app.program.run() app.program.stop() + except AttributeError as e: + logger.error(f"Program failed to initialize: {e}") except KeyboardInterrupt: app.program.stop() sys.exit(0) diff --git a/backend/program/content/overseerr.py b/backend/program/content/overseerr.py index 0f8342c3..59f2b63b 100644 --- a/backend/program/content/overseerr.py +++ b/backend/program/content/overseerr.py @@ -1,10 +1,14 @@ """Overseerr content module""" +from typing import Union from program.indexers.trakt import get_imdbid_from_tmdb from program.media.item import MediaItem from program.settings.manager import settings_manager from utils.logger import logger from utils.request import delete, get, ping, post +from requests.exceptions import RetryError, ConnectionError +from urllib3.exceptions import MaxRetryError +from urllib3.exceptions import NewConnectionError class Overseerr: @@ -39,8 +43,11 @@ def validate(self) -> bool: ) return False return response.ok - except Exception: - logger.error("Overseerr url is not reachable.") + except (ConnectionError, RetryError, MaxRetryError, NewConnectionError) as e: + logger.error("Overseerr URL is not reachable. Please check your network connection and URL settings.") + return False + except Exception as e: + logger.error("Unexpected error during Overseerr validation. Please check the logs for more details.") return False def run(self): @@ -50,9 +57,13 @@ def run(self): self.settings.url + f"/api/v1/request?take={10000}&filter=approved", additional_headers=self.headers, ) - except ConnectionError as e: - logger.error("Failed to fetch requests from overseerr: %s", str(e)) + except (ConnectionError, RetryError, MaxRetryError) as e: + logger.error(f"Failed to fetch requests from overseerr: {str(e)}") + return + except Exception as e: + logger.error(f"Unexpected error during fetching requests: {str(e)}") return + if not response.is_ok or response.data.pageInfo.results == 0: return @@ -63,15 +74,20 @@ def run(self): if item.status == 2 and item.media.status == 3 ] for item in pending_items: - mediaId: int = int(item.media.id) - if not item.media.imdbId: - imdb_id = self.get_imdb_id(item.media) - else: - imdb_id = item.media.imdbId - if not imdb_id or imdb_id in self.recurring_items: + try: + mediaId: int = int(item.media.id) + if not item.media.imdbId: + imdb_id = self.get_imdb_id(item.media) + else: + imdb_id = item.media.imdbId + if not imdb_id or imdb_id in self.recurring_items: + continue + self.recurring_items.add(imdb_id) + yield MediaItem({"imdb_id": imdb_id, "requested_by": self.key, "overseerr_id": mediaId}) + except Exception as e: + logger.error(f"Error processing item {item}: {str(e)}") + continue - self.recurring_items.add(imdb_id) - yield MediaItem({"imdb_id": imdb_id, "requested_by": self.key, "overseerr_id": mediaId}) def get_imdb_id(self, data) -> str: """Get imdbId for item from overseerr""" @@ -81,16 +97,21 @@ def get_imdb_id(self, data) -> str: else: external_id = data.tmdbId - response = get( - self.settings.url + f"/api/v1/{data.mediaType}/{external_id}?language=en", - additional_headers=self.headers, - ) + try: + response = get( + self.settings.url + f"/api/v1/{data.mediaType}/{external_id}?language=en", + additional_headers=self.headers, + ) + except (ConnectionError, RetryError, MaxRetryError) as e: + logger.error(f"Failed to fetch media details from overseerr: {str(e)}") + return + except Exception as e: + logger.error(f"Unexpected error during fetching media details: {str(e)}") + return + if not response.is_ok or not hasattr(response.data, "externalIds"): return - title = getattr(response.data, "title", None) or getattr( - response.data, "originalName" - ) imdb_id = getattr(response.data.externalIds, "imdbId", None) if imdb_id: return imdb_id @@ -100,12 +121,14 @@ def get_imdb_id(self, data) -> str: for id_attr, fetcher in alternate_ids: external_id_value = getattr(response.data.externalIds, id_attr, None) if external_id_value: - new_imdb_id = fetcher(external_id_value) - if new_imdb_id: - logger.debug( - f"Found imdbId for {title} from {id_attr}: {external_id_value}" - ) + try: + new_imdb_id: Union[str, None] = fetcher(external_id_value) + if not new_imdb_id: + continue return new_imdb_id + except Exception as e: + logger.error(f"Error fetching alternate ID: {str(e)}") + continue return @staticmethod @@ -121,9 +144,8 @@ def delete_request(mediaId: int) -> bool: logger.info(f"Deleted request {mediaId} from overseerr") return response.is_ok except Exception as e: - logger.error("Failed to delete request from overseerr ") - logger.error(e) - return False + logger.error(f"Failed to delete request from overseerr: {str(e)}") + return False @staticmethod def mark_processing(mediaId: int) -> bool: @@ -139,8 +161,7 @@ def mark_processing(mediaId: int) -> bool: logger.info(f"Marked media {mediaId} as processing in overseerr") return response.is_ok except Exception as e: - logger.error("Failed to mark media as processing in overseerr with id %s", mediaId) - logger.error(e) + logger.error(f"Failed to mark media as processing in overseerr with id {mediaId}: {str(e)}") return False @staticmethod @@ -157,8 +178,7 @@ def mark_partially_available(mediaId: int) -> bool: logger.info(f"Marked media {mediaId} as partially available in overseerr") return response.is_ok except Exception as e: - logger.error("Failed to mark media as partially available in overseerr with id %s", mediaId) - logger.error(e) + logger.error(f"Failed to mark media as partially available in overseerr with id {mediaId}: {str(e)}") return False @staticmethod @@ -175,8 +195,7 @@ def mark_completed(mediaId: int) -> bool: logger.info(f"Marked media {mediaId} as completed in overseerr") return response.is_ok except Exception as e: - logger.error("Failed to mark media as completed in overseerr with id %s", mediaId) - logger.error(e) + logger.error(f"Failed to mark media as completed in overseerr with id {mediaId}: {str(e)}") return False diff --git a/backend/program/content/trakt.py b/backend/program/content/trakt.py index c88358d6..c47982c0 100644 --- a/backend/program/content/trakt.py +++ b/backend/program/content/trakt.py @@ -2,13 +2,13 @@ import time from types import SimpleNamespace from urllib.parse import urlencode, urlparse -from utils.request import RateLimiter, post -import regex +import regex +from requests import RequestException from program.media.item import MediaItem, Movie, Show from program.settings.manager import settings_manager from utils.logger import logger -from utils.request import get +from utils.request import RateLimiter, get, post class TraktContent: @@ -33,23 +33,30 @@ def __init__(self): def validate(self) -> bool: """Validate Trakt settings.""" - if not self.settings.enabled: - logger.warning("Trakt is set to disabled.") + try: + if not self.settings.enabled: + logger.warning("Trakt is set to disabled.") + return False + if not self.settings.api_key: + logger.error("Trakt API key is not set.") + return False + response = get(f"{self.api_url}/lists/2", additional_headers=self.headers) + if not getattr(response.data, 'name', None): + logger.error("Invalid user settings received from Trakt.") + return False + return True + except ConnectionError: + logger.error("Connection error during Trakt validation.") return False - if not self.settings.api_key: - logger.error("Trakt API key is not set.") + except TimeoutError: + logger.error("Timeout error during Trakt validation.") return False - - # Simple GET request to test Trakt API key - response = get(f"{self.api_url}/lists/2", additional_headers=self.headers) - if not response.is_ok: - logger.error(f"Error connecting to Trakt: {response.status_code}") + except RequestException as e: + logger.error(f"Request exception during Trakt validation: {str(e)}") return False - - if not getattr(response.data, 'name', None): - logger.error("Invalid user settings received from Trakt.") + except Exception as e: + logger.error(f"Exception during Trakt validation: {str(e)}") return False - return True def missing(self): """Log missing items from Trakt""" diff --git a/backend/program/libraries/plex.py b/backend/program/libraries/plex.py index 3fdd042a..481b0fda 100644 --- a/backend/program/libraries/plex.py +++ b/backend/program/libraries/plex.py @@ -37,13 +37,13 @@ def _get_last_fetch_time(self, section): def validate(self): """Validate Plex library""" if not self.settings.token: - logger.error("Plex Library token is not set, this is required!") + logger.error("Plex Library token is not set. This is required!") return False if not self.settings.url: - logger.error("Plex URL is not set, this is required!") + logger.error("Plex URL is not set. This is required!") return False if not self.library_path: - logger.error("Library path is not set, this is required!") + logger.error("Library path is not set. This is required!") return False if not os.path.exists(self.library_path): logger.error("Library path does not exist!") @@ -55,18 +55,18 @@ def validate(self): return True except Unauthorized: logger.error("Plex is not authorized!") - except BadRequest as e: - logger.error(f"Plex bad request received: {str(e)}") - except MaxRetryError as e: - logger.error(f"Plex max retries exceeded: {str(e)}") - except NewConnectionError as e: - logger.error(f"Plex new connection error: {str(e)}") - except RequestsConnectionError as e: - logger.error(f"Plex requests connection error: {str(e)}") - except RequestError as e: - logger.error(f"Plex request error: {str(e)}") + except BadRequest: + logger.error("Plex is not configured correctly!") + except MaxRetryError: + logger.error("Plex max retries exceeded!") + except NewConnectionError: + logger.error("Plex new connection error!") + except RequestsConnectionError: + logger.error("Plex requests connection error!") + except RequestError: + logger.error("Plex request error!") except Exception as e: - logger.exception(f"Plex exception thrown: {str(e)}") + logger.exception(f"Plex exception thrown: {e}") return False def run(self): @@ -218,3 +218,4 @@ def _map_item_from_data(item): # Specials may end up here.. logger.error(f"Unknown Item: {item.title} with type {item.type}") return None + diff --git a/backend/program/media/item.py b/backend/program/media/item.py index 8b7e2081..50545ea2 100644 --- a/backend/program/media/item.py +++ b/backend/program/media/item.py @@ -3,6 +3,7 @@ from typing import List, Optional, Self from program.media.state import States +from RTN import Torrent from RTN.patterns import extract_episodes from utils.logger import logger @@ -24,51 +25,48 @@ def __hash__(self): class MediaItem: """MediaItem class""" - def __init__(self, item): - self.requested_at = item.get("requested_at", None) or datetime.now() - self.requested_by = item.get("requested_by", None) + def __init__(self, item: dict) -> None: + self.requested_at: Optional[datetime] = item.get("requested_at", datetime.now()) + self.requested_by: Optional[str] = item.get("requested_by", None) - self.indexed_at = None + self.indexed_at: Optional[datetime] = None - self.scraped_at = None - self.scraped_times = 0 - self.active_stream = item.get("active_stream", {}) - self.streams = {} + self.scraped_at: Optional[datetime] = None + self.scraped_times: Optional[int] = 0 + self.active_stream: Optional[dict[str, str]] = item.get("active_stream", {}) + self.streams: Optional[dict[str, Torrent]] = {} - self.symlinked = False - self.symlinked_at = None - self.symlinked_times = 0 + self.symlinked: Optional[bool] = False + self.symlinked_at: Optional[datetime] = None + self.symlinked_times: Optional[int] = 0 - self.file = None - self.folder = None - self.is_anime = item.get("is_anime", False) - self.parsed_data = item.get("parsed_data", []) - self.parent = None + self.file: Optional[str] = None + self.folder: Optional[str] = None + self.is_anime: Optional[bool] = item.get("is_anime", False) + self.parent: Optional[Self] = None # Media related - self.title = item.get("title", None) - self.imdb_id = item.get("imdb_id", None) + self.title: Optional[str] = item.get("title", None) + self.imdb_id: Optional[str] = item.get("imdb_id", None) if self.imdb_id: - self.imdb_link = f"https://www.imdb.com/title/{self.imdb_id}/" + self.imdb_link: Optional[str] = f"https://www.imdb.com/title/{self.imdb_id}/" if not hasattr(self, "item_id"): - self.item_id = ItemId(self.imdb_id) - self.tvdb_id = item.get("tvdb_id", None) - self.tmdb_id = item.get("tmdb_id", None) - self.network = item.get("network", None) - self.country = item.get("country", None) - self.language = item.get("language", None) - self.aired_at = item.get("aired_at", None) - self.genres = item.get("genres", []) + self.item_id: ItemId = ItemId(self.imdb_id) + self.tvdb_id: Optional[str] = item.get("tvdb_id", None) + self.tmdb_id: Optional[str] = item.get("tmdb_id", None) + self.network: Optional[str] = item.get("network", None) + self.country: Optional[str] = item.get("country", None) + self.language: Optional[str] = item.get("language", None) + self.aired_at: Optional[datetime] = item.get("aired_at", None) + self.genres: Optional[List[str]] = item.get("genres", []) # Plex related - # TODO: This might be bugged? I wonder if movies that are in collections, - # have a key from the collection.. needs testing. - self.key = item.get("key", None) - self.guid = item.get("guid", None) - self.update_folder = item.get("update_folder", None) + self.key: Optional[str] = item.get("key", None) + self.guid: Optional[str] = item.get("guid", None) + self.update_folder: Optional[str] = item.get("update_folder", None) # Overseerr related - self.overseerr_id = item.get("overseerr_id", None) + self.overseerr_id: Optional[int] = item.get("overseerr_id", None) @property def is_released(self) -> bool: @@ -188,8 +186,6 @@ def to_extended_dict(self, abbreviated_children=False): dict["symlinked_times"] = ( self.symlinked_times if hasattr(self, "symlinked_times") else None ) - - dict["parsed_data"] = self.parsed_data if hasattr(self, "parsed_data") else None dict["is_anime"] = self.is_anime if hasattr(self, "is_anime") else None dict["update_folder"] = ( self.update_folder if hasattr(self, "update_folder") else None diff --git a/backend/program/scrapers/torbox.py b/backend/program/scrapers/torbox.py index a260da25..8ca151e5 100644 --- a/backend/program/scrapers/torbox.py +++ b/backend/program/scrapers/torbox.py @@ -4,7 +4,7 @@ from program.settings.manager import settings_manager from program.settings.versions import models from requests import RequestException -from requests.exceptions import RetryError, ReadTimeout, ConnectTimeout +from requests.exceptions import ConnectTimeout, ReadTimeout, RetryError from RTN import RTN, Torrent, sort_torrents from RTN.exceptions import GarbageTorrent from utils.logger import logger diff --git a/backend/program/state_transition.py b/backend/program/state_transition.py index 53f4f5fc..b458e908 100644 --- a/backend/program/state_transition.py +++ b/backend/program/state_transition.py @@ -1,5 +1,5 @@ -from program.content.trakt import TraktContent from program.content import Listrr, Mdblist, Overseerr, PlexWatchlist +from program.content.trakt import TraktContent from program.downloaders.realdebrid import Debrid from program.downloaders.torbox import TorBoxDownloader from program.indexers.trakt import TraktIndexer diff --git a/backend/program/symlink.py b/backend/program/symlink.py index 83375353..94031ffd 100644 --- a/backend/program/symlink.py +++ b/backend/program/symlink.py @@ -8,6 +8,7 @@ from utils.logger import logger from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer + from .cache import hash_cache diff --git a/backend/program/updaters/plex.py b/backend/program/updaters/plex.py index 48e12fd3..3bccf8d1 100644 --- a/backend/program/updaters/plex.py +++ b/backend/program/updaters/plex.py @@ -48,16 +48,16 @@ def validate(self): # noqa: C901 return True except Unauthorized: logger.error("Plex is not authorized!") - except BadRequest as e: - logger.error(f"Plex is not configured correctly: {e}") - except MaxRetryError as e: - logger.error(f"Plex max retries exceeded: {e}") - except NewConnectionError as e: - logger.error(f"Plex new connection error: {e}") - except RequestsConnectionError as e: - logger.error(f"Plex requests connection error: {e}") - except RequestError as e: - logger.error(f"Plex request error: {e}") + except BadRequest: + logger.error("Plex is not configured correctly!") + except MaxRetryError: + logger.error("Plex max retries exceeded!") + except NewConnectionError: + logger.error("Plex new connection error!") + except RequestsConnectionError: + logger.error("Plex requests connection error!") + except RequestError: + logger.error("Plex request error!") except Exception as e: logger.exception(f"Plex exception thrown: {e}") return False diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 00000000..b96f59c8 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,17 @@ +services: + iceberg: + build: + context: . + dockerfile: Dockerfile + image: iceberg:dev + container_name: iceberg + network_mode: host + tty: true + environment: + - PUID=1000 + - PGID=1000 + - ORIGIN=http://localhost:3000 + - TZ=America/New_York + volumes: + - ./data:/iceberg/data + - /mnt:/mnt diff --git a/entrypoint.sh b/entrypoint.sh index f69e6871..5d97d470 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,39 +1,61 @@ -#!/bin/bash -echo "Starting Container with ${PUID:-1000}:${PGID:-1000} permissions..." +#!/usr/bin/fish +set PUID (set -q PUID; and echo $PUID; or echo 1000) +set PGID (set -q PGID; and echo $PGID; or echo 1000) -if ! [ "$PUID" -eq "$PUID" ] 2> /dev/null; then +echo "Starting Container with $PUID:$PGID permissions..." + +if not echo $PUID | grep -qE '^[0-9]+$' echo "PUID is not a valid integer. Exiting..." exit 1 -fi +end -if ! [ "$PGID" -eq "$PGID" ] 2> /dev/null; then +if not echo $PGID | grep -qE '^[0-9]+$' echo "PGID is not a valid integer. Exiting..." exit 1 -fi +end -: ${USERNAME:=iceberg} -: ${GROUPNAME:=iceberg} +set -q USERNAME; or set USERNAME iceberg +set -q GROUPNAME; or set GROUPNAME iceberg -if ! getent group ${PGID} >/dev/null; then +if not getent group $PGID > /dev/null addgroup -g $PGID $GROUPNAME > /dev/null else - GROUPNAME=$(getent group ${PGID} | cut -d: -f1) -fi + set GROUPNAME (getent group $PGID | cut -d: -f1) +end -if ! getent passwd ${PUID} >/dev/null; then +if not getent passwd $PUID > /dev/null adduser -D -u $PUID -G $GROUPNAME $USERNAME > /dev/null else - USERNAME=$(getent passwd ${PUID} | cut -d: -f1) -fi - -USER_HOME="/home/${USERNAME}" -mkdir -p ${USER_HOME} -chown ${USERNAME}:${GROUPNAME} ${USER_HOME} -chown -R ${USERNAME}:${GROUPNAME} /iceberg -export XDG_CONFIG_HOME="${USER_HOME}/.config" -export POETRY_CACHE_DIR="${USER_HOME}/.cache/pypoetry" + set USERNAME (getent passwd $PUID | cut -d: -f1) +end + +set USER_HOME "/home/$USERNAME" +mkdir -p $USER_HOME +chown $USERNAME:$GROUPNAME $USER_HOME +chown -R $USERNAME:$GROUPNAME /iceberg +set -x XDG_CONFIG_HOME "$USER_HOME/.config" +set -x XDG_DATA_HOME "$USER_HOME/.local/share" +set -x POETRY_CACHE_DIR "$USER_HOME/.cache/pypoetry" +set -x HOME $USER_HOME + +# Ensure poetry is in the PATH +set -x PATH $PATH /app/.venv/bin + su -m $USERNAME -c "poetry config virtualenvs.create false" -ORIGIN=${ORIGIN:-http://localhost:3000} +set -q ORIGIN; or set ORIGIN "http://localhost:3000" echo "Container Initialization complete." -exec su -m $USERNAME -c "cd /iceberg/backend && poetry run python3 main.py & ORIGIN=$ORIGIN node /iceberg/frontend/build" + +# Start rclone in the background +# echo "Starting rclone..." +# rclone rcd --rc-web-gui --rc-addr 0.0.0.0:5572 --rc-no-auth --log-level ERROR &> /dev/null & +# set rclone_pid (jobs -p %1) + +# Start the backend +echo "Starting backend..." +su -m $USERNAME -c "fish -c 'cd /iceberg/backend; and poetry run python3 main.py'" & +set backend_pid (jobs -p %1) + +# Start the frontend +echo "Starting frontend..." +exec su -m $USERNAME -c "fish -c 'ORIGIN=$ORIGIN node /iceberg/frontend/build'" \ No newline at end of file diff --git a/makefile b/makefile index d5ea80dc..b66584b0 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,4 @@ -.PHONY: help install run start stop logs clean format lint test pr-ready +.PHONY: help install run start start-dev stop restart logs logs-dev shell build push clean format check lint sort test coverage pr-ready # Detect operating system ifeq ($(OS),Windows_NT) @@ -10,36 +10,61 @@ else endif help: - @echo Iceberg Local Development Environment - @echo ------------------------------------------------------------------------- - @echo install : Install the required packages - @echo run : Run the Iceberg backend - @echo start : Build and run the Iceberg container \(requires Docker\) - @echo stop : Stop and remove the Iceberg container \(requires Docker\) - @echo logs : Show the logs of the Iceberg container \(requires Docker\) - @echo clean : Remove all the temporary files - @echo format : Format the code using isort - @echo check : Check the code using pyright - @echo lint : Lint the code using ruff and isort - @echo test : Run the tests using pytest - @echo coverage : Run the tests and generate coverage report - @echo pr-ready : Run the linter and tests - @echo ------------------------------------------------------------------------- - + @echo "Iceberg Local Development Environment" + @echo "-------------------------------------------------------------------------" + @echo "install : Install the required packages" + @echo "run : Run the Iceberg backend" + @echo "start : Build and run the Iceberg container (requires Docker)" + @echo "start-dev : Build and run the Iceberg container in development mode (requires Docker)" + @echo "stop : Stop and remove the Iceberg container (requires Docker)" + @echo "logs : Show the logs of the Iceberg container (requires Docker)" + @echo "logs-dev : Show the logs of the Iceberg container in development mode (requires Docker)" + @echo "clean : Remove all the temporary files" + @echo "format : Format the code using isort" + @echo "lint : Lint the code using ruff and isort" + @echo "test : Run the tests using pytest" + @echo "coverage : Run the tests and generate coverage report" + @echo "pr-ready : Run the linter and tests" + @echo "-------------------------------------------------------------------------" # Docker related commands start: stop - @docker build -t iceberg:latest -f Dockerfile . - @docker run -d --name iceberg --hostname iceberg --net host -e PUID=1000 -e PGID=1000 -v $(DATA_PATH):/iceberg/data -v /mnt:/mnt iceberg:latest - @docker logs iceberg -f + @docker compose -f docker-compose.yml up --build -d --force-recreate --remove-orphans + @docker compose -f docker-compose.yml logs -f + +start-dev: stop + @docker compose -f docker-compose-dev.yml up --build -d --force-recreate --remove-orphans + @docker compose -f docker-compose-dev.yml logs -f stop: - @-docker stop iceberg --time 0 - @-docker rm iceberg --force - @-docker rmi iceberg:latest --force + @docker compose -f docker-compose.yml down + @docker compose -f docker-compose-dev.yml down + +restart: + @docker restart iceberg + @docker logs -f iceberg logs: - @docker logs iceberg -f + @docker logs -f iceberg + +logs-dev: + @docker compose -f docker-compose-dev.yml logs -f + +shell: + @docker exec -it iceberg fish + +build: + @docker build -t iceberg . + +push: build + @echo $(DOCKER_PASSWORD) | docker login -u spoked --password-stdin + @docker tag iceberg:latest spoked/iceberg:latest + @docker push spoked/iceberg:latest + @docker logout || true + +tidy: + @docker rmi $(docker images | awk '$1 == "" || $1 == "iceberg" {print $3}') -f + # Poetry related commands diff --git a/poetry.lock b/poetry.lock index 71d7e69c..2c695344 100644 --- a/poetry.lock +++ b/poetry.lock @@ -91,13 +91,13 @@ files = [ [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] @@ -933,18 +933,18 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "pydantic" -version = "2.7.2" +version = "2.7.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.2-py3-none-any.whl", hash = "sha256:834ab954175f94e6e68258537dc49402c4a5e9d0409b9f1b86b7e934a8372de7"}, - {file = "pydantic-2.7.2.tar.gz", hash = "sha256:71b2945998f9c9b7919a45bde9a50397b289937d215ae141c1d0903ba7149fd7"}, + {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, + {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.18.3" +pydantic-core = "2.18.4" typing-extensions = ">=4.6.1" [package.extras] @@ -952,90 +952,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.18.3" +version = "2.18.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:744697428fcdec6be5670460b578161d1ffe34743a5c15656be7ea82b008197c"}, - {file = "pydantic_core-2.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b40c05ced1ba4218b14986fe6f283d22e1ae2ff4c8e28881a70fb81fbfcda7"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a9a75622357076efb6b311983ff190fbfb3c12fc3a853122b34d3d358126c"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2e253af04ceaebde8eb201eb3f3e3e7e390f2d275a88300d6a1959d710539e2"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855ec66589c68aa367d989da5c4755bb74ee92ccad4fdb6af942c3612c067e34"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3e42bb54e7e9d72c13ce112e02eb1b3b55681ee948d748842171201a03a98a"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6ac9ffccc9d2e69d9fba841441d4259cb668ac180e51b30d3632cd7abca2b9b"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c56eca1686539fa0c9bda992e7bd6a37583f20083c37590413381acfc5f192d6"}, - {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:17954d784bf8abfc0ec2a633108207ebc4fa2df1a0e4c0c3ccbaa9bb01d2c426"}, - {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98ed737567d8f2ecd54f7c8d4f8572ca7c7921ede93a2e52939416170d357812"}, - {file = "pydantic_core-2.18.3-cp310-none-win32.whl", hash = "sha256:9f9e04afebd3ed8c15d67a564ed0a34b54e52136c6d40d14c5547b238390e779"}, - {file = "pydantic_core-2.18.3-cp310-none-win_amd64.whl", hash = "sha256:45e4ffbae34f7ae30d0047697e724e534a7ec0a82ef9994b7913a412c21462a0"}, - {file = "pydantic_core-2.18.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b9ebe8231726c49518b16b237b9fe0d7d361dd221302af511a83d4ada01183ab"}, - {file = "pydantic_core-2.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b8e20e15d18bf7dbb453be78a2d858f946f5cdf06c5072453dace00ab652e2b2"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0d9ff283cd3459fa0bf9b0256a2b6f01ac1ff9ffb034e24457b9035f75587cb"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f7ef5f0ebb77ba24c9970da18b771711edc5feaf00c10b18461e0f5f5949231"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73038d66614d2e5cde30435b5afdced2b473b4c77d4ca3a8624dd3e41a9c19be"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6afd5c867a74c4d314c557b5ea9520183fadfbd1df4c2d6e09fd0d990ce412cd"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd7df92f28d351bb9f12470f4c533cf03d1b52ec5a6e5c58c65b183055a60106"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80aea0ffeb1049336043d07799eace1c9602519fb3192916ff525b0287b2b1e4"}, - {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaee40f25bba38132e655ffa3d1998a6d576ba7cf81deff8bfa189fb43fd2bbe"}, - {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9128089da8f4fe73f7a91973895ebf2502539d627891a14034e45fb9e707e26d"}, - {file = "pydantic_core-2.18.3-cp311-none-win32.whl", hash = "sha256:fec02527e1e03257aa25b1a4dcbe697b40a22f1229f5d026503e8b7ff6d2eda7"}, - {file = "pydantic_core-2.18.3-cp311-none-win_amd64.whl", hash = "sha256:58ff8631dbab6c7c982e6425da8347108449321f61fe427c52ddfadd66642af7"}, - {file = "pydantic_core-2.18.3-cp311-none-win_arm64.whl", hash = "sha256:3fc1c7f67f34c6c2ef9c213e0f2a351797cda98249d9ca56a70ce4ebcaba45f4"}, - {file = "pydantic_core-2.18.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f0928cde2ae416a2d1ebe6dee324709c6f73e93494d8c7aea92df99aab1fc40f"}, - {file = "pydantic_core-2.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bee9bb305a562f8b9271855afb6ce00223f545de3d68560b3c1649c7c5295e9"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e862823be114387257dacbfa7d78547165a85d7add33b446ca4f4fae92c7ff5c"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a36f78674cbddc165abab0df961b5f96b14461d05feec5e1f78da58808b97e7"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba905d184f62e7ddbb7a5a751d8a5c805463511c7b08d1aca4a3e8c11f2e5048"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fdd362f6a586e681ff86550b2379e532fee63c52def1c666887956748eaa326"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b214b7ee3bd3b865e963dbed0f8bc5375f49449d70e8d407b567af3222aae4"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691018785779766127f531674fa82bb368df5b36b461622b12e176c18e119022"}, - {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60e4c625e6f7155d7d0dcac151edf5858102bc61bf959d04469ca6ee4e8381bd"}, - {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4e651e47d981c1b701dcc74ab8fec5a60a5b004650416b4abbef13db23bc7be"}, - {file = "pydantic_core-2.18.3-cp312-none-win32.whl", hash = "sha256:ffecbb5edb7f5ffae13599aec33b735e9e4c7676ca1633c60f2c606beb17efc5"}, - {file = "pydantic_core-2.18.3-cp312-none-win_amd64.whl", hash = "sha256:2c8333f6e934733483c7eddffdb094c143b9463d2af7e6bd85ebcb2d4a1b82c6"}, - {file = "pydantic_core-2.18.3-cp312-none-win_arm64.whl", hash = "sha256:7a20dded653e516a4655f4c98e97ccafb13753987434fe7cf044aa25f5b7d417"}, - {file = "pydantic_core-2.18.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:eecf63195be644b0396f972c82598cd15693550f0ff236dcf7ab92e2eb6d3522"}, - {file = "pydantic_core-2.18.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c44efdd3b6125419c28821590d7ec891c9cb0dff33a7a78d9d5c8b6f66b9702"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e59fca51ffbdd1638b3856779342ed69bcecb8484c1d4b8bdb237d0eb5a45e2"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70cf099197d6b98953468461d753563b28e73cf1eade2ffe069675d2657ed1d5"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63081a49dddc6124754b32a3774331467bfc3d2bd5ff8f10df36a95602560361"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:370059b7883485c9edb9655355ff46d912f4b03b009d929220d9294c7fd9fd60"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a64faeedfd8254f05f5cf6fc755023a7e1606af3959cfc1a9285744cc711044"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19d2e725de0f90d8671f89e420d36c3dd97639b98145e42fcc0e1f6d492a46dc"}, - {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:67bc078025d70ec5aefe6200ef094576c9d86bd36982df1301c758a9fff7d7f4"}, - {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:adf952c3f4100e203cbaf8e0c907c835d3e28f9041474e52b651761dc248a3c0"}, - {file = "pydantic_core-2.18.3-cp38-none-win32.whl", hash = "sha256:9a46795b1f3beb167eaee91736d5d17ac3a994bf2215a996aed825a45f897558"}, - {file = "pydantic_core-2.18.3-cp38-none-win_amd64.whl", hash = "sha256:200ad4e3133cb99ed82342a101a5abf3d924722e71cd581cc113fe828f727fbc"}, - {file = "pydantic_core-2.18.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:304378b7bf92206036c8ddd83a2ba7b7d1a5b425acafff637172a3aa72ad7083"}, - {file = "pydantic_core-2.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c826870b277143e701c9ccf34ebc33ddb4d072612683a044e7cce2d52f6c3fef"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e201935d282707394f3668380e41ccf25b5794d1b131cdd96b07f615a33ca4b1"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5560dda746c44b48bf82b3d191d74fe8efc5686a9ef18e69bdabccbbb9ad9442"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b32c2a1f8032570842257e4c19288eba9a2bba4712af542327de9a1204faff8"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:929c24e9dea3990bc8bcd27c5f2d3916c0c86f5511d2caa69e0d5290115344a9"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a8376fef60790152564b0eab376b3e23dd6e54f29d84aad46f7b264ecca943"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dccf3ef1400390ddd1fb55bf0632209d39140552d068ee5ac45553b556780e06"}, - {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41dbdcb0c7252b58fa931fec47937edb422c9cb22528f41cb8963665c372caf6"}, - {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:666e45cf071669fde468886654742fa10b0e74cd0fa0430a46ba6056b24fb0af"}, - {file = "pydantic_core-2.18.3-cp39-none-win32.whl", hash = "sha256:f9c08cabff68704a1b4667d33f534d544b8a07b8e5d039c37067fceb18789e78"}, - {file = "pydantic_core-2.18.3-cp39-none-win_amd64.whl", hash = "sha256:4afa5f5973e8572b5c0dcb4e2d4fda7890e7cd63329bd5cc3263a25c92ef0026"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:77319771a026f7c7d29c6ebc623de889e9563b7087911b46fd06c044a12aa5e9"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:df11fa992e9f576473038510d66dd305bcd51d7dd508c163a8c8fe148454e059"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d531076bdfb65af593326ffd567e6ab3da145020dafb9187a1d131064a55f97c"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33ce258e4e6e6038f2b9e8b8a631d17d017567db43483314993b3ca345dcbbb"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1f9cd7f5635b719939019be9bda47ecb56e165e51dd26c9a217a433e3d0d59a9"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cd4a032bb65cc132cae1fe3e52877daecc2097965cd3914e44fbd12b00dae7c5"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f2718430098bcdf60402136c845e4126a189959d103900ebabb6774a5d9fdb"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c0037a92cf0c580ed14e10953cdd26528e8796307bb8bb312dc65f71547df04d"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b95a0972fac2b1ff3c94629fc9081b16371dad870959f1408cc33b2f78ad347a"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a62e437d687cc148381bdd5f51e3e81f5b20a735c55f690c5be94e05da2b0d5c"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b367a73a414bbb08507da102dc2cde0fa7afe57d09b3240ce82a16d608a7679c"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ecce4b2360aa3f008da3327d652e74a0e743908eac306198b47e1c58b03dd2b"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4435b8d83f0c9561a2a9585b1de78f1abb17cb0cef5f39bf6a4b47d19bafe3"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:616221a6d473c5b9aa83fa8982745441f6a4a62a66436be9445c65f241b86c94"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7e6382ce89a92bc1d0c0c5edd51e931432202b9080dc921d8d003e616402efd1"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff58f379345603d940e461eae474b6bbb6dab66ed9a851ecd3cb3709bf4dcf6a"}, - {file = "pydantic_core-2.18.3.tar.gz", hash = "sha256:432e999088d85c8f36b9a3f769a8e2b57aabd817bbb729a90d1fe7f18f6f1f39"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, + {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, + {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, + {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, + {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, + {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, + {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, + {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, + {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, + {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, + {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, + {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, + {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, + {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 3ac46d36..99bc8195 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,6 @@ description = "Plex torrent streaming through Real Debrid and 3rd party services authors = ["Iceberg Developers"] license = "GPL-3.0" readme = "README.md" -package-mode = false [tool.poetry.dependencies] python = "^3.11"