#+title: Docs #+PROPERTY: header-args :tangle install.sh #+auto_tangle: t * Table of contents :toc: :PROPERTIES: :ID: faf95c8a-9133-4072-8544-0ef456a67611 :END: - [[#welcome-message][Welcome message]] - [[#constants-and-configuration][Constants and Configuration]] - [[#functions][Functions]] - [[#message-formatting][Message formatting]] - [[#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: #+begin_src bash #!/bin/bash set -euo pipefail printf "\033c" echo "====================================================" echo " ___ ___ ___ " echo " ___ / /\ /__/\ / /\ " echo " /__/| / /::\ | |::\ / /:/_ " echo " | |:| / /:/\:\ | |:|:\ / /:/ /\ " echo " | |:| / /:/~/::\ __|__|:|\:\ / /:/ /::\\" echo " __|__|:| /__/:/ /:/\:\ /__/::::| \:\ /__/:/ /:/\:\\" echo "/__/::::\ \ \:\/:/__\/ \ \:\~~\__\/ \ \:\/:/~/:/" echo " ~\~~\:\ \ \::/ \ \:\ \ \::/ /:/ " echo " \ \:\ \ \:\ \ \:\ \__\/ /:/ " echo " \__\/ \ \:\ \ \:\ /__/:/ " echo " \__\/ \__\/ \__\/ " echo "====================================================" echo "Welcome to YAMS (Yet Another Media Server)" echo "Installation process should be really quick" echo "We just need you to answer some questions" echo "We are going to ask for your sudo password in the end" echo "To finish the installation of the CLI" 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=("tv" "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" "docker" "docker" "sed" "awk") #+end_src * Functions :PROPERTIES: :ID: 111a7df4-08f5-4e6c-a799-dd822c5d030e :END: ** Message formatting :PROPERTIES: :ID: 61387bd4-2ecf-44fe-ac69-dc6347c0d1b8 :END: #+begin_src bash 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 ** Directory Handling :PROPERTIES: :ID: new-directory-section :END: #+begin_src bash 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 Dependencies :PROPERTIES: :ID: e7d01eeb-c7ef-42ff-b60d-010be30bc6a8 :END: #+begin_src bash check_dependencies() { # Check for required commands for cmd in "${REQUIRED_COMMANDS[@]}"; do if ! command -v "$cmd" &> /dev/null; then log_error "Required command '$cmd' not found!" fi done # Check Docker 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 ** Service Configuration :PROPERTIES: :ID: new-service-section :END: #+begin_src bash configure_media_service() { log_info "\nTime 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)" 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)}') 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 else media_service_port=8096 fi log_success "\nYAMS will install \"$media_service\" on port \"$media_service_port\"" # Export for use in other functions export media_service media_service_port } configure_vpn() { log_info "\nTime 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 export setup_vpn="n" return 0 fi read -p "VPN service? (with spaces) [$DEFAULT_VPN_SERVICE]: " vpn_service vpn_service=${vpn_service:-$DEFAULT_VPN_SERVICE} log_info "\nPlease 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="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 if [ $charcount -gt 0 ]; then charcount=$((charcount-1)) prompt=$'\b \b' vpn_password="${vpn_password%?}" else prompt='' fi else charcount=$((charcount+1)) prompt='*' vpn_password+="$char" fi done echo [ -z "$vpn_password" ] && log_error "VPN password cannot be empty" # 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" ) echo -e "Service URLs:" for service in "${!services[@]}"; do echo "$service: http://$host_ip:${services[$service]}/" done } 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 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]}" log_info "\nCopying $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 } 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||$puid|g" \ -e "s||$pgid|g" \ -e "s||$media_directory|g" \ -e "s||$media_service|g" \ -e "s||$install_directory|g" \ -e "s|vpn_enabled|$setup_vpn|g" "$env_file" || \ log_error "Failed to update .env file" # Update docker-compose.yaml log_info "Updating docker-compose configuration..." sed -i "s||$media_service|g" "$filename" || \ log_error "Failed to update docker-compose.yaml" # Configure Plex-specific settings if [ "$media_service" == "plex" ]; then 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 # 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 # Update YAMS CLI script log_info "Updating YAMS CLI configuration..." sed -i -e "s||$filename|g" \ -e "s||$install_directory/docker-compose.custom.yaml|g" \ -e "s||$install_directory|g" "$yams_script" || \ log_error "Failed to update YAMS CLI script" } install_cli() { log_info "\nInstalling YAMS CLI..." if sudo cp yams /usr/local/bin/yams && sudo chmod +x /usr/local/bin/yams; then log_success "YAMS CLI installed successfully ✅" else 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 * Script Execution :PROPERTIES: :ID: new-script-execution :END: ** Verify Prerequisites :PROPERTIES: :ID: e945d5a8-5142-41fe-8175-96de7aa84cf2 :END: #+begin_src bash # Prevent running as root if [[ "$EUID" = 0 ]]; then log_error "YAMS must run without sudo! Please run with regular permissions" fi # Check all dependencies log_info "Checking prerequisites..." check_dependencies #+end_src ** 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 log_info "\nWe 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: #+begin_src bash printf "\033c" 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 log_info "All the service locations are also saved in ~/yams_services.txt" running_services_location > ~/yams_services.txt log_info "========================================================" echo log_info "To configure YAMS, check the documentation at" log_info "https://yams.media/config" echo log_info "========================================================" exit 0 #+end_src