From 0a909c832f20976a67bf9c3806dddfb518ecee21 Mon Sep 17 00:00:00 2001 From: Spoked <5782630+dreulavelle@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:56:14 -0500 Subject: [PATCH] Add docker. Other light fixes for paths (#24) * Add docker. Other light fixes for paths * Fix docker pathing in program.py. Add missing watchlist key * Fix saving/loading settings for abs paths * Attempt #1: Move settings.json to data/settings.json * Forgot to add Docker files lol * Add import for Optional --------- Co-authored-by: Dreu Lavelle --- .dockerignore | 53 +++++++++++++++++++ .github/workflows/docker-build.yml | 31 ++++++++++++ Dockerfile | 25 +++++++++ README.md | 81 +++++++++++++++++++++++------- backend/main.py | 2 +- backend/program/program.py | 6 +++ backend/program/updaters/trakt.py | 6 +-- backend/utils/settings.py | 15 +++--- requirements.txt | 2 +- 9 files changed, 190 insertions(+), 31 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-build.yml create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..a0ca56f6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,53 @@ +# Extras That Shouldn't Be Here +.gitignore +.dockerignore +docker-compose.yml +Dockerfile + +# Frontend +.DS_Store +/build +/.svelte-kit +/package +.env +.env.* +!.env.example + +# Backend +logs/ +settings.json +__pycache__ +*.log +data +test* + +# Jupyter Notebooks +.ipynb_checkpoints + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 00000000..e4d37931 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,31 @@ +name: Docker Build and Push + +on: + push: + branches: + - main + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Log in to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + push: true + tags: spoked/iceberg:latest \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..62ad2df5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM alpine:3.19 + +LABEL org.label-schema.name="Iceberg" \ + org.label-schema.description="Iceberg Debrid Downloader" \ + org.label-schema.url="https://github.com/dreulavelle/iceberg" + +RUN apk --update add python3 py3-pip nodejs npm bash && \ + rm -rf /var/cache/apk/* + +RUN npm install -g pnpm + +WORKDIR /iceberg +COPY . /iceberg/ + +RUN python3 -m venv /venv && \ + source /venv/bin/activate && \ + pip3 install --no-cache-dir -r /iceberg/requirements.txt + +RUN cd /iceberg/frontend && \ + pnpm install && \ + pnpm run build + +EXPOSE 4173 + +CMD cd /iceberg/frontend && pnpm run preview --host & cd /iceberg/backend && source /venv/bin/activate && exec python main.py \ No newline at end of file diff --git a/README.md b/README.md index 2cda436f..b80aa9a3 100644 --- a/README.md +++ b/README.md @@ -2,40 +2,83 @@ The idea behind this was to make a simple and functional rewrite of plex debrid that seemed to get a bit clustered. -Rewrite of plex_debrid project, limited functionality: -- Services include: plex, mdblist, torrentio and realdebrid +Rewrite of [plex_debrid](https://github.com/itsToggle/plex_debrid) project. + +Currently: +- Services include: Plex, Mdblist, Torrentio and Real Debrid TODO: +- Implement uncached download in real-rebrid, dont know if we need this, movies seem to work ok... +- Implement updating quality of fetched items if below something +- Add frontend, ongoing... (adding api endpoints as we go along) + +Check out out [Project Board](https://github.com/users/dreulavelle/projects/2) to stay informed! + +COMPLETED: +- ~~Update plex libraries for changes, ongoing... ~~; (functional but we need to be more specific when to update) - ~~Real-debrid should download only one file per stream, lets avoid collections~~ -- ~~Modify scraping logic to try scaping once a day if not found?~~ - ~~Add overseerr support, mostly done~~; still need to mark items as available? +- ~~Add support for shows, ongoing...~~ (Functionalish, needs work...) +- ~~Modify scraping logic to try scaping once a day if not found?~~ - ~~Store data with pickle~~ - ~~Improve logging...~~ -- ~~Update plex libraries for changes, ongoing... ~~; (functional but we need to be more specific when to update) -- Add frontend, ongoing... (adding api endpoints as we go along) -- ~~Add support for shows, ongoing...~~ (Functionalish, needs work...) -- Implement uncached download in real-rebrid, dont know if we need this, movies seem to work ok... -- Implement updating quality of fetched items if below something +- And more.. +Please add features and mention issues over on our [Issue Tracker](https://github.com/dreulavelle/iceberg/issues)! -## Running the project +We are constantly adding features and improvements as we go along and squashing bugs as they arise. -``` -pip install -r requirements.txt -``` +Enjoy! + +## Docker Compose +```yml +version: '3.8' + +services: + iceberg: + image: iceberg:latest + container_name: Iceberg + restart: unless-stopped + ports: + - "4173:4173" + volumes: + - ./data:/iceberg/data + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost:8080/"] + # interval: 1m30s + # timeout: 10s + # retries: 1 ``` + +> [!WARNING] +> You must have a standard settings.json file already in place before bind mounting it! +> An empty settings.json file, or no file at all, will cause issues! + +You can get a copy of the default settings [here](https://raw.githubusercontent.com/dreulavelle/iceberg/main/backend/utils/default_settings.json) + +After copying over the settings file (on a fresh install) you can bind mount it like the compose above. + +## Running outside of Docker + +```sh +pip install -r requirements.txt python3 backend/main.py +cd frontend && npm install && npm run dev ``` -``` -cd frontend +## Development -npm install -npm run dev +First terminal: -# OR +```sh +git clone https://github.com/dreulavelle/iceberg.git +cd frontend && npm install && npm run dev +``` + +Seperate terminal: -pnpm install -pnpm run dev +```sh +python backend/main.py +``` ``` \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index d42c9905..84aebc53 100644 --- a/backend/main.py +++ b/backend/main.py @@ -36,7 +36,7 @@ def lifespan(app: FastAPI): if __name__ == "__main__": try: - uvicorn.run("main:app", host="localhost", port=8080, reload=False) + uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=False) except KeyboardInterrupt: print("Exiting...") sys.exit(0) diff --git a/backend/program/program.py b/backend/program/program.py index 800a2f47..bdbec8c5 100644 --- a/backend/program/program.py +++ b/backend/program/program.py @@ -3,6 +3,7 @@ import inspect import os import sys +from typing import Optional from pydantic import BaseModel, HttpUrl, Field from program.symlink import Symlinker from utils.logger import logger, get_data_path @@ -16,6 +17,7 @@ class PlexConfig(BaseModel): user: str token: str address: HttpUrl + watchlist: Optional[HttpUrl] = None class MdblistConfig(BaseModel): lists: list[str] = Field(default_factory=list) @@ -93,6 +95,10 @@ def _validate_modules(self): return False def __import_modules(self, folder_path: str) -> list[object]: + if os.path.exists('/iceberg'): + folder_path = os.path.join('/iceberg', folder_path) + else: + folder_path = folder_path file_list = [ f[:-3] for f in os.listdir(folder_path) diff --git a/backend/program/updaters/trakt.py b/backend/program/updaters/trakt.py index 2fa04180..6a0a0352 100644 --- a/backend/program/updaters/trakt.py +++ b/backend/program/updaters/trakt.py @@ -22,7 +22,7 @@ def __init__(self): def create_items(self, imdb_ids): """Update media items to state where they can start downloading""" - self.trakt_data.load("backend/data/trakt_data.pkl") + self.trakt_data.load("data/trakt_data.pkl") new_items = MediaItemContainer() get_items = MediaItemContainer() for imdb_id in imdb_ids: @@ -38,7 +38,7 @@ def create_items(self, imdb_ids): for added_item in added_items: logger.debug("Added %s", added_item.title) self.trakt_data.extend(added_items) - self.trakt_data.save("backend/data/trakt_data.pkl") + self.trakt_data.save("data/trakt_data.pkl") return get_items @@ -125,4 +125,4 @@ def create_item_from_imdb_id(imdb_id: str): data = response.data[0].show if data: return _map_item_from_data(data, media_type) - return None + return None \ No newline at end of file diff --git a/backend/utils/settings.py b/backend/utils/settings.py index 5820bf8d..dd4f37e9 100644 --- a/backend/utils/settings.py +++ b/backend/utils/settings.py @@ -8,7 +8,7 @@ class SettingsManager: """Class that handles settings""" def __init__(self): - self.filename = "settings.json" + self.filename = "data/settings.json" self.config_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) self.settings_file = os.path.join(self.config_dir, self.filename) self.settings = {} @@ -16,18 +16,19 @@ def __init__(self): def load(self): """Load settings from file""" - if not os.path.exists(os.path.join(self.config_dir, self.filename)): - shutil.copy(os.path.join(os.path.dirname(__file__), "default_settings.json"), os.path.join(self.config_dir, self.filename)) + if not os.path.exists(self.settings_file): + default_settings_path = os.path.join(os.path.dirname(__file__), "default_settings.json") + shutil.copy(default_settings_path, self.settings_file) logger.debug("Settings file not found, using default settings") - with open(self.filename, "r", encoding="utf-8") as file: + with open(self.settings_file, "r", encoding="utf-8") as file: self.settings = json.loads(file.read()) - logger.debug("Settings loaded from %s", self.filename) + logger.debug("Settings loaded from %s", self.settings_file) def save(self): """Save settings to file""" - with open(self.filename, "w", encoding="utf-8") as file: + with open(self.settings_file, "w", encoding="utf-8") as file: json.dump(self.settings, file, indent=4) - logger.debug("Settings saved to %s", self.filename) + logger.debug("Settings saved to %s", self.settings_file) def get(self, key): """Get setting with key""" diff --git a/requirements.txt b/requirements.txt index 2b308130..f19ab6e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,4 @@ pathos pydantic fastapi uvicorn[standard] -parse-torrent-file \ No newline at end of file +parse-torrent-title \ No newline at end of file