├── .ddb └── deluge │ ├── __init__.py │ ├── actions.py │ └── schema.py ├── .docker ├── deluge │ ├── Dockerfile.jinja │ ├── fixuid.yml │ ├── home │ │ ├── deluge-web.sh │ │ ├── deluge.sh │ │ ├── deluge │ │ │ └── core.conf │ │ ├── passwd.py │ │ └── webui │ │ │ └── web.conf │ └── supervisor.d │ │ └── deluge.ini ├── jackett │ ├── Dockerfile.jinja │ └── fixuid.yml ├── lidarr │ ├── Dockerfile.jinja │ └── fixuid.yml ├── radarr │ ├── Dockerfile.jinja │ └── fixuid.yml ├── sonarr │ ├── Dockerfile.jinja │ └── fixuid.yml ├── sshd │ ├── Dockerfile.jinja │ └── entrypoint.jinja.sh ├── stealthbox-conf │ ├── Dockerfile.jinja │ └── conf │ │ └── stealthbox.json └── stealthbox-tools │ ├── Dockerfile.jinja │ └── tools │ ├── ConfigParserPipe.py │ ├── __init__.py │ ├── boxpasswd.sh │ └── passwd │ ├── autopasswd.sh │ ├── passwd.deluge.py │ └── passwd.pydio.php ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── ddb.minimal.yml ├── ddb.yml └── docker-compose.yml.jsonnet /.ddb/deluge/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, ClassVar 2 | 3 | from ddb.action import Action 4 | from ddb.feature import Feature 5 | 6 | from .actions import Password 7 | from .schema import DelugeSchema 8 | 9 | 10 | class DelugedFeature(Feature): 11 | @property 12 | def name(self) -> str: 13 | return "deluge" 14 | 15 | @property 16 | def dependencies(self) -> Iterable[str]: 17 | return ["core"] 18 | 19 | @property 20 | def schema(self) -> ClassVar[DelugeSchema]: 21 | return DelugeSchema 22 | 23 | @property 24 | def actions(self) -> Iterable[Action]: 25 | return (Password(),) 26 | -------------------------------------------------------------------------------- /.ddb/deluge/actions.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from random import getrandbits 3 | from typing import Union, Iterable, Callable 4 | 5 | from ddb.action import Action 6 | from ddb.action.action import EventBinding 7 | from ddb.config import config 8 | from ddb.event import events 9 | 10 | 11 | class Password(Action): 12 | @property 13 | def event_bindings(self) -> Union[Iterable[Union[Callable, str, EventBinding]], Union[Callable, str, EventBinding]]: 14 | return events.main.start 15 | 16 | @property 17 | def name(self) -> str: 18 | return "deluge:password" 19 | 20 | def execute(self, command: str): 21 | password = config.data.get('stealthbox.deluge.password') 22 | 23 | sha1 = config.data.get('stealthbox.deluge.sha1') 24 | salt = config.data.get('stealthbox.deluge.salt') 25 | 26 | if password and (not sha1 or not salt): 27 | salt = hashlib.sha1(str(getrandbits(40)).encode("utf-8")).hexdigest() 28 | config.data['stealthbox.deluge.salt'] = salt 29 | 30 | s = hashlib.sha1(salt.encode("utf-8")) 31 | s.update(password.encode("utf-8")) 32 | sha1 = s.hexdigest() 33 | config.data['stealthbox.deluge.sha1'] = sha1 34 | -------------------------------------------------------------------------------- /.ddb/deluge/schema.py: -------------------------------------------------------------------------------- 1 | from ddb.feature.schema import FeatureSchema 2 | from marshmallow import fields 3 | 4 | 5 | class DelugeSchema(FeatureSchema): 6 | password = fields.String(required=False, allow_none=True, default=None) 7 | sha1 = fields.String(required=False, allow_none=True, default=None) 8 | salt = fields.String(required=False, allow_none=True, default=None) 9 | -------------------------------------------------------------------------------- /.docker/deluge/Dockerfile.jinja: -------------------------------------------------------------------------------- 1 | FROM alpine:edge 2 | 3 | RUN \ 4 | echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \ 5 | apk update && \ 6 | apk add --upgrade apk-tools && \ 7 | apk add supervisor shadow bash py3-pip deluge@testing && \ 8 | apk add --no-cache --virtual .pip-build-deps make g++ autoconf python3-dev libffi-dev libressl-dev && \ 9 | pip install automat incremental constantly service_identity && \ 10 | apk del .pip-build-deps && \ 11 | rm -rf /var/cache/apk/* 12 | 13 | ADD supervisor.d/* /etc/supervisor.d/ 14 | ADD home/ /home/nobody/ 15 | 16 | RUN chmod +x /home/nobody/*.sh && chmod +x /home/nobody/*.py 17 | RUN chown -R nobody:nobody /usr/bin/supervisord /usr/bin/deluged /usr/bin/deluge-web /etc/supervisord.conf /etc/supervisor.d/ /var/log /var/run/ /home/nobody/ 18 | USER nobody 19 | 20 | # map /config to host defined config path (used to store configuration from app) 21 | VOLUME /config 22 | 23 | # map /data to host defined data path (used to store data from app) 24 | VOLUME /data 25 | 26 | # expose port for http 27 | EXPOSE 8112 28 | 29 | # expose port for deluge daemon 30 | EXPOSE 58846 31 | 32 | # expose port for incoming torrent data (tcp and udp) 33 | EXPOSE 58946 34 | EXPOSE 58946/udp 35 | 36 | CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf", "-n"] -------------------------------------------------------------------------------- /.docker/deluge/fixuid.yml: -------------------------------------------------------------------------------- 1 | user: nobody 2 | group: nobody 3 | paths: 4 | - / 5 | - /config 6 | - /data -------------------------------------------------------------------------------- /.docker/deluge/home/deluge-web.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # wait for deluge daemon process to start (listen for port) 4 | while [[ $(netstat -lnt | awk '$6 == "LISTEN" && $4 ~ ".58846"') == "" ]]; do 5 | sleep 0.1 6 | done 7 | 8 | # if config file doesnt exist then copy stock config file 9 | if [[ ! -f /config/web.conf ]]; then 10 | echo "[info] Deluge webui config file doesn't exist, copying default..." 11 | cp /home/nobody/webui/web.conf /config/ 12 | fi 13 | 14 | if [[ -n "$DELUGE_SHA1" ]] && [[ -n "$DELUGE_SALT" ]]; then 15 | echo "[password] Setting password from DELUGE_SHA1 and DELUGE_SALT environment variables" 16 | /home/nobody/passwd.py /config/web.conf --salt "$DELUGE_SALT" --sha1 "$DELUGE_SHA1" 17 | else 18 | if [[ -n "$DELUGE_PASSWORD" ]]; then 19 | echo "[password] Setting password from DELUGE_PASSWORD environment variables" 20 | /home/nobody/passwd.py /config/web.conf --password "$DELUGE_PASSWORD" 21 | fi 22 | fi 23 | 24 | echo "[info] Starting Deluge webui..." 25 | /usr/bin/deluge-web -c /config 26 | -------------------------------------------------------------------------------- /.docker/deluge/home/deluge.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # if config file doesnt exist (wont exist until user changes a setting) then copy default config file 4 | if [[ ! -f /config/core.conf ]]; then 5 | echo "[info] Deluge config file doesn't exist, copying default..." 6 | cp /home/nobody/deluge/core.conf /config/ 7 | else 8 | echo "[info] Deluge config file already exists, skipping copy" 9 | fi 10 | 11 | echo "[info] Starting Deluge daemon..." 12 | /usr/bin/deluged -d -c /config -L info -l /config/deluged.log -------------------------------------------------------------------------------- /.docker/deluge/home/deluge/core.conf: -------------------------------------------------------------------------------- 1 | { 2 | "file": 1, 3 | "format": 1 4 | }{ 5 | "info_sent": 0.0, 6 | "lsd": true, 7 | "max_download_speed": -1.0, 8 | "send_info": false, 9 | "natpmp": true, 10 | "move_completed_path": "/data/completed", 11 | "peer_tos": "0x00", 12 | "enc_in_policy": 1, 13 | "queue_new_to_top": false, 14 | "ignore_limits_on_local_network": true, 15 | "rate_limit_ip_overhead": false, 16 | "daemon_port": 58846, 17 | "torrentfiles_location": "/data/torrents", 18 | "max_active_limit": 8, 19 | "geoip_db_location": "/usr/share/GeoIP/GeoIP.dat", 20 | "upnp": true, 21 | "utpex": true, 22 | "max_active_downloading": 3, 23 | "max_active_seeding": 5, 24 | "allow_remote": true, 25 | "outgoing_ports": [ 26 | 0, 27 | 0 28 | ], 29 | "enabled_plugins": [], 30 | "max_half_open_connections": 50, 31 | "download_location": "/data/incomplete", 32 | "compact_allocation": false, 33 | "max_upload_speed": -1.0, 34 | "plugins_location": "/config/plugins", 35 | "max_connections_global": 200, 36 | "enc_prefer_rc4": true, 37 | "cache_expiry": 60, 38 | "dht": true, 39 | "stop_seed_at_ratio": false, 40 | "stop_seed_ratio": 2.0, 41 | "max_download_speed_per_torrent": -1, 42 | "prioritize_first_last_pieces": false, 43 | "max_upload_speed_per_torrent": -1, 44 | "auto_managed": true, 45 | "enc_level": 2, 46 | "copy_torrent_file": false, 47 | "max_connections_per_second": 20, 48 | "listen_ports": [ 49 | 58946, 50 | 58946 51 | ], 52 | "max_connections_per_torrent": -1, 53 | "del_copy_torrent_file": false, 54 | "move_completed": true, 55 | "autoadd_enable": false, 56 | "proxies": { 57 | "peer": { 58 | "username": "", 59 | "password": "", 60 | "hostname": "", 61 | "type": 0, 62 | "port": 8080 63 | }, 64 | "web_seed": { 65 | "username": "", 66 | "password": "", 67 | "hostname": "", 68 | "type": 0, 69 | "port": 8080 70 | }, 71 | "tracker": { 72 | "username": "", 73 | "password": "", 74 | "hostname": "", 75 | "type": 0, 76 | "port": 8080 77 | }, 78 | "dht": { 79 | "username": "", 80 | "password": "", 81 | "hostname": "", 82 | "type": 0, 83 | "port": 8080 84 | } 85 | }, 86 | "dont_count_slow_torrents": false, 87 | "add_paused": false, 88 | "random_outgoing_ports": true, 89 | "max_upload_slots_per_torrent": -1, 90 | "new_release_check": true, 91 | "enc_out_policy": 1, 92 | "seed_time_ratio_limit": 7.0, 93 | "remove_seed_at_ratio": false, 94 | "autoadd_location": "/data/watched", 95 | "max_upload_slots_global": 4, 96 | "seed_time_limit": 180, 97 | "cache_size": 512, 98 | "share_ratio_limit": 2.0, 99 | "random_port": false, 100 | "listen_interface": "0.0.0.0" 101 | } -------------------------------------------------------------------------------- /.docker/deluge/home/passwd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import hashlib 4 | import os 5 | import re 6 | from argparse import ArgumentParser, Namespace 7 | from pathlib import Path 8 | from random import getrandbits 9 | 10 | 11 | def main(args: Namespace): 12 | if args.password: 13 | set_password(args.filepath, args.password) 14 | elif args.sha1 and args.salt: 15 | set_sha1_and_salt(args.filepath, args.sha1, args.salt) 16 | else: 17 | raise ValueError("Either password or sha1/salt should be defined to set the password.") 18 | 19 | 20 | def set_password(filepath: str, password: str): 21 | salt = hashlib.sha1(str(getrandbits(40)).encode("utf-8")).hexdigest() 22 | 23 | s = hashlib.sha1(salt.encode("utf-8")) 24 | s.update(password.encode("utf-8")) 25 | sha1 = s.hexdigest() 26 | 27 | return set_sha1_and_salt(filepath, sha1, salt) 28 | 29 | 30 | def set_sha1_and_salt(filepath: str, sha1: str, salt: str): 31 | if not os.path.exists(filepath): 32 | raise IOError("Not exists") 33 | 34 | content = Path(filepath).read_text() 35 | 36 | salt_regex = r'("pwd_salt": *")(.+?)(")' 37 | sha1_regex = r'("pwd_sha1": *")(.+?)(")' 38 | 39 | content = re.sub(sha1_regex, r'\g<1>' + sha1 + r'\g<3>', content) 40 | content = re.sub(salt_regex, r'\g<1>' + salt + r'\g<3>', content) 41 | 42 | Path(filepath).write_text(content) 43 | 44 | 45 | if __name__ == '__main__': 46 | parser = ArgumentParser() 47 | parser.add_argument('filepath', default='/config/web.conf') 48 | parser.add_argument('--password') 49 | parser.add_argument('--sha1') 50 | parser.add_argument('--salt') 51 | 52 | args = parser.parse_args() 53 | 54 | main(args) 55 | -------------------------------------------------------------------------------- /.docker/deluge/home/webui/web.conf: -------------------------------------------------------------------------------- 1 | { 2 | "file": 1, 3 | "format": 1 4 | }{ 5 | "sidebar_show_zero": false, 6 | "show_session_speed": false, 7 | "pwd_sha1": "20fec4b4f689b75c4adca7705f015993764f1bbc", 8 | "show_sidebar": true, 9 | "sessions": { 10 | "bc64a15bf9d74b773810c92b4822c30d": { 11 | "login": "admin", 12 | "expires": 1487945609.0, 13 | "level": 10 14 | } 15 | }, 16 | "enabled_plugins": [], 17 | "base": "/", 18 | "first_login": false, 19 | "theme": "gray", 20 | "pkey": "ssl/daemon.pkey", 21 | "cert": "ssl/daemon.cert", 22 | "session_timeout": 3600, 23 | "https": false, 24 | "default_daemon": "127.0.0.1:58846", 25 | "sidebar_multiple_filters": true, 26 | "pwd_salt": "6b47247a80dde0c28649f97dca8c9d5f26cf1a16", 27 | "port": 8112 28 | } -------------------------------------------------------------------------------- /.docker/deluge/supervisor.d/deluge.ini: -------------------------------------------------------------------------------- 1 | [program:deluge-web-script] 2 | autorestart = false 3 | startsecs = 0 4 | user = nobody 5 | command = /home/nobody/deluge-web.sh 6 | umask = 000 7 | stdout_logfile=/dev/stdout 8 | stdout_logfile_maxbytes=0 9 | 10 | [program:deluge-script] 11 | autorestart = false 12 | startsecs = 0 13 | user = nobody 14 | command = /home/nobody/deluge.sh 15 | umask = 000 16 | stdout_logfile=/dev/stdout 17 | stdout_logfile_maxbytes=0 18 | 19 | [supervisord] 20 | pidfile=/var/run/supervisord.pid 21 | -------------------------------------------------------------------------------- /.docker/jackett/Dockerfile.jinja: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | RUN mkdir -p /app/Jackett && mkdir -p /tmp/Jackett && \ 4 | apt-get update && apt-get install -y curl &&\ 5 | JACKETT_VERSION=$(curl -sX GET "https://api.github.com/repos/Jackett/Jackett/releases/latest" | awk '/tag_name/{print $4;exit}' FS='[""]') && \ 6 | curl -o /tmp/Jackett/Jackett.tar.gz -L https://github.com/Jackett/Jackett/releases/download/$JACKETT_VERSION/Jackett.Binaries.LinuxAMDx64.tar.gz && \ 7 | tar zxf /tmp/Jackett/Jackett.tar.gz -C /app/Jackett --strip-components=1 && rm -rf /tmp/Jackett* &&\ 8 | apt-get purge -y curl && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN apt-get update && apt-get install -y libicu66 && rm -rf /var/lib/apt/lists/* 11 | 12 | RUN mkdir -p /config && mkdir -p /data && chown -R nobody:nogroup /app /config /data 13 | USER nobody 14 | 15 | VOLUME /config /data 16 | 17 | EXPOSE 9117 18 | 19 | ENV XDG_CONFIG_HOME="/config" 20 | WORKDIR /app/Jackett 21 | 22 | ENTRYPOINT ["/app/Jackett/jackett"] 23 | -------------------------------------------------------------------------------- /.docker/jackett/fixuid.yml: -------------------------------------------------------------------------------- 1 | user: nobody 2 | group: nogroup 3 | paths: 4 | - / 5 | - /config 6 | - /data -------------------------------------------------------------------------------- /.docker/lidarr/Dockerfile.jinja: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | RUN apt-get update && apt-get install -y mono-devel ca-certificates-mono && rm -rf /var/lib/apt/lists/* 4 | 5 | RUN mkdir -p /app/Lidarr && mkdir -p /tmp/Lidarr && \ 6 | apt-get update && apt-get install -y curl jq &&\ 7 | LIDARR_TAG_NAME=$(curl -sX GET "https://api.github.com/repos/lidarr/Lidarr/releases" | jq -r .[0].tag_name) && \ 8 | LIDARR_VERSION=$(curl -sX GET "https://api.github.com/repos/lidarr/Lidarr/releases" | jq -r .[0].name) && \ 9 | curl -o /tmp/Lidarr/Lidarr.tar.gz -L https://github.com/lidarr/Lidarr/releases/download/$LIDARR_TAG_NAME/Lidarr.master.$LIDARR_VERSION.linux.tar.gz && \ 10 | tar zxf /tmp/Lidarr/Lidarr.tar.gz -C /app/Lidarr --strip-components=1 && rm -rf /tmp/Lidarr* &&\ 11 | apt-get purge -y curl jq && rm -rf /var/lib/apt/lists/* 12 | 13 | RUN apt-get update && apt-get install -y libicu66 && rm -rf /var/lib/apt/lists/* 14 | 15 | RUN mkdir -p /config && mkdir -p /data && chown -R nobody:nogroup /app /config /data 16 | USER nobody 17 | 18 | VOLUME /config /data 19 | 20 | EXPOSE 8989 21 | 22 | ENV XDG_CONFIG_HOME="/config" 23 | WORKDIR /app/Lidarr 24 | 25 | ENTRYPOINT ["mono", "/app/Lidarr/Lidarr.exe"] 26 | -------------------------------------------------------------------------------- /.docker/lidarr/fixuid.yml: -------------------------------------------------------------------------------- 1 | user: nobody 2 | group: nogroup 3 | paths: 4 | - / 5 | - /config 6 | - /data -------------------------------------------------------------------------------- /.docker/radarr/Dockerfile.jinja: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | RUN mkdir -p /app/Radarr && mkdir -p /tmp/Radarr && \ 4 | apt-get update && apt-get install -y curl &&\ 5 | curl -o /tmp/Radarr/Radarr.tar.gz -L "https://radarr.servarr.com/v1/update/master/updatefile?os=linux&runtime=netcore&arch=x64" &&\ 6 | tar zxf /tmp/Radarr/Radarr.tar.gz -C /app/Radarr --strip-components=1 && rm -rf /tmp/Radarr* &&\ 7 | apt-get purge -y curl && rm -rf /var/lib/apt/lists/* 8 | 9 | RUN apt-get update && apt-get install -y libicu66 && rm -rf /var/lib/apt/lists/* 10 | 11 | RUN mkdir -p /config && mkdir -p /data && chown -R nobody:nogroup /app /config /data 12 | USER nobody 13 | 14 | VOLUME /config /data 15 | 16 | EXPOSE 8989 17 | 18 | ENV XDG_CONFIG_HOME="/config" 19 | WORKDIR /app/Radarr 20 | 21 | ENTRYPOINT ["/app/Radarr/Radarr"] 22 | -------------------------------------------------------------------------------- /.docker/radarr/fixuid.yml: -------------------------------------------------------------------------------- 1 | user: nobody 2 | group: nogroup 3 | paths: 4 | - / 5 | - /config 6 | - /data -------------------------------------------------------------------------------- /.docker/sonarr/Dockerfile.jinja: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | RUN apt-get update && apt-get install -y mono-devel ca-certificates-mono && rm -rf /var/lib/apt/lists/* 4 | 5 | # ENV SONARR_VERSION="2.0.0.5344" 6 | ENV SONARR_BRANCH="master" 7 | 8 | RUN mkdir -p /app/Sonarr && mkdir -p /tmp/Sonarr && \ 9 | apt-get update && apt-get install -y curl jq &&\ 10 | if [ -z ${SONARR_VERSION+x} ]; then \ 11 | SONARR_VERSION=$(curl -sX GET https://services.sonarr.tv/v1/download/${SONARR_BRANCH} | jq -r '.version') && \ 12 | SONARR_LINUX_URL="https://download.sonarr.tv/v2/${SONARR_BRANCH}/mono/NzbDrone.${SONARR_BRANCH}.${SONARR_VERSION}.mono.tar.gz"; \ 13 | else \ 14 | SONARR_LINUX_URL=$(curl -sX GET "https://services.sonarr.tv/v1/download/$SONARR_BRANCH" | jq -r .linux.manual.url); \ 15 | fi && \ 16 | curl -o /tmp/Sonarr/Sonarr.tar.gz -L "$SONARR_LINUX_URL" && \ 17 | tar zxf /tmp/Sonarr/Sonarr.tar.gz -C /app/Sonarr --strip-components=1 && rm -rf /tmp/Sonarr* && \ 18 | apt-get purge -y curl jq && rm -rf /var/lib/apt/lists/* 19 | 20 | RUN mkdir -p /config && mkdir -p /data && chown -R nobody:nogroup /app /config /data 21 | USER nobody 22 | 23 | VOLUME /config /data 24 | 25 | EXPOSE 8989 26 | 27 | ENV XDG_CONFIG_HOME="/config" 28 | WORKDIR /app/Sonarr 29 | 30 | ENTRYPOINT ["mono", "NzbDrone.exe"] 31 | -------------------------------------------------------------------------------- /.docker/sonarr/fixuid.yml: -------------------------------------------------------------------------------- 1 | user: nobody 2 | group: nogroup 3 | paths: 4 | - / 5 | - /config 6 | - /data -------------------------------------------------------------------------------- /.docker/sshd/Dockerfile.jinja: -------------------------------------------------------------------------------- 1 | FROM alpine:edge 2 | MAINTAINER Rémi Alvergnat 3 | 4 | RUN apk add --update openssh shadow && rm -rf /var/cache/apk/* 5 | 6 | RUN addgroup -S {{ stealthbox.ssh.login }} && adduser -S {{ stealthbox.ssh.login }} -G {{ stealthbox.ssh.login }} -s /bin/sh 7 | 8 | VOLUME /etc/ssh 9 | 10 | #fixuid-manual-entrypoint 11 | ADD /entrypoint.sh / 12 | CMD /entrypoint.sh 13 | -------------------------------------------------------------------------------- /.docker/sshd/entrypoint.jinja.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # generate host keys if not present 4 | ssh-keygen -A 5 | 6 | _password="${SSH_PASSWORD-$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-16};echo;)}" 7 | 8 | if [ -z "$SSH_PASSWORD" ]; then 9 | echo "SSH_PASSWORD: $_password" 10 | fi 11 | 12 | echo "{{ stealthbox.ssh.login }}:$_password" | chpasswd 13 | 14 | unset SSH_PASSWORD 15 | 16 | if [ -n "$HOST_UID" ]; then 17 | usermod -u "$HOST_UID" {{ stealthbox.ssh.login }} 18 | fi 19 | if [ -n "$HOST_GID" ]; then 20 | groupmod -g "$HOST_GID" {{ stealthbox.ssh.login }} 21 | fi 22 | 23 | # do not detach (-D), log to stderr (-e), passthrough other arguments 24 | exec /usr/sbin/sshd -D -e -------------------------------------------------------------------------------- /.docker/stealthbox-conf/Dockerfile.jinja: -------------------------------------------------------------------------------- 1 | FROM alpine:edge 2 | MAINTAINER Rémi Alvergnat 3 | 4 | RUN adduser -u 1337 -S box 5 | 6 | COPY conf /home/box/conf 7 | RUN chown -R box:nogroup /home/box 8 | USER box 9 | 10 | WORKDIR /home/box/conf 11 | VOLUME /home/box/conf 12 | 13 | CMD ["echo", "Stealthbox Conf Data Container"] -------------------------------------------------------------------------------- /.docker/stealthbox-conf/conf/stealthbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "password": "box12345", 3 | "deluge": { 4 | "password": null 5 | } 6 | } -------------------------------------------------------------------------------- /.docker/stealthbox-tools/Dockerfile.jinja: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | MAINTAINER Rémi Alvergnat 3 | 4 | RUN adduser -u 1337 -S box 5 | 6 | COPY tools /home/box/tools 7 | 8 | RUN chown -R box:nogroup /home/box 9 | USER box 10 | 11 | WORKDIR /home/box/tools 12 | VOLUME /home/box/tools 13 | 14 | CMD ["echo", "Stealthbox Tools Data Container"] -------------------------------------------------------------------------------- /.docker/stealthbox-tools/tools/ConfigParserPipe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import ConfigParser 4 | import StringIO 5 | 6 | import sys 7 | 8 | if __name__ == "__main__": 9 | config = ConfigParser.RawConfigParser() 10 | 11 | conf = "" 12 | while True: 13 | try: 14 | line = sys.stdin.readline() 15 | except KeyboardInterrupt: 16 | break 17 | 18 | conf += line 19 | 20 | if not line: 21 | break 22 | 23 | conf_file = StringIO.StringIO(conf) 24 | config.readfp(conf_file) 25 | config.write(sys.stdout) 26 | -------------------------------------------------------------------------------- /.docker/stealthbox-tools/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Toilal/stealthbox/e66646ca357e006cb8b7e03f8288ca0046764ee2/.docker/stealthbox-tools/tools/__init__.py -------------------------------------------------------------------------------- /.docker/stealthbox-tools/tools/boxpasswd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SOURCE="${BASH_SOURCE[0]}" 5 | while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink 6 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 7 | SOURCE="$(readlink "$SOURCE")" 8 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located 9 | done 10 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 11 | 12 | if [ ! "$(whoami)" == "box" ]; then 13 | echo "boxpasswd must run as 'box' user." 14 | exit 126; 15 | fi; 16 | 17 | if [ -z "$1" ]; then 18 | echo -n "New password: " 19 | read -s PASSWORD 20 | echo -e 21 | echo -n "New password (confirm): " 22 | read -s PASSWORD2 23 | echo -e 24 | 25 | if [ "$PASSWORD" != "$PASSWORD2" ]; then 26 | echo "Passwords doesn't match" 27 | exit 1 28 | fi 29 | else 30 | PASSWORD=$1 31 | fi 32 | 33 | CHECK="$(cracklib-check <<<"$PASSWORD")" 34 | if [[ $CHECK != *"OK" ]]; then 35 | echo "Password check fail!" 36 | echo $CHECK 37 | exit 2 38 | fi 39 | echo "Changing password..." 40 | 41 | if [ -f /home/box/.boxpasswd ]; then 42 | OLD_PASSWORD=$(cat /home/box/.boxpasswd) 43 | $DIR/passwd/autopasswd.sh "$OLD_PASSWORD" "$PASSWORD" 44 | else 45 | $DIR/passwd/autopasswd.sh "box12345" "$PASSWORD" 46 | fi 47 | 48 | # Store new password 49 | if [ -f /home/box/.boxpasswd ]; then 50 | chmod 600 /home/box/.boxpasswd 51 | fi 52 | echo -n $PASSWORD>/home/box/.boxpasswd 53 | chmod 400 /home/box/.boxpasswd 54 | 55 | # Deluge 56 | deluge_status=$(sv status deluge | cut -d ':' -f1) 57 | deluge_web_status=$(sv status deluge-web | cut -d ':' -f1) 58 | if [ "$deluge_status" == "run" ]; then sv -v -w 15 force-stop deluge; fi; 59 | if [ "$deluge_web_status" == "run" ]; then sv -v -w 15 force-stop deluge-web; fi; 60 | 61 | sed -ri "s/^(box:).*(:.*?)/\1$PASSWORD\2/" /home/box/deluge/auth 62 | 63 | deluge=$($DIR/passwd/passwd.deluge.py $PASSWORD) 64 | deluge_sha1=$(echo $deluge | cut -d ':' -f 1) 65 | deluge_salt=$(echo $deluge | cut -d ':' -f 2) 66 | 67 | sed -ri "s/(\"pwd_sha1\":).*\"(,?)\s*$/\1 \"$deluge_sha1\"\2/" /home/box/deluge/web.conf 68 | sed -ri "s/(\"pwd_salt\":).*\"(,?)\s*$/\1 \"$deluge_salt\"\2/" /home/box/deluge/web.conf 69 | 70 | if [ "$deluge_status" == "run" ]; then sv start deluge; fi; 71 | if [ "$deluge_web_status" == "run" ]; then sv start deluge-web; fi; 72 | 73 | # CouchPotato 74 | couchpotato_status=$(sv status couchpotato | cut -d ':' -f1) 75 | if [ "$couchpotato_status" == "run" ]; then sv -v -w 15 force-stop couchpotato; fi; 76 | 77 | couchpotato_md5=$(echo -n $PASSWORD| md5sum | cut -d ' ' -f 1) 78 | cat /home/box/couchpotato/settings.conf>$DIR/boxpasswd.tmp 79 | echo -e "\n[core]\npassword = $couchpotato_md5\n\n[deluge]\npassword = $PASSWORD\n">>$DIR/boxpasswd.tmp 80 | cat $DIR/boxpasswd.tmp | $DIR/tools/ConfigParserPipe.py> /home/box/couchpotato/settings.conf 81 | rm $DIR/boxpasswd.tmp 82 | 83 | if [ "$couchpotato_status" == "run" ]; then sv start couchpotato; fi; 84 | 85 | # medusa 86 | medusa_status=$(sv status medusa | cut -d ':' -f1) 87 | if [ "$medusa_status" == "run" ]; then sv -v -w 15 force-stop medusa; fi; 88 | 89 | cat /home/box/medusa/config.ini>$DIR/boxpasswd.tmp 90 | echo -e "\n[General]\nweb_password = $PASSWORD\n\n[TORRENT]\ntorrent_password = $PASSWORD\n">>$DIR/boxpasswd.tmp 91 | cat $DIR/boxpasswd.tmp | $DIR/tools/ConfigParserPipe.py> /home/box/medusa/config.ini 92 | rm $DIR/boxpasswd.tmp 93 | 94 | if [ "$medusa_status" == "run" ]; then sv start medusa; fi; 95 | 96 | # HeadPhones 97 | headphones_status=$(sv status headphones | cut -d ':' -f1) 98 | if [ "$headphones_status" == "run" ]; then sv -v -w 15 force-stop headphones; fi; 99 | 100 | cat /home/box/headphones/config.ini>$DIR/boxpasswd.tmp 101 | echo -e "\n[General]\nhttp_password = $PASSWORD\n">>$DIR/boxpasswd.tmp 102 | cat $DIR/boxpasswd.tmp | $DIR/tools/ConfigParserPipe.py> /home/box/headphones/config.ini 103 | rm $DIR/boxpasswd.tmp 104 | 105 | if [ "$headphones_status" == "run" ]; then sv start headphones; fi; 106 | 107 | # Pydio 108 | if [ -f "/home/box/pydio/plugins/conf.sql/pydio.db" ]; 109 | then 110 | pydio_hash=$(php -f $DIR/passwd/passwd.pydio.php "password=$PASSWORD") 111 | sqlite3 /home/box/pydio/plugins/conf.sql/pydio.db "UPDATE ajxp_users SET password='$pydio_hash' WHERE login='box'" 112 | fi 113 | 114 | echo "Password changed!" -------------------------------------------------------------------------------- /.docker/stealthbox-tools/tools/passwd/autopasswd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | old_pass=$1 3 | pass=$2 4 | 5 | passwd < 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | 10 | [*{.conf,.yml,.json,.md}] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /deluge/ 2 | /jackett/ 3 | /sonarr/ 4 | /radarr/ 5 | /lidarr/ 6 | __pycache__ 7 | ###> gfi-centre-ouest/docker-devbox ### 8 | /.docker/deluge/Dockerfile 9 | /.docker/stealthbox-tools/Dockerfile 10 | /.docker/stealthbox-conf/Dockerfile 11 | /.docker/sshd/Dockerfile 12 | /*ddb.local.* 13 | /.bash_enter 14 | /.bash_leave 15 | /.certs/deluge.stealthbox.test.key 16 | /.certs/deluge.stealthbox.test.crt 17 | /.certs/.signer.crt 18 | /.docker/deluge/fixuid.tar.gz 19 | /.docker/sshd/entrypoint.sh 20 | /.certs/jackett.stealthbox.test.key 21 | /.certs/jackett.stealthbox.test.crt 22 | /.docker/jackett/Dockerfile 23 | /.docker/jackett/fixuid.tar.gz 24 | /.certs/sonarr.stealthbox.test.key 25 | /.certs/sonarr.stealthbox.test.crt 26 | /.docker/sonarr/Dockerfile 27 | /.docker/radarr/Dockerfile 28 | /.docker/radarr/fixuid.tar.gz 29 | /.docker/sonarr/fixuid.tar.gz 30 | /.certs/radarr.stealthbox.test.key 31 | /.certs/radarr.stealthbox.test.crt 32 | /.docker/lidarr/Dockerfile 33 | /.docker/lidarr/fixuid.tar.gz 34 | /.certs/lidarr.stealthbox.test.key 35 | /.certs/lidarr.stealthbox.test.crt 36 | /docker-compose.yml 37 | ###< gfi-centre-ouest/docker-devbox ### 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Rémi Alvergnat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StealthBox 2 | 3 | Share your favorite content remotely without spoiling your home network. 4 | 5 | StealthBox is a set of [Docker](https://www.docker.com/) images, so you can install it on any linux server without 6 | polluting it. 7 | 8 | It embeds several applications that are configured together to automate file sharing in the cloud and download content 9 | to your home: 10 | 11 | - [Deluge](https://deluge-torrent.org/), a BitTorrent client. 12 | - [Jackett](https://github.com/Jackett/Jackett). 13 | - [Sonarr](https://sonarr.tv/). 14 | - [Radarr](https://radarr.video/). 15 | - [Lidarr](https://lidarr.audio/). 16 | 17 | ## Requirements 18 | 19 | - A linux box with a wildcard domain name configured (`domain.tld`) 20 | - [docker-devbox](https://github.com/gfi-centre-ouest/docker-devbox) 21 | 22 | ## Install 23 | 24 | - Clone the github repository 25 | 26 | ```bash 27 | $ git clone https://github.com/Toilal/stealthbox 28 | $ cd stealthbox 29 | ``` 30 | 31 | - Create `ddb.local.yml` file with the following content. You should customize your accounts and passwords. 32 | 33 | ```yaml 34 | core: 35 | env: 36 | current: prod 37 | domain: 38 | ext: domain.tld 39 | docker: 40 | reverse_proxy: 41 | certresolver: letsencrypt 42 | stealthbox: 43 | deluge: 44 | password: "box" 45 | ssh: 46 | login: "box" 47 | password: "box" 48 | ``` 49 | 50 | - Generate all files based on your configuration. 51 | 52 | ```bash 53 | $ ddb configure 54 | ``` 55 | 56 | - Build docker-compose stack. 57 | 58 | ```bash 59 | $ docker-compose build 60 | ``` 61 | 62 | - Start docker-compose stack. 63 | 64 | ```bash 65 | $ docker-compose up -d 66 | ``` 67 | 68 | ## URLs 69 | 70 | You can get URLs and ports of all services by running the following command 71 | 72 | ```bash 73 | $ ddb info 74 | ``` 75 | -------------------------------------------------------------------------------- /ddb.minimal.yml: -------------------------------------------------------------------------------- 1 | docker: 2 | disabled_services: ['jackett', 'sonarr', 'radarr', 'lidarr', 'flaresolverr'] -------------------------------------------------------------------------------- /ddb.yml: -------------------------------------------------------------------------------- 1 | stealthbox: 2 | deluge: 3 | password: "box" 4 | ssh: 5 | login: "box" 6 | password: "box" 7 | permissions: 8 | specs: 9 | .docker/sshd/entrypoint.sh: "+x" -------------------------------------------------------------------------------- /docker-compose.yml.jsonnet: -------------------------------------------------------------------------------- 1 | local ddb = import 'ddb.docker.libjsonnet'; 2 | 3 | local pp = std.extVar("docker.port_prefix"); 4 | local domain_ext = std.extVar("core.domain.ext"); 5 | local domain_sub = std.extVar("core.domain.sub"); 6 | local stealthbox_deluge_salt = std.extVar("stealthbox.deluge.salt"); 7 | local stealthbox_deluge_sha1 = std.extVar("stealthbox.deluge.sha1"); 8 | local stealthbox_ssh_login = std.extVar("stealthbox.ssh.login"); 9 | local stealthbox_ssh_password = std.extVar("stealthbox.ssh.password"); 10 | local host_gid = std.extVar("docker.user.gid"); 11 | local host_uid = std.extVar("docker.user.uid"); 12 | 13 | local domain = std.join('.', [domain_sub, domain_ext]); 14 | 15 | local compose = ddb.Compose({ 16 | services: { 17 | deluge: ddb.Build("deluge") + 18 | ddb.User() + 19 | ddb.VirtualHost(8112, "deluge." + domain, "deluge") + { 20 | environment+: { 21 | [if stealthbox_deluge_salt != null then 'DELUGE_SALT']: stealthbox_deluge_salt, 22 | [if stealthbox_deluge_sha1 != null then 'DELUGE_SHA1']: stealthbox_deluge_sha1 23 | }, 24 | volumes: [ 25 | ddb.path.project + "/deluge/config:/config", 26 | ddb.path.project + "/deluge/data:/data" 27 | ] 28 | }, 29 | jackett: ddb.Build("jackett") + 30 | ddb.User() + 31 | ddb.VirtualHost(9117, "jackett." + domain, "jackett") + { 32 | environment+: {}, 33 | volumes: [ 34 | ddb.path.project + "/jackett/config:/config", 35 | ddb.path.project + "/jackett/data:/data" 36 | ] 37 | }, 38 | flaresolverr: ddb.Image("flaresolverr/flaresolverr") + { 39 | environment+: { 40 | LOG_LEVEL: "info" 41 | }, 42 | }, 43 | sonarr: ddb.Build("sonarr") + 44 | ddb.User() + 45 | ddb.VirtualHost(8989, "sonarr." + domain, "sonarr") + { 46 | environment+: {}, 47 | volumes: [ 48 | ddb.path.project + "/sonarr/config:/config", 49 | ddb.path.project + "/sonarr/data:/data" 50 | ] 51 | }, 52 | radarr: ddb.Build("radarr") + 53 | ddb.User() + 54 | ddb.VirtualHost(7878, "radarr." + domain, "radarr") + { 55 | environment+: {}, 56 | volumes: [ 57 | ddb.path.project + "/radarr/config:/config", 58 | ddb.path.project + "/radarr/data:/data", 59 | ] 60 | }, 61 | lidarr: ddb.Build("lidarr") + 62 | ddb.User() + 63 | ddb.VirtualHost(8686, "lidarr." + domain, "lidarr") + { 64 | environment+: {}, 65 | volumes: [ 66 | ddb.path.project + "/lidarr/config:/lidarr", 67 | ddb.path.project + "/lidarr/data:/data", 68 | ] 69 | }, 70 | sshd: ddb.Build("sshd") + 71 | { 72 | ports: [pp+'22:22'], 73 | environment+: { 74 | [if stealthbox_ssh_password != null then 'SSH_PASSWORD']: stealthbox_ssh_password, 75 | HOST_GID: host_gid, 76 | HOST_UID: host_uid 77 | }, 78 | volumes+: [ 79 | "ssh-config:/etc/ssh" 80 | ] 81 | } 82 | } 83 | }); 84 | 85 | compose + { 86 | services+: { 87 | sshd+: { 88 | volumes+: std.filter(function(x) if x != null then true else false, [ 89 | if (std.objectHas(compose.services, "deluge")) then ddb.path.project + "/deluge:/home/" + stealthbox_ssh_login + "/deluge" else null, 90 | if (std.objectHas(compose.services, "jackett")) then ddb.path.project + "/jackett:/home/" + stealthbox_ssh_login + "/jackett" else null, 91 | if (std.objectHas(compose.services, "sonarr")) then ddb.path.project + "/sonarr:/home/" + stealthbox_ssh_login + "/sonarr" else null, 92 | if (std.objectHas(compose.services, "radarr")) then ddb.path.project + "/radarr:/home/" + stealthbox_ssh_login + "/radarr" else null, 93 | if (std.objectHas(compose.services, "lidarr")) then ddb.path.project + "/lidarr:/home/" + stealthbox_ssh_login + "/lidarr" else null, 94 | ]) 95 | } 96 | } 97 | } 98 | --------------------------------------------------------------------------------