├── .dockerignore ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── buildx-on-pr.yml │ └── buildx.yml ├── .gitignore ├── Dockerfile ├── README.md ├── requirements.in ├── requirements.txt ├── root ├── defaults │ ├── config.example.yml │ └── plugins │ │ └── write_magnet.py └── etc │ └── s6-overlay │ └── s6-rc.d │ ├── init-config-end │ └── dependencies.d │ │ └── init-flexget │ ├── init-flexget │ ├── dependencies.d │ │ └── init-install-pkg │ ├── run │ ├── type │ └── up │ ├── init-install-pkg │ ├── dependencies.d │ │ └── init-config │ ├── type │ └── up │ └── svc-flexget │ ├── dependencies.d │ └── init-services │ ├── run │ └── type └── unrar ├── APKBUILD └── makefile.patch /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .github 4 | .gitattributes 5 | READMETEMPLATE.md 6 | README.md 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | target-branch: "master" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.github/workflows/buildx-on-pr.yml: -------------------------------------------------------------------------------- 1 | name: buildx-on-pr 2 | 3 | on: 4 | pull_request: 5 | 6 | env: 7 | DOCKERHUB_USER: wiserain 8 | IMAGE_NAME: ghcr.io/wiserain/flexget 9 | ALPINE_VER: '3.21' 10 | 11 | jobs: 12 | buildx-on-pr: 13 | runs-on: ubuntu-22.04 14 | if: github.event_name == 'pull_request' 15 | steps: 16 | - 17 | name: Checkout 18 | uses: actions/checkout@v4 19 | - 20 | name: Compile requirements.txt 21 | run: | 22 | docker run --rm -i -v ${PWD}:/req alpine:${{ env.ALPINE_VER }} \ 23 | sh -c "apk add py3-pip build-base python3-dev libffi-dev && rm /usr/lib/python*/EXTERNALLY-MANAGED && pip install pip-tools && cd /req && pip-compile -U" 24 | - 25 | name: Set up QEMU 26 | uses: docker/setup-qemu-action@v3 27 | - 28 | name: Set up Buildx 29 | uses: docker/setup-buildx-action@v3 30 | - 31 | name: Initial Buildx 32 | uses: docker/build-push-action@v6 33 | with: 34 | context: . 35 | file: Dockerfile 36 | tags: ${{ env.IMAGE_NAME }}:testing 37 | platforms: linux/amd64,linux/arm64 38 | build-args: | 39 | ALPINE_VER=${{ env.ALPINE_VER }} 40 | # 41 | # now only for repository owner 42 | # 43 | - 44 | name: Login to ghcr.io 45 | uses: docker/login-action@v3 46 | if: github.actor == github.event.repository.owner.login 47 | with: 48 | registry: ghcr.io 49 | username: ${{ github.actor }} 50 | password: ${{ secrets.GITHUB_TOKEN }} 51 | - 52 | name: Buildx and Push 53 | uses: docker/build-push-action@v6 54 | if: github.actor == github.event.repository.owner.login 55 | with: 56 | context: . 57 | file: Dockerfile 58 | tags: | 59 | ghcr.io/${{ env.IMAGE_NAME }}:testing 60 | ghcr.io/${{ env.IMAGE_NAME }}:testing-${{ github.event.pull_request.number }} 61 | platforms: linux/amd64,linux/arm64 62 | build-args: | 63 | ALPINE_VER=${{ env.ALPINE_VER }} 64 | push: true 65 | - 66 | name: Notification 67 | uses: sarisia/actions-status-discord@v1 68 | if: always() && github.actor == github.event.repository.owner.login 69 | with: 70 | webhook: ${{ secrets.DISCORD_WEBHOOK }} 71 | status: ${{ job.status }} 72 | description: | 73 | ghcr.io/${{ env.IMAGE_NAME }}:testing 74 | ghcr.io/${{ env.IMAGE_NAME }}:testing-${{ github.event.pull_request.number }} 75 | - name: Upload Artifact 76 | uses: actions/upload-artifact@v4 77 | if: failure() 78 | with: 79 | name: requirements.txt 80 | path: requirements.txt 81 | -------------------------------------------------------------------------------- /.github/workflows/buildx.yml: -------------------------------------------------------------------------------- 1 | name: buildx 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 21 * * 5" 7 | push: 8 | branches: 9 | - 'master' 10 | 11 | env: 12 | DOCKERHUB_USER: wiserain 13 | IMAGE_NAME: wiserain/flexget 14 | ALPINE_VER: '3.21' 15 | 16 | jobs: 17 | buildx: 18 | runs-on: ubuntu-22.04 19 | if: github.event_name != 'pull_request' 20 | steps: 21 | - 22 | name: Checkout 23 | uses: actions/checkout@v4 24 | - 25 | name: Compile requirements.txt 26 | run: | 27 | docker run --rm -i -v ${PWD}:/req alpine:${{ env.ALPINE_VER }} \ 28 | sh -c "apk add py3-pip build-base python3-dev libffi-dev && rm /usr/lib/python*/EXTERNALLY-MANAGED && pip install pip-tools && cd /req && pip-compile -U" 29 | - 30 | name: Set up QEMU 31 | uses: docker/setup-qemu-action@v3 32 | - 33 | name: Set up Buildx 34 | uses: docker/setup-buildx-action@v3 35 | - 36 | name: Initial Buildx 37 | uses: docker/build-push-action@v6 38 | with: 39 | context: . 40 | file: Dockerfile 41 | tags: ${{ env.IMAGE_NAME }}:testing 42 | platforms: linux/amd64,linux/arm64 43 | build-args: | 44 | ALPINE_VER=${{ env.ALPINE_VER }} 45 | - 46 | name: Load Built Image and Get Variables 47 | id: vars 48 | run: | 49 | docker buildx build \ 50 | -t ${{ env.IMAGE_NAME }}:testing \ 51 | --platform=linux/amd64 \ 52 | --build-arg ALPINE_VER=${{ env.ALPINE_VER }} \ 53 | --load \ 54 | . 55 | CID=$(docker run -d ${{ env.IMAGE_NAME }}:testing) 56 | VER_FULL=$(docker exec $CID flexget -V | sed -n 1p) 57 | echo "VER_FULL=$VER_FULL" >> $GITHUB_ENV 58 | docker rm -f $CID 59 | VER_MINOR=$(echo $VER_FULL | cut -d. -f-2) 60 | echo "VER_MINOR=$VER_MINOR" >> $GITHUB_ENV 61 | VER_MAJOR=$(echo $VER_FULL | cut -d. -f-1) 62 | echo "VER_MAJOR=$VER_MAJOR" >> $GITHUB_ENV 63 | - 64 | name: Login to docker.io 65 | uses: docker/login-action@v3 66 | with: 67 | username: ${{ env.DOCKERHUB_USER }} 68 | password: ${{ secrets.DOCKERHUB_TOKEN }} 69 | - 70 | name: Login to ghcr.io 71 | uses: docker/login-action@v3 72 | with: 73 | registry: ghcr.io 74 | username: ${{ github.actor }} 75 | password: ${{ secrets.GITHUB_TOKEN }} 76 | # 77 | # release 78 | # 79 | - 80 | name: Buildx and Push 81 | uses: docker/build-push-action@v6 82 | with: 83 | context: . 84 | file: Dockerfile 85 | tags: | 86 | ${{ env.IMAGE_NAME }}:latest 87 | ${{ env.IMAGE_NAME }}:${{ env.VER_FULL }} 88 | ${{ env.IMAGE_NAME }}:${{ env.VER_MINOR }} 89 | ${{ env.IMAGE_NAME }}:${{ env.VER_MAJOR }} 90 | ghcr.io/${{ env.IMAGE_NAME }}:latest 91 | ghcr.io/${{ env.IMAGE_NAME }}:${{ env.VER_FULL }} 92 | ghcr.io/${{ env.IMAGE_NAME }}:${{ env.VER_MINOR }} 93 | ghcr.io/${{ env.IMAGE_NAME }}:${{ env.VER_MAJOR }} 94 | platforms: linux/amd64,linux/arm64 95 | build-args: | 96 | ALPINE_VER=${{ env.ALPINE_VER }} 97 | push: true 98 | - 99 | name: Notification 100 | uses: sarisia/actions-status-discord@v1 101 | if: always() 102 | with: 103 | webhook: ${{ secrets.DISCORD_WEBHOOK }} 104 | status: ${{ job.status }} 105 | description: | 106 | ${{ env.IMAGE_NAME }}:latest 107 | ${{ env.IMAGE_NAME }}:${{ env.VER_FULL }} 108 | ${{ env.IMAGE_NAME }}:${{ env.VER_MINOR }} 109 | ${{ env.IMAGE_NAME }}:${{ env.VER_MAJOR }} 110 | ghcr.io/${{ env.IMAGE_NAME }}:latest 111 | ghcr.io/${{ env.IMAGE_NAME }}:${{ env.VER_FULL }} 112 | ghcr.io/${{ env.IMAGE_NAME }}:${{ env.VER_MINOR }} 113 | ghcr.io/${{ env.IMAGE_NAME }}:${{ env.VER_MAJOR }} 114 | - 115 | name: Commit requirements.txt 116 | uses: EndBug/add-and-commit@v9 117 | with: 118 | message: | 119 | Update requirements.txt 120 | - name: Upload Artifact 121 | uses: actions/upload-artifact@v4 122 | if: failure() 123 | with: 124 | name: requirements.txt 125 | path: requirements.txt 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ALPINE_VER=3.21 2 | ARG LIBTORRENT_VER=latest 3 | 4 | FROM ghcr.io/by275/libtorrent:${LIBTORRENT_VER}-alpine${ALPINE_VER} AS libtorrent 5 | FROM ghcr.io/linuxserver/baseimage-alpine:${ALPINE_VER} AS base 6 | 7 | RUN \ 8 | echo "**** install frolvlad/alpine-python3 ****" && \ 9 | apk add --no-cache python3 && \ 10 | if [ ! -e /usr/bin/python ]; then ln -sf /usr/bin/python3 /usr/bin/python; fi && \ 11 | rm /usr/lib/python*/EXTERNALLY-MANAGED && \ 12 | python3 -m ensurepip && \ 13 | rm -r /usr/lib/python*/ensurepip && \ 14 | pip3 install --no-cache --upgrade pip setuptools wheel && \ 15 | if [ ! -e /usr/bin/pip ]; then ln -s pip3 /usr/bin/pip; fi && \ 16 | echo "**** cleanup ****" && \ 17 | rm -rf \ 18 | /tmp/* \ 19 | /root/.cache 20 | 21 | # 22 | # BUILD 23 | # 24 | FROM alpine:${ALPINE_VER} AS unrar 25 | 26 | ARG ALPINE_VER 27 | 28 | RUN \ 29 | apk add --no-cache \ 30 | alpine-sdk \ 31 | bash \ 32 | shadow \ 33 | sudo \ 34 | su-exec \ 35 | openssl 36 | 37 | RUN \ 38 | useradd -m -s /bin/bash \ 39 | -p $(openssl passwd -1 abc) abc && \ 40 | echo "abc ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \ 41 | addgroup abc abuild && \ 42 | su-exec abc:abuild abuild-keygen -ain && \ 43 | mkdir -p /var/cache/distfiles && \ 44 | chmod a+w /var/cache/distfiles 45 | 46 | COPY unrar /home/abc/aports/non-free/unrar/ 47 | RUN chown -R abc:abuild /home/abc 48 | 49 | RUN su-exec abc:abuild env APKBUILD=/home/abc/aports/non-free/unrar/APKBUILD abuild -r 50 | RUN mkdir /unrar-build && find /home/abc/packages -name *.apk -type f -exec tar xf {} -C /unrar-build \; 51 | 52 | 53 | FROM base AS builder 54 | 55 | COPY requirements.txt /tmp/ 56 | 57 | RUN \ 58 | echo "**** install build dependencies ****" && \ 59 | apk add --no-cache \ 60 | build-base \ 61 | python3-dev \ 62 | musl-dev \ 63 | libffi-dev \ 64 | openssl-dev \ 65 | libxml2-dev \ 66 | libxslt-dev \ 67 | libc-dev \ 68 | jpeg-dev \ 69 | linux-headers && \ 70 | pip install -r /tmp/requirements.txt --root /bar --no-warn-script-location 71 | 72 | # copy libtorrent libs 73 | COPY --from=libtorrent /libtorrent-build/usr/lib/ /bar/usr/lib/ 74 | 75 | # copy unrar 76 | COPY --from=unrar /unrar-build/usr/bin/ /bar/usr/bin/ 77 | 78 | ADD https://raw.githubusercontent.com/by275/docker-base/main/_/etc/cont-init.d/install-pkg /bar/etc/s6-overlay/s6-rc.d/init-install-pkg/run 79 | 80 | # copy local files 81 | COPY root/ /bar/ 82 | 83 | RUN \ 84 | echo "**** permissions ****" && \ 85 | chmod a+x \ 86 | /bar/etc/s6-overlay/s6-rc.d/*/run \ 87 | && \ 88 | echo "**** s6: add services to user/contents.d ****" && \ 89 | mkdir -p /tmp/app/contents.d && \ 90 | for dir in /bar/etc/s6-overlay/s6-rc.d/*; do touch "/tmp/app/contents.d/$(basename "$dir")"; done && \ 91 | mv /tmp/app /bar/etc/s6-overlay/s6-rc.d/user 92 | 93 | # 94 | # RELEASE 95 | # 96 | FROM base 97 | LABEL maintainer="wiserain" 98 | LABEL org.opencontainers.image.source=https://github.com/wiserain/docker-flexget 99 | 100 | ENV \ 101 | TZ=Etc/UTC \ 102 | S6_BEHAVIOUR_IF_STAGE2_FAILS=2 \ 103 | PYTHONUNBUFFERED=1 \ 104 | PIP_DISABLE_PIP_VERSION_CHECK=1 \ 105 | PIP_NO_CACHE_DIR=1 \ 106 | FIX_DIR_OWNERSHIP_CONFIG=1 \ 107 | FIX_DIR_OWNERSHIP_DATA=1 108 | 109 | COPY --from=builder /bar/ / 110 | 111 | RUN \ 112 | echo "**** install runtime dependencies ****" && \ 113 | apk add --no-cache \ 114 | `# libtorrent` \ 115 | boost-python3 libstdc++ \ 116 | `# lxml` \ 117 | libxml2 libxslt \ 118 | `# others` \ 119 | jpeg \ 120 | `# system` \ 121 | bash bash-completion findutils tzdata && \ 122 | echo "**** cleanup ****" && \ 123 | rm -rf \ 124 | /tmp/* \ 125 | /root/.cache 126 | 127 | # add default volumes 128 | VOLUME /config /data 129 | WORKDIR /config 130 | 131 | # expose port for flexget webui 132 | EXPOSE 5050 5050/tcp 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-flexget 2 | 3 | Docker image for running [flexget](http://flexget.com/) 4 | 5 | Container features are 6 | 7 | - [lsiobase/alpine](https://github.com/linuxserver/docker-baseimage-alpine) 8 | - pre-installed dependencies for plugins 9 | - telegram 10 | - cfscraper 11 | - convert_magnet 12 | - decompress 13 | - transmission 14 | - deluge 15 | - irc 16 | 17 | ## Usage 18 | 19 | ### docker run 20 | 21 | ```bash 22 | docker run -d \ 23 | --name= \ 24 | --restart on-failure:5 \ 25 | -p 5050:5050 \ 26 | -v :/data \ 27 | -v :/config \ 28 | -e PUID= \ 29 | -e PGID= \ 30 | -e TZ= \ 31 | -e FG_WEBUI_PASSWD= \ 32 | wiserain/flexget 33 | ``` 34 | 35 | ### docker-compose 36 | 37 | ```yml 38 | version: "3" 39 | services: 40 | flexget: 41 | image: wiserain/flexget 42 | container_name: 43 | restart: on-failure:5 44 | ports : 45 | - 5050:5050 46 | volumes: 47 | - :/data 48 | - :/config 49 | environment: 50 | - PUID= 51 | - PGID= 52 | - TZ= 53 | - FG_WEBUI_PASSWD= 54 | ``` 55 | 56 | Most importantly, secure webui using ```FG_WEBUI_PASSWD```. 57 | 58 | ## Environment variables 59 | 60 | | ENV | Description | Default | 61 | |---|---|---| 62 | | ```PUID``` / ```PGID``` | uid and gid for running an app | ```911``` / ```911``` | 63 | | ```TZ``` | timezone | | 64 | | ```FG_WEBUI_PASSWD``` | use a strong password | | 65 | | ```FG_LOG_LEVEL``` | log level | ```info``` | 66 | | ```FG_LOG_FILE``` | log file name | ```flexget.log``` | 67 | | ```FG_PLUGINS``` | see below | | 68 | | ```FIX_DIR_OWNERSHIP_CONFIG``` | set something other than `1` or `true` to skip applying chown for dir `/config` | `1` | 69 | | ```FIX_DIR_OWNERSHIP_DATA``` | set something other than `1` or `true` to skip applying chown for dir `/data` | `1` | 70 | 71 | ### Additional packages 72 | 73 | If there are additional packages you may want to install, create bash script with any name under ```/custom-cont-init.d```, for example, 74 | 75 | ```bash 76 | #!/usr/bin/with-contenv bash 77 | apk add -q --no-cache 78 | pip install 79 | ``` 80 | 81 | Then, it will run every container start. Please find more details [here](https://www.linuxserver.io/blog/2019-09-14-customizing-our-containers). 82 | 83 | Or, you can have a built-in script do this by setting environment variables as follows. 84 | 85 | ```bash 86 | -e "INSTALL_APK_PKGS=build-base python3-dev" \ 87 | -e INSTALL_PIP_PKGS=guppy3 88 | ``` 89 | 90 | ### Custom plugins 91 | 92 | You can install custom plugins by ```FG_PLUGINS="{plugin_name_1} {plugin_name_2}"``` whose value is a space-separated list of plugin names. Currently available ones are 93 | 94 | - ```write_magnet```: Mostly same as built-in ```convert_magnet``` but expect better performance and improved error handling, which is compatible with a version of libtorrent containerized in this image. 95 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | cloudscraper 2 | # plugin: cfscraper 3 | rarfile 4 | # plugin: decompress 5 | irc-bot 6 | # plugin: others 7 | flexget[all] 8 | # including deluge qbittorrent telegram transmission 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.12 3 | # by the following command: 4 | # 5 | # pip-compile 6 | # 7 | aniso8601==10.0.1 8 | # via flask-restx 9 | anyio==4.9.0 10 | # via 11 | # flexget 12 | # httpx 13 | apscheduler==3.11.0 14 | # via flexget 15 | attrs==25.3.0 16 | # via 17 | # jsonschema 18 | # referencing 19 | autocommand==2.2.2 20 | # via jaraco-text 21 | babelfish==0.6.1 22 | # via guessit 23 | beautifulsoup4==4.13.4 24 | # via flexget 25 | blinker==1.9.0 26 | # via flask 27 | brotli==1.1.0 28 | # via flask-compress 29 | certifi==2025.4.26 30 | # via 31 | # flexget 32 | # httpcore 33 | # httpx 34 | # requests 35 | charset-normalizer==3.4.2 36 | # via 37 | # flexget 38 | # requests 39 | cheroot==10.0.1 40 | # via cherrypy 41 | cherrypy==18.10.0 42 | # via flexget 43 | click==8.2.1 44 | # via flask 45 | cloudscraper==1.2.71 46 | # via -r requirements.in 47 | deluge-client==1.10.2 48 | # via flexget 49 | feedparser==6.0.11 50 | # via flexget 51 | flask==3.1.1 52 | # via 53 | # flask-compress 54 | # flask-cors 55 | # flask-login 56 | # flask-restx 57 | # flexget 58 | flask-compress==1.17 59 | # via flexget 60 | flask-cors==6.0.0 61 | # via flexget 62 | flask-login==0.6.3 63 | # via flexget 64 | flask-restx==1.3.0 65 | # via flexget 66 | flexget[all]==3.16.3 67 | # via -r requirements.in 68 | future==0.18.3 69 | # via irc-bot 70 | greenlet==3.2.3 71 | # via sqlalchemy 72 | guessit==3.8.0 73 | # via flexget 74 | h11==0.16.0 75 | # via 76 | # flexget 77 | # httpcore 78 | h2==4.2.0 79 | # via flexget 80 | hpack==4.1.0 81 | # via 82 | # flexget 83 | # h2 84 | html5lib==1.1 85 | # via flexget 86 | httpcore==1.0.9 87 | # via 88 | # flexget 89 | # httpx 90 | httpx==0.28.1 91 | # via 92 | # flexget 93 | # python-telegram-bot 94 | hyperframe==6.1.0 95 | # via 96 | # flexget 97 | # h2 98 | idna==3.10 99 | # via 100 | # anyio 101 | # flexget 102 | # httpx 103 | # requests 104 | importlib-resources==6.5.2 105 | # via flask-restx 106 | irc-bot==1.0.41 107 | # via -r requirements.in 108 | itsdangerous==2.2.0 109 | # via flask 110 | jaraco-collections==5.1.0 111 | # via cherrypy 112 | jaraco-context==6.0.1 113 | # via jaraco-text 114 | jaraco-functools==4.1.0 115 | # via 116 | # cheroot 117 | # jaraco-text 118 | # tempora 119 | jaraco-text==4.0.0 120 | # via jaraco-collections 121 | jinja2==3.1.6 122 | # via 123 | # flask 124 | # flexget 125 | jsonschema==4.24.0 126 | # via 127 | # flask-restx 128 | # flexget 129 | jsonschema-specifications==2025.4.1 130 | # via jsonschema 131 | loguru==0.7.3 132 | # via flexget 133 | markdown-it-py==3.0.0 134 | # via rich 135 | markupsafe==3.0.2 136 | # via 137 | # flask 138 | # jinja2 139 | # werkzeug 140 | mdurl==0.1.2 141 | # via markdown-it-py 142 | more-itertools==10.7.0 143 | # via 144 | # cheroot 145 | # cherrypy 146 | # jaraco-functools 147 | # jaraco-text 148 | packaging==25.0 149 | # via 150 | # flexget 151 | # qbittorrent-api 152 | pendulum==3.1.0 153 | # via flexget 154 | plumbum==1.9.0 155 | # via rpyc 156 | portend==3.2.1 157 | # via cherrypy 158 | psutil==7.0.0 159 | # via flexget 160 | pygments==2.19.1 161 | # via rich 162 | pynzb==0.1.0 163 | # via flexget 164 | pyparsing==3.2.3 165 | # via 166 | # cloudscraper 167 | # flexget 168 | pyrss2gen==1.1 169 | # via flexget 170 | python-dateutil==2.9.0.post0 171 | # via 172 | # flexget 173 | # guessit 174 | # pendulum 175 | # tempora 176 | python-telegram-bot==22.0 177 | # via flexget 178 | pytz==2025.2 179 | # via flask-restx 180 | pyyaml==6.0.2 181 | # via flexget 182 | qbittorrent-api==2025.5.0 183 | # via flexget 184 | rarfile==4.2 185 | # via -r requirements.in 186 | rebulk==3.2.0 187 | # via 188 | # flexget 189 | # guessit 190 | referencing==0.36.2 191 | # via 192 | # jsonschema 193 | # jsonschema-specifications 194 | requests==2.32.3 195 | # via 196 | # cloudscraper 197 | # flexget 198 | # qbittorrent-api 199 | # requests-toolbelt 200 | # transmission-rpc 201 | requests-toolbelt==1.0.0 202 | # via cloudscraper 203 | rich==14.0.0 204 | # via flexget 205 | rpds-py==0.25.1 206 | # via 207 | # jsonschema 208 | # referencing 209 | rpyc==6.0.2 210 | # via flexget 211 | sgmllib3k==1.0.0 212 | # via feedparser 213 | six==1.17.0 214 | # via 215 | # html5lib 216 | # irc-bot 217 | # python-dateutil 218 | sniffio==1.3.1 219 | # via 220 | # anyio 221 | # flexget 222 | socksio==1.0.0 223 | # via flexget 224 | soupsieve==2.7 225 | # via beautifulsoup4 226 | sqlalchemy==2.0.41 227 | # via flexget 228 | tempora==5.8.0 229 | # via portend 230 | transmission-rpc==7.0.11 231 | # via flexget 232 | typing-extensions==4.13.2 233 | # via 234 | # anyio 235 | # beautifulsoup4 236 | # flexget 237 | # referencing 238 | # sqlalchemy 239 | # transmission-rpc 240 | tzdata==2025.2 241 | # via pendulum 242 | tzlocal==5.3.1 243 | # via apscheduler 244 | urllib3==1.26.20 245 | # via 246 | # flexget 247 | # qbittorrent-api 248 | # requests 249 | webencodings==0.5.1 250 | # via html5lib 251 | werkzeug==3.1.3 252 | # via 253 | # flask 254 | # flask-cors 255 | # flask-login 256 | # flask-restx 257 | # flexget 258 | zc-lockfile==3.0.post1 259 | # via cherrypy 260 | zstandard==0.23.0 261 | # via flask-compress 262 | zxcvbn==4.5.0 263 | # via flexget 264 | 265 | # The following packages are considered to be unsafe in a requirements file: 266 | # setuptools 267 | -------------------------------------------------------------------------------- /root/defaults/config.example.yml: -------------------------------------------------------------------------------- 1 | web_server: 2 | bind: 0.0.0.0 3 | port: 5050 4 | web_ui: yes 5 | 6 | tasks: 7 | example-task: 8 | rss: http://example.com/feed.xml 9 | download: /data 10 | series: 11 | - some series 1 12 | - some series 2 13 | 14 | schedules: no 15 | -------------------------------------------------------------------------------- /root/defaults/plugins/write_magnet.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | from datetime import datetime 4 | from urllib.parse import urlparse 5 | 6 | from loguru import logger 7 | 8 | from flexget import plugin 9 | from flexget.event import event 10 | from flexget.utils.pathscrub import pathscrub 11 | from flexget.utils.tools import parse_timedelta 12 | 13 | logger = logger.bind(name='write_magnet') 14 | 15 | 16 | class ConvertMagnet: 17 | """Convert magnet only entries to a torrent file""" 18 | 19 | schema = { 20 | "oneOf": [ 21 | # Allow write_magnet: no form to turn off plugin altogether 22 | {"type": "boolean"}, 23 | { 24 | "type": "object", 25 | "properties": { 26 | "timeout": {"type": "string", "format": "interval"}, 27 | "force": {"type": "boolean"}, 28 | "num_try": {"type": "integer"}, 29 | "use_dht": {"type": "boolean"}, 30 | "http_proxy": {"type": "string", "format": "uri"}, 31 | }, 32 | "additionalProperties": False, 33 | }, 34 | ] 35 | } 36 | 37 | def __init__(self): 38 | try: 39 | import requests 40 | trackers_from = 'https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_ip.txt' 41 | self.trackers = requests.get(trackers_from).content.decode('utf8').split('\n\n')[:-1] 42 | except Exception as e: 43 | logger.debug('Failed to get trackers from {}: {}', trackers_from, str(e)) 44 | self.trackers = [] 45 | 46 | def convert_torrent_info(self, torrent_info): 47 | """from libtorrent torrent_info to python dictionary object""" 48 | import libtorrent as lt 49 | 50 | return { 51 | 'name': torrent_info.name(), 52 | 'num_files': torrent_info.num_files(), 53 | 'total_size': torrent_info.total_size(), # in byte 54 | 'info_hash': str(torrent_info.info_hash()), # original type: libtorrent.sha1_hash 55 | 'num_pieces': torrent_info.num_pieces(), 56 | 'creator': torrent_info.creator() if torrent_info.creator() else 'libtorrent v{}'.format(lt.version), 57 | 'comment': torrent_info.comment(), 58 | 'files': [{'path': file.path, 'size': file.size} for file in torrent_info.files()], 59 | 'magnet_uri': lt.make_magnet_uri(torrent_info), 60 | } 61 | 62 | def magnet_to_torrent(self, magnet_uri, destination_folder, timeout, num_try, use_dht, http_proxy): 63 | import libtorrent as lt 64 | 65 | # parameters 66 | try: 67 | params = lt.parse_magnet_uri(magnet_uri) 68 | except Exception as e: 69 | raise plugin.PluginError('Failed to parse the uri: {}', str(e)) 70 | 71 | # prevent downloading 72 | # https://stackoverflow.com/q/45680113 73 | if isinstance(params, dict): 74 | params['flags'] |= lt.add_torrent_params_flags_t.flag_upload_mode 75 | else: 76 | params.flags |= lt.add_torrent_params_flags_t.flag_upload_mode 77 | 78 | lt_version = [int(v) for v in lt.version.split('.')] 79 | if [0, 16, 13, 0] < lt_version < [1, 1, 3, 0]: 80 | # for some reason the info_hash needs to be bytes but it's a struct called sha1_hash 81 | if isinstance(params, dict): 82 | params['info_hash'] = params['info_hash'].to_bytes() 83 | else: 84 | params.info_hash = params.info_hash.to_bytes() 85 | 86 | # add_trackers - currently always append 87 | try: 88 | if isinstance(params, dict): 89 | params['trackers'] += self.trackers 90 | else: 91 | params.trackers += self.trackers 92 | except Exception as e: 93 | logger.debug('Failed to add trackers: {}', str(e)) 94 | 95 | # session from setting pack 96 | settings = { 97 | # basics 98 | # 'user_agent': 'libtorrent/' + lt.__version__, 99 | 'listen_interfaces': '0.0.0.0:6881', 100 | # dht 101 | 'enable_dht': use_dht, 102 | 'use_dht_as_fallback': True, 103 | 'dht_bootstrap_nodes': 'router.bittorrent.com:6881,dht.transmissionbt.com:6881,router.utorrent.com:6881,127.0.0.1:6881', 104 | 'enable_lsd': False, 105 | 'enable_upnp': True, 106 | 'enable_natpmp': True, 107 | 'announce_to_all_tiers': True, 108 | 'announce_to_all_trackers': True, 109 | 'aio_threads': 4*2, 110 | 'checking_mem_usage': 1024*2, 111 | } 112 | if http_proxy: 113 | # TODO: TEST http_proxy 114 | proxy_url = urlparse(http_proxy) 115 | logger.debug(proxy_url) 116 | settings.update({ 117 | 'proxy_username': proxy_url.username, 118 | 'proxy_password': proxy_url.password, 119 | 'proxy_hostname': proxy_url.hostname, 120 | 'proxy_port': proxy_url.port, 121 | 'proxy_type': lt.proxy_type_t.http_pw if proxy_url.username and proxy_url.password else lt.proxy_type_t.http, 122 | 'force_proxy': True, 123 | 'anonymous_mode': True, 124 | }) 125 | session = lt.session(settings) 126 | 127 | # session.add_extension('ut_metadata') 128 | # session.add_extension('ut_pex') 129 | # session.add_extension('metadata_transfer') 130 | 131 | # handle 132 | handle = session.add_torrent(params) 133 | 134 | if use_dht: 135 | handle.force_dht_announce() 136 | 137 | logger.debug('Acquiring torrent metadata for magnet {}', magnet_uri) 138 | 139 | max_try = max(num_try, 1) 140 | for tryid in range(max_try): 141 | timeout_value = timeout 142 | logger.debug('Trying to get metadata ... {}/{}'.format(tryid+1, max_try)) 143 | while not handle.has_metadata(): 144 | time.sleep(0.1) 145 | timeout_value -= 0.1 146 | if timeout_value <= 0: 147 | break 148 | 149 | if handle.has_metadata(): 150 | lt_info = handle.get_torrent_info() 151 | logger.debug('Metadata acquired after {}*{}+{:.1f} seconds', tryid, timeout, timeout - timeout_value) 152 | break 153 | else: 154 | if tryid+1 == max_try: 155 | session.remove_torrent(handle, True) 156 | raise plugin.PluginError( 157 | 'Timed out after {}*{} seconds'.format(max_try, timeout) 158 | ) 159 | 160 | # create torrent object 161 | torrent = lt.create_torrent(lt_info) 162 | torrent.set_creator('libtorrent v{}'.format(lt.version)) # signature 163 | torrent_dict = torrent.generate() 164 | 165 | torrent_info = self.convert_torrent_info(lt_info) 166 | torrent_info.update({ 167 | 'trackers': params['trackers'] if isinstance(params, dict) else params.trackers, 168 | 'creation_date': datetime.fromtimestamp(torrent_dict[b'creation date']).isoformat(), 169 | }) 170 | 171 | # start scraping 172 | timeout_value = timeout 173 | logger.debug('Trying to get peerinfo ... ') 174 | while handle.status(0).num_complete < 0: 175 | time.sleep(0.1) 176 | timeout_value -= 0.1 177 | if timeout_value <= 0: 178 | break 179 | 180 | if handle.status(0).num_complete >= 0: 181 | torrent_status = handle.status(0) 182 | logger.debug('Peerinfo acquired after {:.1f} seconds', timeout - timeout_value) 183 | 184 | torrent_info.update({ 185 | 'seeders': torrent_status.num_complete, 186 | 'peers': torrent_status.num_incomplete, 187 | 'total_wanted': torrent_status.total_wanted 188 | }) 189 | else: 190 | raise plugin.PluginError('Timed out after {} seconds'.format(timeout)) 191 | 192 | session.remove_torrent(handle, True) 193 | 194 | torrent_path = pathscrub( 195 | os.path.join(destination_folder, lt_info.name() + ".torrent") 196 | ) 197 | with open(torrent_path, "wb") as f: 198 | f.write(lt.bencode(torrent_dict)) 199 | logger.debug('Torrent file wrote to {}', torrent_path) 200 | 201 | return torrent_path, torrent_info 202 | 203 | def prepare_config(self, config): 204 | if not isinstance(config, dict): 205 | config = {} 206 | config.setdefault('timeout', '15 seconds') 207 | config.setdefault('force', False) 208 | config.setdefault('num_try', 3) 209 | config.setdefault('use_dht', True) 210 | config.setdefault('http_proxy', '') 211 | return config 212 | 213 | @plugin.priority(plugin.PRIORITY_FIRST) 214 | def on_task_start(self, task, config): 215 | if config is False: 216 | return 217 | try: 218 | import libtorrent # noqa 219 | except ImportError: 220 | raise plugin.DependencyError( 221 | 'write_magnet', 'libtorrent', 'libtorrent package required', logger 222 | ) 223 | 224 | @plugin.priority(130) 225 | def on_task_download(self, task, config): 226 | if config is False: 227 | return 228 | config = self.prepare_config(config) 229 | # Create the conversion target directory 230 | converted_path = os.path.join(task.manager.config_base, 'converted') 231 | 232 | timeout = parse_timedelta(config['timeout']).total_seconds() 233 | 234 | if not os.path.isdir(converted_path): 235 | os.mkdir(converted_path) 236 | 237 | for entry in task.accepted: 238 | if entry['url'].startswith('magnet:'): 239 | entry.setdefault('urls', [entry['url']]) 240 | try: 241 | logger.info('Converting entry {} magnet URI to a torrent file', entry['title']) 242 | torrent_file, torrent_info = self.magnet_to_torrent( 243 | entry['url'], converted_path, timeout, config['num_try'], config['use_dht'], config['http_proxy'] 244 | ) 245 | except (plugin.PluginError, TypeError) as e: 246 | logger.error( 247 | 'Unable to convert Magnet URI for entry {}: {}', entry['title'], e 248 | ) 249 | if config['force']: 250 | entry.fail('Magnet URI conversion failed') 251 | continue 252 | # Windows paths need an extra / prepended to them for url 253 | if not torrent_file.startswith('/'): 254 | torrent_file = '/' + torrent_file 255 | entry['url'] = torrent_file 256 | entry['file'] = torrent_file 257 | # make sure it's first in the list because of how download plugin works 258 | entry['urls'].insert(0, 'file://{}'.format(torrent_file)) 259 | 260 | # TODO: could be populate extra fields from torrent_info 261 | if "content_size" not in entry.keys(): 262 | entry["content_size"] = ( 263 | round(torrent_info['total_wanted'] / 1024 ** 2) 264 | ) 265 | entry['seeders'] = torrent_info['seeders'] 266 | entry['leechers'] = torrent_info['peers'] 267 | 268 | 269 | @event('plugin.register') 270 | def register_plugin(): 271 | plugin.register(ConvertMagnet, 'write_magnet', api_ver=2) 272 | 273 | # confirmed working with libtorrent version 1.1.5 1.1.13 1.2.5+ 274 | -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-flexget: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiserain/docker-flexget/6977c565306ca461cb3f205494bc7ab364c2a886/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-flexget -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-flexget/dependencies.d/init-install-pkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiserain/docker-flexget/6977c565306ca461cb3f205494bc7ab364c2a886/root/etc/s6-overlay/s6-rc.d/init-flexget/dependencies.d/init-install-pkg -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-flexget/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | 3 | # in case of missing permissions, e.g. --cap-drop CAP_DAC_OVERRIDE 4 | chown -R root:root /config 5 | 6 | # remove config-lock 7 | find /config -name ".config-lock" -exec rm -f {} \; 8 | 9 | # copy config.yml 10 | if [[ -f /config/config.yml ]]; then 11 | echo "*** Using existing config.yml" 12 | else 13 | echo "*** New config.yml from template" 14 | cp /defaults/config.example.yml /config/config.yml 15 | fi 16 | 17 | # install custom plugins 18 | if [[ -n "${FG_PLUGINS:-}" ]]; then 19 | mkdir -p /config/plugins 20 | for plugin_name in ${FG_PLUGINS}; do 21 | plugin_src="/defaults/plugins/${plugin_name}.py" 22 | plugin_trg="/config/plugins/${plugin_name}.py" 23 | plugin_log="*** Installing plugin: '${plugin_name}'" 24 | if [[ -f "${plugin_src}" ]]; then 25 | if [[ ! -f "${plugin_trg}" ]]; then 26 | cp "${plugin_src}" "${plugin_trg}" >/dev/null 2>&1 && \ 27 | echo "${plugin_log} - successful" || \ 28 | echo "${plugin_log} - copy failed" 29 | else 30 | echo "${plugin_log} - already exists. skipping ..." 31 | fi 32 | else 33 | echo "${plugin_log} - no such plugin available" 34 | fi 35 | done 36 | fi 37 | 38 | # set FG_WEBUI_PASSWD 39 | if [[ -n "${FG_WEBUI_PASSWD:-}" ]]; then 40 | echo "*** Setting flexget web password" 41 | if ! flexget -c /config/config.yml --loglevel ERROR web passwd "${FG_WEBUI_PASSWD}" | \ 42 | tee /dev/stderr | grep -q 'Updated password'; then 43 | exit 1 44 | fi 45 | fi 46 | 47 | # permissions 48 | if [ "${FIX_DIR_OWNERSHIP_CONFIG}" = "1" ] || [ "${FIX_DIR_OWNERSHIP_CONFIG}" = "true" ]; then 49 | find /config \! \( -uid "$(id -u abc)" -gid "$(id -g abc)" \) -print0 | \ 50 | xargs -0 --no-run-if-empty chown -h abc:abc 51 | fi 52 | if [ "${FIX_DIR_OWNERSHIP_DATA}" = "1" ] || [ "${FIX_DIR_OWNERSHIP_DATA}" = "true" ]; then 53 | find /data \! \( -uid "$(id -u abc)" -gid "$(id -g abc)" \) -print0 | \ 54 | xargs -0 --no-run-if-empty chown -h abc:abc 55 | fi 56 | 57 | echo "*** Flexget v$(flexget -V | sed -n 1p) ready!" 58 | -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-flexget/type: -------------------------------------------------------------------------------- 1 | oneshot -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-flexget/up: -------------------------------------------------------------------------------- 1 | /etc/s6-overlay/s6-rc.d/init-flexget/run -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-install-pkg/dependencies.d/init-config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiserain/docker-flexget/6977c565306ca461cb3f205494bc7ab364c2a886/root/etc/s6-overlay/s6-rc.d/init-install-pkg/dependencies.d/init-config -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-install-pkg/type: -------------------------------------------------------------------------------- 1 | oneshot -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-install-pkg/up: -------------------------------------------------------------------------------- 1 | /etc/s6-overlay/s6-rc.d/init-install-pkg/run -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/svc-flexget/dependencies.d/init-services: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiserain/docker-flexget/6977c565306ca461cb3f205494bc7ab364c2a886/root/etc/s6-overlay/s6-rc.d/svc-flexget/dependencies.d/init-services -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/svc-flexget/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | 3 | command=" 4 | flexget 5 | -c /config/config.yml 6 | --loglevel ${FG_LOG_LEVEL:-info} 7 | --logfile ${FG_LOG_FILE:-flexget.log} 8 | daemon start --autoreload-config 9 | " 10 | 11 | echo "*** Executing => $(echo $command)" 12 | 13 | exec \ 14 | s6-setuidgid abc $command 15 | -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/svc-flexget/type: -------------------------------------------------------------------------------- 1 | longrun -------------------------------------------------------------------------------- /unrar/APKBUILD: -------------------------------------------------------------------------------- 1 | # Contributor: Sören Tempel 2 | # Contributor: Carlo Landmeter 3 | # Maintainer: Natanael Copa 4 | pkgname=unrar 5 | pkgver=7.1.3 6 | pkgrel=0 7 | pkgdesc="RAR uncompression program" 8 | url="https://www.rarlab.com/rar_add.htm" 9 | arch="all" 10 | options="!check" # no test suite 11 | license="custom" 12 | subpackages="$pkgname-doc" 13 | source="https://www.rarlab.com/rar/unrarsrc-$pkgver.tar.gz" 14 | builddir="$srcdir/$pkgname" 15 | 16 | build() { 17 | make CXX="${CXX:-g++}" STRIP=: -f makefile 18 | } 19 | 20 | package() { 21 | make DESTDIR="$pkgdir"/usr install 22 | 23 | install -Dm644 license.txt \ 24 | "$pkgdir"/usr/share/licenses/$pkgname/license.txt 25 | } 26 | 27 | sha512sums=" 28 | 210a4c65e5c3f3a3f9561a8fab805b6a6a182ac6274f8671d22020076873952b92cb8cc2523943df20a97ed01415b38e2bb59082f03dd5f7da0d6e85dc9193d4 unrarsrc-7.1.3.tar.gz 29 | " 30 | -------------------------------------------------------------------------------- /unrar/makefile.patch: -------------------------------------------------------------------------------- 1 | diff -upr unrar.orig/makefile unrar/makefile 2 | --- unrar.orig/makefile 2018-03-11 17:33:12.697132381 +0100 3 | +++ unrar/makefile 2018-03-11 17:33:48.763911497 +0100 4 | @@ -2,13 +2,14 @@ 5 | # Makefile for UNIX - unrar 6 | 7 | # Linux using GCC 8 | -CXX=c++ 9 | -CXXFLAGS=-O2 -Wno-logical-op-parentheses -Wno-switch -Wno-dangling-else 10 | +CXX?=c++ 11 | +CXXFLAGS?=-O2 12 | +CXXFLAGS+=-Wno-logical-op-parentheses -Wno-switch -Wno-dangling-else 13 | LIBFLAGS=-fPIC 14 | DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -DRAR_SMP 15 | STRIP=strip 16 | AR=ar 17 | -LDFLAGS=-pthread 18 | +LDFLAGS+=-pthread 19 | DESTDIR=/usr 20 | 21 | # Linux using LCC 22 | --------------------------------------------------------------------------------