diff --git a/menu/alias/alias.yml b/menu/alias/alias.yml new file mode 100644 index 00000000..532a040f --- /dev/null +++ b/menu/alias/alias.yml @@ -0,0 +1,147 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Install PGVPN + template: + src: pgvpn + dest: /bin/pgvpn + force: yes + mode: 0775 + + - name: Install Status UFSMonitor + template: + src: sufs + dest: /bin/sufs + force: yes + mode: 0775 + + - name: Nano UFSMonitor + template: + src: nufs + dest: /bin/nufs + force: yes + mode: 0775 + + ###### Install PTSupdate + + - name: PTSupdate + template: + src: ptsupdate + dest: /bin/ptsupdate + force: yes + mode: 0775 + + ###### Install PGBlitz + + - name: PlexGuide + template: + src: plexguide + dest: /bin/plexguide + force: yes + mode: 0775 + owner: root + + - name: PG + template: + src: pts + dest: /bin/pts + force: yes + mode: 0775 + owner: root + + - name: PGBlitz + template: + src: pgblitz + dest: /bin/pgblitz + force: yes + mode: 0775 + owner: root + + ###### Server reboot + + - name: server reboot + template: + src: reboot + dest: /bin/reboot + force: yes + mode: 0775 + + ###### Check list of services + + - name: list systemd services + template: + src: slist + dest: /bin/slist + force: yes + mode: 0775 + owner: root + + ###### Server update + + - name: update server + template: + src: update + dest: /bin/update + force: yes + mode: 0775 + + ###### Server upgrade + + - name: upgrade server + template: + src: upgrade + dest: /bin/upgrade + force: yes + mode: 0775 + owner: root + + ###### Install app + + - name: install appname + template: + src: install + dest: /bin/install + force: yes + mode: 0775 + owner: root + + ###### Autoremove installed app packages + + - name: autoremove unused packages after app install + template: + src: autoremove + dest: /bin/autoremove + force: yes + mode: 0775 + owner: root + + - name: Prune docker containers appname + template: + src: prune + dest: /bin/prune + force: yes + mode: 0775 + owner: root + + - name: Install PGFork + template: + src: pgfork + dest: /bin/pgfork + force: yes + mode: 0775 + + - name: Install Status UFSMonitor + template: + src: sufs + dest: /bin/ptsadd + force: yes + mode: 0775 + diff --git a/menu/alias/templates/autoremove b/menu/alias/templates/autoremove new file mode 100644 index 00000000..316e25eb --- /dev/null +++ b/menu/alias/templates/autoremove @@ -0,0 +1,9 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +sudo apt autoremove diff --git a/menu/alias/templates/backup b/menu/alias/templates/backup new file mode 100644 index 00000000..52e5bc6f --- /dev/null +++ b/menu/alias/templates/backup @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +bash /opt/plexguide/roles/backup/scripts/main.sh diff --git a/menu/alias/templates/install b/menu/alias/templates/install new file mode 100644 index 00000000..36b1d1ba --- /dev/null +++ b/menu/alias/templates/install @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +sudo apt install diff --git a/menu/alias/templates/nufs b/menu/alias/templates/nufs new file mode 100644 index 00000000..97264517 --- /dev/null +++ b/menu/alias/templates/nufs @@ -0,0 +1,9 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +sudo nano /etc/systemd/system/ufsmonitor.service && systemctl daemon-reload diff --git a/menu/alias/templates/pg b/menu/alias/templates/pg new file mode 100644 index 00000000..76ba9ba8 --- /dev/null +++ b/menu/alias/templates/pg @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +file="/var/pgblitz/pg.number" +if [ -e "$file" ]; then + check="$(cat /var/pgblitz/pg.number | head -c 1)" + if [[ "$check" == "5" || "$check" == "6" || "$check" == "7" ]]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 INSTALLER BLOCK: Notice +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +We detected PG Version $check is running! Per the instructions, PG 8 +must be installed on a FRESH BOX! Exiting! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + exit + fi +fi + +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/start.sh +source /opt/plexguide/menu/functions/install.sh + +mkdir -p /opt/plexguide/roles/log +mkdir -p /var/plexguide/logs +mkdir -p /opt/appdata/plexguide + +sudocheck +missingpull + +if [[ ! -e "/bin/pgblitz" ]]; then + cp /opt/plexguide/menu/alias/templates/pgblitz /bin +fi + +chown 1000:1000 /bin/pgblitz &>/dev/null & +chmod 0755 /bin/pgblitz &>/dev/null & +# pg deploy contains pgdeploy at end +pginstall diff --git a/menu/alias/templates/pgblitz b/menu/alias/templates/pgblitz new file mode 100644 index 00000000..36c228d4 --- /dev/null +++ b/menu/alias/templates/pgblitz @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +file="/var/plexguide/pg.number" +if [ -e "$file" ]; then + check="$(cat /var/plexguide/pg.number | head -c 1)" + if [[ "$check" == "5" || "$check" == "6" || "$check" == "7" ]]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 INSTALLER BLOCK: Notice +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +We detected PG Version $check is running! Per the instructions, PG 8 +must be installed on a FRESH BOX! Exiting! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + exit + fi +fi + +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/start.sh +source /opt/plexguide/menu/functions/install.sh + +mkdir -p /opt/plexguide/roles/log +mkdir -p /var/plexguide/logs +mkdir -p /opt/appdata/plexguide + +sudocheck +missingpull + +# pg deploy contains pgdeploy at end +pginstall diff --git a/menu/alias/templates/pgfork b/menu/alias/templates/pgfork new file mode 100644 index 00000000..25449d7f --- /dev/null +++ b/menu/alias/templates/pgfork @@ -0,0 +1,31 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +if [[ $EUID -ne 0 ]]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ You Must Execute as a SUDO USER (with sudo) or as ROOT! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + exit 1 +fi + +### Execut YML +ansible-playbook /opt/plexguide/menu/pgfork/main.yml + +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ SYSTEM MESSAGE: Installed User's Forked Version of PG! Standby! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +sleep 1.5 +file="/var/plexguide/community.app" +if [ -e "$file" ]; then rm -rf /var/plexguide/community.app; fi +bash /opt/plexguide/menu/interface/ending.sh diff --git a/menu/alias/templates/pgupdate b/menu/alias/templates/pgupdate new file mode 100644 index 00000000..f25980b5 --- /dev/null +++ b/menu/alias/templates/pgupdate @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/start.sh + +sudocheck +missingpull +exitcheck diff --git a/menu/alias/templates/pgvpn b/menu/alias/templates/pgvpn new file mode 100644 index 00000000..91488cc0 --- /dev/null +++ b/menu/alias/templates/pgvpn @@ -0,0 +1,15 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +echo "" +echo "-----------------------------------------------------" +echo "SYSTEM MESSAGE: Please Copy Your Information" +echo "-----------------------------------------------------" +echo "" +cat /opt/appdata/plexguide/vpn.info +echo "" +echo "Config Info: Visit http://pgvpn.pgblitz.com or WIKI" diff --git a/menu/alias/templates/plexguide b/menu/alias/templates/plexguide new file mode 100644 index 00000000..8206d134 --- /dev/null +++ b/menu/alias/templates/plexguide @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +file="/var/plexguide/pg.number" +if [ -e "$file" ]; then + check="$(cat /var/plexguide/pg.number | head -c 1)" + if [[ "$check" == "5" || "$check" == "6" || "$check" == "7" ]]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 INSTALLER BLOCK: Notice +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +We detected PG Version $check is running! Per the instructions, PG 8 +must be installed on a FRESH BOX! Exiting! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + exit + fi +fi + +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/start.sh +source /opt/plexguide/menu/functions/install.sh + +mkdir -p /opt/plexguide/roles/log +mkdir -p /var/plexguide/logs +mkdir -p /opt/appdata/plexguide + +sudocheck +missingpull + +if [[ ! -e "/bin/pgblitz" ]]; then + cp /opt/plexguide/menu/alias/templates/pgblitz /bin/ &>/dev/null & + chown 1000:1000 /bin/pgblitz + chmod 0755 /bin/pgblitz +fi +# pg deploy contains pgdeploy at end +pginstall diff --git a/menu/alias/templates/prune b/menu/alias/templates/prune new file mode 100644 index 00000000..d0337851 --- /dev/null +++ b/menu/alias/templates/prune @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +sudo docker system prune --volumes diff --git a/menu/alias/templates/pts b/menu/alias/templates/pts new file mode 100644 index 00000000..36c228d4 --- /dev/null +++ b/menu/alias/templates/pts @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +file="/var/plexguide/pg.number" +if [ -e "$file" ]; then + check="$(cat /var/plexguide/pg.number | head -c 1)" + if [[ "$check" == "5" || "$check" == "6" || "$check" == "7" ]]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 INSTALLER BLOCK: Notice +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +We detected PG Version $check is running! Per the instructions, PG 8 +must be installed on a FRESH BOX! Exiting! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + exit + fi +fi + +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/start.sh +source /opt/plexguide/menu/functions/install.sh + +mkdir -p /opt/plexguide/roles/log +mkdir -p /var/plexguide/logs +mkdir -p /opt/appdata/plexguide + +sudocheck +missingpull + +# pg deploy contains pgdeploy at end +pginstall diff --git a/menu/alias/templates/ptsadd b/menu/alias/templates/ptsadd new file mode 100644 index 00000000..73b603b0 --- /dev/null +++ b/menu/alias/templates/ptsadd @@ -0,0 +1,40 @@ +#!/bin/bash +##usercheck +if [ $(grep "1000" /etc/passwd | cut -d: -f1 | awk '{print $1}') ]; then + usermod -aG sudo $(grep "1000" /etc/passwd | cut -d: -f1 | awk '{print $1}') + sudo usermod -s /bin/bash $(grep "1000" /etc/passwd | cut -d: -f1 | awk '{print $1}') + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo " ✅ PASSED ! We found the user UID " $(grep "1000" /etc/passwd | cut -d: -f1 | awk '{print $1}') + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +else + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " ⌛ INFO ! " + echo " ⌛ INFO ! Only lowercase and dont empty parts" + echo " ⌛ INFO ! Enter a password (8+ chars)" + echo " ⌛ INFO ! " + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + read -p "Enter username : " username + read -s -p "Enter password : " password + echo "" + egrep "^$username" /etc/passwd >/dev/null + pass=$(perl -e 'print crypt($ARGV[0], "password")' $password) + useradd -m -p $pass $username + usermod -aG sudo $username + sudo usermod -s /bin/bash $username + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " ✅ PASSED ! User has been added to system!" + echo " ✅ PASSED ! Your Username : " $username + echo " ✅ PASSED ! Your Password : " $password + echo " ✅ PASSED ! " + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +fi +tee <<-EOF +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +😂 What a Lame name: $(grep "1000" /etc/passwd | cut -d: -f1 | awk '{print $1}') +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +exit 0 diff --git a/menu/alias/templates/ptsupdate b/menu/alias/templates/ptsupdate new file mode 100644 index 00000000..cfb4797a --- /dev/null +++ b/menu/alias/templates/ptsupdate @@ -0,0 +1,15 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/start.sh + +sudocheck +missingpull +owned +exitcheck + diff --git a/menu/alias/templates/reboot b/menu/alias/templates/reboot new file mode 100644 index 00000000..b78e4606 --- /dev/null +++ b/menu/alias/templates/reboot @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +sudo reboot diff --git a/menu/alias/templates/slist b/menu/alias/templates/slist new file mode 100644 index 00000000..2af3b3b9 --- /dev/null +++ b/menu/alias/templates/slist @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +sudo ls /etc/systemd/system diff --git a/menu/alias/templates/sst2 b/menu/alias/templates/sst2 new file mode 100644 index 00000000..954c8924 --- /dev/null +++ b/menu/alias/templates/sst2 @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +sudo systemctl status supertransfer2 diff --git a/menu/alias/templates/sufs b/menu/alias/templates/sufs new file mode 100644 index 00000000..48370360 --- /dev/null +++ b/menu/alias/templates/sufs @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +sudo systemctl status ufsmonitor diff --git a/menu/alias/templates/update b/menu/alias/templates/update new file mode 100644 index 00000000..fcf27cf1 --- /dev/null +++ b/menu/alias/templates/update @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +sudo apt update -y diff --git a/menu/alias/templates/upgrade b/menu/alias/templates/upgrade new file mode 100644 index 00000000..582c6740 --- /dev/null +++ b/menu/alias/templates/upgrade @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +sudo apt upgrade -y diff --git a/menu/amazonaws/amazonaws.sh b/menu/amazonaws/amazonaws.sh new file mode 100644 index 00000000..dbfc1b69 --- /dev/null +++ b/menu/amazonaws/amazonaws.sh @@ -0,0 +1,248 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +test=$(hcloud server list) +if [ "$test" == "" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - You Must Input an API from Hetzner First! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚡ Reference: http://hcloud.pgblitz.com + +* Activate a Hetzner Cloud Account and Create a Project +* Click Access (left hand side) and then click API Tokens +* Create a Token and Save It (and paste below here) +* Not Ready? Just Something & Press [ENTER] + +EOF + hcloud context create plexguide + + test=$(hcloud server list) + if [ "$test" == "" ]; then + hcloud context delete plexguide + exit + fi + +fi + +# Start Process +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 PG - Hetzner's Cloud Generator +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚡ Reference: http://hcloud.pgblitz.com + +[1] Deploy a New Server +[2] Destory a Server +A - List Server Info +B - Display Inital Server Passwords +Z - Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +read -p 'Type a Number | Press [ENTER]: ' typed /opt/appdata/plexguide/server.info + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ PG - New Server Information - [$name] +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + cat /opt/appdata/plexguide/server.info + + # Creates Log + touch /opt/appdata/plexguide/server.store + cat /opt/appdata/plexguide/server.info >>/opt/appdata/plexguide/server.store + echo "Server Name: $name" >>/opt/appdata/plexguide/server.store + echo "" >>/opt/appdata/plexguide/server.store + + # Variable Info + serverip=$(cat /opt/appdata/plexguide/server.info | tail -n +3 | head -n 1 | cut -d " " -f2-) + initialpw=$(cat /opt/appdata/plexguide/server.info | tail -n +4 | cut -d " " -f3-) + + tee <<-EOF + +⚠️ To Reach Your Server >>> EXIT PG >>> TYPE: pg-$name ⚠️ + +✅️ [IMPORTANT NOTE] + +Wait for one minute for the server to boot! Typing pg-$name will +display your initial password! Also can manually by typing: + +Command: ssh root@$serverip +FIRST TIME LOGIN - Initial Password: $initialpw + +EOF + read -p 'Press [ENTER] to Exit ' fill >/bin/pg-$name + echo "echo ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >/bin/pg-$name + echo "echo '↘️ Server - $name | Initial Password $initialpw'" >>/bin/pg-$name + echo "echo ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >>/bin/pg-$name + echo "echo '✅️ Donate @ donate.pgblitz.com - Helps Costs & Mrs. Admin - #1 Enemy!'" >>/bin/pg-$name + echo "echo ''" >>/bin/pg-$name + echo "ssh root@$serverip" >>/bin/pg-$name + chmod 777 /bin/pg-$name + chown 1000:1000 /bin/pg-$name + + bash /opt/plexguide/menu/hetzner/hetzner.sh + exit + +elif [ "$typed" == "A" ] || [ "$typed" == "a" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ PG - Hetzner Server Cloud List +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Server Name +━━━━━━━━━━━ +EOF + hcloud server list | tail -n +2 | cut -d " " -f2- | cut -d " " -f2- | cut -d " " -f2- + echo + read -p 'Press [ENTER] to Continue! ' typed ") + if [ "$next" == "0" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ PG - Server: $destroy - Does Not Exist! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -p 'Press [ENTER] to Continue! ' typed old) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + touch /opt/appdata/plexguide/server.store + tac -r /opt/appdata/plexguide/server.store + echo "" & + echo "" + read -p 'Press [ENTER] to Continue! ' corn " | cut -d " " -f2- | cut -d " " -f2- | cut -d " " -f2-) +#ipcheck=$(echo $check | awk '{ print $3 }') +#⛔️ WARNING! - Must Configure RClone First /w >>> gdrive +# read -n 1 -s -r -p "Press [ANY] Key to Continue " diff --git a/menu/appguard/appguard.sh b/menu/appguard/appguard.sh new file mode 100644 index 00000000..0c843498 --- /dev/null +++ b/menu/appguard/appguard.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# KEY VARIABLE RECALL & EXECUTION +program=$(cat /tmp/program_var) +mkdir -p /var/plexguide/cron/ +mkdir -p /opt/appdata/plexguide/cron +# FUNCTIONS START ############################################################## +source /opt/plexguide/menu/functions/functions.sh + +# FIRST QUESTION +question1() { + appguard=$(cat /var/plexguide/server.ht) + if [ "$appguard" == "" ]; then + guard="DISABLED" && opp="Enable" + else guard="ENABLED" && opp="Disable"; fi + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 Welcome to AppGuard! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚡ Reference: http://appguard.pgblitz.com + +Currently: [$guard] + +1. $opp AppGuard +Z. Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + read -p 'Type a Number | Press [ENTER]: ' typed /var/plexguide/server.ht; fi + bash /opt/plexguide/menu/appguard/rebuild.sh + elif [[ "$typed" == "z" || "$typed" == "Z" ]]; then + exit + else badinput1; fi +} + +# FUNCTIONS END ############################################################## + +break=off && while [ "$break" == "off" ]; do question1; done diff --git a/menu/appguard/rebuild.sh b/menu/appguard/rebuild.sh new file mode 100644 index 00000000..487b1b1d --- /dev/null +++ b/menu/appguard/rebuild.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +docker ps -a --format "{{.Names}}" >/var/plexguide/container.running + +sed -i -e "/traefik/d" /var/plexguide/container.running +sed -i -e "/watchtower/d" /var/plexguide/container.running +sed -i -e "/wp-*/d" /var/plexguide/container.running +sed -i -e "/plex/d" /var/plexguide/container.running +sed -i -e "/emby/d" /var/plexguide/container.running +sed -i -e "/jellyfin/d" /var/plexguide/container.running +sed -i -e "/ombi/d" /var/plexguide/container.running +sed -i -e "/oauth/d" /var/plexguide/container.running +sed -i -e "/portainer/d" /var/plexguide/container.running +sed -i -e "/dockergc/d" /var/plexguide/container.running + +count=$(wc -l /tmp/program_var + + if [ -e "/opt/coreapps/apps/$app.yml" ]; then ansible-playbook /opt/coreapps/apps/$app.yml; fi + if [ -e "/opt/communityapps/$app.yml" ]; then ansible-playbook /opt/communityapps/apps/$app.yml; fi +done + +echo "" +tee <<-EOF + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ✅️ AppGuard - Completed! All Containers were Rebuilt! + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF +read -p 'Continue? | Press [ENTER] ' name /var/plexguide/final.choice + +#### Note How to Make It Select a Type - echo "removal" > /var/plexguide/type.choice +program=$(cat /var/plexguide/type.choice) + +menu=$(echo "on") + +while [ "$menu" != "break" ]; do + menu=$(cat /var/plexguide/final.choice) + + ### Loads Key Variables + bash /opt/plexguide/menu/interface/$program/var.sh + ### Loads Key Execution + ansible-playbook /opt/plexguide/menu/core/selection.yml + ### Executes Actions + bash /opt/plexguide/menu/interface/$program/file.sh + + ### Calls Variable Again - Incase of Break + menu=$(cat /var/plexguide/final.choice) + + if [ "$menu" == "break" ]; then + echo "" + echo "---------------------------------------------------" + echo "SYSTEM MESSAGE: User Selected to Exit the Interface" + echo "---------------------------------------------------" + echo "" + sleep .5 + fi + +done diff --git a/menu/core/selection.yml b/menu/core/selection.yml new file mode 100644 index 00000000..5f0520a6 --- /dev/null +++ b/menu/core/selection.yml @@ -0,0 +1,182 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + + - name: Recall Menu Type + shell: "cat /var/plexguide/type.choice" + register: menutemp + + - name: "Set Facts for Menu" + set_fact: + menu: "{{menutemp.stdout}}" + +################ Key Variable to Set Menu Length + - name: "Default Blank Variables" + set_fact: + info1: "\n\n1. Exit Interface" + info2: "" + info3: "" + info4: "" + info5: "" + info6: "" + info7: "" + info8: "" + info9: "" + + - name: Launch Container Primary Information + include_tasks: "../interface/{{menu}}/choice.yml" + +################ Blank Out Variable Recall + - name: Recall Menu Type + shell: "cat /var/plexguide/menu.number" + register: tempnumber + + - name: "Blank Out Variable Recall" + set_fact: + numberselect: "{{tempnumber.stdout}}" + +################ Sets Choice Limitation + - name: "Choice Number {{numberselect}}" + set_fact: + choices: choice.user_input == "1" or + choice.user_input == "2" + when: numberselect == "2" + + - name: "Choice Number {{numberselect}}" + set_fact: + choices: choice.user_input == "1" or + choice.user_input == "2" or + choice.user_input == "3" + when: numberselect == "3" + + - name: "Choice Number {{numberselect}}" + set_fact: + choices: choice.user_input == "1" or + choice.user_input == "2" or + choice.user_input == "3" or + choice.user_input == "4" + when: numberselect == "4" + + - name: "Choice Number {{numberselect}}" + set_fact: + choices: choice.user_input == "1" or + choice.user_input == "2" or + choice.user_input == "3" or + choice.user_input == "4" or + choice.user_input == "5" + when: numberselect == "5" + + - name: "Choice Number {{numberselect}}" + set_fact: + choices: choice.user_input == "1" or + choice.user_input == "2" or + choice.user_input == "3" or + choice.user_input == "4" or + choice.user_input == "5" or + choice.user_input == "6" + when: numberselect == "6" + + - name: "Choice Number {{numberselect}}" + set_fact: + choices: choice.user_input == "1" or + choice.user_input == "2" or + choice.user_input == "3" or + choice.user_input == "4" or + choice.user_input == "5" or + choice.user_input == "6" or + choice.user_input == "7" + when: numberselect == "7" + + - name: "Choice Number {{numberselect}}" + set_fact: + choices: choice.user_input == "1" or + choice.user_input == "2" or + choice.user_input == "3" or + choice.user_input == "4" or + choice.user_input == "5" or + choice.user_input == "6" or + choice.user_input == "7" or + choice.user_input == "8" + when: numberselect == "8" + + - name: "Choice Number {{numberselect}}" + set_fact: + choices: choice.user_input == "1" or + choice.user_input == "2" or + choice.user_input == "3" or + choice.user_input == "4" or + choice.user_input == "5" or + choice.user_input == "6" or + choice.user_input == "7" or + choice.user_input == "8" or + choice.user_input == "9" + when: numberselect == "9" +################# Recalls Variables for Set Length + - name: PG Main Menu + pause: + prompt: "\n------------------------------------------------------------- + {{head1}} + {{head2}} + \n------------------------------------------------------------- + {{info1}} + {{info2}} + {{info3}} + {{info4}} + {{info5}} + {{info6}} + {{info7}} + {{info8}} + {{info9}} + \n\nType a [NUMBER] Choice & Press [ENTER]" + register: choice + until: "{{choices}}" + retries: 99 + delay: 1 + + - name: Set Choice + set_fact: + fchoice: "{{choice.user_input}}" + + - name: Exiting Interface + shell: "echo break > /var/plexguide/final.choice" + when: fchoice == "1" + + - name: Choice 2 Selected + shell: "echo 2 > /var/plexguide/final.choice" + when: fchoice == "2" + + - name: Choice 3 Selected + shell: "echo 3 > /var/plexguide/final.choice" + when: fchoice == "3" + + - name: Choice 4 Selected + shell: "echo 4 > /var/plexguide/final.choice" + when: fchoice == "4" + + - name: Choice 5 Selected + shell: "echo 5 > /var/plexguide/final.choice" + when: fchoice == "5" + + - name: Choice 6 Selected + shell: "echo 6 > /var/plexguide/final.choice" + when: fchoice == "6" + + - name: Choice 7 Selected + shell: "echo 7 > /var/plexguide/final.choice" + when: fchoice == "7" + + - name: Choice 8 Selected + shell: "echo 8 > /var/plexguide/final.choice" + when: fchoice == "8" + + - name: Choice 9 Selected + shell: "echo 9 > /var/plexguide/final.choice" + when: fchoice == "9" diff --git a/menu/cron/bcron.sh b/menu/cron/bcron.sh new file mode 100644 index 00000000..807597eb --- /dev/null +++ b/menu/cron/bcron.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +pgrole=$(cat /tmp/program_var) +path=$(cat /var/plexguide/server.hd.path) +tarlocation=$(cat /var/plexguide/data.location) +serverid=$(cat /var/plexguide/pg.serverid) + +doc=no +rolecheck=$(docker ps | grep -c "\<$pgrole\>") +if [ $rolecheck != 0 ]; then docker stop $pgrole && doc=yes; fi + +tar \ + --ignore-failed-read \ + --warning=no-file-changed \ + --warning=no-file-removed \ + -cvzf $tarlocation/$pgrole.tar /opt/appdata/$pgrole/ + +if [ $doc == yes ]; then docker restart $pgrole; fi + +chown -R 1000:1000 $tarlocation +rclone --config /opt/appdata/plexguide/rclone.conf copy $tarlocation/$pgrole.tar gdrive:/plexguide/backup/$serverid -v --checksum --drive-chunk-size=64M + +du -sh --apparent-size /opt/appdata/$pgrole | awk '{print $1}' +rm -rf '$tarlocation/$pgrole.tar' diff --git a/menu/cron/cron.sh b/menu/cron/cron.sh new file mode 100644 index 00000000..0bbb2d83 --- /dev/null +++ b/menu/cron/cron.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# KEY VARIABLE RECALL & EXECUTION +program=$(cat /tmp/program_var) +mkdir -p /var/plexguide/cron/ +mkdir -p /opt/appdata/plexguide/cron +# FUNCTIONS START ############################################################## +source /opt/plexguide/menu/functions/functions.sh + +# FIRST QUESTION +question1() { + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⌛ PG Cron - Schedule Cron Jobs (Backups) | $program? +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚡ Reference: http://cron.pgblitz.com + +[1] No +[2] Yes + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + read -p '↘️ Type Number | Press [ENTER]: ' typed /var/plexguide/cron/cron.day && break=1 + elif [ "$typed" == "8" ]; then + echo "*/1" >/var/plexguide/cron/$program.cron.day && break=1 + else badinput; fi +} + +# THIRD QUESTION +question3() { + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⌛ PG Cron - Hour of the Day? +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Type an HOUR from [0 to 23] + +0 = 00:00 | 12AM +12 = 12:00 | 12PM +18 = 18:00 | 6 PM + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + read -p '↘️ Type a Number | Press [ENTER]: ' typed /var/plexguide/cron/cron.hour && break=1 + else badinput; fi +} + +# FUNCTIONS END ############################################################## + +break=off && while [ "$break" == "off" ]; do question1; done +break=off && while [ "$break" == "off" ]; do question2; done +break=off && while [ "$break" == "off" ]; do question3; done + +echo $(($RANDOM % 59)) >/var/plexguide/cron/cron.minute +ansible-playbook /opt/plexguide/menu/cron/cron.yml diff --git a/menu/cron/cron.yml b/menu/cron/cron.yml new file mode 100644 index 00000000..a5407ff0 --- /dev/null +++ b/menu/cron/cron.yml @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + # KEY VARIABLES ################################################################ + - name: Set PGRole + shell: 'cat /tmp/program_var' + register: pgrole + + - name: Set PGRole + shell: 'cat /var/plexguide/cron/cron.hour' + register: cronhour + + - name: Set PGRole + shell: 'cat /var/plexguide/cron/cron.minute' + register: cronminute + + - name: Set PGRole + shell: 'cat /var/plexguide/cron/cron.day' + register: cronday + + # CRON START ################################################################### + # - name: Build Cron Job File + # shell: echo "ansible-playbook /opt/plexguide/menu/cron/bcron.yml --extra-vars 'program_var={{pgrole.stdout}}'" > /opt/appdata/plexguide/cron/{{pgrole.stdout}} + + - name: Build Cron Job Schedule + cron: + name: '{{pgrole.stdout}}' + weekday: '{{cronday.stdout}}' + minute: '{{cronminute.stdout}}' + hour: '{{cronhour.stdout}}' + user: root + job: 'echo {{pgrole.stdout}} > /tmp/program_var && bash /opt/pgvault/pgcron' + state: present + become_user: root diff --git a/menu/cron/mass.sh b/menu/cron/mass.sh new file mode 100644 index 00000000..c57ebd13 --- /dev/null +++ b/menu/cron/mass.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################# + +# KEY VARIABLE RECALL & EXECUTION +mkdir -p /var/plexguide/cron/ +mkdir -p /opt/appdata/plexguide/cron +# FUNCTIONS START ############################################################## +source /opt/plexguide/menu/functions/functions.sh + +weekrandom() { + while read p; do + echo "$p" >/tmp/program_var + echo $(($RANDOM % 23)) >/var/plexguide/cron/cron.hour + echo $(($RANDOM % 59)) >/var/plexguide/cron/cron.minute + echo $(($RANDOM % 6)) >/var/plexguide/cron/cron.day + ansible-playbook /opt/plexguide/menu/cron/cron.yml + done /tmp/program_var + echo $(($RANDOM % 23)) >/var/plexguide/cron/cron.hour + echo $(($RANDOM % 59)) >/var/plexguide/cron/cron.minute + echo "*/1" >/var/plexguide/cron/cron.day + ansible-playbook /opt/plexguide/menu/cron/cron.yml + done /tmp/program_var + bash /opt/plexguide/menu/cron/cron.sh + done $1; fi +} + +# For ZipLocations + +variable /var/plexguide/server.hd.path "/mnt" +pgpath=$(cat /var/plexguide/server.hd.path) + +used=$(df -h $pgpath | tail -n +2 | awk '{print $3}') +capacity=$(df -h $pgpath | tail -n +2 | awk '{print $2}') +percentage=$(df -h $pgpath | tail -n +2 | awk '{print $5}') +###################### FOR VARIABLS ROLE SO DOESNT CREATE RED - START + +# Menu Interface +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 PG Processing Disk Interface +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🌵 Processing Disk : $pgpath + Processing Space: $used of $capacity | $percentage Used Capacity + +☑️ PG does not format your second disk, nor mount it! We can +only assist by changing the location path! + +☑️ Enables PG System to process items on a SECONDARY Drive rather +than tax the PRIMARY DRIVE. Like Windows, you can have your items +process on a (D): Drive instead of on a (C): Drive. + +Do You Want To Change the Processing Disk? + +[1] No +[2] Yes + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +# Standby +read -p '↘️ Type a Number | Press [ENTER]: ' typed >> exit +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + # Standby + read -p '↘️ Type the NEW PATH (Follow Above Examples): ' typed /dev/null 2>&1 + + file="$typed/test" + if [ -e "$file" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ WOOT WOOT: Location Is Valid - $typed +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 2 + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⌛ STANDBY: Setting Up Your Permissions +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 2 + + chown 1000:1000 "$typed" + chmod 0775 "$typed" + rm -rf "$typed/test" + echo $typed >/var/plexguide/server.hd.path + break=off + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⌛ STANDBY: Making Folders & Rebuilding the Systems Docker Containers! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 2 + + ansible-playbook /opt/plexguide/menu/installer/main.yml + bash /opt/plexguide/menu/dlpath/rebuild.sh + + tee <<-EOF +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ WOOT WOOT: Process Complete! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + else + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - Mount Error! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +$typed does not exist! + +You may have forgotten to create it, but PG is unable to see it! +Try >>> cd $path and see what happens! + +Exiting! Nothing has changed! + +EOF + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + fi +else + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🍖 NOM NOM: Failed to Make a Valid Selection! Restarting the Process! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 3 + bash /opt/plexguide/menu/dlpath/dlpath.sh + exit +fi + +exit diff --git a/menu/dlpath/dlpathold.sh b/menu/dlpath/dlpathold.sh new file mode 100644 index 00000000..4a262f8b --- /dev/null +++ b/menu/dlpath/dlpathold.sh @@ -0,0 +1,235 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +pgpath=$(cat /var/plexguide/server.hd.path) + +break=no +while [ "$break" == "no" ]; do + + tee <<-EOF + +--------------------------------------------------------------------------- +PGBlitz Download/Processing Selection Interface +--------------------------------------------------------------------------- + +NOTE: PG does not format harddrives. User is responsible to format and mount +their secondary drives! You may change this later in the PG Settings! + +PURPOSE: Allow DOWNLOADS to process on a SECONDARY DRIVE (which is good for +small or slow primary drives). Like Windows, you can have your stuff download +and process on a (D): Drive instead of the (C): Drive. + +WARNING: Don't know what to do? Leave Select - NO - + +Default Path: /mnt +Current Path: $pgpath + +EOF + read -p "Change the Current Download/Processing Path? (y/n): " -n 1 -r + echo # move cursor to a new line + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "" + echo "---------------------------------------------------" + echo "SYSTEM MESSAGE: [Y] Key was NOT Selected - Exiting!" + echo "---------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit 1 + fi + + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: User Selected to Change the Path! +--------------------------------------------------------------------------- + +Current Path: $pgpath + +NOTE: When typing your path following the examples as shown below! We will +then attempt to check to see if your path exists! If not, we will let you know! + +Examples: +/mnt/mymedia +/secondhd/media +/myhd/storage/media + +[Z] Exit + +EOF + + read -p 'Type the NEW PATH (follow example above): ' typed + + storage=$(grep $typed /var/plexguide/ver.temp) + + if [[ "$typed" == "exit" || "$typed" == "Exit" || "$typed" == "EXIT" || "$typed" == "z" || "$typed" == "Z" ]]; then + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: Exiting the Downloading/Processing Selection Interface +--------------------------------------------------------------------------- + +EOF + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + else + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: Checking Path: $typed +--------------------------------------------------------------------------- +EOF + sleep 1.5 + + ##################################################### TYPED CHECKERS - START + typed2=$typed + bonehead=no + ##### If BONEHEAD forgot to add a / in the beginning, we fix for them + initial="$(echo $typed | head -c 1)" + if [ "$initial" != "/" ]; then + typed="/$typed" + bonehead=yes + fi + ##### If BONEHEAD added a / at the end, we fix for them + initial="${typed: -1}" + if [ "$initial" == "/" ]; then + typed=${typed::-1} + bonehead=yes + fi + + ##### Notify User is a Bonehead + if [ "$bonehead" == "yes" ]; then + tee <<-EOF + +--------------------------------------------------------------------------- +ALERT: We Fixed Your Typos (pay attention to the example next time) +--------------------------------------------------------------------------- + +You Typed : $typed2 +Changed To: $typed + +EOF + + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + else + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: Input is Valid +--------------------------------------------------------------------------- +EOF + fi + ##################################################### TYPED CHECKERS - START + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: Checking if the Location is Valid +--------------------------------------------------------------------------- +EOF + sleep 1.5 + + mkdir $typed/test 1>/dev/null 2>&1 + + file="$typed/test" + if [ -e "$file" ]; then + + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: The Path Exists! Review the Amount of Space You Have! +--------------------------------------------------------------------------- + +Your Current Space for $typed: + +EOF + df -h $typed + echo "" + echo "Pay Attention to How Much Space You Have!" + echo "" + + read -p "Continue to Set $typed? (y/n): " -n 1 -r + echo # move cursor to a new line + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "" + echo "---------------------------------------------------------------------------" + echo "SYSTEM MESSAGE: [Y] Key was NOT Selected - Restarting!" + echo "---------------------------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + bash /opt/plexguide/menu/interface/dlpath/main.sh + exit + fi + + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: CHMODing & CHOWNing: $typed +--------------------------------------------------------------------------- + +Note: Please Standby +EOF + sleep 2 + + chown 1000:1000 "$typed" + chmod 0775 "$typed" + rm -rf "$typed/test" + echo $typed >/var/plexguide/server.hd.path + break=off + + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: Rewriting Folders! STANDBY! +--------------------------------------------------------------------------- + +EOF + sleep 2 + ansible-playbook /opt/plexguide/menu/interface/folders/main.yml + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: Rebuilding Containers! STANDBY! +--------------------------------------------------------------------------- + +EOF + sleep 2 + + bash /opt/plexguide/menu/interface/dlpath/rebuild.sh + + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: Process Complete! +--------------------------------------------------------------------------- + +EOF + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + else + tee <<-EOF + +--------------------------------------------------------------------------- +SYSTEM MESSAGE: $typed DOES NOT Exist! +--------------------------------------------------------------------------- + +Note: Restarting the Process! Remember, you have to format your secondary +location (if another harddrive). You must ensure that linux is able to READ +your location. + +Advice: Exit PG and (Test) Type >>> mkdir $typed/testfolder + +EOF + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + fi + + ### Final FI + fi + +done diff --git a/menu/dlpath/rebuild.sh b/menu/dlpath/rebuild.sh new file mode 100644 index 00000000..567eea62 --- /dev/null +++ b/menu/dlpath/rebuild.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +docker ps -a --format "{{.Names}}" >/var/plexguide/container.running + +sed -i -e "/traefik/d" /var/plexguide/container.running +sed -i -e "/watchtower/d" /var/plexguide/container.running +sed -i -e "/wp-*/d" /var/plexguide/container.running +sed -i -e "/plex/d" /var/plexguide/container.running +sed -i -e "/emby/d" /var/plexguide/container.running +sed -i -e "/jellyfin/d" /var/plexguide/container.running +sed -i -e "/pgblitz/d" /var/plexguide/container.running +sed -i -e "/oauth/d" /var/plexguide/container.running +sed -i -e "/dockergc/d" /var/plexguide/container.running +sed -i -e "/pgui/d" /var/plexguide/container.running + +### Your Wondering Why No While Loop, using a While Loops Screws Up Ansible Prompts +### BackDoor WorkAround to Stop This Behavior +count=$(wc -l /tmp/program_var + if [ -e "/opt/coreapps/apps/$app.yml" ]; then ansible-playbook /opt/coreapps/apps/$app.yml; fi + if [ -e "/opt/communityapps/$app.yml" ]; then ansible-playbook /opt/communityapps/apps/$app.yml; fi +done + +echo "" +echo 'INFO - Rebuilding Complete!' >/var/plexguide/logs/pg.log && bash /opt/plexguide/menu/log/log.sh diff --git a/menu/functions/functions.sh b/menu/functions/functions.sh new file mode 100644 index 00000000..4f0c5578 --- /dev/null +++ b/menu/functions/functions.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# BAD INPUT +badinput() { + echo + read -p '⛔️ ERROR - Bad Input! | Press [ENTER] ' typed $1; fi +} + +readrcloneconfig() { + touch /opt/appdata/plexguide/rclone.conf + mkdir -p /var/plexguide/rclone/ + + gdcheck=$(cat /opt/appdata/plexguide/rclone.conf | grep gdrive) + if [ "$gdcheck" != "" ]; then + echo "good" >/var/plexguide/rclone/gdrive.status && gdstatus="good" + else echo "bad" >/var/plexguide/rclone/gdrive.status && gdstatus="bad"; fi + + gccheck=$(cat /opt/appdata/plexguide/rclone.conf | grep "remote = gdrive:/encrypt") + if [ "$gccheck" != "" ]; then + echo "good" >/var/plexguide/rclone/gcrypt.status && gcstatus="good" + else echo "bad" >/var/plexguide/rclone/gcrypt.status && gcstatus="bad"; fi + + tdcheck=$(cat /opt/appdata/plexguide/rclone.conf | grep tdrive) + if [ "$tdcheck" != "" ]; then + echo "good" >/var/plexguide/rclone/tdrive.status && tdstatus="good" + else echo "bad" >/var/plexguide/rclone/tdrive.status && tdstatus="bad"; fi + +} + +rcloneconfig() { + rclone config --config /opt/appdata/plexguide/rclone.conf +} + +keysprocessed() { + mkdir -p /opt/appdata/plexguide/keys/processed + ls -1 /opt/appdata/plexguide/keys/processed | wc -l >/var/plexguide/project.keycount +} diff --git a/menu/functions/install.sh b/menu/functions/install.sh new file mode 100644 index 00000000..6c0fc67a --- /dev/null +++ b/menu/functions/install.sh @@ -0,0 +1,553 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/plexguide/menu/functions/functions.sh + +updateprime() { + abc="/var/plexguide" + mkdir -p ${abc} + chmod 0775 ${abc} + chown 1000:1000 ${abc} + + mkdir -p /opt/appdata/plexguide + chmod 0775 /opt/appdata/plexguide + chown 1000:1000 /opt/appdata/plexguide + + variable /var/plexguide/pgfork.project "UPDATE ME" + variable /var/plexguide/pgfork.version "changeme" + variable /var/plexguide/tld.program "portainer" + variable /opt/appdata/plexguide/plextoken "" + variable /var/plexguide/server.ht "" + variable /var/plexguide/server.email "NOT-SET" + variable /var/plexguide/server.domain "NOT-SET" + variable /var/plexguide/pg.number "New-Install" + variable /var/plexguide/emergency.log "" + variable /var/plexguide/pgbox.running "" + pgnumber=$(cat /var/plexguide/pg.number) + + hostname -I | awk '{print $1}' >/var/plexguide/server.ip + file="${abc}/server.hd.path" + if [ ! -e "$file" ]; then echo "/mnt" >${abc}/server.hd.path; fi + + file="${abc}/new.install" + if [ ! -e "$file" ]; then newinstall; fi + + ospgversion=$(cat /etc/*-release | grep Debian | grep 9) + if [ "$ospgversion" != "" ]; then + echo "debian" >${abc}/os.version + else echo "ubuntu" >${abc}/os.version; fi + + echo "3" >${abc}/pg.mergerinstall + echo "52" >${abc}/pg.pythonstart + echo "12" >${abc}/pg.aptupdate + echo "150" >${abc}/pg.preinstall + echo "24" >${abc}/pg.folders + echo "16" >${abc}/pg.dockerinstall + echo "15" >${abc}/pg.server + echo "1" >${abc}/pg.serverid + echo "33" >${abc}/pg.dependency + echo "11" >${abc}/pg.docstart + echo "2" >${abc}/pg.motd + echo "115" >${abc}/pg.alias + echo "3" >${abc}/pg.dep + echo "3" >${abc}/pg.cleaner + echo "3" >${abc}/pg.gcloud + echo "12" >${abc}/pg.hetzner + echo "1" >${abc}/pg.amazonaws + echo "8.4" >${abc}/pg.verionid + echo "11" >${abc}/pg.watchtower + echo "1" >${abc}/pg.installer + echo "7" >${abc}/pg.prune + echo "21" >${abc}/pg.mountcheck + +} + +pginstall() { + updateprime + bash /opt/plexguide/menu/pggce/gcechecker.sh + core pythonstart + core aptupdate + core alias &>/dev/null & + core folders + core dependency + core mergerinstall + core dockerinstall + core docstart + + touch /var/plexguide/install.roles + rolenumber=3 + # Roles Ensure that PG Replicates and has once if missing; important for startup, cron and etc + if [[ $(cat /var/plexguide/install.roles) != "$rolenumber" ]]; then + rm -rf /opt/communityapps + rm -rf /opt/coreapps + rm -rf /opt/pgshield + + pgcore + pgcommunity + pgshield + echo "$rolenumber" >/var/plexguide/install.roles + fi + + portainer + pgui + core motd &>/dev/null & + core hetzner &>/dev/null & + core gcloud + core cleaner &>/dev/null & + core serverid + core watchtower + core prune + customcontainers &>/dev/null & + pgedition + core mountcheck + emergency + pgdeploy +} + +core() { + touch /var/plexguide/pg."${1}".stored + start=$(cat /var/plexguide/pg."${1}") + stored=$(cat /var/plexguide/pg."${1}".stored) + if [ "$start" != "$stored" ]; then + $1 + cat /var/plexguide/pg."${1}" >/var/plexguide/pg."${1}".stored + fi +} + +############################################################ INSTALLER FUNCTIONS +alias() { + ansible-playbook /opt/plexguide/menu/alias/alias.yml +} + +aptupdate() { + yes | apt-get update + yes | apt-get install software-properties-common + yes | apt-get install sysstat nmon + sed -i 's/false/true/g' /etc/default/sysstat +} + +customcontainers() { + mkdir -p /opt/mycontainers + touch /opt/appdata/plexguide/rclone.conf + mkdir -p /opt/communityapps/apps + rclone --config /opt/appdata/plexguide/rclone.conf copy /opt/mycontainers/ /opt/communityapps/apps +} + +cleaner() { + ansible-playbook /opt/plexguide/menu/pg.yml --tags autodelete &>/dev/null & + ansible-playbook /opt/plexguide/menu/pg.yml --tags clean &>/dev/null & + ansible-playbook /opt/plexguide/menu/pg.yml --tags clean-encrypt &>/dev/null & +} + +dependency() { + ospgversion=$(cat /var/plexguide/os.version) + if [ "$ospgversion" == "debian" ]; then + ansible-playbook /opt/plexguide/menu/dependency/dependencydeb.yml + else + ansible-playbook /opt/plexguide/menu/dependency/dependency.yml + fi +} + +docstart() { + ansible-playbook /opt/plexguide/menu/pg.yml --tags docstart +} + +emergency() { + variable /var/plexguide/emergency.display "On" + if [[ $(ls /opt/appdata/plexguide/emergency) != "" ]]; then + + # If not on, do not display emergency logs + if [[ $(cat /var/plexguide/emergency.display) == "On" ]]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ Emergency & Warning Log Generator | Visit - http://emlog.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +NOTE: This can be turned [On] or Off in Settings! + +EOF + + countmessage=0 + while read p; do + let countmessage++ + echo -n "${countmessage}. " && cat /opt/appdata/plexguide/emergency/$p + done <<<"$(ls /opt/appdata/plexguide/emergency)" + + echo + read -n 1 -s -r -p "Acknowledge Info | Press [ENTER]" + echo + else + touch /var/plexguide/emergency.log + fi + fi + +} + +folders() { + ansible-playbook /opt/plexguide/menu/installer/folders.yml +} + +prune() { + ansible-playbook /opt/plexguide/menu/prune/main.yml +} + +hetzner() { + if [ -e "$file" ]; then rm -rf /bin/hcloud; fi + version="v1.10.0" + wget -P /opt/appdata/plexguide "https://github.com/hetznercloud/cli/releases/download/$version/hcloud-linux-amd64-$version.tar.gz" + tar -xvf "/opt/appdata/plexguide/hcloud-linux-amd64-$version.tar.gz" -C /opt/appdata/plexguide + mv "/opt/appdata/plexguide/hcloud-linux-amd64-$version/bin/hcloud" /bin/ + rm -rf /opt/appdata/plexguide/hcloud-linux-amd64-$version.tar.gz + rm -rf /opt/appdata/plexguide/hcloud-linux-amd64-$version +} + +gcloud() { + export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" + echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list + curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add + sudo apt-get update && sudo apt-get install google-cloud-sdk -y +} + +mergerinstall() { + + ub16check=$(cat /etc/*-release | grep xenial) + ub18check=$(cat /etc/*-release | grep bionic) + deb9check=$(cat /etc/*-release | grep stretch) + activated=false + + apt --fix-broken install -y + apt-get remove mergerfs -y + mkdir -p /var/plexguide + + if [ "$ub16check" != "" ]; then + activated=true + echo "ub16" >/var/plexguide/mergerfs.version + wget "https://github.com/trapexit/mergerfs/releases/download/2.28.1/mergerfs_2.28.1.ubuntu-xenial_amd64.deb" + + elif [ "$ub18check" != "" ]; then + activated=true + echo "ub18" >/var/plexguide/mergerfs.version + wget "https://github.com/trapexit/mergerfs/releases/download/2.28.1/mergerfs_2.28.1.ubuntu-bionic_amd64.deb" + + elif [ "$deb9check" != "" ]; then + activated=true + echo "deb9" >/var/plexguide/mergerfs.version + wget "https://github.com/trapexit/mergerfs/releases/download/2.28.1/mergerfs_2.28.1.debian-stretch_amd64.deb" + + elif [ "$activated" != "true" ]; then + activated=true && echo "ub18 - but didn't detect correctly" >/var/plexguide/mergerfs.version + wget "https://github.com/trapexit/mergerfs/releases/download/2.28.1/mergerfs_2.28.1.ubuntu-bionic_amd64.deb" + else + apt-get install g++ pkg-config git git-buildpackage pandoc debhelper libfuse-dev libattr1-dev -y + git clone https://github.com/trapexit/mergerfs.git + cd mergerfs + make clean + make deb + cd .. + fi + + apt install -y ./mergerfs*_amd64.deb + rm mergerfs*_amd64.deb + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ MergerFS has been updated! Requires PG Clone redeployment. +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +INFORMATION: MergerFS was updated on your system and brings performance improvements! +Users have reported faster plex scanning and playback with the new mergerfs and pgclone configuration. + +ATTENTION: +You are required to re-deploy your mounts in the PG Clone menu (option 4, option A). +It is advised to check the VFS mount settings in the options menu (C,2), as options have been updated. + +WARNING: This is not optional, you must redeploy your mounts in the PG Clone menu. +Your mounts are currently down until you re-deploy pg clone as it requires configuration updates! +This is not done for you, you must go to the PG Clone Menu (option 4) and deploy (option A). + +We apologize for this one-time inconvenience. + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + read -p 'Acknowledge Info | Press [ENTER] ' typed /dev/null 2>&1 + file="${abc}/new.install" + if [ ! -e "$file" ]; then + touch ${abc}/pg.number && echo off >/tmp/program_source + bash /opt/plexguide/menu/version/file.sh + file="${abc}/new.install" + if [ ! -e "$file" ]; then exit; fi + fi +} + +pgdeploy() { + touch /var/plexguide/pg.edition + bash /opt/plexguide/menu/start/start.sh +} + +pgedition() { + file="${abc}/path.check" + if [ ! -e "$file" ]; then touch ${abc}/path.check && bash /opt/plexguide/menu/dlpath/dlpath.sh; fi + # FOR PG-BLITZ + file="${abc}/project.deployed" + if [ ! -e "$file" ]; then echo "no" >${abc}/project.deployed; fi + file="${abc}/project.keycount" + if [ ! -e "$file" ]; then echo "0" >${abc}/project.keycount; fi + file="${abc}/server.id" + if [ ! -e "$file" ]; then echo "[NOT-SET]" -rf >${abc}/rm; fi +} + +portainer() { + dstatus=$(docker ps --format '{{.Names}}' | grep "portainer") + if [ "$dstatus" != "portainer" ]; then + ansible-playbook /opt/coreapps/apps/portainer.yml &>/dev/null & + fi +} + +# Roles Ensure that PG Replicates and has once if missing; important for startup, cron and etc +pgcore() { if [ ! -e "/opt/coreapps/place.holder" ]; then ansible-playbook /opt/plexguide/menu/pgbox/pgboxcore.yml; fi; } +pgcommunity() { if [ ! -e "/opt/communityapps/place.holder" ]; then ansible-playbook /opt/plexguide/menu/pgbox/pgboxcommunity.yml; fi; } +pgshield() { if [ ! -e "/opt/pgshield/place.holder" ]; then + echo 'pgshield' >/var/plexguide/pgcloner.rolename + echo 'PGShield' >/var/plexguide/pgcloner.roleproper + echo 'PGShield' >/var/plexguide/pgcloner.projectname + echo 'v8.6' >/var/plexguide/pgcloner.projectversion + echo 'pgshield.sh' >/var/plexguide/pgcloner.startlink + ansible-playbook "/opt/plexguide/menu/pgcloner/corev2/primary.yml" +fi; } + +pgui() { + file="/var/plexguide/pgui.switch" + if [ ! -e "$file" ]; then echo "On" >/var/plexguide/pgui.switch; fi + + pguicheck=$(cat /var/plexguide/pgui.switch) + if [[ "$pguicheck" == "On" ]]; then + + dstatus=$(docker ps --format '{{.Names}}' | grep "pgui") + if [ "$dstatus" != "pgui" ]; then + bash /opt/plexguide/menu/pgcloner/solo/pgui.sh + ansible-playbook /opt/pgui/pgui.yml + fi + fi +} + +pythonstart() { + + ansible="2.8.2" + pip="19.1.1" + + apt-get install -y --reinstall \ + nano \ + git \ + build-essential \ + libssl-dev \ + libffi-dev \ + python3-dev \ + python3-pip \ + python-dev \ + python-pip + python3 -m pip install --disable-pip-version-check --upgrade --force-reinstall pip==${pip} + python3 -m pip install --disable-pip-version-check --upgrade --force-reinstall setuptools + python3 -m pip install --disable-pip-version-check --upgrade --force-reinstall \ + pyOpenSSL \ + requests \ + netaddr + python -m pip install --disable-pip-version-check --upgrade --force-reinstall pip==${pip} + python -m pip install --disable-pip-version-check --upgrade --force-reinstall setuptools + python -m pip install --disable-pip-version-check --upgrade --force-reinstall ansible==${1-$ansible} + + ## Copy pip to /usr/bin + cp /usr/local/bin/pip /usr/bin/pip + cp /usr/local/bin/pip3 /usr/bin/pip3 + + mkdir -p /etc/ansible/inventories/ 1>/dev/null 2>&1 + echo "[local]" >/etc/ansible/inventories/local + echo "127.0.0.1 ansible_connection=local" >>/etc/ansible/inventories/local + + ### Reference: https://docs.ansible.com/ansible/2.4/intro_configuration.html + echo "[defaults]" >/etc/ansible/ansible.cfg + echo "deprecation_warnings=False" >>/etc/ansible/ansible.cfg + echo "command_warnings = False" >>/etc/ansible/ansible.cfg + echo "callback_whitelist = profile_tasks" >>/etc/ansible/ansible.cfg + echo "inventory = /etc/ansible/inventories/local" >>/etc/ansible/ansible.cfg + + # Variables Need to Line Up with pg.sh (start) + touch /var/plexguide/background.1 +} + +dockerinstall() { + ospgversion=$(cat /var/plexguide/os.version) + if [ "$ospgversion" == "debian" ]; then + ansible-playbook /opt/plexguide/menu/pg.yml --tags dockerdeb + else + ansible-playbook /opt/plexguide/menu/pg.yml --tags docker + # If Docker FAILED, Emergency Install + file="/usr/bin/docker" + if [ ! -e "$file" ]; then + clear + echo "Installing Docker the Old School Way - (Please Be Patient)" + sleep 2 + clear + curl -fsSL get.docker.com -o get-docker.sh + sh get-docker.sh + echo "" + echo "Starting Docker (Please Be Patient)" + sleep 2 + systemctl start docker + sleep 2 + fi + + ##### Checking Again, if fails again; warns user + file="/usr/bin/docker" + if [ -e "$file" ]; then + sleep 5 + else + echo "INFO - FAILED: Docker Failed to Install! Exiting PGBlitz!" + exit + fi + fi +} + +serverid() { + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ Establishing Server ID 💬 Use One Word & Keep it Simple +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + read -p '🌏 TYPE Server ID | Press [ENTER]: ' typed /var/plexguide/server.id + sleep 1 + fi +} + +watchtower() { + + file="/var/plexguide/watchtower.wcheck" + if [ ! -e "$file" ]; then + echo "4" >/var/plexguide/watchtower.wcheck + fi + + wcheck=$(cat "/var/plexguide/watchtower.wcheck") + if [[ "$wcheck" -ge "1" && "$wcheck" -le "3" ]]; then + wexit="1" + else wexit=0; fi + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📂 PG WatchTower Edition 📓 Reference: watchtower.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +💬 WatchTower updates your containers soon as possible! + +[1] Containers: Auto-Update All +[2] Containers: Auto-Update All Except | Plex & Emby +[3] Containers: Never Update +Z - Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + # Standby + read -p 'Type a Number | Press [ENTER]: ' typed /var/plexguide/watchtower.wcheck + elif [ "$typed" == "2" ]; then + watchtowergen + sed -i -e "/plex/d" /tmp/watchtower.set 1>/dev/null 2>&1 + sed -i -e "/emby/d" /tmp/watchtower.set 1>/dev/null 2>&1 + sed -i -e "/jellyfin/d" /tmp/watchtower.set 1>/dev/null 2>&1 + ansible-playbook /opt/coreapps/apps/watchtower.yml + echo "2" >/var/plexguide/watchtower.wcheck + elif [ "$typed" == "3" ]; then + echo null >/tmp/watchtower.set + ansible-playbook /opt/coreapps/apps/watchtower.yml + echo "3" >/var/plexguide/watchtower.wcheck + elif [[ "$typed" == "Z" || "$typed" == "z" ]]; then + if [ "$wexit" == "0" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚠️ WatchTower Preference Must be Set Once! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 3 + watchtower + fi + exit + else + badinput + watchtower + fi +} + +watchtowergen() { + bash /opt/coreapps/apps/_appsgen.sh + bash /opt/communityapps/apps/_appsgen.sh + while read p; do + echo -n $p >>/tmp/watchtower.set + echo -n " " >>/tmp/watchtower.set + done /var/plexguide/project.account + + file="/var/plexguide/project.final" + if [ ! -e "$file" ]; then echo "[NOT SET]" >/var/plexguide/project.final; fi + + file="/var/plexguide/project.processor" + if [ ! -e "$file" ]; then echo "NOT-SET" >/var/plexguide/project.processor; fi + + file="/var/plexguide/project.location" + if [ ! -e "$file" ]; then echo "NOT-SET" >/var/plexguide/project.location; fi + + file="/var/plexguide/project.ipregion" + if [ ! -e "$file" ]; then echo "NOT-SET" >/var/plexguide/project.ipregion; fi + + file="/var/plexguide/project.ipaddress" + if [ ! -e "$file" ]; then echo "IP NOT-SET" >/var/plexguide/project.ipaddress; fi + + file="/var/plexguide/gce.deployed" + if [ -e "$file" ]; then + echo "Server Deployed" >/var/plexguide/gce.deployed.status + else echo "Not Deployed" >/var/plexguide/gce.deployed.status; fi + + project=$(cat /var/plexguide/project.final) + account=$(cat /var/plexguide/project.account) + processor=$(cat /var/plexguide/project.processor) + ipregion=$(cat /var/plexguide/project.ipregion) + ipaddress=$(cat /var/plexguide/project.ipaddress) + serverstatus=$(cat /var/plexguide/gce.deployed.status) +} diff --git a/menu/functions/pgvault.func b/menu/functions/pgvault.func new file mode 100644 index 00000000..ead32afa --- /dev/null +++ b/menu/functions/pgvault.func @@ -0,0 +1,559 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +runningcheck() { + initial2 + runcheck5=$(docker ps | grep ${program_var}) + if [ "$runcheck5" != "" ]; then running=1; else running=0; fi +} + +initial() { + rm -rf /var/plexguide/pgvault.output 1>/dev/null 2>&1 + rm -rf /var/plexguide/pgvault.buildup 1>/dev/null 2>&1 + rm -rf /var/plexguide/program.temp 1>/dev/null 2>&1 + rm -rf /var/plexguide/app.list 1>/dev/null 2>&1 + rm -rf /var/plexguide/pgvault.output 1>/dev/null 2>&1 + touch /var/plexguide/pgvault.output + touch /var/plexguide/program.temp + touch /var/plexguide/app.list + touch /var/plexguide/pgvault.buildup + touch /var/plexguide/pgvault.output + touch /var/plexguide/rclone.size + space=$(cat /var/plexguide/data.location) + # To Get Used Space + used=$(df -h /opt/appdata/plexguide | tail -n +2 | awk '{print $3}') + # To Get All Space + capacity=$(df -h /opt/appdata/plexguide | tail -n +2 | awk '{print $2}') + # Percentage + percentage=$(df -h /opt/appdata/plexguide | tail -n +2 | awk '{print $5}') +} + +initial2() { + path=$(cat /var/plexguide/server.hd.path) + tarlocation=$(cat /var/plexguide/data.location) + program_size=$(cat /var/plexguide/rclone.size) + program_var=$(cat /tmp/program_var) + server_id=$(cat /var/plexguide/server.id) +} + +final() { + echo + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + read -p '✅ Process Complete! | PRESS [ENTER] ' typed pgvault.serverlist +} + +pgboxrecall() { + ls -p /opt/coreapps/apps | grep -v / >/var/plexguide/pgvault.apprecall + while read p; do + sed -i "/^$p\b/Id" /var/plexguide/pgvault.apprecall + done >/var/plexguide/pgvault.apprecall + done /dev/null 2>&1 + while read p; do + echo -n $p >>/var/plexguide/program.temp + echo -n " " >>/var/plexguide/program.temp + num=$((num + 1)) + if [ "$num" == 7 ]; then + num=0 + echo " " >>/var/plexguide/program.temp + fi + done /var/plexguide/pgvault.apprecall + while read p; do + sed -i "/^$p\b/Id" /var/plexguide/pgvault.apprecall + done >/var/plexguide/pgvault.buildup + sed -i "/^$typed\b/Id" /var/plexguide/pgvault.apprecall + + num=0 + rm -rf /var/plexguide/pgvault.output 1>/dev/null 2>&1 + while read p; do + echo -n $p >>/var/plexguide/pgvault.output + echo -n " " >>/var/plexguide/pgvault.output + if [ "$num" == 7 ]; then + num=0 + echo " " >>/var/plexguide/pgvault.output + fi + done /tmp/server.list + + ### List Out Apps In Readable Order (One's Not Installed) + num=0 + rm -rf /var/plexguide/program.temp 1>/dev/null 2>&1 + while read p; do + echo -n $p >>/var/plexguide/program.temp + echo -n " " >>/var/plexguide/program.temp + num=$((num + 1)) + if [ "$num" == 7 ]; then + num=0 + echo " " >>/var/plexguide/program.temp + fi + done /tmp/server.select + + if [[ "$server" == "exit" || "$server" == "Exit" || "$server" == "EXIT" || "$server" == "z" || "$server" == "Z" ]]; then exit; fi + + current2=$(cat /tmp/server.list | grep "\<$server\>") + if [ "$current2" == "" ]; then + badserver + serverprime + fi + + tempserver=$server + ls -l /mnt/gdrive/plexguide/backup/$tempserver | awk '{print $9}' | tail -n +2 >/var/plexguide/pgvault.restoreapps + + ### Blank Out Temp List + rm -rf /var/plexguide/pgvault.apprecall 1>/dev/null 2>&1 + touch /var/plexguide/pgvault.apprecall + + while read p; do + basename "$p" .tar >>/var/plexguide/pgvault.apprecall + done /dev/null 2>&1 + touch /var/plexguide/program.temp + mathprime +} + +buildup2() { + echo "$typed" >>/var/plexguide/pgvault.buildup + sed -i "/^$typed\b/Id" /var/plexguide/pgvault.apprecall + + num=0 + rm -rf /var/plexguide/pgvault.output 1>/dev/null 2>&1 + while read p; do + echo -n $p >>/var/plexguide/pgvault.output + echo -n " " >>/var/plexguide/pgvault.output + if [ "$num" == 7 ]; then + num=0 + echo " " >>/var/plexguide/pgvault.output + fi + done /tmp/program_var + # Execute Main Program + backup_process + + sleep 2 + done /dev/null 2>&1 + fi + + ###### Start the Backup Process - Backup Locally First + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ Zipping Data Locally - $program_var +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + path=$(cat /var/plexguide/server.hd.path) + tarlocation=$(cat /var/plexguide/data.location) + server_id=$(cat /var/plexguide/server.id) + + tar \ + --warning=no-file-changed --ignore-failed-read --absolute-names --warning=no-file-removed \ + --exclude-from=/opt/pgvault/exclude.list \ + -C /opt/appdata/${program_var} -cvf /opt/appdata/plexguide/${program_var}.tar ./ + + #tar \ + #--warning=no-file-changed --ignore-failed-read --absolute-names --warning=no-file-removed \ + #--exclude-from=/opt/pgvault/exclude.list \ + #-cfv ${program_var}.tar /opt/appdata/${program_var} + + ##### Restart Docker Application if was Running Prior + if [ "$running" == "1" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ Restarting Docker Application - $program_var +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 2 + docker restart $program_var 1>/dev/null 2>&1 + fi + + ###### Backing Up Files to GDrive + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ Sending Zipped Data to Google Drive - $program_var +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + rclone --config /opt/appdata/plexguide/rclone.conf mkdir gdrive:/plexguide/backup/${server_id} 1>/dev/null 2>&1 + + rclone --config /opt/appdata/plexguide/rclone.conf \ + --stats-one-line --stats 1s --progress \ + moveto ${tarlocation}/${program_var}.tar \ + gdrive:/plexguide/backup/${server_id}/${program_var}.tar \ + -v --checksum --drive-chunk-size=64M --transfers=8 --user-agent="backup" + + ##### Remove File Incase + rm -rf ${tarlocation}/${program_var}.tar 1>/dev/null 2>&1 +} +######################################################## END - PG Vault Backup +# +##################################################### START - PG Vault Restore +restore_start() { + + while read p; do + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +PG Vault - Restoring: $p +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + sleep 2.5 + + # Store Used Program + echo $p >/tmp/program_var + # Execute Main Program + restore_process + + sleep 2 + done /dev/null 2>&1 + fi + + ###### Start the Backup Process - Backup Locally First + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ UnZipping & Restoring Data - $program_var +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + mkdir -p "/opt/appdata/${program_var}" + rm -rf "/opt/appdata/${program_var}/*" + chown -R 1000:1000 "/opt/appdata/${program_var}" + chmod -R 775 "/opt/appdata/${program_var}" + tar -C /opt/appdata/${program_var} -xvf ${tarlocation}/${program_var}.tar + chown -R 1000:1000 "/opt/appdata/${program_var}" + chmod -R 775 "/opt/appdata/${program_var}" + + ##### Restart Docker Application if was Running Prior + if [ "$running" == "1" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ Restarting Docker Application - $program_var +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 2 + docker restart $program_var 1>/dev/null 2>&1 + fi + + ##### Remove File Incase + rm -rf ${tarlocation}/${program_var}.tar 1>/dev/null 2>&1 +} +##################################################### END - PG Vault Restore +# +##################################################### START - Backup Interface +vaultbackup() { + ### List Out Apps In Readable Order (One's Not Installed) + notrun=$(cat /var/plexguide/program.temp) + buildup=$(cat /var/plexguide/pgvault.output) + + if [ "$buildup" == "" ]; then buildup="NONE"; fi + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 PG Vault ~ Data Storage 📓 Reference: pgvault.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📂 Potential Data to Backup + +$notrun + +💾 Apps Queued for Backup + +$buildup + +[A] Backup +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -p '🌍 Type APP for QUEUE | Press [ENTER]: ' typed ") + if [ "$current2" != "" ]; then + queued + vaultbackup + fi + + cat /var/plexguide/pgvault.buildup >/tmp/appcheck.5 + cat /var/plexguide/pgvault.apprecall >>/tmp/appcheck.5 + current1=$(cat /tmp/appcheck.5 | grep "\<$typed\>") + if [ "$current1" == "" ]; then badinput && vaultbackup; fi + + buildup +} +##################################################### END - Backup Interface +# +##################################################### START - Restore Interface +vaultrestore() { + notrun=$(cat /var/plexguide/program.temp) + buildup=$(cat /var/plexguide/pgvault.output) + + if [ "$buildup" == "" ]; then buildup="NONE"; fi + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 PG Vault ~ Data Recall 📓 Reference: pgvault.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +📂 Potential Data to Restore + +$notrun + +💾 Apps Queued for Restore + +$buildup + +[A] Restore +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -p '🌍 Type APP for QUEUE | Press [ENTER]: ' typed ") + if [ "$current2" != "" ]; then + queued + vaultrestore + fi + + cat /var/plexguide/pgvault.buildup >/tmp/appcheck.5 + cat /var/plexguide/pgvault.apprecall >>/tmp/appcheck.5 + current1=$(cat /tmp/appcheck.5 | grep "\<$typed\>") + if [ "$current1" == "" ]; then badinput && vaultrestore; fi + + buildup2 +} +##################################################### START Primary Interface +primaryinterface() { + initial2 + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📁 PG Vault - Main Interface 📓 Reference: pgvault.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🌵 PG Disk Used Space: $used of $capacity | $percentage Used Capacity + +[1] Data Backup +[2] Data Restore +[3] Current Server ID : $server_id +[4] Processing Location: $tarlocation + +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + read -p 'Type a Number | Press [ENTER]: ' typed /var/plexguide/server.id.stored + bash /opt/plexguide/menu/interface/serverid.sh + primaryinterface + elif [ "$typed" == "4" ]; then + bash /opt/plexguide/menu/data/location.sh + primaryinterface + elif [[ "$typed" == "Z" || "$typed" == "z" ]]; then + exit + else + badinput + primaryinterface + fi +} +##################################################### END Primary Interface + +restorecheck() { + if [ "$restoreid" == "[NOT-SET]" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - You Must Set Your Recovery ID First! Restarting Process! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -n 1 -s -r -p "Press [ANY] Key to Continue " + echo + primaryinterface + exit + fi +} diff --git a/menu/functions/start.sh b/menu/functions/start.sh new file mode 100644 index 00000000..f1090ca8 --- /dev/null +++ b/menu/functions/start.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/install.sh + +sudocheck() { + if [[ $EUID -ne 0 ]]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ You Must Execute as a SUDO USER (with sudo) or as ROOT! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + exit 1 + fi +} + +downloadpg() { + rm -rf /opt/plexguide + git clone https://github.com/MrDoobPG/PGBlitz.com.git /opt/plexguide && cp /opt/plexguide/menu/interface/alias/templates/plexguide /bin/ + cp /opt/plexguide/menu/interface/alias/templates/plexguide /bin/plexguide +} + +missingpull() { + file="/opt/plexguide/menu/functions/install.sh" + if [ ! -e "$file" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ /opt/plexguide went missing! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + sleep 2 + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 🍖 NOM NOM - Re-Downloading PGBlitz for BoneHead User! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 2 + downloadpg + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ Repair Complete! Standby! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + sleep 2 + fi +} + +exitcheck() { + bash /opt/plexguide/menu/version/file.sh + file="/var/plexguide/exited.upgrade" + if [ ! -e "$file" ]; then + bash /opt/plexguide/menu/interface/ending.sh + else + rm -rf /var/plexguide/exited.upgrade 1>/dev/null 2>&1 + echo "" + bash /opt/plexguide/menu/interface/ending.sh + fi +} diff --git a/menu/hetzner/pghetznerigpu.sh b/menu/hetzner/pghetznerigpu.sh new file mode 100644 index 00000000..caa03a7b --- /dev/null +++ b/menu/hetzner/pghetznerigpu.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# +# Title: PGBlitz (Hetzner iGPU / GPU) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# Coder : MrDoob | Freelaancer Coder TechLead +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +tee <<-EOF + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ⌛ Verifiying PG Hetzner iGPU / GPU HW-Transcode ! + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +echo "Updating packages" +apt-get update -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "Upgrading packages" +apt-get upgrade -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "Dist-Upgrading packages" +apt-get dist-upgrade -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "Autoremove old Updates" +apt-get autoremove -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "install vainfo" +sudo apt-get install vainfo -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +apt-get install lsb-release -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "install complete" + +tee <<-EOF + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 🚀 PG Hetzner iGPU / GPU HW-Transcode + + NOTE : You MUST have Plex Pass to enable hardware transcoding in the Plex server + + Your Operations System : $(lsb_release -sd) + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + [1] Ubuntu 16.04 LTS + [2] Ubuntu 18.04 LTS + [3] Debian 9.6 + + [4] iGPU / GPU TEST + + [Z] Exit + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + +# Standby +read -p 'Type a Number | Press [ENTER]: ' typed &1 >>/dev/null + export DEBIAN_FRONTEND=noninteractive + sleep 10 +elif [ "$typed" == "Z" ] || [ "$typed" == "z" ]; then + exit +else + bash /opt/plexguide/menu/tools/tools.sh + exit +fi + +bash /opt/plexguide/menu/tools/tools.sh +exit diff --git a/menu/installer/alias.yml b/menu/installer/alias.yml new file mode 100644 index 00000000..a7f3d69a --- /dev/null +++ b/menu/installer/alias.yml @@ -0,0 +1,134 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Install Status UFSMonitor + template: + src: sufs + dest: /bin/sufs + force: yes + mode: 0775 + + - name: Nano UFSMonitor + template: + src: nufs + dest: /bin/nufs + force: yes + mode: 0775 + + ###### Install PGLog + + - name: Nano PGLog + template: + src: pglog + dest: /bin/pglog + force: yes + mode: 0775 + + ###### Install PGUpdate + + - name: PGUpdate + template: + src: pgupdate + dest: /bin/pgupdate + force: yes + mode: 0775 + + ###### Install PGBlitz + + - name: PGBlitz + template: + src: plexguide + dest: /bin/plexguide + force: yes + mode: 0775 + owner: root + + ###### Server reboot + + - name: server reboot + template: + src: reboot + dest: /bin/reboot + force: yes + mode: 0775 + + ###### Check list of services + + - name: list systemd services + template: + src: slist + dest: /bin/slist + force: yes + mode: 0775 + owner: root + + ###### Server update + + - name: update server + template: + src: update + dest: /bin/update + force: yes + mode: 0775 + + ###### Server upgrade + + - name: upgrade server + template: + src: upgrade + dest: /bin/upgrade + force: yes + mode: 0775 + owner: root + + ###### Install app + + - name: install appname + template: + src: install + dest: /bin/install + force: yes + mode: 0775 + owner: root + + ###### Autoremove installed app packages + + - name: autoremove unused packages after app install + template: + src: autoremove + dest: /bin/autoremove + force: yes + mode: 0775 + owner: root + + ###### Remove old docker containers (Tip from @barryclamsworth) + + - name: Prune docker containers appname + template: + src: prune + dest: /bin/prune + force: yes + mode: 0775 + owner: root + + - name: Install PGFork + template: + src: pgfork + dest: /bin/pgfork + force: yes + mode: 0775 + + - name: Install Backup + template: + src: backup + dest: /bin/backup + force: yes + mode: 0775 diff --git a/menu/installer/folders.yml b/menu/installer/folders.yml new file mode 100644 index 00000000..2f809fdd --- /dev/null +++ b/menu/installer/folders.yml @@ -0,0 +1,200 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Register Domain + shell: 'cat /var/plexguide/server.domain' + register: domain + ignore_errors: True + + - name: Register IP + shell: 'cat /var/plexguide/server.ip' + register: ipaddress + ignore_errors: True + + - name: Register Ports + shell: 'cat /var/plexguide/server.ports' + register: ports + ignore_errors: True + + - name: Register HD Path + shell: 'cat /var/plexguide/server.hd.path' + register: path + ignore_errors: True + + - name: Register HD Path + shell: 'cat /var/plexguide/server.hd.path' + register: path + ignore_errors: True + + - name: Register Auth Path + shell: 'cat /var/plexguide/server.ht' + register: auth + ignore_errors: True + + - name: Logging Ansible Role Information + shell: "echo 'INFO - Ansible Role Folders Started' > /var/plexguide/logs/pg.log && bash /opt/plexguide/menu/log/log.sh" + + ############ GCrypt + - name: GCrypt Check + stat: + path: /mnt/gcrypt + register: gcrypt + + - name: Create GCrypt Folders + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - /mnt/gcrypt/tv + - /mnt/gcrypt/movies + - /mnt/gcrypt/music + - /mnt/gcrypt/ebooks + - /mnt/gcrypt/abooks + when: gcrypt.stat.exists == False + ignore_errors: yes + ############ TCrypt + - name: TCrypt Check + stat: + path: /mnt/tcrypt + register: tcrypt + + - name: Create TCrypt Folders + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - /mnt/tcrypt/tv + - /mnt/tcrypt/movies + - /mnt/tcrypt/music + - /mnt/tcrypt/ebooks + - /mnt/tcrypt/abooks + when: tcrypt.stat.exists == False + ignore_errors: yes + + ############ Personal Containers Folder + - name: MyContainers Check + stat: + path: /opt/mycontainers + register: mycontainers + + - name: Create MyContainers Folders + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - /opt/mycontainers + when: mycontainers.stat.exists == False + ignore_errors: yes + + ############ PGUnion + - name: PGUnion Check + stat: + path: /mnt/unionfs + register: pgunion + + - name: Create PGUnion Folders + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - /mnt/unionfs + when: pgunion.stat.exists == False + ignore_errors: yes + + ############ TMP + - name: TMP Check + stat: + path: /mnt/tmp + register: tmp + + - name: Create GDrive Folders + file: 'path={{item}} state=directory' + with_items: + - /mnt/tmp + when: tmp.stat.exists == False + ignore_errors: yes + + ############ GDrive + - name: GDrive Check + stat: + path: /mnt/gdrive + register: gdrive + + - name: Create GDrive Folders + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - /mnt/gdrive + - /mnt/gdrive/tv + - /mnt/gdrive/movies + - /mnt/gdrive/music + - /mnt/gdrive/ebooks + - /mnt/gdrive/abooks + when: gdrive.stat.exists == False + ignore_errors: yes + ############ TCrypt + - name: Create GDrive Folders + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - /mnt/tcrypt + - /mnt/tcrypt/tv + - /mnt/tcrypt/movies + - /mnt/tcrypt/music + - /mnt/tcrypt/ebooks + - /mnt/tcrypt/abooks + when: gdrive.stat.exists == False + ignore_errors: yes + + ############ TDrive + - name: TDrive Check + stat: + path: /mnt/tdrive + register: tdrive + + - name: Create TDrive Folders + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - /mnt/tdrive + - /mnt/tdrive/tv + - /mnt/tdrive/movies + - /mnt/tdrive/music + - /mnt/tdrive/ebooks + - /mnt/tdrive/abooks + when: tdrive.stat.exists == False + ignore_errors: yes + + ############ Encrypt + - name: Encrypt Check + stat: + path: /mnt/encrypt + register: encrypt + + - name: Create Encrypt Folders + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - /mnt/encrypt/tv + - /mnt/encrypt/movies + - /mnt/encrypt/music + - /mnt/encrypt/ebooks + - /mnt/encrypt/abooks + when: encrypt.stat.exists == False + ignore_errors: yes + + - name: Create Basic Directories + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000 recurse=true' + with_items: + - '/opt/appdata/plexguide' + - '/opt/communityapps' + - '/opt/coreapps' + - '/var/plexguide' + - '/mnt/move' + - '/var/plexguide/logs' + - '/opt/appdata/plexguide/keys/unprocessed' + - '/opt/appdata/plexguide/keys/processed' + - '/opt/appdata/plexguide/keys/badjson' + - '{{path.stdout}}/pgops' + - '{{path.stdout}}/downloads' + - '{{path.stdout}}/nzb' + - '{{path.stdout}}/torrent' + - '{{path.stdout}}/move' + - '/opt/var' + ignore_errors: yes diff --git a/menu/interface/cloudselect.sh b/menu/interface/cloudselect.sh new file mode 100644 index 00000000..6eaafbfa --- /dev/null +++ b/menu/interface/cloudselect.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +# Menu Interface +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📂 PG Cloud Service Installer | http://cloud.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +[1] Cloud Instance: Google (Blitz ~ GCE Edition) +[2] Cloud Instance: Hetzner +Z - Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + +# Standby +read -p 'Type a Number | Press [ENTER]: ' typed /var/plexguide/type.choice + bash /opt/plexguide/menu/pgcloner/blitzgce.sh +elif [ "$typed" == "2" ]; then + bash /opt/plexguide/menu/pgcloner/hetzner.sh +elif [ "$typed" == "Z" ] || [ "$typed" == "z" ]; then + exit +else + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - Please Make a Valid Selection! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + + bash /opt/plexguide/menu/cloudselect/cloudselect.sh + exit +fi diff --git a/menu/interface/ending.sh b/menu/interface/ending.sh new file mode 100644 index 00000000..47259c12 --- /dev/null +++ b/menu/interface/ending.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - FlickerRate +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/plexguide/menu/functions/install.sh +emergency + +# PG ascii art with color +echo "" +cat <<"EOF" +┌─────────────────────────────────────┐ +│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ +│░█▀█░█░░░█▀▀░█░█░█▀▀░█░█░▀█▀░█▀▄░█▀▀░│ +│░█▀▀░█░░░█▀▀░▄▀▄░█░█░█░█░░█░░█░█░█▀▀░│ +│░▀░░░▀▀▀░▀▀▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀░░▀▀▀░│ +│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ +└─────────────────────────────────────┘ +EOF + +cat <<"EOF" +┌─────────────────────────────────────┐ +│ -== Team PGBLitz ==- │ +│ ————————————————————————————————————│ +│ Star PG: github.pgblitz.com │ +│ Donate: donate.pgblitz.com │ +│ ————————————————————————————————————│ +│ Restart PGBlitz: pgblitz │ +│ Update PGBlitz: pgupdate │ +│ View the PG Blitz Logs: blitz │ +│ Download Your PG Fork: pgfork │ +│ ————————————————————————————————————│ +│ Thanks For Being Part of the Team │ +└─────────────────────────────────────┘ + +EOF + +if [[ ! -e "/bin/pgblitz" ]]; then + cp /opt/plexguide/menu/alias/templates/pgblitz /bin +fi + +chown 1000:1000 /bin/pgblitz &>/dev/null & +chmod 0755 /bin/pgblitz &>/dev/null & diff --git a/menu/interface/gce/choice.yml b/menu/interface/gce/choice.yml new file mode 100644 index 00000000..3f3270d7 --- /dev/null +++ b/menu/interface/gce/choice.yml @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- name: Register Project + shell: 'cat /var/plexguide/project.final' + register: project + +- name: Register Account + shell: 'cat /var/plexguide/project.account' + register: account + +- name: Register Processor Count + shell: 'cat /var/plexguide/project.processor' + register: processor + +- name: Register IP region + shell: 'cat /var/plexguide/project.ipregion' + register: ipregion + +- name: Register IP region + shell: 'cat /var/plexguide/project.ipaddress' + register: ipaddress + +- name: Register Deployment Status + shell: 'cat /var/plexguide/gce.deployed.status' + register: deployment + +- name: 'Key Menu Facts' + set_fact: + head1: "\nPGBlitz GCE Deployment Interface - Make a Selection" + head2: '' + info2: "\n2. Log-In to Your Account: {{account.stdout}}" + info3: "\n3. Build a New Project" + info4: "\n4. Establish Project ID : [{{project.stdout}}]" + info5: "\n5. Set Processor Count : [{{processor.stdout}}]" + info6: "\n6. Set IP Region / Server: [{{ipaddress.stdout}}] - [{{ipregion.stdout}}]" + info7: "\n7. Deploy PG GCE Server : [{{deployment.stdout}}]" + info8: "\n8. SSH Securely into your GCE Feeder Box" + info9: "\n9. Destroy Server" diff --git a/menu/interface/gce/file.sh b/menu/interface/gce/file.sh new file mode 100644 index 00000000..29f67e1d --- /dev/null +++ b/menu/interface/gce/file.sh @@ -0,0 +1,826 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +menu=$(cat /var/plexguide/final.choice) + +if [ "$menu" == "2" ]; then + ########## Server Must Not Be Deployed - START + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Checking Existing Deployment" + echo "--------------------------------------------------------" + echo "" + inslist=$(gcloud compute instances list | grep pg-gce) + if [ "$inslist" != "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Must Delete Current Server!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Prevents Conflicts with Changes!" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + exit + fi + ########## Server Must Not Be Deployed - END + + gcloud auth login + echo "[NOT SET]" >/var/plexguide/project.final +fi + +if [ "$menu" == "3" ]; then + ############################## BILLING CHECKS - START + billing=$(gcloud beta billing accounts list | grep "\") + if [ "$billing" == "" ]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Google Cloud Billing is Not Turned On!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: You Must Turn On Your Billing! PG is checking for the word >>> True" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## BILLING CHECKS - END + + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Creating Project ID" + echo "--------------------------------------------------------" + echo "" + date=$(date +%m%d) + rand=$(echo $((1 + RANDOM + RANDOM + RANDOM + RANDOM + RANDOM + RANDOM + RANDOM + RANDOM + RANDOM + RANDOM))) + projectid="pg-$date-$rand" + gcloud projects create $projectid + sleep 1 + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Linking Project to the Billing Account" + echo "--------------------------------------------------------" + echo "" + + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Created - Project $projectid" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: If using this project, ENSURE to SET this project!" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " +fi + +if [ "$menu" == "4" ]; then + ############################## BILLING CHECKS - START + billing=$(gcloud beta billing accounts list | grep "\") + if [ "$billing" == "" ]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Google Cloud Billing is Not Turned On!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: You Must Turn On Your Billing! PG is checking for the word >>> True" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## BILLING CHECKS - END + ########## Server Must Not Be Deployed - START + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Checking Existing Deployment" + echo "--------------------------------------------------------" + echo "" + + inslist=$(gcloud compute instances list | grep pg-gce) + if [ "$inslist" != "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Must Delete Current Server!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Prevents Conflicts with Changes!" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + exit + fi + ########## Server Must Not Be Deployed - END + + gcloud projects list && gcloud projects list >/var/plexguide/projects.list + echo "" + echo "------------------------------------------------------------------------------" + echo "SYSTEM MESSAGE: GCloud Project Interface" + echo "------------------------------------------------------------------------------" + echo "" + echo "NOTE: If no project is listed, please visit https://project.pgblitz.com and" + echo " review the wiki on how to build a project! Without one, this will fail!" + echo "" + read -p "Set or Change the Project ID (y/n)? " -n 1 -r + echo # move cursor to a new line + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: [Y] Key was NOT Selected - Exiting!" + echo "--------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit 1 + else + echo "" # leave if statement and continue. + fi + + typed=nullstart + while [ "$typed" != "$list" ]; do + echo "------------------------------------------------------------------------------" + echo "SYSTEM MESSAGE: Project Selection Interface" + echo "------------------------------------------------------------------------------" + echo "" + cat /var/plexguide/projects.list | cut -d' ' -f1 | tail -n +2 + cat /var/plexguide/projects.list | cut -d' ' -f1 | tail -n +2 >/var/plexguide/project.cut + echo "" + echo "NOTE: Type the Name of the Project you want to utilize!" + read -p 'Type the Name of the Project to Utlize & Press [ENTER]: ' typed + list=$(cat /var/plexguide/project.cut | grep $typed) + echo "" + + if [ "$typed" != "$list" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Please type the exact name!" + echo "--------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + else + echo "----------------------------------------------" + echo "SYSTEM MESSAGE: Passed the Validation Checks!" + echo "----------------------------------------------" + echo "" + echo "Set Project is: $list" + gcloud config set project $typed + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + echo "" + echo "----------------------------------------------" + echo "SYSTEM MESSAGE: Enabling Your API!" + echo "----------------------------------------------" + echo "" + echo "NOTE: Enabling Compute API - Please Standby!" + gcloud services enable compute.googleapis.com + echo "" + echo "NOTE: Enabling GDrive API for Project - $typed" + gcloud services enable drive.googleapis.com --project $typed + echo "" + sleep 1 + echo "----------------------------------------------" + echo "SYSTEM MESSAGE: Finished!" + echo "----------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + fi + done + + echo $typed >/var/plexguide/project.final + echo 'INFO - Selected: Exiting Application Suite Interface' >/var/plexguide/logs/pg.log && bash /opt/plexguide/menu/log/log.sh + exit +fi + +if [ "$menu" == "5" ]; then + ############################## BILLING CHECKS - START + billing=$(gcloud beta billing accounts list | grep "\") + if [ "$billing" == "" ]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Google Cloud Billing is Not Turned On!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: You Must Turn On Your Billing! PG is checking for the word >>> True" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## BILLING CHECKS - END + ############################## PROJECT BILLING CHECKS - START + project=$(cat /var/plexguide/project.final) + projectlink=$(gcloud beta billing accounts list | grep "\" | awk '{ print $1 }') + billingcheck=$(gcloud beta billing projects link $project --billing-account $projectlink | grep "billingEnabled: true") + if [ "$billingcheck" == "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Billing Failed - Turn It On Or Check" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Common Billing Issue for GCE Credits" + echo "NOTE: Cannot Continue with GCE" + echo "" + echo "1. Too Many Projects - Delete Unused Ones!" + echo "2. Ran Out of Credits & Must Turn On (Warning - Expensive)" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## PROJECT BILLING CHECKS - END + + ########## Server Must Not Be Deployed - START + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Checking Existing Deployment" + echo "--------------------------------------------------------" + echo "" + + inslist=$(gcloud compute instances list | grep pg-gce) + if [ "$inslist" != "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Must Delete Current Server!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Prevents Conflicts with Changes!" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + exit + fi + ########## Server Must Not Be Deployed - END + + ### Part 1 + pcount=$(cat /var/plexguide/project.processor) + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Current Processor Count Interface" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Processor Count: [$pcount]" + echo "" + read -p "Set or Change the Processor Count (y/n)? " -n 1 -r + echo # move cursor to a new line + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: [Y] Key was NOT Selected - Exiting!" + echo "--------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit 1 + else + echo "" + fi + + ### part 2 + typed=nullstart + prange="2 4 6" + tcheck="" + break=off + while [ "$break" == "off" ]; do + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Processor Count Interface" + echo "--------------------------------------------------------" + echo "" + echo "Ideal Processor Usage = 4" + echo "Set Your Processor Count | Range 2, 4 or 6" + echo "" + echo "NOTE: More Processors = Faster Credit Drain" + echo "" + read -p 'Type a Number 2, 4 or 6 | PRESS [ENTER]: ' typed + tcheck=$(echo $prange | grep $typed) + echo "" + + if [ "$tcheck" == "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Type a Number from 2, 4, or 6" + echo "--------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + echo "" + else + echo "----------------------------------------------" + echo "SYSTEM MESSAGE: Passed! Process Count $typed Set" + echo "----------------------------------------------" + echo "" + echo $typed >/var/plexguide/project.processor + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + break=on + fi + done + +fi + +if [ "$menu" == "6" ]; then + ############################## BILLING CHECKS - START + billing=$(gcloud beta billing accounts list | grep "\") + if [ "$billing" == "" ]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Google Cloud Billing is Not Turned On!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: You Must Turn On Your Billing! PG is checking for the word >>> True" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## BILLING CHECKS - END + ############################## PROJECT BILLING CHECKS - START + project=$(cat /var/plexguide/project.final) + projectlink=$(gcloud beta billing accounts list | grep "\" | awk '{ print $1 }') + billingcheck=$(gcloud beta billing projects link $project --billing-account $projectlink | grep "billingEnabled: true") + if [ "$billingcheck" == "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Billing Failed - Turn It On Or Check" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Common Billing Issue for GCE Credits" + echo "NOTE: Cannot Continue with GCE" + echo "" + echo "1. Too Many Projects - Delete Unused Ones!" + echo "2. Ran Out of Credits & Must Turn On (Warning - Expensive)" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## PROJECT BILLING CHECKS - END + + ########## Server Must Not Be Deployed - START + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Checking Existing Deployment" + echo "--------------------------------------------------------" + echo "" + + inslist=$(gcloud compute instances list | grep pg-gce) + if [ "$inslist" != "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Must Delete Current Server!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Prevents Conflicts with Changes!" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + exit + fi + ########## Server Must Not Be Deployed - END + + gcloud compute regions list | awk '{print $1}' | tail -n +2 >/tmp/regions.list + num=0 + echo " " >/tmp/regions.print + + while read p; do + echo -n $p >>/tmp/regions.print + echo -n " " >>/tmp/regions.print + + num=$((num + 1)) + if [ $num == 5 ]; then + num=0 + echo " " >>/tmp/regions.print + fi + done /var/plexguide/project.region + + typed=nullstart + prange=$(cat /tmp/regions.print) + tcheck="" + break=off + while [ "$break" == "off" ]; do + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Google Cloud IP Regions List" + echo "--------------------------------------------------------" + cat /tmp/regions.print + echo "" && echo "" + read -p 'Type the Name of an IP Region | PRESS [ENTER]: ' typed + echo "" + tcheck=$(echo $prange | grep $typed) + + if [ "$tcheck" == "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Type an IP Region Name" + echo "--------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + echo "" + else + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Passed! IP Region $typed Set" + echo "--------------------------------------------------------" + echo "" + echo $typed >/var/plexguide/project.ipregion + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + echo "" + break=on + fi + done + + ############## IP Address - Part 2 + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Deleting Any Prior GCE IP Addresses" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Please Standby" + + break=off + while [ "$break" == off ]; do + + gcloud compute addresses list | grep pg-gce | tail -n +1 >/tmp/ip.delete + ipdelete=$(cat /tmp/ip.delete) + if [ "$ipdelete" != "" ]; then + regdelete=$(gcloud compute addresses list | grep pg-gce | head -n +1 | awk '{print $2}') + addprint=$(gcloud compute addresses list | grep pg-gce | head -n +1 | awk '{print $3}') + gcloud compute addresses delete pg-gce --region=$regdelete --quiet + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Deleted $regdelete - $addprint" + echo "--------------------------------------------------------" + else + break=on + fi + done + + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Creating New IP Address" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Please Standby" + echo "" + projectname=$(cat /var/plexguide/project.final) + region=$(cat /var/plexguide/project.ipregion) + gcloud compute addresses create pg-gce --region $region --project $projectname + gcloud compute addresses list | grep pg-gce | awk '{print $3}' >/var/plexguide/project.ipaddress + ipaddress=$(cat /var/plexguide/project.ipaddress) + sleep 1.5 + echo "" & + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Passed! GCE IP: $ipaddress" + echo "--------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + + ########## Server Must Not Be Deployed - START + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Checking Existing Deployment" + echo "--------------------------------------------------------" + echo "" + + inslist=$(gcloud compute instances list | grep pg-gce) + if [ "$inslist" != "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Must Delete Current Server!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Prevents Conflicts with Changes!" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + exit + fi + ########## Server Must Not Be Deployed - END + + ### Part 1 + ipregion=$(cat /var/plexguide/project.ipregion) + gcloud compute zones list | awk '{print $1}' | tail -n +2 | grep $ipregion >/tmp/zones.list + num=0 + echo " " >/tmp/zones.print + + while read p; do + echo -n $p >>/tmp/zones.print + echo -n " " >>/tmp/zones.print + + num=$((num + 1)) + if [ $num == 4 ]; then + num=0 + echo " " >>/tmp/zones.print + fi + done /var/plexguide/project.location + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + break=on + fi + done + +fi + +################################################################################ DEPLOY END + +if [ "$menu" == "7" ]; then + ############################## BILLING CHECKS - START + billing=$(gcloud beta billing accounts list | grep "\") + if [ "$billing" == "" ]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Google Cloud Billing is Not Turned On!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: You Must Turn On Your Billing! PG is checking for the word >>> True" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## BILLING CHECKS - END + ############################## PROJECT BILLING CHECKS - START + project=$(cat /var/plexguide/project.final) + projectlink=$(gcloud beta billing accounts list | grep "\" | awk '{ print $1 }') + billingcheck=$(gcloud beta billing projects link $project --billing-account $projectlink | grep "billingEnabled: true") + if [ "$billingcheck" == "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Billing Failed - Turn It On Or Check" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Common Billing Issue for GCE Credits" + echo "NOTE: Cannot Continue with GCE" + echo "" + echo "1. Too Many Projects - Delete Unused Ones!" + echo "2. Ran Out of Credits & Must Turn On (Warning - Expensive)" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## PROJECT BILLING CHECKS - END + + ########## Server Must Not Be Deployed - START + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Checking Existing Deployment" + echo "--------------------------------------------------------" + echo "" + ########## + project=$(cat /var/plexguide/project.final) + ipaddress=$(cat /var/plexguide/project.ipaddress) + location=$(cat /var/plexguide/project.location) + region=$(cat /var/plexguide/project.ipregion) + cpu=$(cat /var/plexguide/project.processor) + + inslist=$(gcloud compute instances list | grep pg-gce) + if [ "$inslist" != "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Failed! Must Delete Current Server!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Prevents Conflicts with Changes!" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + exit + fi + ########## Server Must Not Be Deployed - END + + ############ FireWall + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Checking PG GCE Firewall Rules" + echo "--------------------------------------------------------" + echo "" + + inslist=$(gcloud compute firewall-rules list | grep plexguide) + if [ "$inslist" == "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: FireWall Rules Do Not Exist!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Building Firewall Rules! Please Wait" + echo "" + gcloud compute firewall-rules create plexguide --allow all + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + fi + + ########### Deployment + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Building PG GCE Template" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Please Standby!" + echo "" + + blueprint=$(gcloud compute instance-templates list | grep pg-gce-blueprint) + if [ "$blueprint" != "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Deleting Old Templates" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Please Standby!" + echo "" + gcloud compute instance-templates delete pg-gce-blueprint --quiet + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Building New Template" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Please Standby!" + echo "" + fi + + gcloud compute instance-templates create pg-gce-blueprint \ + --custom-cpu $cpu --custom-memory 8GB \ + --image-family ubuntu-1804-lts --image-project ubuntu-os-cloud \ + --boot-disk-auto-delete --boot-disk-size 100GB \ + --local-ssd interface=nvme + + sleep .5 + + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Deploying PG GCE Server" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Please Standby!" + echo "" + gcloud compute instances create pg-gce --source-instance-template pg-gce-blueprint --zone $location + echo "" + + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Assigning the IP Address to the GCE" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Please Standby" + echo "" + + gcloud compute instances delete-access-config pg-gce --access-config-name "external-nat" --zone $location --quiet + echo "" + gcloud compute instances add-access-config pg-gce --access-config-name "external-nat" --address $ipaddress + echo "" + + ######## Final Checks + finalchecks=$(gcloud compute instances list | grep pg-gce) + if [ "finalchecks" != "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Deployment Complete" + echo "--------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + touch /var/plexguide/gce.deployed + else + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Deployment Failed" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Unable to detect a running PG-GCE Server!" + echo "Please check your configs, billings, and permissions" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + fi +fi + +################################################################################ DEPLOY END +if [ "$menu" == "8" ]; then + ############################## BILLING CHECKS - START + billing=$(gcloud beta billing accounts list | grep "\") + if [ "$billing" == "" ]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Google Cloud Billing is Not Turned On!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: You Must Turn On Your Billing! PG is checking for the word >>> True" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## BILLING CHECKS - END + ############################## PROJECT BILLING CHECKS - START + project=$(cat /var/plexguide/project.final) + projectlink=$(gcloud beta billing accounts list | grep "\" | awk '{ print $1 }') + billingcheck=$(gcloud beta billing projects link $project --billing-account $projectlink | grep "billingEnabled: true") + if [ "$billingcheck" == "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Billing Failed - Turn It On Or Check" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Common Billing Issue for GCE Credits" + echo "NOTE: Cannot Continue with GCE" + echo "" + echo "1. Too Many Projects - Delete Unused Ones!" + echo "2. Ran Out of Credits & Must Turn On (Warning - Expensive)" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## PROJECT BILLING CHECKS - END + + ######## Final Message + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Securely Entering Your GCE Feeder Box" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: If asked to create keys, remember the passcodes!" + echo "1. To exit the GCE, type exit!" + echo "2. Install PG on your GCE and Select Feeder Edition!" + echo "3. Problems? Try rm -rf /root/.ssh/google_compute_engine" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + echo "" + ipproject=$(cat /var/plexguide/project.location) + gcloud compute ssh pg-gce --zone "$ipproject" + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Welcome Back To Your Main Server" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Sanity Check - You Exited Your GCE Feeder Box" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " +fi + +if [ "$menu" == "9" ]; then + ############################## BILLING CHECKS - START + billing=$(gcloud beta billing accounts list | grep "\") + if [ "$billing" == "" ]; then + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Google Cloud Billing is Not Turned On!" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: You Must Turn On Your Billing! PG is checking for the word >>> True" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## BILLING CHECKS - END + ############################## PROJECT BILLING CHECKS - START + project=$(cat /var/plexguide/project.final) + projectlink=$(gcloud beta billing accounts list | grep "\" | awk '{ print $1 }') + billingcheck=$(gcloud beta billing projects link $project --billing-account $projectlink | grep "billingEnabled: true") + if [ "$billingcheck" == "" ]; then + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Billing Failed - Turn It On Or Check" + echo "--------------------------------------------------------" + echo "" + echo "NOTE: Common Billing Issue for GCE Credits" + echo "NOTE: Cannot Continue with GCE" + echo "" + echo "1. Too Many Projects - Delete Unused Ones!" + echo "2. Ran Out of Credits & Must Turn On (Warning - Expensive)" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit + fi + ############################## PROJECT BILLING CHECKS - END + + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: Destroying GCE Server" + echo "--------------------------------------------------------" + echo "" + location=$(cat /var/plexguide/project.location) + echo "NOTE: Please Standby" + echo "" + gcloud compute instances delete pg-gce --quiet --zone "$location" + rm -rf /root/.ssh/google_compute_engine 1>/dev/null 2>&1 + rm -rf /var/plexguide/gce.deployed 1>/dev/null 2>&1 + echo "" + echo "--------------------------------------------------------" + echo "SYSTEM MESSAGE: PG GCE Server Destroyed!" + echo "--------------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " +fi diff --git a/menu/interface/gce/var.sh b/menu/interface/gce/var.sh new file mode 100644 index 00000000..088c171c --- /dev/null +++ b/menu/interface/gce/var.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +echo 9 >/var/plexguide/menu.number + +gcloud info | grep Account: | cut -c 10- >/var/plexguide/project.account + +file="/var/plexguide/project.final" +if [ ! -e "$file" ]; then + echo "[NOT SET]" >/var/plexguide/project.final +fi + +file="/var/plexguide/project.processor" +if [ ! -e "$file" ]; then + echo "NOT-SET" >/var/plexguide/project.processor +fi + +file="/var/plexguide/project.location" +if [ ! -e "$file" ]; then + echo "NOT-SET" >/var/plexguide/project.location +fi + +file="/var/plexguide/project.ipregion" +if [ ! -e "$file" ]; then + echo "NOT-SET" >/var/plexguide/project.ipregion +fi + +file="/var/plexguide/project.ipaddress" +if [ ! -e "$file" ]; then + echo "IP NOT-SET" >/var/plexguide/project.ipaddress +fi + +file="/var/plexguide/gce.deployed" +if [ -e "$file" ]; then + echo "Server Deployed" >/var/plexguide/gce.deployed.status +else + echo "Not Deployed" >/var/plexguide/gce.deployed.status +fi diff --git a/menu/interface/install/scripts/ansible.sh b/menu/interface/install/scripts/ansible.sh new file mode 100644 index 00000000..7cb91e5f --- /dev/null +++ b/menu/interface/install/scripts/ansible.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +######################################################## Declare Variables +sname="Ansible - Install" +pg_ansible=$(cat /var/plexguide/pg.ansible) +pg_ansible_stored=$(cat /var/plexguide/pg.ansible.stored) +######################################################## START: PG Log +sudo echo "INFO - Start of Script: $sname" >/var/plexguide/logs/pg.log +sudo bash /opt/plexguide/menu/log/log.sh +######################################################## START: Main Script +if [ "$pg_ansible" == "$pg_ansible_stored" ]; then + echo "" 1>/dev/null 2>&1 +else + echo "Installing / Upgrading Ansible" >/var/plexguide/message.phase + bash /opt/plexguide/menu/interface/install/scripts/message.sh + echo "" + #sudo apt-get remove ansible -y + #sudo apt-add-repository --remove ppa:ansible/ansible -y && sudo add-apt-repository ppa:ansible/ansible-2.5 -y && sudo apt install ansible -y + #apt-get update -y + #apt-get install ansible 2.5.5 -y + #apt-mark hold ansible + #yes | apt-get update + python -m pip install --disable-pip-version-check --upgrade --force-reinstall ansible==${1-2.5.11} + ############# FOR ANSIBLE + mkdir -p /etc/ansible/inventories/ 1>/dev/null 2>&1 + echo "[local]" >/etc/ansible/inventories/local + echo "127.0.0.1 ansible_connection=local" >>/etc/ansible/inventories/local + + ### Reference: https://docs.ansible.com/ansible/2.4/intro_configuration.html + echo "[defaults]" >/etc/ansible/ansible.cfg + echo "deprecation_warnings=False" >>/etc/ansible/ansible.cfg + echo "command_warnings = False" >>/etc/ansible/ansible.cfg + echo "callback_whitelist = profile_tasks" >>/etc/ansible/ansible.cfg + echo "inventory = /etc/ansible/inventories/local" >>/etc/ansible/ansible.cfg + + ### Disabling cows for people that have cowsay installed + echo "nocows = 1" >>/etc/ansible/ansible.cfg + + cat /var/plexguide/pg.ansible >/var/plexguide/pg.ansible.stored +fi +######################################################## END: Main Script +# +# +######################################################## END: PG Log +sudo echo "INFO - END of Script: $sname" >/var/plexguide/logs/pg.log +sudo bash /opt/plexguide/menu/log/log.sh diff --git a/menu/interface/install/scripts/edition.sh b/menu/interface/install/scripts/edition.sh new file mode 100644 index 00000000..a50cfddc --- /dev/null +++ b/menu/interface/install/scripts/edition.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +######################################################## Declare Variables +sname="PG Installer: Set PG Edition" +pg_edition=$(cat /var/plexguide/pg.edition) +pg_edition_stored=$(cat /var/plexguide/pg.edition.stored) +######################################################## START: PG Log +sudo echo "INFO - Start of Script: $sname" >/var/plexguide/logs/pg.log +sudo bash /opt/plexguide/menu/log/log.sh +######################################################## START: Main Script +if [ "$pg_edition" == "$pg_edition_stored" ]; then + echo "" 1>/dev/null 2>&1 +else + bash /opt/plexguide/menu/editions/editions.sh +fi +######################################################## END: Main Script +# +# +######################################################## END: PG Log +sudo echo "INFO - END of Script: $sname" >/var/plexguide/logs/pg.log +sudo bash /opt/plexguide/menu/log/log.sh diff --git a/menu/interface/install/scripts/message.sh b/menu/interface/install/scripts/message.sh new file mode 100644 index 00000000..84c38f51 --- /dev/null +++ b/menu/interface/install/scripts/message.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +message=$(cat /var/plexguide/message.phase) + +echo "" +echo "----------------------------------------------------" +echo "PLEASE STANDBY" +echo "System Message: $message" +echo "----------------------------------------------------" +sleep 2 diff --git a/menu/interface/serverid.sh b/menu/interface/serverid.sh new file mode 100644 index 00000000..72257b05 --- /dev/null +++ b/menu/interface/serverid.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +touch /var/plexguide/server.id.stored +start=$(cat /var/plexguide/server.id) +stored=$(cat /var/plexguide/server.id.stored) + +if [ "$start" != "$stored" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +↘️ ESTABLISHING ~ Server's Identification +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚠️ WARNING: Use > One Word - All LowerCase & Keep it Simple! + +EOF + + # Standby + read -p '🌏 TYPE Server ID | Press [ENTER]: ' typed /var/plexguide/server.id + cat /var/plexguide/server.id >/var/plexguide/server.id.stored + + sleep 3 + fi + +fi diff --git a/menu/interface/serverid/choice.yml b/menu/interface/serverid/choice.yml new file mode 100644 index 00000000..917c6dc2 --- /dev/null +++ b/menu/interface/serverid/choice.yml @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- name: Register Project + shell: 'cat /var/plexguide/server.id' + register: tempid + +- name: 'Set Server ID' + set_fact: + serverid: '{{tempid.stdout}}' + +- name: 'Key Menu Facts' + set_fact: + head1: "\nPG Server Identification Interface" + head2: "\nServer ID: {{serverid}}" + info2: "\n2. Server ID: Change It" diff --git a/menu/interface/serverid/file.sh b/menu/interface/serverid/file.sh new file mode 100644 index 00000000..0e948221 --- /dev/null +++ b/menu/interface/serverid/file.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +menu=$(cat /var/plexguide/final.choice) + +if [ "$menu" == "2" ]; then + echo "" + echo "-----------------------------------------------------" + echo "SYSTEM MESSAGE: Please Read the Following Information" + echo "-----------------------------------------------------" + echo "" + echo "NOTE: Setting the Server ID enables the Server to have" + echo "a unique name for backup and setup purposes." + echo "" + echo "Remember KISS: Keep-It-Simple Stupid! Create a simple" + echo "one word server ID such as hetzner1 or myserver" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue" + echo "" + echo "" + read -p "Set or Change the Server ID (y/n)? " -n 1 -r + echo # move cursor to a new line + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "" + echo "---------------------------------------------------" + echo "SYSTEM MESSAGE: [Y] Key was NOT Selected - Exiting!" + echo "---------------------------------------------------" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + exit 1 + fi + + break=no + while [ "$break" == "no" ]; do + echo "" + read -p 'Type a Sever ID & Then Press [ENTER]: ' typed + #typed=typed+0 + echo "" + echo "-------------------------------------------------" + echo "SYSTEM MESSAGE: Server ID - $typed" + echo "-------------------------------------------------" + echo "" + read -p "Continue with the Set Server ID (y/n)? " -n 1 -r + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "" + echo "---------------------------------------------------" + echo "SYSTEM MESSAGE: [Y] Key was NOT Selected" + echo "---------------------------------------------------" + echo "" + echo "You will be able to set the Server ID Again!" + echo + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + else + echo "" + echo "---------------------------------------------------" + echo "SYSTEM MESSAGE: Server ID - $typed" + echo "---------------------------------------------------" + echo "" + echo "Your Server ID is Now Set! Thank you!" + echo "" + echo $typed >/var/plexguide/server.id + break=yes + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo "" + fi + done + +#### Final fi +fi + +idtest=$(cat /var/plexguide/server.id) +if [ "$idtest" == "NOT-SET" ]; then + echo "" + echo "---------------------------------------------------" + echo "SYSTEM MESSAGE: You Must Create a SERVER ID!" + echo "---------------------------------------------------" + echo "" + echo "Restarting the Process" + echo + read -n 1 -s -r -p "Press [ANY KEY] to Continue " + echo serverid >/var/plexguide/type.choice && bash /opt/plexguide/menu/core/scripts/main.sh + exit +fi diff --git a/menu/interface/serverid/var.sh b/menu/interface/serverid/var.sh new file mode 100644 index 00000000..562eab07 --- /dev/null +++ b/menu/interface/serverid/var.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +echo 2 >/var/plexguide/menu.number + +file="/var/plexguide/server.id" +if [ ! -e "$file" ]; then + echo NOT-SET >/var/plexguide/server.id +fi diff --git a/menu/interface/settings.sh b/menu/interface/settings.sh new file mode 100644 index 00000000..a24fa886 --- /dev/null +++ b/menu/interface/settings.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/install.sh +# Menu Interface +setstart() { + + emdisplay=$(cat /var/plexguide/emergency.display) + switchcheck=$(cat /var/plexguide/pgui.switch) + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 PG Settings Interface Menu +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +[1] Download Path : Change the Processing Location +[2] MultiHD : Add Multiple HDs and/or Mount Points to MergerFS +[3] Processor : Enhance the CPU Processing Power +[4] WatchTower : Auto-Update Application Manager +[5] Change Time : Change the Server Time +[6] PG UI : $switchcheck | Port 8555 | pgui.domain.com +[7] Emergency Display: $emdisplay + +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + + # Standby + read -p 'Type a Number | Press [ENTER]: ' typed /var/plexguide/pgui.switch + docker stop pgui + docker rm pgui + service localspace stop + rm -f /etc/systemd/system/localspace.servive + rm -f /etc/systemd/system/localspace.service + else + echo "On" >/var/plexguide/pgui.switch + bash /opt/plexguide/menu/pgcloner/solo/pgui.sh + ansible-playbook /opt/pgui/pgui.yml + service localspace start + fi + setstart + ;; + 7) + if [[ "$emdisplay" == "On" ]]; then + echo "Off" >/var/plexguide/emergency.display + else echo "On" >/var/plexguide/emergency.display; fi + setstart + ;; + z) + exit + ;; + Z) + exit + ;; + *) + setstart + ;; + esac + +} + +setstart diff --git a/menu/interface/uninstall/choice.yml b/menu/interface/uninstall/choice.yml new file mode 100644 index 00000000..d7d2d47c --- /dev/null +++ b/menu/interface/uninstall/choice.yml @@ -0,0 +1,13 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- name: 'Key Menu Facts' + set_fact: + head1: "\nPGBlitz UnInstaller Interface" + head2: "\nWARNING! Ensure to Backup any Data!" + info2: "\n2. I want to UnInstall PGBlitz!" diff --git a/menu/interface/uninstall/file.sh b/menu/interface/uninstall/file.sh new file mode 100644 index 00000000..570a86e0 --- /dev/null +++ b/menu/interface/uninstall/file.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +menu=$(cat /var/plexguide/final.choice) + +if [ "$menu" == "2" ]; then + #read -n 1 -s -r -p "Press [ANY KEY] to Continue " + + echo "" + echo "-----------------------------------------------------------" + echo "SYSTEM MESSAGE: WARNING! PGBlitz Uninstall Interface!" + echo "-----------------------------------------------------------" + echo "" + sleep 3 + + while true; do + read -p "Pay Attention! Do YOU WANT to Continue Uninstalling PG (y or n)!? " yn + case $yn in + [Yy]*) + echo "" + echo "Ok... we are just double checking!" + sleep 2 + break + ;; + [Nn]*) echo "Ok! Exiting the Interface!" && echo "" && sleep 3 && exit ;; + *) echo "Please answer y or n (for yes or no)" ;; + esac + done + + echo "" + echo "-----------------------------------------------------------" + echo "SYSTEM MESSAGE: Uninstalling PG! May the Force Be With You!" + echo "-----------------------------------------------------------" + echo "" + sleep 3 + + echo "0" >/var/plexguide/pg.preinstall.stored + echo "0" >/var/plexguide/pg.ansible.stored + echo "0" >/var/plexguide/pg.rclone.stored + echo "0" >/var/plexguide/pg.python.stored + echo "0" >/var/plexguide/pg.docker.stored + echo "0" >/var/plexguide/pg.docstart.stored + echo "0" >/var/plexguide/pg.watchtower.stored + echo "0" >/var/plexguide/pg.label.stored + echo "0" >/var/plexguide/pg.alias.stored + echo "0" >/var/plexguide/pg.dep + rm -rf /var/plexguide/dep* 1>/dev/null 2>&1 + + echo "" + echo "-----------------------------------------------------------" + echo "SYSTEM MESSAGE: Removing All PGBlitz Dependent Services" + echo "-----------------------------------------------------------" + echo "" + sleep 2 + ansible-playbook /opt/plexguide/menu/interface/uninstall/remove-service.yml + + echo "" + echo "-----------------------------------------------------------" + echo "SYSTEM MESSAGE: Removing All PGBlitz File Directories" + echo "-----------------------------------------------------------" + echo "" + sleep 2 + rm -rf /var/plexguide + + echo "" + echo "-----------------------------------------------------------" + echo "SYSTEM MESSAGE: Uninstalling Docker & Generated Containers" + echo "-----------------------------------------------------------" + echo "" + sleep 2 + rm -rf /etc/docker + apt-get purge docker-ce -y --allow-change-held-packages + rm -rf /var/lib/docker + + while true; do + read -p "Pay Attention! Do you want to DELETE /opt/appdata (y or n)? " yn + case $yn in + [Yy]*) + echo "" + echo "Deleting Your Data Forever - Please Wait!" + rm -rf /opt/appdata + sleep 3 + echo "I'm here, I'm there, wait...I'm your DATA! Poof! I'm gone!" + sleep 3 + break + ;; + [Nn]*) echo "Data Will NOT be deleted!" && break ;; + *) echo "Please answer y or n (for yes or no)" ;; + esac + done + + echo "" + echo "---------------------------------------------------" + echo "SYSTEM MESSAGE: Success! PG Uninstalled! Rebooting!" + echo "---------------------------------------------------" + echo "" + sleep 3 + echo "" + echo "----------------------------------------------------" + echo "SYSTEM MESSAGE: PGBlitz Will Never Die! GoodBye!" + echo "----------------------------------------------------" + echo "" + sleep 2 + reboot + +fi diff --git a/menu/interface/uninstall/remove-service.yml b/menu/interface/uninstall/remove-service.yml new file mode 100644 index 00000000..ce8f8ccc --- /dev/null +++ b/menu/interface/uninstall/remove-service.yml @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + vars: + service_vars: + - { name: crypt.service } + - { name: pgdrive.service } + - { name: gdrive.service } + - { name: gcrypt.service } + - { name: tdrive.service } + - { name: tcrypt.service } + - { name: supertransfer2.service } + - { name: unionfs.service } + - { name: pgmove.service } + - { name: pgunion.service } + - { name: move.service } + - { name: pgblitz.service } + - { name: plexdrive.service } + - { name: st2monitor.service } + tasks: + - name: Checking Existing Service Name + stat: + path: '/etc/systemd/system/{{ item.name }}' + with_items: '{{ service_vars }}' + register: check_service_name + + - name: Stop If Service Is Running + systemd: state=stopped name={{ item.item.name }} daemon_reload=yes enabled=no + with_items: '{{ check_service_name.results }}' + when: item.stat.exists + + - name: Remove Services + file: + path: '/etc/systemd/system/{{ item.item.name }}' + state: absent + with_items: '{{ check_service_name.results }}' + when: item.stat.exists diff --git a/menu/interface/uninstall/unfiles.yml b/menu/interface/uninstall/unfiles.yml new file mode 100644 index 00000000..0ed0eaee --- /dev/null +++ b/menu/interface/uninstall/unfiles.yml @@ -0,0 +1,20 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Removing File Directories + file: + state: absent + path: "{{ item }}" + with_items: + - /var/plexguide + - /opt/appdata/plexguide + - {{path.stdout}}/nzbget + - {{path.stdout}}/sab + - {{path.stdout}}/rutorrent + - {{path.stdout}}/move + - {{path.stdout}}/gcrypt + - {{path.stdout}}/deluge + - {{path.stdout}}/torrentvpn + - {{path.stdout}}/qbittorrent + ignore_errors: yes diff --git a/menu/interface/uninstall/var.sh b/menu/interface/uninstall/var.sh new file mode 100644 index 00000000..7c78d74f --- /dev/null +++ b/menu/interface/uninstall/var.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +echo 2 >/var/plexguide/menu.number diff --git a/menu/interface/version/choice.yml b/menu/interface/version/choice.yml new file mode 100644 index 00000000..d164d013 --- /dev/null +++ b/menu/interface/version/choice.yml @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Register Project + shell: 'cat /var/plexguide/pg.number' + register: serverid + + - name: Installing EDGE + git: + repo: 'https://github.com/PGBlitz/PGBlitz.com' + dest: '/opt/plexguide' + version: Edge + force: yes + when: serverid.stdout == "edge" + + - name: 'Installing Version {{serverid.stdout}}' + git: + repo: 'https://github.com/PGBlitz/PGBlitz.com' + dest: '/opt/plexguide' + version: '{{serverid.stdout}}' + force: yes + when: not serverid.stdout == "edge" + ignore_errors: True + + - name: 'Stops First Time Run' + shell: 'touch /var/plexguide/ask.yes' + register: program diff --git a/menu/interface/version/file.sh b/menu/interface/version/file.sh new file mode 100644 index 00000000..18360ffe --- /dev/null +++ b/menu/interface/version/file.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +rm -rf /var/plexguide/ver.temp 1>/dev/null 2>&1 +touch /var/plexguide/ver.temp + +sleep 4 +## Builds Version List for Display +while read p; do + echo $p >>/var/plexguide/ver.temp +done /var/plexguide/pg.number + ansible-playbook /opt/plexguide/menu/interface/version/choice.yml + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ SYSTEM MESSAGE: Installed Verison - $storage - Standby! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 4 + else + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ SYSTEM MESSAGE: Version $storage does not exist! - Standby! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 4 + cat /var/plexguide/ver.temp + echo "" + fi + +done diff --git a/menu/interface/vpnserver/choice.yml b/menu/interface/vpnserver/choice.yml new file mode 100644 index 00000000..9a7a66e9 --- /dev/null +++ b/menu/interface/vpnserver/choice.yml @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- name: 'Key Menu Facts' + set_fact: + head1: "\nWelcome to the PG VPN Server Access Interface!" + head2: "\nCommand to View VPN Information: pgvpn" + info2: "\n2. VPNServer: Install" + info3: "\n3. VPNServer: UnInstall" diff --git a/menu/interface/vpnserver/file.sh b/menu/interface/vpnserver/file.sh new file mode 100644 index 00000000..f1c5b7e6 --- /dev/null +++ b/menu/interface/vpnserver/file.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +menu=$(cat /var/plexguide/final.choice) + +if [ "$menu" == "2" ]; then + echo "" + echo "-----------------------------------------------------" + echo "SYSTEM MESSAGE: Installing - Please Standby!" + echo "-----------------------------------------------------" + echo "" + echo "NOTE: Install Time: 2 to 4 Minutes!" + sleep 2 + echo "" + wget https://git.io/vpnsetup -O vpnsetup.sh 1>/dev/null 2>&1 + sudo sh vpnsetup.sh >/opt/appdata/plexguide/vpninfo.raw + cat /opt/appdata/plexguide/vpninfo.raw | tail -n -12 | head -n +4 >/opt/appdata/plexguide/vpn.info + rm -rf /opt/appdata/plexguide/vpninfo.raw + echo + echo "-----------------------------------------------------" + echo "SYSTEM MESSAGE: Please Copy Your Information" + echo "-----------------------------------------------------" + echo "" + cat /opt/appdata/plexguide/vpn.info + echo "" + echo "Config Info: Visit http://pgvpn.pgblitz.com or WIKI" + echo "Note: pgvpn <<< command to recall your vpn info" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " +else + echo "" # leave if statement and continue. +fi + +if [ "$menu" == "3" ]; then + echo "Uninstaller Not Ready!" + echo "" + read -n 1 -s -r -p "Press [ANY KEY] to Continue " +fi diff --git a/menu/interface/vpnserver/var.sh b/menu/interface/vpnserver/var.sh new file mode 100644 index 00000000..1f7a49ea --- /dev/null +++ b/menu/interface/vpnserver/var.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +echo 3 >/var/plexguide/menu.number diff --git a/menu/log/log.sh b/menu/log/log.sh new file mode 100644 index 00000000..fc0be0df --- /dev/null +++ b/menu/log/log.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +dt=$(date '+%d/%m/%Y %H:%M:%S') +log=$(cat /var/plexguide/logs/pg.log) +echo "$dt $log" >>"/var/plexguide/logs/pg.log" diff --git a/menu/motd/10-hostname-color b/menu/motd/10-hostname-color new file mode 100644 index 00000000..a12023a5 --- /dev/null +++ b/menu/motd/10-hostname-color @@ -0,0 +1,3 @@ +#!/bin/bash + +/usr/bin/env figlet "$(hostname)" | /usr/bin/env lolcat -f diff --git a/menu/motd/20-sysinfo b/menu/motd/20-sysinfo new file mode 100644 index 00000000..1c057e30 --- /dev/null +++ b/menu/motd/20-sysinfo @@ -0,0 +1,30 @@ +#!/bin/bash + +# get load averages +IFS=" " read LOAD1 LOAD5 LOAD15 <<<$(cat /proc/loadavg | awk '{ print $1,$2,$3 }') +# get free memory +IFS=" " read USED FREE TOTAL <<<$(free -htm | grep "Mem" | awk {'print $3,$4,$2'}) +# get processes +PROCESS=$(ps -eo user= | sort | uniq -c | awk '{ print $2 " " $1 }') +PROCESS_ALL=$(echo "$PROCESS" | awk {'print $2'} | awk '{ SUM += $1} END { print SUM }') +PROCESS_ROOT=$(echo "$PROCESS" | grep root | awk {'print $2'}) +PROCESS_USER=$(echo "$PROCESS" | grep -v root | awk {'print $2'} | awk '{ SUM += $1} END { print SUM }') +# get processors +PROCESSOR_NAME=$(grep "model name" /proc/cpuinfo | cut -d ' ' -f3- | awk {'print $0'} | head -1) +PROCESSOR_COUNT=$(grep -ioP 'processor\t:' /proc/cpuinfo | wc -l) +IP_ADDRESS=$(ip a | grep glo | awk '{print $2}' | head -1 | cut -f1 -d/) +W="\e[0;39m" +G="\e[1;32m" + +echo -e " +${W}system: +$W Distro.......: $W$(cat /etc/*release | grep "PRETTY_NAME" | cut -d "=" -f 2- | sed 's/"//g') +$W Kernel.......: $W$(uname -sr) + +$W Uptime.......: $W$(uptime -p) +$W Load.........: $G$LOAD1$W (1m), $G$LOAD5$W (5m), $G$LOAD15$W (15m) +$W Processes....: $W$G$PROCESS_ROOT$W (root), $G$PROCESS_USER$W (user), $G$PROCESS_ALL$W (total) + +$W CPU..........: $W$PROCESSOR_NAME ($G$PROCESSOR_COUNT$W vCPU) +$W Memory.......: $G$USED$W used, $G$FREE$W free, $G$TOTAL$W total$W +$W Network......: $G$IP_ADDRESS$W" diff --git a/menu/motd/30-diskinfo b/menu/motd/30-diskinfo new file mode 100644 index 00000000..161a32a7 --- /dev/null +++ b/menu/motd/30-diskinfo @@ -0,0 +1,42 @@ +#!/bin/bash + +# config +drives="sda sdb sdc" +max_usage=90 +bar_width=50 +target_temp=22 +# colors +white="\e[39m" +green="\e[1;32m" +red="\e[1;31m" +dim="\e[2m" +undim="\e[0m" + +# disk usage: ignore zfs, squashfs & tmpfs +mapfile -t dfs < <(df -H -x fuse.rclone -x fuse.unionfs -x proc -x sys -x dev -x zfs -x squashfs -x tmpfs -x devtmpfs --output=target,pcent,size,avail,used | tail -n+2) +printf "\nstorage:\n" + +for line in "${dfs[@]}"; do + + if [[ $line =~ .*docker.* ]]; then + continue + fi + + # get disk usage + usage=$(echo "$line" | awk '{print $2}' | sed 's/%//') + path=$(echo "$line" | awk '{print $1}' | sed 's/%//') + total=$(echo "$line" | awk '{print $3}' | sed 's/%//') + free=$(echo "$line" | awk '{print $4}' | sed 's/%//') + used=$(echo "$line" | awk '{print $5}' | sed 's/%//') + # color is green if usage < max_usage, else red + if [ "${usage}" -ge "${max_usage}" ]; then + color=$red + else + color=$green + fi + + output+=$(echo " ${path} ${color}${used}${undim} used, ${color}${free}${undim} free, ${color}${total}${undim} total\n") + +done +printf "${output}" | + awk '{printf " %.15s %s\n", $1 "............:", $2 " " $3 " " $4 " " $5 " " $6 " " $7}' diff --git a/menu/motd/motd.yml b/menu/motd/motd.yml new file mode 100644 index 00000000..f4a32a7a --- /dev/null +++ b/menu/motd/motd.yml @@ -0,0 +1,40 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Disable existing MOTD + shell: chmod -x /etc/update-motd.d/* + + - name: Deploy Dynamic MOTD + file: + path: /etc/update-motd.d + state: directory + mode: 0775 + + - name: Import MOTD Files + copy: 'src={{item}} dest=/etc/update-motd.d/{{item}} force=yes mode=0775' + with_items: + - 10-hostname-color + - 20-sysinfo + - 30-diskinfo + + - name: Install lolcat pip module + pip: + name: lolcat + state: latest + ignore_errors: yes + + - cron: + name: 'Update MOTD' + user: 'root' + minute: '10' + job: 'update-motd' + state: absent + become_user: root diff --git a/menu/network/network.sh b/menu/network/network.sh new file mode 100644 index 00000000..5023a6d9 --- /dev/null +++ b/menu/network/network.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/plexguide/menu/functions/functions.sh +# Menu Interface +question1() { + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📂 PG System & Network Auditor +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +[1] System & Network Benchmark - Basic +[2] System & Network Benchmark - Advanced +[3] Simple SpeedTest + +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + + # Standby + read -p 'Type a Number | Press [ENTER]: ' typed &1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "Upgrading packages" +apt-get upgrade -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "Dist-Upgrading packages" +apt-get dist-upgrade -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "Autoremove old Updates" +apt-get autoremove -yqq 2>&1 >>/dev/null +export DEBIAN_FRONTEND=noninteractive +echo "install complete" + +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 PG System Tweaker +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💬 PG System Tweaker + +[1] Network Tweaker ( Debian 9 & Ubuntu 18 only ) +[2] Docker Swapness +[3] PGBlitz logrotator +[4] VnStat autoinstaller + +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + +# Standby +read -p 'Type a Number | Press [ENTER]: ' typed &1 >>/dev/null + export DEBIAN_FRONTEND=noninteractive + echo "networktools installed" + sleep 2 + network=$(ifconfig | grep -E 'eno1|enp|ens5' | awk '{print $1}' | sed -e 's/://g') + sleep 2 + echo $network "network detected" + ethtool -K $network tso off tx off + sed -i '$a\' /etc/crontab + sed -i '$a\#################################' /etc/crontab + sed -i '$a\## PG Network tweak ' /etc/crontab + sed -i '$a\#################################' /etc/crontab + sed -i '$a\@reboot ethtool -K '$network' tso off tx off\' /etc/crontab + sleep 2 + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo " ✅ PASSED ! Network Tweak done" + echo " ✅ PASSED ! crontab line added" + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" && sleep 10 + +elif [ "$typed" == "2" ]; then + sudo sysctl vm.swappiness=0 + sudo sysctl vm.overcommit_memory=1 + sed -i '$a\' /etc/sysctl.conf + sed -i '$a\' /etc/sysctl.conf + sed -i '$a\#########################################' /etc/sysctl.conf + sed -i '$a\## Docker PG Swapness changes ' /etc/sysctl.conf + sed -i '$a\#########################################' /etc/sysctl.conf + sed -i '$a\vm.swappiness=0\' /etc/sysctl.conf + sed -i '$a\vm.overcommit_memory=1\' /etc/sysctl.conf + sleep 2 + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo " ✅ PASSED ! Docker swappiness offline" + echo " ✅ PASSED ! systctl edit" + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" && sleep 10 + +elif [ "$typed" == "3" ]; then + username=$(grep "1000" /etc/passwd | cut -d: -f1 | awk '{print $1}') + + sed -i '/#compress/s/^#*//g' /etc/logrotate.conf + sed -i 's/weekly/daily/g' /etc/logrotate.conf + sed -i 's/rotate 4/rotate 1/g' /etc/logrotate.conf + + sleep 2 + + sed -i '$a\ ' /etc/logrotate.conf + sed -i '$a\########################################' /etc/logrotate.conf + sed -i '$a\## PGBlitz Upload logrotate ' /etc/logrotate.conf + sed -i '$a\########################################' /etc/logrotate.conf + sed -i '$a\ ' /etc/logrotate.conf + sed -i '$a\/var/plexguide/logs/*.log {' /etc/logrotate.conf + sed -i '$a\ su '$username' '$username' ' /etc/logrotate.conf + sed -i '$a\ rotate 7' /etc/logrotate.conf + sed -i '$a\ daily' /etc/logrotate.conf + sed -i '$a\ compress' /etc/logrotate.conf + sed -i '$a\ missingok' /etc/logrotate.conf + sed -i '$a\ notifempty' /etc/logrotate.conf + sed -i '$a\ maxage 7' /etc/logrotate.conf + sed -i '$a\ create 755 '$username' '$username'' /etc/logrotate.conf + sed -i '$a\}' /etc/logrotate.conf + sed -i '$a\ ' /etc/logrotate.conf + sed -i '$a\######################################' /etc/logrotate.conf + sed -i '$a\## PGBlitz Upload logrotate ' /etc/logrotate.conf + sed -i '$a\######################################' /etc/logrotate.conf + sleep 2 + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo " ✅ PASSED ! PGBlitz logrotate installed" + echo " ✅ PASSED ! Daily backup from the logs" + echo " ✅ PASSED ! max age 7 Days " + echo " ✅ PASSED ! auto delete older logs" + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" && sleep 10 + +elif [ "$typed" == "4" ]; then + echo "networktools | vnstat | vnstati install | please wait" + apt-get install ethtool vnstat vnstati -yqq 2>&1 >>/dev/null + export DEBIAN_FRONTEND=noninteractive + echo "networktools | vnstat | vnstati installed" + sleep 2 + network=$(ifconfig | grep -E 'eno1|enp|ens5' | awk '{print $1}' | sed -e 's/://g') + sleep 2 + echo $network "network detected" + sed -i 's/eth0/'$network'/g' /etc/vnstat.conf + sed -i 's/UnitMode 0/UnitMode 1/g' /etc/vnstat.conf + sed -i 's/RateUnit 1/RateUnit 0/g' /etc/vnstat.conf + sed -i 's/Locale "-"/Locale "LC_ALL=en_US.UTF-8"/g' /etc/vnstat.conf + sleep 2 + /etc/init.d/vnstat restart 2>&1 >>/dev/null + sleep 2 + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo " ✅ PASSED ! vnstat installed" + echo " ✅ PASSED ! vnstat -l [ live traffic ]" + echo " ✅ PASSED ! vnstat -d [ daily traffic ]" + echo " ✅ PASSED ! vnstat -w [ weekly traffic ]" + echo " ✅ PASSED ! vnstat -m [ month traffic ]" + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" && sleep 10 + +elif [ "$typed" == "Z" ] || [ "$typed" == "z" ]; then + exit +else + bash /opt/plexguide/menu/tools/tools.sh + exit +fi +bash /opt/plexguide/menu/tools/tools.sh +exit diff --git a/menu/pg.yml b/menu/pg.yml new file mode 100644 index 00000000..ea818778 --- /dev/null +++ b/menu/pg.yml @@ -0,0 +1,12 @@ +--- +- hosts: localhost + vars: + extra: '' + + roles: + - { role: docker, tags: ['docker'] } + - { role: docstart, tags: ['docstart'] } + - { role: dockerdeb, tags: ['dockerdeb'] } + - { role: autodelete, tags: ['autodelete'] } + - { role: clean, tags: ['clean'] } + - { role: clean-encrypt, tags: ['clean-encrypt'] } diff --git a/menu/pgbox/cname.sh b/menu/pgbox/cname.sh new file mode 100644 index 00000000..982a07d5 --- /dev/null +++ b/menu/pgbox/cname.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): LooseSeal2 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/plexguide/menu/functions/functions.sh + +# vars +program=$(cat /tmp/program_var) +domain=$(cat "/var/plexguide/server.domain") + +variable /var/plexguide/"$program".cname "$program" + +variable /var/plexguide/"$program".port "" + +# FIRST QUESTION +question1() { + cname=$(cat "/var/plexguide/$program.cname") + port=$(cat "/var/plexguide/$program.port") + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⌛ $program - Set subdomains & ports +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +⚡ Reference: http://$program.pgblitz.com + +EOF + if [[ $port != "" ]]; then + tee <<-EOF +External Url: https://$cname.$domain:$port +EOF + else + tee <<-EOF +External Url: https://$cname.$domain +EOF + fi + + tee <<-EOF + +[1] Change subdomain +[2] Change external port + +EOF + + if [[ $port != "" ]]; then + tee <<-EOF +[A] Use https://$cname.$domain:$port +EOF + else + tee <<-EOF +[A] Use https://$cname.$domain +EOF + fi + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + + read -p '↘️ Type Number | Press [ENTER]: ' typed "/var/plexguide/$program.cname" + question1 + fi + fi + elif [ "$typed" == "2" ]; then + read -p "🌍 Type port 1025-65535 to use for $program | blank for default | Press [ENTER]: " typed "/var/plexguide/$program.port" + else + if ! [[ "$typed" =~ ^[0-9]+$ && "$typed" -ge 1025 && "$typed" -le 65535 ]]; then + badinput1 + else + echo "$typed" >"/var/plexguide/$program.port" + fi + fi + question1 + else badinput1; fi +} + +question1 + +manualuser() { + while read p; do + echo "$p" >"/var/plexguide/$program.cname" + done http://$program.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + +tee <<-EOF +▫ $program:${port} <- Use this as the url when connecting another app to $program. +EOF + +if [ "$ports" == "" ]; then + tee <<-EOF +▫ $ip:${port}${extra} +EOF +fi + +if [ "$domain" != "NOT-SET" ]; then + if [ "$ports" == "" ]; then + tee <<-EOF +▫ $domain:${port}${extra} +EOF + fi + tee <<-EOF +▫ $cname.$domain${extra} +EOF +fi + +if [ "$program" == "plex" ]; then + tee <<-EOF + +First Time Plex Claim Notice +━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + if [ "$domain" != "NOT-SET" ]; then + tee <<-EOF +▫ http://plex.${domain}:32400 <-- Use http; not https +EOF + fi + + tee <<-EOF +▫ $ip:${port}${extra} +EOF +fi + +if [[ "$program" == *"sonarr"* ]] || [[ "$program" == *"radarr"* ]] || [[ "$program" == *"lidarr"* ]]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💬 Manual Configuration Required > http://$program.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + $program requires additional manual configuration! +EOF + if [[ "$program" == *"sonarr"* ]] || [[ "$program" == *"radarr"* ]] || [[ "$program" == *"lidarr"* ]]; then + tee <<-EOF + + $program requires "downloader mappings" to enable hardlinking and rapid importing. + + If you do not have these mappings, $program can't rename and move the files on import. + This will result in files being copied instead of moved, and it will cause other issues. + + The mappings are on the download client settings (advanced setting), at the bottom of the page. + Visit https://github.com/PGBlitz/PGBlitz.com/wiki/Remote-Path-Mappings for more information. + +EOF + fi + tee <<-EOF +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚠ Failure to perform manual configuration changes will cause problems! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🌍 Visit the wiki for instructions on how to configure $program. +http://$program.pgblitz.com or http://github.com/PGBlitz/PGBlitz.com/wiki/$program + +EOF +fi diff --git a/menu/pgbox/pgbox_communitypersonal.yml b/menu/pgbox/pgbox_communitypersonal.yml new file mode 100644 index 00000000..71b44e8f --- /dev/null +++ b/menu/pgbox/pgbox_communitypersonal.yml @@ -0,0 +1,18 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Register User + shell: 'cat /var/plexguide/boxcommunity.user' + register: boxuser + + - name: Register Branch + shell: 'cat /var/plexguide/boxcommunity.branch' + register: boxbranch + + - name: 'Cloning {{boxuser.stdout}} Community Apps' + git: + repo: 'https://github.com/{{boxuser.stdout}}/Apps-Community' + dest: '/opt/communityapps' + version: '{{boxbranch.stdout}}' + force: yes diff --git a/menu/pgbox/pgbox_corepersonal.yml b/menu/pgbox/pgbox_corepersonal.yml new file mode 100644 index 00000000..15b098fd --- /dev/null +++ b/menu/pgbox/pgbox_corepersonal.yml @@ -0,0 +1,18 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Register User + shell: 'cat /var/plexguide/boxcore.user' + register: boxuser + + - name: Register Branch + shell: 'cat /var/plexguide/boxcore.branch' + register: boxbranch + + - name: 'Cloning {{boxuser.stdout}} Core Apps' + git: + repo: 'https://github.com/{{boxuser.stdout}}/Apps-Core' + dest: '/opt/coreapps' + version: '{{boxbranch.stdout}}' + force: yes diff --git a/menu/pgbox/pgboxcommunity.sh b/menu/pgbox/pgboxcommunity.sh new file mode 100644 index 00000000..c03477ae --- /dev/null +++ b/menu/pgbox/pgboxcommunity.sh @@ -0,0 +1,362 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# FUNCTIONS START ############################################################## +source /opt/plexguide/menu/functions/functions.sh +source /opt/plexguide/menu/functions/install.sh + +queued() { + echo + read -p "⛔️ ERROR - $typed already queued! | Press [ENTER] " typed ") + if [ "$croncheck" == "0" ]; then bash /opt/plexguide/menu/cron/cron.sh; fi +} + +cronmass() { + croncheck=$(cat /opt/communityapps/apps/_cron.list | grep -c "\<$p\>") + if [ "$croncheck" == "0" ]; then bash /opt/plexguide/menu/cron/cron.sh; fi +} + +initial() { + rm -rf /var/plexguide/pgbox.output 1>/dev/null 2>&1 + rm -rf /var/plexguide/pgbox.buildup 1>/dev/null 2>&1 + rm -rf /var/plexguide/program.temp 1>/dev/null 2>&1 + rm -rf /var/plexguide/app.list 1>/dev/null 2>&1 + touch /var/plexguide/pgbox.output + touch /var/plexguide/program.temp + touch /var/plexguide/app.list + touch /var/plexguide/pgbox.buildup + + mkdir -p /opt/communityapps + + if [ "$boxversion" == "official" ]; then + ansible-playbook /opt/plexguide/menu/pgbox/pgboxcommunity.yml + else ansible-playbook /opt/plexguide/menu/pgbox/pgbox_communitypersonal.yml; fi + + echo "" + echo "💬 Pulling Update Files - Please Wait" + file="/opt/communityapps/place.holder" + waitvar=0 + while [ "$waitvar" == "0" ]; do + sleep .5 + if [ -e "$file" ]; then waitvar=1; fi + done + customcontainers +} + +question1() { + + ### Remove Running Apps + while read p; do + sed -i "/^$p\b/Id" /var/plexguide/app.list + done /var/plexguide/app.list + while read p; do + echo "" >>/opt/communityapps/apps/$p.yml + echo "##PG-Community" >>/opt/communityapps/apps/$p.yml + + mkdir -p /opt/mycontainers + touch /opt/appdata/plexguide/rclone.conf + done /var/plexguide/pgbox.running + + ### Remove Official Apps + while read p; do + # reminder, need one for custom apps + baseline=$(cat /opt/communityapps/apps/$p.yml | grep "##PG-Community") + if [ "$baseline" == "" ]; then sed -i -e "/$p/d" /var/plexguide/app.list; fi + done >/var/plexguide/program.temp + echo -n " " >>/var/plexguide/program.temp + num=$((num + 1)) + if [[ "$num" == "7" ]]; then + num=0 + echo " " >>/var/plexguide/program.temp + fi + done ") + if [ "$current" != "" ]; then queued && question1; fi + + current=$(cat /var/plexguide/pgbox.running | grep "\<$typed\>") + if [ "$current" != "" ]; then exists && question1; fi + + current=$(cat /var/plexguide/program.temp | grep "\<$typed\>") + if [ "$current" == "" ]; then badinput1 && question1; fi + + part1 +} + +part1() { + echo "$typed" >>/var/plexguide/pgbox.buildup + num=0 + + touch /var/plexguide/pgbox.output && rm -rf /var/plexguide/pgbox.output + + while read p; do + echo -n $p >>/var/plexguide/pgbox.output + echo -n " " >>/var/plexguide/pgbox.output + if [[ "$num" == 7 ]]; then + num=0 + echo " " >>/var/plexguide/pgbox.output + fi + done /tmp/program_var + + bash /opt/communityapps/apps/image/_image.sh + done /var/plexguide/boxcommunity.user + echo "$boxbranch" >/var/plexguide/boxcommunity.branch + pinterface + ;; + 2) + existcheck=$(git ls-remote --exit-code -h "https://github.com/$boxuser/Apps-Community" | grep "$boxbranch") + if [ "$existcheck" == "" ]; then + echo + read -p '💬 Exiting! Forked Version Does Not Exist! | Press [ENTER]: ' typed /tmp/output.info +mainbanner diff --git a/menu/pgbox/pgboxcommunity.yml b/menu/pgbox/pgboxcommunity.yml new file mode 100644 index 00000000..6ecad41f --- /dev/null +++ b/menu/pgbox/pgboxcommunity.yml @@ -0,0 +1,19 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Check if Image Variable Exists + stat: + path: '/opt/communityapps' + register: pathcheck + + - name: 'Transfer Image Variable' + shell: 'rm -rf /opt/communityapps' + when: pathcheck.stat.exists + + - name: Cloning Community Apps + git: + repo: 'https://github.com/PGBlitz/Apps-Community' + dest: /opt/communityapps + version: 'v8.6' + force: yes diff --git a/menu/pgbox/pgboxcore.sh b/menu/pgbox/pgboxcore.sh new file mode 100644 index 00000000..c9ae3c35 --- /dev/null +++ b/menu/pgbox/pgboxcore.sh @@ -0,0 +1,361 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# FUNCTIONS START ############################################################## +source /opt/plexguide/menu/functions/functions.sh + +queued() { + echo + read -p "⛔️ ERROR - $typed Already Queued! | Press [ENTER] " typed ") + if [ "$croncheck" == "0" ]; then bash /opt/plexguide/menu/cron/cron.sh; fi +} + +cronmass() { + croncheck=$(cat /opt/coreapps/apps/_cron.list | grep -c "\<$p\>") + if [ "$croncheck" == "0" ]; then bash /opt/plexguide/menu/cron/cron.sh; fi +} + +initial() { + rm -rf /var/plexguide/pgbox.output 1>/dev/null 2>&1 + rm -rf /var/plexguide/pgbox.buildup 1>/dev/null 2>&1 + rm -rf /var/plexguide/program.temp 1>/dev/null 2>&1 + rm -rf /var/plexguide/app.list 1>/dev/null 2>&1 + touch /var/plexguide/pgbox.output + touch /var/plexguide/program.temp + touch /var/plexguide/app.list + touch /var/plexguide/pgbox.buildup + + mkdir -p /opt/coreapps + + if [ "$boxversion" == "official" ]; then + ansible-playbook /opt/plexguide/menu/pgbox/pgboxcore.yml + else ansible-playbook /opt/plexguide/menu/pgbox/pgbox_corepersonal.yml; fi + + echo "" + echo "💬 Pulling Update Files - Please Wait" + file="/opt/coreapps/place.holder" + waitvar=0 + while [ "$waitvar" == "0" ]; do + sleep .5 + if [ -e "$file" ]; then waitvar=1; fi + done + +} + +question1() { + + ### Remove Running Apps + while read p; do + sed -i "/^$p\b/Id" /var/plexguide/app.list + done /var/plexguide/app.list + while read p; do + echo "" >>/opt/coreapps/apps/$p.yml + echo "##PG-Core" >>/opt/coreapps/apps/$p.yml + + mkdir -p /opt/mycontainers + touch /opt/appdata/plexguide/rclone.conf + done /var/plexguide/pgbox.running + + ### Remove Official Apps + while read p; do + # reminder, need one for custom apps + baseline=$(cat /opt/coreapps/apps/$p.yml | grep "##PG-Core") + if [ "$baseline" == "" ]; then sed -i -e "/$p/d" /var/plexguide/app.list; fi + done >/var/plexguide/program.temp + echo -n " " >>/var/plexguide/program.temp + num=$((num + 1)) + if [[ "$num" == "7" ]]; then + num=0 + echo " " >>/var/plexguide/program.temp + fi + done ") + if [ "$current" != "" ]; then queued && question1; fi + + current=$(cat /var/plexguide/pgbox.running | grep "\<$typed\>") + if [ "$current" != "" ]; then exists && question1; fi + + current=$(cat /var/plexguide/program.temp | grep "\<$typed\>") + if [ "$current" == "" ]; then badinput1 && question1; fi + + part1 +} + +part1() { + echo "$typed" >>/var/plexguide/pgbox.buildup + num=0 + + touch /var/plexguide/pgbox.output && rm -rf /var/plexguide/pgbox.output + + while read p; do + echo -n $p >>/var/plexguide/pgbox.output + echo -n " " >>/var/plexguide/pgbox.output + if [[ "$num" == 7 ]]; then + num=0 + echo " " >>/var/plexguide/pgbox.output + fi + done /tmp/program_var + + bash /opt/coreapps/apps/image/_image.sh + + # CName & Port Execution + bash /opt/plexguide/menu/pgbox/cname.sh + done /var/plexguide/boxcore.user + echo "$boxbranch" >/var/plexguide/boxcore.branch + pinterface + ;; + 2) + existcheck=$(git ls-remote --exit-code -h "https://github.com/$boxuser/Apps-Core" | grep "$boxbranch") + if [ "$existcheck" == "" ]; then + echo + read -p '💬 Exiting! Forked Version Does Not Exist! | Press [ENTER]: ' typed /tmp/output.info +mainbanner diff --git a/menu/pgbox/pgboxcore.yml b/menu/pgbox/pgboxcore.yml new file mode 100644 index 00000000..1fc19ff7 --- /dev/null +++ b/menu/pgbox/pgboxcore.yml @@ -0,0 +1,19 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Check if Image Variable Exists + stat: + path: '/opt/coreapps' + register: pathcheck + + - name: 'Transfer Image Variable' + shell: 'rm -rf /opt/coreapps' + when: pathcheck.stat.exists + + - name: Cloning Core Apps + git: + repo: 'https://github.com/PGBlitz/Apps-Core' + dest: /opt/coreapps + version: 'v8.6' + force: yes diff --git a/menu/pgbox/pgboxselect.sh b/menu/pgbox/pgboxselect.sh new file mode 100644 index 00000000..5212c881 --- /dev/null +++ b/menu/pgbox/pgboxselect.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +mainstart() { + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 PG Box Apps Interface Selection 📓 Reference: pgbox.pgblitz.com +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +💬 PG Box installs a series of Core and Community applications! + +[1] PG Box: Core +[2] PG Box: Community +[3] PG Box: Removal +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + # Standby + read -p 'Type a Number | Press [ENTER]: ' typed /var/plexguide/pgcloner.rolename +echo 'BlitzGCE' >/var/plexguide/pgcloner.roleproper +echo 'BlitzGCE' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'blitzgce.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 Blitz GCE scripts are setup so that users can deploy any +Google Cloud Edition container to act as as feeder between two to +three months!" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/core/main.sh b/menu/pgcloner/core/main.sh new file mode 100644 index 00000000..41241c20 --- /dev/null +++ b/menu/pgcloner/core/main.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# FUNCTIONS START ############################################################## +source /opt/plexguide/menu/functions/functions.sh + +rolename=$(cat /var/plexguide/pgcloner.rolename) +roleproper=$(cat /var/plexguide/pgcloner.roleproper) +projectname=$(cat /var/plexguide/pgcloner.projectname) +projectversion=$(cat /var/plexguide/pgcloner.projectversion) + +mkdir -p "/opt/$rolename" + +initial() { + ansible-playbook "/opt/plexguide/menu/pgcloner/core/primary.yml" + echo "" + echo "💬 Pulling Update Files - Please Wait" + file="/opt/$rolename/place.holder" + waitvar=0 + while [ "$waitvar" == "0" ]; do + sleep .5 + if [ -e "$file" ]; then waitvar=1; fi + done +} + +custom() { + mkdir -p "/opt/$rolename" + ansible-playbook "/opt/plexguide/menu/pgcloner/core/personal.yml" + + echo "" + echo "💬 Pulling Update Files - Please Wait" + file="/opt/$rolename/place.holder" + waitvar=0 + while [ "$waitvar" == "0" ]; do + sleep .5 + if [ -e "$file" ]; then waitvar=1; fi + done +} + +mainbanner() { + clonerinfo=$(cat /var/plexguide/pgcloner.info) + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 $roleproper +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +$clonerinfo + +[1] Utilize $roleproper - PGBlitz's +[2] Utilize $roleproper - Personal (Forked) + +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + read -p 'Type a Selection | Press [ENTER]: ' typed /var/plexguide/$rolename.user + echo "$branch" >/var/plexguide/$rolename.branch + pinterface + ;; + 2) + existcheck=$(git ls-remote --exit-code -h "https://github.com/$user/$projectname" | grep "$branch") + if [ "$existcheck" == "" ]; then + echo + read -p '💬 Exiting! Forked Version Does Not Exist! | Press [ENTER]: ' typed /tmp/output.info +mainbanner diff --git a/menu/pgcloner/core/personal.yml b/menu/pgcloner/core/personal.yml new file mode 100644 index 00000000..d447c4cc --- /dev/null +++ b/menu/pgcloner/core/personal.yml @@ -0,0 +1,26 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Register Role + shell: 'cat /var/plexguide/pgcloner.projectname' + register: pname + + - name: Register Role + shell: 'cat /var/plexguide/pgcloner.rolename' + register: prole + + - name: Register User - Personal + shell: 'cat /var/plexguide/{{prole.stdout}}.user' + register: user + + - name: Register Branch - Personal + shell: 'cat /var/plexguide/{{prole.stdout}}.branch' + register: branch + + - name: 'Cloning Personal Forked Role' + git: + repo: 'https://github.com/{{user.stdout}}/{{pname.stdout}}' + dest: '/opt/{{prole.stdout}}' + version: '{{branch.stdout}}' + force: yes diff --git a/menu/pgcloner/core/primary.yml b/menu/pgcloner/core/primary.yml new file mode 100644 index 00000000..e8d5e3d9 --- /dev/null +++ b/menu/pgcloner/core/primary.yml @@ -0,0 +1,31 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Register Project Name + shell: 'cat /var/plexguide/pgcloner.projectname' + register: pname + + - name: Register Role + shell: 'cat /var/plexguide/pgcloner.rolename' + register: prole + + - name: Register Project Version + shell: 'cat /var/plexguide/pgcloner.projectversion' + register: pversion + + - name: Check if Path Exists + stat: + path: '/opt/{{prole.stdout}}' + register: pathcheck + + - name: 'Transfer Image Variable' + shell: 'rm -rf /opt/{{prole.stdout}}' + when: pathcheck.stat.exists + + - name: Clone Role + git: + repo: 'https://github.com/MrDoobPG/{{pname.stdout}}' + dest: '/opt/{{prole.stdout}}' + version: '{{pversion.stdout}}' + force: yes diff --git a/menu/pgcloner/corev2/main.sh b/menu/pgcloner/corev2/main.sh new file mode 100644 index 00000000..d767f541 --- /dev/null +++ b/menu/pgcloner/corev2/main.sh @@ -0,0 +1,158 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# FUNCTIONS START ############################################################## +source /opt/plexguide/menu/functions/functions.sh + +rolename=$(cat /var/plexguide/pgcloner.rolename) +roleproper=$(cat /var/plexguide/pgcloner.roleproper) +projectname=$(cat /var/plexguide/pgcloner.projectname) +projectversion=$(cat /var/plexguide/pgcloner.projectversion) +startlink=$(cat /var/plexguide/pgcloner.startlink) + +mkdir -p "/opt/$rolename" + +initial() { + ansible-playbook "/opt/plexguide/menu/pgcloner/corev2/primary.yml" + echo "" + echo "💬 Pulling Update Files - Please Wait" + file="/opt/$rolename/place.holder" + waitvar=0 + while [ "$waitvar" == "0" ]; do + sleep .5 + if [ -e "$file" ]; then waitvar=1; fi + done + bash /opt/${rolename}/${startlink} +} + +custom() { + mkdir -p "/opt/$rolename" + ansible-playbook "/opt/plexguide/menu/pgcloner/corev2/personal.yml" + + echo "" + echo "💬 Pulling Update Files - Please Wait" + file="/opt/$rolename/place.holder" + waitvar=0 + while [ "$waitvar" == "0" ]; do + sleep .5 + if [ -e "$file" ]; then waitvar=1; fi + done + bash /opt/${rolename}/${startlink} +} + +mainbanner() { + clonerinfo=$(cat /var/plexguide/pgcloner.info) + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 $roleproper +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +$clonerinfo + +[1] Utilize $roleproper +[2] Utilize $roleproper - Personal (Forked) + +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + + read -p 'Type a Selection | Press [ENTER]: ' typed /var/plexguide/$rolename.user + echo "$branch" >/var/plexguide/$rolename.branch + pinterface + ;; + 2) + existcheck=$(git ls-remote --exit-code -h "https://github.com/$user/$projectname" | grep "$branch") + if [ "$existcheck" == "" ]; then + echo + read -p '💬 Exiting! Forked Version Does Not Exist! | Press [ENTER]: ' typed /tmp/output.info +mainbanner diff --git a/menu/pgcloner/corev2/personal.yml b/menu/pgcloner/corev2/personal.yml new file mode 100644 index 00000000..d447c4cc --- /dev/null +++ b/menu/pgcloner/corev2/personal.yml @@ -0,0 +1,26 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Register Role + shell: 'cat /var/plexguide/pgcloner.projectname' + register: pname + + - name: Register Role + shell: 'cat /var/plexguide/pgcloner.rolename' + register: prole + + - name: Register User - Personal + shell: 'cat /var/plexguide/{{prole.stdout}}.user' + register: user + + - name: Register Branch - Personal + shell: 'cat /var/plexguide/{{prole.stdout}}.branch' + register: branch + + - name: 'Cloning Personal Forked Role' + git: + repo: 'https://github.com/{{user.stdout}}/{{pname.stdout}}' + dest: '/opt/{{prole.stdout}}' + version: '{{branch.stdout}}' + force: yes diff --git a/menu/pgcloner/corev2/primary.yml b/menu/pgcloner/corev2/primary.yml new file mode 100644 index 00000000..e8d5e3d9 --- /dev/null +++ b/menu/pgcloner/corev2/primary.yml @@ -0,0 +1,31 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Register Project Name + shell: 'cat /var/plexguide/pgcloner.projectname' + register: pname + + - name: Register Role + shell: 'cat /var/plexguide/pgcloner.rolename' + register: prole + + - name: Register Project Version + shell: 'cat /var/plexguide/pgcloner.projectversion' + register: pversion + + - name: Check if Path Exists + stat: + path: '/opt/{{prole.stdout}}' + register: pathcheck + + - name: 'Transfer Image Variable' + shell: 'rm -rf /opt/{{prole.stdout}}' + when: pathcheck.stat.exists + + - name: Clone Role + git: + repo: 'https://github.com/MrDoobPG/{{pname.stdout}}' + dest: '/opt/{{prole.stdout}}' + version: '{{pversion.stdout}}' + force: yes diff --git a/menu/pgcloner/hetzner.sh b/menu/pgcloner/hetzner.sh new file mode 100644 index 00000000..bae78c95 --- /dev/null +++ b/menu/pgcloner/hetzner.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'hetzner' >/var/plexguide/pgcloner.rolename +echo 'HCloud (Hetzner)' >/var/plexguide/pgcloner.roleproper +echo 'Hetzner' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'hcloud.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 HCloud in conjuction with PGBlitz enables users to +deploy Hetzner Cloud Instance (VMs) within seconds" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/multihd.sh b/menu/pgcloner/multihd.sh new file mode 100644 index 00000000..e6355382 --- /dev/null +++ b/menu/pgcloner/multihd.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'multihd' >/var/plexguide/pgcloner.rolename +echo 'MultiHD' >/var/plexguide/pgcloner.roleproper +echo 'MultiHD' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'multihd.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 MultiHD enables to add multiple drives and mountpoints to MergerFS!" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/pgclone.sh b/menu/pgcloner/pgclone.sh new file mode 100644 index 00000000..b7c1db27 --- /dev/null +++ b/menu/pgcloner/pgclone.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'pgclone' >/var/plexguide/pgcloner.rolename +echo 'PG Clone' >/var/plexguide/pgcloner.roleproper +echo 'PGClone' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'pgclone.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 PG Clone utilizes RClone's Mounts + MergerFS's Union" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/pgpatrol.sh b/menu/pgcloner/pgpatrol.sh new file mode 100644 index 00000000..88a75ad1 --- /dev/null +++ b/menu/pgcloner/pgpatrol.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'pgpatrol' >/var/plexguide/pgcloner.rolename +echo 'PGPatrol' >/var/plexguide/pgcloner.roleproper +echo 'PGPatrol' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 PG Patrol can boot idle plex users, users utilizing multiple +ips (sharing the server), and much more!" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/pgpress.sh b/menu/pgcloner/pgpress.sh new file mode 100644 index 00000000..b56a85b7 --- /dev/null +++ b/menu/pgcloner/pgpress.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'pgpress' >/var/plexguide/pgcloner.rolename +echo 'PGPress' >/var/plexguide/pgcloner.roleproper +echo 'PGPress' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'pressmain.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 PGPress is a combined group of services that enables the user to +deploy their own wordpress websites; including the use of other multiple +instances!" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/pgshield.sh b/menu/pgcloner/pgshield.sh new file mode 100644 index 00000000..b71d87cd --- /dev/null +++ b/menu/pgcloner/pgshield.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'pgshield' >/var/plexguide/pgcloner.rolename +echo 'PGShield' >/var/plexguide/pgcloner.roleproper +echo 'PGShield' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'pgshield.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 PG Shield protects users by deploying adding Google +Authentication to all the containers for protection!" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/pgvault-protected.sh b/menu/pgcloner/pgvault-protected.sh new file mode 100644 index 00000000..7a16da79 --- /dev/null +++ b/menu/pgcloner/pgvault-protected.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'pgvault' >/var/plexguide/pgcloner.rolename +echo 'PG Vault' >/var/plexguide/pgcloner.roleproper +echo 'PGVault' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'pgvault-protected.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 Backup & Resrore is a combined group of services that utilizes the backup +and restore processes, which enables the safe storage and transport through +the use of Google Drive in a hasty and efficient manner!" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/pgvault.sh b/menu/pgcloner/pgvault.sh new file mode 100644 index 00000000..bacba343 --- /dev/null +++ b/menu/pgcloner/pgvault.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'pgvault' >/var/plexguide/pgcloner.rolename +echo 'PG Vault' >/var/plexguide/pgcloner.roleproper +echo 'PGVault' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'pgvault.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 PG Vault is a combined group of services that utilizes the backup +and restore processes, which enables the safe storage and transport through +the use of Google Drive in a hasty and efficient manner!" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgcloner/solo/pgui.sh b/menu/pgcloner/solo/pgui.sh new file mode 100644 index 00000000..62a658a6 --- /dev/null +++ b/menu/pgcloner/solo/pgui.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'pgui' >/var/plexguide/pgcloner.rolename +echo 'UI' >/var/plexguide/pgcloner.roleproper +echo 'BlitzUI' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion + +### START PROCESS +ansible-playbook /opt/plexguide/menu/pgcloner/core/primary.yml diff --git a/menu/pgcloner/traefik.sh b/menu/pgcloner/traefik.sh new file mode 100644 index 00000000..98bc9e54 --- /dev/null +++ b/menu/pgcloner/traefik.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### FILL OUT THIS AREA ### +echo 'traefik' >/var/plexguide/pgcloner.rolename +echo 'Traefik' >/var/plexguide/pgcloner.roleproper +echo 'Traefik' >/var/plexguide/pgcloner.projectname +echo 'v8.6' >/var/plexguide/pgcloner.projectversion +echo 'traefik.sh' >/var/plexguide/pgcloner.startlink + +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +echo "💬 Traefik is a modern HTTP reverse proxy and load balancer that makes +deploying microservices easy. It serves as a reverse proxy that enables a +user to mass obtain https (secure) certificates for all their containers" >/var/plexguide/pgcloner.info +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +### START PROCESS +bash /opt/plexguide/menu/pgcloner/corev2/main.sh diff --git a/menu/pgdnsswitcher/pgdnschanger.sh b/menu/pgdnsswitcher/pgdnschanger.sh new file mode 100644 index 00000000..4544bd0a --- /dev/null +++ b/menu/pgdnsswitcher/pgdnschanger.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# +# Title: PGBlitz (PG DNS chnager) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# Coder : MrDoob | Freelaancer Coder TechLead +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +tee <<-EOF + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ⌛ Verifiying PG DNS ( resolv.conf ) changer + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + +echo "Updating packages" +apt-get update -yqq >/dev/null +echo "Upgrading packages" +apt-get upgrade -yqq >/dev/null +echo "Dist-Upgrading packages" +apt-get dist-upgrade -yqq >/dev/null +echo "Autoremove old Updates" +apt-get autoremove -yqq >/dev/null +echo "install complete" + +tee <<-EOF + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 🚀 PG DNS ( resolv.conf ) changer + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + [1] Google DNS IPv4 + [2] Google DNS IPv4 + IPv6 + [3] Cloudflare DNS IPv4 + [4] Cloudflare DNS IPv4 + IPv6 + [5] OpenDNS IPv4 + [6] Comodo Secure DNS + + [Z] Exit + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + +# Standby +read -p 'Type a Number | Press [ENTER]: ' typed "0" and exit.user_input < "4" + retries: 99 + delay: 1 + + - fail: + msg: 'User Opted NOT to CONTINUE! Aborting!' + when: exit.user_input == "1" + ############################## REGISTER PROJECT + - pause: + prompt: "\nWhat is Your GitHub UserName (Case Sensitive)?" + register: project + when: exit.user_input == "2" + + - name: Store User Name + shell: "echo '{{project.user_input}}' > /var/plexguide/pgfork.project" + when: exit.user_input == "2" + ############################## REGISTER BRANCH + - pause: + prompt: "\nExample: Version-6 / mybranch / rclonefix\nPlease Update Your Branch-Release (Case Sensitive)" + register: version + when: exit.user_input == "2" + + - name: Store Project Link + shell: "echo '{{version.user_input}}' > /var/plexguide/pgfork.version" + when: exit.user_input == "2" + ############################################# END + - name: Reregister Project Link + shell: 'cat /var/plexguide/pgfork.project' + register: projectfinal + + - name: Reregister Project Link + shell: 'cat /var/plexguide/pgfork.version' + register: versionfinal + + - name: Clone Fork + git: + repo: 'https://github.com/{{projectfinal.stdout}}/PGBlitz.com' + dest: /opt/plexguide + version: '{{versionfinal.stdout}}' + force: yes + + - name: Note Fork Version + shell: "echo 'Personal Fork' > /var/plexguide/pg.number" diff --git a/menu/pggce/gcechecker.sh b/menu/pggce/gcechecker.sh new file mode 100644 index 00000000..672ca9dc --- /dev/null +++ b/menu/pggce/gcechecker.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +### NOTE THIS IS JUST A COPY - MAIN ONE SITE IN MAIN REPO - THIS IS JUST FOR INFO +file1="/dev/nvme0n1" +file2="/var/plexguide/gce.check" +gcheck=$(dnsdomainname | tail -c 10) +if [ -e "$file1" ] && [ ! -e "$file2" ] && [ "$gcheck" == ".internal" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📂 Google Cloud Feeder Edition SET! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +⚡ Google Cloud Instance Detected! + +⚠️ NOTE: Setting Up the NVME Drive For You! Please Wait! +⚠️ NOTE: Please don't close it ! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + apt-get install mdadm --no-install-recommends -yqq 2>&1 >>/dev/null + export DEBIAN_FRONTEND=noninteractive + #Check for NVME + lsblk | grep nvme | awk '{print $1}' >/var/plexguide/nvme.log + lsblk | grep nvme | awk '{print $1}' >/var/plexguide/nvmeraid.log + sed -i 's/nvme0n//g' /var/plexguide/nvmeraid.log + #Check for NVME + nvme="$(tail -n1 /var/plexguide/nvmeraid.log)" + + if [[ "$nvme" == "2" ]]; then + mdadm --create /dev/md0 --level=0 --raid-devices=2 /dev/nvme0n1 /dev/nvme0n2 + mkfs.ext4 -F /dev/md0 + mkdir -p /mnt + mount /dev/md0 /mnt + sed -i '$ a\/dev/md0 /mnt ext4 discard,defaults,nobarrier,nofail 0 0' /etc/fstab + chown -cR 1000:1000 /mnt + tune2fs -m 0 /dev/md0 + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✅ PASSED ! PG NVME RAID0 Creator with 2 NVMEs - finish" + echo "✅ PASSED ! HDD Space now :" "$(df -h /mnt/ --total --local -x tmpfs | grep 'total' | awk '{print $2}')" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + sleep 2 + elif [[ "$nvme" == "3" ]]; then + mdadm --create /dev/md0 --level=0 --raid-devices=3 /dev/nvme0n1 /dev/nvme0n2 /dev/nvme0n3 + mkfs.ext4 -F /dev/md0 + mkdir -p /mnt + mount /dev/md0 /mnt + sed -i '$ a\/dev/md0 /mnt ext4 discard,defaults,nobarrier,nofail 0 0' /etc/fstab + chown -cR 1000:1000 /mnt + tune2fs -m 0 /dev/md0 + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✅ PASSED ! PG NVME RAID0 Creator with 3 NVMEs - finish" + echo "✅ PASSED ! HDD Space now :" "$(df -h /mnt/ --total --local -x tmpfs | grep 'total' | awk '{print $2}')" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + sleep 2 + elif [[ "$nvme" == "4" ]]; then + mdadm --create /dev/md0 --level=0 --raid-devices=4 /dev/nvme0n1 /dev/nvme0n2 /dev/nvme0n3 /dev/nvme0n4 + mkfs.ext4 -F /dev/md0 + mkdir -p /mnt + mount /dev/md0 /mnt + sed -i '$ a\/dev/md0 /mnt ext4 discard,defaults,nobarrier,nofail 0 0' /etc/fstab + chown -cR 1000:1000 /mnt + tune2fs -m 0 /dev/md0 + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✅ PASSED ! PG NVME RAID0 Creator with 4 NVMEs - finish" + echo "✅ PASSED ! HDD Space now :" "$(df -h /mnt/ --total --local -x tmpfs | grep 'total' | awk '{print $2}')" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + sleep 2 + elif [[ "$nvme" == "1" ]]; then + sleep 3 + mkfs.ext4 -F /dev/nvme0n1 1>/dev/null 2>&1 + mount -o discard,defaults,nobarrier /dev/nvme0n1 /mnt + chmod a+w /mnt 1>/dev/null 2>&1 + echo UUID="$(blkid | grep nvme0n1 | cut -f2 -d'"')" /mnt ext4 discard,defaults,nobarrier,nofail 0 2 | tee -a /etc/fstab + + mkdir -p /nvme1 1>/dev/null 2>&1 + mkfs.ext4 -F /dev/nvme0n1 + mount -o discard,defaults,nobarrier /dev/nvme0n1 /nvme1 + chmod a+w /nvme1 1>/dev/null 2>&1 + echo UUID="$(blkid | grep nvme0n1 | cut -f2 -d'"')" /nvme1 ext4 discard,defaults,nobarrier,nofail 0 2 | tee -a /etc/fstab + else + echo "nothing to do" + fi + + touch /var/plexguide/gce.check + rm -rf /var/plexguide/gce.failed 1>/dev/null 2>&1 + rm -rf /var/plexguide/gce.false 1>/dev/null 2>&1 + rm -rf /var/plexguide/nvme.log 1>/dev/null 2>&1 + rm -rf /var/plexguide/nvmeraid.log 1>/dev/null 2>&1 + + echo "feeder" >/var/plexguide/pg.server.deploy + cat /var/plexguide/pg.edition >/var/plexguide/pg.edition.stored + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📂 GCE Harddrive Deployed! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +⚡ Automatically Setting PG Google Feeder Edition (GCE) + +⚠️ Please Wait! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + sleep 6 +elif [ ! -e "$file1" ] && [ ! -e "$file2" ] && [ "$gcheck" == ".internal" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📂 Google Cloud Feeder Edition Failed! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +⚡ Google Cloud Instance Detected, but you Failed to setup an NVME + drive per the wiki! This mistake only occurs on manual GCE + deployments. Most likely you setup an SSD instead! The install will + continue, but this will fail! Wipe the box and setup again with an + NVME Drive! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -p 'Press [ENTER] to Continue! ' typed /dev/null 2>&1 + rm -rf /var/plexguide/gce.false 1>/dev/null 2>&1 + rm -rf /var/plexguide/nvme.log 1>/dev/null 2>&1 + rm -rf /var/plexguide/nvmeraid.log 1>/dev/null 2>&1 +else + touch /var/plexguide/gce.false +fi diff --git a/menu/pgscan/LICENSE.md b/menu/pgscan/LICENSE.md new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/menu/pgscan/LICENSE.md @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/menu/pgscan/config.py b/menu/pgscan/config.py new file mode 100644 index 00000000..7445e23e --- /dev/null +++ b/menu/pgscan/config.py @@ -0,0 +1,373 @@ +import argparse +import json +import logging +import os +import sys +import uuid +from copy import copy + +logger = logging.getLogger("CONFIG") + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super( + Singleton, cls).__call__(*args, **kwargs) + + return cls._instances[cls] + + +class Config(object): + __metaclass__ = Singleton + + base_config = { + 'PLEX_USER': 'plex', + 'PLEX_SECTION_PATH_MAPPINGS': {}, + 'PLEX_SCANNER': '/usr/lib/plexmediaserver/Plex\\ Media\\ Scanner', + 'PLEX_SUPPORT_DIR': '/var/lib/plexmediaserver/Library/Application\ Support', + 'PLEX_LD_LIBRARY_PATH': '/usr/lib/plexmediaserver', + 'PLEX_DATABASE_PATH': '/var/lib/plexmediaserver/Library/Application Support/Plex Media Server' + '/Plug-in Support/Databases/com.plexapp.plugins.library.db', + 'PLEX_LOCAL_URL': 'http://localhost:32400', + 'PLEX_EMPTY_TRASH': False, + 'PLEX_EMPTY_TRASH_MAX_FILES': 100, + 'PLEX_EMPTY_TRASH_CONTROL_FILES': [], + 'PLEX_EMPTY_TRASH_ZERO_DELETED': False, + 'PLEX_WAIT_FOR_EXTERNAL_SCANNERS': True, + 'PLEX_ANALYZE_TYPE': 'basic', + 'PLEX_ANALYZE_DIRECTORY': True, + 'PLEX_TOKEN': '', + 'SERVER_IP': '0.0.0.0', + 'SERVER_PORT': 3467, + 'SERVER_PASS': uuid.uuid4().hex, + 'SERVER_PATH_MAPPINGS': {}, + 'SERVER_SCAN_DELAY': 180, + 'SERVER_MAX_FILE_CHECKS': 10, + 'SERVER_FILE_CHECK_DELAY': 60, + 'SERVER_FILE_EXIST_PATH_MAPPINGS': {}, + 'SERVER_ALLOW_MANUAL_SCAN': False, + 'SERVER_IGNORE_LIST': [], + 'SERVER_USE_SQLITE': False, + 'SERVER_SCAN_PRIORITIES': {}, + 'SERVER_SCAN_FOLDER_ON_FILE_EXISTS_EXHAUSTION': False, + 'RCLONE_RC_CACHE_EXPIRE': { + 'ENABLED': False, + 'FILE_EXISTS_TO_REMOTE_MAPPINGS': { + }, + 'RC_URL': 'http://localhost:5572' + }, + 'DOCKER_NAME': 'plex', + 'RUN_COMMAND_BEFORE_SCAN': '', + 'RUN_COMMAND_AFTER_SCAN': '', + 'USE_DOCKER': False, + 'USE_SUDO': True, + 'GDRIVE': { + 'CLIENT_ID': '', + 'CLIENT_SECRET': '', + 'POLL_INTERVAL': 60, + 'ENABLED': False, + 'TEAMDRIVE': False, + 'SCAN_EXTENSIONS': [], + 'IGNORE_PATHS': [] + } + } + + base_settings = { + 'config': { + 'argv': '--config', + 'env': 'PLEX_AUTOSCAN_CONFIG', + 'default': os.path.join(os.path.dirname(sys.argv[0]), 'config', 'config.json') + }, + 'logfile': { + 'argv': '--logfile', + 'env': 'PLEX_AUTOSCAN_LOGFILE', + 'default': os.path.join(os.path.dirname(sys.argv[0]), 'plex_autoscan.log') + }, + 'loglevel': { + 'argv': '--loglevel', + 'env': 'PLEX_AUTOSCAN_LOGLEVEL', + 'default': 'INFO' + }, + 'queuefile': { + 'argv': '--queuefile', + 'env': 'PLEX_AUTOSCAN_QUEUEFILE', + 'default': os.path.join(os.path.dirname(sys.argv[0]), 'queue.db') + }, + 'tokenfile': { + 'argv': '--tokenfile', + 'env': 'PLEX_AUTOSCAN_TOKENFILE', + 'default': os.path.join(os.path.dirname(sys.argv[0]), 'token.json') + }, + 'cachefile': { + 'argv': '--cachefile', + 'env': 'PLEX_AUTOSCAN_CACHEFILE', + 'default': os.path.join(os.path.dirname(sys.argv[0]), 'cache.db') + } + } + + def __init__(self): + """Initializes config""" + # Args and settings + self.args = self.parse_args() + self.settings = self.get_settings() + # Configs + self.configs = None + + @property + def default_config(self): + cfg = copy(self.base_config) + + # add example scan priorities + cfg['SERVER_SCAN_PRIORITIES'] = { + "0": [ + '/Movies/' + ], + "1": [ + '/TV/' + ], + "2": [ + '/Music/' + ] + } + + # add example section path mappings + cfg['PLEX_SECTION_PATH_MAPPINGS'] = { + '1': [ + '/Movies/' + ], + '2': [ + '/TV/' + ] + } + + # add example file trash control files + cfg['PLEX_EMPTY_TRASH_CONTROL_FILES'] = ['/mnt/unionfs/mounted.bin'] + + # add example server path mappings + cfg['SERVER_PATH_MAPPINGS'] = { + '/mnt/unionfs': [ + '/home/seed/media/fused' + ] + } + + # add example file exist path mappings + cfg['SERVER_FILE_EXIST_PATH_MAPPINGS'] = { + '/home/thompsons/plexdrive': [ + '/data' + ] + } + # add example server ignore list + cfg['SERVER_IGNORE_LIST'] = ['/.grab/', '.DS_Store', 'Thumbs.db'] + + # add example scan extensions to gdrive + cfg['GDRIVE']['SCAN_EXTENSIONS'] = ['webm', 'mkv', 'flv', 'vob', 'ogv', 'ogg', 'drc', 'gif', 'gifv', 'mng', + 'avi', 'mov', 'qt', 'wmv', 'yuv', 'rm', 'rmvb', 'asf', 'amv', 'mp4', 'm4p', + 'm4v', 'mpg', 'mp2', 'mpeg', 'mpe', 'mpv', 'm2v', 'm4v', 'svi', '3gp', + '3g2', 'mxf', 'roq', 'nsv', 'f4v', 'f4p', 'f4a', 'f4b', 'mp3', 'flac', 'ts'] + + # add example rclone file exists to remote mappings + cfg['RCLONE_RC_CACHE_EXPIRE']['FILE_EXISTS_TO_REMOTE_MAPPINGS'] = { + 'Media/': [ + '/home/thompsons/plexdrive/Media' + ] + } + + return cfg + + def __inner_upgrade(self, settings1, settings2, key=None, overwrite=False): + sub_upgraded = False + merged = copy(settings2) + + if isinstance(settings1, dict): + for k, v in settings1.items(): + # missing k + if k not in settings2: + merged[k] = v + sub_upgraded = True + if not key: + logger.info("Added %r config option: %s", + str(k), str(v)) + else: + logger.info("Added %r to config option %r: %s", + str(k), str(key), str(v)) + continue + + # iterate children + if isinstance(v, dict) or isinstance(v, list): + merged[k], did_upgrade = self.__inner_upgrade(settings1[k], settings2[k], key=k, + overwrite=overwrite) + sub_upgraded = did_upgrade if did_upgrade else sub_upgraded + elif settings1[k] != settings2[k] and overwrite: + merged = settings1 + sub_upgraded = True + elif isinstance(settings1, list) and key: + for v in settings1: + if v not in settings2: + merged.append(v) + sub_upgraded = True + logger.info("Added to config option %r: %s", + str(key), str(v)) + continue + + return merged, sub_upgraded + + def upgrade_settings(self, currents): + fields_env = {} + + # ENV gets priority: ENV > config.json + for name, data in self.base_config.items(): + if name in os.environ: + # Use JSON decoder to get same behaviour as config file + fields_env[name] = json.JSONDecoder().decode(os.environ[name]) + logger.info("Using ENV setting %s=%s", name, fields_env[name]) + + # Update in-memory config with environment settings + currents.update(fields_env) + + # Do inner upgrade + upgraded_settings, upgraded = self.__inner_upgrade( + self.base_config, currents) + return upgraded_settings, upgraded + + def load(self): + if not os.path.exists(self.settings['config']): + logger.warn("No config file found, creating default config.") + self.save(self.default_config) + + cfg = {} + with open(self.settings['config'], 'r') as fp: + cfg, upgraded = self.upgrade_settings(json.load(fp)) + + # Save config if upgraded + if upgraded: + self.save(cfg) + exit(0) + + self.configs = cfg + + def save(self, cfg, exitOnSave=True): + with open(self.settings['config'], 'w') as fp: + json.dump(cfg, fp, indent=2, sort_keys=True) + if exitOnSave: + logger.warn( + "Please configure/review config before running again: %r", + self.settings['config'] + ) + + if exitOnSave: + exit(0) + + def get_settings(self): + setts = {} + for name, data in self.base_settings.items(): + # Argrument priority: cmd < environment < default + try: + value = None + # Command line argument + if self.args[name]: + value = self.args[name] + logger.info("Using ARG setting %s=%s", name, value) + + # Envirnoment variable + elif data['env'] in os.environ: + value = os.environ[data['env']] + logger.info("Using ENV setting %s=%s" % ( + data['env'], + value + )) + + # Default + else: + value = data['default'] + logger.info("Using default setting %s=%s" % ( + data['argv'], + value + )) + + setts[name] = value + + except Exception: + logger.exception( + "Exception retrieving setting value: %r" % name) + + return setts + + # Parse command line arguments + def parse_args(self): + parser = argparse.ArgumentParser( + description=( + 'Script to assist sonarr/radarr with plex imports. Will only scan the folder \n' + 'that has been imported, instead of the whole library section.' + ), + formatter_class=argparse.RawTextHelpFormatter + ) + + # Mode + parser.add_argument('cmd', + choices=('sections', 'server', + 'authorize', 'update_sections'), + help=( + '"sections": prints plex sections\n' + '"server": starts the application\n' + '"authorize": authorize against a google account\n' + '"update_sections": update section mappings in config\n' + ) + ) + + # Config file + parser.add_argument(self.base_settings['config']['argv'], + nargs='?', + const=None, + help='Config file location (default: %s)' % self.base_settings[ + 'config']['default'] + ) + + # Log file + parser.add_argument(self.base_settings['logfile']['argv'], + nargs='?', + const=None, + help='Log file location (default: %s)' % self.base_settings[ + 'logfile']['default'] + ) + + # Queue file + parser.add_argument(self.base_settings['queuefile']['argv'], + nargs='?', + const=None, + help='Queue file location (default: %s)' % self.base_settings[ + 'queuefile']['default'] + ) + + # Token file + parser.add_argument(self.base_settings['tokenfile']['argv'], + nargs='?', + const=None, + help='Google token file location (default: %s)' % self.base_settings[ + 'tokenfile']['default'] + ) + + # Cache file + parser.add_argument(self.base_settings['cachefile']['argv'], + nargs='?', + const=None, + help='Google cache file location (default: %s)' % self.base_settings[ + 'cachefile']['default'] + ) + + # Logging level + parser.add_argument(self.base_settings['loglevel']['argv'], + choices=('WARN', 'INFO', 'DEBUG'), + help='Log level (default: %s)' % self.base_settings['loglevel']['default'] + ) + + # Print help by default if no arguments + if len(sys.argv) == 1: + parser.print_help() + + sys.exit(0) + + else: + return vars(parser.parse_args()) diff --git a/menu/pgscan/config/default.config b/menu/pgscan/config/default.config new file mode 100644 index 00000000..7a6ab83e --- /dev/null +++ b/menu/pgscan/config/default.config @@ -0,0 +1,103 @@ +{ + "DOCKER_NAME":"plex", + "GDRIVE":{ + "CLIENT_ID":"", + "CLIENT_SECRET":"", + "ENABLED":false, + "POLL_INTERVAL":60, + "IGNORE_PATHS": [], + "SCAN_EXTENSIONS":[ + "webm","mkv","flv","vob","ogv","ogg","drc","gif", + "gifv","mng","avi","mov","qt","wmv","yuv","rm", + "rmvb","asf","amv","mp4","m4p","m4v","mpg","mp2", + "mpeg","mpe","mpv","m2v","m4v","svi","3gp","3g2", + "mxf","roq","nsv","f4v","f4p","f4a","f4b","mp3", + "flac","ts" + ], + "TEAMDRIVE": false + }, + "PLEX_ANALYZE_DIRECTORY":true, + "PLEX_ANALYZE_TYPE":"basic", + "PLEX_DATABASE_PATH":"/opt/appdata/plex/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db", + "PLEX_EMPTY_TRASH":true, + "PLEX_EMPTY_TRASH_CONTROL_FILES":[ + "/mnt/unionfs/mounted.bin" + ], + "PLEX_EMPTY_TRASH_MAX_FILES":100, + "PLEX_EMPTY_TRASH_ZERO_DELETED":false, + "PLEX_LD_LIBRARY_PATH":"/usr/lib/plexmediaserver", + "PLEX_LOCAL_URL":"https://ipv4:32400", + "PLEX_SCANNER":"/usr/lib/plexmediaserver/Plex\\ Media\\ Scanner", + "PLEX_SECTION_PATH_MAPPINGS":{ + "1":[ + "/Movies/" + ], + "2":[ + "/TV/" + ], + "3":[ + "/Music/" + ] + }, + "PLEX_SUPPORT_DIR":"/var/lib/plexmediaserver/Library/Application\\ Support", + "PLEX_TOKEN":"{{token}}", + "PLEX_USER":"plex", + "PLEX_WAIT_FOR_EXTERNAL_SCANNERS":true, + "RCLONE_RC_CACHE_EXPIRE":{ + "ENABLED":false, + "FILE_EXISTS_TO_REMOTE_MAPPINGS": {}, + "MOUNT_FOLDER":"/mnt/rclone", + "RC_URL":"http://localhost:5572" + }, + "RUN_COMMAND_BEFORE_SCAN":"", + "RUN_COMMAND_AFTER_SCAN": "", + "SERVER_ALLOW_MANUAL_SCAN":false, + "SERVER_FILE_CHECK_DELAY":60, + "SERVER_MAX_FILE_CHECKS":10, + "SERVER_FILE_EXIST_PATH_MAPPINGS":{ + "/mnt/unionfs/Media":[ + "/data" + ] + }, + "SERVER_IGNORE_LIST":[ + "/.grab/", + ".DS_Store", + "Thumbs.db" + ], + "SERVER_IP":"0.0.0.0", + "SERVER_PASS":"{{token.stdout}}", + "SERVER_PATH_MAPPINGS":{ + "/data/Movies/":[ + "/movies/", + "/mnt/unionfs/Media/Movies/", + "My Drive/Media/Movies/" + ], + "/data/TV/":[ + "/tv/", + "/mnt/unionfs/Media/TV/", + "My Drive/Media/TV/" + ], + "/data/Music/":[ + "/music/", + "/mnt/unionfs/Media/Music/", + "My Drive/Media/Music/" + ] + }, + "SERVER_PORT":3468, + "SERVER_SCAN_DELAY":180, + "SERVER_SCAN_FOLDER_ON_FILE_EXISTS_EXHAUSTION":true, + "SERVER_SCAN_PRIORITIES":{ + "0":[ + "/TV/" + ], + "1":[ + "/Movies/" + ], + "2":[ + "/Music/" + ] + }, + "SERVER_USE_SQLITE":true, + "USE_DOCKER":true, + "USE_SUDO":false +} diff --git a/menu/pgscan/db.py b/menu/pgscan/db.py new file mode 100644 index 00000000..50fd2664 --- /dev/null +++ b/menu/pgscan/db.py @@ -0,0 +1,124 @@ +import logging +import os + +from peewee import DeleteQuery +from peewee import Model, SqliteDatabase, CharField, IntegerField + +import config + +logger = logging.getLogger("DB") + +# Config +conf = config.Config() + +db_path = conf.settings['queuefile'] +database = SqliteDatabase(db_path, threadlocals=True) + + +class BaseQueueModel(Model): + class Meta: + database = database + + +class QueueItemModel(BaseQueueModel): + scan_path = CharField(max_length=256, unique=True, null=False) + scan_for = CharField(max_length=64, null=False) + scan_section = IntegerField(null=False) + scan_type = CharField(max_length=64, null=False) + + +def create_database(db, db_path): + if not os.path.exists(db_path): + db.create_tables([QueueItemModel]) + logger.info("Created database tables") + + +def connect(db): + if not db.is_closed(): + return False + return db.connect() + + +def init(db, db_path): + if not os.path.exists(db_path): + create_database(db, db_path) + connect(db) + + +def get_next_item(): + item = None + try: + item = QueueItemModel.get() + except Exception: + # logger.exception("Exception getting first item to scan: ") + pass + return item + + +def exists_file_root_path(file_path): + items = get_all_items() + if '.' in file_path: + dir_path = os.path.dirname(file_path) + else: + dir_path = file_path + + for item in items: + if dir_path.lower() in item['scan_path'].lower(): + return True, item['scan_path'] + return False, None + + +def get_all_items(): + items = [] + try: + for item in QueueItemModel.select(): + items.append({'scan_path': item.scan_path, + 'scan_for': item.scan_for, + 'scan_type': item.scan_type, + 'scan_section': item.scan_section}) + except Exception: + logger.exception("Exception getting all items from database: ") + return None + return items + + +def get_queue_count(): + count = 0 + try: + count = QueueItemModel.select().count() + except Exception: + logger.exception("Exception getting queued item count from database: ") + return count + + +def remove_item(scan_path): + try: + return DeleteQuery(QueueItemModel).where(QueueItemModel.scan_path == scan_path).execute() + except Exception: + logger.exception("Exception deleting %r from database: ", scan_path) + return False + + +def add_item(scan_path, scan_for, scan_section, scan_type): + item = None + try: + return QueueItemModel.create(scan_path=scan_path, scan_for=scan_for, scan_section=scan_section, + scan_type=scan_type) + except AttributeError as ex: + return item + except Exception: + pass + # logger.exception("Exception adding %r to database: ", scan_path) + return item + + +def queued_count(): + try: + return QueueItemModel.select().count() + except Exception: + logger.exception("Exception retrieving queued count: ") + return 0 + + +# Init +init(database, db_path) diff --git a/menu/pgscan/gdrive.py b/menu/pgscan/gdrive.py new file mode 100644 index 00000000..6194a217 --- /dev/null +++ b/menu/pgscan/gdrive.py @@ -0,0 +1,303 @@ +import json +import logging +import os +from urllib import urlencode + +import backoff +import requests +from sqlitedict import SqliteDict + +import utils + +logger = logging.getLogger("GDRIVE") + + +class Gdrive: + def __init__(self, config, token_path, cache_path): + self.cfg = config + self.token_path = token_path + self.cache_path = cache_path + self.token = None + self.cache = None + + def first_run(self): + # token file + if not os.path.exists(self.token_path): + # token.json does not exist, lets do the first run auth process + print("Visit %s and authorize against the account you wish to use" % + self.authorize_url()) + auth_code = raw_input('Enter authorization code: ') + if self.first_access_token(auth_code) and self.token is not None: + self.dump_token() + else: + logger.error( + "Failed to authorize with the supplied client_id/client_secret/auth_code...") + return False + else: + self.token = utils.load_json(self.token_path) + + # cache file + self.cache = SqliteDict(self.cache_path, tablename='cache', encode=json.dumps, decode=json.loads, + autocommit=False) + return True + + def authorize_url(self): + payload = { + 'client_id': self.cfg['GDRIVE']['CLIENT_ID'], + 'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob', + 'response_type': 'code', + 'access_type': 'offline', + 'scope': 'https://www.googleapis.com/auth/drive' + } + url = 'https://accounts.google.com/o/oauth2/v2/auth?' + \ + urlencode(payload) + return url + + def first_access_token(self, auth_code): + logger.info("Requesting access token for auth code %r", auth_code) + payload = { + 'code': auth_code, + 'client_id': self.cfg['GDRIVE']['CLIENT_ID'], + 'client_secret': self.cfg['GDRIVE']['CLIENT_SECRET'], + 'grant_type': 'authorization_code', + 'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob', + } + success, resp, data = self._make_request('https://www.googleapis.com/oauth2/v4/token', data=payload, + headers={}, request_type='post') + if success and resp.status_code == 200: + logger.info("Retrieved first access token!") + self.token = data + self.token['page_token'] = '' + return True + else: + logger.error("Error retrieving first access_token:\n%s", data) + return False + + def refresh_access_token(self): + logger.debug("Renewing access token...") + payload = { + 'refresh_token': self.token['refresh_token'], + 'client_id': self.cfg['GDRIVE']['CLIENT_ID'], + 'client_secret': self.cfg['GDRIVE']['CLIENT_SECRET'], + 'grant_type': 'refresh_token', + } + success, resp, data = self._make_request('https://www.googleapis.com/oauth2/v4/token', data=payload, + headers={}, request_type='post') + if success and resp.status_code == 200 and 'access_token' in data: + logger.info("Renewed access token!") + + refresh_token = self.token['refresh_token'] + page_token = self.token['page_token'] + self.token = data + if 'refresh_token' not in self.token or not self.token['refresh_token']: + self.token['refresh_token'] = refresh_token + self.token['page_token'] = page_token + self.dump_token() + return True + else: + logger.error("Error renewing access token:\n%s", data) + return False + + def get_changes_first_page_token(self): + success, resp, data = self._make_request('https://www.googleapis.com/drive/v3/changes/startPageToken', + params={'supportsTeamDrives': self.cfg['GDRIVE']['TEAMDRIVE']}) + if success and resp.status_code == 200: + if 'startPageToken' not in data: + logger.error( + "Failed to retrieve startPageToken from returned startPageToken:\n%s", data) + return False + self.token['page_token'] = data['startPageToken'] + self.dump_token() + return True + else: + logger.error("Error retrieving first page token:\n%s", data) + return False + + def get_changes(self): + success, resp, data = self._make_request('https://www.googleapis.com/drive/v3/changes', + params={'pageToken': self.token['page_token'], 'pageSize': 1000, + 'includeRemoved': True, + 'includeTeamDriveItems': self.cfg['GDRIVE'][ + 'TEAMDRIVE'], + 'supportsTeamDrives': self.cfg['GDRIVE']['TEAMDRIVE'], + 'fields': 'changes(file(md5Checksum,mimeType,modifiedTime,' + 'name,parents,teamDriveId,trashed),' + 'fileId,removed,teamDrive(id,name),' + 'teamDriveId),newStartPageToken,nextPageToken'}) + if success and resp.status_code == 200: + # page token logic + if data is not None and 'nextPageToken' in data: + self.token['page_token'] = data['nextPageToken'] + self.dump_token() + elif data is not None and 'newStartPageToken' in data: + self.token['page_token'] = data['newStartPageToken'] + self.dump_token() + else: + logger.error("Unexpected response while polling for changes from page %s:\n%s", + str(self.token['page_token']), data) + return False, data + return True, data + else: + logger.error("Error getting page changes for page_token %r:\n%s", + self.token['page_token'], data) + return False, data + + def get_id_metadata(self, item_id, teamdrive_id=None): + # return cache from metadata if available + cached_metadata = self._get_cached_metdata(item_id) + if cached_metadata: + return True, cached_metadata + + # does item_id match teamdrive_id? + if teamdrive_id is not None and item_id == teamdrive_id: + success, resp, data = self._make_request( + 'https://www.googleapis.com/drive/v3/teamdrives/%s' % str(item_id)) + if success and resp.status_code == 200 and 'name' in data: + # we successfully retrieved this teamdrive info, lets place a mimeType key in the result + # so we know it needs to be cached + data['mimeType'] = 'application/vnd.google-apps.folder' + else: + # retrieve file metadata + success, resp, data = self._make_request('https://www.googleapis.com/drive/v3/files/%s' % str(item_id), + params={ + 'supportsTeamDrives': self.cfg['GDRIVE']['TEAMDRIVE'], + 'fields': 'id,md5Checksum,mimeType,modifiedTime,name,parents,' + 'trashed,teamDriveId'}) + if success and resp.status_code == 200: + return True, data + else: + logger.error( + "Error retrieving metadata for item %r:\n%s", item_id, data) + return False, data + + def get_id_file_paths(self, item_id, teamdrive_id=None): + file_paths = [] + added_to_cache = 0 + + try: + def get_item_paths(obj_id, path, paths, new_cache_entries, teamdrive_id=None): + success, obj = self.get_id_metadata(obj_id, teamdrive_id) + if not success: + return new_cache_entries + + teamdrive_id = teamdrive_id if 'teamDriveId' not in obj else obj['teamDriveId'] + + # add item object to cache if we know its not from cache + if 'mimeType' in obj: + # we know this is a new item fetched from the api, because the cache does not store this field + self.add_item_to_cache( + obj['id'], obj['name'], [] if 'parents' not in obj else obj['parents']) + new_cache_entries += 1 + + if path.strip() == '': + path = obj['name'] + else: + path = os.path.join(obj['name'], path) + + if 'parents' in obj and obj['parents']: + for parent in obj['parents']: + new_cache_entries += get_item_paths( + parent, path, paths, new_cache_entries, teamdrive_id) + + if (not obj or 'parents' not in obj or not obj['parents']) and len(path): + paths.append(path) + return new_cache_entries + return new_cache_entries + + added_to_cache += get_item_paths(item_id, '', + file_paths, added_to_cache, teamdrive_id) + if added_to_cache: + logger.debug("Dumping cache due to new entries!") + self.dump_cache() + + if len(file_paths): + return True, file_paths + else: + return False, file_paths + + except Exception: + logger.exception( + "Exception retrieving filepaths for '%s': ", item_id) + + return False, [] + + # cache + def add_item_to_cache(self, item_id, item_name, item_parents): + if item_id not in self.cache: + logger.info("Added '%s' to cache: %s", item_id, item_name) + self.cache[item_id] = {'name': item_name, 'parents': item_parents} + return + + def remove_item_from_cache(self, item_id): + if self.cache.pop(item_id, None): + return True + return False + + # dump jsons + def dump_token(self): + utils.dump_json(self.token_path, self.token) + return + + def dump_cache(self): + self.cache.commit() + return + + ############################################################ + # INTERNALS + ############################################################ + + # cache + def _get_cached_metdata(self, item_id): + if item_id in self.cache: + return self.cache[item_id] + return None + + # requests + @backoff.on_predicate(backoff.expo, lambda x: not x[0] and ( + 'error' in x[2] and 'code' in x[2]['error'] and x[2]['error']['code'] != 401), max_tries=8) + def _make_request(self, url, headers=None, data=None, params=None, request_type='get'): + refreshed_token = False + + while True: + if headers is None and self.token: + auth_headers = { + 'Authorization': 'Bearer %s' % self.token['access_token'], + } + else: + auth_headers = {} + + resp = None + if request_type == 'get': + resp = requests.get(url, params=params, headers=headers if headers is not None else auth_headers, + timeout=30) + elif request_type == 'post': + resp = requests.post(url, data=data, headers=headers if headers is not None else auth_headers, + timeout=30) + else: + return False, resp, { + 'error': {'code': 401, 'message': 'Invalid request_type was supplied to _make_request'}} + + # response logic + try: + data = resp.json() + except ValueError: + logger.exception("Exception while decoding response from Google Drive for data:\n%s\nTraceback: ", + resp.text) + return False, resp, { + 'error': {'code': resp.status_code, 'message': 'Failed to json decode Google Drive response'}} + + if 'error' in data and 'code' in data['error'] and ( + 'message' in data['error'] and 'Invalid Credentials' in data['error']['message']): + # the token has expired. + if not refreshed_token: + refreshed_token = True + self.refresh_access_token() + continue + else: + # attempt was already made to refresh token + return False, resp, data + + if resp.status_code == 200: + return True, resp, data + else: + return False, resp, data diff --git a/menu/pgscan/gpl3.tracking b/menu/pgscan/gpl3.tracking new file mode 100644 index 00000000..924dc607 --- /dev/null +++ b/menu/pgscan/gpl3.tracking @@ -0,0 +1,5 @@ +PG Project Modifications: +Adopting Configuration Files + +Project Info: +Original Project Info: https://github.com/l3uddz/plexautoscan diff --git a/menu/pgscan/pgscan.sh b/menu/pgscan/pgscan.sh new file mode 100644 index 00000000..f71c1b7a --- /dev/null +++ b/menu/pgscan/pgscan.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# KEY VARIABLE RECALL & EXECUTION +mkdir -p /var/plexguide/pgscan + +# FUNCTIONS START ############################################################## + +# FIRST FUNCTION +variable() { + file="$1" + if [ ! -e "$file" ]; then echo "$2" >$1; fi +} + +deploycheck() { + dcheck=$(systemctl status pgscan | grep "\(running\)\>" | grep "\") + if [ "$dcheck" != "" ]; then + dstatus="✅ DEPLOYED" + else dstatus="⚠️ NOT DEPLOYED"; fi +} + +plexcheck() { + pcheck=$(docker ps | grep "\") + if [ "$pcheck" == "" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - Plex is Not Installed or Running! Exiting! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -p 'Confirm Info | PRESS [ENTER] ' typed = config['SERVER_MAX_FILE_CHECKS']: + logger.warning( + "File '%s' exhausted all available checks, aborting scan request.", check_path) + # remove item from database if sqlite is enabled + if config['SERVER_USE_SQLITE']: + if db.remove_item(path): + logger.info("Removed '%s' from database", path) + time.sleep(1) + else: + logger.error("Failed removing '%s' from database", path) + return + + else: + logger.info("File '%s' did not exist on check %d of %d, checking again in %s seconds.", check_path, + checks, + config['SERVER_MAX_FILE_CHECKS'], + config['SERVER_FILE_CHECK_DELAY']) + time.sleep(config['SERVER_FILE_CHECK_DELAY']) + # send rclone cache clear if enabled + if config['RCLONE_RC_CACHE_EXPIRE']['ENABLED']: + utils.rclone_rc_clear_cache(config, check_path) + + # build plex scanner command + if os.name == 'nt': + final_cmd = '"%s" --scan --refresh --section %s --directory "%s"' \ + % (config['PLEX_SCANNER'], str(section), scan_path) + else: + cmd = 'export LD_LIBRARY_PATH=' + config['PLEX_LD_LIBRARY_PATH'] + ';' + if not config['USE_DOCKER']: + cmd += 'export PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR=' + \ + config['PLEX_SUPPORT_DIR'] + ';' + cmd += config['PLEX_SCANNER'] + ' --scan --refresh --section ' + str(section) + ' --directory ' + cmd_quote( + scan_path) + + if config['USE_DOCKER']: + final_cmd = 'docker exec -u %s -i %s bash -c %s' % \ + (cmd_quote(config['PLEX_USER']), cmd_quote( + config['DOCKER_NAME']), cmd_quote(cmd)) + elif config['USE_SUDO']: + final_cmd = 'sudo -u %s bash -c %s' % ( + config['PLEX_USER'], cmd_quote(cmd)) + else: + final_cmd = cmd + + # invoke plex scanner + priority = utils.get_priority(config, scan_path) + logger.debug( + "Waiting for turn in the scan request backlog with priority: %d", priority) + + lock.acquire(priority) + try: + logger.info("Scan request is now being processed") + # wait for existing scanners being ran by plex + if config['PLEX_WAIT_FOR_EXTERNAL_SCANNERS']: + scanner_name = os.path.basename( + config['PLEX_SCANNER']).replace('\\', '') + if not utils.wait_running_process(scanner_name): + logger.warning( + "There was a problem waiting for existing '%s' process(s) to finish, aborting scan.", scanner_name) + # remove item from database if sqlite is enabled + if config['SERVER_USE_SQLITE']: + if db.remove_item(path): + logger.info("Removed '%s' from database", path) + time.sleep(1) + else: + logger.error( + "Failed removing '%s' from database", path) + return + else: + logger.info("No '%s' processes were found.", scanner_name) + + # run external command before scan if supplied + if len(config['RUN_COMMAND_BEFORE_SCAN']) > 2: + logger.info("Running external command: %r", + config['RUN_COMMAND_BEFORE_SCAN']) + utils.run_command(config['RUN_COMMAND_BEFORE_SCAN']) + logger.info("Finished running external command") + + # begin scan + logger.info("Starting Plex Scanner") + logger.debug(final_cmd) + utils.run_command(final_cmd.encode("utf-8")) + logger.info("Finished scan!") + + # remove item from database if sqlite is enabled + if config['SERVER_USE_SQLITE']: + if db.remove_item(path): + logger.info("Removed '%s' from database", path) + time.sleep(1) + logger.info("There is %d queued items remaining...", + db.queued_count()) + else: + logger.error("Failed removing '%s' from database", path) + + # empty trash if configured + if config['PLEX_EMPTY_TRASH'] and config['PLEX_TOKEN'] and config['PLEX_EMPTY_TRASH_MAX_FILES']: + logger.info("Checking deleted item count in 10 seconds...") + time.sleep(10) + + # check deleted item count, don't proceed if more than this value + deleted_items = get_deleted_count(config) + if deleted_items > config['PLEX_EMPTY_TRASH_MAX_FILES']: + logger.warning("There were %d deleted files, skipping emptying trash for section %s", deleted_items, + section) + elif deleted_items == -1: + logger.error( + "Could not determine deleted item count, aborting emptying trash") + elif not config['PLEX_EMPTY_TRASH_ZERO_DELETED'] and not deleted_items and scan_type != 'Upgrade': + logger.info( + "Skipping emptying trash as there were no deleted items") + else: + logger.info( + "Emptying trash to clear %d deleted items", deleted_items) + empty_trash(config, str(section)) + + # analyze movie/episode + if config['PLEX_ANALYZE_TYPE'].lower() != 'off' and not scan_path_is_directory: + logger.debug("Sleeping 10 seconds before sending analyze request") + time.sleep(10) + analyze_item(config, path) + + # run external command after scan if supplied + if len(config['RUN_COMMAND_AFTER_SCAN']) > 2: + logger.info("Running external command: %r", + config['RUN_COMMAND_AFTER_SCAN']) + utils.run_command(config['RUN_COMMAND_AFTER_SCAN']) + logger.info("Finished running external command") + + except Exception: + logger.exception( + "Unexpected exception occurred while processing: '%s'", scan_path) + finally: + lock.release() + return + + +def show_sections(config): + if os.name == 'nt': + final_cmd = '""%s" --list"' % config['PLEX_SCANNER'] + else: + cmd = 'export LD_LIBRARY_PATH=' + config['PLEX_LD_LIBRARY_PATH'] + ';' + if not config['USE_DOCKER']: + cmd += 'export PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR=' + \ + config['PLEX_SUPPORT_DIR'] + ';' + cmd += config['PLEX_SCANNER'] + ' --list' + + if config['USE_DOCKER']: + final_cmd = 'docker exec -u %s -it %s bash -c %s' % ( + cmd_quote(config['PLEX_USER']), cmd_quote(config['DOCKER_NAME']), cmd_quote(cmd)) + elif config['USE_SUDO']: + final_cmd = 'sudo -u %s bash -c "%s"' % (config['PLEX_USER'], cmd) + else: + final_cmd = cmd + logger.info("Using Plex Scanner") + logger.debug(final_cmd) + os.system(final_cmd) + + +def analyze_item(config, scan_path): + if not os.path.exists(config['PLEX_DATABASE_PATH']): + logger.info( + "Could not analyze '%s' because plex database could not be found?", scan_path) + return + # get files metadata_item_id + metadata_item_ids = get_file_metadata_ids(config, scan_path) + if metadata_item_ids is None or not len(metadata_item_ids): + logger.info( + "Aborting analyze of '%s' because could not find any metadata_item_id for it", scan_path) + return + metadata_item_id = ','.join(str(x) for x in metadata_item_ids) + + # build plex analyze command + analyze_type = 'analyze-deeply' if config['PLEX_ANALYZE_TYPE'].lower( + ) == 'deep' else 'analyze' + if os.name == 'nt': + final_cmd = '"%s" --%s --item %s' % ( + config['PLEX_SCANNER'], analyze_type, metadata_item_id) + else: + cmd = 'export LD_LIBRARY_PATH=' + config['PLEX_LD_LIBRARY_PATH'] + ';' + if not config['USE_DOCKER']: + cmd += 'export PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR=' + \ + config['PLEX_SUPPORT_DIR'] + ';' + cmd += config['PLEX_SCANNER'] + ' --' + \ + analyze_type + ' --item ' + metadata_item_id + + if config['USE_DOCKER']: + final_cmd = 'docker exec -u %s -i %s bash -c %s' % \ + (cmd_quote(config['PLEX_USER']), cmd_quote( + config['DOCKER_NAME']), cmd_quote(cmd)) + elif config['USE_SUDO']: + final_cmd = 'sudo -u %s bash -c %s' % ( + config['PLEX_USER'], cmd_quote(cmd)) + else: + final_cmd = cmd + + # begin analysis + logger.info("Starting %s analysis of metadata_item(s): %s", + 'deep' if config['PLEX_ANALYZE_TYPE'].lower() == 'deep' else 'basic', metadata_item_id) + logger.debug(final_cmd) + utils.run_command(final_cmd.encode("utf-8")) + logger.info("Finished %s analysis of metadata_item(s): %s!", + 'deep' if config['PLEX_ANALYZE_TYPE'].lower() == 'deep' else 'basic', metadata_item_id) + + +def get_file_metadata_ids(config, file_path): + results = [] + media_item_row = None + + try: + with sqlite3.connect(config['PLEX_DATABASE_PATH']) as conn: + conn.row_factory = sqlite3.Row + with closing(conn.cursor()) as c: + # query media_parts to retrieve media_item_row for this file + for x in range(5): + media_item_row = c.execute( + "SELECT * FROM media_parts WHERE file=?", (file_path,)).fetchone() + if media_item_row: + logger.info( + "Found row in media_parts where file = '%s' after %d/5 tries!", file_path, x + 1) + break + else: + logger.error("Could not locate record in media_parts where file = '%s', %d/5 attempts...", + file_path, x + 1) + time.sleep(10) + + if not media_item_row: + logger.error( + "Could not locate record in media_parts where file = '%s' after 5 tries...", file_path) + return None + + media_item_id = media_item_row['media_item_id'] + if media_item_id and int(media_item_id): + # query db to find metadata_item_id + metadata_item_id = \ + c.execute("SELECT * FROM media_items WHERE id=?", (int(media_item_id),)).fetchone()[ + 'metadata_item_id'] + if metadata_item_id and int(metadata_item_id): + logger.debug("Found metadata_item_id for '%s': %d", + file_path, int(metadata_item_id)) + + # query db to find parent_id of metadata_item_id + if config['PLEX_ANALYZE_DIRECTORY']: + parent_id = \ + c.execute("SELECT * FROM metadata_items WHERE id=?", + (int(metadata_item_id),)).fetchone()['parent_id'] + if not parent_id or not int(parent_id): + # could not find parent_id of this item, likely its a movie... + # lets just return the metadata_item_id + return [int(metadata_item_id)] + logger.debug( + "Found parent_id for '%s': %d", file_path, int(parent_id)) + + # if mode is basic, single parent_id is enough + if config['PLEX_ANALYZE_TYPE'].lower() == 'basic': + return [int(parent_id)] + + # lets find all metadata_item_id's with this parent_id for use with deep analyze + metadata_items = c.execute("SELECT * FROM metadata_items WHERE parent_id=?", + (int(parent_id),)).fetchall() + if not metadata_items: + # could not find any results, lets just return metadata_item_id + return [int(metadata_item_id)] + + for row in metadata_items: + if row['id'] and int(row['id']) and int(row['id']) not in results: + results.append(int(row['id'])) + + logger.debug( + "Found media_item_id's for '%s': %s", file_path, results) + logger.info("Found %d media_item_id's to deep analyze for: '%s'", len( + results), file_path) + else: + # user had PLEX_ANALYZE_DIRECTORY as False - lets just scan the single metadata_item_id + results.append(int(metadata_item_id)) + + except Exception as ex: + logger.exception( + "Exception finding metadata_item_id for '%s': ", file_path) + return results + + +def empty_trash(config, section): + for control in config['PLEX_EMPTY_TRASH_CONTROL_FILES']: + if not os.path.exists(control): + logger.info( + "Skipping emptying trash as control file does not exist: '%s'", control) + return + + if len(config['PLEX_EMPTY_TRASH_CONTROL_FILES']): + logger.info("Control file(s) exist!") + + for x in range(5): + try: + resp = requests.put('%s/library/sections/%s/emptyTrash?X-Plex-Token=%s' % ( + config['PLEX_LOCAL_URL'], section, config['PLEX_TOKEN']), data=None, timeout=30) + if resp.status_code == 200: + logger.info( + "Trash cleared for section %s after %d/5 tries!", section, x + 1) + break + else: + logger.error("Unexpected response status_code for empty trash request: %d, %d/5 attempts...", + resp.status_code, x + 1) + time.sleep(10) + except Exception as ex: + logger.exception( + "Exception sending empty trash for section %s, %d/5 attempts: ", section, x + 1) + time.sleep(10) + return + + +def get_deleted_count(config): + try: + with sqlite3.connect(config['PLEX_DATABASE_PATH']) as conn: + with closing(conn.cursor()) as c: + deleted_metadata = \ + c.execute( + 'SELECT count(*) FROM metadata_items WHERE deleted_at IS NOT NULL').fetchone()[0] + deleted_media_parts = \ + c.execute( + 'SELECT count(*) FROM media_parts WHERE deleted_at IS NOT NULL').fetchone()[0] + + return int(deleted_metadata) + int(deleted_media_parts) + + except Exception as ex: + logger.exception( + "Exception retrieving deleted item count from database: ") + return -1 diff --git a/menu/pgscan/requirements.txt b/menu/pgscan/requirements.txt new file mode 100644 index 00000000..1fce3839 --- /dev/null +++ b/menu/pgscan/requirements.txt @@ -0,0 +1,6 @@ +Flask ~= 0.12.2 +psutil ~= 5.4.0 +requests ~= 2.20.0 +peewee ~= 2.10.1 +backoff ~= 1.5.0 +sqlitedict ~= 1.5.0 diff --git a/menu/pgscan/scan.py b/menu/pgscan/scan.py new file mode 100644 index 00000000..b4696f81 --- /dev/null +++ b/menu/pgscan/scan.py @@ -0,0 +1,591 @@ +#!/usr/bin/env python2.7 +from gdrive import Gdrive +import utils +import plex +import db +import json +import logging +import os +import sys +import time +from copy import copy +from logging.handlers import RotatingFileHandler + +from flask import Flask +from flask import abort +from flask import jsonify +from flask import request + +# Get config +import config +import threads + +############################################################ +# INIT +############################################################ + +# Logging +logFormatter = logging.Formatter( + '%(asctime)24s - %(levelname)8s - %(name)9s [%(thread)5d]: %(message)s') +rootLogger = logging.getLogger() +rootLogger.setLevel(logging.INFO) + +# Decrease modules logging +logging.getLogger('requests').setLevel(logging.ERROR) +logging.getLogger('werkzeug').setLevel(logging.ERROR) +logging.getLogger('peewee').setLevel(logging.ERROR) +logging.getLogger('urllib3.connectionpool').setLevel(logging.ERROR) +logging.getLogger('sqlitedict').setLevel(logging.ERROR) + +# Console logger, log to stdout instead of stderr +consoleHandler = logging.StreamHandler(sys.stdout) +consoleHandler.setFormatter(logFormatter) +rootLogger.addHandler(consoleHandler) + +# Load initial config +conf = config.Config() + +# File logger +fileHandler = RotatingFileHandler( + conf.settings['logfile'], + maxBytes=1024 * 1024 * 5, + backupCount=5, + encoding='utf-8' +) +fileHandler.setFormatter(logFormatter) +rootLogger.addHandler(fileHandler) + +# Set configured log level +rootLogger.setLevel(conf.settings['loglevel']) +# Load config file +conf.load() + +# Scan logger +logger = rootLogger.getChild("AUTOSCAN") + +# Multiprocessing +thread = threads.Thread() +scan_lock = threads.PriorityLock() +resleep_paths = [] + +# local imports + +google = None + + +############################################################ +# QUEUE PROCESSOR +############################################################ + + +def queue_processor(): + logger.info("Starting queue processor in 10 seconds") + try: + time.sleep(10) + logger.info("Queue processor started") + db_scan_requests = db.get_all_items() + items = 0 + for db_item in db_scan_requests: + thread.start(plex.scan, args=[conf.configs, scan_lock, db_item['scan_path'], db_item['scan_for'], + db_item['scan_section'], + db_item['scan_type'], resleep_paths]) + items += 1 + time.sleep(2) + logger.info("Restored %d scan requests from database", items) + except Exception: + logger.exception( + "Exception while processing scan requests from database: ") + return + + +############################################################ +# FUNCS +############################################################ + + +def start_scan(path, scan_for, scan_type): + section = utils.get_plex_section(conf.configs, path) + if section <= 0: + return False + else: + logger.debug("Using section id: %d for '%s'", section, path) + + if conf.configs['SERVER_USE_SQLITE']: + db_exists, db_file = db.exists_file_root_path(path) + if not db_exists and db.add_item(path, scan_for, section, scan_type): + logger.info("Added '%s' to database, proceeding with scan", path) + else: + logger.info( + "Already processing '%s' from same folder, aborting adding an extra scan request to the queue", db_file) + resleep_paths.append(db_file) + return False + + thread.start(plex.scan, args=[ + conf.configs, scan_lock, path, scan_for, section, scan_type, resleep_paths]) + return True + + +def start_queue_reloader(): + thread.start(queue_processor) + return True + + +def start_google_monitor(): + thread.start(thread_google_monitor) + return True + + +############################################################ +# GOOGLE DRIVE +############################################################ + +def process_google_changes(changes): + global google + file_paths = [] + + # convert changes to file paths list + for change in changes: + logger.debug("Processing Google change: %s", change) + if 'file' in change and 'fileId' in change: + # dont consider trashed/removed events for processing + if ('trashed' in change['file'] and change['file']['trashed']) or ( + 'removed' in change and change['removed']): + # remove item from cache + if google.remove_item_from_cache(change['fileId']): + logger.info("Removed '%s' from cache: %s", + change['fileId'], change['file']['name']) + continue + + # we always want to add changes to the cache so renames etc can be reflected inside the cache + google.add_item_to_cache(change['fileId'], change['file']['name'], + [] if 'parents' not in change['file'] else change['file']['parents']) + + # dont process folder events + if 'mimeType' in change['file'] and 'vnd.google-apps.folder' in change['file']['mimeType']: + # ignore this change as we dont want to scan folders + continue + + # get this files paths + success, item_paths = google.get_id_file_paths(change['fileId'], + change['file']['teamDriveId'] if 'teamDriveId' in change[ + 'file'] else None) + if success and len(item_paths): + file_paths.extend(item_paths) + elif 'teamDrive' in change and 'teamDriveId' in change: + # this is a teamdrive change + # dont consider trashed/removed events for processing + if 'removed' in change and change['removed']: + # remove item from cache + if google.remove_item_from_cache(change['teamDriveId']): + logger.info("Removed teamDrive '%s' from cache: %s", change['teamDriveId'], + change['teamDrive']['name'] if 'name' in change['teamDrive'] else 'Unknown teamDrive') + continue + + if 'id' in change['teamDrive'] and 'name' in change['teamDrive']: + # we always want to add changes to the cache so renames etc can be reflected inside the cache + google.add_item_to_cache( + change['teamDrive']['id'], change['teamDrive']['name'], []) + continue + + # always dump the cache after running changes + google.dump_cache() + + # remove files that are not of an allowed extension type + removed_rejected_extensions = 0 + for file_path in copy(file_paths): + if not utils.allowed_scan_extension(file_path, conf.configs['GDRIVE']['SCAN_EXTENSIONS']): + # this file did not have an allowed extension, remove it + file_paths.remove(file_path) + removed_rejected_extensions += 1 + + if removed_rejected_extensions: + logger.info("Ignored %d file(s) from Google Drive changes for disallowed file extensions", + removed_rejected_extensions) + + # remove files that are in the ignore paths list + removed_rejected_paths = 0 + for file_path in copy(file_paths): + for ignore_path in conf.configs['GDRIVE']['IGNORE_PATHS']: + if file_path.lower().startswith(ignore_path.lower()): + # this file was from an ignored path, remove it + file_paths.remove(file_path) + removed_rejected_paths += 1 + + if removed_rejected_paths: + logger.info("Ignored %d file(s) from Google Drive changes for disallowed file paths", + removed_rejected_paths) + + # remove files that already exist in the plex database + removed_rejected_exists = utils.remove_files_exist_in_plex_database( + file_paths, conf.configs['PLEX_DATABASE_PATH']) + + if removed_rejected_exists: + logger.info("Ignored %d file(s) from Google Drive changes for already being in Plex!", + removed_rejected_exists) + + # process the file_paths list + if len(file_paths): + logger.info("Proceeding with scan of %d file(s) from Google Drive changes: %s", len( + file_paths), file_paths) + + # loop each file, remapping and starting a scan thread + for file_path in file_paths: + final_path = utils.map_pushed_path(conf.configs, file_path) + start_scan(final_path, 'Google Drive', 'Download') + + return True + + +def thread_google_monitor(): + global google + + logger.info("Starting Google Drive changes monitor in 30 seconds...") + time.sleep(30) + + # load access tokens + google = Gdrive( + conf.configs, conf.settings['tokenfile'], conf.settings['cachefile']) + if not google.first_run(): + logger.error("Failed to retrieve Google Drive access tokens...") + exit(1) + else: + logger.info("Google Drive access tokens were successfully loaded") + + try: + + logger.info("Google Drive changes monitor started") + while True: + if not google.token['page_token']: + # we have no page_token, likely this is first run, lets retrieve a starting page token + if not google.get_changes_first_page_token(): + logger.error( + "Failed to retrieve starting Google Drive changes page token...") + return + else: + logger.info( + "Retrieved starting Google Drive changes page token: %s", google.token['page_token']) + time.sleep(conf.configs['GDRIVE']['POLL_INTERVAL']) + + # get page changes + changes = [] + changes_attempts = 0 + + while True: + try: + success, page = google.get_changes() + changes_attempts = 0 + except Exception: + changes_attempts += 1 + logger.exception( + "Exception occurred while polling Google Drive for changes on page %s on attempt %d/12: ", + str(google.token['page_token']), changes_attempts) + + if changes_attempts < 12: + logger.warning( + "Sleeping for 5 minutes before trying to poll Google Drive for changes again...") + time.sleep(60 * 5) + continue + else: + logger.error("Failed to poll Google Drive changes after 12 consecutive attempts, " + "aborting...") + return + + if not success: + logger.error("Failed to retrieve Google Drive changes for page: %s, aborting...", + str(google.token['page_token'])) + return + else: + # successfully retrieved some changes + if 'changes' in page: + changes.extend(page['changes']) + + # page logic + if page is not None and 'nextPageToken' in page: + # there are more pages to retrieve + logger.debug( + "There are more Google Drive changes pages to retrieve, retrieving next page...") + continue + elif page is not None and 'newStartPageToken' in page: + # there are no more pages to retrieve + break + else: + logger.error("There was an unexpected outcome when polling Google Drive for changes, " + "aborting future polls...") + return + + # process changes + if len(changes): + logger.info( + "There's %d Google Drive change(s) to process", len(changes)) + process_google_changes(changes) + + # sleep before polling for changes again + time.sleep(conf.configs['GDRIVE']['POLL_INTERVAL']) + + except Exception: + logger.exception("Fatal Exception occurred while monitoring Google Drive for changes, page = %s: ", + google.token['page_token']) + + +############################################################ +# SERVER +############################################################ + +app = Flask(__name__) +app.config['JSON_AS_ASCII'] = False + + +@app.route("/api/%s" % conf.configs['SERVER_PASS'], methods=['GET', 'POST']) +def api_call(): + data = {} + try: + if request.content_type == 'application/json': + data = request.get_json(silent=True) + elif request.method == 'POST': + data = request.form.to_dict() + else: + data = request.args.to_dict() + + # verify cmd was supplied + if 'cmd' not in data: + logger.error("Unknown %s API call from %r", + request.method, request.remote_addr) + return jsonify({'error': 'No cmd parameter was supplied'}) + else: + logger.info("Client %s API call from %r, type: %s", + request.method, request.remote_addr, data['cmd']) + + # process cmds + cmd = data['cmd'].lower() + if cmd == 'queue_count': + # queue count + if not conf.configs['SERVER_USE_SQLITE']: + # return error if SQLITE db is not enabled + return jsonify({'error': 'SERVER_USE_SQLITE must be enabled'}) + return jsonify({'queue_count': db.get_queue_count()}) + + else: + # unknown cmd + return jsonify({'error': 'Unknown cmd: %s' % cmd}) + + except Exception: + logger.exception("Exception parsing %s API call from %r: ", + request.method, request.remote_addr) + + return jsonify({'error': 'Unexpected error occurred, check logs...'}) + + +@app.route("/%s" % conf.configs['SERVER_PASS'], methods=['GET']) +def manual_scan(): + if not conf.configs['SERVER_ALLOW_MANUAL_SCAN']: + return abort(401) + page = """ + + + Plex Autoscan + + + + +
+
+
+

Plex Autoscan

+ +

Path to scan

+
+
+ +
+ +
+
+ +
+
+
+ + """ + return page, 200 + + +@app.route("/%s" % conf.configs['SERVER_PASS'], methods=['POST']) +def client_pushed(): + if request.content_type == 'application/json': + data = request.get_json(silent=True) + else: + data = request.form.to_dict() + + if not data: + logger.error("Invalid scan request from: %r", request.remote_addr) + abort(400) + logger.debug("Client %r request dump:\n%s", request.remote_addr, + json.dumps(data, indent=4, sort_keys=True)) + + if ('eventType' in data and data['eventType'] == 'Test') or ('EventType' in data and data['EventType'] == 'Test'): + logger.info("Client %r made a test request, event: '%s'", + request.remote_addr, 'Test') + elif 'eventType' in data and data['eventType'] == 'Manual': + logger.info("Client %r made a manual scan request for: '%s'", + request.remote_addr, data['filepath']) + final_path = utils.map_pushed_path(conf.configs, data['filepath']) + # ignore this request? + ignore, ignore_match = utils.should_ignore(final_path, conf.configs) + if ignore: + logger.info("Ignored scan request for '%s' because '%s' was matched from SERVER_IGNORE_LIST", final_path, + ignore_match) + return "Ignoring scan request because %s was matched from your SERVER_IGNORE_LIST" % ignore_match + if start_scan(final_path, 'Manual', 'Manual'): + return """ + + + Plex Autoscan + + + + +
+
+
+

Plex Autoscan

+

Success

+ +
+
+
+ + """.format(final_path) + else: + return """ + + + Plex Autoscan + + + + +
+
+
+

Plex Autoscan

+

Error

+ +
+
+
+ + """.format(data['filepath']) + + elif 'series' in data and 'eventType' in data and data['eventType'] == 'Rename' and 'path' in data['series']: + # sonarr Rename webhook + logger.info("Client %r scan request for series: '%s', event: '%s'", request.remote_addr, data['series']['path'], + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + final_path = utils.map_pushed_path( + conf.configs, data['series']['path']) + start_scan(final_path, 'Sonarr', + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + + elif 'movie' in data and 'eventType' in data and data['eventType'] == 'Rename' and 'folderPath' in data['movie']: + # radarr Rename webhook + logger.info("Client %r scan request for movie: '%s', event: '%s'", request.remote_addr, + data['movie']['folderPath'], + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + final_path = utils.map_pushed_path( + conf.configs, data['movie']['folderPath']) + start_scan(final_path, 'Radarr', + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + + elif 'movie' in data and 'movieFile' in data and 'folderPath' in data['movie'] and \ + 'relativePath' in data['movieFile'] and 'eventType' in data: + # radarr download/upgrade webhook + path = os.path.join(data['movie']['folderPath'], + data['movieFile']['relativePath']) + logger.info("Client %r scan request for movie: '%s', event: '%s'", request.remote_addr, path, + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + final_path = utils.map_pushed_path(conf.configs, path) + start_scan(final_path, 'Radarr', + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + + elif 'series' in data and 'episodeFile' in data and 'eventType' in data: + # sonarr download/upgrade webhook + path = os.path.join(data['series']['path'], + data['episodeFile']['relativePath']) + logger.info("Client %r scan request for series: '%s', event: '%s'", request.remote_addr, path, + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + final_path = utils.map_pushed_path(conf.configs, path) + start_scan(final_path, 'Sonarr', + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + + elif 'artist' in data and 'trackFile' in data and 'eventType' in data: + # lidarr download/upgrade webhook + path = os.path.join(data['artist']['path'], + data['trackFile']['relativePath']) + logger.info("Client %r scan request for album track: '%s', event: '%s'", request.remote_addr, path, + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + final_path = utils.map_pushed_path(conf.configs, path) + start_scan(final_path, 'Lidarr', + "Upgrade" if ('isUpgrade' in data and data['isUpgrade']) else data['eventType']) + + else: + logger.error("Unknown scan request from: %r", request.remote_addr) + abort(400) + + return "OK" + + +############################################################ +# MAIN +############################################################ + +if __name__ == "__main__": + logger.info(""" +PG Scan Started! +""") + if conf.args['cmd'] == 'sections': + plex.show_sections(conf.configs) + + exit(0) + elif conf.args['cmd'] == 'update_sections': + plex.updateSectionMappings(conf) + elif conf.args['cmd'] == 'authorize': + if not conf.configs['GDRIVE']['ENABLED']: + logger.error( + "You must enable the ENABLED setting in the GDRIVE config section...") + exit(1) + else: + google = Gdrive( + conf.configs, conf.settings['tokenfile'], conf.settings['cachefile']) + if not google.first_run(): + logger.error("Failed to retrieve access tokens...") + exit(1) + else: + logger.info("Access tokens were successfully retrieved!") + exit(0) + + elif conf.args['cmd'] == 'server': + if conf.configs['SERVER_USE_SQLITE']: + start_queue_reloader() + + if conf.configs['GDRIVE']['ENABLED']: + if not os.path.exists(conf.settings['tokenfile']): + logger.error( + "You must authorize your Google Drive account with the authorize option...") + exit(1) + start_google_monitor() + + logger.info("Starting server: http://%s:%d/%s", + conf.configs['SERVER_IP'], + conf.configs['SERVER_PORT'], + conf.configs['SERVER_PASS'] + ) + app.run(host=conf.configs['SERVER_IP'], + port=conf.configs['SERVER_PORT'], debug=False, use_reloader=False) + logger.info("Server stopped") + exit(0) + else: + logger.error("Unknown command...") + exit(1) diff --git a/menu/pgscan/system/pgscan.service b/menu/pgscan/system/pgscan.service new file mode 100644 index 00000000..0cf07582 --- /dev/null +++ b/menu/pgscan/system/pgscan.service @@ -0,0 +1,17 @@ +# /etc/systemd/system/pgscan.service + +[Unit] +Description=Plex Autoscan +After=network-online.target unionfs.service + +[Service] +User=1000 +Group=1000 +Type=simple +WorkingDirectory=/opt/appdata/pgscan/ +ExecStart=/opt/appdata/pgscan/scan.py server --loglevel=INFO +Restart=always +RestartSec=10 + +[Install] +WantedBy=default.target diff --git a/menu/pgscan/threads.py b/menu/pgscan/threads.py new file mode 100644 index 00000000..0310f096 --- /dev/null +++ b/menu/pgscan/threads.py @@ -0,0 +1,61 @@ +import Queue +import copy +import threading + + +class PriorityLock: + def __init__(self): + self._is_available = True + self._mutex = threading.Lock() + self._waiter_queue = Queue.PriorityQueue() + + def acquire(self, priority=0): + self._mutex.acquire() + # First, just check the lock. + if self._is_available: + self._is_available = False + self._mutex.release() + return True + event = threading.Event() + self._waiter_queue.put((priority, event)) + self._mutex.release() + event.wait() + # When the event is triggered, we have the lock. + return True + + def release(self): + self._mutex.acquire() + # Notify the next thread in line, if any. + try: + _, event = self._waiter_queue.get_nowait() + except Queue.Empty: + self._is_available = True + else: + event.set() + self._mutex.release() + + +class Thread: + def __init__(self): + self.threads = [] + + def start(self, target, name=None, args=None, track=False): + thread = threading.Thread( + target=target, name=name, args=args if args else []) + thread.daemon = True + thread.start() + if track: + self.threads.append(thread) + return thread + + def join(self): + for thread in copy.copy(self.threads): + thread.join() + self.threads.remove(thread) + return + + def kill(self): + for thread in copy.copy(self.threads): + thread.kill() + self.threads.remove(thread) + return diff --git a/menu/pgscan/utils.py b/menu/pgscan/utils.py new file mode 100644 index 00000000..b75d228f --- /dev/null +++ b/menu/pgscan/utils.py @@ -0,0 +1,243 @@ +import json +import logging +import os +import sqlite3 +import subprocess +import sys +import time +from contextlib import closing +from copy import copy + +import requests + +try: + from urlparse import urljoin +except ImportError: + from urllib.parse import urljoin + +import psutil + +logger = logging.getLogger("UTILS") + + +def get_plex_section(config, path): + for section, mappings in config['PLEX_SECTION_PATH_MAPPINGS'].items(): + for mapping in mappings: + if mapping.lower() in path.lower(): + return int(section) + logger.error("Unable to map '%s' to a section id....", path) + return -1 + + +def map_pushed_path(config, path): + for mapped_path, mappings in config['SERVER_PATH_MAPPINGS'].items(): + for mapping in mappings: + if mapping in path: + logger.debug("Mapping '%s' to '%s'", mapping, mapped_path) + return path.replace(mapping, mapped_path) + return path + + +def map_pushed_path_file_exists(config, path): + for mapped_path, mappings in config['SERVER_FILE_EXIST_PATH_MAPPINGS'].items(): + for mapping in mappings: + if mapping in path: + logger.debug("Mapping file check path '%s' to '%s'", + mapping, mapped_path) + return path.replace(mapping, mapped_path) + return path + + +def map_file_exists_path_for_rclone(config, path): + for mapped_path, mappings in config['RCLONE_RC_CACHE_EXPIRE']['FILE_EXISTS_TO_REMOTE_MAPPINGS'].items(): + for mapping in mappings: + if mapping in path: + logger.debug( + "Mapping file check path '%s' to '%s' for rclone cache clear", mapping, mapped_path) + return path.replace(mapping, mapped_path) + return path + + +def is_process_running(process_name): + try: + for process in psutil.process_iter(): + if process.name().lower() == process_name.lower(): + return True, process + + return False, None + except psutil.ZombieProcess: + return False, None + except Exception: + logger.exception( + "Exception checking for process: '%s': ", process_name) + return False, None + + +def wait_running_process(process_name): + try: + running, process = is_process_running(process_name) + while running and process: + logger.info("'%s' is running, pid: %d, cmdline: %r. Checking again in 60 seconds...", process.name(), + process.pid, process.cmdline()) + time.sleep(60) + running, process = is_process_running(process_name) + + return True + + except Exception: + logger.exception("Exception waiting for process: '%s'", process_name()) + + return False + + +def run_command(command): + process = subprocess.Popen( + command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + while True: + output = str(process.stdout.readline()).lstrip( + 'b').replace('\\n', '').strip() + if process.poll() is not None: + break + if output and len(output) >= 8: + logger.info(output) + + rc = process.poll() + return rc + + +def should_ignore(file_path, config): + for item in config['SERVER_IGNORE_LIST']: + if item.lower() in file_path.lower(): + return True, item + + return False, None + + +def remove_item_from_list(item, from_list): + while item in from_list: + from_list.pop(from_list.index(item)) + return + + +def get_priority(config, scan_path): + try: + for priority, paths in config['SERVER_SCAN_PRIORITIES'].items(): + for path in paths: + if path.lower() in scan_path.lower(): + logger.debug("Using priority %d for path '%s'", + int(priority), scan_path) + return int(priority) + logger.debug("Using default priority 0 for path '%s'", scan_path) + except Exception: + logger.exception( + "Exception determining priority to use for '%s': ", scan_path) + return 0 + + +def rclone_rc_clear_cache(config, scan_path): + try: + rclone_rc_url = urljoin( + config['RCLONE_RC_CACHE_EXPIRE']['RC_URL'], 'cache/expire') + + cache_clear_path = map_file_exists_path_for_rclone( + config, scan_path).lstrip(os.path.sep) + logger.debug("Top level cache_clear_path: '%s'", cache_clear_path) + + while True: + last_clear_path = cache_clear_path + cache_clear_path = os.path.dirname(cache_clear_path) + if cache_clear_path == last_clear_path or not len(cache_clear_path): + # is the last path we tried to clear, the same as this path, if so, abort + logger.error("Aborting rclone cache clear for '%s' due to directory level exhaustion, last level: '%s'", + scan_path, last_clear_path) + return False + else: + last_clear_path = cache_clear_path + + # send cache clear request + logger.info("Sending rclone cache clear for: '%s'", + cache_clear_path) + try: + resp = requests.post(rclone_rc_url, json={ + 'remote': cache_clear_path}, timeout=120) + if '{' in resp.text and '}' in resp.text: + data = resp.json() + if 'error' in data: + logger.info( + "Failed to clear rclone cache for '%s': %s", cache_clear_path, data['error']) + continue + elif ('status' in data and 'message' in data) and data['status'] == 'ok': + logger.info( + "Successfully cleared rclone cache for '%s'", cache_clear_path) + return True + + # abort on unexpected response (no json response, no error/status & message in returned json + logger.error("Unexpected rclone cache clear response from %s while trying to clear '%s': %s", + rclone_rc_url, cache_clear_path, resp.text) + break + + except Exception: + logger.exception("Exception sending rclone cache clear to %s for '%s': ", rclone_rc_url, + cache_clear_path) + break + + except Exception: + logger.exception( + "Exception clearing rclone directory cache for '%s': ", scan_path) + return False + + +def load_json(file_path): + if os.path.sep not in file_path: + file_path = os.path.join(os.path.dirname(sys.argv[0]), file_path) + + with open(file_path, 'r') as fp: + return json.load(fp) + + +def dump_json(file_path, obj, processing=True): + if os.path.sep not in file_path: + file_path = os.path.join(os.path.dirname(sys.argv[0]), file_path) + + with open(file_path, 'w') as fp: + if processing: + json.dump(obj, fp, indent=2, sort_keys=True) + else: + json.dump(obj, fp) + return + + +def remove_files_exist_in_plex_database(file_paths, plex_db_path): + removed_items = 0 + try: + if plex_db_path and os.path.exists(plex_db_path): + with sqlite3.connect(plex_db_path) as conn: + conn.row_factory = sqlite3.Row + with closing(conn.cursor()) as c: + for file_path in copy(file_paths): + # check if file exists in plex + file_name = os.path.basename(file_path) + logger.debug( + "Checking if '%s' exists in the plex database at '%s'", file_name, plex_db_path) + found_item = c.execute("SELECT * FROM media_parts WHERE file LIKE ?", ('%' + file_name,)) \ + .fetchone() + if found_item: + logger.debug( + "'%s' was found in the plex media_parts table", file_name) + file_paths.remove(file_path) + removed_items += 1 + + except Exception: + logger.exception( + "Exception checking if %s exists in the plex database: ", file_paths) + return removed_items + + +def allowed_scan_extension(file_path, extensions): + check_path = file_path.lower() + for ext in extensions: + if check_path.endswith(ext.lower()): + logger.debug("'%s' had allowed extension: %s", file_path, ext) + return True + logger.debug("'%s' did not have an allowed extension", file_path) + return False diff --git a/menu/pgstage/pgstage.yml b/menu/pgstage/pgstage.yml new file mode 100644 index 00000000..2d034fb5 --- /dev/null +++ b/menu/pgstage/pgstage.yml @@ -0,0 +1,10 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: 'Cloning PGBlitz' + git: + repo: 'https://github.com/MrDoobPG/Install' + dest: '/opt/pgstage' + version: 'master' + force: yes diff --git a/menu/pgtrakt/LICENSE b/menu/pgtrakt/LICENSE new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/menu/pgtrakt/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/menu/pgtrakt/config.json.backup b/menu/pgtrakt/config.json.backup new file mode 100644 index 00000000..7a1303e7 --- /dev/null +++ b/menu/pgtrakt/config.json.backup @@ -0,0 +1,111 @@ +{ + "core": { + "debug": false + }, + "automatic": { + "movies": { + "anticipated": 3, + "boxoffice": 10, + "interval": 24, + "popular": 3, + "trending": 2 + }, + "shows": { + "anticipated": 10, + "interval": 48, + "popular": 1, + "trending": 2 + } + }, + "notifications": { + "verbose": false + }, + "filters": { + "shows": { + "allowed_countries": [ + "us", + "gb", + "ca" + ], + "allowed_languages": [], + "blacklisted_min_runtime": 15, + "blacklisted_min_year": 2010, + "blacklisted_max_year": 2019, + "blacklisted_genres": [ + "animation", + "game-show", + "talk-show", + "home-and-garden", + "children", + "reality", + "anime", + "news", + "documentary", + "special-interest" + ], + "blacklisted_networks": [ + "twitch", + "youtube", + "nickelodeon", + "hallmark", + "reelzchannel", + "disney", + "cnn", + "cbbc", + "the movie network", + "teletoon", + "cartoon network", + "espn", + "fox sports", + "yahoo!" + ], + "disabled_for": [], + }, + "movies": { + "allowed_countries": [ + "us", + "gb", + "ca" + ], + "allowed_languages": [], + "blacklisted_genres": [ + "documentary", + "music", + "short", + "sporting-event", + "film-noir", + "fan-film" + ], + "blacklist_title_keywords": [ + "untitled", + "ufc" + ], + "blacklisted_min_runtime": 35, + "blacklisted_min_year": 2000, + "blacklisted_max_year": 2019, + "allowed_countries": [ + "us", + "gb", + "ca" + ], + "disabled_for": [], + } + }, + "radarr": { + "api_key": "", + "profile": "HD-1080p", + "url": "http://localhost:7878/", + "root_folder": "/movies/" + }, + "sonarr": { + "api_key": "", + "profile": "HD-1080p", + "url": "http://localhost:8989/", + "root_folder": "/tv/", + "tags": {} + }, + "trakt": { + "client_id": "", + "client_secret": "" + } +} diff --git a/menu/pgtrakt/config.json.sample b/menu/pgtrakt/config.json.sample new file mode 100644 index 00000000..d740df97 --- /dev/null +++ b/menu/pgtrakt/config.json.sample @@ -0,0 +1,102 @@ +{ + "core": { + "debug": false + }, + "automatic": { + "movies": { + "anticipated": 3, + "boxoffice": 10, + "interval": 24, + "popular": 3, + "trending": 2 + }, + "shows": { + "anticipated": 10, + "interval": 48, + "popular": 1, + "trending": 2 + } + }, + "notifications": { + "verbose": false + }, + "filters": { + "shows": { + "allowed_countries": [ + "us", + "gb", + "ca" + ], + "allowed_languages": [], + "blacklisted_min_runtime": 15, + "blacklisted_min_year": 1980, + "blacklisted_max_year": 2019, + "blacklisted_genres": [ + "animation", + "game-show", + "news", + "special-interest" + ], + "blacklisted_networks": [ + "twitch", + "youtube", + "reelzchannel", + "cbbc", + "teletoon", + "espn", + "fox sports", + "yahoo!" + ], + "blacklisted_tvdb_ids": [], + "disabled_for": [] + }, + "movies": { + "allowed_countries": [ + "us", + "gb", + "ca" + ], + "allowed_languages": [], + "blacklisted_genres": [ + "short", + "sporting-event", + "film-noir", + "fan-film" + ], + "blacklist_title_keywords": [ + "untitled", + "ufc" + ], + "blacklisted_min_runtime": 30, + "blacklisted_min_year": 1970, + "blacklisted_max_year": 2020, + "blacklisted_tmdb_ids": [], + "allowed_countries": [ + "us", + "gb", + "ca" + ], + "disabled_for": [] + } + }, + "core": { + "debug": false + }, + "radarr": { + "api_key": "{{rapi.stdout}}", + "profile": "{{rprofile.stdout}}", + "url": "http://0.0.0.0:7878", + "root_folder": "{{rpath.stdout}}" + }, + "sonarr": { + "api_key": "{{sapi.stdout}}", + "profile": "{{sprofile.stdout}}", + "url": "http://0.0.0.0:8989", + "root_folder": "{{spath.stdout}}", + "tags": {} + }, + "trakt": { + "client_id": "{{client.stdout}}", + "client_secret": "{{secret.stdout}}" + } + } diff --git a/menu/pgtrakt/gpl3.tracking b/menu/pgtrakt/gpl3.tracking new file mode 100644 index 00000000..118f630e --- /dev/null +++ b/menu/pgtrakt/gpl3.tracking @@ -0,0 +1,5 @@ +PG Project Modifications: +Adopting Configuration Files + +Project Info: +Original Project Info: https://github.com/l3uddz/traktarr diff --git a/menu/pgtrakt/helpers/misc.py b/menu/pgtrakt/helpers/misc.py new file mode 100644 index 00000000..6543d68d --- /dev/null +++ b/menu/pgtrakt/helpers/misc.py @@ -0,0 +1,86 @@ +from copy import copy + +from misc.log import logger + +log = logger.get_logger(__name__) + + +def get_response_dict(response, key_field=None, key_value=None): + found_response = None + try: + if isinstance(response, list): + if not key_field or not key_value: + found_response = response[0] + else: + for result in response: + if isinstance(result, dict) and key_field in result and result[key_field] == key_value: + found_response = result + break + + if not found_response: + log.error( + "Unable to find a result with key %s where the value is %s", key_field, key_value) + + elif isinstance(response, dict): + found_response = response + else: + log.error("Unexpected response instance type of %s for %s", + type(response).__name__, response) + + except Exception: + log.exception("Exception determining response for %s: ", response) + return found_response + + +def backoff_handler(details): + log.warning("Backing off {wait:0.1f} seconds afters {tries} tries " + "calling function {target} with args {args} and kwargs " + "{kwargs}".format(**details)) + + +def dict_merge(dct, merge_dct): + for k, v in merge_dct.items(): + import collections + + if k in dct and isinstance(dct[k], dict) and isinstance(merge_dct[k], collections.Mapping): + dict_merge(dct[k], merge_dct[k]) + else: + dct[k] = merge_dct[k] + + return dct + + +def unblacklist_genres(genre, blacklisted_genres): + genres = genre.split(',') + for allow_genre in genres: + if allow_genre in blacklisted_genres: + blacklisted_genres.remove(allow_genre) + return + + +def allowed_genres(genre, object_type, trakt_object): + allowed_object = False + genres = genre.split(',') + + for item in genres: + if item.lower() in trakt_object[object_type]['genres']: + allowed_object = True + break + return allowed_object + + +def sorted_list(original_list, list_type, sort_key, reverse=True): + prepared_list = copy(original_list) + for item in prepared_list: + if not item[list_type][sort_key]: + if sort_key == 'released' or sort_key == 'first_aired': + item[list_type][sort_key] = "" + else: + item[list_type][sort_key] = 0 + + return sorted(prepared_list, key=lambda k: k[list_type][sort_key], reverse=reverse) + + +# reference: https://stackoverflow.com/a/16712886 +def substring_after(s, delim): + return s.partition(delim)[2] diff --git a/menu/pgtrakt/helpers/radarr.py b/menu/pgtrakt/helpers/radarr.py new file mode 100644 index 00000000..32b6a917 --- /dev/null +++ b/menu/pgtrakt/helpers/radarr.py @@ -0,0 +1,55 @@ +from misc.log import logger + +log = logger.get_logger(__name__) + + +def movies_to_tmdb_dict(radarr_movies): + movies = {} + try: + for tmp in radarr_movies: + if 'tmdbId' not in tmp: + log.debug("Could not handle movie: %s", tmp['title']) + continue + movies[tmp['tmdbId']] = tmp + return movies + except Exception: + log.exception("Exception processing Radarr movies to TMDB dict: ") + return None + + +def remove_existing_movies(radarr_movies, trakt_movies, callback=None): + new_movies_list = [] + + if not radarr_movies or not trakt_movies: + log.error("Inappropriate parameters were supplied") + return None + + try: + # turn radarr movies result into a dict with tmdb id as keys + processed_movies = movies_to_tmdb_dict(radarr_movies) + if not processed_movies: + return None + + # loop list adding to movies that do not already exist + for tmp in trakt_movies: + if 'movie' not in tmp or 'ids' not in tmp['movie'] or 'tmdb' not in tmp['movie']['ids']: + log.debug( + "Skipping movie because it did not have required fields: %s", tmp) + if callback: + callback('movie', tmp) + continue + # check if movie exists in processed_movies + if tmp['movie']['ids']['tmdb'] in processed_movies: + log.debug("Removing existing movie: %s", tmp['movie']['title']) + if callback: + callback('movie', tmp) + continue + + new_movies_list.append(tmp) + + log.debug("Filtered %d Trakt movies to %d movies that weren't already in Radarr", len(trakt_movies), + len(new_movies_list)) + return new_movies_list + except Exception: + log.exception("Exception removing existing movies from Trakt list: ") + return None diff --git a/menu/pgtrakt/helpers/rating.py b/menu/pgtrakt/helpers/rating.py new file mode 100644 index 00000000..53c6be2e --- /dev/null +++ b/menu/pgtrakt/helpers/rating.py @@ -0,0 +1,33 @@ +from misc.log import logger +import json +import requests + +log = logger.get_logger(__name__) + + +def get_rating(apikey, movie): + imdbID = movie['movie']['ids']['imdb'] + if(imdbID): + log.debug("Requesting ratings from OMDB for %s (%d) | Genres: %s | Country: %s | imdbID: %s", movie['movie']['title'], movie['movie']['year'], + ', '.join(movie['movie']['genres']), movie['movie']['country'].upper(), imdbID) + r = requests.get('http://www.omdbapi.com/?i=' + + imdbID + '&apikey=' + apikey) + if(r.status_code == 200): + log.debug("Successfully requested ratings from OMDB for %s (%d) | Genres: %s | Country: %s | imdbID: %s", + movie['movie']['title'], movie['movie']['year'], + ', '.join(movie['movie']['genres']), movie['movie']['country'].upper(), imdbID) + for source in json.loads(r.text)["Ratings"]: + if(source['Source'] == 'Rotten Tomatoes'): + log.debug("Rotten Tomatoes shows rating: %s for %s (%d) | Genres: %s | Country: %s | imdbID: %s ", source['Value'], movie['movie']['title'], movie['movie']['year'], + ', '.join(movie['movie']['genres']), movie['movie']['country'].upper(), imdbID) + return int(source['Value'].split('%')[0]) + else: + log.debug("Error encountered when requesting ratings from OMDB for %s (%d) | Genres: %s | Country: %s | imdbID: %s", + movie['movie']['title'], movie['movie']['year'], + ', '.join(movie['movie']['genres']), movie['movie']['country'].upper(), imdbID) + else: + log.debug("Skipping %s (%d) | Genres: %s | Country: %s as it does not have an imdbID", + movie['movie']['title'], movie['movie']['year'], + ', '.join(movie['movie']['genres']), movie['movie']['country'].upper()) + + return -1 diff --git a/menu/pgtrakt/helpers/sonarr.py b/menu/pgtrakt/helpers/sonarr.py new file mode 100644 index 00000000..60a03b1d --- /dev/null +++ b/menu/pgtrakt/helpers/sonarr.py @@ -0,0 +1,89 @@ +from misc.log import logger + +log = logger.get_logger(__name__) + + +def series_tag_id_from_network(profile_tags, network_tags, network): + try: + tags = [] + for tag_name, tag_networks in network_tags.items(): + for tag_network in tag_networks: + if tag_network.lower() in network.lower() and tag_name.lower() in profile_tags: + log.debug("Using %s tag for network: %s", + tag_name, network) + tags.append(profile_tags[tag_name.lower()]) + if tags: + return tags + except Exception: + log.exception( + "Exception determining tag to use for network %s: ", network) + return None + + +def readable_tag_from_ids(profile_tag_ids, chosen_tag_ids): + try: + if not chosen_tag_ids: + return None + + tags = [] + for tag_name, tag_id in profile_tag_ids.items(): + if tag_id in chosen_tag_ids: + tags.append(tag_name) + if tags: + return tags + except Exception: + log.exception( + "Exception building readable tag name list from ids %s: ", chosen_tag_ids) + return None + + +def series_to_tvdb_dict(sonarr_series): + series = {} + try: + for tmp in sonarr_series: + if 'tvdbId' not in tmp: + log.debug("Could not handle show: %s", tmp['title']) + continue + series[tmp['tvdbId']] = tmp + return series + except Exception: + log.exception("Exception processing Sonarr shows to TVDB dict: ") + return None + + +def remove_existing_series(sonarr_series, trakt_series, callback=None): + new_series_list = [] + + if not sonarr_series or not trakt_series: + log.error("Inappropriate parameters were supplied") + return None + + try: + # turn sonarr series result into a dict with tvdb id as keys + processed_series = series_to_tvdb_dict(sonarr_series) + if not processed_series: + return None + + # loop list adding to series that do not already exist + for tmp in trakt_series: + if 'show' not in tmp or 'ids' not in tmp['show'] or 'tvdb' not in tmp['show']['ids']: + log.debug( + "Skipping show because it did not have required fields: %s", tmp) + if callback: + callback('show', tmp) + continue + # check if show exists in processed_series + if tmp['show']['ids']['tvdb'] in processed_series: + log.debug("Removing existing show: %s", tmp['show']['title']) + if callback: + callback('show', tmp) + continue + + new_series_list.append(tmp) + + log.debug("Filtered %d Trakt shows to %d shows that weren't already in Sonarr", len(trakt_series), + len(new_series_list)) + return new_series_list + except Exception: + log.exception("Exception removing existing shows from Trakt list: ") + return None diff --git a/menu/pgtrakt/helpers/str.py b/menu/pgtrakt/helpers/str.py new file mode 100644 index 00000000..e2784092 --- /dev/null +++ b/menu/pgtrakt/helpers/str.py @@ -0,0 +1,35 @@ +from misc.log import logger + +log = logger.get_logger(__name__) + + +def get_year_from_timestamp(timestamp): + year = 0 + try: + if not timestamp: + return 0 + + year = timestamp[:timestamp.index('-')] + except Exception: + log.exception("Exception parsing year from %s: ", timestamp) + return int(year) if str(year).isdigit() else 0 + + +def is_ascii(string): + try: + string.encode('ascii') + except UnicodeEncodeError: + return False + except UnicodeDecodeError: + return False + except Exception: + log.exception(u"Exception checking if %r was ascii: ", string) + return False + return True + + +def ensure_endswith(data, endswith_key): + if not data.strip().endswith(endswith_key): + return "%s%s" % (data.strip(), endswith_key) + else: + return data diff --git a/menu/pgtrakt/helpers/trakt.py b/menu/pgtrakt/helpers/trakt.py new file mode 100644 index 00000000..d6f7aead --- /dev/null +++ b/menu/pgtrakt/helpers/trakt.py @@ -0,0 +1,305 @@ +from helpers import str as misc_str +from misc.log import logger + +log = logger.get_logger(__name__) + + +def blacklisted_show_genre(show, genres): + blacklisted = False + try: + if not show['show']['genres']: + log.debug("%s was blacklisted because it had no genres", + show['show']['title']) + blacklisted = True + else: + for genre in genres: + if genre.lower() in show['show']['genres']: + log.debug("%s was blacklisted because it has genre: %s", + show['show']['title'], genre) + blacklisted = True + break + + except Exception: + log.exception( + "Exception determining if show has a blacklisted genre %s: ", show) + return blacklisted + + +def blacklisted_show_year(show, earliest_year, latest_year): + blacklisted = False + try: + year = misc_str.get_year_from_timestamp(show['show']['first_aired']) + if not year: + log.debug( + "%s was blacklisted due to having an unknown first_aired date", show['show']['title']) + blacklisted = True + else: + if year < earliest_year or year > latest_year: + log.debug("%s was blacklisted because it first aired in: %d", + show['show']['title'], year) + blacklisted = True + except Exception: + log.exception( + "Exception determining if show is within min_year and max_year range %s:", show) + return blacklisted + + +def blacklisted_show_country(show, allowed_countries): + blacklisted = False + try: + if not show['show']['country']: + log.debug("%s was blacklisted because it had no country", + show['show']['title']) + blacklisted = True + else: + if show['show']['country'].lower() not in allowed_countries: + log.debug("%s was blacklisted because it's from country: %s", show['show']['title'], + show['show']['country']) + blacklisted = True + + except Exception: + log.exception( + "Exception determining if show was from an allowed country %s: ", show) + return blacklisted + + +def blacklisted_show_network(show, networks): + blacklisted = False + try: + if not show['show']['network']: + log.debug("%s was blacklisted because it had no network", + show['show']['title']) + blacklisted = True + else: + for network in networks: + if network.lower() in show['show']['network'].lower(): + log.debug("%s was blacklisted because it's from network: %s", show['show']['title'], + show['show']['network']) + blacklisted = True + break + + except Exception: + log.exception( + "Exception determining if show is from a blacklisted network %s: ", show) + return blacklisted + + +def blacklisted_show_runtime(show, lowest_runtime): + blacklisted = False + try: + if not show['show']['runtime'] or not isinstance(show['show']['runtime'], int): + log.debug("%s was blacklisted because it had no runtime", + show['show']['title']) + blacklisted = True + elif int(show['show']['runtime']) < lowest_runtime: + log.debug("%s was blacklisted because it had a runtime of: %d", show['show']['title'], + show['show']['runtime']) + blacklisted = True + + except Exception: + log.exception( + "Exception determining if show had sufficient runtime %s: ", show) + return blacklisted + + +def blacklisted_show_id(show, blacklisted_ids): + blacklisted = False + try: + if not show['show']['ids']['tvdb'] or not isinstance(show['show']['ids']['tvdb'], int): + log.debug( + "%s was blacklisted because it had an invalid tvdb id", show['show']['title']) + blacklisted = True + elif show['show']['ids']['tvdb'] in blacklisted_ids: + log.debug("%s was blacklisted because it had a blacklisted tvdb id of: %d", show['show']['title'], + show['show']['ids']['tvdb']) + blacklisted = True + + except Exception: + log.exception( + "Exception determining if show had a blacklisted tvdb id %s: ", show) + return blacklisted + + +def is_show_blacklisted(show, blacklist_settings, ignore_blacklist, callback=None): + if ignore_blacklist: + return False + + blacklisted = False + try: + if blacklisted_show_year(show, blacklist_settings.blacklisted_min_year, + blacklist_settings.blacklisted_max_year): + blacklisted = True + if blacklisted_show_country(show, blacklist_settings.allowed_countries): + blacklisted = True + if blacklisted_show_genre(show, blacklist_settings.blacklisted_genres): + blacklisted = True + if blacklisted_show_network(show, blacklist_settings.blacklisted_networks): + blacklisted = True + if blacklisted_show_runtime(show, blacklist_settings.blacklisted_min_runtime): + blacklisted = True + if blacklisted_show_id(show, blacklist_settings.blacklisted_tvdb_ids): + blacklisted = True + + if blacklisted and callback: + callback('show', show) + + except Exception: + log.exception( + "Exception determining if show was blacklisted %s: ", show) + return blacklisted + + +def blacklisted_movie_genre(movie, genres): + blacklisted = False + try: + if not movie['movie']['genres']: + log.debug("%s was blacklisted because it had no genres", + movie['movie']['title']) + blacklisted = True + else: + for genre in genres: + if genre.lower() in movie['movie']['genres']: + log.debug("%s was blacklisted because it has genre: %s", + movie['movie']['title'], genre) + blacklisted = True + break + + except Exception: + log.exception( + "Exception determining if movie has a blacklisted genre %s: ", movie) + return blacklisted + + +def blacklisted_movie_year(movie, earliest_year, latest_year): + blacklisted = False + try: + year = movie['movie']['year'] + if year is None or not isinstance(year, int): + log.debug("%s was blacklisted due to having an unknown year", + movie['movie']['title']) + blacklisted = True + else: + if int(year) < earliest_year or int(year) > latest_year: + log.debug("%s was blacklisted because it's year is: %d", + movie['movie']['title'], int(year)) + blacklisted = True + except Exception: + log.exception( + "Exception determining if movie is within min_year and max_year ranger %s:", movie) + return blacklisted + + +def blacklisted_movie_country(movie, allowed_countries): + blacklisted = False + try: + if not movie['movie']['country']: + log.debug("%s was blacklisted because it had no country", + movie['movie']['title']) + blacklisted = True + else: + if movie['movie']['country'].lower() not in allowed_countries: + log.debug("%s was blacklisted because it's from country: %s", movie['movie']['title'], + movie['movie']['country']) + blacklisted = True + + except Exception: + log.exception( + "Exception determining if movie was from an allowed country %s: ", movie) + return blacklisted + + +def blacklisted_movie_title(movie, blacklisted_keywords): + blacklisted = False + try: + if not movie['movie']['title']: + log.debug("Blacklisted movie because it had no title: %s", movie) + blacklisted = True + else: + for keyword in blacklisted_keywords: + if keyword.lower() in movie['movie']['title'].lower(): + log.debug("%s was blacklisted because it had title keyword: %s", + movie['movie']['title'], keyword) + blacklisted = True + break + + except Exception: + log.exception( + "Exception determining if movie had a blacklisted title %s: ", movie) + return blacklisted + + +def blacklisted_movie_runtime(movie, lowest_runtime): + blacklisted = False + try: + if not movie['movie']['runtime'] or not isinstance(movie['movie']['runtime'], int): + log.debug("%s was blacklisted because it had no runtime", + movie['movie']['title']) + blacklisted = True + elif int(movie['movie']['runtime']) < lowest_runtime: + log.debug("%s was blacklisted because it had a runtime of: %d", movie['movie']['title'], + movie['movie']['runtime']) + blacklisted = True + + except Exception: + log.exception( + "Exception determining if movie had sufficient runtime %s: ", movie) + return blacklisted + + +def blacklisted_movie_id(movie, blacklisted_ids): + blacklisted = False + try: + if not movie['movie']['ids']['tmdb'] or not isinstance(movie['movie']['ids']['tmdb'], int): + log.debug( + "%s was blacklisted because it had an invalid tmdb id", movie['movie']['title']) + blacklisted = True + elif movie['movie']['ids']['tmdb'] in blacklisted_ids: + log.debug("%s was blacklisted because it had a blacklisted tmdb id of: %d", movie['movie']['title'], + movie['movie']['ids']['tmdb']) + blacklisted = True + + except Exception: + log.exception( + "Exception determining if show had a blacklisted tmdb id %s: ", movie) + return blacklisted + + +def is_movie_blacklisted(movie, blacklist_settings, ignore_blacklist, callback=None): + if ignore_blacklist: + return False + + blacklisted = False + try: + if blacklisted_movie_title(movie, blacklist_settings.blacklist_title_keywords): + blacklisted = True + if blacklisted_movie_year(movie, blacklist_settings.blacklisted_min_year, + blacklist_settings.blacklisted_max_year): + blacklisted = True + if blacklisted_movie_country(movie, blacklist_settings.allowed_countries): + blacklisted = True + if blacklisted_movie_genre(movie, blacklist_settings.blacklisted_genres): + blacklisted = True + if blacklisted_movie_runtime(movie, blacklist_settings.blacklisted_min_runtime): + blacklisted = True + if blacklisted_movie_id(movie, blacklist_settings.blacklisted_tmdb_ids): + blacklisted = True + + if blacklisted and callback: + callback('movie', movie) + + except Exception: + log.exception( + "Exception determining if movie was blacklisted %s: ", movie) + return blacklisted + + +def extract_list_user_and_key_from_url(list_url): + try: + import re + list_user = re.search('\/users\/([^/]*)', list_url).group(1) + list_key = re.search('\/lists\/([^/]*)', list_url).group(1) + + return list_user, list_key + except: + log.error('The URL "%s" is not in the correct format', list_url) + exit() diff --git a/menu/pgtrakt/list_of_country_codes.md b/menu/pgtrakt/list_of_country_codes.md new file mode 100644 index 00000000..c57a9a66 --- /dev/null +++ b/menu/pgtrakt/list_of_country_codes.md @@ -0,0 +1,79 @@ +- `au` (Australia) +- `at` (Austria) +- `be` (Belgium) +- `bo` (Bolivia, Plurinational State of) +- `ba` (Bosnia and Herzegovina) +- `br` (Brazil) +- `io` (British Indian Ocean Territory) +- `bg` (Bulgaria) +- `ca` (Canada) +- `cl` (Chile) +- `cn` (China) +- `co` (Colombia) +- `hr` (Croatia) +- `cu` (Cuba) +- `cz` (Czech Republic) +- `dk` (Denmark) +- `eg` (Egypt) +- `ee` (Estonia) +- `fi` (Finland) +- `fr` (France) +- `pf` (French Polynesia) +- `ge` (Georgia) +- `de` (Germany) +- `gr` (Greece) +- `hk` (Hong Kong) +- `hu` (Hungary) +- `is` (Iceland) +- `in` (India) +- `id` (Indonesia) +- `ir` (Iran, Islamic Republic of) +- `ie` (Ireland) +- `il` (Israel) +- `it` (Italy) +- `jp` (Japan) +- `ke` (Kenya) +- `kp` (Korea, Democratic People's Republic of) +- `kr` (Korea, Republic of) +- `kw` (Kuwait) +- `lv` (Latvia) +- `lb` (Lebanon) +- `lt` (Lithuania) +- `mk` (Macedonia, Republic of) +- `my` (Malaysia) +- `mv` (Maldives) +- `mx` (Mexico) +- `mn` (Mongolia) +- `me` (Montenegro) +- `ma` (Morocco) +- `nl` (Netherlands) +- `nz` (New Zealand) +- `ng` (Nigeria) +- `no` (Norway) +- `pk` (Pakistan) +- `py` (Paraguay) +- `ph` (Philippines) +- `pl` (Poland) +- `pt` (Portugal) +- `ro` (Romania) +- `ru` (Russian Federation) +- `sa` (Saudi Arabia) +- `rs` (Serbia) +- `sg` (Singapore) +- `sk` (Slovakia) +- `si` (Slovenia) +- `za` (South Africa) +- `es` (Spain) +- `sz` (Swaziland) +- `se` (Sweden) +- `ch` (Switzerland) +- `tw` (Taiwan) +- `th` (Thailand) +- `tr` (Turkey) +- `ua` (Ukraine) +- `ae` (United Arab Emirates) +- `gb` (United Kingdom) +- `us` (United States) +- `um` (United States Minor Outlying Islands) +- `ve` (Venezuela, Bolivarian Republic of) +- `vn` (Vietnam) diff --git a/menu/pgtrakt/list_of_language_codes.md b/menu/pgtrakt/list_of_language_codes.md new file mode 100644 index 00000000..45c74d39 --- /dev/null +++ b/menu/pgtrakt/list_of_language_codes.md @@ -0,0 +1,131 @@ +- `af` (Afrikaans) +- `ak` (Akan) +- `sq` (Albanian) +- `am` (Amharic) +- `ar` (Arabic) +- `hy` (Armenian) +- `as` (Assamese) +- `ay` (Aymara) +- `az` (Azerbaijani) +- `bm` (Bambara) +- `eu` (Basque) +- `be` (Belarusian) +- `bn` (Bengali) +- `nb` (Bokmål, Norwegian; Norwegian Bokmål) +- `bs` (Bosnian) +- `br` (Breton) +- `bg` (Bulgarian) +- `my` (Burmese) +- `ca` (Catalan; Valencian) +- `km` (Central Khmer) +- `ch` (Chamorro) +- `ce` (Chechen) +- `zh` (Chinese) +- `kw` (Cornish) +- `co` (Corsican) +- `cr` (Cree) +- `hr` (Croatian) +- `cs` (Czech) +- `da` (Danish) +- `dv` (Divehi; Dhivehi; Maldivian) +- `nl` (Dutch; Flemish) +- `dz` (Dzongkha) +- `en` (English) +- `eo` (Esperanto) +- `et` (Estonian) +- `ee` (Ewe) +- `fo` (Faroese) +- `fi` (Finnish) +- `fr` (French) +- `gd` (Gaelic; Scottish Gaelic) +- `gl` (Galician) +- `ka` (Georgian) +- `de` (German) +- `el` (Greek, Modern (1453-)) +- `gn` (Guarani) +- `gu` (Gujarati) +- `ht` (Haitian; Haitian Creole) +- `ha` (Hausa) +- `he` (Hebrew) +- `hi` (Hindi) +- `hu` (Hungarian) +- `is` (Icelandic) +- `ig` (Igbo) +- `id` (Indonesian) +- `iu` (Inuktitut) +- `ga` (Irish) +- `it` (Italian) +- `ja` (Japanese) +- `jv` (Javanese) +- `kl` (Kalaallisut; Greenlandic) +- `kn` (Kannada) +- `kk` (Kazakh) +- `rw` (Kinyarwanda) +- `ky` (Kirghiz; Kyrgyz) +- `ko` (Korean) +- `ku` (Kurdish) +- `lo` (Lao) +- `la` (Latin) +- `lv` (Latvian) +- `lt` (Lithuanian) +- `lb` (Luxembourgish; Letzeburgesch) +- `mk` (Macedonian) +- `mg` (Malagasy) +- `ms` (Malay) +- `ml` (Malayalam) +- `mt` (Maltese) +- `mi` (Maori) +- `mr` (Marathi) +- `mh` (Marshallese) +- `mn` (Mongolian) +- `nv` (Navajo; Navaho) +- `nr` (Ndebele, South; South Ndebele) +- `ne` (Nepali) +- `no` (Norwegian) +- `nn` (Norwegian Nynorsk; Nynorsk, Norwegian) +- `oc` (Occitan (post 1500); Provençal) +- `pa` (Panjabi; Punjabi) +- `fa` (Persian) +- `pl` (Polish) +- `pt` (Portuguese) +- `ps` (Pushto; Pashto) +- `qu` (Quechua) +- `ro` (Romanian; Moldavian; Moldovan) +- `rn` (Rundi) +- `ru` (Russian) +- `sm` (Samoan) +- `sg` (Sango) +- `sa` (Sanskrit) +- `sr` (Serbian) +- `sn` (Shona) +- `si` (Sinhala; Sinhalese) +- `sk` (Slovak) +- `sl` (Slovenian) +- `so` (Somali) +- `es` (Spanish; Castilian) +- `sw` (Swahili) +- `sv` (Swedish) +- `tl` (Tagalog) +- `ty` (Tahitian) +- `tg` (Tajik) +- `ta` (Tamil) +- `tt` (Tatar) +- `te` (Telugu) +- `th` (Thai) +- `bo` (Tibetan) +- `ti` (Tigrinya) +- `to` (Tonga (Tonga Islands)) +- `tr` (Turkish) +- `tk` (Turkmen) +- `uk` (Ukrainian) +- `ur` (Urdu) +- `uz` (Uzbek) +- `ve` (Venda) +- `vi` (Vietnamese) +- `cy` (Welsh) +- `fy` (Western Frisian) +- `wo` (Wolof) +- `xh` (Xhosa) +- `yi` (Yiddish) +- `za` (Zhuang; Chuang) +- `zu` (Zulu) diff --git a/menu/pgtrakt/list_of_movie_genres.md b/menu/pgtrakt/list_of_movie_genres.md new file mode 100644 index 00000000..feee12d1 --- /dev/null +++ b/menu/pgtrakt/list_of_movie_genres.md @@ -0,0 +1,34 @@ +- `action` +- `adventure` +- `animation` +- `anime` +- `comedy` +- `crime` +- `disaster` +- `documentary` +- `drama` +- `eastern` +- `family` +- `fan-film` +- `fantasy` +- `film-noir` +- `history` +- `holiday` +- `horror` +- `indie` +- `music` +- `musical` +- `mystery` +- `none` +- `road` +- `romance` +- `science-fiction` +- `short` +- `sporting-event` +- `sports` +- `superhero` +- `suspense` +- `thriller` +- `tv-movie` +- `war` +- `western` diff --git a/menu/pgtrakt/list_of_show_genres.md b/menu/pgtrakt/list_of_show_genres.md new file mode 100644 index 00000000..b7dd63ef --- /dev/null +++ b/menu/pgtrakt/list_of_show_genres.md @@ -0,0 +1,39 @@ +- `action` +- `adventure` +- `animation` +- `anime` +- `biography` +- `children` +- `comedy` +- `crime` +- `disaster` +- `documentary` +- `drama` +- `eastern` +- `family` +- `fantasy` +- `game-show` +- `history` +- `holiday` +- `home-and-garden` +- `horror` +- `mini-series` +- `music` +- `musical` +- `mystery` +- `news` +- `none` +- `reality` +- `romance` +- `science-fiction` +- `short` +- `soap` +- `special-interest` +- `sporting-event` +- `sports` +- `superhero` +- `suspense` +- `talk-show` +- `thriller` +- `war` +- `western` diff --git a/menu/pgtrakt/media/__init__.py b/menu/pgtrakt/media/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/menu/pgtrakt/media/pvr.py b/menu/pgtrakt/media/pvr.py new file mode 100644 index 00000000..380e9c64 --- /dev/null +++ b/menu/pgtrakt/media/pvr.py @@ -0,0 +1,158 @@ +import os.path +from abc import ABC, abstractmethod + +import backoff +import requests + +from helpers.misc import backoff_handler +from helpers import str as misc_str +from helpers import misc +from misc.log import logger + +log = logger.get_logger(__name__) + + +class PVR(ABC): + def __init__(self, server_url, api_key): + self.server_url = server_url + self.api_key = api_key + self.headers = { + 'Content-Type': 'application/json', + 'X-Api-Key': self.api_key, + } + + def validate_api_key(self): + try: + # request system status to validate api_key + req = requests.get( + os.path.join(misc_str.ensure_endswith( + self.server_url, "/"), 'api/system/status'), + headers=self.headers, + timeout=60, + allow_redirects=False + ) + log.debug("Request Response: %d", req.status_code) + + if req.status_code == 200 and 'version' in req.json(): + return True + return False + except Exception: + log.exception("Exception validating api_key: ") + return False + + @abstractmethod + def get_objects(self): + pass + + @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) + def _get_objects(self, endpoint): + try: + # make request + req = requests.get( + os.path.join(misc_str.ensure_endswith( + self.server_url, "/"), endpoint), + headers=self.headers, + timeout=60, + allow_redirects=False + ) + log.debug("Request URL: %s", req.url) + log.debug("Request Response: %d", req.status_code) + + if req.status_code == 200: + resp_json = req.json() + log.debug("Found %d objects", len(resp_json)) + return resp_json + else: + log.error( + "Failed to retrieve all objects, request response: %d", req.status_code) + except Exception: + log.exception("Exception retrieving objects: ") + return None + + @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) + def get_profile_id(self, profile_name): + try: + # make request + req = requests.get( + os.path.join(misc_str.ensure_endswith( + self.server_url, "/"), 'api/profile'), + headers=self.headers, + timeout=60, + allow_redirects=False + ) + log.debug("Request URL: %s", req.url) + log.debug("Request Response: %d", req.status_code) + + if req.status_code == 200: + resp_json = req.json() + for profile in resp_json: + if profile['name'].lower() == profile_name.lower(): + log.debug("Found id of %s profile: %d", + profile_name, profile['id']) + return profile['id'] + log.debug("Profile %s with id %d did not match %s", + profile['name'], profile['id'], profile_name) + else: + log.error( + "Failed to retrieve all quality profiles, request response: %d", req.status_code) + except Exception: + log.exception( + "Exception retrieving id of profile %s: ", profile_name) + return None + + def _prepare_add_object_payload(self, title, title_slug, profile_id, root_folder): + return { + 'title': title, + 'titleSlug': title_slug, + 'qualityProfileId': profile_id, + 'images': [], + 'monitored': True, + 'rootFolderPath': root_folder, + 'addOptions': { + 'ignoreEpisodesWithFiles': False, + 'ignoreEpisodesWithoutFiles': False, + } + } + + @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) + def _add_object(self, endpoint, payload, identifier_field, identifier): + try: + # make request + req = requests.post( + os.path.join(misc_str.ensure_endswith( + self.server_url, "/"), endpoint), + headers=self.headers, + json=payload, + timeout=60, + allow_redirects=False + ) + log.debug("Request URL: %s", req.url) + log.debug("Request Payload: %s", payload) + log.debug("Request Response Code: %d", req.status_code) + log.debug("Request Response Text:\n%s", req.text) + + response_json = None + if 'json' in req.headers['Content-Type'].lower(): + response_json = misc.get_response_dict( + req.json(), identifier_field, identifier) + + if (req.status_code == 201 or req.status_code == 200) \ + and (response_json and identifier_field in response_json) \ + and response_json[identifier_field] == identifier: + log.debug("Successfully added %s (%d)", + payload['title'], identifier) + return True + elif response_json and ('errorMessage' in response_json or 'message' in response_json): + message = response_json['errorMessage'] if 'errorMessage' in response_json else response_json['message'] + + log.error("Failed to add %s (%d) - status_code: %d, reason: %s", payload['title'], identifier, + req.status_code, message) + return False + else: + log.error("Failed to add %s (%d), unexpected response:\n%s", + payload['title'], identifier, req.text) + return False + except Exception: + log.exception("Exception adding %s (%d): ", + payload['title'], identifier) + return None diff --git a/menu/pgtrakt/media/radarr.py b/menu/pgtrakt/media/radarr.py new file mode 100644 index 00000000..c60b5bc6 --- /dev/null +++ b/menu/pgtrakt/media/radarr.py @@ -0,0 +1,29 @@ +import backoff + +from helpers.misc import backoff_handler, dict_merge +from media.pvr import PVR +from misc.log import logger + +log = logger.get_logger(__name__) + + +class Radarr(PVR): + def get_objects(self): + return self._get_objects('api/movie') + + @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) + def add_movie(self, movie_tmdbid, movie_title, movie_year, movie_title_slug, profile_id, root_folder, + search_missing=False): + payload = self._prepare_add_object_payload( + movie_title, movie_title_slug, profile_id, root_folder) + + payload = dict_merge(payload, { + 'tmdbId': movie_tmdbid, + 'year': movie_year, + 'minimumAvailability': 'released', + 'addOptions': { + 'searchForMovie': search_missing + } + }) + + return self._add_object('api/movie', payload, identifier_field='tmdbId', identifier=movie_tmdbid) diff --git a/menu/pgtrakt/media/sonarr.py b/menu/pgtrakt/media/sonarr.py new file mode 100644 index 00000000..e52a51b5 --- /dev/null +++ b/menu/pgtrakt/media/sonarr.py @@ -0,0 +1,62 @@ +import os.path + +import backoff +import requests +from helpers.misc import backoff_handler, dict_merge + +from helpers import str as misc_str +from media.pvr import PVR +from misc.log import logger + +log = logger.get_logger(__name__) + + +class Sonarr(PVR): + def get_objects(self): + return self._get_objects('api/series') + + @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) + def get_tags(self): + tags = {} + try: + # make request + req = requests.get( + os.path.join(misc_str.ensure_endswith( + self.server_url, "/"), 'api/tag'), + headers=self.headers, + timeout=60, + allow_redirects=False + ) + log.debug("Request URL: %s", req.url) + log.debug("Request Response: %d", req.status_code) + + if req.status_code == 200: + resp_json = req.json() + log.debug("Found %d tags", len(resp_json)) + for tag in resp_json: + tags[tag['label']] = tag['id'] + return tags + else: + log.error( + "Failed to retrieve all tags, request response: %d", req.status_code) + except Exception: + log.exception("Exception retrieving tags: ") + return None + + @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) + def add_series(self, series_tvdbid, series_title, series_title_slug, profile_id, root_folder, tag_ids=None, + search_missing=False): + payload = self._prepare_add_object_payload( + series_title, series_title_slug, profile_id, root_folder) + + payload = dict_merge(payload, { + 'tvdbId': series_tvdbid, + 'tags': [] if not tag_ids or not isinstance(tag_ids, list) else tag_ids, + 'seasons': [], + 'seasonFolder': True, + 'addOptions': { + 'searchForMissingEpisodes': search_missing + } + }) + + return self._add_object('api/series', payload, identifier_field='tvdbId', identifier=series_tvdbid) diff --git a/menu/pgtrakt/media/trakt.py b/menu/pgtrakt/media/trakt.py new file mode 100644 index 00000000..65b80051 --- /dev/null +++ b/menu/pgtrakt/media/trakt.py @@ -0,0 +1,575 @@ +import time + +import backoff +import requests + +from helpers.misc import backoff_handler, dict_merge +from helpers.trakt import extract_list_user_and_key_from_url +from misc.log import logger + +log = logger.get_logger(__name__) + + +class Trakt: + non_user_lists = ['anticipated', 'trending', + 'popular', 'boxoffice', 'watched', 'played'] + + def __init__(self, cfg): + self.cfg = cfg + + ############################################################ + # Requests + ############################################################ + + def _make_request(self, url, payload={}, authenticate_user=None, request_type='get'): + headers, authenticate_user = self._headers(authenticate_user) + + if authenticate_user: + url = url.replace('{authenticate_user}', authenticate_user) + + # make request + if request_type == 'delete': + req = requests.delete(url, headers=headers, + params=payload, timeout=30) + else: + req = requests.get(url, headers=headers, + params=payload, timeout=30) + log.debug("Request URL: %s", req.url) + log.debug("Request Payload: %s", payload) + log.debug("Request User: %s", authenticate_user) + log.debug("Response Code: %d", req.status_code) + + return req + + @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) + def _make_item_request(self, url, object_name, payload={}): + payload = dict_merge(payload, {'extended': 'full'}) + + try: + req = self._make_request(url, payload) + + if req.status_code == 200: + resp_json = req.json() + return resp_json + elif req.status_code == 401: + log.error( + "The authentication to Trakt is revoked. Please re-authenticate.") + exit() + else: + log.error("Failed to retrieve %s, request response: %d", + object_name, req.status_code) + return None + except Exception: + log.exception("Exception retrieving %s: ", object_name) + return None + + @backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler) + def _make_items_request(self, url, limit, languages, type_name, object_name, authenticate_user=None, payload={}, + sleep_between=5, genres=None): + if not languages: + languages = ['en'] + + payload = dict_merge(payload, { + 'extended': 'full', 'limit': limit, 'page': 1, 'languages': ','.join(languages)}) + if genres: + payload['genres'] = genres + + processed = [] + + if authenticate_user: + type_name = type_name.replace( + '{authenticate_user}', self._user_used_for_authentication(authenticate_user)) + + try: + while True: + req = self._make_request(url, payload, authenticate_user) + + current_page = payload['page'] + total_pages = 0 if 'X-Pagination-Page-Count' not in req.headers else int( + req.headers['X-Pagination-Page-Count']) + + log.debug("Response Page: %d of %d", current_page, total_pages) + + if req.status_code == 200: + resp_json = req.json() + if type_name == 'person' and 'cast' in resp_json: + # handle person results + for item in resp_json['cast']: + if item not in processed: + if object_name.rstrip('s') not in item and 'title' in item: + processed.append( + {object_name.rstrip('s'): item}) + else: + processed.append(item) + else: + for item in resp_json: + if item not in processed: + if object_name.rstrip('s') not in item and 'title' in item: + processed.append( + {object_name.rstrip('s'): item}) + else: + processed.append(item) + + # check if we have fetched the last page, break if so + if total_pages == 0: + log.debug("There were no more pages to retrieve") + break + elif current_page >= total_pages: + log.debug( + "There are no more pages to retrieve results from") + break + else: + log.info( + "There are %d pages left to retrieve results from", total_pages - current_page) + payload['page'] += 1 + time.sleep(sleep_between) + elif req.status_code == 401: + log.error( + "The authentication to Trakt is revoked. Please re-authenticate.") + exit() + else: + log.error("Failed to retrieve %s %s, request response: %d", + type_name, object_name, req.status_code) + break + + if len(processed): + log.debug("Found %d %s %s", len( + processed), type_name, object_name) + return processed + return None + except Exception: + log.exception("Exception retrieving %s %s: ", + type_name, object_name) + return None + + def validate_client_id(self): + try: + # request anticipated shows to validate client_id + req = self._make_request( + url='https://api.trakt.tv/shows/anticipated', + ) + + if req.status_code == 200: + return True + return False + except Exception: + log.exception("Exception validating client_id: ") + return False + + def remove_recommended_item(self, item_type, trakt_id, authenticate_user=None): + ret = self._make_request( + url='https://api.trakt.tv/recommendations/%ss/%s' % ( + item_type, str(trakt_id)), + authenticate_user=authenticate_user, + request_type='delete' + ) + if ret.status_code == 204: + return True + return False + + ############################################################ + # OAuth Authentication + ############################################################ + + def __oauth_request_device_code(self): + log.info( + "We're talking to Trakt to get your verification code. Please wait a moment...") + + payload = {'client_id': self.cfg.trakt.client_id} + + print(self._headers_without_authentication()) + + # Request device code + req = requests.post('https://api.trakt.tv/oauth/device/code', params=payload, + headers=self._headers_without_authentication()) + device_code_response = req.json() + + # Display needed information to the user + log.info('Go to: %s on any device and enter %s. We\'ll be polling Trakt every %s seconds for a reply', + device_code_response['verification_url'], device_code_response['user_code'], + device_code_response['interval']) + + return device_code_response + + def __oauth_process_token_request(self, req): + success = False + + if req.status_code == 200: + # Success; saving the access token + access_token_response = req.json() + access_token = access_token_response['access_token'] + + # But first we need to find out what user this token belongs to + temp_headers = self._headers_without_authentication() + temp_headers['Authorization'] = 'Bearer ' + access_token + + req = requests.get( + 'https://api.trakt.tv/users/me', headers=temp_headers) + + from misc.config import Config + new_config = Config() + + new_config.merge_settings({ + "trakt": { + req.json()['username']: access_token_response + } + }) + + success = True + elif req.status_code == 404: + log.debug('The device code was wrong') + log.error( + 'Whoops, something went wrong; aborting the authentication process') + elif req.status_code == 409: + log.error( + 'You\'ve already authenticated this application; aborting the authentication process') + elif req.status_code == 410: + log.error( + 'The authentication process has expired; please start again') + elif req.status_code == 418: + log.error( + 'You\'ve denied the authentication; are you sure? Please try again') + elif req.status_code == 429: + log.debug('We\'re polling too quickly.') + + return success, req.status_code + + def __oauth_poll_for_access_token(self, device_code, polling_interval=5, polling_expire=600): + polling_start = time.time() + time.sleep(polling_interval) + tries = 0 + + while time.time() - polling_start < polling_expire: + tries += 1 + + log.debug('Polling Trakt for the %sth time; %s seconds left', tries, + polling_expire - round(time.time() - polling_start)) + + payload = {'code': device_code, 'client_id': self.cfg.trakt.client_id, + 'client_secret': self.cfg.trakt.client_secret, 'grant_type': 'authorization_code'} + + # Poll Trakt for access token + req = requests.post('https://api.trakt.tv/oauth/device/token', params=payload, + headers=self._headers_without_authentication()) + + success, status_code = self.__oauth_process_token_request(req) + + if success: + break + elif status_code == 426: + log.debug('Increasing the interval by one second') + polling_interval += 1 + + time.sleep(polling_interval) + return False + + def __oauth_refresh_access_token(self, refresh_token): + payload = {'refresh_token': refresh_token, 'client_id': self.cfg.trakt.client_id, + 'client_secret': self.cfg.trakt.client_secret, 'grant_type': 'refresh_token'} + + req = requests.post('https://api.trakt.tv/oauth/token', params=payload, + headers=self._headers_without_authentication()) + + success, status_code = self.__oauth_process_token_request(req) + + return success + + def oauth_authentication(self): + try: + device_code_response = self.__oauth_request_device_code() + + if self.__oauth_poll_for_access_token(device_code_response['device_code'], + device_code_response['interval'], + device_code_response['expires_in']): + return True + except Exception: + log.exception("Exception occurred when authenticating user") + return False + + def _get_first_authenticated_user(self): + import copy + + users = copy.copy(self.cfg.trakt) + + if 'client_id' in users.keys(): + users.pop('client_id') + + if 'client_secret' in users.keys(): + users.pop('client_secret') + + if len(users) > 0: + return list(users.keys())[0] + + def _user_is_authenticated(self, user): + return user in self.cfg['trakt'].keys() + + def _renew_oauth_token_if_expired(self, user): + token_information = self.cfg['trakt'][user] + + # Check if the acces_token for the user is expired + expires_at = token_information['created_at'] + \ + token_information['expires_in'] + if expires_at < round(time.time()): + log.info("The access token for the user %s has expired. We're requesting a new one; please wait a moment.", + user) + + if self.__oauth_refresh_access_token(token_information["refresh_token"]): + log.info( + "The access token for the user %s has been refreshed. Please restart the application.", user) + + def _user_used_for_authentication(self, user=None): + if user is None: + user = self._get_first_authenticated_user() + elif not self._user_is_authenticated(user): + log.error('The user %s you specified to use for authentication is not authenticated yet. ' + + 'Authenticate the user first, before you use it to retrieve lists.', user) + + exit() + + return user + + def _headers_without_authentication(self): + return { + 'Content-Type': 'application/json', + 'trakt-api-version': '2', + 'trakt-api-key': self.cfg.trakt.client_id + } + + def _headers(self, user=None): + headers = self._headers_without_authentication() + + user = self._user_used_for_authentication(user) + + if user is not None: + self._renew_oauth_token_if_expired(user) + headers['Authorization'] = 'Bearer ' + \ + self.cfg['trakt'][user]['access_token'] + else: + log.info('No user') + + return headers, user + + ############################################################ + # Shows + ############################################################ + + def get_show(self, show_id): + return self._make_item_request( + url='https://api.trakt.tv/shows/%s' % str(show_id), + object_name='show', + ) + + def get_trending_shows(self, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/shows/trending', + limit=limit, + languages=languages, + object_name='shows', + type_name='trending', + genres=genres + ) + + def get_popular_shows(self, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/shows/popular', + limit=limit, + languages=languages, + object_name='shows', + type_name='popular', + genres=genres + ) + + def get_anticipated_shows(self, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/shows/anticipated', + limit=limit, + languages=languages, + object_name='shows', + type_name='anticipated', + genres=genres + ) + + def get_person_shows(self, person, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/people/%s/shows' % person, + limit=limit, + languages=languages, + object_name='shows', + type_name='person', + genres=genres + ) + + def get_most_played_shows(self, limit=1000, languages=None, genres=None, most_type=None): + return self._make_items_request( + url='https://api.trakt.tv/shows/played/%s' % ( + 'weekly' if not most_type else most_type), + limit=limit, + languages=languages, + object_name='shows', + type_name='played', + genres=genres + ) + + def get_most_watched_shows(self, limit=1000, languages=None, genres=None, most_type=None): + return self._make_items_request( + url='https://api.trakt.tv/shows/watched/%s' % ( + 'weekly' if not most_type else most_type), + limit=limit, + languages=languages, + object_name='shows', + type_name='watched', + genres=genres + ) + + def get_recommended_shows(self, authenticate_user=None, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/recommendations/shows', + authenticate_user=authenticate_user, + limit=limit, + languages=languages, + object_name='shows', + type_name='recommended from {authenticate_user}', + genres=genres + ) + + def get_watchlist_shows(self, authenticate_user=None, limit=1000, languages=None): + return self._make_items_request( + url='https://api.trakt.tv/users/{authenticate_user}/watchlist/shows', + authenticate_user=authenticate_user, + limit=limit, + languages=languages, + object_name='shows', + type_name='watchlist from {authenticate_user}', + ) + + def get_user_list_shows(self, list_url, authenticate_user=None, limit=1000, languages=None): + list_user, list_key = extract_list_user_and_key_from_url(list_url) + + log.debug('Fetching %s from %s', list_key, list_user) + + return self._make_items_request( + url='https://api.trakt.tv/users/' + list_user + + '/lists/' + list_key + '/items/shows', + authenticate_user=authenticate_user, + limit=limit, + languages=languages, + object_name='shows', + type_name=(list_key + ' from ' + list_user), + ) + + ############################################################ + # Movies + ############################################################ + + def get_movie(self, movie_id): + return self._make_item_request( + url='https://api.trakt.tv/movies/%s' % str(movie_id), + object_name='movie', + ) + + def get_trending_movies(self, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/movies/trending', + limit=limit, + languages=languages, + object_name='movies', + type_name='trending', + genres=genres + ) + + def get_popular_movies(self, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/movies/popular', + limit=limit, + languages=languages, + object_name='movies', + type_name='popular', + genres=genres + ) + + def get_anticipated_movies(self, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/movies/anticipated', + limit=limit, + languages=languages, + object_name='movies', + type_name='anticipated', + genres=genres + ) + + def get_person_movies(self, person, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/people/%s/movies' % person, + limit=limit, + languages=languages, + object_name='movies', + type_name='person', + genres=genres + ) + + def get_most_played_movies(self, limit=1000, languages=None, genres=None, most_type=None): + return self._make_items_request( + url='https://api.trakt.tv/movies/played/%s' % ( + 'weekly' if not most_type else most_type), + limit=limit, + languages=languages, + object_name='movies', + type_name='played', + genres=genres + ) + + def get_most_watched_movies(self, limit=1000, languages=None, genres=None, most_type=None): + return self._make_items_request( + url='https://api.trakt.tv/movies/watched/%s' % ( + 'weekly' if not most_type else most_type), + limit=limit, + languages=languages, + object_name='movies', + type_name='watched', + genres=genres + ) + + def get_boxoffice_movies(self, limit=1000, languages=None): + return self._make_items_request( + url='https://api.trakt.tv/movies/boxoffice', + limit=limit, + languages=languages, + object_name='movies', + type_name='anticipated', + ) + + def get_recommended_movies(self, authenticate_user=None, limit=1000, languages=None, genres=None): + return self._make_items_request( + url='https://api.trakt.tv/recommendations/movies', + authenticate_user=authenticate_user, + limit=limit, + languages=languages, + object_name='movies', + type_name='recommended from {authenticate_user}', + genres=genres + ) + + def get_watchlist_movies(self, authenticate_user=None, limit=1000, languages=None): + return self._make_items_request( + url='https://api.trakt.tv/users/{authenticate_user}/watchlist/movies', + authenticate_user=authenticate_user, + limit=limit, + languages=languages, + object_name='movies', + type_name='watchlist from {authenticate_user}', + ) + + def get_user_list_movies(self, list_url, authenticate_user=None, limit=1000, languages=None): + list_user, list_key = extract_list_user_and_key_from_url(list_url) + + log.debug('Fetching %s from %s', list_key, list_user) + + return self._make_items_request( + url='https://api.trakt.tv/users/' + list_user + + '/lists/' + list_key + '/items/movies', + authenticate_user=authenticate_user, + limit=limit, + languages=languages, + object_name='movies', + type_name=(list_key + ' from ' + list_user), + ) diff --git a/menu/pgtrakt/misc/__init__.py b/menu/pgtrakt/misc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/menu/pgtrakt/misc/config.py b/menu/pgtrakt/misc/config.py new file mode 100644 index 00000000..cccd244d --- /dev/null +++ b/menu/pgtrakt/misc/config.py @@ -0,0 +1,211 @@ +import json +import os +import sys + +from attrdict import AttrDict + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super( + Singleton, cls).__call__(*args, **kwargs) + + return cls._instances[cls] + + +class AttrConfig(AttrDict): + """ + Simple AttrDict subclass to return None when requested attribute does not exist + """ + + def __init__(self, config): + super().__init__(config) + + def __getattr__(self, item): + try: + return super().__getattr__(item) + except AttributeError: + pass + # Default behaviour + return None + + +class Config(object, metaclass=Singleton): + base_config = { + 'core': { + 'debug': False + }, + 'trakt': { + 'client_id': '', + 'client_secret': '' + }, + 'sonarr': { + 'url': 'http://localhost:8989/', + 'api_key': '', + 'profile': 'HD-1080p', + 'root_folder': '/tv/', + 'tags': { + } + }, + 'radarr': { + 'url': 'http://localhost:7878/', + 'api_key': '', + 'profile': 'HD-1080p', + 'root_folder': '/movies/' + }, + 'omdb': { + 'api_key': '' + }, + 'filters': { + 'shows': { + 'disabled_for': [], + 'blacklisted_genres': [], + 'blacklisted_networks': [], + 'allowed_countries': [], + 'allowed_languages': [], + 'blacklisted_min_runtime': 15, + 'blacklisted_min_year': 2000, + 'blacklisted_max_year': 2019, + 'blacklisted_tvdb_ids': [], + }, + 'movies': { + 'disabled_for': [], + 'blacklisted_genres': [], + 'blacklisted_min_runtime': 60, + 'blacklisted_min_year': 2000, + 'blacklisted_max_year': 2019, + 'blacklist_title_keywords': [], + 'blacklisted_tmdb_ids': [], + 'allowed_countries': [], + 'allowed_languages': [], + 'rating_limit': "" + } + }, + 'automatic': { + 'movies': { + 'interval': 20, + 'anticipated': 3, + 'trending': 3, + 'popular': 3, + 'boxoffice': 10 + }, + 'shows': { + 'interval': 48, + 'anticipated': 10, + 'trending': 1, + 'popular': 1 + } + }, + 'notifications': { + 'verbose': True + } + } + + def __init__(self, config_path, logfile): + """Initializes config""" + self.conf = None + + self.config_path = config_path + self.log_path = logfile + + @property + def cfg(self): + # Return existing loaded config + if self.conf: + return self.conf + + # Built initial config if it doesn't exist + if self.build_config(): + print("Please edit the default configuration before running again!") + sys.exit(0) + # Load config, upgrade if necessary + else: + tmp = self.load_config() + self.conf, upgraded = self.upgrade_settings(tmp) + + # Save config if upgraded + if upgraded: + self.dump_config() + print("New config options were added, adjust and restart!") + sys.exit(0) + + return self.conf + + @property + def logfile(self): + return self.log_path + + def build_config(self): + if not os.path.exists(self.config_path): + print("Dumping default config to: %s" % self.config_path) + with open(self.config_path, 'w') as fp: + json.dump(self.base_config, fp, sort_keys=True, indent=2) + return True + else: + return False + + def dump_config(self): + if os.path.exists(self.config_path): + with open(self.config_path, 'w') as fp: + json.dump(self.conf, fp, sort_keys=True, indent=2) + return True + else: + return False + + def load_config(self): + with open(self.config_path, 'r') as fp: + return AttrConfig(json.load(fp)) + + def __inner_upgrade(self, settings1, settings2, key=None, overwrite=False): + sub_upgraded = False + merged = settings2.copy() + + if isinstance(settings1, dict): + for k, v in settings1.items(): + # missing k + if k not in settings2: + merged[k] = v + sub_upgraded = True + if not key: + print("Added %r config option: %s" % (str(k), str(v))) + else: + print("Added %r to config option %r: %s" % + (str(k), str(key), str(v))) + continue + + # iterate children + if isinstance(v, dict) or isinstance(v, list): + merged[k], did_upgrade = self.__inner_upgrade(settings1[k], settings2[k], key=k, + overwrite=overwrite) + sub_upgraded = did_upgrade if did_upgrade else sub_upgraded + elif settings1[k] != settings2[k] and overwrite: + merged = settings1 + sub_upgraded = True + elif isinstance(settings1, list) and key: + for v in settings1: + if v not in settings2: + merged.append(v) + sub_upgraded = True + print("Added to config option %r: %s" % (str(key), str(v))) + continue + + return merged, sub_upgraded + + def upgrade_settings(self, currents): + upgraded_settings, upgraded = self.__inner_upgrade( + self.base_config, currents) + return AttrConfig(upgraded_settings), upgraded + + def merge_settings(self, settings_to_merge): + upgraded_settings, upgraded = self.__inner_upgrade( + settings_to_merge, self.conf, overwrite=True) + + self.conf = upgraded_settings + + if upgraded: + self.dump_config() + + return AttrConfig(upgraded_settings), upgraded diff --git a/menu/pgtrakt/misc/log.py b/menu/pgtrakt/misc/log.py new file mode 100644 index 00000000..46eea9e1 --- /dev/null +++ b/menu/pgtrakt/misc/log.py @@ -0,0 +1,55 @@ +import logging +import os +import sys +from logging.handlers import RotatingFileHandler + +from misc.config import Config + + +class Logger: + def __init__(self, file_name=None, log_level=logging.DEBUG, + log_format='%(asctime)s - %(levelname)-10s - %(name)-35s - %(funcName)-35s - %(message)s'): + self.log_format = log_format + + # init root_logger + self.log_formatter = logging.Formatter(log_format) + self.root_logger = logging.getLogger() + self.root_logger.setLevel(log_level) + + # disable bloat loggers + logging.getLogger("requests").setLevel(logging.WARNING) + logging.getLogger('urllib3').setLevel(logging.ERROR) + logging.getLogger('schedule').setLevel(logging.ERROR) + + # init console_logger + self.console_handler = logging.StreamHandler(sys.stdout) + self.console_handler.setFormatter(self.log_formatter) + self.root_logger.addHandler(self.console_handler) + + # init file_logger + if file_name: + if os.path.sep not in file_name: + # file_name was a filename, lets build a full file_path + self.log_file_path = os.path.join(os.path.dirname( + os.path.realpath(sys.argv[0])), file_name) + else: + self.log_file_path = file_name + + self.file_handler = RotatingFileHandler( + self.log_file_path, + maxBytes=1024 * 1024 * 5, + backupCount=5 + ) + self.file_handler.setFormatter(self.log_formatter) + self.root_logger.addHandler(self.file_handler) + + # Set chosen logging level + self.root_logger.setLevel(log_level) + + def get_logger(self, name): + return self.root_logger.getChild(name) + + +# Default logger +logger = Logger(Config().logfile, + logging.DEBUG if Config().cfg.core.debug else logging.INFO) diff --git a/menu/pgtrakt/notifications/__init__.py b/menu/pgtrakt/notifications/__init__.py new file mode 100644 index 00000000..79a1e5d0 --- /dev/null +++ b/menu/pgtrakt/notifications/__init__.py @@ -0,0 +1,58 @@ +from misc.log import logger + +from .pushover import Pushover +from .slack import Slack + +log = logger.get_logger(__name__) + +SERVICES = { + 'pushover': Pushover, + 'slack': Slack +} + + +class Notifications: + def __init__(self): + self.services = [] + + def load(self, **kwargs): + if 'service' not in kwargs: + log.error( + "You must specify a service to load with the service parameter") + return False + elif kwargs['service'] not in SERVICES: + log.error("You specified an invalid service to load: %s", + kwargs['service']) + return False + + try: + chosen_service = SERVICES[kwargs['service']] + del kwargs['service'] + + # load service + service = chosen_service(**kwargs) + self.services.append(service) + + except Exception: + log.exception( + "Exception while loading service, kwargs=%r: ", kwargs) + + def send(self, **kwargs): + try: + # remove service keyword if supplied + if 'service' in kwargs: + # send notification to specified service + chosen_service = kwargs['service'].lower() + del kwargs['service'] + else: + chosen_service = None + + # send notification(s) + for service in self.services: + if chosen_service and service.NAME.lower() != chosen_service: + continue + elif service.send(**kwargs): + log.debug("Sent notification with %s", service.NAME) + except Exception: + log.exception( + "Exception sending notification, kwargs=%r: ", kwargs) diff --git a/menu/pgtrakt/notifications/pushover.py b/menu/pgtrakt/notifications/pushover.py new file mode 100644 index 00000000..ce6230f1 --- /dev/null +++ b/menu/pgtrakt/notifications/pushover.py @@ -0,0 +1,37 @@ +import requests + +from misc.log import logger + +log = logger.get_logger(__name__) + + +class Pushover: + NAME = "Pushover" + + def __init__(self, app_token, user_token, priority=0): + self.app_token = app_token + self.user_token = user_token + self.priority = priority + log.debug("Initialized Pushover notification agent") + + def send(self, **kwargs): + if not self.app_token or not self.user_token: + log.error( + "You must specify an app_token and user_token when initializing this class") + return False + + # send notification + try: + payload = { + 'token': self.app_token, + 'user': self.user_token, + 'message': kwargs['message'], + 'priority': self.priority, + } + resp = requests.post( + 'https://api.pushover.net/1/messages.json', data=payload, timeout=30) + return True if resp.status_code == 200 else False + + except Exception: + log.exception("Error sending notification to %r", self.user_token) + return False diff --git a/menu/pgtrakt/notifications/slack.py b/menu/pgtrakt/notifications/slack.py new file mode 100644 index 00000000..cfba9c02 --- /dev/null +++ b/menu/pgtrakt/notifications/slack.py @@ -0,0 +1,39 @@ +import requests + +from misc.log import logger + +log = logger.get_logger(__name__) + + +class Slack: + NAME = "Slack" + + def __init__(self, webhook_url, sender_name='traktarr', sender_icon=':movie_camera:', channel=None): + self.webhook_url = webhook_url + self.sender_name = sender_name + self.sender_icon = sender_icon + self.channel = channel + log.debug("Initialized Slack notification agent") + + def send(self, **kwargs): + if not self.webhook_url or not self.sender_name or not self.sender_icon: + log.error( + "You must specify an webhook_url, sender_name and sender_icon when initializing this class") + return False + + # send notification + try: + payload = { + 'text': kwargs['message'], + 'username': self.sender_name, + 'icon_emoji': self.sender_icon, + } + if self.channel: + payload['channel'] = self.channel + + resp = requests.post(self.webhook_url, json=payload, timeout=30) + return True if resp.status_code == 200 else False + + except Exception: + log.exception("Error sending notification to %r", self.webhook_url) + return False diff --git a/menu/pgtrakt/pgtrakt.py b/menu/pgtrakt/pgtrakt.py new file mode 100644 index 00000000..63076a8f --- /dev/null +++ b/menu/pgtrakt/pgtrakt.py @@ -0,0 +1,952 @@ +#!/usr/bin/env python3 +import os.path +import signal +import sys +import time + +import click +import schedule + +from pyfiglet import Figlet + +############################################################ +# INIT +############################################################ +cfg = None +log = None +notify = None + + +# Click +@click.group(help='Add new shows & movies to Sonarr/Radarr from Trakt.') +@click.version_option('1.2.3', prog_name='pgtrakt') +@click.option( + '--config', + envvar='TRAKTARR_CONFIG', + type=click.Path(file_okay=True, dir_okay=False), + help='Configuration file', + show_default=True, + default=os.path.join(os.path.dirname( + os.path.realpath(sys.argv[0])), "config.json") +) +@click.option( + '--logfile', + envvar='TRAKTARR_LOGFILE', + type=click.Path(file_okay=True, dir_okay=False), + help='Log file', + show_default=True, + default=os.path.join(os.path.dirname( + os.path.realpath(sys.argv[0])), "activity.log") +) +def app(config, logfile): + # Setup global variables + global cfg, log, notify + + # Load config + from misc.config import Config + cfg = Config(config_path=config, logfile=logfile).cfg + + # Load logger + from misc.log import logger + log = logger.get_logger('pgtrack') + + # Load notifications + from notifications import Notifications + notify = Notifications() + + # Notifications + init_notifications() + + +############################################################ +# Trakt OAuth +############################################################ + +@app.command(help='Authenticate traktarr.') +def trakt_authentication(): + from media.trakt import Trakt + trakt = Trakt(cfg) + + if trakt.oauth_authentication(): + log.info("Authentication information saved; please restart the application") + exit() + + +def validate_trakt(trakt, notifications): + if not trakt.validate_client_id(): + log.error("Aborting due to failure to validate Trakt API Key") + if notifications: + callback_notify( + {'event': 'error', 'reason': 'Failure to validate Trakt API Key'}) + exit() + else: + log.info("Validated Trakt API Key") + + +def validate_pvr(pvr, type, notifications): + if not pvr.validate_api_key(): + log.error("Aborting due to failure to validate %s URL / API Key", type) + if notifications: + callback_notify( + {'event': 'error', 'reason': 'Failure to validate %s URL / API Key' % type}) + return None + else: + log.info("Validated %s URL & API Key", type) + + +def get_profile_id(pvr, profile): + # retrieve profile id for requested profile + profile_id = pvr.get_profile_id(profile) + if not profile_id or not profile_id > 0: + log.error("Aborting due to failure to retrieve Profile ID for: %s", profile) + exit() + log.info("Retrieved Profile ID for %s: %d", profile, profile_id) + return profile_id + + +def get_profile_tags(pvr): + profile_tags = pvr.get_tags() + if profile_tags is None: + log.error("Aborting due to failure to retrieve Tag ID's") + exit() + log.info("Retrieved %d Tag ID's", len(profile_tags)) + return profile_tags + + +def get_objects(pvr, type, notifications): + objects_list = pvr.get_objects() + if not objects_list: + log.error("Aborting due to failure to retrieve %s shows list", type) + if notifications: + callback_notify( + {'event': 'error', 'reason': 'Failure to retrieve %s shows list' % type}) + exit() + objects_type = 'movies' if type.lower() == 'radarr' else 'shows' + log.info("Retrieved %s %s list, %s found: %d", type, + objects_type, objects_type, len(objects_list)) + return objects_list + + +############################################################ +# SHOWS +############################################################ + +@app.command(help='Add a single show to Sonarr.') +@click.option('--show_id', '-id', help='Trakt show_id.', required=True) +@click.option('--folder', '-f', default=None, help='Add show with this root folder to Sonarr.') +@click.option('--no-search', is_flag=True, help='Disable search when adding show to Sonarr.') +def show(show_id, folder=None, no_search=False): + from media.sonarr import Sonarr + from media.trakt import Trakt + from helpers import sonarr as sonarr_helper + + # replace sonarr root_folder if folder is supplied + if folder: + cfg['sonarr']['root_folder'] = folder + + trakt = Trakt(cfg) + sonarr = Sonarr(cfg.sonarr.url, cfg.sonarr.api_key) + + validate_trakt(trakt, False) + validate_pvr(sonarr, 'Sonarr', False) + + profile_id = get_profile_id(sonarr, cfg.sonarr.profile) + profile_tags = get_profile_tags(sonarr) + + # get trakt show + trakt_show = trakt.get_show(show_id) + + if not trakt_show: + log.error("Aborting due to failure to retrieve Trakt show") + return None + else: + log.info("Retrieved Trakt show information for %s: %s (%d)", show_id, trakt_show['title'], + trakt_show['year']) + + # determine which tags to use when adding this series + use_tags = sonarr_helper.series_tag_id_from_network( + profile_tags, cfg.sonarr.tags, trakt_show['network']) + + # add show to sonarr + if sonarr.add_series(trakt_show['ids']['tvdb'], trakt_show['title'], trakt_show['ids']['slug'], profile_id, + cfg.sonarr.root_folder, use_tags, not no_search): + log.info("ADDED %s (%d) with tags: %s", trakt_show['title'], trakt_show['year'], + sonarr_helper.readable_tag_from_ids(profile_tags, use_tags)) + else: + log.error("FAILED adding %s (%d) with tags: %s", trakt_show['title'], trakt_show['year'], + sonarr_helper.readable_tag_from_ids(profile_tags, use_tags)) + + return + + +@app.command(help='Add multiple shows to Sonarr.') +@click.option('--list-type', '-t', + help='Trakt list to process. For example, anticipated, trending, popular, person, watched, played, ' + 'recommended, watchlist or any URL to a list', required=True) +@click.option('--add-limit', '-l', default=0, help='Limit number of shows added to Sonarr.', show_default=True) +@click.option('--add-delay', '-d', default=2.5, help='Seconds between each add request to Sonarr.', show_default=True) +@click.option('--sort', '-s', default='votes', type=click.Choice(['votes', 'rating', 'release']), + help='Sort list to process.') +@click.option('--genre', '-g', default=None, help='Only add shows from this genre to Sonarr.') +@click.option('--folder', '-f', default=None, help='Add shows with this root folder to Sonarr.') +@click.option('--actor', '-a', default=None, help='Only add movies from this actor to Radarr.') +@click.option('--no-search', is_flag=True, help='Disable search when adding shows to Sonarr.') +@click.option('--notifications', is_flag=True, help='Send notifications.') +@click.option('--authenticate-user', + help='Specify which user to authenticate with to retrieve Trakt lists. Default: first user in the config') +@click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.') +@click.option('--remove-rejected-from-recommended', is_flag=True, + help='Removes rejected/existing shows from recommended.') +def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folder=None, actor=None, no_search=False, + notifications=False, authenticate_user=None, ignore_blacklist=False, remove_rejected_from_recommended=False): + from media.sonarr import Sonarr + from media.trakt import Trakt + from helpers import misc as misc_helper + from helpers import sonarr as sonarr_helper + from helpers import trakt as trakt_helper + + added_shows = 0 + + # remove genre from shows blacklisted_genres if supplied + if genre: + misc_helper.unblacklist_genres( + genre, cfg['filters']['shows']['blacklisted_genres']) + + # replace sonarr root_folder if folder is supplied + if folder: + cfg['sonarr']['root_folder'] = folder + + # validate trakt client_id + trakt = Trakt(cfg) + sonarr = Sonarr(cfg.sonarr.url, cfg.sonarr.api_key) + + validate_trakt(trakt, notifications) + validate_pvr(sonarr, 'Sonarr', notifications) + + profile_id = get_profile_id(sonarr, cfg.sonarr.profile) + profile_tags = get_profile_tags(sonarr) + + pvr_objects_list = get_objects(sonarr, 'Sonarr', notifications) + + # get trakt series list + if list_type.lower() == 'anticipated': + trakt_objects_list = trakt.get_anticipated_shows( + genres=genre, languages=cfg.filters.shows.allowed_languages) + elif list_type.lower() == 'trending': + trakt_objects_list = trakt.get_trending_shows( + genres=genre, languages=cfg.filters.shows.allowed_languages) + elif list_type.lower() == 'popular': + trakt_objects_list = trakt.get_popular_shows( + genres=genre, languages=cfg.filters.shows.allowed_languages) + elif list_type.lower() == 'person': + if not actor: + log.error( + "You must specify an actor with the --actor / -a parameter when using the person list type!") + return None + trakt_objects_list = trakt.get_person_shows(person=actor, genres=genre, + languages=cfg.filters.shows.allowed_languages) + elif list_type.lower() == 'recommended': + trakt_objects_list = trakt.get_recommended_shows(authenticate_user, genres=genre, + languages=cfg.filters.shows.allowed_languages) + elif list_type.lower().startswith('played'): + most_type = misc_helper.substring_after(list_type.lower(), "_") + trakt_objects_list = trakt.get_most_played_shows(genres=genre, languages=cfg.filters.shows.allowed_languages, + most_type=most_type if most_type else None) + elif list_type.lower().startswith('watched'): + most_type = misc_helper.substring_after(list_type.lower(), "_") + trakt_objects_list = trakt.get_most_watched_shows(genres=genre, languages=cfg.filters.shows.allowed_languages, + most_type=most_type if most_type else None) + elif list_type.lower() == 'watchlist': + trakt_objects_list = trakt.get_watchlist_shows(authenticate_user) + else: + trakt_objects_list = trakt.get_user_list_shows( + list_type, authenticate_user) + + if not trakt_objects_list: + log.error( + "Aborting due to failure to retrieve Trakt %s shows list", list_type) + if notifications: + callback_notify( + {'event': 'abort', 'type': 'shows', 'list_type': list_type, + 'reason': 'Failure to retrieve Trakt %s shows list' % list_type}) + return None + else: + log.info("Retrieved Trakt %s shows list, shows found: %d", + list_type, len(trakt_objects_list)) + + # set remove_rejected_recommended to False if this is not the recommended list + if list_type.lower() != 'recommended': + remove_rejected_from_recommended = False + + # build filtered series list without series that exist in sonarr + processed_series_list = sonarr_helper.remove_existing_series(pvr_objects_list, trakt_objects_list, + callback_remove_recommended + if remove_rejected_from_recommended else None) + if processed_series_list is None: + log.error( + "Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list") + if notifications: + callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type, + 'reason': 'Failure to remove existing Sonarr shows from retrieved Trakt %s shows list' + % list_type}) + return None + else: + log.info("Removed existing Sonarr shows from Trakt shows list, shows left to process: %d", + len(processed_series_list)) + + # sort filtered series list + if sort == 'release': + sorted_series_list = misc_helper.sorted_list( + processed_series_list, 'show', 'first_aired') + log.info("Sorted shows list to process by release date") + elif sort == 'rating': + sorted_series_list = misc_helper.sorted_list( + processed_series_list, 'show', 'rating') + log.info("Sorted shows list to process by highest rating") + else: + sorted_series_list = misc_helper.sorted_list( + processed_series_list, 'show', 'votes') + log.info("Sorted shows list to process by highest votes") + + # loop series_list + log.info("Processing list now...") + for series in sorted_series_list: + try: + # check if genre matches genre supplied via argument + if genre and not misc_helper.allowed_genres(genre, 'show', series): + log.debug("Skipping: %s because it was not from %s genre(s)", + series['show']['title'], genre.lower()) + continue + + # check if series passes out blacklist criteria inspection + if not trakt_helper.is_show_blacklisted(series, cfg.filters.shows, ignore_blacklist, + callback_remove_recommended + if remove_rejected_from_recommended else None): + log.info("Adding: %s | Genres: %s | Network: %s | Country: %s", series['show']['title'], + ', '.join(series['show']['genres'] + ), series['show']['network'], + series['show']['country'].upper()) + + # determine which tags to use when adding this series + use_tags = sonarr_helper.series_tag_id_from_network(profile_tags, cfg.sonarr.tags, + series['show']['network']) + # add show to sonarr + if sonarr.add_series(series['show']['ids']['tvdb'], series['show']['title'], + series['show']['ids']['slug'], profile_id, cfg.sonarr.root_folder, use_tags, + not no_search): + log.info("ADDED %s (%d) with tags: %s", series['show']['title'], series['show']['year'], + sonarr_helper.readable_tag_from_ids(profile_tags, use_tags)) + if notifications: + callback_notify( + {'event': 'add_show', 'list_type': list_type, 'show': series['show']}) + added_shows += 1 + else: + log.error("FAILED adding %s (%d) with tags: %s", series['show']['title'], series['show']['year'], + sonarr_helper.readable_tag_from_ids(profile_tags, use_tags)) + + # stop adding shows, if added_shows >= add_limit + if add_limit and added_shows >= add_limit: + break + + # sleep before adding any more + time.sleep(add_delay) + + except Exception: + log.exception("Exception while processing show %s: ", + series['show']['title']) + + log.info("Added %d new show(s) to Sonarr", added_shows) + + # send notification + if notifications: + notify.send(message="Added %d shows from Trakt's %s list" % + (added_shows, list_type)) + + return added_shows + + +############################################################ +# MOVIES +############################################################ + +@app.command(help='Add a single movie to Radarr.') +@click.option('--movie_id', '-id', help='Trakt movie_id.', required=True) +@click.option('--folder', '-f', default=None, help='Add movie with this root folder to Radarr.') +@click.option('--no-search', is_flag=True, help='Disable search when adding movie to Radarr.') +def movie(movie_id, folder=None, no_search=False): + from media.radarr import Radarr + from media.trakt import Trakt + + # replace radarr root_folder if folder is supplied + if folder: + cfg['radarr']['root_folder'] = folder + + # validate trakt api_key + trakt = Trakt(cfg) + radarr = Radarr(cfg.radarr.url, cfg.radarr.api_key) + + validate_trakt(trakt, False) + validate_pvr(radarr, 'Radarr', False) + + profile_id = get_profile_id(radarr, cfg.radarr.profile) + + # get trakt movie + trakt_movie = trakt.get_movie(movie_id) + + if not trakt_movie: + log.error("Aborting due to failure to retrieve Trakt movie") + return None + else: + log.info("Retrieved Trakt movie information for %s: %s (%d)", movie_id, trakt_movie['title'], + trakt_movie['year']) + + # add movie to radarr + if radarr.add_movie(trakt_movie['ids']['tmdb'], trakt_movie['title'], trakt_movie['year'], + trakt_movie['ids']['slug'], profile_id, cfg.radarr.root_folder, not no_search): + log.info("ADDED %s (%d)", trakt_movie['title'], trakt_movie['year']) + else: + log.error("FAILED adding %s (%d)", + trakt_movie['title'], trakt_movie['year']) + + return + + +@app.command(help='Add multiple movies to Radarr.') +@click.option('--list-type', '-t', + help='Trakt list to process. For example, anticipated, trending, popular, boxoffice, person, watched, ' + 'recommended, played, watchlist or any URL to a list', required=True) +@click.option('--add-limit', '-l', default=0, help='Limit number of movies added to Radarr.', show_default=True) +@click.option('--add-delay', '-d', default=2.5, help='Seconds between each add request to Radarr.', show_default=True) +@click.option('--sort', '-s', default='votes', type=click.Choice(['votes', 'rating', 'release']), + help='Sort list to process.') +@click.option('--rating', '-r', default=None, type=(int), help='Set a minimum rating threshold (according to Rotten Tomatoes)') +@click.option('--genre', '-g', default=None, help='Only add movies from this genre to Radarr.') +@click.option('--folder', '-f', default=None, help='Add movies with this root folder to Radarr.') +@click.option('--actor', '-a', default=None, help='Only add movies from this actor to Radarr.') +@click.option('--no-search', is_flag=True, help='Disable search when adding movies to Radarr.') +@click.option('--notifications', is_flag=True, help='Send notifications.') +@click.option('--authenticate-user', + help='Specify which user to authenticate with to retrieve Trakt lists. Default: first user in the config.') +@click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.') +@click.option('--remove-rejected-from-recommended', is_flag=True, + help='Removes rejected/existing movies from recommended.') +def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', rating=None, genre=None, folder=None, actor=None, no_search=False, + notifications=False, authenticate_user=None, ignore_blacklist=False, remove_rejected_from_recommended=False): + from media.radarr import Radarr + from media.trakt import Trakt + from helpers import misc as misc_helper + from helpers import radarr as radarr_helper + from helpers import trakt as trakt_helper + from helpers import rating as rating_helper + + added_movies = 0 + + # remove genre from movies blacklisted_genres if supplied + if genre: + misc_helper.unblacklist_genres( + genre, cfg['filters']['movies']['blacklisted_genres']) + + # replace radarr root_folder if folder is supplied + if folder: + cfg['radarr']['root_folder'] = folder + + # validate trakt api_key + trakt = Trakt(cfg) + radarr = Radarr(cfg.radarr.url, cfg.radarr.api_key) + + validate_trakt(trakt, notifications) + validate_pvr(radarr, 'Radarr', notifications) + + profile_id = get_profile_id(radarr, cfg.radarr.profile) + + pvr_objects_list = get_objects(radarr, 'Radarr', notifications) + + # get trakt movies list + if list_type.lower() == 'anticipated': + trakt_objects_list = trakt.get_anticipated_movies( + genres=genre, languages=cfg.filters.movies.allowed_languages) + elif list_type.lower() == 'trending': + trakt_objects_list = trakt.get_trending_movies( + genres=genre, languages=cfg.filters.movies.allowed_languages) + elif list_type.lower() == 'popular': + trakt_objects_list = trakt.get_popular_movies( + genres=genre, languages=cfg.filters.movies.allowed_languages) + elif list_type.lower() == 'boxoffice': + trakt_objects_list = trakt.get_boxoffice_movies() + elif list_type.lower() == 'person': + if not actor: + log.error( + "You must specify an actor with the --actor / -a parameter when using the person list type!") + return None + trakt_objects_list = trakt.get_person_movies(person=actor, genres=genre, + languages=cfg.filters.movies.allowed_languages) + + elif list_type.lower() == 'recommended': + trakt_objects_list = trakt.get_recommended_movies(authenticate_user, genres=genre, + languages=cfg.filters.movies.allowed_languages) + elif list_type.lower().startswith('played'): + most_type = misc_helper.substring_after(list_type.lower(), "_") + trakt_objects_list = trakt.get_most_played_movies(genres=genre, languages=cfg.filters.movies.allowed_languages, + most_type=most_type if most_type else None) + elif list_type.lower().startswith('watched'): + most_type = misc_helper.substring_after(list_type.lower(), "_") + trakt_objects_list = trakt.get_most_watched_movies(genres=genre, languages=cfg.filters.movies.allowed_languages, + most_type=most_type if most_type else None) + elif list_type.lower() == 'watchlist': + trakt_objects_list = trakt.get_watchlist_movies(authenticate_user) + else: + trakt_objects_list = trakt.get_user_list_movies( + list_type, authenticate_user) + + if not trakt_objects_list: + log.error( + "Aborting due to failure to retrieve Trakt %s movies list", list_type) + if notifications: + callback_notify( + {'event': 'abort', 'type': 'movies', 'list_type': list_type, + 'reason': 'Failure to retrieve Trakt %s movies list' % list_type}) + return None + else: + log.info("Retrieved Trakt %s movies list, movies found: %d", + list_type, len(trakt_objects_list)) + + # set remove_rejected_recommended to False if this is not the recommended list + if list_type.lower() != 'recommended': + remove_rejected_from_recommended = False + + # build filtered movie list without movies that exist in radarr + processed_movies_list = radarr_helper.remove_existing_movies(pvr_objects_list, trakt_objects_list, + callback_remove_recommended + if remove_rejected_from_recommended else None) + if processed_movies_list is None: + log.error( + "Aborting due to failure to remove existing Radarr movies from retrieved Trakt movies list") + if notifications: + callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type, + 'reason': 'Failure to remove existing Radarr movies from retrieved ' + 'Trakt %s movies list' % list_type}) + return None + else: + log.info("Removed existing Radarr movies from Trakt movies list, movies left to process: %d", + len(processed_movies_list)) + + # sort filtered movie list + if sort == 'release': + sorted_movies_list = misc_helper.sorted_list( + processed_movies_list, 'movie', 'released') + log.info("Sorted movies list to process by release date") + elif sort == 'rating': + sorted_movies_list = misc_helper.sorted_list( + processed_movies_list, 'movie', 'rating') + log.info("Sorted movies list to process by highest rating") + else: + sorted_movies_list = misc_helper.sorted_list( + processed_movies_list, 'movie', 'votes') + log.info("Sorted movies list to process by highest votes") + + # loop movies + log.info("Processing list now...") + for movie in sorted_movies_list: + try: + # check if genre matches genre supplied via argument + if genre and not misc_helper.allowed_genres(genre, 'movie', movie): + log.debug("Skipping: %s because it was not from %s genre(s)", + movie['movie']['title'], genre.lower()) + continue + + # check if movie passes out blacklist criteria inspection + if not trakt_helper.is_movie_blacklisted(movie, cfg.filters.movies, ignore_blacklist, + callback_remove_recommended if remove_rejected_from_recommended + else None): + # Assuming the movie is not blacklisted, proceed to pull RT score if the user wishes to restrict + movieRating = None + if (rating != None and cfg['omdb']['api_key'] != ''): + movieRating = rating_helper.get_rating( + cfg['omdb']['api_key'], movie) + if (movieRating == -1): + log.debug("Skipping: %s because it did not have a rating/lacked imdbID", + movie['movie']['title']) + continue + if (rating == None or movieRating >= rating): + log.info("Adding: %s (%d) | Genres: %s | Country: %s", movie['movie']['title'], movie['movie']['year'], + ', '.join(movie['movie']['genres']), movie['movie']['country'].upper()) + # add movie to radarr + if radarr.add_movie(movie['movie']['ids']['tmdb'], movie['movie']['title'], movie['movie']['year'], + movie['movie']['ids']['slug'], profile_id, cfg.radarr.root_folder, not no_search): + log.info( + "ADDED %s (%d)", movie['movie']['title'], movie['movie']['year']) + if notifications: + callback_notify( + {'event': 'add_movie', 'list_type': list_type, 'movie': movie['movie']}) + added_movies += 1 + else: + log.error("FAILED adding %s (%d)", + movie['movie']['title'], movie['movie']['year']) + else: + log.info("SKIPPING: %s (%d) | Genres: %s | Country: %s", movie['movie']['title'], + movie['movie']['year'], + ', '.join(movie['movie']['genres']), movie['movie']['country'].upper()) + # stop adding movies, if added_movies >= add_limit + if add_limit and added_movies >= add_limit: + break + + # sleep before adding any more + time.sleep(add_delay) + + except Exception: + log.exception("Exception while processing movie %s: ", + movie['movie']['title']) + + log.info("Added %d new movie(s) to Radarr", added_movies) + + # send notification + if notifications: + notify.send(message="Added %d movies from Trakt's %s list" % + (added_movies, list_type)) + + return added_movies + + +############################################################ +# CALLBACKS +############################################################ + + +def callback_remove_recommended(media_type, media_info): + from media.trakt import Trakt + + trakt = Trakt(cfg) + + if not media_info[media_type]['title'] or not media_info[media_type]['year']: + log.debug("Skipping removing %s item from recommended list as no title/year was available:\n%s", media_type, + media_info) + return + + media_name = '%s (%d)' % ( + media_info[media_type]['title'], media_info[media_type]['year']) + + if trakt.remove_recommended_item(media_type, media_info[media_type]['ids']['trakt']): + log.info("Removed rejected recommended %s: %s", media_type, media_name) + else: + log.info("FAILED removing rejected recommended %s: %s", + media_type, media_name) + + +def callback_notify(data): + log.debug("Received callback data: %s", data) + + # handle event + if data['event'] == 'add_movie': + if cfg.notifications.verbose: + notify.send( + message="Added %s movie: %s (%d)" % (data['list_type'], data['movie']['title'], data['movie']['year'])) + return + elif data['event'] == 'add_show': + if cfg.notifications.verbose: + notify.send( + message="Added %s show: %s (%d)" % (data['list_type'], data['show']['title'], data['show']['year'])) + return + elif data['event'] == 'abort': + notify.send(message="Aborted adding Trakt %s %s due to: %s" % + (data['list_type'], data['type'], data['reason'])) + return + elif data['event'] == 'error': + notify.send(message="Error: %s" % data['reason']) + return + else: + log.error("Unexpected callback: %s", data) + return + + +############################################################ +# AUTOMATIC +############################################################ + + +def automatic_shows(add_delay=2.5, sort='votes', no_search=False, notifications=False, ignore_blacklist=False): + from media.trakt import Trakt + + total_shows_added = 0 + try: + log.info("Started") + + for list_type, value in cfg.automatic.shows.items(): + added_shows = None + + if list_type.lower() == 'interval': + continue + + if list_type.lower() in Trakt.non_user_lists or ( + '_' in list_type and list_type.lower().partition("_")[0] in Trakt.non_user_lists): + limit = value + + if limit <= 0: + log.info("Skipped Trakt's %s shows list", list_type) + continue + else: + log.info("Adding %d shows from Trakt's %s list", + limit, list_type) + + local_ignore_blacklist = ignore_blacklist + + if list_type.lower() in cfg.filters.shows.disabled_for: + local_ignore_blacklist = True + + # run shows + added_shows = shows.callback(list_type=list_type, add_limit=limit, + add_delay=add_delay, sort=sort, no_search=no_search, + notifications=notifications, ignore_blacklist=local_ignore_blacklist) + elif list_type.lower() == 'watchlist': + for authenticate_user, limit in value.items(): + if limit <= 0: + log.info("Skipped Trakt's %s for %s", + list_type, authenticate_user) + continue + else: + log.info("Adding %d shows from the %s from %s", + limit, list_type, authenticate_user) + + local_ignore_blacklist = ignore_blacklist + + if "watchlist:%s".format(authenticate_user) in cfg.filters.shows.disabled_for: + local_ignore_blacklist = True + + # run shows + added_shows = shows.callback(list_type=list_type, add_limit=limit, + add_delay=add_delay, sort=sort, no_search=no_search, + notifications=notifications, authenticate_user=authenticate_user, + ignore_blacklist=local_ignore_blacklist) + elif list_type.lower() == 'lists': + for list, v in value.items(): + if isinstance(v, dict): + authenticate_user = v['authenticate_user'] + limit = v['limit'] + else: + authenticate_user = None + limit = v + + local_ignore_blacklist = ignore_blacklist + + if "list:%s".format(list) in cfg.filters.shows.disabled_for: + local_ignore_blacklist = True + + # run shows + added_shows = shows.callback(list_type=list, add_limit=limit, + add_delay=add_delay, sort=sort, no_search=no_search, + notifications=notifications, authenticate_user=authenticate_user, + ignore_blacklist=local_ignore_blacklist) + + if added_shows is None: + log.error("Failed adding shows from Trakt's %s list", list_type) + time.sleep(10) + continue + total_shows_added += added_shows + + # sleep + time.sleep(10) + + log.info("Finished, added %d shows total to Sonarr!", total_shows_added) + # send notification + if notifications: + notify.send(message="Added %d shows total to Sonarr!" % + total_shows_added) + + except Exception: + log.exception("Exception while automatically adding shows: ") + return + + +def automatic_movies(add_delay=2.5, sort='votes', no_search=False, notifications=False, ignore_blacklist=False, rating_limit=None): + from media.trakt import Trakt + + total_movies_added = 0 + try: + log.info("Started") + + for list_type, value in cfg.automatic.movies.items(): + added_movies = None + + if list_type.lower() == 'interval': + continue + + if list_type.lower() in Trakt.non_user_lists or ( + '_' in list_type and list_type.lower().partition("_")[0] in Trakt.non_user_lists): + limit = value + + if limit <= 0: + log.info("Skipped Trakt's %s movies list", list_type) + continue + else: + log.info("Adding %d movies from Trakt's %s list", + limit, list_type) + + local_ignore_blacklist = ignore_blacklist + + if list_type.lower() in cfg.filters.movies.disabled_for: + local_ignore_blacklist = True + + # run movies + added_movies = movies.callback(list_type=list_type, add_limit=limit, + add_delay=add_delay, sort=sort, no_search=no_search, + notifications=notifications, rating=rating_limit) + elif list_type.lower() == 'watchlist': + for authenticate_user, limit in value.items(): + if limit <= 0: + log.info("Skipped Trakt's %s for %s", + list_type, authenticate_user) + continue + else: + log.info("Adding %d movies from the %s from %s", + limit, list_type, authenticate_user) + + local_ignore_blacklist = ignore_blacklist + + if "watchlist:%s".format(authenticate_user) in cfg.filters.movies.disabled_for: + local_ignore_blacklist = True + + # run movies + added_movies = movies.callback(list_type=list_type, add_limit=limit, + add_delay=add_delay, sort=sort, no_search=no_search, + notifications=notifications, authenticate_user=authenticate_user, + ignore_blacklist=local_ignore_blacklist, rating=rating_limit) + elif list_type.lower() == 'lists': + for list, v in value.items(): + if isinstance(v, dict): + authenticate_user = v['authenticate_user'] + limit = v['limit'] + else: + authenticate_user = None + limit = v + + local_ignore_blacklist = ignore_blacklist + + if "list:%s".format(list) in cfg.filters.movies.disabled_for: + local_ignore_blacklist = True + + # run shows + added_movies = movies.callback(list_type=list, add_limit=limit, + add_delay=add_delay, sort=sort, no_search=no_search, + notifications=notifications, authenticate_user=authenticate_user, + ignore_blacklist=local_ignore_blacklist, rating=rating_limit) + + if added_movies is None: + log.error("Failed adding movies from Trakt's %s list", list_type) + time.sleep(10) + continue + total_movies_added += added_movies + + # sleep + time.sleep(10) + + log.info("Finished, added %d movies total to Radarr!", + total_movies_added) + # send notification + if notifications: + notify.send(message="Added %d movies total to Radarr!" % + total_movies_added) + + except Exception: + log.exception("Exception while automatically adding movies: ") + return + + +@app.command(help='Run in automatic mode.') +@click.option('--add-delay', '-d', default=2.5, help='Seconds between each add request to Sonarr / Radarr.', + show_default=True) +@click.option('--sort', '-s', default='votes', type=click.Choice(['votes', 'rating', 'release']), + help='Sort list to process.') +@click.option('--no-search', is_flag=True, help='Disable search when adding to Sonarr / Radarr.') +@click.option('--run-now', is_flag=True, help="Do a first run immediately without waiting.") +@click.option('--no-notifications', is_flag=True, help="Disable notifications.") +@click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.') +def run(add_delay=2.5, sort='votes', no_search=False, run_now=False, no_notifications=False, ignore_blacklist=False): + log.info("Automatic mode is now running...") + + # Add tasks to schedule and do first run if enabled + if cfg.automatic.movies.interval: + movie_schedule = schedule.every(cfg.automatic.movies.interval).hours.do( + automatic_movies, + add_delay, + sort, + no_search, + not no_notifications, + ignore_blacklist, + int(cfg.filters.movies.rating_limit) if cfg.filters.movies.rating_limit != "" else None + ) + if run_now: + movie_schedule.run() + + # Sleep between tasks + time.sleep(add_delay) + + if cfg.automatic.shows.interval: + shows_schedule = schedule.every(cfg.automatic.shows.interval).hours.do( + automatic_shows, + add_delay, + sort, + no_search, + not no_notifications, + ignore_blacklist + ) + if run_now: + shows_schedule.run() + + # Sleep between tasks + time.sleep(add_delay) + + # Enter running schedule + while True: + try: + # Sleep until next run + log.info("Next job at %s", schedule.next_run()) + time.sleep(max(schedule.idle_seconds(), 0)) + # Check jobs to run + schedule.run_pending() + + except Exception as e: + log.exception( + "Unhandled exception occurred while processing scheduled tasks: %s", e) + time.sleep(1) + + +############################################################ +# MISC +############################################################ + +def init_notifications(): + try: + for notification_name, notification_config in cfg.notifications.items(): + if notification_name.lower() == 'verbose': + continue + + notify.load(**notification_config) + except Exception: + log.exception("Exception initializing notification agents: ") + return + + +# Handles exit signals, cancels jobs and exits cleanly +def exit_handler(signum, frame): + log.info("Received %s, canceling jobs and exiting.", + signal.Signals(signum).name) + schedule.clear() + exit() + + +############################################################ +# MAIN +############################################################ + +if __name__ == "__main__": + + print("") + print("""PGTrakt Started + +""") + + # Register the signal handlers + signal.signal(signal.SIGTERM, exit_handler) + signal.signal(signal.SIGINT, exit_handler) + + # Start application + app() diff --git a/menu/pgtrakt/pgtrakt.sh b/menu/pgtrakt/pgtrakt.sh new file mode 100644 index 00000000..d78b7ab2 --- /dev/null +++ b/menu/pgtrakt/pgtrakt.sh @@ -0,0 +1,581 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# KEY VARIABLE RECALL & EXECUTION +mkdir -p /var/plexguide/pgtrakt + +# FUNCTIONS START ############################################################## + +# FIRST FUNCTION +variable() { + file="$1" + if [ ! -e "$file" ]; then echo "$2" >$1; fi +} + +deploycheck() { + dcheck=$(systemctl status pgtrakt | grep "\(running\)\>" | grep "\") + if [ "$dcheck" != "" ]; then + dstatus="✅ DEPLOYED" + else dstatus="⚠️ NOT DEPLOYED"; fi +} + +sonarrcheck() { + pcheck=$(docker ps | grep "\") + if [ "$pcheck" == "" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - Sonarr is not Installed/Running! Cannot Proceed! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -p 'Confirm Info | PRESS [ENTER] ' typed ") + if [ "$pcheck" == "" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - Radarr is not Installed/Running! Cannot Proceed! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + read -p 'Confirm Info | PRESS [ENTER] ' typed exit +EOF + read -p '↘️ Type Sonarr Location | Press [ENTER]: ' typed /var/plexguide/pgtrak.sprofile + read -p '🌎 Acknowledge Info | Press [ENTER] ' typed exit +EOF + read -p '↘️ Type Radarr Location | Press [ENTER]: ' typed /var/plexguide/pgtrak.rprofile + read -p '🌎 Acknowledge Info | Press [ENTER] ' typed exit +EOF + read -p '↘️ Type API Client | Press [ENTER]: ' typed /var/plexguide/pgtrak.client + read -p '↘️ Type API Secret | Press [ENTER]: ' typed /var/plexguide/pgtrak.secret + + if [[ "$typed" == "exit" || "$typed" == "Exit" || "$typed" == "EXIT" || "$typed" == "z" || "$typed" == "Z" ]]; then + question1 + else + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅ SYSTEM MESSAGE: PGTrak API Notice +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +NOTE: The API Client and Secret is set! Ensure to setup your and + prior to deploying PGTrak. + +INFO: Messed up? Rerun this API Interface to update the information! + +EOF + + read -p '🌎 Acknowledge Info | Press [ENTER] ' typed exit +EOF + read -p '↘️ Type Sonarr Location | Press [ENTER]: ' typed /dev/null 2>&1 + + file="$typed/test" + if [ -e "$file" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅ SYSTEM MESSAGE: Sonarr Path Completed! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + + ### Removes /mnt if /mnt/unionfs exists + #check=$(echo $typed | head -c 12) + #if [ "$check" == "/mnt/unionfs" ]; then + #typed=${typed:4} + #fi + + echo "$typed" >/var/plexguide/pgtrak.spath + read -p '🌎 Acknowledge Info | Press [ENTER] ' typed >> mkdir $typed/testfolder + +EOF + read -p '🌎 Acknowledge Info | Press [ENTER] ' typed exit +EOF + read -p '↘️ Type Radarr Location | Press [ENTER]: ' typed /dev/null 2>&1 + + file="$typed/test" + if [ -e "$file" ]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅ SYSTEM MESSAGE: Radarr Path Completed! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + + ### Removes /mnt if /mnt/unionfs exists + #check=$(echo $typed | head -c 12) + #if [ "$check" == "/mnt/unionfs" ]; then + #typed=${typed:4} + #fi + + echo "$typed" >/var/plexguide/pgtrak.rpath + read -p '🌎 Acknowledge Info | Press [ENTER] ' typed >> mkdir $typed/testfolder + +EOF + read -p '🌎 Acknowledge Info | Press [ENTER] ' typed /var/plexguide/pgtrakt/video.transcodes && question1 + elif [ "$typed" == "2" ]; then + echo "True" >/var/plexguide/pgtrakt/video.transcodes && question1 + else badinput; fi +} + +selection2() { + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 Limit Amount of Different IPs a User Can Make? +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚡ Reference: http://pgtrakt.pgblitz.com + +Set a Number from [1] 99 + +EOF + read -p 'Type Number | PRESS [ENTER] ' typed /var/plexguide/pgtrakt/multiple.ips && question1 + else badinput; fi +} + +selection3() { + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 Limit How Long a User Can Pause For! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚡ Reference: http://pgtrakt.pgblitz.com + +Set a Number from [5] 999 Mintues + +EOF + read -p 'Type Number | PRESS [ENTER] ' typed /var/plexguide/pgtrakt/kick.minutes && question1 + else badinput; fi +} + +# FIRST QUESTION +question1() { + + api=$(cat /var/plexguide/pgtrak.secret) + if [ "$api" == "NOT-SET" ]; then api="NOT-SET"; else api="SET"; fi + + rpath=$(cat /var/plexguide/pgtrak.rpath) + spath=$(cat /var/plexguide/pgtrak.spath) + rprofile=$(cat /var/plexguide/pgtrak.rprofile) + sprofile=$(cat /var/plexguide/pgtrak.sprofile) + deploycheck + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 PGTrakt Interface +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⚡ Reference: http://pgtrakt.pgblitz.com + +NOTE: Changes Made? Must Redeploy PGTrak when Complete! + +[1] Trakt API-Key [$api] +[2] Sonarr Path [$spath] +[3] Raddar Path [$rpath] +[4] Sonarr Profile [$sprofile] +[5] Radarr Profile [$rprofile] +[6] Deploy PGTrak [$dstatus] +Z - Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + read -p '↘️ Type Number | Press [ENTER]: ' typed } 1>/dev/null 2>&1 + info1=$(echo ${info:0:32}) 1>/dev/null 2>&1 + echo "$info1" >/var/plexguide/pgtrak.rapi + fi + + file="/opt/appdata/sonarr/config.xml" + if [ -e "$file" ]; then + info=$(cat /opt/appdata/sonarr/config.xml) + info=${info#*} 1>/dev/null 2>&1 + info2=$(echo ${info:0:32}) 1>/dev/null 2>&1 + echo "$info2" >/var/plexguide/pgtrak.sapi + fi + fi + # keys for sonarr and radarr need to be added + ansible-playbook /opt/plexguide/menu/pgtrakt/pgtrakt.yml && question1 + + elif [[ "$typed" == "Z" || "$typed" == "z" ]]; then + exit + else badinput; fi +} + +# FUNCTIONS END ############################################################## +token +variable /var/plexguide/pgtrak.client "NOT-SET" +variable /var/plexguide/pgtrak.secret "NOT-SET" +variable /var/plexguide/pgtrak.rpath "NOT-SET" +variable /var/plexguide/pgtrak.spath "NOT-SET" +variable /var/plexguide/pgtrak.sprofile "NOT-SET" +variable /var/plexguide/pgtrak.rprofile "NOT-SET" +variable /var/plexguide/pgtrak.rprofile "NOT-SET" + +deploycheck +question1 diff --git a/menu/pgtrakt/pgtrakt.yml b/menu/pgtrakt/pgtrakt.yml new file mode 100644 index 00000000..0b8dc3e6 --- /dev/null +++ b/menu/pgtrakt/pgtrakt.yml @@ -0,0 +1,140 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# YML Author: Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +# Origin: https://github.com/l3uddz/plex_patrol +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + # FACTS ###################################################################### + - name: 'Set Known Facts' + set_fact: + pgrole: 'pgtrakt' + extport: '0' + + - name: Server ID + shell: 'cat /var/plexguide/pg.serverid' + register: serverid + + - name: Token Recall + shell: 'cat /var/plexguide/plex.token' + register: plextoken + + - name: Recall User + shell: 'cat /var/plexguide/plex.user' + register: user + + - name: Register IP + shell: 'cat /var/plexguide/server.ip' + register: ipaddress + + - name: Sonarr API + shell: 'cat /var/plexguide/pgtrak.sapi' + register: sapi + ignore_errors: True + + - name: Radarr API + shell: 'cat /var/plexguide/pgtrak.rapi' + register: rapi + ignore_errors: True + + - name: Sonarr Profile + shell: 'cat /var/plexguide/pgtrak.sprofile' + register: sprofile + ignore_errors: True + + - name: Radarr Profile + shell: 'cat /var/plexguide/pgtrak.rprofile' + register: rprofile + ignore_errors: True + + - name: Trakt.tv Client + shell: 'cat /var/plexguide/pgtrak.client' + register: client + ignore_errors: True + + - name: Trakt.tv Secret + shell: 'cat /var/plexguide/pgtrak.secret' + register: secret + ignore_errors: True + + - name: Radarr Path + shell: 'cat /var/plexguide/pgtrak.rpath' + register: rpath + ignore_errors: True + + - name: API Sonnar + shell: 'cat /var/plexguide/pgtrak.spath' + register: spath + ignore_errors: True + + - name: Install pip requirements + pip: + requirements: /opt/plexguide/menu/pgtrakt/requirements.txt + executable: pip3 + + - name: 'Including cron job' + include_tasks: '/opt/coreapps/apps/_core.yml' + + - name: Create Basic Directories + file: 'path={{item}} state=directory mode=0775 owner=1000 group=1000' + with_items: + - '/opt/appdata/pgtrakt/' + + - name: Transfer Files + copy: + src: /opt/plexguide/menu/pgtrakt + dest: /opt/appdata + owner: '1000' + group: '1000' + mode: a+x + force: yes + + - name: Import default config + template: + src: /opt/appdata/pgtrakt/config.json.sample + dest: /opt/appdata/pgtrakt/config.json + owner: '1000' + group: '1000' + mode: 0775 + force: yes + + - name: Set pgtrak.py as executable + file: + path: /opt/appdata/pgtrakt/pgtrakt.py + owner: '1000' + group: '1000' + mode: a+x + + - name: 'Create /usr/local/bin symlink' + file: + src: '/opt/appdata/pgtrakt/pgtrakt.py' + dest: '/bin/pgtrakt' + state: link + + - name: Check Service's Existance + stat: + path: '/etc/systemd/systemd/pgtrakt.service' + register: pgp + + - name: Stop service + service: + name: pgtrakt + state: stopped + when: pgp.stat.exists + + - name: PGTrakt Service + template: + src: /opt/appdata/pgtrakt/systemd/pgtrakt.service + dest: /etc/systemd/system/pgtrakt.service + force: yes + + - name: Daemon-Reload + systemd: state=stopped name=pgtrakt daemon_reload=yes enabled=no + + - name: Start PGTrakt + systemd: state=started name=pgtrakt enabled=yes diff --git a/menu/pgtrakt/requirements.txt b/menu/pgtrakt/requirements.txt new file mode 100644 index 00000000..80660023 --- /dev/null +++ b/menu/pgtrakt/requirements.txt @@ -0,0 +1,6 @@ +backoff==1.5.0 +schedule==0.5.0 +attrdict==2.0.0 +click==6.7 +requests~=2.20.0 +pyfiglet diff --git a/menu/pgtrakt/systemd/pgtrakt.service b/menu/pgtrakt/systemd/pgtrakt.service new file mode 100644 index 00000000..51394c84 --- /dev/null +++ b/menu/pgtrakt/systemd/pgtrakt.service @@ -0,0 +1,15 @@ +# /etc/systemd/system/pgtrakt.service + +[Unit] +Description=pgtrakt +After=network-online.target + +[Service] +Type=simple +WorkingDirectory=/opt/appdata/pgtrakt +ExecStart=/usr/bin/python3 /opt/appdata/pgtrakt/pgtrakt.py run +Restart=always +RestartSec=10 + +[Install] +WantedBy=default.target diff --git a/menu/pgui/gtused.sh b/menu/pgui/gtused.sh new file mode 100644 index 00000000..068f36df --- /dev/null +++ b/menu/pgui/gtused.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# +# Title: PGBlitz (G/TDrive used space) +# Author(s): Admin9705 +# Coder: MrDoob +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +#functions +config="/opt/appdata/plexguide/rclone.conf" +log="/var/plexguide" +useragent="$(cat /var/plexguide/uagent)" + +#if else loop for checking what is running + +if grep -q "gdrive:" $config; then + rclone size gdrive: \ + --verbose=1 \ + --fast-list \ + --retries 3 \ + --no-update-modtime \ + --user-agent="$useragent" \ + --timeout=30m \ + --exclude="**encrypt**" \ + --config /opt/appdata/plexguide/rclone.conf | awk '{print $3,$4}' >>$log/gduncrypt.log + sed -i 's/Total size: / /g' $log/gduncrypt.log +fi +sleep 2 +if grep -q "tdrive:" $config; then + rclone size tdrive: \ + --verbose=1 \ + --fast-list \ + --retries 3 \ + --no-update-modtime \ + --user-agent="$useragent" \ + --timeout=30m \ + --exclude="**encrypt**" \ + --config /opt/appdata/plexguide/rclone.conf | awk '{print $3,$4}' >>$log/tduncrypt.log + sed -i 's/Total size: / /g' $log/tduncrypt.log +fi +sleep 2 +if grep -q "gcrypt:" $config; then + rclone size gcrypt: \ + --verbose=1 \ + --fast-list \ + --retries 3 \ + --user-agent="$useragent" \ + --no-update-modtime \ + --timeout=30m \ + --config /opt/appdata/plexguide/rclone.conf | awk '{print $3,$4}' >>$log/gdcrypt.log + sed -i 's/Total size: / /g' $log/gdcrypt.log +fi +sleep 2 +if grep -q "tcrypt:" $config; then + rclone size tcrypt: \ + --verbose=1 \ + --fast-list \ + --retries 3 \ + --user-agent="$useragent" \ + --no-update-modtime \ + --timeout=30m \ + --config /opt/appdata/plexguide/rclone.conf | awk '{print $3,$4}' >>$log/tdcrypt.log + sed -i 's/Total size: / /g' $log/tdcrypt.log +fi diff --git a/menu/pgui/localspace.service b/menu/pgui/localspace.service new file mode 100644 index 00000000..3020a1e2 --- /dev/null +++ b/menu/pgui/localspace.service @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Title: PGBlitz (G/TDrive used space) +# Author(s): Admin9705 +# Coder: MrDoob +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +[Unit] +Description='PGui Used space | localspace ' +After=multi-user.target + +[Service] +User=0 +Group=0 +Type=simple +ExecStart=/bin/bash /opt/plexguide/menu/pgui/localspace.sh +KillMode=process +RemainAfterExit=20sec +TimeoutStopSec=20sec + +[Install] +WantedBy=multi-user.target diff --git a/menu/pgui/localspace.sh b/menu/pgui/localspace.sh new file mode 100644 index 00000000..11a68fc5 --- /dev/null +++ b/menu/pgui/localspace.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# +# Title: PGBlitz (local used space) +# Author(s): Admin9705 +# Coder: MrDoob +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# Starting Actions +#networktools | vnstat | vnstati install | please wait" +apt-get install ethtool vnstat vnstati -yqq 2>&1 >>/dev/null +network="$(ifconfig | grep -E 'eno1|enp|ens5' | awk '{print $1}' | sed -e 's/://g')" +sed -i 's/eth0/'$network'/g' /etc/vnstat.conf +sed -i 's/UnitMode 0/UnitMode 1/g' /etc/vnstat.conf +sed -i 's/RateUnit 1/RateUnit 0/g' /etc/vnstat.conf +sed -i 's/Locale "-"/Locale "LC_ALL=en_US.UTF-8"/g' /etc/vnstat.conf +/etc/init.d/vnstat restart 2>&1 >>/dev/null + +startscript() { + + while [ 1 ]; do + + rm -rf /var/plexguide/spaceused.log + rm -rf /var/plexguide/logs/trafficused.log + rm -rf /var/plexguide/logs/incomplete-used.log + + # move and downloads for the UI + + du -sh /mnt/move | awk '{print $1}' >>/var/plexguide/spaceused.log + du -sh /mnt/downloads | awk '{print $1}' >>/var/plexguide/spaceused.log + + echo "Used Traffic | last 7 days" >>/var/plexguide/logs/trafficused.log + + vnstat -d | tail -n 10 | head -n 8 >>/var/plexguide/logs/trafficused.log + + #used space of incomplete + + du -sh /mnt/incomplete | awk '{print $1}' >>/var/plexguide/logs/incomplete-used.log + + sleep 60 + + done + +} + +# keeps the function in a loop +cheeseballs=0 +while [[ "$cheeseballs" == "0" ]]; do startscript; done diff --git a/menu/pgui/localspace.yml b/menu/pgui/localspace.yml new file mode 100644 index 00000000..ebff1542 --- /dev/null +++ b/menu/pgui/localspace.yml @@ -0,0 +1,30 @@ +#!/bin/bash +# +# Title: PGBlitz (local space used service) +# YML Author: Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Check Service's Existance + stat: + path: '/etc/systemd/systemd/localspace.service' + register: pgp + - name: Stop service + service: + name: localspace + state: stopped + when: pgp.stat.exists + - name: localspace Service + template: + src: /opt/plexguide/menu/pgui/localspace.service + dest: /etc/systemd/system/localspace.service + force: yes + + - name: Daemon-Reload + systemd: state=stopped name=localspace daemon_reload=yes enabled=no + - name: Start pgscan + systemd: state=started name=localspace enabled=yes diff --git a/menu/pgui/mcdeploy.yml b/menu/pgui/mcdeploy.yml new file mode 100644 index 00000000..962575b8 --- /dev/null +++ b/menu/pgui/mcdeploy.yml @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# YML Author: Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: Check Service's Existance + stat: + path: '/etc/systemd/systemd/mountcheck.service' + register: pgp + + - name: Stop service + service: + name: mountcheck + state: stopped + when: pgp.stat.exists + + - name: MountCheck Service + template: + src: /opt/plexguide/menu/pgui/mountcheck.service + dest: /etc/systemd/system/mountcheck.service + force: yes + + - name: Daemon-Reload + systemd: state=stopped name=mountcheck daemon_reload=yes enabled=no + + - name: Start pgscan + systemd: state=started name=mountcheck enabled=yes diff --git a/menu/pgui/mountcheck.service b/menu/pgui/mountcheck.service new file mode 100644 index 00000000..5e103519 --- /dev/null +++ b/menu/pgui/mountcheck.service @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +[Unit] +Description=MountCheck Service Daemon +After=multi-user.target + +[Service] +Type=simple +User=0 +Group=0 +ExecStart=/bin/bash /opt/plexguide/menu/pgui/mountcheck.sh +TimeoutStopSec=20 +KillMode=process +RemainAfterExit=yes +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/menu/pgui/mountcheck.sh b/menu/pgui/mountcheck.sh new file mode 100644 index 00000000..ae33ad8c --- /dev/null +++ b/menu/pgui/mountcheck.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +mkdir -p /opt/appdata/plexguide/emergency +mkdir -p /opt/appdata/plexguide +rm -rf /opt/appdata/plexguide/emergency/* +sleep 15 +diskspace27=0 + +while true; do + + gdrivecheck=$(systemctl is-active gdrive) + gcryptcheck=$(systemctl is-active gcrypt) + tdrivecheck=$(systemctl is-active tdrive) + tcryptcheck=$(systemctl is-active tcrypt) + pgunioncheck=$(systemctl is-active pgunion) + pgblitzcheck=$(systemctl is-active pgblitz) + + # Todo remove the dupes or change to crypt once PGUI is updated + + if [[ "$gdrivecheck" != "active" ]]; then + echo "🔴 Not Operational" >/var/plexguide/pg.gdrive + else echo "✅ Operational" >/var/plexguide/pg.gdrive; fi + + if [[ "$gdrivecheck" != "active" ]]; then + echo "🔴 Not Operational" >/var/plexguide/pg.gmount + else echo "✅ Operational" >/var/plexguide/pg.gmount; fi + + if [[ "$tdrivecheck" != "active" ]]; then + echo "🔴 Not Operational " >/var/plexguide/pg.tdrive + else echo "✅ Operational" >/var/plexguide/pg.tdrive; fi + + if [[ "$tdrivecheck" != "active" ]]; then + echo "🔴 Not Operational " >/var/plexguide/pg.tmount + else echo "✅ Operational" >/var/plexguide/pg.tmount; fi + + if [[ "$pgunioncheck" != "active" ]]; then + echo "🔴 Not Operational " >/var/plexguide/pg.union + else echo "✅ Operational " >/var/plexguide/pg.union; fi + + if [[ "$pgunioncheck" != "active" ]]; then + echo "🔴 Not Operational " >/var/plexguide/pg.umount + else echo "✅ Operational " >/var/plexguide/pg.umount; fi + + # Disk Calculations - 5000000 = 5GB + + leftover=$(df /opt/appdata/plexguide | tail -n +2 | awk '{print $4}') + + if [[ "$leftover" -lt "5000000" ]]; then + diskspace27=1 + echo "Emergency: Primary DiskSpace Under 5GB - Stopped Downloading Programs (i.e. NZBGET, RuTorrent)" >/opt/appdata/plexguide/emergency/message.1 + docker stop nzbget 1>/dev/null 2>&1 + docker stop sabnzbd 1>/dev/null 2>&1 + docker stop rutorrent 1>/dev/null 2>&1 + docker stop deluge 1>/dev/null 2>&1 + docker stop qbittorrent 1>/dev/null 2>&1 + docker stop deluge-vpn 1>/dev/null 2>&1 + docker stop transmission 1>/dev/null 2>&1 + docker stop rflood-vpn 1>/dev/null 2>&1 + docker stop rutorrent-vpn 1>/dev/null 2>&1 + docker stop transmission-vpn 1>/dev/null 2>&1 + docker stop jdownloader2 1>/dev/null 2>&1 + docker stop jd2-openvpn 1>/dev/null 2>&1 + elif [[ "$leftover" -gt "3000000" && "$diskspace27" == "1" ]]; then + docker start nzbget 1>/dev/null 2>&1 + docker start sabnzbd 1>/dev/null 2>&1 + docker start rutorrent 1>/dev/null 2>&1 + docker start deluge 1>/dev/null 2>&1 + docker start qbittorrent 1>/dev/null 2>&1 + docker start deluge-vpn 1>/dev/null 2>&1 + docker start transmission 1>/dev/null 2>&1 + docker start rflood-vpn 1>/dev/null 2>&1 + docker start rutorrent-vpn 1>/dev/null 2>&1 + docker start transmission-vpn 1>/dev/null 2>&1 + docker start jdownloader2 1>/dev/null 2>&1 + docker start jd2-openvpn 1>/dev/null 2>&1 + rm -rf /opt/appdata/plexguide/emergency/message.1 + diskspace27=0 + fi + + ##### Warning for Ports Open with Traefik Deployed + if [[ $(cat /var/plexguide/pg.ports) != "Closed" && $(docker ps --format '{{.Names}}' | grep "traefik") == "traefik" ]]; then + echo "Warning: Traefik deployed with ports open! Server at risk for explotation!" >/opt/appdata/plexguide/emergency/message.a + elif [ -e "/opt/appdata/plexguide/emergency/message.a" ]; then rm -rf /opt/appdata/plexguide/emergency/message.a; fi + + if [[ $(cat /var/plexguide/pg.ports) == "Closed" && $(docker ps --format '{{.Names}}' | grep "traefik") == "" ]]; then + echo "Warning: Apps Cannot Be Accessed! Ports are Closed & Traefik is not enabled! Either deploy traefik or open your ports (which is worst for security)" >/opt/appdata/plexguide/emergency/message.b + elif [ -e "/opt/appdata/plexguide/emergency/message.b" ]; then rm -rf /opt/appdata/plexguide/emergency/message.b; fi + ##### Warning for Bad Traefik Deployment - message.c is tied to traefik showing a status! Do not change unless you know what your doing + touch /opt/appdata/plexguide/traefik.check + domain=$(cat /var/plexguide/server.domain) + + cname="portainer" + if [[ -f "/var/plexguide/portainer.cname" ]]; then + cname=$(cat "/var/plexguide/portainer.cname") + fi + + wget -q "https://${cname}.${domain}" -O "/opt/appdata/plexguide/traefik.check" + if [[ $(cat /opt/appdata/plexguide/traefik.check) == "" && $(docker ps --format '{{.Names}}' | grep traefik) == "traefik" ]]; then + echo "Traefik is Not Deployed Properly! Cannot Reach the Portainer SubDomain!" >/opt/appdata/plexguide/emergency/message.c + else + if [ -e "/opt/appdata/plexguide/emergency/message.c" ]; then + rm -rf /opt/appdata/plexguide/emergency/message.c + fi + fi + ##### Warning for Traefik Rate Limit Exceeded + if [[ $(cat /opt/appdata/plexguide/traefik.check) == "" && $(docker logs traefik | grep "rateLimited") != "" ]]; then + echo "$domain's rated limited exceed | Traefik (LetsEncrypt)! Takes upto one week to clear up (or use a new domain)" >/opt/appdata/plexguide/emergency/message.d + else + if [ -e "/opt/appdata/plexguide/emergency/message.d" ]; then + rm -rf /opt/appdata/plexguide/emergency/message.d + fi + fi + + ################# Generate Output + echo "" >/var/plexguide/emergency.log + + if [[ $(ls /opt/appdata/plexguide/emergency) != "" ]]; then + countmessage=0 + while read p; do + let countmessage++ + echo -n "${countmessage}. " >>/var/plexguide/emergency.log + echo "$(cat /opt/appdata/plexguide/emergency/$p)" >>/var/plexguide/emergency.log + done <<<"$(ls /opt/appdata/plexguide/emergency)" + else + echo "NONE" >/var/plexguide/emergency.log + fi + + sleep 5 +done diff --git a/menu/pgvault/exclude.list b/menu/pgvault/exclude.list new file mode 100644 index 00000000..a842cb4f --- /dev/null +++ b/menu/pgvault/exclude.list @@ -0,0 +1,2 @@ +./database/Library/Application Support/Plex Media Server/Cache/PhotoTranscoder +./database/Library/Application Support/Plex Media Server/Cache/Transcode diff --git a/menu/pgvault/pgcron b/menu/pgvault/pgcron new file mode 100644 index 00000000..e8166219 --- /dev/null +++ b/menu/pgvault/pgcron @@ -0,0 +1,11 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +source /opt/pgvault/functions.sh +source /opt/pgvault/pgvault.func +initial2 +backup_process diff --git a/menu/plex/plex.sh b/menu/plex/plex.sh new file mode 100644 index 00000000..98d8b741 --- /dev/null +++ b/menu/plex/plex.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# FUNCTIONS START ############################################################## + +# BAD INPUT +badinput() { + echo + read -p '⛔️ ERROR - BAD INPUT! | PRESS [ENTER] ' typed /var/plexguide/plex.server && question3 + elif [ "$typed" == "2" ]; then + echo local >/var/plexguide/plex.server + elif [[ "$typed" == "z" || "$typed" == "Z" ]]; then + exit + else badinput2; fi +} + +# THIRD QUESTION +question3() { + tee <<-EOF +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 Remote Plex Server - Claim the Plex Server +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +To Claim the Plex Server, visit https://www.plex.tv/claim/ and input the +code below! You have 5 minutes to do so! + +If you are reinstalling plex with existing appdata press enter to skip +this step as you won't need to claim it again. +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + + read -p 'Plex Server Claim Number | Press [ENTER]: ' typed /var/plexguide/plex.claim && break=on +} + +# FUNCTIONS END ############################################################## + +question1 +#ansible-playbook /opt/coreapps/apps/plex.yml diff --git a/menu/plex/token.sh b/menu/plex/token.sh new file mode 100644 index 00000000..3b04491e --- /dev/null +++ b/menu/plex/token.sh @@ -0,0 +1,131 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# FUNCTIONS START ############################################################## + +# BAD INPUT +badinput() { + echo + read -p '⛔️ ERROR - BAD INPUT! | PRESS [ENTER] ' typed /var/plexguide/plex.pw + echo "$user" >/var/plexguide/plex.user + ansible-playbook /opt/plexguide/menu/plex/token.yml + token=$(cat /var/plexguide/plex.token) + if [ "$token" != "" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ PG - PlexToken Generation Succeeded! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 4 + else + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ PG - PlexToken Generation Failed! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +NOTE: Process will repeat until you succeed or exit! + +EOF + read -p 'Confirm Info | Press [ENTER] ' typed /var/plexguide/plex.token" + ignore_errors: yes + + - name: Token Generation + uri: + url: https://plex.tv/users/sign_in.json + method: POST + status_code: 201 + headers: + X-Plex-Product: 'plexguide' + X-Plex-Version: '1.0.0' + X-Plex-Client-Identifier: 'XXX' + Content-Type: 'application/x-www-form-urlencoded; charset=utf-8' + password: '{{pw.stdout}}' + user: '{{user.stdout}}' + register: plextoken + + - name: Set Token + set_fact: + plexauth: "{{plextoken.json.user.authToken | regex_replace('\n', '') }}" + + - name: Save Token + shell: 'echo {{plexauth}} > /var/plexguide/plex.token' + ignore_errors: yes diff --git a/menu/portguard/portguard.sh b/menu/portguard/portguard.sh new file mode 100644 index 00000000..52d61eba --- /dev/null +++ b/menu/portguard/portguard.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# KEY VARIABLE RECALL & EXECUTION +program=$(cat /tmp/program_var) +mkdir -p /var/plexguide/cron/ +mkdir -p /opt/appdata/plexguide/cron +# FUNCTIONS START ############################################################## + +# BAD INPUT +badinput() { + echo + read -p '⛔️ ERROR - BAD INPUT! | PRESS [ENTER] ' typed /var/plexguide/server.ports + else echo "127.0.0.1:" >/var/plexguide/server.ports; fi + bash /opt/plexguide/menu/portguard/rebuild.sh + elif [[ "$typed" == "z" || "$typed" == "Z" ]]; then + exit + else badinput; fi +} + +# FUNCTIONS END ############################################################## + +break=off && while [ "$break" == "off" ]; do question1; done diff --git a/menu/portguard/rebuild.sh b/menu/portguard/rebuild.sh new file mode 100644 index 00000000..4afd987f --- /dev/null +++ b/menu/portguard/rebuild.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +docker ps -a --format "{{.Names}}" >/var/plexguide/container.running + +sed -i -e "/traefik/d" /var/plexguide/container.running +sed -i -e "/watchtower/d" /var/plexguide/container.running +sed -i -e "/wp-*/d" /var/plexguide/container.running +sed -i -e "/x2go*/d" /var/plexguide/container.running +sed -i -e "/authclient/d" /var/plexguide/container.running +sed -i -e "/dockergc/d" /var/plexguide/container.running +sed -i -e "/oauth/d" /var/plexguide/container.running + +count=$(wc -l /tmp/program_var + sleep 1 + + if [ -e "/opt/coreapps/apps/$app.yml" ]; then ansible-playbook /opt/coreapps/apps/$app.yml; fi + if [ -e "/opt/communityapps/$app.yml" ]; then ansible-playbook /opt/communityapps/apps/$app.yml; fi +done + +echo "" +tee <<-EOF + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ✅️ PortGuard - All Containers Rebuilt! + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF +read -p 'Continue? | Press [ENTER] ' name /dev/null 2>&1' + state: present + ignore_errors: yes diff --git a/menu/removal/removal.sh b/menu/removal/removal.sh new file mode 100644 index 00000000..a25b9dc0 --- /dev/null +++ b/menu/removal/removal.sh @@ -0,0 +1,136 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +rm -rf /tmp/backup.build 1>/dev/null 2>&1 +rm -rf /tmp/backup.list 1>/dev/null 2>&1 +rm -rf /tmp/backup.final 1>/dev/null 2>&1 + +docker ps --format '{{.Names}}' >/tmp/backup.list +sed -i -e "/traefik/d" /tmp/backup.list +sed -i -e "/watchtower/d" /tmp/backup.list +sed -i -e "/wp-*/d" /tmp/backup.list +sed -i -e "/x2go*/d" /tmp/backup.list +sed -i -e "/plexguide/d" /tmp/backup.list +sed -i -e "/cloudplow/d" /tmp/backup.list +sed -i -e "/phlex/d" /tmp/backup.list + +#### Commenting Out To Let User See +num=0 +while read p; do + let "num++" + echo -n $p >>/tmp/backup.final + echo -n " " >>/tmp/backup.final + if [ "$num" == 7 ]; then + num=0 + echo " " >>/tmp/backup.final + fi +done ") +if [ "$tcheck" == "" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - Type an Application Name! Case Senstive! Restarting! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 3 + bash /opt/plexguide/menu/removal/removal.sh + exit +fi + +if [ "$typed" == "" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - The App Name Cannot Be Blank! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 3 + bash /opt/traefik/tld.sh + exit +fi + +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💎 PASS: Uninstalling - $typed +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +sleep 1.5 + +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🍖 NOM NOM - Stopping | Removing > $typed Docker Container +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +sleep .5 + +docker stop $typed 1>/dev/null 2>&1 +docker rm $typed 1>/dev/null 2>&1 + +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🍖 NOM NOM - Removing /opt/appdata/$typed +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +sleep 1 +rm -rf /opt/appdata/$typed + +file="/opt/coreapps/apps/$typed.yml" +if [ -e "$file" ]; then + check=$(cat /opt/coreapps/apps/$typed.yml | grep '##PG-Community') + if [ "$check" == "##PG-Community" ]; then rm -r /opt/communityapps/apps/$typed.yml; fi + rm -rf /var/plexguide/community.app +fi + +sleep 1.5 + +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ PASS: Uninstalled - $typed - Exiting! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF +sleep 2 diff --git a/menu/roles/autodelete/tasks/main.yml b/menu/roles/autodelete/tasks/main.yml new file mode 100644 index 00000000..1371693c --- /dev/null +++ b/menu/roles/autodelete/tasks/main.yml @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- name: Remove old NZBGET job + cron: + name: Build a Cron Job - NZBGET + state: absent + ignore_errors: yes + +- name: Remove old SABNZBD job + cron: + name: Build a Cron Job - SABNZBD + state: absent + ignore_errors: yes + +- name: Remove old DELUGE job + cron: + name: Build a Cron Job - DELUGE + state: absent + ignore_errors: yes + +- name: Remove old RUTORRENT job + cron: + name: Build a Cron Job - RUTORRENT + state: absent + ignore_errors: yes + +- name: Remove old QBITTORRENT job + cron: + name: Build a Cron Job - QBITTORRENT + state: absent + ignore_errors: yes diff --git a/menu/roles/clean-encrypt/tasks/main.yml b/menu/roles/clean-encrypt/tasks/main.yml new file mode 100644 index 00000000..35038b94 --- /dev/null +++ b/menu/roles/clean-encrypt/tasks/main.yml @@ -0,0 +1,40 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- name: Install Cloudplow; remove old cronjob + cron: + name: 'Cloudplow Clean' + user: 'root' + state: absent + +- name: Install Cloudplow; remove old directory + file: + state: absent + path: '/opt/appdata/cloudplow' + +- name: Install Cloudplow; download + become_user: root + shell: 'git clone https://github.com/Admin9705/clean /opt/appdata/cloudplow' + +- name: Install Cloudplow; dependencies + become_user: root + shell: 'python3 -m pip install -r /opt/appdata/cloudplow/requirements.txt' + +- name: Install Cloudplow; config + template: + src: config.js2 + dest: /opt/appdata/cloudplow/config.json + force: yes + +- name: Install Cloudplow; cronjob + cron: + name: 'Cloudplow Clean' + user: 'root' + minute: '0' + hour: '0' + job: 'cd /opt/appdata/cloudplow && /usr/bin/python3 cloudplow.py clean' diff --git a/menu/roles/clean-encrypt/templates/config.js2 b/menu/roles/clean-encrypt/templates/config.js2 new file mode 100644 index 00000000..7a1ebd9e --- /dev/null +++ b/menu/roles/clean-encrypt/templates/config.js2 @@ -0,0 +1,53 @@ +{ + "core": { + "dry_run": false + }, + "hidden": { + "/mnt/move/.unionfs-fuse": { + "hidden_remotes": [ + "gcrypt" + ] + } + }, + "notifications": { + "Pushover": { + "app_token": "app token goes here", + "service": "pushover", + "user_token": "user token goes here" + } + }, + "remotes": { + "gcrypt": { + "hidden_remote": "gcrypt:", + "rclone_excludes": [ + "**partial~", + "**_HIDDEN~", + ".unionfs/**", + ".unionfs-fuse/**" + ], + "rclone_extras": { + "--checkers": 16, + "--drive-chunk-size": "64M", + "--stats": "60s", + "--transfers": 8, + "--verbose": 1 + }, + "rclone_sleeps": { + "Error 403: User rate limit exceeded": { + "count": 5, + "sleep": 25, + "timeout": 300 + } + }, + "remove_empty_dir_depth": 10, + "upload_folder": "/mnt/move", + "upload_remote": "gcrypt:" + } + }, + "syncer": { + }, + "uploader": { + "google": { + } + } +} diff --git a/menu/roles/clean/tasks/main.yml b/menu/roles/clean/tasks/main.yml new file mode 100644 index 00000000..dddcb38b --- /dev/null +++ b/menu/roles/clean/tasks/main.yml @@ -0,0 +1,27 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +# Credit to ENZTV for the basis of this script https://enztv.wordpress.com/2017/03/09/unionfs-cleanup/ +############################################### +--- +- name: Install gdriveclean; remove old cronjob + cron: + name: 'gdriveclean' + user: 'root' + state: absent + ignore_errors: yes + +- name: Install gdriveclean; remove existing unionfs-fuse whiteout files + file: + state: absent + path: '/mnt/move/.unionfs-fuse' + +- name: Install gdriveclean; cronjob + cron: + name: 'gdriveclean' + state: absent + ignore_errors: yes diff --git a/menu/roles/docker/files/daemon.json b/menu/roles/docker/files/daemon.json new file mode 100644 index 00000000..5a7b4c68 --- /dev/null +++ b/menu/roles/docker/files/daemon.json @@ -0,0 +1,6 @@ +{ + "storage-driver": "overlay2", + "userland-proxy": false, + "log-driver": "json-file", + "log-opts": {"max-size": "12m", "max-file": "4"} +} diff --git a/menu/roles/docker/files/override.conf b/menu/roles/docker/files/override.conf new file mode 100644 index 00000000..312eb7f6 --- /dev/null +++ b/menu/roles/docker/files/override.conf @@ -0,0 +1,5 @@ +[Unit] +After=pgunion.service + +[Service] +ExecStartPre=/bin/sleep 2 diff --git a/menu/roles/docker/tasks/main.yml b/menu/roles/docker/tasks/main.yml new file mode 100644 index 00000000..91175314 --- /dev/null +++ b/menu/roles/docker/tasks/main.yml @@ -0,0 +1,169 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- name: 'Establish Facts' + set_fact: + switch: 'on' + updatecheck: 'default' + +- name: 'Docker Check' + stat: + path: '/usr/bin/docker' + register: check + +- name: 'Docker Version Check - True' + shell: "docker --version | awk '{print $3}'" + register: updatecheck + +- name: 'Switch - On' + set_fact: + switch: 'off' + when: updatecheck.stdout == "18.09.8," + +- name: Install required packages + apt: + name: '{{ packages }}' + vars: + packages: + - apt-transport-https + - ca-certificates + - software-properties-common + when: switch == "on" + +- name: Add official gpg signing key + apt_key: + id: 0EBFCD88 + url: https://download.docker.com/linux/ubuntu/gpg + when: switch == "on" + +- name: 'Stop All Containers' + shell: 'docker stop $(docker ps -a -q)' + ignore_errors: yes + when: + - check.stat.exists == True + - switch == "on" + +- name: Official Repo + apt_repository: + repo: 'deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} edge' + register: apt_docker_repositories + when: switch == "on" + +- name: Update APT packages list + apt: + update_cache: yes + when: apt_docker_repositories.changed and switch == "on" + +- name: Release docker-ce from hold + dpkg_selections: + name: docker-ce + selection: install + when: switch == "on" + +- name: Install docker-ce + apt: + name: docker-ce + state: latest + update_cache: yes + when: switch == "on" + +- name: Put docker-ce into hold + dpkg_selections: + name: docker-ce + selection: hold + when: switch == "on" + +- name: Uninstall docker-py pip module + pip: + name: docker-py + state: absent + ignore_errors: yes + when: switch == "on" + +- name: Install docker pip module + pip: + name: docker + state: latest + ignore_errors: yes + when: switch == "on" + +- name: Check docker daemon.json exists + stat: + path: /etc/docker/daemon.json + register: docker_daemon + +- name: Stop docker to enable overlay2 + systemd: + state: stopped + name: docker + enabled: yes + when: + - docker_daemon.stat.exists == False + - switch == "on" + +- name: Import daemon.json + copy: + src: daemon.json + dest: /etc/docker/daemon.json + force: yes + mode: 0775 + when: + - docker_daemon.stat.exists == False + - switch == "on" + +- name: Start docker (Please Wait) + systemd: + state: started + name: docker + enabled: yes + when: + - docker_daemon.stat.exists == False + - switch == "on" + +- name: 'Wait for 20 seconds before commencing' + wait_for: + timeout: 20 + when: switch == "on" + +- name: Check override folder exists + stat: + path: /etc/systemd/system/docker.service.d + register: docker_override + +- name: Create override folder + file: + path: /etc/systemd/system/docker.service.d + state: directory + mode: 0775 + when: + - docker_override.stat.exists == False + - switch == "on" + tags: docker_standard + +- name: Import override file + copy: + src: override.conf + dest: /etc/systemd/system/docker.service.d/override.conf + force: yes + mode: 0775 + tags: docker_standard + when: switch == "on" + +- name: create plexguide network + docker_network: + name: 'plexguide' + state: present + tags: docker_standard + when: switch == "on" + +- name: 'Start All Containers' + shell: 'docker start $(docker ps -a -q)' + ignore_errors: yes + when: + - switch == "on" + - check.stat.exists == True diff --git a/menu/roles/dockerdeb/files/daemon.json b/menu/roles/dockerdeb/files/daemon.json new file mode 100644 index 00000000..5a7b4c68 --- /dev/null +++ b/menu/roles/dockerdeb/files/daemon.json @@ -0,0 +1,6 @@ +{ + "storage-driver": "overlay2", + "userland-proxy": false, + "log-driver": "json-file", + "log-opts": {"max-size": "12m", "max-file": "4"} +} diff --git a/menu/roles/dockerdeb/files/override.conf b/menu/roles/dockerdeb/files/override.conf new file mode 100644 index 00000000..c709d878 --- /dev/null +++ b/menu/roles/dockerdeb/files/override.conf @@ -0,0 +1,5 @@ +[Unit] +After=pgunion.service + +[Service] +ExecStartPre=/bin/sleep 5 diff --git a/menu/roles/dockerdeb/tasks/main.yml b/menu/roles/dockerdeb/tasks/main.yml new file mode 100644 index 00000000..dccc131f --- /dev/null +++ b/menu/roles/dockerdeb/tasks/main.yml @@ -0,0 +1,167 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- name: 'Establish Facts' + set_fact: + switch: 'on' + updatecheck: 'default' + +- name: 'Docker Check' + stat: + path: '/usr/bin/docker' + register: check + +- name: 'Docker Version Check - True' + shell: "docker --version | awk '{print $3}'" + register: updatecheck + +- name: 'Switch - On' + set_fact: + switch: 'off' + when: updatecheck.stdout == "18.09.8," + +- name: Install required packages + apt: 'name={{item}} state=present' + with_items: + - apt-transport-https + - ca-certificates + - software-properties-common + when: switch == "on" + +- name: Add official gpg signing key + apt_key: + id: 0EBFCD88 + url: https://download.docker.com/linux/debian/gpg + when: switch == "on" + +- name: 'Stop All Containers' + shell: 'docker stop $(docker ps -a -q)' + ignore_errors: yes + when: + - check.stat.exists == True + - switch == "on" + +- name: Official Repo + apt_repository: + repo: 'deb [arch=amd64] https://download.docker.com/linux/debian {{ ansible_distribution_release }} edge' + register: apt_docker_repositories + when: switch == "on" + +- name: Update APT packages list + apt: + update_cache: yes + when: apt_docker_repositories.changed and switch == "on" + +- name: Release docker-ce from hold + dpkg_selections: + name: docker-ce + selection: install + when: switch == "on" + +- name: Install docker-ce + apt: + name: docker-ce + state: present + update_cache: yes + when: switch == "on" + +- name: Put docker-ce into hold + dpkg_selections: + name: docker-ce + selection: hold + when: switch == "on" + +- name: Uninstall docker-py pip module + pip: + name: docker-py + state: absent + ignore_errors: yes + when: switch == "on" + +- name: Install docker pip module + pip: + name: docker + state: latest + ignore_errors: yes + when: switch == "on" + +- name: Check docker daemon.json exists + stat: + path: /etc/docker/daemon.json + register: docker_daemon + +- name: Stop docker to enable overlay2 + systemd: + state: stopped + name: docker + enabled: yes + when: + - docker_daemon.stat.exists == False + - switch == "on" + +- name: Import daemon.json + copy: + src: daemon.json + dest: /etc/docker/daemon.json + force: yes + mode: 0775 + when: + - docker_daemon.stat.exists == False + - switch == "on" + +- name: Start docker (Please Wait) + systemd: + state: started + name: docker + enabled: yes + when: + - docker_daemon.stat.exists == False + - switch == "on" + +- name: 'Wait for 20 seconds before commencing' + wait_for: + timeout: 20 + when: switch == "on" + +- name: Check override folder exists + stat: + path: /etc/systemd/system/docker.service.d + register: docker_override + +- name: Create override folder + file: + path: /etc/systemd/system/docker.service.d + state: directory + mode: 0775 + when: + - docker_override.stat.exists == False + - switch == "on" + tags: docker_standard + +- name: Import override file + copy: + src: override.conf + dest: /etc/systemd/system/docker.service.d/override.conf + force: yes + mode: 0775 + tags: docker_standard + when: switch == "on" + +- name: create plexguide network + docker_network: + name: 'plexguide' + state: present + tags: docker_standard + when: switch == "on" + +- name: 'Start All Containers' + shell: 'docker start $(docker ps -a -q)' + ignore_errors: yes + when: + - switch == "on" + - check.stat.exists == True diff --git a/menu/roles/docstart/tasks/main.yml b/menu/roles/docstart/tasks/main.yml new file mode 100644 index 00000000..b0fe59b1 --- /dev/null +++ b/menu/roles/docstart/tasks/main.yml @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq - Sub7Seven +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +########################################## Scripts +- name: Check DockerFix Service + stat: + path: '/etc/systemd/system/v2docstart.service' + register: v2docstart + +- name: Install Docker Reboot + template: + src: v2dockerscript.js2 + dest: /opt/appdata/plexguide/v2dockerscript.sh + force: yes +########################################## Remove Old Services + +- name: Remove Old Docker Fix + file: + state: absent + path: /etc/systemd/system/docstart.service + +- name: Remove Old Docker File + file: + state: absent + path: /opt/appdata/plexguide/docstart.sh + +########################################## New Service + +- name: Stop If DockerFix Service Running + systemd: state=stopped name=v2docstart + when: v2docstart.stat.exists + +- name: Install DockerFix Service + template: + src: v2docstart.js2 + dest: /etc/systemd/system/v2docstart.service + force: yes + +- name: Daemon-Reload + systemd: state=stopped name=v2docstart daemon_reload=yes enabled=no + +- name: Start DockerFix + systemd: state=started name=v2docstart enabled=yes diff --git a/menu/roles/docstart/templates/v2dockerscript.js2 b/menu/roles/docstart/templates/v2dockerscript.js2 new file mode 100644 index 00000000..ec998853 --- /dev/null +++ b/menu/roles/docstart/templates/v2dockerscript.js2 @@ -0,0 +1,14 @@ +containers=$(comm -12 <(docker ps -a -q | sort) <(docker ps -q | sort)) +for container in $containers; +do + echo Stopping $container + docker=$(docker stop $container) +done + +sleep 9 + +for container in $containers; +do + echo Starting $container + docker=$(docker start $container) +done diff --git a/menu/roles/docstart/templates/v2docstart.js2 b/menu/roles/docstart/templates/v2docstart.js2 new file mode 100644 index 00000000..c34ed5a3 --- /dev/null +++ b/menu/roles/docstart/templates/v2docstart.js2 @@ -0,0 +1,16 @@ + [Unit] + Description=Move Service Daemon + After=multi-user.target + + [Service] + Type=simple + User=root + Group=root + ExecStart=/bin/bash /opt/appdata/plexguide/v2dockerscript.sh + TimeoutStopSec=20 + KillMode=process + RemainAfterExit=yes + Restart=always + + [Install] + WantedBy=multi-user.target diff --git a/menu/roles/network_tuning/tasks/main.yml b/menu/roles/network_tuning/tasks/main.yml new file mode 100644 index 00000000..41a92738 --- /dev/null +++ b/menu/roles/network_tuning/tasks/main.yml @@ -0,0 +1,225 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Flicker-Rate +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +########### Check If Backup sysctl exists +- name: sysctl Checker + stat: + path: /opt/appdata/plexguide/sysctl.conf + register: sysctl + +########### Backup Sysctl Config +- name: Backup Kernel Parameters + copy: + src: /etc/sysctl.conf + dest: /opt/appdata/plexguide/sysctl.conf + when: sysctl.stat.exists == False + +########### Reset Sysctl Config With Backup File +- name: Reseting Kernel Parameters + copy: + src: /opt/appdata/plexguide/sysctl.conf + dest: /etc/sysctl.conf + when: sysctl.stat.exists == True + +########### Adjust kernel params +# enable/disable tweaks with --skip-tags + +#################### bbr +- name: Enable BBR TCP Congestion Control + blockinfile: + path: /etc/sysctl.conf + marker: '# TCP BBR' + block: | + net.core.default_qdisc=fq + net.ipv4.tcp_congestion_control=bbr + tags: + - bbr + +- name: Seedboxer Kernel Tweaks + blockinfile: + path: /etc/sysctl.conf + marker: '# Seedboxer' + block: | + fs.file-max = 2097152 + vm.swappiness = 10 + vm.dirty_ratio = 60 + vm.dirty_background_ratio = 2 + net.ipv4.tcp_synack_retries = 2 + net.ipv4.ip_local_port_range = 2000 65535 + net.ipv4.tcp_rfc1337 = 1 + net.ipv4.tcp_fin_timeout = 15 + net.ipv4.tcp_keepalive_time = 300 + net.ipv4.tcp_keepalive_probes = 5 + net.ipv4.tcp_keepalive_intvl = 15 + net.core.rmem_default = 31457280 + net.core.rmem_max = 12582912 + net.core.wmem_default = 31457280 + net.core.wmem_max = 12582912 + net.core.somaxconn = 4096 + net.core.netdev_max_backlog = 65536 + net.core.optmem_max = 25165824 + net.ipv4.tcp_mem = 65536 131072 262144 + net.ipv4.udp_mem = 65536 131072 262144 + net.ipv4.tcp_rmem = 8192 87380 16777216 + net.ipv4.udp_rmem_min = 16384 + net.ipv4.tcp_wmem = 8192 65536 16777216 + net.ipv4.udp_wmem_min = 16384 + net.ipv4.tcp_max_tw_buckets = 1440000 + net.ipv4.tcp_tw_recycle = 1 + net.ipv4.tcp_tw_reuse = 1 + tags: + - seedboxer + +#################### tj007s13 +# https://www.reddit.com/r/seedboxes/comments/6spbla/my_linux_server_performance_tweaks/ +- name: tj007s13's kernel tweaks + blockinfile: + path: /etc/sysctl.conf + marker: '# tj007s13' + block: | + vm.swappiness = 1 + fs.file-max = 2000000 + kernel.pid_max = 4194303 + kernel.sched_migration_cost_ns = 5000000 + kernel.sched_autogroup_enabled = 0 + net.ipv4.tcp_slow_start_after_idle = 0 + net.ipv4.tcp_no_metrics_save = 0 + net.ipv4.tcp_abort_on_overflow = 0 + net.ipv4.tcp_window_scaling = 1 + net.ipv4.tcp_tw_reuse = 1 + vm.dirty_background_ratio = 20 + vm.dirty_ratio = 30 + net.ipv4.tcp_rfc1337 = 1 + net.ipv4.tcp_sack = 1 + net.ipv4.tcp_fack = 1 + net.ipv4.tcp_workaround_signed_windows = 1 + net.ipv4.tcp_timestamps = 1 + net.ipv4.tcp_syncookies = 1 + net.ipv4.tcp_syn_retries = 2 + net.ipv4.tcp_synack_retries = 2 + net.ipv4.tcp_orphan_retries = 2 + net.ipv4.tcp_retries2 = 8 + net.ipv4.ip_local_port_range = 1024 65535 + net.core.netdev_max_backlog = 3240000 + net.core.somaxconn = 50000 + net.ipv4.tcp_max_tw_buckets = 1440000 + net.ipv4.tcp_max_syn_backlog = 3240000 + net.ipv4.tcp_mtu_probing = 1 + net.ipv4.tcp_fin_timeout = 15 + net.ipv4.tcp_keepalive_time = 600 + net.ipv4.tcp_keepalive_intvl = 10 + net.ipv4.tcp_keepalive_probes = 9 + net.ipv4.ip_no_pmtu_disc = 0 + net.core.rmem_default = 16777216 + net.core.wmem_default = 16777216 + net.core.optmem_max = 16777216 + net.core.rmem_max = 16777216 + net.core.wmem_max = 16777216 + net.ipv4.tcp_fastopen = 3 + net.ipv4.tcp_rmem = 4096 87380 16777216 + net.ipv4.tcp_wmem = 4096 65536 16777216 + net.ipv4.tcp_adv_win_scale = 1 + net.core.default_qdisc = fq + net.ipv4.tcp_congestion_control = bbr + tags: + - tj + +#################### klaver.it +- name: Klaver.it Kernel Tweaks + blockinfile: + path: /etc/sysctl.conf + marker: '# Klaver' + block: | + net.ipv4.tcp_syncookies = 1 + net.ipv4.tcp_syn_retries = 2 + net.ipv4.tcp_synack_retries = 2 + net.ipv4.tcp_max_syn_backlog = 4096 + net.ipv4.ip_forward = 0 + net.ipv4.conf.all.forwarding = 0 + net.ipv4.conf.default.forwarding = 0 + net.ipv6.conf.all.forwarding = 0 + net.ipv6.conf.default.forwarding = 0 + net.ipv4.conf.all.send_redirects = 0 + net.ipv4.conf.default.send_redirects = 0 + net.ipv4.conf.all.accept_source_route = 0 + net.ipv4.conf.default.accept_source_route = 0 + net.ipv6.conf.all.accept_source_route = 0 + net.ipv6.conf.default.accept_source_route = 0 + net.ipv4.conf.all.rp_filter = 1 + net.ipv4.conf.default.rp_filter = 1 + net.ipv4.conf.all.accept_redirects = 0 + net.ipv4.conf.default.accept_redirects = 0 + net.ipv4.conf.all.secure_redirects = 0 + net.ipv4.conf.default.secure_redirects = 0 + net.ipv6.conf.all.accept_redirects = 0 + net.ipv6.conf.default.accept_redirects = 0 + net.ipv4.conf.all.log_martians = 1 + net.ipv4.conf.default.log_martians = 1 + net.ipv4.tcp_fin_timeout = 7 + net.ipv4.tcp_keepalive_time = 300 + net.ipv4.tcp_keepalive_probes = 5 + net.ipv4.tcp_keepalive_intvl = 15 + net.ipv4.conf.all.bootp_relay = 0 + net.ipv4.conf.all.proxy_arp = 0 + net.ipv4.tcp_timestamps = 1 + net.ipv4.icmp_echo_ignore_all = 0 + net.ipv4.icmp_echo_ignore_broadcasts = 1 + net.ipv4.icmp_ignore_bogus_error_responses = 1 + net.ipv4.ip_local_port_range = 16384 65535 + net.ipv4.tcp_rfc1337 = 1 + net.ipv4.tcp_window_scaling = 1 + net.ipv4.tcp_rmem = 8192 87380 16777216 + net.ipv4.udp_rmem_min = 16384 + net.core.rmem_default = 262144 + net.core.rmem_max = 16777216 + net.ipv4.tcp_wmem = 8192 65536 16777216 + net.ipv4.udp_wmem_min = 16384 + net.core.wmem_default = 262144 + net.core.wmem_max = 16777216 + net.core.somaxconn = 32768 + net.core.netdev_max_backlog = 16384 + net.core.dev_weight = 64 + net.core.optmem_max = 65535 + net.ipv4.tcp_max_tw_buckets = 1440000 + net.ipv4.tcp_tw_recycle = 0 + net.ipv4.tcp_tw_reuse = 1 + net.ipv4.tcp_max_orphans = 16384 + net.ipv4.tcp_orphan_retries = 0 + net.ipv4.ipfrag_high_thresh = 512000 + net.ipv4.ipfrag_low_thresh = 446464 + net.ipv4.tcp_no_metrics_save = 1 + net.ipv4.tcp_moderate_rcvbuf = 1 + net.unix.max_dgram_qlen = 50 + net.ipv4.neigh.default.gc_thresh3 = 2048 + net.ipv4.neigh.default.gc_thresh2 = 1024 + net.ipv4.neigh.default.gc_thresh1 = 32 + net.ipv4.neigh.default.gc_interval = 30 + net.ipv4.neigh.default.proxy_qlen = 96 + net.ipv4.neigh.default.unres_qlen = 6 + net.ipv4.tcp_retries2 = 15 + net.ipv4.tcp_retries1 = 3 + net.ipv4.tcp_slow_start_after_idle = 0 + net.ipv4.tcp_fastopen = 3 + net.ipv4.route.flush = 1 + fs.file-max = 209708 + vm.swappiness = 30 + vm.dirty_ratio = 30 + vm.dirty_background_ratio = 5 + vm.mmap_min_addr = 4096 + vm.overcommit_ratio = 50 + vm.overcommit_memory = 0 + kernel.shmmax = 268435456 + kernel.shmall = 268435456 + vm.min_free_kbytes = 65535 + tags: + - klaver + +################### Load Kernel Configs + +- name: Apply Kernel Settings + shell: 'sysctl -e -p' diff --git a/menu/start/quotes.sh b/menu/start/quotes.sh new file mode 100644 index 00000000..c7fd3eed --- /dev/null +++ b/menu/start/quotes.sh @@ -0,0 +1,420 @@ +quote0() { + echo "Welcome to PGBlitz! Started in December of 2016!" >/var/plexguide/startup.quote + echo " Admin9705" >/var/plexguide/startup.source +} + +quote1() { + echo "Manbearpig is in there and we all have to kill him while we all have the +chance, I'm cereal!" >/var/plexguide/startup.quote + echo " Al Gore ~ SouthPark" >/var/plexguide/startup.source +} + +quote2() { + echo "There are no stupid answers, just stupid people" >/var/plexguide/startup.quote + echo " Mr. Garrison ~ SouthPark" >/var/plexguide/startup.source +} + +quote3() { + echo "Don't do drugs kids. There is a time and place for everything. It's +called college." >/var/plexguide/startup.quote + echo " Chef ~ SouthPark" >/var/plexguide/startup.source +} + +quote4() { + echo "I left you plenty of food. It's at the supermarket." >/var/plexguide/startup.quote + echo " Peggy Bundy ~ Married With Children" >/var/plexguide/startup.source +} + +quote5() { + echo "Dad had one great dream, a dream that had been handed down from +generation to generation of male Bundys: to build their own room and live +separately from their wives. Sadly, they all failed." >/var/plexguide/startup.quote + echo " Al Bundy ~ Married With Children" >/var/plexguide/startup.source +} + +quote6() { + echo "Look, Al doesn’t like me blowing smoke in his eggs. What am I supposed to +do? Stop smoking?" >/var/plexguide/startup.quote + echo " Peggy Bundy ~ Married With Children" >/var/plexguide/startup.source +} + +quote7() { + echo "I don't care what your doing; it's just the idiotic way that your doing +it!" >/var/plexguide/startup.quote + echo " Vincent ~ Final Fantasy 7" >/var/plexguide/startup.source +} + +quote8() { + echo "Now, we fight like men! And ladies! And ladies who dress like men!" >/var/plexguide/startup.quote + echo " Gilgamesh ~ Final Fantasy 5" >/var/plexguide/startup.source +} + +quote9() { + echo "Now, we fight like men! And ladies! And ladies who dress like men!" >/var/plexguide/startup.quote + echo " Gilgamesh ~ Final Fantasy 5 (ROM Translation)" >/var/plexguide/startup.source +} + +quote10() { + echo "Tomorrow is gonna be just like today, and I know that because today is +just like yesterday!" >/var/plexguide/startup.quote + echo " Amy Dubanowski ~ SuperStore" >/var/plexguide/startup.source +} + +quote11() { + echo "You don’t win friends with salad!" >/var/plexguide/startup.quote + echo " Homer, Bart & Marge ~ The Simpsons" >/var/plexguide/startup.source +} + +quote12() { + echo "It takes two to lie: one to lie and one to listen." >/var/plexguide/startup.quote + echo " Homer ~ The Simpsons" >/var/plexguide/startup.source +} + +quote13() { + echo "Loneliness and cheeseburgers are a dangerous mix." >/var/plexguide/startup.quote + echo " Comic Book Guy ~ The Simpsons" >/var/plexguide/startup.source +} + +quote14() { + echo "Hello, 911? It's Quagmire. Yeah, it's caught in the window this time." >/var/plexguide/startup.quote + echo " Glen Quagmire ~ Family Guy" >/var/plexguide/startup.source +} + +quote15() { + echo "My favorite exercise is a cross between a lunge and a crunch... +I call it lunch." >/var/plexguide/startup.quote + echo " Anonymous ~ The Internet" >/var/plexguide/startup.source +} + +quote16() { + echo "McLovin? What kind of stupid name is that Fogell? What... are you +trying to be; an Irish R&B singer?" >/var/plexguide/startup.quote + echo " Evan ~ SuperBad" >/var/plexguide/startup.source +} + +quote17() { + echo "Hello, 911 Emergency? There’s a handsome guy in my bathroom! Hey, wait +a second. Cancel that – it’s only me!" >/var/plexguide/startup.quote + echo " Johnny Bravo ~ Cartoon Network" >/var/plexguide/startup.source +} + +quote18() { + echo "Happy-happy, joy-joy! Happy-happy, joy-joy!" >/var/plexguide/startup.quote + echo " Stimpy ~ The Ren & Stimpy Show" >/var/plexguide/startup.source +} + +quote19() { + echo "Presenting the Cheese-A-Phone. We can communicate with various cheeses, +regardless of foreign tongue. Go ahead, Ren, say something in Limburger" >/var/plexguide/startup.quote + echo " Stimpy ~ The Ren & Stimpy Show" >/var/plexguide/startup.source +} + +quote20() { + echo "The only thing i'm pimping is Sweet Lady Propane, and I'm tricking her +out all over this town!" >/var/plexguide/startup.quote + echo " Hank Hill ~ King of the Hill" >/var/plexguide/startup.source +} + +quote21() { + echo "Why would anyone smoke weed when they could just mow a lawn?" >/var/plexguide/startup.quote + echo " Hank Hill ~ King of the Hill" >/var/plexguide/startup.source +} + +quote22() { + echo "An ‘F’ in English? Bobby, you speak English!" >/var/plexguide/startup.quote + echo " Hank Hill ~ King of the Hill" >/var/plexguide/startup.source +} + +quote23() { + echo "I am the Great Cornholio, I need T.P. for my bunghole" >/var/plexguide/startup.quote + echo " Beavis ~ Beavis & Butthead" >/var/plexguide/startup.source +} + +quote24() { + echo "Hey Butt-head, I dreamed I was at school last night. Do you think that +counts for attendance?" >/var/plexguide/startup.quote + echo " Beavis ~ Beavis & Butthead" >/var/plexguide/startup.source +} + +quote25() { + echo "Man I tell what, I felt like a one-legged cat try’in to burry turd’s +on a frozen pond out there." >/var/plexguide/startup.quote + echo " Tom ~ Beavis & Butthead" >/var/plexguide/startup.source +} + +quote26() { + echo "Did you know when you eat rump roast you’re eating a cow’s butt?" >/var/plexguide/startup.quote + echo " Beavis ~ Beavis & Butthead" >/var/plexguide/startup.source +} + +quote27() { + echo "Did you know when you eat rump roast you’re eating a cow’s butt?" >/var/plexguide/startup.quote + echo " Beavis ~ Beavis & Butthead" >/var/plexguide/startup.source +} + +quote28() { + echo "I am Al Gore, and I used to be the next president of the United States +of America!" >/var/plexguide/startup.quote + echo " Al Gore ~ Speech @ Bocconi University" >/var/plexguide/startup.source +} + +quote29() { + echo "During my service in the United States Congress I took the initiative +in creating the Internet." >/var/plexguide/startup.quote + echo " Al Gore ~ 1999 Interview with CNN" >/var/plexguide/startup.source +} + +quote30() { + echo "If a book about failures doesn’t sell, is it a success?" >/var/plexguide/startup.quote + echo " Jerry Seinfeld" >/var/plexguide/startup.source +} + +quote31() { + echo "If you think nobody cares about you, try missing a couple of payments!" >/var/plexguide/startup.quote + echo " Steven Wright" >/var/plexguide/startup.source +} + +quote32() { + echo "I’m addicted to placebos!" >/var/plexguide/startup.quote + echo " Steven Wright" >/var/plexguide/startup.source +} + +quote33() { + echo "If we’re not meant to have midnight snacks, why is there a light in +the fridge." >/var/plexguide/startup.quote + echo " Anonymous ~ The Internet" >/var/plexguide/startup.source +} + +quote34() { + echo "I could tell that my parents hated me. My bath toys were a toaster and +a radio!" >/var/plexguide/startup.quote + echo " Rodney Dangerfield" >/var/plexguide/startup.source +} + +quote35() { + echo "The quickest way for a parent to get a child’s attention is to sit down +and look comfortable." >/var/plexguide/startup.quote + echo " Lane Olinghouse" >/var/plexguide/startup.source +} + +quote36() { + echo "Donate Today! My biggest adversary is Mrs. Admin for all the time +spent on the project!" >/var/plexguide/startup.quote + echo " Admin9705" >/var/plexguide/startup.source +} + +quote37() { + echo "Thank you to Sponsors & Donors of the Project! Your assistance +provides continous support in what we do!" >/var/plexguide/startup.quote + echo " Admin9705" >/var/plexguide/startup.source +} + +quote38() { + echo "Have a funny quote? PM Admin9705 and we will add it!" >/var/plexguide/startup.quote + echo " System Message" >/var/plexguide/startup.source +} + +quote39() { + echo "Greater good?’ I am your wife! I’m the greatest good you’re ever gonna +get!" >/var/plexguide/startup.quote + echo " Honey ~ The Incredibles" >/var/plexguide/startup.source +} + +quote40() { + echo "We get the warhead and we hold the world ransom for…. One million +dollars!" >/var/plexguide/startup.quote + echo " Dr. Evil ~ Austin Powers: International Man of Mystery" >/var/plexguide/startup.source +} + +quote41() { + echo "Excuse me. I believe you have my stapler." >/var/plexguide/startup.quote + echo " Milton Waddams ~ Office Space" >/var/plexguide/startup.source +} + +quote42() { + echo "I learned a long time ago that worrying is like a rocking chair. It +gives you something to do but it doesn’t get you anywhere" >/var/plexguide/startup.quote + echo " Van Wilder ~ National Lampoon’s Van Wilder" >/var/plexguide/startup.source +} + +quote43() { + echo "It’s not a man purse. It’s called a satchel. Indiana Jones wears one." >/var/plexguide/startup.quote + echo " Alan Garner ~ The Hangover" >/var/plexguide/startup.source +} + +quote44() { + echo "As If!" >/var/plexguide/startup.quote + echo " Cher Horowitz ~ Clueless" >/var/plexguide/startup.source +} + +quote45() { + echo "Tina, you fat lard! Come get some Dinner!" >/var/plexguide/startup.quote + echo " Napoleon Dynamite" >/var/plexguide/startup.source +} + +quote46() { + echo "Oh my God, they killed Kenny!" >/var/plexguide/startup.quote + echo " Stan Marsh ~ SouthPark" >/var/plexguide/startup.source +} + +quote47() { + echo "I've got a new recipeeeeeee!" >/var/plexguide/startup.quote + echo " Ignis Scientia - Final Fantasy XV" >/var/plexguide/startup.source +} + +quote48() { + echo "I know you're like me. When you see somebody walking down the street +wearing a Superman t-shirt, you just want to shoot them in the chest. And when +they start to bleed go, \"I guess not\"." >/var/plexguide/startup.quote + echo " Dane Cook ~ Comedian" >/var/plexguide/startup.source +} + +quote49() { + echo "What is a man? A miserable little pile of secrets." >/var/plexguide/startup.quote + echo " Dracula, Castlevania ~ Symphony of the Night" >/var/plexguide/startup.source +} + +quote50() { + echo "It's time to kick ass and chew bubble gun... and I'm all out of gum." >/var/plexguide/startup.quote + echo " Duke Nukem ~ Duke Nukem 3D" >/var/plexguide/startup.source +} + +quote51() { + echo "Thank You Mario! But our Princess is another castle!" >/var/plexguide/startup.quote + echo " Toad(s) ~ NES Mario I" >/var/plexguide/startup.source +} + +quote51() { + echo "Get over here!" >/var/plexguide/startup.quote + echo " Scorpion ~ Mortal Kombat" >/var/plexguide/startup.source +} + +quote52() { + echo "There are two ways this can go down; and in both of them... you die!" >/var/plexguide/startup.quote + echo " Duke Nukem" >/var/plexguide/startup.source +} + +quote53() { + echo "Lead me, follow me, or get the hell out of my way." >/var/plexguide/startup.quote + echo " GEN George S. Patton Jr. ~ US Army" >/var/plexguide/startup.source +} + +quote54() { + echo "If you find yourself in a fair fight, you didn't plan your mission +properly." >/var/plexguide/startup.quote + echo " COL David Hackworth ~ US Army" >/var/plexguide/startup.source +} + +quote55() { + echo "Yeah, I'm gonna need you to come in on Saturday. Oh, oh, and I almost +forgot. Ah, I'm also gonna need you to go ahead and come in on Sunday, too." >/var/plexguide/startup.quote + echo " Bill Lumbergh ~ Office Space" >/var/plexguide/startup.source +} + +quote56() { + echo "Didn't you get the memo?" >/var/plexguide/startup.quote + echo " Bill Lumbergh ~ Office Space" >/var/plexguide/startup.source +} + +quote57() { + echo "No one in this country can pronounce my name right. I mean it's not +that hard. I mean, 'Ni-i-na-najaad', Niinanajaad." >/var/plexguide/startup.quote + echo " Samir ~ Office Space" >/var/plexguide/startup.source +} + +quote58() { + echo "I could set this building on fire." >/var/plexguide/startup.quote + echo " Milton ~ Office Space" >/var/plexguide/startup.source +} + +quote59() { + echo "PC Load Letter? What the f*ck does that mean?." >/var/plexguide/startup.quote + echo " Michael Bolton ~ Office Space" >/var/plexguide/startup.source +} + +quote60() { + echo "When you come in on Monday and you're not feeling real well, does +anyone ever say to you 'Sounds like someone has a case of the Mondays?'" >/var/plexguide/startup.quote + echo " Peter Gibbons ~ Office Space" >/var/plexguide/startup.source +} + +quote61() { + echo "The thing is Bob it's not that I'm lazy, it's that I just don't care." >/var/plexguide/startup.quote + echo " Peter Gibbons ~ Office Space" >/var/plexguide/startup.source +} + +quote62() { + echo "Hello Peter what's happening. I'm gonna need you to go ahead and come +in tomorrow. So if you could be here at around....9 that'd be great." >/var/plexguide/startup.quote + echo " Bill Lumbergh ~ Office Space" >/var/plexguide/startup.source +} + +quote63() { + echo "Why does it say paper jam when there is no paper jam? I swear to God +one of these days I am just kicking this piece of sh*t out the window!" >/var/plexguide/startup.quote + echo " Samir ~ Office Space" >/var/plexguide/startup.source +} + +quote64() { + echo "I can't believe you like money too. We should hang out." >/var/plexguide/startup.quote + echo " Frito ~ Office Space" >/var/plexguide/startup.source +} + +quote64() { + echo "I can't believe you like money too. We should hang out." >/var/plexguide/startup.quote + echo " Frito ~ Idiocracy" >/var/plexguide/startup.source +} + +quote65() { + echo "One! Don't you feel dumb. Two! Look at you. Three! Don't you ever make +jokes about me behind my back or else I'll stomp you into the ground." >/var/plexguide/startup.quote + echo " Major Payne" >/var/plexguide/startup.source +} + +quote66() { + echo "Heh, heh, heh" >/var/plexguide/startup.quote + echo " Major Payne" >/var/plexguide/startup.source +} + +quote67() { + echo "You know, somebody actually complimented me on my driving today. They +left a little note on the windscreen, it said ~ Parking Fine." >/var/plexguide/startup.quote + echo " Cooper ~ British Comedian" >/var/plexguide/startup.source +} + +quote68() { + echo "I used to be indecisive but now I am not quite sure." >/var/plexguide/startup.quote + echo " Cooper ~ British Comedian" >/var/plexguide/startup.source +} + +quote69() { + echo "An original idea. That can't be too hard. The library must be full of +them." >/var/plexguide/startup.quote + echo " Fry ~ British Comedian" >/var/plexguide/startup.source +} + +quote70() { + echo "Last night, I dreamt I ate a ten pound marshmallow. When I woke up, the +pillow was gone." >/var/plexguide/startup.quote + echo " Cooper ~ British Comedian" >/var/plexguide/startup.source +} + +quote71() { + echo "Do you want ants?! Because that's how you get ants!" >/var/plexguide/startup.quote + echo " Malory Archer ~ Archer (2009)" >/var/plexguide/startup.source +} + +quote72() { + echo "Seriously Lana, call Kenny Loggins cause you're in the... danger zone." >/var/plexguide/startup.quote + echo " Sterling Archer ~ Archer (2009)" >/var/plexguide/startup.source +} + +quote73() { + echo "Sir, that stolen lemur bit one of your prostitutes right in the face. + And she says she can't go to a hospital because she's, I quote, \"tripping balls\"." >/var/plexguide/startup.quote + echo " Woodhouse ~ Archer (2009)" >/var/plexguide/startup.source +} + +# END FUNCTIONS ################################################################ +num=$(echo $(($RANDOM % 74))) +quote$num diff --git a/menu/start/start.sh b/menu/start/start.sh new file mode 100644 index 00000000..2de97cf6 --- /dev/null +++ b/menu/start/start.sh @@ -0,0 +1,290 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +file="/var/plexguide/pg.number" +if [ -e "$file" ]; then + check="$(cat /var/plexguide/pg.number | head -c 1)" + if [[ "$check" == "5" || "$check" == "6" || "$check" == "7" ]]; then + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 INSTALLER BLOCK: Notice +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +We detected PG Version $check is running! Per the instructions, PG 8 +must be installed on a FRESH BOX! Exiting! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + exit + fi +fi + +# Create Variables (If New) & Recall +pcloadletter() { + touch /var/plexguide/pgclone.transport + temp=$(cat /var/plexguide/pgclone.transport) + if [ "$temp" == "mu" ]; then + transport="Move" + elif [ "$temp" == "me" ]; then + transport="Move: Encrypted" + elif [ "$temp" == "bu" ]; then + transport="Blitz" + elif [ "$temp" == "be" ]; then + transport="Blitz: Encrypted" + elif [ "$temp" == "le" ]; then + transport="Local" + else transport="NOT-SET"; fi + echo "$transport" >/var/plexguide/pg.transport +} + +variable() { + file="$1" + if [ ! -e "$file" ]; then echo "$2" >$1; fi +} + +# What Loads the Order of Execution +primestart() { + pcloadletter + varstart + menuprime +} + +# When Called, A Quoate is Randomly Selected +quoteselect() { + bash /opt/plexguide/menu/start/quotes.sh + quote=$(cat /var/plexguide/startup.quote) + source=$(cat /var/plexguide/startup.source) +} + +varstart() { + ###################### FOR VARIABLS ROLE SO DOESNT CREATE RED - START + file="/var/plexguide" + if [ ! -e "$file" ]; then + mkdir -p /var/plexguide/logs 1>/dev/null 2>&1 + chown -R 0775 /var/plexguide 1>/dev/null 2>&1 + chmod -R 1000:1000 /var/plexguide 1>/dev/null 2>&1 + fi + + file="/opt/appdata/plexguide" + if [ ! -e "$file" ]; then + mkdir -p /opt/appdata/plexguide 1>/dev/null 2>&1 + chown 0775 /opt/appdata/plexguide 1>/dev/null 2>&1 + chmod 1000:1000 /opt/appdata/plexguide 1>/dev/null 2>&1 + fi + + ###################### FOR VARIABLS ROLE SO DOESNT CREATE RED - START + variable /var/plexguide/pgfork.project "NOT-SET" + variable /var/plexguide/pgfork.version "NOT-SET" + variable /var/plexguide/tld.program "NOT-SET" + variable /opt/appdata/plexguide/plextoken "NOT-SET" + variable /var/plexguide/server.ht "" + variable /var/plexguide/server.ports "127.0.0.1:" + variable /var/plexguide/server.email "NOT-SET" + variable /var/plexguide/server.domain "NOT-SET" + variable /var/plexguide/tld.type "standard" + variable /var/plexguide/transcode.path "standard" + variable /var/plexguide/pgclone.transport "NOT-SET" + variable /var/plexguide/plex.claim "" + + #### Temp Fix - Fixes Bugged AppGuard + serverht=$(cat /var/plexguide/server.ht) + if [ "$serverht" == "NOT-SET" ]; then + rm /var/plexguide/server.ht + touch /var/plexguide/server.ht + fi + + hostname -I | awk '{print $1}' >/var/plexguide/server.ip + ###################### FOR VARIABLS ROLE SO DOESNT CREATE RED - END + echo "export NCURSES_NO_UTF8_ACS=1" >>/etc/bash.bashrc.local + + # run pg main + file="/var/plexguide/update.failed" + if [ -e "$file" ]; then + rm -rf /var/plexguide/update.failed + exit + fi + ################################################################################# + + # Touch Variables Incase They Do Not Exist + touch /var/plexguide/pg.edition + touch /var/plexguide/server.id + touch /var/plexguide/pg.number + touch /var/plexguide/traefik.deployed + touch /var/plexguide/server.ht + touch /var/plexguide/server.ports + touch /var/plexguide/pg.server.deploy + + # For PG UI - Force Variable to Set + ports=$(cat /var/plexguide/server.ports) + if [ "$ports" == "" ]; then + echo "Open" >/var/plexguide/pg.ports + else echo "Closed" >/var/plexguide/pg.ports; fi + + ansible --version | head -n +1 | awk '{print $2'} >/var/plexguide/pg.ansible + docker --version | head -n +1 | awk '{print $3'} | sed 's/,$//' >/var/plexguide/pg.docker + cat /etc/os-release | head -n +5 | tail -n +5 | cut -d'"' -f2 >/var/plexguide/pg.os + + file="/var/plexguide/gce.false" + if [ -e "$file" ]; then echo "No" >/var/plexguide/pg.gce; else echo "Yes" >/var/plexguide/pg.gce; fi + + # Call Variables + edition=$(cat /var/plexguide/pg.edition) + serverid=$(cat /var/plexguide/server.id) + pgnumber=$(cat /var/plexguide/pg.number) + + # Declare Traefik Deployed Docker State + if [[ $(docker ps | grep "traefik") == "" ]]; then + traefik="NOT DEPLOYED" + echo "Not Deployed" >/var/plexguide/pg.traefik + else + traefik="DEPLOYED" + echo "Deployed" >/var/plexguide/pg.traefik + fi + + if [[ $(docker ps | grep "oauth") == "" ]]; then + traefik="NOT DEPLOYED" + echo "Not Deployed" >/var/plexguide/pg.auth + else + traefik="DEPLOYED" + echo "Deployed" >/var/plexguide/pg.oauth + fi + + # For ZipLocations + file="/var/plexguide/data.location" + if [ ! -e "$file" ]; then echo "/opt/appdata/plexguide" >/var/plexguide/data.location; fi + + space=$(cat /var/plexguide/data.location) + used=$(df -h /opt/appdata/plexguide | tail -n +2 | awk '{print $3}') + capacity=$(df -h /opt/appdata/plexguide | tail -n +2 | awk '{print $2}') + percentage=$(df -h /opt/appdata/plexguide | tail -n +2 | awk '{print $5}') + + # For the PGBlitz UI + echo "$used" >/var/plexguide/pg.used + echo "$capacity" >/var/plexguide/pg.capacity +} + +menuprime() { + transport=$(cat /var/plexguide/pg.transport) + + # Menu Interface + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🌎 $transport | Version: $pgnumber | ID: $serverid +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +🌵 PG Disk Used Space: $used of $capacity | $percentage Used Capacity +EOF + + # Displays Second Drive If GCE + edition=$(cat /var/plexguide/pg.server.deploy) + if [ "$edition" == "feeder" ]; then + used_gce=$(df -h /mnt | tail -n +2 | awk '{print $3}') + capacity_gce=$(df -h /mnt | tail -n +2 | awk '{print $2}') + percentage_gce=$(df -h /mnt | tail -n +2 | awk '{print $5}') + echo " GCE Disk Used Space: $used_gce of $capacity_gce | $percentage_gce Used Capacity" + fi + + disktwo=$(cat "/var/plexguide/server.hd.path") + if [ "$edition" != "feeder" ]; then + used_gce2=$(df -h "$disktwo" | tail -n +2 | awk '{print $3}') + capacity_gce2=$(df -h "$disktwo" | tail -n +2 | awk '{print $2}') + percentage_gce2=$(df -h "$disktwo" | tail -n +2 | awk '{print $5}') + + if [[ "$disktwo" != "/mnt" ]]; then + echo " 2nd Disk Used Space: $used_gce2 of $capacity_gce2 | $percentage_gce2 Used Capacity" + fi + fi + + # Declare Ports State + ports=$(cat /var/plexguide/server.ports) + + if [ "$ports" == "" ]; then + ports="OPEN" + else ports="CLOSED"; fi + + quoteselect + + tee <<-EOF + +[1] Traefik : Reverse Proxy +[2] Port Guard: [$ports] Protects the Server Ports +[3] PG Shield : Enable Google's OAuthentication Protection +[4] PG Clone : Mount Transport +[5] PG Box : Apps ~ Core, Community & Removal +[6] PG Press : Deploy WordPress Instances +[7] PG Vault : Backup & Restore +[8] PG Cloud : GCE & Virtual Instances +[9] PG Tools +[10] PG Settings +[Z] Exit + +"$quote" + +$source +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + # Standby + read -p '↘️ Type Number | Press [ENTER]: ' typed /dev/null | grep 'gdrive' | head -n1 | cut -b1-8) + if [ "$gcheck" != "[gdrive]" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - Must Configure RClone First /w >>> gdrive +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +NOTE: You must deploy PG Move or PG Blitz in order to use the backup +function. GDrive configuration is required to move data! + +EOF + read -n 1 -s -r -p "Press [ANY] Key to Continue " + echo + bash /opt/plexguide/menu/tools/tools.sh + exit + fi + else + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ WARNING! - Backup is Only for GDrive / GCE Editions +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +NOTE: If backing up your files, they are located at the folllowing +location: /opt/appdata + +You're on OWN because it's too complex for PG to standardize a backup. +Example, you may have a second hard drive, may store it to the same +drive, a NAS... (kind of hard to account for all the situations). +Think you get the idea! + +EOF + read -n 1 -s -r -p "Press [ANY] Key to Continue " + echo + bash /opt/plexguide/menu/tools/tools.sh + exit + fi +} + +# Menu Interface +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚀 PG Tools Interface Menu +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +[1] PG Patrol +[2] PG Trakt +[3] PG Hetzner iGPU / GPU HW-Transcode +[4] PG DNS changer +[5] PG System Tweak +[6] Personal VPN Service Installer +[7] System & Network Auditor +[8] TroubleShoot ~ PreInstaller + +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + +# Standby +read -p 'Type a Number | Press [ENTER]: ' typed /var/plexguide/type.choice && bash /opt/plexguide/menu/core/scripts/main.sh +elif [ "$typed" == "7" ]; then + bash /opt/plexguide/menu/network/network.sh +elif [ "$typed" == "8" ]; then + bash /opt/plexguide/menu/tshoot/tshoot.sh +elif [ "$typed" == "Z" ] || [ "$typed" == "z" ]; then + exit +else + bash /opt/plexguide/menu/tools/tools.sh + exit +fi + +bash /opt/plexguide/menu/tools/tools.sh +exit diff --git a/menu/tshoot/tshoot.sh b/menu/tshoot/tshoot.sh new file mode 100644 index 00000000..ef9bac08 --- /dev/null +++ b/menu/tshoot/tshoot.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ + +# Menu Interface +tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🚥 PG TroubleShoot Interface +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +[1] Pre-Installer: Force the Entire Process Again +[2] UnInstaller : Docker & Running Containers | Force Pre-Install +[3] UnInstaller : PGBlitz +Z - Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + +# Standby +read -p 'Type a Number | Press [ENTER]: ' typed /var/plexguide/pg.preinstall.stored + echo "0" >/var/plexguide/pg.ansible.stored + echo "0" >/var/plexguide/pg.rclone.stored + echo "0" >/var/plexguide/pg.python.stored + echo "0" >/var/plexguide/pg.docker.stored + echo "0" >/var/plexguide/pg.docstart.stored + echo "0" >/var/plexguide/pg.watchtower.stored + echo "0" >/var/plexguide/pg.label.stored + echo "0" >/var/plexguide/pg.alias.stored + echo "0" >/var/plexguide/pg.dep.stored + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ WOOT WOOT - Process Complete! Exit & Restart PGBlitz Now! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 5 + +elif [ "$typed" == "2" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🍖 NOM NOM - Uninstalling Docker & Resetting the Variables! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 3 + + rm -rf /etc/docker + apt-get purge docker-ce + rm -rf /var/lib/docker + rm -rf /var/plexguide/dep* + echo "0" >/var/plexguide/pg.preinstall.stored + echo "0" >/var/plexguide/pg.ansible.stored + echo "0" >/var/plexguide/pg.rclone.stored + echo "0" >/var/plexguide/pg.python.stored + echo "0" >/var/plexguide/pg.docstart.stored + echo "0" >/var/plexguide/pg.watchtower.stored + echo "0" >/var/plexguide/pg.label.stored + echo "0" >/var/plexguide/pg.alias.stored + echo "0" >/var/plexguide/pg.dep + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ WOOT WOOT - Process Complete! Exit & Restart PGBlitz Now! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 5 +elif [ "$typed" == "3" ]; then + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🍖 NOM NOM - Starting the PG UnInstaller +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 3 + + echo "uninstall" >/var/plexguide/type.choice && bash /opt/plexguide/menu/core/scripts/main.sh +elif [[ "$typed" == "Z" || "$typed" == "z" ]]; then + exit +else + bash /opt/plexguide/menu/tshoot/tshoot.sh + exit +fi + +bash /opt/plexguide/menu/tshoot/tshoot.sh +exit diff --git a/menu/version/choice.yml b/menu/version/choice.yml new file mode 100644 index 00000000..fc2f3f7e --- /dev/null +++ b/menu/version/choice.yml @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: 'Installing Version' + git: + repo: 'https://github.com/MrDoobPG/PGBlitz.com' + dest: '/opt/plexguide' + version: 'master' + force: yes + + - name: 'Stops First Time Run' + shell: 'touch /var/plexguide/ask.yes' + register: program diff --git a/menu/version/file.sh b/menu/version/file.sh new file mode 100644 index 00000000..c66ac99e --- /dev/null +++ b/menu/version/file.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# +# Title: PGBlitz (Reference Title File) +# Author(s): Admin9705 - Deiteq +# URL: https://pgblitz.com - http://github.pgblitz.com +# GNU: General Public License v3.0 +################################################################################ +mainstart() { + echo "" + echo "💬 Pulling Update Files - Please Wait" + file="/opt/pgstage/place.holder" + waitvar=0 + while [ "$waitvar" == "0" ]; do + sleep .5 + if [ -e "$file" ]; then waitvar=1; fi + done + + pgnumber=$(cat "/var/plexguide/pg.number") + latest=$(cat "/opt/pgstage/versions.sh" | head -n1) + dev=$(cat /opt/pgstage/versions.sh | sed -n 4p) + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📂 Update Interface +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Stable: : $latest + +Dev : $dev [ BE CAREFUL ] + +Installed : $pgnumber + +[Z] Exit + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +EOF + + break=no + read -p '🌍 TYPE master or dev | PRESS ENTER: ' typed + storage=$(grep $typed /opt/pgstage/versions.sh) + + parttwo +} + +parttwo() { + if [[ "$typed" == "exit" || "$typed" == "Exit" || "$typed" == "EXIT" || "$typed" == "z" || "$typed" == "Z" ]]; then + echo "" + touch /var/plexguide/exited.upgrade + exit + fi + + if [ "$storage" != "" ]; then + break=yes + echo $storage >/var/plexguide/pg.number + ansible-playbook /opt/plexguide/menu/version/choice.yml + + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅️ SYSTEM MESSAGE: Installing Verison - $typed - Standby! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 2 + touch /var/plexguide/new.install + + file="/var/plexguide/community.app" + if [ -e "$file" ]; then rm -rf /var/plexguide/community.app; fi + + exit + else + tee <<-EOF + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +⛔️ SYSTEM MESSAGE: Version $typed does not exist! - Standby! +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +EOF + sleep 2 + mainstart + fi +} + +rm -rf /opt/pgstage +mkdir -p /opt/pgstage +ansible-playbook /opt/plexguide/menu/pgstage/pgstage.yml #&>/de v/null & +mainstart