Merge branch 'v3' into 'master'
V3 - The big one Closes #38, #35, #30, #3, and #39 See merge request rogs/yams!22
This commit is contained in:
commit
76bbdc9537
Binary file not shown.
@ -1,4 +1,2 @@
|
||||
version: "3"
|
||||
|
||||
# services:
|
||||
# Add your custom services here!
|
||||
|
@ -1,5 +1,3 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
# <media_service> is used to serve your media to the client devices
|
||||
<media_service>:
|
||||
@ -11,8 +9,8 @@ services:
|
||||
- PGID=${PGID}
|
||||
- VERSION=docker
|
||||
volumes:
|
||||
- ${MEDIA_DIRECTORY}/movies:/data/movies
|
||||
- ${MEDIA_DIRECTORY}/tvshows:/data/tvshows
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${MEDIA_DIRECTORY}:/data
|
||||
- ${INSTALL_DIRECTORY}/config/${MEDIA_SERVICE}:/config
|
||||
ports: # plex
|
||||
- 8096:8096 # plex
|
||||
@ -20,18 +18,36 @@ services:
|
||||
|
||||
# qBitorrent is used to download torrents
|
||||
qbittorrent:
|
||||
image: lscr.io/linuxserver/qbittorrent:4.6.0
|
||||
image: lscr.io/linuxserver/qbittorrent
|
||||
container_name: qbittorrent
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- WEBUI_PORT=8080
|
||||
- WEBUI_PORT=8081
|
||||
volumes:
|
||||
- ${MEDIA_DIRECTORY}/downloads:/downloads
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${MEDIA_DIRECTORY}:/data
|
||||
- ${INSTALL_DIRECTORY}/config/qbittorrent:/config
|
||||
restart: unless-stopped
|
||||
ports: # qbittorrent
|
||||
- 8080:8080 # qbittorrent
|
||||
- 8081:8081 # qbittorrent
|
||||
#network_mode: "service:gluetun"
|
||||
|
||||
# SABnzbd is used to download from usenet
|
||||
sabnzbd:
|
||||
image: lscr.io/linuxserver/sabnzbd:latest
|
||||
container_name: sabnzbd
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=America/Montevideo
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${MEDIA_DIRECTORY}:/data
|
||||
- ${INSTALL_DIRECTORY}/config/sabnzbd:/config
|
||||
ports: # sabnzbd
|
||||
- 8080:8080 # sabnzbd
|
||||
restart: unless-stopped
|
||||
#network_mode: "service:gluetun"
|
||||
|
||||
# Sonarr is used to query, add downloads to the download queue and index TV shows
|
||||
@ -43,8 +59,8 @@ services:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${MEDIA_DIRECTORY}/tvshows:/tv
|
||||
- ${MEDIA_DIRECTORY}/downloads:/downloads
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${MEDIA_DIRECTORY}:/data
|
||||
- ${INSTALL_DIRECTORY}/config/sonarr:/config
|
||||
ports:
|
||||
- 8989:8989
|
||||
@ -59,8 +75,8 @@ services:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${MEDIA_DIRECTORY}/movies:/movies
|
||||
- ${MEDIA_DIRECTORY}/downloads:/downloads
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${MEDIA_DIRECTORY}:/data
|
||||
- ${INSTALL_DIRECTORY}/config/radarr:/config
|
||||
ports:
|
||||
- 7878:7878
|
||||
@ -75,8 +91,8 @@ services:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${MEDIA_DIRECTORY}/music:/music
|
||||
- ${MEDIA_DIRECTORY}/downloads:/downloads
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${MEDIA_DIRECTORY}:/data
|
||||
- ${INSTALL_DIRECTORY}/config/lidarr:/config
|
||||
ports:
|
||||
- 8686:8686
|
||||
@ -91,8 +107,8 @@ services:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${MEDIA_DIRECTORY}/books:/books
|
||||
- ${MEDIA_DIRECTORY}/downloads:/downloads
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${MEDIA_DIRECTORY}:/data
|
||||
- ${INSTALL_DIRECTORY}/config/readarr:/config
|
||||
ports:
|
||||
- 8787:8787
|
||||
@ -107,8 +123,8 @@ services:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${MEDIA_DIRECTORY}/movies:/movies
|
||||
- ${MEDIA_DIRECTORY}/tvshows:/tv
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${MEDIA_DIRECTORY}:/data
|
||||
- ${INSTALL_DIRECTORY}/config/bazarr:/config
|
||||
ports:
|
||||
- 6767:6767
|
||||
@ -123,6 +139,7 @@ services:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ${INSTALL_DIRECTORY}/config/prowlarr:/config
|
||||
ports:
|
||||
- 9696:9696
|
||||
@ -140,15 +157,17 @@ services:
|
||||
- 8888:8888/tcp # HTTP proxy
|
||||
- 8388:8388/tcp # Shadowsocks
|
||||
- 8388:8388/udp # Shadowsocks
|
||||
- 8003:8000/tcp # Admin
|
||||
#- 8080:8080/tcp # gluetun
|
||||
volumes:
|
||||
- ${INSTALL_DIRECTORY}/config/gluetun:/config
|
||||
#- 8081:8081/tcp # gluetun
|
||||
environment:
|
||||
- VPN_SERVICE_PROVIDER=${VPN_SERVICE}
|
||||
- VPN_TYPE=openvpn
|
||||
- OPENVPN_USER=${VPN_USER}
|
||||
- OPENVPN_PASSWORD=${VPN_PASSWORD}
|
||||
- OPENVPN_CIPHERS=AES-256-GCM
|
||||
- PORT_FORWARD_ONLY=on
|
||||
- VPN_PORT_FORWARDING=on
|
||||
restart: unless-stopped
|
||||
|
||||
# Portainer helps debugging and monitors the containers
|
||||
|
805
docs.org
805
docs.org
@ -6,37 +6,24 @@
|
||||
:PROPERTIES:
|
||||
:ID: faf95c8a-9133-4072-8544-0ef456a67611
|
||||
:END:
|
||||
|
||||
- [[#welcome-message][Welcome message]]
|
||||
- [[#constants-and-configuration][Constants and Configuration]]
|
||||
- [[#functions][Functions]]
|
||||
- [[#message-formatting][Message formatting]]
|
||||
- [[#check-the-dependencies][Check the dependencies]]
|
||||
- [[#running-services-location][Running services location]]
|
||||
- [[#verify-all-the-dependencies][Verify all the dependencies]]
|
||||
- [[#gather-all-the-required-information][Gather all the required information]]
|
||||
- [[#checking-install-location][Checking install location]]
|
||||
- [[#setting-the-correct-user][Setting the correct user]]
|
||||
- [[#media-directory][Media directory]]
|
||||
- [[#setting-perferred-media-service][Setting perferred media service]]
|
||||
- [[#setting-the-vpn][Setting the VPN]]
|
||||
- [[#installing-yams][Installing YAMS]]
|
||||
- [[#copy-the-docker-compose-file-to-the-install-location][Copy the docker-compose file to the install location]]
|
||||
- [[#set-puid-pgid-media-folder-media-service-config-folder-and-vpn-on-the-yams-scripts][Set PUID, PGID, Media Folder, Media Service, Config folder and VPN on the YAMS scripts]]
|
||||
- [[#set-the-configuration-for-the-yams-binary][Set the configuration for the YAMS binary]]
|
||||
- [[#success-message][Success message!]]
|
||||
- [[#final-steps][Final steps]]
|
||||
- [[#install-the-yams-cli][Install the YAMS CLI]]
|
||||
- [[#set-the-correct-permissions-to-the-media-and-install-directories][Set the correct permissions to the media and install directories]]
|
||||
- [[#create-the-config-directory][Create the config directory]]
|
||||
- [[#display-closing-message][Display closing message]]
|
||||
- [[#directory-handling][Directory Handling]]
|
||||
- [[#check-dependencies][Check Dependencies]]
|
||||
- [[#service-configuration][Service Configuration]]
|
||||
- [[#file-operations][File Operations]]
|
||||
- [[#script-execution][Script Execution]]
|
||||
- [[#verify-prerequisites][Verify Prerequisites]]
|
||||
- [[#installation-process][Installation Process]]
|
||||
- [[#display-closing-message][Display Closing Message]]
|
||||
|
||||
* Welcome message
|
||||
:PROPERTIES:
|
||||
:ID: 525c03eb-cab9-44f8-8cc5-e5ec9035a938
|
||||
:END:
|
||||
|
||||
This is just a welcome message for the script
|
||||
|
||||
#+begin_src bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
@ -64,264 +51,234 @@ echo "===================================================="
|
||||
echo ""
|
||||
#+end_src
|
||||
|
||||
* Constants and Configuration
|
||||
:PROPERTIES:
|
||||
:ID: new-constants-section
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
# Constants
|
||||
readonly DEFAULT_INSTALL_DIR="/opt/yams"
|
||||
readonly DEFAULT_MEDIA_DIR="/srv/media"
|
||||
readonly SUPPORTED_MEDIA_SERVICES=("jellyfin" "emby" "plex")
|
||||
readonly DEFAULT_MEDIA_SERVICE="jellyfin"
|
||||
readonly DEFAULT_VPN_SERVICE="protonvpn"
|
||||
readonly MEDIA_SUBDIRS=("tvshows" "movies" "music" "books" "downloads" "blackhole")
|
||||
|
||||
# Color codes
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Dependencies
|
||||
readonly REQUIRED_COMMANDS=("curl" "sed" "awk")
|
||||
#+end_src
|
||||
|
||||
* Functions
|
||||
:PROPERTIES:
|
||||
:ID: 111a7df4-08f5-4e6c-a799-dd822c5d030e
|
||||
:END:
|
||||
|
||||
To make development easier, we declare some functions that are going to be used a lot later
|
||||
|
||||
** Message formatting
|
||||
:PROPERTIES:
|
||||
:ID: 61387bd4-2ecf-44fe-ac69-dc6347c0d1b8
|
||||
:END:
|
||||
Both of these functions format the message in different colors, depending on what the message means
|
||||
*** Success
|
||||
:PROPERTIES:
|
||||
:ID: ec8f113c-43f9-4585-a1b5-8c7ec4e84bb2
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
send_success_message() {
|
||||
echo -e $(printf "\e[32m$1\e[0m")
|
||||
log_success() {
|
||||
echo -e "${GREEN}$1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}$1${NC}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}$1${NC}"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo "$1"
|
||||
}
|
||||
#+end_src
|
||||
|
||||
*** Error
|
||||
** Directory Handling
|
||||
:PROPERTIES:
|
||||
:ID: 1a6cd951-c9ce-46fc-8953-f5e206f7cd23
|
||||
:ID: new-directory-section
|
||||
:END:
|
||||
|
||||
Error is basically the same as before, but it ~exit 255~ to finish the execution.
|
||||
|
||||
#+begin_src bash
|
||||
send_error_message() {
|
||||
echo -e $(printf "\e[31m$1\e[0m")
|
||||
exit 255
|
||||
create_and_verify_directory() {
|
||||
local dir="$1"
|
||||
local dir_type="$2"
|
||||
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "The directory \"$dir\" does not exist. Attempting to create..."
|
||||
if mkdir -p "$dir"; then
|
||||
log_success "Directory $dir created ✅"
|
||||
else
|
||||
log_error "Failed to create $dir_type directory at \"$dir\". Check permissions ❌"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -w "$dir" ] || [ ! -r "$dir" ]; then
|
||||
log_error "Directory \"$dir\" is not writable or readable. Check permissions ❌"
|
||||
fi
|
||||
}
|
||||
|
||||
setup_directory_structure() {
|
||||
local media_dir="$1"
|
||||
|
||||
create_and_verify_directory "$media_dir" "media"
|
||||
|
||||
for subdir in "${MEDIA_SUBDIRS[@]}"; do
|
||||
create_and_verify_directory "$media_dir/$subdir" "media subdirectory"
|
||||
done
|
||||
}
|
||||
|
||||
verify_user_permissions() {
|
||||
local username="$1"
|
||||
local directory="$2"
|
||||
|
||||
if ! id -u "$username" &>/dev/null; then
|
||||
log_error "User \"$username\" doesn't exist!"
|
||||
fi
|
||||
|
||||
if ! sudo -u "$username" test -w "$directory"; then
|
||||
log_error "User \"$username\" doesn't have write permissions to \"$directory\""
|
||||
fi
|
||||
}
|
||||
#+end_src
|
||||
|
||||
** Check the dependencies
|
||||
** Check Dependencies
|
||||
:PROPERTIES:
|
||||
:ID: e7d01eeb-c7ef-42ff-b60d-010be30bc6a8
|
||||
:END:
|
||||
|
||||
This function verifies that the dependencies are installed. ~Docker~ and ~Docker Compose~ are required
|
||||
for YAMS to work.
|
||||
|
||||
#+begin_src bash
|
||||
check_dependencies() {
|
||||
if command -v docker &> /dev/null; then
|
||||
send_success_message "docker exists ✅ "
|
||||
if docker compose version &> /dev/null; then
|
||||
send_success_message "docker compose exists ✅ "
|
||||
local missing_packages=()
|
||||
local install_cmd=""
|
||||
|
||||
# Check for required commands and collect missing ones
|
||||
for pkg in "${REQUIRED_COMMANDS[@]}"; do
|
||||
if ! command -v "$pkg" &> /dev/null; then
|
||||
missing_packages+=("$pkg")
|
||||
else
|
||||
echo -e $(printf "\e[31m ⚠️ docker compose not found! ⚠️\e[0m")
|
||||
read -p "Do you want YAMS to install Docker Compose? IT ONLY WORKS ON DEBIAN AND UBUNTU! [y/N]: " install_docker
|
||||
install_docker=${install_docker:-"n"}
|
||||
|
||||
if [ "$install_docker" == "y" ]; then
|
||||
bash ./docker.sh
|
||||
else
|
||||
send_error_message "Install Docker Compose and come back later!"
|
||||
log_success "$pkg exists ✅"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e $(printf "\e[31m ⚠️ docker not found! ⚠️\e[0m")
|
||||
read -p "Do you want YAMS to install Docker and Docker Compose? IT ONLY WORKS ON DEBIAN AND UBUNTU! [y/N]: " install_docker
|
||||
install_docker=${install_docker:-"n"}
|
||||
|
||||
if [ "$install_docker" == "y" ]; then
|
||||
bash ./docker.sh
|
||||
else
|
||||
send_error_message "Install Docker and Docker Compose and come back later!"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
#+end_src
|
||||
|
||||
** Running services location
|
||||
:PROPERTIES:
|
||||
:ID: 53213557-edfe-4da7-88c0-e0e202429116
|
||||
:END:
|
||||
|
||||
This function just displays the locations for every container so the user can access to it when YAMS
|
||||
finish installing.
|
||||
|
||||
#+begin_src bash
|
||||
running_services_location() {
|
||||
host_ip=$(hostname -I | awk '{ print $1 }')
|
||||
|
||||
services=(
|
||||
"qBittorrent:8080"
|
||||
"Radarr:7878"
|
||||
"Sonarr:8989"
|
||||
"Lidarr:8686"
|
||||
"Readarr:8787"
|
||||
"Prowlarr:9696"
|
||||
"Bazarr:6767"
|
||||
"$media_service:$media_service_port"
|
||||
"Portainer:9000"
|
||||
)
|
||||
|
||||
echo -e "Service URLs:"
|
||||
for service in "${services[@]}"; do
|
||||
service_name="${service%%:*}"
|
||||
service_port="${service##*:}"
|
||||
echo "$service_name: http://$host_ip:$service_port/"
|
||||
done
|
||||
|
||||
# If there are missing packages, offer to install them
|
||||
if [ ${#missing_packages[@]} -gt 0 ]; then
|
||||
log_warning "Missing required packages: ${missing_packages[*]}"
|
||||
read -p "Would you like to install the missing packages? (y/N) [Default = n]: " install_deps
|
||||
install_deps=${install_deps:-"n"}
|
||||
|
||||
if [ "${install_deps,,}" = "y" ]; then
|
||||
echo "Installing missing packages..."
|
||||
if ! sudo apt update && sudo apt install -y "${missing_packages[@]}"; then
|
||||
log_error "Failed to install missing packages. Please install them manually: ${missing_packages[*]}"
|
||||
fi
|
||||
log_success "Successfully installed missing packages ✅"
|
||||
else
|
||||
log_error "Please install the required packages manually: ${missing_packages[*]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check Docker and Docker Compose
|
||||
if command -v docker &> /dev/null; then
|
||||
log_success "docker exists ✅"
|
||||
if docker compose version &> /dev/null; then
|
||||
log_success "docker compose exists ✅"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
log_warning "⚠️ Docker/Docker Compose not found! ⚠️"
|
||||
read -p "Install Docker and Docker Compose? Only works on Debian/Ubuntu (y/N) [Default = n]: " install_docker
|
||||
install_docker=${install_docker:-"n"}
|
||||
|
||||
if [ "${install_docker,,}" = "y" ]; then
|
||||
bash ./docker.sh
|
||||
else
|
||||
log_error "Please install Docker and Docker Compose first"
|
||||
fi
|
||||
}
|
||||
#+end_src
|
||||
|
||||
* Verify all the dependencies
|
||||
** Service Configuration
|
||||
:PROPERTIES:
|
||||
:ID: e945d5a8-5142-41fe-8175-96de7aa84cf2
|
||||
:ID: new-service-section
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
echo "Checking prerequisites..."
|
||||
configure_media_service() {
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
log_info "Time to choose your media service."
|
||||
log_info "Your media service is responsible for serving your files to your network."
|
||||
log_info "Supported media services:"
|
||||
log_info "- jellyfin (recommended, easier)"
|
||||
log_info "- emby"
|
||||
log_info "- plex (advanced, always online)"
|
||||
|
||||
|
||||
check_dependencies
|
||||
|
||||
if [[ "$EUID" = 0 ]]; then
|
||||
send_error_message "YAMS has to run without sudo! Please, run it again with regular permissions"
|
||||
fi
|
||||
#+end_src
|
||||
|
||||
* Gather all the required information
|
||||
:PROPERTIES:
|
||||
:ID: 438cecef-2bd6-4d7c-b429-6c674ae311d9
|
||||
:END:
|
||||
** Checking install location
|
||||
:PROPERTIES:
|
||||
:ID: fff12355-9d79-40fe-a540-cfba2a176a3e
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
default_install_directory="/opt/yams"
|
||||
|
||||
read -p "Where do you want to install the docker-compose file? [$default_install_directory]: " install_directory
|
||||
install_directory=${install_directory:-$default_install_directory}
|
||||
|
||||
if [ ! -d "$install_directory" ]; then
|
||||
echo "The directory \"$install_directory\" does not exists. Attempting to create..."
|
||||
if mkdir -p "$install_directory"; then
|
||||
send_success_message "Directory $install_directory created ✅"
|
||||
else
|
||||
send_error_message "There was an error creating the installation directory at \"$install_directory\". Make sure you have the necessary permissions ❌"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -w "$install_directory" ] || [ ! -r "$install_directory" ]; then
|
||||
send_error_message "The directory \"$install_directory\" is not writable or readable by the current user. Set the correct permissions or try a different directory" ❌
|
||||
fi
|
||||
|
||||
filename="$install_directory/docker-compose.yaml"
|
||||
custom_file_filename="$install_directory/docker-compose.custom.yaml"
|
||||
env_file="$install_directory/.env"
|
||||
#+end_src
|
||||
|
||||
** Setting the correct user
|
||||
:PROPERTIES:
|
||||
:ID: 7428d7b7-aec5-4638-b370-84e9055fb412
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
read -p "What's the user that is going to own the media server files? [$USER]: " username
|
||||
username=${username:-$USER}
|
||||
|
||||
if id -u "$username" &>/dev/null; then
|
||||
puid=$(id -u "$username");
|
||||
pgid=$(id -g "$username");
|
||||
else
|
||||
send_error_message "The user \"$username\" doesn't exist!"
|
||||
fi
|
||||
#+end_src
|
||||
|
||||
** Media directory
|
||||
:PROPERTIES:
|
||||
:ID: 9726dead-8833-4f23-98b8-2790d72605de
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
read -p "Please, input your media directory [/srv/media]: " media_directory
|
||||
media_directory=${media_directory:-"/srv/media"}
|
||||
|
||||
read -p "Are you sure your media directory is \"$media_directory\"? [y/N]: " media_directory_correct
|
||||
media_directory_correct=${media_directory_correct:-"n"}
|
||||
|
||||
if [ ! -d "$media_directory" ]; then
|
||||
echo "The directory \"$media_directory\" does not exists. Attempting to create..."
|
||||
if mkdir -p "$media_directory"; then
|
||||
send_success_message "Directory $media_directory created ✅"
|
||||
else
|
||||
send_error_message "There was an error creating the installation directory at \"$media_directory\". Make sure you have the necessary permissions ❌"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$media_directory_correct" == "n" ]; then
|
||||
send_error_message "Media directory is not correct. Please fix it and run the script again ❌"
|
||||
fi
|
||||
#+end_src
|
||||
|
||||
** Setting perferred media service
|
||||
:PROPERTIES:
|
||||
:ID: 3af8dbed-3a88-4739-a721-6434993c0b67
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
echo -e "\n\n\nTime to choose your media service."
|
||||
echo "Your media service is responsible for serving your files to your network."
|
||||
echo "By default, YAMS supports 3 media services:"
|
||||
echo "- jellyfin (recommended, easier)"
|
||||
echo "- emby"
|
||||
echo "- plex (advanced, always online)"
|
||||
|
||||
read -p "Choose your media service [jellyfin]: " media_service
|
||||
media_service=${media_service:-"jellyfin"}
|
||||
read -p "Choose your media service [$DEFAULT_MEDIA_SERVICE]: " media_service
|
||||
media_service=${media_service:-$DEFAULT_MEDIA_SERVICE}
|
||||
media_service=$(echo "$media_service" | awk '{print tolower($0)}')
|
||||
|
||||
media_service_port=8096
|
||||
if [[ ! " ${SUPPORTED_MEDIA_SERVICES[@]} " =~ " ${media_service} " ]]; then
|
||||
log_error "\"$media_service\" is not supported by YAMS"
|
||||
fi
|
||||
|
||||
# Set media service port
|
||||
if [ "$media_service" == "plex" ]; then
|
||||
media_service_port=32400
|
||||
fi
|
||||
|
||||
if echo "emby plex jellyfin" | grep -qw "$media_service"; then
|
||||
echo -e "\nYAMS is going to install \"$media_service\" on port \"$media_service_port\""
|
||||
else
|
||||
send_error_message "\"$media_service\" is not supported by YAMS. Are you sure you chose the correct service?"
|
||||
media_service_port=8096
|
||||
fi
|
||||
#+end_src
|
||||
|
||||
** Setting the VPN
|
||||
:PROPERTIES:
|
||||
:ID: 1da4fe67-ee20-4b70-8f36-4a9f7161b6ca
|
||||
:END:
|
||||
echo
|
||||
log_success "YAMS will install \"$media_service\" on port \"$media_service_port\""
|
||||
|
||||
#+begin_src bash
|
||||
echo -e "\nTime to set up the VPN."
|
||||
echo "You can check the supported VPN list here: https://yams.media/advanced/vpn."
|
||||
# Export for use in other functions
|
||||
export media_service media_service_port
|
||||
}
|
||||
|
||||
read -p "Do you want to configure a VPN? [Y/n]: " setup_vpn
|
||||
configure_vpn() {
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
log_info "Time to set up the VPN."
|
||||
log_info "Supported VPN providers: https://yams.media/advanced/vpn"
|
||||
|
||||
read -p "Configure VPN? (Y/n) [Default = y]: " setup_vpn
|
||||
setup_vpn=${setup_vpn:-"y"}
|
||||
|
||||
if [ "$setup_vpn" == "y" ]; then
|
||||
read -p "What's your VPN service? (with spaces) [mullvad]: " vpn_service
|
||||
vpn_service=${vpn_service:-"mullvad"}
|
||||
if [ "${setup_vpn,,}" != "y" ]; then
|
||||
export setup_vpn="n"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e "\nYou should read $vpn_service's documentation in case it has different configurations for username and password."
|
||||
echo "The documentation for $vpn_service is here: https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/${vpn_service// /-}.md"
|
||||
read -p "VPN service? (with spaces) [$DEFAULT_VPN_SERVICE]: " vpn_service
|
||||
vpn_service=${vpn_service:-$DEFAULT_VPN_SERVICE}
|
||||
|
||||
read -p "What's your VPN username? (without spaces): " vpn_user
|
||||
echo
|
||||
log_info "Please check $vpn_service's documentation for specific configuration:"
|
||||
log_info "https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/${vpn_service// /-}.md"
|
||||
|
||||
read -p "VPN username (without spaces): " vpn_user
|
||||
[ -z "$vpn_user" ] && log_error "VPN username cannot be empty"
|
||||
|
||||
# Use hidden input for password
|
||||
unset vpn_password
|
||||
charcount=0
|
||||
prompt="What's your VPN password? (if you are using mullvad, just enter your username again): "
|
||||
while IFS= read -p "$prompt" -r -s -n 1 char
|
||||
do
|
||||
if [[ $char == $'\0' ]]
|
||||
then
|
||||
prompt="VPN password (if using mullvad, enter username again): "
|
||||
while IFS= read -p "$prompt" -r -s -n 1 char; do
|
||||
if [[ $char == $'\0' ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ $char == $'\177' ]]; then
|
||||
@ -339,173 +296,253 @@ if [ "$setup_vpn" == "y" ]; then
|
||||
fi
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "Configuring the docker-compose file for the user \"$username\" on \"$install_directory\"..."
|
||||
#+end_src
|
||||
[ -z "$vpn_password" ] && log_error "VPN password cannot be empty"
|
||||
|
||||
* Installing YAMS
|
||||
:PROPERTIES:
|
||||
:ID: 44e5f3f1-3ae7-4f88-ba96-8149c9980fb2
|
||||
:END:
|
||||
** Copy the docker-compose file to the install location
|
||||
:PROPERTIES:
|
||||
:ID: 09018e25-ed48-46e9-85c3-586c37844c11
|
||||
:END:
|
||||
# Export for use in other functions
|
||||
export vpn_service vpn_user vpn_password setup_vpn
|
||||
}
|
||||
|
||||
#+begin_src bash
|
||||
copy_files=(
|
||||
"docker-compose.example.yaml:$filename"
|
||||
".env.example:$env_file"
|
||||
"docker-compose.custom.yaml:$custom_file_filename"
|
||||
running_services_location() {
|
||||
local host_ip
|
||||
host_ip=$(hostname -I | awk '{ print $1 }')
|
||||
|
||||
local -A services=(
|
||||
["qBittorrent"]="8081"
|
||||
["SABnzbd"]="8080"
|
||||
["Radarr"]="7878"
|
||||
["Sonarr"]="8989"
|
||||
["Lidarr"]="8686"
|
||||
["Readarr"]="8787"
|
||||
["Prowlarr"]="9696"
|
||||
["Bazarr"]="6767"
|
||||
["$media_service"]="$media_service_port"
|
||||
["Portainer"]="9000"
|
||||
)
|
||||
|
||||
for file_mapping in "${copy_files[@]}"; do
|
||||
source_file="${file_mapping%%:*}"
|
||||
destination_file="${file_mapping##*:}"
|
||||
echo -e "Service URLs:"
|
||||
for service in "${!services[@]}"; do
|
||||
echo "$service: http://$host_ip:${services[$service]}/"
|
||||
done
|
||||
}
|
||||
|
||||
echo -e "\nCopying $source_file to $destination_file..."
|
||||
if cp "$source_file" "$destination_file"; then
|
||||
send_success_message "$source_file was copied successfuly! ✅"
|
||||
get_user_info() {
|
||||
read -p "User to own the media server files? [$USER]: " username
|
||||
username=${username:-$USER}
|
||||
|
||||
if id -u "$username" &>/dev/null; then
|
||||
puid=$(id -u "$username")
|
||||
pgid=$(id -g "$username")
|
||||
else
|
||||
send_error_message "Failed to copy $source_file to $destination_file. Ensure your user ($USER) has the necessary permissions ❌"
|
||||
log_error "User \"$username\" doesn't exist!"
|
||||
fi
|
||||
|
||||
export username puid pgid
|
||||
}
|
||||
|
||||
get_installation_paths() {
|
||||
read -p "Installation directory? [$DEFAULT_INSTALL_DIR]: " install_directory
|
||||
install_directory=${install_directory:-$DEFAULT_INSTALL_DIR}
|
||||
create_and_verify_directory "$install_directory" "installation"
|
||||
|
||||
read -p "Media directory? [$DEFAULT_MEDIA_DIR]: " media_directory
|
||||
media_directory=${media_directory:-$DEFAULT_MEDIA_DIR}
|
||||
|
||||
read -p "Are you sure your media directory is \"$media_directory\"? (y/N) [Default = n]: " media_directory_correct
|
||||
media_directory_correct=${media_directory_correct:-"n"}
|
||||
|
||||
if [ "${media_directory_correct,,}" != "y" ]; then
|
||||
log_error "Media directory is not correct. Please fix it and run the script again ❌"
|
||||
fi
|
||||
|
||||
setup_directory_structure "$media_directory"
|
||||
verify_user_permissions "$username" "$media_directory"
|
||||
|
||||
export install_directory media_directory
|
||||
}
|
||||
#+end_src
|
||||
|
||||
** File Operations
|
||||
:PROPERTIES:
|
||||
:ID: new-file-operations-section
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
copy_configuration_files() {
|
||||
local -A files=(
|
||||
["docker-compose.example.yaml"]="docker-compose.yaml"
|
||||
[".env.example"]=".env"
|
||||
["docker-compose.custom.yaml"]="docker-compose.custom.yaml"
|
||||
)
|
||||
|
||||
for src in "${!files[@]}"; do
|
||||
local dest="$install_directory/${files[$src]}"
|
||||
echo
|
||||
log_info "Copying $src to $dest..."
|
||||
|
||||
if cp "$src" "$dest"; then
|
||||
log_success "$src copied successfully ✅"
|
||||
else
|
||||
log_error "Failed to copy $src to $dest. Check permissions ❌"
|
||||
fi
|
||||
done
|
||||
#+end_src
|
||||
}
|
||||
|
||||
#+RESULTS:
|
||||
update_configuration_files() {
|
||||
local filename="$install_directory/docker-compose.yaml"
|
||||
local env_file="$install_directory/.env"
|
||||
local yams_script="yams"
|
||||
|
||||
** Set PUID, PGID, Media Folder, Media Service, Config folder and VPN on the YAMS scripts
|
||||
:PROPERTIES:
|
||||
:ID: 3d169001-f0f7-477f-a954-0460484f4b43
|
||||
:END:
|
||||
# Update .env file
|
||||
log_info "Updating environment configuration..."
|
||||
sed -i -e "s|<your_PUID>|$puid|g" \
|
||||
-e "s|<your_PGID>|$pgid|g" \
|
||||
-e "s|<media_directory>|$media_directory|g" \
|
||||
-e "s|<media_service>|$media_service|g" \
|
||||
-e "s|<install_directory>|$install_directory|g" \
|
||||
-e "s|vpn_enabled|$setup_vpn|g" "$env_file" || \
|
||||
log_error "Failed to update .env file"
|
||||
|
||||
This steps prepares all the files with the correct information that was collected on the "[[#gather-all-the-required-information][Gather all the
|
||||
required information]]" step.
|
||||
# Update VPN configuration in .env file
|
||||
if [ "${setup_vpn,,}" == "y" ]; then
|
||||
sed -i -e "s|^VPN_ENABLED=.*|VPN_ENABLED=y|" \
|
||||
-e "s|^VPN_SERVICE=.*|VPN_SERVICE=$vpn_service|" \
|
||||
-e "s|^VPN_USER=.*|VPN_USER=$vpn_user|" \
|
||||
-e "s|^VPN_PASSWORD=.*|VPN_PASSWORD=$vpn_password|" "$env_file" || \
|
||||
log_error "Failed to update VPN configuration in .env"
|
||||
else
|
||||
sed -i -e "s|^VPN_ENABLED=.*|VPN_ENABLED=n|" "$env_file" || \
|
||||
log_error "Failed to update VPN configuration in .env"
|
||||
fi
|
||||
|
||||
#+begin_src bash
|
||||
sed -i -e "s|<your_PUID>|$puid|g" "$env_file" \
|
||||
-e "s|<your_PGID>|$pgid|g" "$env_file" \
|
||||
-e "s|<media_directory>|$media_directory|g" "$env_file" \
|
||||
-e "s|<media_service>|$media_service|g" "$env_file" \
|
||||
-e "s|<media_service>|$media_service|g" "$filename"
|
||||
# Update docker-compose.yaml
|
||||
log_info "Updating docker-compose configuration..."
|
||||
sed -i "s|<media_service>|$media_service|g" "$filename" || \
|
||||
log_error "Failed to update docker-compose.yaml"
|
||||
|
||||
# Configure Plex-specific settings
|
||||
if [ "$media_service" == "plex" ]; then
|
||||
sed -i -e "s|#network_mode: host # plex|network_mode: host # plex|g" "$filename" \
|
||||
-e "s|ports: # plex|#ports: # plex|g" "$filename" \
|
||||
-e "s|- 8096:8096 # plex|#- 8096:8096 # plex|g" "$filename"
|
||||
log_info "Configuring Plex-specific settings..."
|
||||
sed -i -e 's|#network_mode: host # plex|network_mode: host # plex|g' \
|
||||
-e 's|ports: # plex|#ports: # plex|g' \
|
||||
-e 's|- 8096:8096 # plex|#- 8096:8096 # plex|g' "$filename" || \
|
||||
log_error "Failed to configure Plex settings"
|
||||
fi
|
||||
|
||||
sed -i -e "s|<install_directory>|$install_directory|g" "$env_file" \
|
||||
-e "s|vpn_enabled|$setup_vpn|g" "$env_file" \
|
||||
|
||||
if [ "$setup_vpn" == "y" ]; then
|
||||
sed -i -e "s|vpn_service|$vpn_service|g" "$env_file" \
|
||||
-e "s|vpn_user|$vpn_user|g" "$env_file" \
|
||||
-e "s|vpn_password|$vpn_password|g" "$env_file" \
|
||||
-e "s|#network_mode: \"service:gluetun\"|network_mode: \"service:gluetun\"|g" "$filename" \
|
||||
-e "s|ports: # qbittorrent|#ports: # qbittorrent|g" "$filename" \
|
||||
-e "s|- 8080:8080 # qbittorrent|#- 8080:8080 # qbittorrent|g" "$filename" \
|
||||
-e "s|#- 8080:8080/tcp # gluetun|- 8080:8080/tcp # gluetun|g" "$filename"
|
||||
# Configure VPN settings if enabled
|
||||
if [ "${setup_vpn,,}" == "y" ]; then
|
||||
log_info "Configuring VPN settings..."
|
||||
sed -i -e "s|vpn_service|$vpn_service|g" \
|
||||
-e "s|vpn_user|$vpn_user|g" \
|
||||
-e "s|vpn_password|$vpn_password|g" \
|
||||
-e 's|#network_mode: "service:gluetun"|network_mode: "service:gluetun"|g' \
|
||||
-e 's|ports: # qbittorrent|#ports: # qbittorrent|g' \
|
||||
-e 's|ports: # sabnzbd|#ports: # sabnzbd|g' \
|
||||
-e 's|- 8081:8081 # qbittorrent|#- 8081:8081 # qbittorrent|g' \
|
||||
-e 's|- 8080:8080 # sabnzbd|#- 8080:8080 # sabnzbd|g' \
|
||||
-e 's|#- 8080:8080/tcp # gluetun|- 8080:8080/tcp # gluetun|g' \
|
||||
-e 's|#- 8081:8081/tcp # gluetun|- 8081:8081/tcp # gluetun|g' "$filename" || \
|
||||
log_error "Failed to configure VPN settings"
|
||||
fi
|
||||
#+end_src
|
||||
|
||||
** Set the configuration for the YAMS binary
|
||||
:PROPERTIES:
|
||||
:ID: b6a8732f-9dbe-4d93-b04d-27156eacdea2
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
sed -i -e "s|<filename>|$filename|g" yams \
|
||||
-e "s|<custom_file_filename>|$custom_file_filename|g" yams \
|
||||
-e "s|<install_directory>|$install_directory|g" yams
|
||||
#+end_src
|
||||
|
||||
** Success message!
|
||||
:PROPERTIES:
|
||||
:ID: 7b0ed8f5-780b-4685-8123-8d5c4229eaba
|
||||
:END:
|
||||
|
||||
Finally, YAMS is installed 🔥. Show the success message
|
||||
|
||||
#+begin_src bash
|
||||
send_success_message "Everything installed correctly! 🎉"
|
||||
|
||||
echo "Running the server..."
|
||||
echo "This is going to take a while..."
|
||||
|
||||
docker compose -f "$filename" up -d
|
||||
#+end_src
|
||||
* Final steps
|
||||
:PROPERTIES:
|
||||
:ID: 65ce5828-b69a-4a0e-83f6-b029e19caea1
|
||||
:END:
|
||||
** Install the YAMS CLI
|
||||
:PROPERTIES:
|
||||
:ID: f4f9d166-8a2b-4d79-bc7f-fe73ecf5fb77
|
||||
:END:
|
||||
|
||||
This steps requires ~sudo~ because it's copying the main yams script to the ~/usr/local/bin/yams~
|
||||
directory.
|
||||
|
||||
#+begin_src bash
|
||||
echo -e "\nWe need your sudo password to install the YAMS CLI and configure permissions..."
|
||||
# Update YAMS CLI script
|
||||
log_info "Updating YAMS CLI configuration..."
|
||||
sed -i -e "s|<filename>|$filename|g" \
|
||||
-e "s|<custom_file_filename>|$install_directory/docker-compose.custom.yaml|g" \
|
||||
-e "s|<install_directory>|$install_directory|g" "$yams_script" || \
|
||||
log_error "Failed to update YAMS CLI script"
|
||||
}
|
||||
|
||||
install_cli() {
|
||||
echo
|
||||
log_info "Installing YAMS CLI..."
|
||||
if sudo cp yams /usr/local/bin/yams && sudo chmod +x /usr/local/bin/yams; then
|
||||
send_success_message "YAMS CLI installed successfully ✅"
|
||||
log_success "YAMS CLI installed successfully ✅"
|
||||
else
|
||||
send_error_message "Failed to install YAMS CLI. Make sure you have the necessary permissions ❌"
|
||||
log_error "Failed to install YAMS CLI. Check permissions ❌"
|
||||
fi
|
||||
}
|
||||
|
||||
set_permissions() {
|
||||
local dirs=("$media_directory" "$install_directory" "$install_directory/config")
|
||||
|
||||
for dir in "${dirs[@]}"; do
|
||||
log_info "Setting permissions for $dir..."
|
||||
if [ ! -d "$dir" ]; then
|
||||
mkdir -p "$dir" || log_error "Failed to create directory $dir"
|
||||
fi
|
||||
|
||||
if sudo chown -R "$puid:$pgid" "$dir"; then
|
||||
log_success "Permissions set successfully for $dir ✅"
|
||||
else
|
||||
log_error "Failed to set permissions for $dir ❌"
|
||||
fi
|
||||
done
|
||||
}
|
||||
#+end_src
|
||||
|
||||
** Set the correct permissions to the media and install directories
|
||||
* Script Execution
|
||||
:PROPERTIES:
|
||||
:ID: 4cfb9397-776d-46db-84cc-54b78395cba8
|
||||
:ID: new-script-execution
|
||||
:END:
|
||||
|
||||
This adds the correct permissions to the media folder, in case they are not correct.
|
||||
|
||||
#+begin_src bash
|
||||
if sudo chown -R "$puid":"$pgid" "$media_directory"; then
|
||||
send_success_message "Media directory ownership and permissions set successfully ✅"
|
||||
else
|
||||
send_error_message "Failed to set ownership and permissions for the media directory. Check permissions ❌"
|
||||
fi
|
||||
|
||||
if sudo chown -R "$puid":"$pgid" "$install_directory"; then
|
||||
send_success_message "Install directory ownership and permissions set successfully ✅"
|
||||
else
|
||||
send_error_message "Failed to set ownership and permissions for the install directory. Check permissions ❌"
|
||||
fi
|
||||
#+end_src
|
||||
|
||||
** Create the config directory
|
||||
** Verify Prerequisites
|
||||
:PROPERTIES:
|
||||
:ID: 699f35fe-edde-406d-be0b-3ff2eaa6d7eb
|
||||
:ID: e945d5a8-5142-41fe-8175-96de7aa84cf2
|
||||
:END:
|
||||
|
||||
This is where all the configurations are going to be saved. If it doesn't it will try and create it. If
|
||||
it can't be created, we'll raise an error.
|
||||
|
||||
#+begin_src bash
|
||||
if [[ -d "$install_directory/config" ]]; then
|
||||
send_success_message "Configuration folder \"$install_directory/config\" exists ✅"
|
||||
else
|
||||
if sudo mkdir -p "$install_directory/config"; then
|
||||
send_success_message "Configuration folder \"$install_directory/config\" created ✅"
|
||||
else
|
||||
send_error_message "Failed to create or access the configuration folder. Check permissions ❌"
|
||||
fi
|
||||
# Prevent running as root
|
||||
if [[ "$EUID" = 0 ]]; then
|
||||
log_error "YAMS must run without sudo! Please run with regular permissions"
|
||||
fi
|
||||
|
||||
if sudo chown -R "$puid":"$pgid" "$install_directory/config"; then
|
||||
send_success_message "Configuration folder ownership and permissions set successfully ✅"
|
||||
else
|
||||
send_error_message "Failed to set ownership and permissions for the configuration folder. Check permissions ❌"
|
||||
fi
|
||||
# Check all dependencies
|
||||
log_info "Checking prerequisites..."
|
||||
check_dependencies
|
||||
#+end_src
|
||||
|
||||
* Display closing message
|
||||
** Installation Process
|
||||
:PROPERTIES:
|
||||
:ID: new-installation-process
|
||||
:END:
|
||||
|
||||
#+begin_src bash
|
||||
# Get user information
|
||||
get_user_info
|
||||
|
||||
# Get installation paths
|
||||
get_installation_paths
|
||||
|
||||
# Configure services
|
||||
configure_media_service
|
||||
configure_vpn
|
||||
|
||||
log_info "Configuring the docker-compose file for user \"$username\" in \"$install_directory\"..."
|
||||
|
||||
# Copy and update configuration files
|
||||
copy_configuration_files
|
||||
update_configuration_files
|
||||
|
||||
log_success "Everything installed correctly! 🎉"
|
||||
|
||||
# Start services
|
||||
log_info "Starting YAMS services..."
|
||||
log_info "This may take a while..."
|
||||
|
||||
if ! docker compose -f "$install_directory/docker-compose.yaml" up -d; then
|
||||
log_error "Failed to start YAMS services"
|
||||
fi
|
||||
|
||||
# Install CLI and set permissions
|
||||
echo
|
||||
log_info "We need your sudo password to install the YAMS CLI and configure permissions..."
|
||||
install_cli
|
||||
set_permissions
|
||||
|
||||
#+end_src
|
||||
|
||||
* Display Closing Message
|
||||
:PROPERTIES:
|
||||
:ID: 238e3eae-9df7-4a7f-a460-7a61c07b5442
|
||||
:END:
|
||||
@ -513,36 +550,42 @@ fi
|
||||
#+begin_src bash
|
||||
printf "\033c"
|
||||
|
||||
echo "========================================================"
|
||||
echo " _____ ___ ___ ___ "
|
||||
echo " / /::\ / /\ /__/\ / /\ "
|
||||
echo " / /:/\:\ / /::\ \ \:\ / /:/_ "
|
||||
echo " / /:/ \:\ / /:/\:\ \ \:\ / /:/ /\ "
|
||||
echo " /__/:/ \__\:| / /:/ \:\ _____\__\:\ / /:/ /:/_ "
|
||||
echo " \ \:\ / /:/ /__/:/ \__\:\ /__/::::::::\ /__/:/ /:/ /\\"
|
||||
echo " \ \:\ /:/ \ \:\ / /:/ \ \:\~~\~~\/ \ \:\/:/ /:/"
|
||||
echo " \ \:\/:/ \ \:\ /:/ \ \:\ ~~~ \ \::/ /:/ "
|
||||
echo " \ \::/ \ \:\/:/ \ \:\ \ \:\/:/ "
|
||||
echo " \__\/ \ \::/ \ \:\ \ \::/ "
|
||||
echo " \__\/ \__\/ \__\/ "
|
||||
echo "========================================================"
|
||||
send_success_message "All done!✅ Enjoy YAMS!"
|
||||
echo "You can check the installation on $install_directory"
|
||||
echo "========================================================"
|
||||
echo "Everything should be running now! To check everything running, go to:"
|
||||
cat << "EOF"
|
||||
========================================================
|
||||
_____ ___ ___ ___
|
||||
/ /::\ / /\ /__/\ / /\
|
||||
/ /:/\:\ / /::\ \ \:\ / /:/_
|
||||
/ /:/ \:\ / /:/\:\ \ \:\ / /:/ /\
|
||||
/__/:/ \__\:| / /:/ \:\ _____\__\:\ / /:/ /:/_
|
||||
\ \:\ / /:/ /__/:/ \__\:\ /__/::::::::\ /__/:/ /:/ /\\
|
||||
\ \:\ /:/ \ \:\ / /:/ \ \:\~~\~~\/ \ \:\/:/ /:/
|
||||
\ \:\/:/ \ \:\ /:/ \ \:\ ~~~ \ \::/ /:/
|
||||
\ \::/ \ \:\/:/ \ \:\ \ \:\/:/
|
||||
\__\/ \ \::/ \ \:\ \ \::/
|
||||
\__\/ \__\/ \__\/
|
||||
========================================================
|
||||
EOF
|
||||
|
||||
log_success "All done!✅ Enjoy YAMS!"
|
||||
log_info "You can check the installation in $install_directory"
|
||||
log_info "========================================================"
|
||||
log_info "Everything should be running now! To check everything running, go to:"
|
||||
echo
|
||||
|
||||
running_services_location
|
||||
|
||||
echo
|
||||
log_info "You might need to wait for a couple of minutes while everything gets up and running"
|
||||
echo
|
||||
echo "You might need to wait for a couple of minutes while everything gets up and running"
|
||||
echo
|
||||
echo "All the services location are also saved in ~/yams_services.txt"
|
||||
log_info "All the service locations are also saved in ~/yams_services.txt"
|
||||
running_services_location > ~/yams_services.txt
|
||||
echo "========================================================"
|
||||
|
||||
log_info "========================================================"
|
||||
echo
|
||||
echo "To configure YAMS, check the documentation at"
|
||||
echo "https://yams.media/config"
|
||||
log_info "To configure YAMS, check the documentation at"
|
||||
log_info "https://yams.media/config"
|
||||
echo
|
||||
echo "========================================================"
|
||||
log_info "========================================================"
|
||||
|
||||
exit 0
|
||||
#+end_src
|
||||
|
572
install.sh
572
install.sh
@ -23,171 +23,195 @@ echo "To finish the installation of the CLI"
|
||||
echo "===================================================="
|
||||
echo ""
|
||||
|
||||
send_success_message() {
|
||||
echo -e $(printf "\e[32m$1\e[0m")
|
||||
# Constants
|
||||
readonly DEFAULT_INSTALL_DIR="/opt/yams"
|
||||
readonly DEFAULT_MEDIA_DIR="/srv/media"
|
||||
readonly SUPPORTED_MEDIA_SERVICES=("jellyfin" "emby" "plex")
|
||||
readonly DEFAULT_MEDIA_SERVICE="jellyfin"
|
||||
readonly DEFAULT_VPN_SERVICE="protonvpn"
|
||||
readonly MEDIA_SUBDIRS=("tvshows" "movies" "music" "books" "downloads" "blackhole")
|
||||
|
||||
# Color codes
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Dependencies
|
||||
readonly REQUIRED_COMMANDS=("curl" "sed" "awk")
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}$1${NC}"
|
||||
}
|
||||
|
||||
send_error_message() {
|
||||
echo -e $(printf "\e[31m$1\e[0m")
|
||||
exit 255
|
||||
log_error() {
|
||||
echo -e "${RED}$1${NC}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_dependencies() {
|
||||
if command -v docker &> /dev/null; then
|
||||
send_success_message "docker exists ✅ "
|
||||
if docker compose version &> /dev/null; then
|
||||
send_success_message "docker compose exists ✅ "
|
||||
else
|
||||
echo -e $(printf "\e[31m ⚠️ docker compose not found! ⚠️\e[0m")
|
||||
read -p "Do you want YAMS to install Docker Compose? IT ONLY WORKS ON DEBIAN AND UBUNTU! (y/N) [Default = n]: " install_docker
|
||||
install_docker=${install_docker:-"n"}
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}$1${NC}"
|
||||
}
|
||||
|
||||
if [ "$install_docker" == "y" ]; then
|
||||
bash ./docker.sh
|
||||
else
|
||||
send_error_message "Install Docker Compose and come back later!"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e $(printf "\e[31m ⚠️ docker not found! ⚠️\e[0m")
|
||||
read -p "Do you want YAMS to install Docker and Docker Compose? IT ONLY WORKS ON DEBIAN AND UBUNTU! (y/N) [Default = n]: " install_docker
|
||||
install_docker=${install_docker:-"n"}
|
||||
log_info() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
if [ "$install_docker" == "y" ]; then
|
||||
bash ./docker.sh
|
||||
create_and_verify_directory() {
|
||||
local dir="$1"
|
||||
local dir_type="$2"
|
||||
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "The directory \"$dir\" does not exist. Attempting to create..."
|
||||
if mkdir -p "$dir"; then
|
||||
log_success "Directory $dir created ✅"
|
||||
else
|
||||
send_error_message "Install Docker and Docker Compose and come back later!"
|
||||
log_error "Failed to create $dir_type directory at \"$dir\". Check permissions ❌"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -w "$dir" ] || [ ! -r "$dir" ]; then
|
||||
log_error "Directory \"$dir\" is not writable or readable. Check permissions ❌"
|
||||
fi
|
||||
}
|
||||
|
||||
running_services_location() {
|
||||
host_ip=$(hostname -I | awk '{ print $1 }')
|
||||
setup_directory_structure() {
|
||||
local media_dir="$1"
|
||||
|
||||
services=(
|
||||
"qBittorrent:8080"
|
||||
"Radarr:7878"
|
||||
"Sonarr:8989"
|
||||
"Lidarr:8686"
|
||||
"Readarr:8787"
|
||||
"Prowlarr:9696"
|
||||
"Bazarr:6767"
|
||||
"$media_service:$media_service_port"
|
||||
"Portainer:9000"
|
||||
)
|
||||
create_and_verify_directory "$media_dir" "media"
|
||||
|
||||
echo -e "Service URLs:"
|
||||
for service in "${services[@]}"; do
|
||||
service_name="${service%%:*}"
|
||||
service_port="${service##*:}"
|
||||
echo "$service_name: http://$host_ip:$service_port/"
|
||||
for subdir in "${MEDIA_SUBDIRS[@]}"; do
|
||||
create_and_verify_directory "$media_dir/$subdir" "media subdirectory"
|
||||
done
|
||||
}
|
||||
|
||||
echo "Checking prerequisites..."
|
||||
verify_user_permissions() {
|
||||
local username="$1"
|
||||
local directory="$2"
|
||||
|
||||
|
||||
check_dependencies
|
||||
|
||||
if [[ "$EUID" = 0 ]]; then
|
||||
send_error_message "YAMS has to run without sudo! Please, run it again with regular permissions"
|
||||
if ! id -u "$username" &>/dev/null; then
|
||||
log_error "User \"$username\" doesn't exist!"
|
||||
fi
|
||||
|
||||
default_install_directory="/opt/yams"
|
||||
if ! sudo -u "$username" test -w "$directory"; then
|
||||
log_error "User \"$username\" doesn't have write permissions to \"$directory\""
|
||||
fi
|
||||
}
|
||||
|
||||
read -p "Where do you want to install the docker-compose file? [$default_install_directory]: " install_directory
|
||||
install_directory=${install_directory:-$default_install_directory}
|
||||
check_dependencies() {
|
||||
local missing_packages=()
|
||||
local install_cmd=""
|
||||
|
||||
if [ ! -d "$install_directory" ]; then
|
||||
echo "The directory \"$install_directory\" does not exists. Attempting to create..."
|
||||
if mkdir -p "$install_directory"; then
|
||||
send_success_message "Directory $install_directory created ✅"
|
||||
# Check for required commands and collect missing ones
|
||||
for pkg in "${REQUIRED_COMMANDS[@]}"; do
|
||||
if ! command -v "$pkg" &> /dev/null; then
|
||||
missing_packages+=("$pkg")
|
||||
else
|
||||
send_error_message "There was an error creating the installation directory at \"$install_directory\". Make sure you have the necessary permissions ❌"
|
||||
log_success "$pkg exists ✅"
|
||||
fi
|
||||
done
|
||||
|
||||
# If there are missing packages, offer to install them
|
||||
if [ ${#missing_packages[@]} -gt 0 ]; then
|
||||
log_warning "Missing required packages: ${missing_packages[*]}"
|
||||
read -p "Would you like to install the missing packages? (y/N) [Default = n]: " install_deps
|
||||
install_deps=${install_deps:-"n"}
|
||||
|
||||
if [ "${install_deps,,}" = "y" ]; then
|
||||
echo "Installing missing packages..."
|
||||
if ! sudo apt update && sudo apt install -y "${missing_packages[@]}"; then
|
||||
log_error "Failed to install missing packages. Please install them manually: ${missing_packages[*]}"
|
||||
fi
|
||||
|
||||
if [ ! -w "$install_directory" ] || [ ! -r "$install_directory" ]; then
|
||||
send_error_message "The directory \"$install_directory\" is not writable or readable by the current user. Set the correct permissions or try a different directory" ❌
|
||||
fi
|
||||
|
||||
filename="$install_directory/docker-compose.yaml"
|
||||
custom_file_filename="$install_directory/docker-compose.custom.yaml"
|
||||
env_file="$install_directory/.env"
|
||||
|
||||
read -p "What's the user that is going to own the media server files? [$USER]: " username
|
||||
username=${username:-$USER}
|
||||
|
||||
if id -u "$username" &>/dev/null; then
|
||||
puid=$(id -u "$username");
|
||||
pgid=$(id -g "$username");
|
||||
log_success "Successfully installed missing packages ✅"
|
||||
else
|
||||
send_error_message "The user \"$username\" doesn't exist!"
|
||||
log_error "Please install the required packages manually: ${missing_packages[*]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
read -p "Please, input your media directory [/srv/media]: " media_directory
|
||||
media_directory=${media_directory:-"/srv/media"}
|
||||
# Check Docker and Docker Compose
|
||||
if command -v docker &> /dev/null; then
|
||||
log_success "docker exists ✅"
|
||||
if docker compose version &> /dev/null; then
|
||||
log_success "docker compose exists ✅"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
read -p "Are you sure your media directory is \"$media_directory\"? (y/N) [Default = n]: " media_directory_correct
|
||||
media_directory_correct=${media_directory_correct:-"n"}
|
||||
log_warning "⚠️ Docker/Docker Compose not found! ⚠️"
|
||||
read -p "Install Docker and Docker Compose? Only works on Debian/Ubuntu (y/N) [Default = n]: " install_docker
|
||||
install_docker=${install_docker:-"n"}
|
||||
|
||||
if [ ! -d "$media_directory" ]; then
|
||||
echo "The directory \"$media_directory\" does not exists. Attempting to create..."
|
||||
if mkdir -p "$media_directory"; then
|
||||
send_success_message "Directory $media_directory created ✅"
|
||||
if [ "${install_docker,,}" = "y" ]; then
|
||||
bash ./docker.sh
|
||||
else
|
||||
send_error_message "There was an error creating the installation directory at \"$media_directory\". Make sure you have the necessary permissions ❌"
|
||||
fi
|
||||
log_error "Please install Docker and Docker Compose first"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$media_directory_correct" == "n" ]; then
|
||||
send_error_message "Media directory is not correct. Please fix it and run the script again ❌"
|
||||
fi
|
||||
configure_media_service() {
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
log_info "Time to choose your media service."
|
||||
log_info "Your media service is responsible for serving your files to your network."
|
||||
log_info "Supported media services:"
|
||||
log_info "- jellyfin (recommended, easier)"
|
||||
log_info "- emby"
|
||||
log_info "- plex (advanced, always online)"
|
||||
|
||||
echo -e "\n\n\nTime to choose your media service."
|
||||
echo "Your media service is responsible for serving your files to your network."
|
||||
echo "By default, YAMS supports 3 media services:"
|
||||
echo "- jellyfin (recommended, easier)"
|
||||
echo "- emby"
|
||||
echo "- plex (advanced, always online)"
|
||||
|
||||
read -p "Choose your media service [jellyfin]: " media_service
|
||||
media_service=${media_service:-"jellyfin"}
|
||||
read -p "Choose your media service [$DEFAULT_MEDIA_SERVICE]: " media_service
|
||||
media_service=${media_service:-$DEFAULT_MEDIA_SERVICE}
|
||||
media_service=$(echo "$media_service" | awk '{print tolower($0)}')
|
||||
|
||||
media_service_port=8096
|
||||
if [[ ! " ${SUPPORTED_MEDIA_SERVICES[@]} " =~ " ${media_service} " ]]; then
|
||||
log_error "\"$media_service\" is not supported by YAMS"
|
||||
fi
|
||||
|
||||
# Set media service port
|
||||
if [ "$media_service" == "plex" ]; then
|
||||
media_service_port=32400
|
||||
fi
|
||||
|
||||
if echo "emby plex jellyfin" | grep -qw "$media_service"; then
|
||||
echo -e "\nYAMS is going to install \"$media_service\" on port \"$media_service_port\""
|
||||
else
|
||||
send_error_message "\"$media_service\" is not supported by YAMS. Are you sure you chose the correct service?"
|
||||
media_service_port=8096
|
||||
fi
|
||||
|
||||
echo -e "\nTime to set up the VPN."
|
||||
echo "You can check the supported VPN list here: https://yams.media/advanced/vpn."
|
||||
echo
|
||||
log_success "YAMS will install \"$media_service\" on port \"$media_service_port\""
|
||||
|
||||
read -p "Do you want to configure a VPN? (Y/n) [Default = y]: " setup_vpn
|
||||
# Export for use in other functions
|
||||
export media_service media_service_port
|
||||
}
|
||||
|
||||
configure_vpn() {
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
log_info "Time to set up the VPN."
|
||||
log_info "Supported VPN providers: https://yams.media/advanced/vpn"
|
||||
|
||||
read -p "Configure VPN? (Y/n) [Default = y]: " setup_vpn
|
||||
setup_vpn=${setup_vpn:-"y"}
|
||||
|
||||
if [ "$setup_vpn" == "y" ]; then
|
||||
read -p "What's your VPN service? (with spaces) [mullvad]: " vpn_service
|
||||
vpn_service=${vpn_service:-"mullvad"}
|
||||
if [ "${setup_vpn,,}" != "y" ]; then
|
||||
export setup_vpn="n"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e "\nYou should read $vpn_service's documentation in case it has different configurations for username and password."
|
||||
echo "The documentation for $vpn_service is here: https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/${vpn_service// /-}.md"
|
||||
read -p "VPN service? (with spaces) [$DEFAULT_VPN_SERVICE]: " vpn_service
|
||||
vpn_service=${vpn_service:-$DEFAULT_VPN_SERVICE}
|
||||
|
||||
read -p "What's your VPN username? (without spaces): " vpn_user
|
||||
echo
|
||||
log_info "Please check $vpn_service's documentation for specific configuration:"
|
||||
log_info "https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/${vpn_service// /-}.md"
|
||||
|
||||
read -p "VPN username (without spaces): " vpn_user
|
||||
[ -z "$vpn_user" ] && log_error "VPN username cannot be empty"
|
||||
|
||||
# Use hidden input for password
|
||||
unset vpn_password
|
||||
charcount=0
|
||||
prompt="What's your VPN password? (if you are using mullvad, just enter your username again): "
|
||||
while IFS= read -p "$prompt" -r -s -n 1 char
|
||||
do
|
||||
if [[ $char == $'\0' ]]
|
||||
then
|
||||
prompt="VPN password (if using mullvad, enter username again): "
|
||||
while IFS= read -p "$prompt" -r -s -n 1 char; do
|
||||
if [[ $char == $'\0' ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ $char == $'\177' ]]; then
|
||||
@ -205,131 +229,261 @@ if [ "$setup_vpn" == "y" ]; then
|
||||
fi
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "Configuring the docker-compose file for the user \"$username\" on \"$install_directory\"..."
|
||||
[ -z "$vpn_password" ] && log_error "VPN password cannot be empty"
|
||||
|
||||
copy_files=(
|
||||
"docker-compose.example.yaml:$filename"
|
||||
".env.example:$env_file"
|
||||
"docker-compose.custom.yaml:$custom_file_filename"
|
||||
# Export for use in other functions
|
||||
export vpn_service vpn_user vpn_password setup_vpn
|
||||
}
|
||||
|
||||
running_services_location() {
|
||||
local host_ip
|
||||
host_ip=$(hostname -I | awk '{ print $1 }')
|
||||
|
||||
local -A services=(
|
||||
["qBittorrent"]="8081"
|
||||
["SABnzbd"]="8080"
|
||||
["Radarr"]="7878"
|
||||
["Sonarr"]="8989"
|
||||
["Lidarr"]="8686"
|
||||
["Readarr"]="8787"
|
||||
["Prowlarr"]="9696"
|
||||
["Bazarr"]="6767"
|
||||
["$media_service"]="$media_service_port"
|
||||
["Portainer"]="9000"
|
||||
)
|
||||
|
||||
for file_mapping in "${copy_files[@]}"; do
|
||||
source_file="${file_mapping%%:*}"
|
||||
destination_file="${file_mapping##*:}"
|
||||
echo -e "Service URLs:"
|
||||
for service in "${!services[@]}"; do
|
||||
echo "$service: http://$host_ip:${services[$service]}/"
|
||||
done
|
||||
}
|
||||
|
||||
echo -e "\nCopying $source_file to $destination_file..."
|
||||
if cp "$source_file" "$destination_file"; then
|
||||
send_success_message "$source_file was copied successfuly! ✅"
|
||||
get_user_info() {
|
||||
read -p "User to own the media server files? [$USER]: " username
|
||||
username=${username:-$USER}
|
||||
|
||||
if id -u "$username" &>/dev/null; then
|
||||
puid=$(id -u "$username")
|
||||
pgid=$(id -g "$username")
|
||||
else
|
||||
send_error_message "Failed to copy $source_file to $destination_file. Ensure your user ($USER) has the necessary permissions ❌"
|
||||
log_error "User \"$username\" doesn't exist!"
|
||||
fi
|
||||
|
||||
export username puid pgid
|
||||
}
|
||||
|
||||
get_installation_paths() {
|
||||
read -p "Installation directory? [$DEFAULT_INSTALL_DIR]: " install_directory
|
||||
install_directory=${install_directory:-$DEFAULT_INSTALL_DIR}
|
||||
create_and_verify_directory "$install_directory" "installation"
|
||||
|
||||
read -p "Media directory? [$DEFAULT_MEDIA_DIR]: " media_directory
|
||||
media_directory=${media_directory:-$DEFAULT_MEDIA_DIR}
|
||||
|
||||
read -p "Are you sure your media directory is \"$media_directory\"? (y/N) [Default = n]: " media_directory_correct
|
||||
media_directory_correct=${media_directory_correct:-"n"}
|
||||
|
||||
if [ "${media_directory_correct,,}" != "y" ]; then
|
||||
log_error "Media directory is not correct. Please fix it and run the script again ❌"
|
||||
fi
|
||||
|
||||
setup_directory_structure "$media_directory"
|
||||
verify_user_permissions "$username" "$media_directory"
|
||||
|
||||
export install_directory media_directory
|
||||
}
|
||||
|
||||
copy_configuration_files() {
|
||||
local -A files=(
|
||||
["docker-compose.example.yaml"]="docker-compose.yaml"
|
||||
[".env.example"]=".env"
|
||||
["docker-compose.custom.yaml"]="docker-compose.custom.yaml"
|
||||
)
|
||||
|
||||
for src in "${!files[@]}"; do
|
||||
local dest="$install_directory/${files[$src]}"
|
||||
echo
|
||||
log_info "Copying $src to $dest..."
|
||||
|
||||
if cp "$src" "$dest"; then
|
||||
log_success "$src copied successfully ✅"
|
||||
else
|
||||
log_error "Failed to copy $src to $dest. Check permissions ❌"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
sed -i -e "s|<your_PUID>|$puid|g" "$env_file" \
|
||||
-e "s|<your_PGID>|$pgid|g" "$env_file" \
|
||||
-e "s|<media_directory>|$media_directory|g" "$env_file" \
|
||||
-e "s|<media_service>|$media_service|g" "$env_file" \
|
||||
-e "s|<media_service>|$media_service|g" "$filename"
|
||||
update_configuration_files() {
|
||||
local filename="$install_directory/docker-compose.yaml"
|
||||
local env_file="$install_directory/.env"
|
||||
local yams_script="yams"
|
||||
|
||||
# Update .env file
|
||||
log_info "Updating environment configuration..."
|
||||
sed -i -e "s|<your_PUID>|$puid|g" \
|
||||
-e "s|<your_PGID>|$pgid|g" \
|
||||
-e "s|<media_directory>|$media_directory|g" \
|
||||
-e "s|<media_service>|$media_service|g" \
|
||||
-e "s|<install_directory>|$install_directory|g" \
|
||||
-e "s|vpn_enabled|$setup_vpn|g" "$env_file" || \
|
||||
log_error "Failed to update .env file"
|
||||
|
||||
# Update VPN configuration in .env file
|
||||
if [ "${setup_vpn,,}" == "y" ]; then
|
||||
sed -i -e "s|^VPN_ENABLED=.*|VPN_ENABLED=y|" \
|
||||
-e "s|^VPN_SERVICE=.*|VPN_SERVICE=$vpn_service|" \
|
||||
-e "s|^VPN_USER=.*|VPN_USER=$vpn_user|" \
|
||||
-e "s|^VPN_PASSWORD=.*|VPN_PASSWORD=$vpn_password|" "$env_file" || \
|
||||
log_error "Failed to update VPN configuration in .env"
|
||||
else
|
||||
sed -i -e "s|^VPN_ENABLED=.*|VPN_ENABLED=n|" "$env_file" || \
|
||||
log_error "Failed to update VPN configuration in .env"
|
||||
fi
|
||||
|
||||
# Update docker-compose.yaml
|
||||
log_info "Updating docker-compose configuration..."
|
||||
sed -i "s|<media_service>|$media_service|g" "$filename" || \
|
||||
log_error "Failed to update docker-compose.yaml"
|
||||
|
||||
# Configure Plex-specific settings
|
||||
if [ "$media_service" == "plex" ]; then
|
||||
sed -i -e "s|#network_mode: host # plex|network_mode: host # plex|g" "$filename" \
|
||||
-e "s|ports: # plex|#ports: # plex|g" "$filename" \
|
||||
-e "s|- 8096:8096 # plex|#- 8096:8096 # plex|g" "$filename"
|
||||
log_info "Configuring Plex-specific settings..."
|
||||
sed -i -e 's|#network_mode: host # plex|network_mode: host # plex|g' \
|
||||
-e 's|ports: # plex|#ports: # plex|g' \
|
||||
-e 's|- 8096:8096 # plex|#- 8096:8096 # plex|g' "$filename" || \
|
||||
log_error "Failed to configure Plex settings"
|
||||
fi
|
||||
|
||||
sed -i -e "s|<install_directory>|$install_directory|g" "$env_file" \
|
||||
-e "s|vpn_enabled|$setup_vpn|g" "$env_file" \
|
||||
|
||||
if [ "$setup_vpn" == "y" ]; then
|
||||
sed -i -e "s|vpn_service|$vpn_service|g" "$env_file" \
|
||||
-e "s|vpn_user|$vpn_user|g" "$env_file" \
|
||||
-e "s|vpn_password|$vpn_password|g" "$env_file" \
|
||||
-e "s|#network_mode: \"service:gluetun\"|network_mode: \"service:gluetun\"|g" "$filename" \
|
||||
-e "s|ports: # qbittorrent|#ports: # qbittorrent|g" "$filename" \
|
||||
-e "s|- 8080:8080 # qbittorrent|#- 8080:8080 # qbittorrent|g" "$filename" \
|
||||
-e "s|#- 8080:8080/tcp # gluetun|- 8080:8080/tcp # gluetun|g" "$filename"
|
||||
# Configure VPN settings if enabled
|
||||
if [ "${setup_vpn,,}" == "y" ]; then
|
||||
log_info "Configuring VPN settings..."
|
||||
sed -i -e "s|vpn_service|$vpn_service|g" \
|
||||
-e "s|vpn_user|$vpn_user|g" \
|
||||
-e "s|vpn_password|$vpn_password|g" \
|
||||
-e 's|#network_mode: "service:gluetun"|network_mode: "service:gluetun"|g' \
|
||||
-e 's|ports: # qbittorrent|#ports: # qbittorrent|g' \
|
||||
-e 's|ports: # sabnzbd|#ports: # sabnzbd|g' \
|
||||
-e 's|- 8081:8081 # qbittorrent|#- 8081:8081 # qbittorrent|g' \
|
||||
-e 's|- 8080:8080 # sabnzbd|#- 8080:8080 # sabnzbd|g' \
|
||||
-e 's|#- 8080:8080/tcp # gluetun|- 8080:8080/tcp # gluetun|g' \
|
||||
-e 's|#- 8081:8081/tcp # gluetun|- 8081:8081/tcp # gluetun|g' "$filename" || \
|
||||
log_error "Failed to configure VPN settings"
|
||||
fi
|
||||
|
||||
sed -i -e "s|<filename>|$filename|g" yams \
|
||||
-e "s|<custom_file_filename>|$custom_file_filename|g" yams \
|
||||
-e "s|<install_directory>|$install_directory|g" yams
|
||||
|
||||
send_success_message "Everything installed correctly! 🎉"
|
||||
|
||||
echo "Running the server..."
|
||||
echo "This is going to take a while..."
|
||||
|
||||
docker compose -f "$filename" up -d
|
||||
|
||||
echo -e "\nWe need your sudo password to install the YAMS CLI and configure permissions..."
|
||||
# Update YAMS CLI script
|
||||
log_info "Updating YAMS CLI configuration..."
|
||||
sed -i -e "s|<filename>|$filename|g" \
|
||||
-e "s|<custom_file_filename>|$install_directory/docker-compose.custom.yaml|g" \
|
||||
-e "s|<install_directory>|$install_directory|g" "$yams_script" || \
|
||||
log_error "Failed to update YAMS CLI script"
|
||||
}
|
||||
|
||||
install_cli() {
|
||||
echo
|
||||
log_info "Installing YAMS CLI..."
|
||||
if sudo cp yams /usr/local/bin/yams && sudo chmod +x /usr/local/bin/yams; then
|
||||
send_success_message "YAMS CLI installed successfully ✅"
|
||||
log_success "YAMS CLI installed successfully ✅"
|
||||
else
|
||||
send_error_message "Failed to install YAMS CLI. Make sure you have the necessary permissions ❌"
|
||||
log_error "Failed to install YAMS CLI. Check permissions ❌"
|
||||
fi
|
||||
}
|
||||
|
||||
set_permissions() {
|
||||
local dirs=("$media_directory" "$install_directory" "$install_directory/config")
|
||||
|
||||
for dir in "${dirs[@]}"; do
|
||||
log_info "Setting permissions for $dir..."
|
||||
if [ ! -d "$dir" ]; then
|
||||
mkdir -p "$dir" || log_error "Failed to create directory $dir"
|
||||
fi
|
||||
|
||||
if sudo chown -R "$puid":"$pgid" "$media_directory"; then
|
||||
send_success_message "Media directory ownership and permissions set successfully ✅"
|
||||
if sudo chown -R "$puid:$pgid" "$dir"; then
|
||||
log_success "Permissions set successfully for $dir ✅"
|
||||
else
|
||||
send_error_message "Failed to set ownership and permissions for the media directory. Check permissions ❌"
|
||||
log_error "Failed to set permissions for $dir ❌"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Prevent running as root
|
||||
if [[ "$EUID" = 0 ]]; then
|
||||
log_error "YAMS must run without sudo! Please run with regular permissions"
|
||||
fi
|
||||
|
||||
if sudo chown -R "$puid":"$pgid" "$install_directory"; then
|
||||
send_success_message "Install directory ownership and permissions set successfully ✅"
|
||||
else
|
||||
send_error_message "Failed to set ownership and permissions for the install directory. Check permissions ❌"
|
||||
# Check all dependencies
|
||||
log_info "Checking prerequisites..."
|
||||
check_dependencies
|
||||
|
||||
# Get user information
|
||||
get_user_info
|
||||
|
||||
# Get installation paths
|
||||
get_installation_paths
|
||||
|
||||
# Configure services
|
||||
configure_media_service
|
||||
configure_vpn
|
||||
|
||||
log_info "Configuring the docker-compose file for user \"$username\" in \"$install_directory\"..."
|
||||
|
||||
# Copy and update configuration files
|
||||
copy_configuration_files
|
||||
update_configuration_files
|
||||
|
||||
log_success "Everything installed correctly! 🎉"
|
||||
|
||||
# Start services
|
||||
log_info "Starting YAMS services..."
|
||||
log_info "This may take a while..."
|
||||
|
||||
if ! docker compose -f "$install_directory/docker-compose.yaml" up -d; then
|
||||
log_error "Failed to start YAMS services"
|
||||
fi
|
||||
|
||||
if [[ -d "$install_directory/config" ]]; then
|
||||
send_success_message "Configuration folder \"$install_directory/config\" exists ✅"
|
||||
else
|
||||
if sudo mkdir -p "$install_directory/config"; then
|
||||
send_success_message "Configuration folder \"$install_directory/config\" created ✅"
|
||||
else
|
||||
send_error_message "Failed to create or access the configuration folder. Check permissions ❌"
|
||||
fi
|
||||
fi
|
||||
|
||||
if sudo chown -R "$puid":"$pgid" "$install_directory/config"; then
|
||||
send_success_message "Configuration folder ownership and permissions set successfully ✅"
|
||||
else
|
||||
send_error_message "Failed to set ownership and permissions for the configuration folder. Check permissions ❌"
|
||||
fi
|
||||
# Install CLI and set permissions
|
||||
echo
|
||||
log_info "We need your sudo password to install the YAMS CLI and configure permissions..."
|
||||
install_cli
|
||||
set_permissions
|
||||
|
||||
printf "\033c"
|
||||
|
||||
echo "========================================================"
|
||||
echo " _____ ___ ___ ___ "
|
||||
echo " / /::\ / /\ /__/\ / /\ "
|
||||
echo " / /:/\:\ / /::\ \ \:\ / /:/_ "
|
||||
echo " / /:/ \:\ / /:/\:\ \ \:\ / /:/ /\ "
|
||||
echo " /__/:/ \__\:| / /:/ \:\ _____\__\:\ / /:/ /:/_ "
|
||||
echo " \ \:\ / /:/ /__/:/ \__\:\ /__/::::::::\ /__/:/ /:/ /\\"
|
||||
echo " \ \:\ /:/ \ \:\ / /:/ \ \:\~~\~~\/ \ \:\/:/ /:/"
|
||||
echo " \ \:\/:/ \ \:\ /:/ \ \:\ ~~~ \ \::/ /:/ "
|
||||
echo " \ \::/ \ \:\/:/ \ \:\ \ \:\/:/ "
|
||||
echo " \__\/ \ \::/ \ \:\ \ \::/ "
|
||||
echo " \__\/ \__\/ \__\/ "
|
||||
echo "========================================================"
|
||||
send_success_message "All done!✅ Enjoy YAMS!"
|
||||
echo "You can check the installation on $install_directory"
|
||||
echo "========================================================"
|
||||
echo "Everything should be running now! To check everything running, go to:"
|
||||
cat << "EOF"
|
||||
========================================================
|
||||
_____ ___ ___ ___
|
||||
/ /::\ / /\ /__/\ / /\
|
||||
/ /:/\:\ / /::\ \ \:\ / /:/_
|
||||
/ /:/ \:\ / /:/\:\ \ \:\ / /:/ /\
|
||||
/__/:/ \__\:| / /:/ \:\ _____\__\:\ / /:/ /:/_
|
||||
\ \:\ / /:/ /__/:/ \__\:\ /__/::::::::\ /__/:/ /:/ /\\
|
||||
\ \:\ /:/ \ \:\ / /:/ \ \:\~~\~~\/ \ \:\/:/ /:/
|
||||
\ \:\/:/ \ \:\ /:/ \ \:\ ~~~ \ \::/ /:/
|
||||
\ \::/ \ \:\/:/ \ \:\ \ \:\/:/
|
||||
\__\/ \ \::/ \ \:\ \ \::/
|
||||
\__\/ \__\/ \__\/
|
||||
========================================================
|
||||
EOF
|
||||
|
||||
log_success "All done!✅ Enjoy YAMS!"
|
||||
log_info "You can check the installation in $install_directory"
|
||||
log_info "========================================================"
|
||||
log_info "Everything should be running now! To check everything running, go to:"
|
||||
echo
|
||||
|
||||
running_services_location
|
||||
|
||||
echo
|
||||
log_info "You might need to wait for a couple of minutes while everything gets up and running"
|
||||
echo
|
||||
echo "You might need to wait for a couple of minutes while everything gets up and running"
|
||||
echo
|
||||
echo "All the services location are also saved in ~/yams_services.txt"
|
||||
log_info "All the service locations are also saved in ~/yams_services.txt"
|
||||
running_services_location > ~/yams_services.txt
|
||||
echo "========================================================"
|
||||
|
||||
log_info "========================================================"
|
||||
echo
|
||||
echo "To configure YAMS, check the documentation at"
|
||||
echo "https://yams.media/config"
|
||||
log_info "To configure YAMS, check the documentation at"
|
||||
log_info "https://yams.media/config"
|
||||
echo
|
||||
echo "========================================================"
|
||||
log_info "========================================================"
|
||||
|
||||
exit 0
|
||||
|
315
yams
315
yams
@ -1,36 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
dc="docker compose -f <filename> -f <custom_file_filename>"
|
||||
install_directory="<install_directory>"
|
||||
|
||||
option=${1:-"--help"}
|
||||
|
||||
help() {
|
||||
echo "yams - Yet Another Media Server"
|
||||
echo
|
||||
echo "Usage: yams [--help|restart|stop|start|destroy|check-vpn|update]"
|
||||
echo "options:"
|
||||
echo "--help displays this help message"
|
||||
echo "restart restarts yams services"
|
||||
echo "stop stops all yams services"
|
||||
echo "start starts yams services"
|
||||
echo "destroy destroy yams services so you can start from scratch"
|
||||
echo "check-vpn checks if the VPN is working as expected"
|
||||
echo "update updates YAMS"
|
||||
}
|
||||
|
||||
send_success_message() {
|
||||
echo -e "$(printf "\e[32m$1\e[0m")"
|
||||
}
|
||||
|
||||
send_error_message() {
|
||||
echo -e "$(printf "\e[31m$1\e[0m")"
|
||||
exit 255
|
||||
}
|
||||
|
||||
find_available_ip_endpoint() {
|
||||
ip_endpoints=(
|
||||
# Constants
|
||||
readonly DC="docker compose -f <filename> -f <custom_file_filename>"
|
||||
readonly INSTALL_DIRECTORY="<install_directory>"
|
||||
readonly TIMEOUT_SECONDS=60
|
||||
readonly IP_ENDPOINTS=(
|
||||
"https://ipinfo.io/ip"
|
||||
"https://api.ipify.org"
|
||||
"https://checkip.amazonaws.com"
|
||||
@ -39,104 +14,218 @@ find_available_ip_endpoint() {
|
||||
"https://wtfismyip.com/text"
|
||||
)
|
||||
|
||||
for ip in ${ip_endpoints[@]}; do
|
||||
endpoint=$(curl -s "$ip")
|
||||
if [ "$endpoint" != "" ]; then
|
||||
echo $ip
|
||||
break
|
||||
fi
|
||||
done
|
||||
# Color codes for better readability
|
||||
readonly RED='\033[0;31m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Available commands
|
||||
declare -A COMMANDS=(
|
||||
["--help"]="displays this help message"
|
||||
["restart"]="restarts yams services"
|
||||
["stop"]="stops all yams services"
|
||||
["start"]="starts yams services"
|
||||
["destroy"]="destroy yams services so you can start from scratch"
|
||||
["check-vpn"]="checks if the VPN is working as expected"
|
||||
["backup"]="backs up yams to the destination location"
|
||||
)
|
||||
|
||||
# Functions
|
||||
log_success() {
|
||||
echo -e "${GREEN}$1${NC}"
|
||||
}
|
||||
|
||||
if [ "$option" == "--help" ]; then
|
||||
help
|
||||
exit 0
|
||||
log_error() {
|
||||
echo -e "${RED}$1${NC}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}$1${NC}"
|
||||
}
|
||||
|
||||
show_help() {
|
||||
echo "yams - Yet Another Media Server"
|
||||
echo
|
||||
echo "Usage: yams [command] [options]"
|
||||
echo
|
||||
echo "Commands:"
|
||||
for cmd in "${!COMMANDS[@]}"; do
|
||||
printf "%-25s %s\n" "$cmd" "${COMMANDS[$cmd]}"
|
||||
done
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " yams start # Start all YAMS services"
|
||||
echo " yams backup /path/to/backup # Backup YAMS to specified directory"
|
||||
}
|
||||
|
||||
wait_for_services() {
|
||||
local wait_time=0
|
||||
echo -n "Waiting for services to start"
|
||||
|
||||
while [ $wait_time -lt $TIMEOUT_SECONDS ]; do
|
||||
# Get the total number of services and number of running services
|
||||
local total_services
|
||||
local running_services
|
||||
|
||||
total_services=$($DC ps --format '{{.Name}}' | wc -l)
|
||||
running_services=$($DC ps --format '{{.Status}}' | grep -c "Up")
|
||||
|
||||
if [ "$total_services" -eq "$running_services" ]; then
|
||||
echo
|
||||
log_success "All $total_services services are up and running!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$option" == "restart" ]; then
|
||||
$dc stop && $dc up -d
|
||||
echo "YAMS is starting. Wait 1 min until all the services are up and running..."
|
||||
exit 0
|
||||
# Show progress with count
|
||||
echo -n "."
|
||||
sleep 1
|
||||
((wait_time++))
|
||||
|
||||
# Every 10 seconds, show status
|
||||
if [ $((wait_time % 10)) -eq 0 ]; then
|
||||
echo
|
||||
echo -n "$running_services/$total_services services running"
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
log_error "Not all services started within ${TIMEOUT_SECONDS} seconds ($running_services/$total_services running)"
|
||||
}
|
||||
|
||||
find_available_ip_endpoint() {
|
||||
for endpoint in "${IP_ENDPOINTS[@]}"; do
|
||||
if curl -s --connect-timeout 5 "$endpoint" > /dev/null; then
|
||||
echo "$endpoint"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
get_ip_with_retries() {
|
||||
local context=$1 # "local" or "qbittorrent"
|
||||
local cmd_prefix=""
|
||||
|
||||
if [ "$context" = "qbittorrent" ]; then
|
||||
cmd_prefix="docker exec qbittorrent"
|
||||
fi
|
||||
|
||||
if [ "$option" == "stop" ]; then
|
||||
$dc stop
|
||||
exit 0
|
||||
for endpoint in "${IP_ENDPOINTS[@]}"; do
|
||||
local ip
|
||||
if [ "$context" = "local" ]; then
|
||||
ip=$(curl -s --connect-timeout 5 "$endpoint")
|
||||
else
|
||||
ip=$($cmd_prefix curl -s --connect-timeout 5 "$endpoint")
|
||||
fi
|
||||
|
||||
if [ "$option" == "start" ]; then
|
||||
$dc up -d
|
||||
echo "YAMS is starting. Wait 1 min until all the services are up and running..."
|
||||
exit 0
|
||||
if [ -n "$ip" ] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "$ip"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ "$option" == "check-vpn" ]; then
|
||||
check_vpn() {
|
||||
echo "Getting your IP..."
|
||||
ip_endpoint=$(find_available_ip_endpoint)
|
||||
if [ "$ip_endpoint" == "" ]; then
|
||||
send_error_message "No available endpoint to get IP address!"
|
||||
fi
|
||||
your_ip=$(curl -s $ip_endpoint)
|
||||
echo "$your_ip"
|
||||
echo "Your local IP country is $(curl -s https://am.i.mullvad.net/country)"
|
||||
echo
|
||||
echo
|
||||
echo "Getting your qBittorrent IP..."
|
||||
local your_ip
|
||||
your_ip=$(get_ip_with_retries "local") || log_error "Failed to get your IP address from any endpoint"
|
||||
echo "Your IP: $your_ip"
|
||||
|
||||
qbittorrent_ip=$(docker exec qbittorrent sh -c "curl -s $ip_endpoint");
|
||||
if [ -n "$qbittorrent_ip" ]; then
|
||||
echo "$qbittorrent_ip"
|
||||
echo "Your country in qBittorrent is $(docker exec -it qbittorrent sh -c 'curl -s https://am.i.mullvad.net/country')"
|
||||
if [ "$qbittorrent_ip" == "$your_ip" ]; then
|
||||
send_error_message "Your IPs are the same! qBittorrent is exposing your IP! ⚠️"
|
||||
local country
|
||||
country=$(curl -s --connect-timeout 5 "https://am.i.mullvad.net/country") || log_warning "Couldn't determine your country"
|
||||
[ -n "$country" ] && echo "Your local IP country is $country"
|
||||
|
||||
echo -e "\nGetting your qBittorrent IP..."
|
||||
local qbit_ip
|
||||
qbit_ip=$(get_ip_with_retries "qbittorrent") || log_error "Failed to get qBittorrent IP from any endpoint"
|
||||
echo "qBittorrent IP: $qbit_ip"
|
||||
|
||||
local qbit_country
|
||||
qbit_country=$(docker exec qbittorrent curl -s --connect-timeout 5 "https://am.i.mullvad.net/country") || log_warning "Couldn't determine qBittorrent country"
|
||||
[ -n "$qbit_country" ] && echo "qBittorrent country is $qbit_country"
|
||||
|
||||
if [ "$qbit_ip" == "$your_ip" ]; then
|
||||
log_error "⚠️ WARNING: Your IPs are the same! qBittorrent is exposing your IP!"
|
||||
else
|
||||
send_success_message "Your IPs are different. qBittorrent is masking your IP! ✅ "
|
||||
log_success "✅ Success: Your IPs are different. qBittorrent is masking your IP!"
|
||||
fi
|
||||
else
|
||||
send_error_message "Failed to retrieve qBittorrent IP. Please check your setup. ⚠️"
|
||||
}
|
||||
|
||||
backup_yams() {
|
||||
local destination=$1
|
||||
local backup_date
|
||||
backup_date=$(date '+%Y-%m-%d-%s')
|
||||
local backup_file="$destination/yams-backup-$backup_date.tar.gz"
|
||||
|
||||
echo "Stopping YAMS services..."
|
||||
$DC stop > /dev/null 2>&1 || log_error "Failed to stop services"
|
||||
|
||||
echo -e "\nBacking up YAMS to $destination..."
|
||||
echo "This may take a while depending on the size of your installation."
|
||||
echo "Please wait... ⌛"
|
||||
|
||||
# Copy current yams script and create backup
|
||||
cp "$(which yams)" "$INSTALL_DIRECTORY" || log_warning "Failed to backup yams script"
|
||||
tar --exclude='transcoding-temp' -czf "$backup_file" -C "$INSTALL_DIRECTORY" . ||
|
||||
log_error "Failed to create backup archive"
|
||||
|
||||
echo -e "\nStarting YAMS services..."
|
||||
$DC start > /dev/null 2>&1 || log_warning "Failed to restart services"
|
||||
|
||||
log_success "Backup completed successfully! 🎉"
|
||||
echo "Backup file: $backup_file"
|
||||
}
|
||||
|
||||
destroy_yams() {
|
||||
echo -e "\nWARNING: This will destroy all your YAMS services!"
|
||||
read -p "Are you sure you want to continue? This is not recoverable! ⚠️ 🚨 [y/N]: " -r
|
||||
if [[ ${REPLY,,} =~ ^y$ ]]; then
|
||||
$DC down || log_error "Failed to destroy services"
|
||||
echo -e "\nYAMS services were destroyed. To restart, run: yams start"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local command=${1:-"--help"}
|
||||
local destination=${2:-.}
|
||||
|
||||
# Validate and normalize destination path if provided
|
||||
if [ "$command" = "backup" ]; then
|
||||
destination=$(realpath "$destination") || log_error "Invalid backup destination path"
|
||||
fi
|
||||
|
||||
if [ "$option" == "destroy" ]; then
|
||||
echo
|
||||
echo
|
||||
read -p "Are you sure you want to destroy all your yams services? THIS IS NOT RECOVERABLE! ⚠️ ️🚨 [y/N]: " destroy_now
|
||||
destroy_now=${destroy_now:-"n"}
|
||||
if [ "$destroy_now" == "y" ]; then
|
||||
$dc down
|
||||
echo
|
||||
echo
|
||||
echo "yams services were destroyed. To restart, run: "
|
||||
echo "\$ yams start"
|
||||
fi
|
||||
fi
|
||||
case "$command" in
|
||||
--help)
|
||||
show_help
|
||||
;;
|
||||
restart)
|
||||
$DC stop && $DC up -d
|
||||
wait_for_services
|
||||
;;
|
||||
stop)
|
||||
$DC stop || log_error "Failed to stop services"
|
||||
log_success "Services stopped successfully"
|
||||
;;
|
||||
start)
|
||||
$DC up -d || log_error "Failed to start services"
|
||||
wait_for_services
|
||||
;;
|
||||
check-vpn)
|
||||
check_vpn
|
||||
;;
|
||||
destroy)
|
||||
destroy_yams
|
||||
;;
|
||||
backup)
|
||||
backup_yams "$destination"
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown command: $command\nRun 'yams --help' for usage information"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [ "$option" == "update" ]; then
|
||||
echo "Updating YAMS..."
|
||||
$dc stop
|
||||
rm -rf /tmp/yams && mkdir /tmp/yams
|
||||
wget https://gitlab.com/rogs/yams/-/raw/master/docker-compose.example.yaml -O /tmp/yams/docker-compose.example.yml > /dev/null 2>&1
|
||||
source $install_directory/.env
|
||||
|
||||
filename="$install_directory/docker-compose.yaml"
|
||||
|
||||
cp /tmp/yams/docker-compose.example.yml $filename
|
||||
|
||||
|
||||
sed -i -e "s;<media_service>;$MEDIA_SERVICE;g" "$filename"
|
||||
if [ "$MEDIA_SERVICE" == "plex" ]; then
|
||||
sed -i -e "s|#network_mode: host # plex|network_mode: host # plex|g" "$filename" \
|
||||
-e "s|ports: # plex|#ports: # plex|g" $filename \
|
||||
-e "s|- 8096:8096 # plex|#- 8096:8096 # plex|g" $filename
|
||||
fi
|
||||
|
||||
if [ "$VPN_ENABLED" == "y" ]; then
|
||||
sed -i -e "s;#network_mode: \"service:gluetun\";network_mode: \"service:gluetun\";g" "$filename" \
|
||||
-e "s;ports: # qbittorrent;#port: # qbittorrent;g" "$filename" \
|
||||
-e "s;- 8080:8080 # qbittorrent;#- 8080:8080 # qbittorrent;g" "$filename" \
|
||||
-e "s;#- 8080:8080/tcp # gluetun;- 8080:8080/tcp # gluetun;g" "$filename"
|
||||
fi
|
||||
|
||||
$dc up -d
|
||||
echo "YAMS was updated and it is starting. Wait 1 min until all the services are up and running..."
|
||||
fi
|
||||
main "$@"
|
||||
|
Loading…
x
Reference in New Issue
Block a user