Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.git
.github
docs
data
.env
.env.*
!.env.example
docker-compose.yml
docker-compose.build.yml
README.md
LICENSE
__pycache__
**/__pycache__
*.py[cod]
.venv
venv
.vscode
.idea
.DS_Store
35 changes: 6 additions & 29 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,57 +1,34 @@
# --- core ---
TZ=Australia/Sydney
JELLYLOOK_PORT=3045
RETENTION_DAYS=60
RECS_PER_SCAN=60
PER_PAGE=20
LOG_LEVEL=INFO

# --- watch-history source ---
# HISTORY_SOURCE picks your media-server stack:
# jellystat = Jellyfin + Jellystat (default)
# tautulli = Plex + Tautulli
# jellyfin = Jellyfin only (no Jellystat)
HISTORY_SOURCE=jellystat # jellystat | tautulli | jellyfin
HISTORY_SOURCE=jellystat

# Jellyfin + Jellystat — hash out (#) this block if you use Plex + Tautulli
JELLYSTAT_URL=http://192.168.1.5:3015
JELLYSTAT_API_KEY= # Jellystat -> Settings -> API Keys
JELLYSTAT_API_KEY=
JELLYFIN_URL=http://192.168.1.5:8096
JELLYFIN_API_KEY= # used for ownership check + id/poster fallback
JELLYFIN_API_KEY=

# Plex + Tautulli — un-hash this block (and set HISTORY_SOURCE=tautulli)
# if you use Plex; hash out the Jellystat/Jellyfin block above instead
#TAUTULLI_URL=http://192.168.1.5:8181
#TAUTULLI_API_KEY= # Tautulli -> Settings -> Web Interface -> API key
TAUTULLI_URL=http://192.168.1.5:8181
TAUTULLI_API_KEY=

# --- requests (Overseerr / Jellyseerr) ---
SEERR_URL=http://192.168.1.5:5055
SEERR_API_KEY=

# --- AI provider switch ---
# LLM_PROVIDER: anthropic | openai | google | openwebui | ollama
# Fill in ONLY the key(s) for the provider you use.
LLM_PROVIDER=anthropic
LLM_MODEL=claude-haiku-4-5 # e.g. gpt-4o-mini · gemini-2.0-flash · qwen3:14b
LLM_MODEL=claude-haiku-4-5
LLM_TEMPERATURE=0.7

# anthropic (Claude)
ANTHROPIC_API_KEY=

# openai (or any OpenAI-compatible endpoint)
OPENAI_API_KEY=
OPENAI_BASE_URL=https://api.openai.com/v1

# google (Gemini — key from https://aistudio.google.com/apikey)
GOOGLE_API_KEY=

# openwebui (key from your Open WebUI account settings)
OPENWEBUI_BASE_URL=http://192.168.1.6:8080
OPENWEBUI_API_KEY=

# ollama (no key needed)
OLLAMA_BASE_URL=http://192.168.1.6:11434

# --- ratings / artwork ---
TMDB_API_KEY=
OMDB_API_KEY=
146 changes: 146 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: Publish container image

on:
push:
branches:
- main
- 'claude/**'
tags:
- 'v*.*.*'
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build:
name: Smoke test and publish
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Check out repository
uses: actions/checkout@v5

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build amd64 image for testing
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
load: true
tags: jellylook:ci
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Start the container
run: |
docker run -d --name jellylook-ci -p 8000:8000 \
-e HISTORY_SOURCE=jellystat \
-e JELLYSTAT_API_KEY=ci-dummy \
-e JELLYFIN_API_KEY=ci-dummy \
-e TMDB_API_KEY=ci-dummy \
-e OMDB_API_KEY=ci-dummy \
-e LLM_PROVIDER=anthropic \
-e ANTHROPIC_API_KEY=ci-dummy \
-e SEERR_URL=http://127.0.0.1:9 \
jellylook:ci

- name: Wait for the health endpoint
run: |
for _ in $(seq 1 30); do
if curl -fsS http://127.0.0.1:8000/health | grep -q '"status":"ok"'; then
echo "health endpoint is up"
exit 0
fi
sleep 2
done
echo "container never became healthy"
exit 1

- name: Check the UI and API respond
run: |
curl -fsS -o /dev/null -w 'index.html %{http_code}\n' http://127.0.0.1:8000/
curl -fsS -o /dev/null -w 'app.js %{http_code}\n' http://127.0.0.1:8000/app.js
curl -fsS http://127.0.0.1:8000/api/status | tee /tmp/status.json
grep -q '"provider":"anthropic"' /tmp/status.json
curl -fsS http://127.0.0.1:8000/api/settings | grep -q 'default_sort'

- name: Check the container reports itself healthy
run: |
for _ in $(seq 1 30); do
state=$(docker inspect -f '{{.State.Health.Status}}' jellylook-ci)
echo "health: $state"
[ "$state" = "healthy" ] && exit 0
[ "$state" = "unhealthy" ] && exit 1
sleep 5
done
echo "HEALTHCHECK never reported healthy"
exit 1

- name: Check startup fails fast on missing configuration
run: |
set +e
docker run --rm jellylook:ci > /tmp/badconf.log 2>&1
status=$?
set -e
cat /tmp/badconf.log
if [ "$status" -eq 0 ]; then
echo "expected a non-zero exit when required keys are missing"
exit 1
fi
grep -q "TMDB_API_KEY required" /tmp/badconf.log

- name: Container logs
if: always()
run: docker logs jellylook-ci || true

- name: Stop the container
if: always()
run: docker rm -f jellylook-ci || true

- name: Log in to the container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Derive image tags
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,format=short

- name: Build and push multi-arch image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Summary
run: |
echo "### Published" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
echo "${{ steps.meta.outputs.tags }}" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
FROM python:3.12-slim

LABEL org.opencontainers.image.title="jellylook" \
org.opencontainers.image.description="Self-hosted AI watch-next recommendations for Jellyfin and Plex" \
org.opencontainers.image.source="https://github.com/dean1850/jellylook" \
org.opencontainers.image.licenses="MIT"

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
DATA_DIR=/app/data

RUN apt-get update \
&& apt-get install -y --no-install-recommends tzdata \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app/ ./app/

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=4)" || exit 1

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Loading
Loading