├── .gitignore ├── .imeges ├── check.jpg ├── check1.jpg ├── check2.jpg ├── log.jpg ├── log1.jpg ├── log2.jpg └── permission.jpg ├── LICENSE ├── Makefile ├── README.md ├── build-package.sh └── src ├── INFO.sh ├── PACKAGE_ICON.PNG ├── PACKAGE_ICON_256.PNG ├── WIZARD_UIFILES ├── install_uifile ├── install_uifile_rus ├── uninstall_uifile └── uninstall_uifile_rus ├── conf ├── privilege └── resource ├── scripts ├── functions ├── installer ├── postinst ├── postuninst ├── postupgrade ├── preinst ├── preuninst ├── preupgrade ├── service-setup └── start-stop-status └── ui ├── TorrServer.sc ├── config └── images ├── torrserver-16.png ├── torrserver-24.png ├── torrserver-256.png ├── torrserver-32.png ├── torrserver-48.png ├── torrserver-64.png └── torrserver-72.png /.gitignore: -------------------------------------------------------------------------------- 1 | spk/ 2 | dest_bin/ 3 | build/ 4 | -------------------------------------------------------------------------------- /.imeges/check.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/.imeges/check.jpg -------------------------------------------------------------------------------- /.imeges/check1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/.imeges/check1.jpg -------------------------------------------------------------------------------- /.imeges/check2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/.imeges/check2.jpg -------------------------------------------------------------------------------- /.imeges/log.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/.imeges/log.jpg -------------------------------------------------------------------------------- /.imeges/log1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/.imeges/log1.jpg -------------------------------------------------------------------------------- /.imeges/log2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/.imeges/log2.jpg -------------------------------------------------------------------------------- /.imeges/permission.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/.imeges/permission.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 vladlenas 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TORRSERVER_VERSION="MatriX.135" 2 | PKG_VERSION="1.2.135" 3 | DSM="7.1" 4 | 5 | .PHONY: torrserver-% clean 6 | 7 | all: torrserver-amd64 torrserver-386 torrserver-arm64 torrserver-arm7 8 | 9 | torrserver-%: 10 | @./build-package.sh ${TORRSERVER_VERSION} $* ${PKG_VERSION} ${DSM} 11 | 12 | clean: 13 | rm -rf spk dest_bin build 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TorrServer package for Synology NAS 2 | Synology NAS package for DSM 7.1 based on TorrServer binaries https://github.com/YouROK/TorrServer/releases 3 | 4 | You can thank the TorrServer developer here https://github.com/YouROK/TorrServer#donate 5 | 6 | # Sopported Architecture 7 | * arm7 - armv7 alpine alpine4k armada370 armada375 armada38x armadaxp comcerto2k monaco 8 | * arm64 - aarch64 armv8 rtd1296 armada37xx 9 | * amd64 - apollolake avoton braswell broadwell broadwellnk broadwellnkv2 broadwellntbap bromolow cedarview denverton epyc7002 geminilake grantley kvmx64 purley r1000 v1000 x86_64 10 | * 386 - evansport x86 11 | 12 | # Automatic update 13 | Add https://grigi.lt/ to your Synology NAS Package Center sources! 14 | 15 | # Making packages from precompiled TorrServer binaries 16 | ``` 17 | git clone https://github.com/vladlenas/Synology-TorrServer.git 18 | cd Synology-TorrServer/ 19 | ``` 20 | ``` 21 | make 22 | ``` 23 | TorrServer version change 24 | ``` 25 | nano Makefile 26 | TORRSERVER_VERSION="MatriX.84" 27 | PKG_VERSION="1.2.84" 28 | ``` 29 | # Clear working directory 30 | ``` 31 | make clean 32 | ``` 33 | # Log file 34 | ``` 35 | /var/packages/TorrServer/var/TorrServer.log 36 | /var/log/packages/TorrServer.log 37 | ``` 38 | # Permission DSM 7.0 for write cache to hard drive. 39 | * `Control Panel > Shared Folder > Select Shared Folder > Edit > Permissions > System internal user > TorrServer user add read/write` 40 | 41 | # Credits and References 42 | * YouROK: https://github.com/YouROK 43 | * SynoCommunity: https://github.com/SynoCommunity/spksrc 44 | * Synology DSM 7.0 Developer Guide: https://help.synology.com/developer-guide/ 45 | * Architecture per Synology model: https://github.com/SynoCommunity/spksrc/wiki/Architecture-per-Synology-model 46 | -------------------------------------------------------------------------------- /build-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | TORRSERVER_VERSION=$1 6 | ARCH=$2 7 | PKG_VERSION=$3 8 | DSM=$4 9 | 10 | download_torrserver() { 11 | local base_url="https://github.com/YouROK/TorrServer/releases/download/${TORRSERVER_VERSION}" 12 | local bin_name="TorrServer-linux-${ARCH}" 13 | local src_bin="${base_url}/${bin_name}" 14 | local dest_bin="dest_bin" 15 | 16 | if [[ -f ${dest_bin}/TorrServer-linux-${ARCH} ]]; then 17 | echo ">>> Binaries already exist: ${bin_name}" 18 | return 19 | fi 20 | 21 | echo ">>> Downloading TorrServer-linux-${ARCH}:" 22 | wget -q -P ${dest_bin} ${src_bin} 23 | } 24 | 25 | download_ffprobe() { 26 | local tmp_download=$1 27 | local ffprobe_url="https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v6.1/ffprobe-6.1-linux-32.zip" 28 | local dest_bin="dest_bin" 29 | 30 | if [[ -f dest_bin/ffprobe ]]; then 31 | echo ">>> Binaries already exist: ffprobe" 32 | return 33 | fi 34 | 35 | echo ">>> Downloading ffprobe:" 36 | mkdir -p ${dest_bin} 37 | wget -q -O ${tmp_downloads}/ffprob.zip ${ffprobe_url} 38 | unzip -q ${tmp_downloads}/ffprob.zip -d dest_bin 39 | } 40 | 41 | make_inner_pkg() { 42 | local tmp_dir=$1 43 | local dest_dir=$2 44 | local dest_pkg="$dest_dir/package.tgz" 45 | local torrserver_bin="dest_bin/TorrServer-linux-${ARCH}" 46 | local ffprobe_bin="dest_bin/ffprobe" 47 | 48 | echo ">>> Making inner package.tgz" 49 | 50 | mkdir -p ${tmp_dir}/bin 51 | cp -a ${torrserver_bin} ${tmp_dir}/bin/TorrServer 52 | cp -a ${ffprobe_bin} ${tmp_dir}/bin 53 | chmod +x ${tmp_dir}/bin/* 54 | cp -r src/ui ${tmp_dir} 55 | 56 | pkg_size=$(du -sk "${tmp_dir}" | awk '{print $1}') 57 | echo "${pkg_size}" >>"$dest_dir/extractsize_tmp" 58 | 59 | ls --color=no $tmp_dir | tar -cJf $dest_pkg -C "$tmp_dir" -T /dev/stdin 60 | } 61 | 62 | make_spk() { 63 | local spk_tmp_dir=$1 64 | local spk_dest_dir="./spk" 65 | local pkg_size=$(cat ${spk_tmp_dir}/extractsize_tmp) 66 | local spk_filename="TorrServer-${DSM}-${TORRSERVER_VERSION}-${ARCH}.spk" 67 | 68 | echo ">>> Making spk: ${spk_filename}" 69 | mkdir -p ${spk_dest_dir} 70 | rm "${spk_tmp_dir}/extractsize_tmp" 71 | 72 | cp -r src/scripts $spk_tmp_dir 73 | cp -r src/PACKAGE_ICON_256.PNG $spk_tmp_dir 74 | cp -r src/PACKAGE_ICON.PNG $spk_tmp_dir 75 | cp -r src/conf/ $spk_tmp_dir 76 | cp -r src/WIZARD_UIFILES $spk_tmp_dir 77 | 78 | ./src/INFO.sh ${PKG_VERSION} ${ARCH} ${pkg_size} >"${spk_tmp_dir}"/INFO 79 | 80 | tar -cf "${spk_dest_dir}/${spk_filename}" -C "${spk_tmp_dir}" $(ls ${spk_tmp_dir}) 81 | } 82 | 83 | make_pkg() { 84 | mkdir -p ./build 85 | local pkg_temp_dir=$(mktemp -d -p ./build) 86 | local spk_temp_dir=$(mktemp -d -p ./build) 87 | 88 | make_inner_pkg ${pkg_temp_dir} ${spk_temp_dir} 89 | make_spk ${spk_temp_dir} 90 | echo ">>> Done" 91 | echo "" 92 | } 93 | 94 | main() { 95 | echo ">>> Building package for DSM-${DSM} ${TORRSERVER_VERSION} ${ARCH}" 96 | download_ffprobe 97 | download_torrserver 98 | make_pkg 99 | } 100 | 101 | main 102 | -------------------------------------------------------------------------------- /src/INFO.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PKG_VERSION=$1 4 | ARCH=$2 5 | PKG_SIZE=$3 6 | DSM=$4 7 | 8 | TIMESTAMP=$(date -u +%Y%m%d-%H:%M:%S) 9 | 10 | case $ARCH in 11 | amd64) 12 | PLATFORMS="x86_64 apollolake avoton braswell broadwell broadwellnk broadwellnkv2 broadwellntbap bromolow cedarview denverton epyc7002 geminilake grantley kvmx64 purley r1000 v1000" 13 | ;; 14 | 386) 15 | PLATFORMS="x86 evansport" 16 | ;; 17 | arm64) 18 | PLATFORMS="armv8 rtd1296 rtd1296b armada37xx" 19 | ;; 20 | arm7) 21 | PLATFORMS="armv7 alpine alpine4k armada370 armada375 armada38x armadaxp comcerto2k monaco" 22 | ;; 23 | arm5) 24 | PLATFORMS_ARM5="armv5 88f6281 88f628x" 25 | ;; 26 | *) 27 | echo "Unsupported architecture: ${ARCH}" 28 | exit 1 29 | ;; 30 | esac 31 | 32 | cat <TorrServer Matrix Authorization", 7 | "subitems": [{ 8 | "key": "wizard_enable_aut", 9 | "desc": "Yes" 10 | }] 11 | }, 12 | { 13 | "type": "textfield", 14 | "desc": "Username. Defaults to admin", 15 | "subitems": [{ 16 | "key": "wizard_username", 17 | "desc": "Username" 18 | }] 19 | }, 20 | { 21 | "type": "password", 22 | "desc": "Password. Defaults to admin", 23 | "subitems": [{ 24 | "key": "wizard_password", 25 | "desc": "Password" 26 | }] 27 | }, 28 | { 29 | "type": "textfield", 30 | "desc": "Permission for cache to shared folder", 31 | "subitems": [{ 32 | "key": "wizard_volume", 33 | "desc": "Shared folder", 34 | "defaultValue": "downloads", 35 | "validator": { 36 | "allowBlank": false, 37 | "regex": { 38 | "expr": "/^[\\w _-]+$/", 39 | "errorText": "Subdirectories are not supported." 40 | } 41 | } 42 | }] 43 | }, 44 | { 45 | "desc": "Please read Permission Management for details." 46 | } 47 | ] 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /src/WIZARD_UIFILES/install_uifile_rus: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "step_title": "TorrServer Авторизация и Разрешения", 4 | "items": [{ 5 | "type": "multiselect", 6 | "desc": "TorrServer Авторизация", 7 | "subitems": [{ 8 | "key": "wizard_enable_aut", 9 | "desc": "Да" 10 | }] 11 | }, 12 | { 13 | "type": "textfield", 14 | "desc": "Username. По умолчанию admin", 15 | "subitems": [{ 16 | "key": "wizard_username", 17 | "desc": "Username" 18 | }] 19 | }, 20 | { 21 | "type": "password", 22 | "desc": "Password. По умолчанию admin", 23 | "subitems": [{ 24 | "key": "wizard_password", 25 | "desc": "Password" 26 | }] 27 | }, 28 | { 29 | "type": "textfield", 30 | "desc": "Разрешение на кеширование в общую папку", 31 | "subitems": [{ 32 | "key": "wizard_volume", 33 | "desc": "Общая папка", 34 | "defaultValue": "downloads", 35 | "validator": { 36 | "allowBlank": false, 37 | "regex": { 38 | "expr": "/^[\\w _-]+$/", 39 | "errorText": "Субдиректории не поддерживаются." 40 | } 41 | } 42 | }] 43 | }, 44 | { 45 | "desc": "Пожалуйста прочтите Permission Management." 46 | } 47 | ] 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /src/WIZARD_UIFILES/uninstall_uifile: -------------------------------------------------------------------------------- 1 | [{ 2 | "step_title": "Uninstall package", 3 | "items": [{ 4 | "type": "singleselect", 5 | "desc": "Keep or delete package settings.", 6 | "subitems": [{ 7 | "key": "wizard_keep_data", 8 | "desc": "Uninstall only. Keep existing files for future re-installation.", 9 | "defaultValue": true 10 | }, { 11 | "key": "wizard_delete_data", 12 | "desc": "Erase all of the package data files. (Not Recoverable)", 13 | "defaultValue": false 14 | }] 15 | }] 16 | }] 17 | -------------------------------------------------------------------------------- /src/WIZARD_UIFILES/uninstall_uifile_rus: -------------------------------------------------------------------------------- 1 | [{ 2 | "step_title": "Uninstall package", 3 | "items": [{ 4 | "type": "singleselect", 5 | "desc": "Сохранить или удалить настройки пакета.", 6 | "subitems": [{ 7 | "key": "wizard_keep_data", 8 | "desc": "Удалить только. Сохраните существующие файлы для будущей переустановки.", 9 | "defaultValue": true 10 | }, { 11 | "key": "wizard_delete_data", 12 | "desc": "Удалить все файлы данных пакета. (Не подлежит восстановлению)", 13 | "defaultValue": false 14 | }] 15 | }] 16 | }] 17 | -------------------------------------------------------------------------------- /src/conf/privilege: -------------------------------------------------------------------------------- 1 | { 2 | "defaults": { 3 | "run-as": "package" 4 | }, 5 | "username": "TorrServer", 6 | "groupname": "TorrServer" 7 | } 8 | -------------------------------------------------------------------------------- /src/conf/resource: -------------------------------------------------------------------------------- 1 | { 2 | "usr-local-linker": { 3 | "bin": ["bin/TorrServer","bin/ffprobe"] 4 | }, 5 | "port-config": { 6 | "protocol-file": "ui/TorrServer.sc" 7 | }, 8 | "data-share": { 9 | "shares": [ 10 | { 11 | "name": "{{wizard_volume}}", 12 | "permission": { 13 | "rw": [ 14 | "TorrServer" 15 | ] 16 | } 17 | }], 18 | "once": "false" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/scripts/functions: -------------------------------------------------------------------------------- 1 | 2 | ### common installer functions and variables for synocommunity generic service installer 3 | # 4 | # functions are common for all DSM versions 5 | # 6 | # The script must be sh/ash compatible and not use bash syntax. 7 | # SRM and DSM < 6.0 have only the busybox built-in shell an not bash 8 | # 9 | 10 | # Tools shortcuts 11 | MV="/bin/mv -f" 12 | RM="/bin/rm -rf" 13 | CP="/bin/cp -rfp" 14 | MKDIR="/bin/mkdir -p" 15 | LN="/bin/ln -nsf" 16 | TEE="/usr/bin/tee -a" 17 | RSYNC="/bin/rsync -avh" 18 | TAR="/bin/tar" 19 | 20 | INST_ETC="/var/packages/${SYNOPKG_PKGNAME}/etc" 21 | INST_VARIABLES="${INST_ETC}/installer-variables" 22 | 23 | if [ -z "${SYNOPKG_PKGVAR}" ]; then 24 | # define SYNOPKG_PKGVAR for compatibility with DSM7 (replaces former INST_VAR) 25 | SYNOPKG_PKGVAR="${SYNOPKG_PKGDEST}/var" 26 | fi 27 | 28 | 29 | ### Functions library 30 | 31 | log_step () 32 | { 33 | install_log "===> Step $1. STATUS=${SYNOPKG_PKG_STATUS} USER=$USER GROUP=$GROUP SHARE_PATH=${SHARE_PATH}" 34 | } 35 | 36 | 37 | initialize_variables () 38 | { 39 | # Expect user to be set from package specific variables 40 | if [ -n "${USER}" -a -z "${USER_DESC}" ]; then 41 | USER_DESC="User running $SYNOPKG_PKGNAME" 42 | fi 43 | 44 | # Default description if group name provided by UI 45 | if [ -n "${GROUP}" -a -z "${GROUP_DESC}" ]; then 46 | GROUP_DESC="SynoCommunity Package Group" 47 | fi 48 | 49 | # Extract share volume and share name from download location if provided 50 | if [ -n "${SHARE_PATH}" ]; then 51 | if [ -n "${wizard_volume}" ]; then 52 | SHARE_PATH="${wizard_volume}/${SHARE_PATH}" 53 | fi 54 | SHARE_VOLUME=$(echo "${SHARE_PATH}" | awk -F/ '{print "/"$2}') 55 | SHARE_NAME=$(echo "${SHARE_PATH}" | awk -F/ '{print $3}') 56 | fi 57 | } 58 | 59 | 60 | install_python_virtualenv () 61 | { 62 | # Output Python version (confirming PATH) 63 | python3 --version 64 | 65 | # Create a Python virtualenv 66 | python3 -m venv --system-site-packages ${SYNOPKG_PKGDEST}/env 67 | 68 | # Update to latest pip package installer 69 | python3 -m pip install --upgrade pip 70 | 71 | if [ ${SYNOPKG_DSM_VERSION_MAJOR} -lt 7 ]; then 72 | set_unix_permissions ${SYNOPKG_PKGDEST}/env 73 | fi 74 | } 75 | 76 | 77 | install_python_wheels () 78 | { 79 | # default PATH to wheelhouse 80 | cachedir=${SYNOPKG_PKGVAR}/pip-cache 81 | wheelhouse=${SYNOPKG_PKGDEST}/share/wheelhouse 82 | requirements=${wheelhouse}/requirements.txt 83 | 84 | # Install the wheels 85 | echo "Install packages from wheels" 86 | if [ -s ${requirements} ]; then 87 | echo "Install packages from wheels [${requirements}]" 88 | pip install $([ $# -gt 0 ] && echo $*) \ 89 | --force-reinstall \ 90 | --cache-dir ${cachedir} \ 91 | --find-links ${wheelhouse} \ 92 | --requirement ${requirements} 93 | fi 94 | 95 | if [ ${SYNOPKG_DSM_VERSION_MAJOR} -lt 7 ]; then 96 | set_unix_permissions ${SYNOPKG_PKGDEST}/env 97 | fi 98 | 99 | echo "Installed modules:" 100 | pip freeze 101 | } 102 | 103 | 104 | # function to read and export variables from a text file 105 | # empty lines and lines starting with # are ignored 106 | # we cannot 'source' the file to load the variables, when values have special characters like <, >, ... 107 | load_variables_from_file () 108 | { 109 | if [ -n "$1" -a -r "$1" ]; then 110 | while read -r _line; do 111 | if [ "$(echo ${_line} | grep -v ^[/s]*#)" != "" ]; then 112 | _key="$(echo ${_line} | cut --fields=1 --delimiter==)" 113 | _value="$(echo ${_line} | cut --fields=2- --delimiter==)" 114 | export "${_key}=${_value}" 115 | fi 116 | done < "$1" 117 | fi 118 | } 119 | 120 | 121 | save_wizard_variables () 122 | { 123 | if [ -e "${INST_VARIABLES}" ]; then 124 | $RM "${INST_VARIABLES}" 125 | fi 126 | if [ -n "${GROUP}" ]; then 127 | echo "GROUP=${GROUP}" >> ${INST_VARIABLES} 128 | fi 129 | if [ -n "${SHARE_PATH}" ]; then 130 | echo "SHARE_PATH=${SHARE_PATH}" >> ${INST_VARIABLES} 131 | fi 132 | } 133 | -------------------------------------------------------------------------------- /src/scripts/installer: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # DSM 5 -> 7 upgrade path: 4 | # - Not supported 5 | # DSM 6 -> 7 upgrade path: 6 | # - files are migrated from ${SYNOPKG_PKGDEST}/var to ${SYNOPKG_PKGVAR} 7 | 8 | # installer log is not writable, use for reference only. 9 | INST_LOG="/var/log/packages/${SYNOPKG_PKGNAME}.log" 10 | 11 | # Optional FWPORTS file 12 | FWPORTS_FILE="/var/packages/${SYNOPKG_PKGNAME}/conf/${SYNOPKG_PKGNAME}.sc" 13 | 14 | # Temporary directory when the package is upgrading 15 | TMP_DIR="${SYNOPKG_TEMP_UPGRADE_FOLDER}" 16 | 17 | install_log () 18 | { 19 | local _msg_="$@" 20 | if [ -z "${_msg_}" ]; then 21 | # read multiline from stdin 22 | while IFS=$'\n' read -r line; do 23 | install_log "${line}" 24 | done 25 | else 26 | # stderr goes to /var/log/packages/{package}.log 27 | # stdout goes to dialog in package manager ui. 28 | echo -e "$(date +'%Y/%m/%d %H:%M:%S')\t${_msg_}" 1>&2 29 | fi 30 | } 31 | 32 | # Invoke shell function if available 33 | call_func () 34 | { 35 | FUNC=$1 36 | if type "${FUNC}" 2>/dev/null | grep -q 'function' 2>/dev/null; then 37 | install_log "Begin ${FUNC}" 38 | LOG=$2 39 | ARG=$3 40 | if [ -z "${LOG}" ]; then 41 | if [ -z "${ARG}" ]; then 42 | eval ${FUNC} 43 | else 44 | eval ${FUNC} ${ARG} 45 | fi 46 | else 47 | if [ -z "${ARG}" ]; then 48 | eval ${FUNC} 2>&1 | ${LOG} 49 | else 50 | eval ${FUNC} ${ARG} 2>&1 | ${LOG} 51 | fi 52 | fi 53 | install_log "End ${FUNC}" 54 | fi 55 | } 56 | 57 | # Source installer variables and functions 58 | INST_FUNCTIONS=$(dirname $0)"/functions" 59 | if [ -r "${INST_FUNCTIONS}" ]; then 60 | . "${INST_FUNCTIONS}" 61 | fi 62 | 63 | 64 | # Source package specific variables and functions 65 | SVC_SETUP=$(dirname $0)"/service-setup" 66 | if [ -r "${SVC_SETUP}" ]; then 67 | . "${SVC_SETUP}" 68 | fi 69 | 70 | 71 | # Load (wizard) variables stored by postinst 72 | call_func "load_variables_from_file" install_log ${INST_VARIABLES} 73 | 74 | # init variables either from ${INST_VARIABLES}, from package or from wizard 75 | call_func "initialize_variables" 76 | 77 | 78 | ### Functions library 79 | 80 | # 81 | # syncronize all files from var folder in spk (extracted to target/var) 82 | # (@appdata) /var/packages//target/var -> /var/packages//var 83 | # 84 | syno_sync_var_folder () { 85 | # take all files that do not alredy exist and rename the other files to *.new to avoid overwriting 86 | # existing configuration files. 87 | # finally remove the target/var folder that is not used under DSM 7. 88 | if [ -d ${SYNOPKG_PKGDEST}/var -a "$(ls -A ${SYNOPKG_PKGDEST}/var 2>/dev/null)" ]; then 89 | 90 | echo "Install files from var folder" 91 | 92 | # Move all files except existing 93 | echo "$RSYNC --ignore-existing --remove-source-files ${SYNOPKG_PKGDEST}/var/ ${SYNOPKG_PKGVAR}" 94 | $RSYNC --ignore-existing --remove-source-files ${SYNOPKG_PKGDEST}/var/ ${SYNOPKG_PKGVAR} 95 | 96 | # Rename any remaining (thus pre-existing) files with .new suffix 97 | find ${SYNOPKG_PKGDEST}/var -type f -exec sh -c 'x="{}"; mv "$x" "${x}.new"' \; 98 | 99 | # Move .new files (overwrite existing) 100 | echo "$RSYNC --remove-source-files ${SYNOPKG_PKGDEST}/var/ ${SYNOPKG_PKGVAR}" 101 | $RSYNC --remove-source-files ${SYNOPKG_PKGDEST}/var/ ${SYNOPKG_PKGVAR} 102 | 103 | # Remove var folder extraced from spk 104 | $RM ${SYNOPKG_PKGDEST}/var 105 | fi 106 | } 107 | 108 | set_unix_permissions () 109 | { 110 | echo "Notice: set_unix_permissions() is no longer required on DSM7." 111 | } 112 | 113 | syno_remove_user () 114 | { 115 | echo "${SYNOPKG_PKGNAME} has not been updated to DSM7 yet. syno_remove_user() is no longer supported." 116 | } 117 | 118 | syno_group_create () 119 | { 120 | echo "${SYNOPKG_PKGNAME} has not been updated to DSM7 yet. syno_group_create() is no longer supported." 121 | } 122 | 123 | syno_group_remove () 124 | { 125 | echo "${SYNOPKG_PKGNAME} has not been updated to DSM7 yet. syno_group_remove() is no longer supported." 126 | } 127 | 128 | syno_user_add_to_group () 129 | { 130 | echo "${SYNOPKG_PKGNAME} has not been updated to DSM7 yet. syno_user_add_to_group() is no longer supported." 131 | } 132 | 133 | set_syno_permissions () 134 | { 135 | echo "${SYNOPKG_PKGNAME} has not been updated to DSM7 yet. set_syno_permissions() is no longer supported." 136 | } 137 | 138 | syno_user_add_to_legacy_group () { 139 | echo "${SYNOPKG_PKGNAME} has not been updated to DSM7 yet. syno_user_add_to_legacy_group() is no longer supported." 140 | } 141 | 142 | 143 | ### Generic package behaviors 144 | 145 | preinst () 146 | { 147 | log_step "preinst" 148 | call_func "validate_preinst" 149 | call_func "service_preinst" install_log 150 | 151 | # Check volume exists 152 | if [ -n "${SHARE_PATH}" ]; then 153 | if [ ! -d "${SHARE_VOLUME}" ]; then 154 | echo "ERROR: Volume ${SHARE_VOLUME} does not exist." | $TEE 1>&2 155 | exit 1 156 | fi 157 | fi 158 | 159 | exit 0 160 | } 161 | 162 | postinst () 163 | { 164 | log_step "postinst" 165 | call_func "save_wizard_variables" install_log 166 | 167 | # copy target/var data to permanent storage 168 | # and don't override old configurations 169 | call_func "syno_sync_var_folder" install_log 170 | 171 | call_func "service_postinst" install_log 172 | 173 | if [ -n "${LOG_FILE}" ]; then 174 | echo "Installation log: ${INST_LOG}" >> ${LOG_FILE} 175 | fi 176 | 177 | exit 0 178 | } 179 | 180 | preuninst () 181 | { 182 | log_step "preuninst" 183 | call_func "validate_preuninst" 184 | call_func "service_preuninst" install_log 185 | exit 0 186 | } 187 | 188 | postuninst () 189 | { 190 | log_step "postuninst" 191 | call_func "service_postuninst" install_log 192 | 193 | if [ "$wizard_delete_data" = "true" ]; then 194 | echo "Removing files..." | install_log 195 | if [ "$(ls -A ${SYNOPKG_PKGHOME})" != "" ]; then 196 | find ${SYNOPKG_PKGHOME} -mindepth 1 -delete -print | install_log 197 | fi 198 | 199 | if [ "$(ls -A ${SYNOPKG_PKGVAR})" != "" ]; then 200 | find ${SYNOPKG_PKGVAR} -mindepth 1 -delete -print | install_log 201 | fi 202 | 203 | if [ "$(ls -A /var/packages/${SYNOPKG_PKGNAME}/etc)" != "" ]; then 204 | find /var/packages/${SYNOPKG_PKGNAME}/etc -mindepth 1 -delete -print | install_log 205 | fi 206 | fi 207 | exit 0 208 | } 209 | 210 | preupgrade () 211 | { 212 | log_step "preupgrade" 213 | call_func "validate_preupgrade" 214 | 215 | # dsm6 -> dsm7 216 | if [ -d ${SYNOPKG_PKGDEST}/var ]; then 217 | if [ ! -d ${SYNOPKG_PKGVAR} -o $(realpath ${SYNOPKG_PKGVAR}) = $(realpath ${SYNOPKG_PKGDEST}/var) ]; then 218 | # Copy to temporary directory if upgrading from DSM6 -> DSM7 219 | # Then restore once to permanent storage at postupgrade 220 | if [ -d ${SYNOPKG_PKGDEST}/var -a "$(ls -A ${SYNOPKG_PKGDEST}/var 2>/dev/null)" ]; then 221 | echo "Backup target/var folder used under DSM6" | install_log 222 | echo "$RSYNC ${SYNOPKG_PKGDEST}/var/ ${TMP_DIR}" | install_log 223 | $RSYNC ${SYNOPKG_PKGDEST}/var/ ${TMP_DIR} 2>&1 | install_log 224 | # remove DSM6 target/var folder 225 | rm -rf ${SYNOPKG_PKGDEST}/var 2>&1 | install_log 226 | fi 227 | fi 228 | fi 229 | 230 | call_func "service_preupgrade" install_log 231 | call_func "service_save" install_log 232 | exit 0 233 | } 234 | 235 | postupgrade () 236 | { 237 | log_step "postupgrade" 238 | 239 | call_func "service_restore" install_log 240 | 241 | # dsm6 -> dsm7 242 | if [ -d ${TMP_DIR} -a "$(ls -A ${TMP_DIR} 2>/dev/null)" ]; then 243 | # Restore once to permanent storage at postupgrade 244 | echo "Resore var folder from DSM6" | install_log 245 | echo "$RSYNC ${TMP_DIR}/ ${SYNOPKG_PKGVAR}" | install_log 246 | $RSYNC ${TMP_DIR}/ ${SYNOPKG_PKGVAR} 2>&1 | install_log 247 | fi 248 | 249 | # dsm7: Now sync in any new files to permanent storage 250 | call_func "syno_sync_var_folder" install_log 251 | call_func "service_postupgrade" install_log 252 | 253 | exit 0 254 | } 255 | -------------------------------------------------------------------------------- /src/scripts/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . $(dirname $0)/installer 3 | $(basename $0) > $SYNOPKG_TEMP_LOGFILE 4 | -------------------------------------------------------------------------------- /src/scripts/postuninst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . $(dirname $0)/installer 3 | $(basename $0) > $SYNOPKG_TEMP_LOGFILE 4 | -------------------------------------------------------------------------------- /src/scripts/postupgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . $(dirname $0)/installer 3 | $(basename $0) > $SYNOPKG_TEMP_LOGFILE 4 | -------------------------------------------------------------------------------- /src/scripts/preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . $(dirname $0)/installer 3 | $(basename $0) > $SYNOPKG_TEMP_LOGFILE 4 | -------------------------------------------------------------------------------- /src/scripts/preuninst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . $(dirname $0)/installer 3 | $(basename $0) > $SYNOPKG_TEMP_LOGFILE 4 | -------------------------------------------------------------------------------- /src/scripts/preupgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . $(dirname $0)/installer 3 | $(basename $0) > $SYNOPKG_TEMP_LOGFILE 4 | -------------------------------------------------------------------------------- /src/scripts/service-setup: -------------------------------------------------------------------------------- 1 | ### Generic variables and functions 2 | ### ------------------------------- 3 | 4 | if [ -z "${SYNOPKG_PKGNAME}" ] || [ -z "${SYNOPKG_DSM_VERSION_MAJOR}" ]; then 5 | echo "Error: Environment variables are not set." 1>&2; 6 | echo "Please run me using synopkg instead. Example: \"synopkg start [packagename]\"" 1>&2; 7 | exit 1 8 | fi 9 | 10 | USER="TorrServer" 11 | EFF_USER="TorrServer" 12 | 13 | # start-stop-status script redirect stdout/stderr to LOG_FILE 14 | LOG_FILE="${SYNOPKG_PKGVAR}/${SYNOPKG_PKGNAME}.log" 15 | 16 | # Service command has to deliver its pid into PID_FILE 17 | PID_FILE="${SYNOPKG_PKGVAR}/${SYNOPKG_PKGNAME}.pid" 18 | 19 | # Package config files 20 | CONFIG_DIR="${SYNOPKG_PKGVAR}" 21 | 22 | ### Package specific variables and functions 23 | ### ---------------------------------------- 24 | 25 | if [ -r "${CONFIG_DIR}/accs.db" ]; then 26 | SERVICE_COMMAND="${SYNOPKG_PKGDEST}/bin/TorrServer -d ${SYNOPKG_PKGVAR} -a -l ${LOG_FILE}" 27 | else 28 | SERVICE_COMMAND="${SYNOPKG_PKGDEST}/bin/TorrServer -d ${SYNOPKG_PKGVAR} -l ${LOG_FILE}" 29 | fi 30 | 31 | SVC_BACKGROUND=y 32 | SVC_WRITE_PID=y 33 | 34 | service_postinst () 35 | { 36 | if [ "${SYNOPKG_PKG_STATUS}" = "INSTALL" ]; then 37 | if [ "${wizard_enable_aut}" = "true" ]; then 38 | echo "{" >> ${SYNOPKG_PKGVAR}/accs.db | install_log 39 | echo '"@user@":"@password@"' >> ${SYNOPKG_PKGVAR}/accs.db | install_log 40 | echo "}" >> ${SYNOPKG_PKGVAR}/accs.db | install_log 41 | 42 | sed -i -e "s|@user@|${wizard_username:=admin}|g" ${SYNOPKG_PKGVAR}/accs.db | install_log 43 | sed -i -e "s|@password@|${wizard_password:=admin}|g" ${SYNOPKG_PKGVAR}/accs.db | install_log 44 | fi 45 | fi 46 | } 47 | 48 | service_preupgrade () 49 | { 50 | # Copy config files and removing old files. 51 | if [ -d ${SYNOPKG_PKGDEST}/config/ -a "$(ls -A ${SYNOPKG_PKGDEST}/config/ 2>/dev/null)" ]; then 52 | #Removing old files used before ver. 1.2.125. 53 | if [ -r "${SYNOPKG_PKGDEST}/config/${SYNOPKG_PKGNAME}.sc" ]; then 54 | $RM ${SYNOPKG_PKGDEST}/config/${SYNOPKG_PKGNAME}.sc | install_log 55 | fi 56 | 57 | # Copy config files to temporary directory. 58 | echo "Backup target/config folder" | install_log 59 | echo "$RSYNC ${SYNOPKG_PKGDEST}/config/ ${TMP_DIR}" | install_log 60 | $RSYNC ${SYNOPKG_PKGDEST}/config/ ${TMP_DIR} 2>&1 | install_log 61 | fi 62 | } 63 | -------------------------------------------------------------------------------- /src/scripts/start-stop-status: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Default display name 4 | DNAME="${SYNOPKG_PKGNAME}" 5 | 6 | if [ "$SYNOPKG_DSM_VERSION_MAJOR" -lt 7 ]; then 7 | # define SYNOPKG_PKGVAR for forward compatibility 8 | SYNOPKG_PKGVAR="${SYNOPKG_PKGDEST}/var" 9 | fi 10 | 11 | # Source package specific variable and functions 12 | SVC_SETUP=$(dirname $0)"/service-setup" 13 | if [ -r "${SVC_SETUP}" ]; then 14 | . "${SVC_SETUP}" 15 | fi 16 | 17 | 18 | # Invoke shell function if available 19 | call_func () 20 | { 21 | FUNC=$1 22 | if type "${FUNC}" 2>/dev/null | grep -q 'function' 2>/dev/null; then 23 | echo "Begin ${FUNC}" >> ${LOG_FILE} 24 | eval ${FUNC} >> ${LOG_FILE} 25 | echo "End ${FUNC}" >> ${LOG_FILE} 26 | fi 27 | } 28 | 29 | 30 | start_daemon () 31 | { 32 | i=0 33 | if [ -z "${SVC_QUIET}" ]; then 34 | if [ -z "${SVC_KEEP_LOG}" ]; then 35 | date > ${LOG_FILE} 36 | else 37 | date >> ${LOG_FILE} 38 | fi 39 | fi 40 | call_func "service_prestart" 41 | printf "%s" "$SERVICE_COMMAND" | while read -r service || [ -n "$service" ] 42 | do 43 | i=$((i + 1)) 44 | if [ -z "${SVC_QUIET}" ]; then 45 | echo "Starting ${DNAME} command ${service}" >> ${LOG_FILE} 46 | fi 47 | if [ -n "${service}" ]; then 48 | if [ -n "${SVC_NO_REDIRECT}" ]; then 49 | OUT="/dev/null" 50 | else 51 | OUT="${LOG_FILE}" 52 | fi 53 | if [ -n "${USER}" -a -n "${SYNOPKG_DSM_VERSION_MAJOR}" -a "$SYNOPKG_DSM_VERSION_MAJOR" -lt 6 ]; then 54 | if [ -z "${SVC_CWD}" ]; then 55 | SVC_CD="" 56 | else 57 | SVC_CD="cd ${SVC_CWD}; " 58 | fi 59 | if [ -n "${SYNOPKG_DSM_VERSION_MAJOR}" ] && [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then 60 | SU="su ${EFF_USER} -s" 61 | else 62 | SU="" 63 | fi 64 | if [ -z "${SVC_BACKGROUND}" ]; then 65 | $SU /bin/sh -c "${SVC_CD}${service}" >> ${OUT} 2>&1 66 | else 67 | $SU /bin/sh -c "${SVC_CD}${service}" >> ${OUT} 2>&1 & 68 | fi 69 | 70 | else 71 | # DSM 6 user is set by conf/privilege 72 | if [ -n "${SVC_CWD}" ]; then 73 | cd "${SVC_CWD}" 74 | fi 75 | if [ -z "${SVC_BACKGROUND}" ]; then 76 | ${service} >> ${OUT} 2>&1 77 | else 78 | ${service} >> ${OUT} 2>&1 & 79 | fi 80 | fi 81 | if [ -n "${SVC_WRITE_PID}" -a -n "${SVC_BACKGROUND}" -a -n "${PID_FILE}" ]; then 82 | [ $i -eq 1 ] && echo -ne "$!" > ${PID_FILE} || echo -ne " $!" >> ${PID_FILE} 83 | else 84 | wait_for_status 0 ${SVC_WAIT_TIMEOUT:=20} 85 | fi 86 | fi 87 | done 88 | } 89 | 90 | stop_daemon () 91 | { 92 | if [ -n "${PID_FILE}" -a -r "${PID_FILE}" ]; then 93 | for pid in $(cat "${PID_FILE}") 94 | do 95 | if [ -z "${SVC_QUIET}" ]; then 96 | date >> ${LOG_FILE} 97 | echo "Stopping ${DNAME} service : $(ps -p${pid} -o comm=) (${pid})" >> ${LOG_FILE} 98 | fi 99 | kill -TERM ${pid} >> ${LOG_FILE} 2>&1 100 | wait_for_status 1 ${SVC_WAIT_TIMEOUT:=20} ${pid} || kill -KILL ${pid} >> ${LOG_FILE} 2>&1 101 | done 102 | if [ -f "${PID_FILE}" ]; then 103 | rm -f "${PID_FILE}" > /dev/null 104 | fi 105 | fi 106 | call_func "service_poststop" 107 | } 108 | 109 | #------------------------------------------------------ 110 | # daemon_status() 111 | # $1: PID to check, if empty use ${PID_FILE} 112 | # status: Keeps track of kill -0 exit status 113 | # 114 | # Return 0 when all pid are OK, else return 1 115 | #------------------------------------------------------ 116 | daemon_status () 117 | { 118 | status=0 119 | [ -z "${1}" ] && pid_list=$(cat ${PID_FILE} 2>/dev/null) || pid_list=${1} 120 | if [ -n "${pid_list}" ]; then 121 | for pid in ${pid_list} 122 | do 123 | kill -0 ${pid} > /dev/null 2>&1 124 | let status=$status+$? 125 | done 126 | if [ $status -ne 0 ]; then 127 | rm -f "${PID_FILE}" > /dev/null 128 | return 1 129 | else 130 | return 0 131 | fi 132 | else 133 | return 1 134 | fi 135 | } 136 | 137 | #------------------------------------------------------ 138 | # wait_for_status() 139 | # $1: expected return from daemon_status() call 140 | # $2: timeout (e.g. number of loop to be done) 141 | # $3: PID to check being passed to daemon_status() 142 | # counter: Number of 1sec iteration to wait until 143 | # the return value from daemon_status() 144 | # match expected value 145 | # 146 | # Wait for a duration of $counter seconds for the 147 | # return value from daemon_status(). If it match 148 | # return 0 else return 1 if wait time is over. 149 | #------------------------------------------------------ 150 | wait_for_status () 151 | { 152 | # default value: 20 seconds 153 | counter=${2} 154 | counter=${counter:=20} 155 | while [ ${counter} -gt 0 ]; do 156 | daemon_status ${3} 157 | [ $? -eq $1 ] && return 158 | let counter=counter-1 159 | sleep 1 160 | done 161 | return 1 162 | } 163 | 164 | case $1 in 165 | start) 166 | if daemon_status; then 167 | echo "${DNAME} is already running" >> ${LOG_FILE} 168 | exit 0 169 | else 170 | echo "Starting ${DNAME} ..." >> ${LOG_FILE} 171 | start_daemon 172 | exit $? 173 | fi 174 | ;; 175 | stop) 176 | if daemon_status; then 177 | echo "Stopping ${DNAME} ..." >> ${LOG_FILE} 178 | stop_daemon 179 | exit $? 180 | else 181 | echo "${DNAME} is not running" >> ${LOG_FILE} 182 | exit 0 183 | fi 184 | ;; 185 | status) 186 | if daemon_status; then 187 | echo "${DNAME} is running" 188 | exit 0 189 | else 190 | echo "${DNAME} is not running" 191 | exit 3 192 | fi 193 | ;; 194 | log) 195 | # log output for DSM < 6 196 | if [ -n "${LOG_FILE}" -a -r "${LOG_FILE}" ]; then 197 | # Shorten long logs to last 100 lines 198 | TEMP_LOG_FILE="${SYNOPKG_PKGVAR}/${SYNOPKG_PKGNAME}_temp.log" 199 | # Clear any previous log 200 | echo "Full log: ${LOG_FILE}" > "${TEMP_LOG_FILE}" 201 | tail -n100 "${LOG_FILE}" >> "${TEMP_LOG_FILE}" 202 | echo "${TEMP_LOG_FILE}" 203 | fi 204 | exit 0 205 | ;; 206 | *) 207 | exit 1 208 | ;; 209 | esac 210 | -------------------------------------------------------------------------------- /src/ui/TorrServer.sc: -------------------------------------------------------------------------------- 1 | [torrserver] 2 | title="TorrServer MatriX" 3 | desc="TorrServer MatriX" 4 | port_forward="yes" 5 | dst.ports="8090/tcp" 6 | -------------------------------------------------------------------------------- /src/ui/config: -------------------------------------------------------------------------------- 1 | { 2 | ".url": { 3 | "SYNO.SDS.TorrServer": { 4 | "title": "TorrServer MatriX", 5 | "desc": "TorrServer MatriX", 6 | "icon": "images/torrserver-{0}.png", 7 | "type": "url", 8 | "protocol": "http", 9 | "port": "8090", 10 | "url": "/", 11 | "allUsers": true, 12 | "grantPrivilege": "all", 13 | "advanceGrantPrivilege": true 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/images/torrserver-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/src/ui/images/torrserver-16.png -------------------------------------------------------------------------------- /src/ui/images/torrserver-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/src/ui/images/torrserver-24.png -------------------------------------------------------------------------------- /src/ui/images/torrserver-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/src/ui/images/torrserver-256.png -------------------------------------------------------------------------------- /src/ui/images/torrserver-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/src/ui/images/torrserver-32.png -------------------------------------------------------------------------------- /src/ui/images/torrserver-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/src/ui/images/torrserver-48.png -------------------------------------------------------------------------------- /src/ui/images/torrserver-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/src/ui/images/torrserver-64.png -------------------------------------------------------------------------------- /src/ui/images/torrserver-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladlenas/Synology-TorrServer/b6d9f735c8eb175a8e003626be2a8d11e40c434e/src/ui/images/torrserver-72.png --------------------------------------------------------------------------------