V3 - The big one #71

Merged
rogs merged 31 commits from v3 into master 2024-12-30 11:01:29 -03:00
6 changed files with 1052 additions and 749 deletions

Binary file not shown.

View File

@ -1,4 +1,2 @@
version: "3"
# services:
# Add your custom services here!

View File

@ -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
View File

@ -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

View File

@ -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
View File

@ -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 "$@"