diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml
new file mode 100644
index 00000000..b8cf09dd
--- /dev/null
+++ b/.github/workflows/shellcheck.yml
@@ -0,0 +1,26 @@
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+name: 'Lint and Test'
+
+jobs:
+ shellcheck:
+ name: Shellcheck
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Run ShellCheck
+ uses: ludeeus/action-shellcheck@master
+ with:
+ check_together: 'yes'
+ env:
+ SHELLCHECK_OPTS: -e SC2154
+
+ pre-commit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v3
+ - uses: pre-commit/action@v3.0.0
diff --git a/.github/workflows/test-and-upload-to-testpypi.yml b/.github/workflows/test-and-upload-to-testpypi.yml
deleted file mode 100644
index d73bc96e..00000000
--- a/.github/workflows/test-and-upload-to-testpypi.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: Test & Upload to TestPyPI
-
-# Controls when the action will run.
-on:
- # Triggers the workflow on push to the master branch
- push:
- branches: [ main ]
-
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "build"
- build:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
- steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
-
- # Sets up python3
- - uses: actions/setup-python@v2
- with:
- python-version: 3.8
-
- # Installs and upgrades pip, installs other dependencies and installs the package from setup.py
- - name: "Installs and upgrades pip, installs other dependencies and installs the package from setup.py"
- run: |
- # Upgrade pip
- python3 -m pip install --upgrade pip
- # Install build deps
- python3 -m pip install setuptools wheel twine
- # If requirements.txt exists, install from it
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- # Install the package from setup.py
- python3 setup.py install
-
- # Tests with unittest
- - name: Test with unittest
- run: |
- cd tests
- python3 -m unittest discover
- cd ..
-
- # Upload to TestPyPI
- - name: Build and Upload to TestPyPI
- run: |
- python3 setup.py sdist bdist_wheel
- python3 -m twine upload dist/*
- env:
- TWINE_USERNAME: __token__
- TWINE_PASSWORD: ${{ secrets.TWINE_TEST_TOKEN }}
- TWINE_REPOSITORY: testpypi
diff --git a/.github/workflows/upload-to-pip.yml b/.github/workflows/upload-to-pip.yml
deleted file mode 100644
index ddd77d85..00000000
--- a/.github/workflows/upload-to-pip.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: Upload to PIP
-
-# Controls when the action will run.
-on:
- # Triggers the workflow when a release is created
- release:
- types: [created]
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "upload"
- upload:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
- steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
-
- # Sets up python
- - uses: actions/setup-python@v2
- with:
- python-version: 3.8
-
- # Install dependencies
- - name: "Installs dependencies"
- run: |
- python3 -m pip install --upgrade pip
- python3 -m pip install setuptools wheel twine
-
- # Build and upload to PyPI
- - name: "Builds and uploads to PyPI"
- run: |
- python3 setup.py sdist bdist_wheel
- python3 -m twine upload dist/*
- env:
- TWINE_USERNAME: __token__
- TWINE_PASSWORD: ${{ secrets.TWINE_TOKEN }}
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index f17f07b3..00000000
--- a/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-__pycache__/
-build/
-dist/
-*.egg-info/
-*.egg
-venv/
-.env
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..cce767d4
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,21 @@
+# See https://pre-commit.com for more information
+repos:
+- repo: https://github.com/Lucas-C/pre-commit-hooks
+ rev: v1.1.10
+ hooks:
+ - id: remove-tabs
+
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.0.1
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: fix-byte-order-marker
+ - id: mixed-line-ending
+ - id: check-merge-conflict
+ - id: check-case-conflict
+ - id: check-executables-have-shebangs
+ - id: check-docstring-first
+ - id: check-symlinks
+ - id: destroyed-symlinks
+ - id: fix-byte-order-marker
diff --git a/README.md b/README.md
index d90d398b..93789fbe 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,129 @@
# truetool
+
A easy tool for frequently used TrueNAS SCALE CLI utilities.
-Previously known as "trueupdate"
-## How to install
+## Table of contents:
+* [Synopsis](#synopsis)
+* [Arguments](#arguments)
+* [How to Install](#how-to-install)
+* [How to Update](#how-to-update)
+* [Creating a Cron Job](#creating-a-cron-job)
+* [Additional Information](#additional-information)
-run `pip install truetool`
+
-Please be aware you will need to reinstall after every SCALE update
+## Synopsis
+
+TrueTool is a commandline tool, designed to enable some features of TrueNAS SCALE that are either not-enabled by default or not-available in the Web-GUI.
+It also offers a few handy shortcuts for commonly required chores, like: Enabling Apt or Helm
+
+## Arguments
+
+| Flag | Example | Parameter | Description |
+|----------------- |------------------------ |----------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| --delete-backup | --delete-backup | None | Opens a menu to delete backups
_Useful if you need to delete old system backups or backups from other scripts_ |
+| --restore | --restore | None | Restore TrueTool specific `ix-applications dataset` snapshot |
+| --mount | --mount | None | Initiates mounting feature
Choose between unmounting and mounting PVC data |
+| --dns | --dns | None | list all of your applications DNS names and their web ports
+| --list-backups | --list-backups | None | Prints a list of backups available
+| --helm-enable | --helm-enable | None | Enables Helm command access on SCALE
+| --apt-enable | --apt-enable | None | Enables Apt command access on SCALE
+| --no-color | --no-color | None | Disables showing colors in terminal output, usefull for SCALE Email output
+| -U | -U | None | Update applications, ignoring major version changes |
+| -u | -u | None | Update applications, do NOT update if there was a major version change |
+| -b | -b 14 | Integer | Backup `ix-appliactions` dataset
_Creates backups up to the number you've chosen_ |
+| -i | -i nextcloud -i sonarr | String | Applications listed will be ignored during updating
_List one application after another as shown in the example_ |
+| -v | -v | None | Verbose Output
_Look at the bottom of this page for an example_ |
+| -t | -t 150 | Integer | Set a custom timeout to be used with either:
`-m`
_Time the script will wait for application to be "STOPPED"_
or
`-(u\|U)`
_Time the script will wait for application to be either "STOPPED" or "ACTIVE"_ |
+| -s | -s | None | Sync Catalogs prior to updating |
+| -p | -p | None | Prune old/unused docker images |
+
+
+
+
+
+
+## How to Install
+
+### Create a Scripts Dataset
+
+In this example we created a `scripts` dataset on the Truenas SCALE system, feel free to use another folder.
+
+### Open a Terminal
+
+**Change Directory to your scripts folder**
+```
+cd /mnt/pool/scripts
+```
+
+**Git Clone truetool**
+```
+git clone https://github.com/truecharts/truetool.git
+```
+
+**Change Directory to truetool folder**
+```
+cd truetool
+```
+
+From here, you can just run truetool with `bash truetool.sh -ARGUMENTS`
+
+
## How to Update
-run `pip install --upgrade truetool`
+TrueTool updates itself automatically.
-## How to use
+
-running `truetool` should be a good start.
+### Update with your Cron Job
-Additional options are available:
+Here, we will update the script prior to running it, incase there is a bugfix, or any new additions to the script
-### Help
+**Cron Job Command**
+```
+bash /mnt/pool/scripts/truetool/truetool.sh -b 14 -rsup
+```
-- `truetool -h` for the CLI help page
+
+
+
+## Creating a Cron Job
+
+1. TrueNAS SCALE GUI
+2. System Settings
+3. Advanced
+4. Cron Jobs
+ 1. Click Add
+
+| Name | Value | Reason |
+|------------------------ |------------------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `Description` | TrueTool Update apps | This is up to you, put whatever you think is a good description in here |
+| `Command` | `bash /PATH/TO/truetool_DIRECTORY/truetool.sh --no-color -b 14 -sup` | This is the command you will be running on your schedule, example: `bash /mnt/speed/scripts/truetool/truetool.sh -b 14 -rsup` |
+| `Run As User` | `root` | Running the script as `root` is REQUIRED. You cannot access all of the kubernetes functions without this user. |
+| `Schedule` | Up to you, example: `0400` | Again up to you |
+| `Hide Standard Output` | `False` or Unticked | It's best to keep an eye on updates and enable this to recieve email reports |
+| `Hide Standard Error` | `False` or Unticked | We definately want to see what errors occured during updating |
+| `Enabled` | `True` or Ticked | This will Enable the script to run on your schedule |
-### Update
-- `truetool -u` or ` truetool --update` updates TrueNAS SCALE Apps
+
+
+
+### Additional Information
-- `truetool --catalog CATALOGNAME` where CATALOGNAME is the name of the catalog you want to process in caps
-- `truetool --versioning SCHEME` where SCHEME is the highest semver version you want to process. options: `patch`, `minor` and `major`
-- `truetool -a` or ` truetool --all` updates both active (running) and non-active (stuck or stopped) Apps
+#### TrueTool vs HeavyScript
+TrueTool and HeavyScript are based, in essence, based on the the original (python based) TrueUpdate and TrueTool.
+Then Support-Manager for TrueCharts, HeavyBullets8, ported this to Bash and started adding some additional logic and options for tasks we frequently needed our users to do, such as mounting PVC's.
-### Backup
-- `truetool -b` or ` truetool --backup` backup the complete Apps system prior to updates. Deletes old backups prior, number of old backups can be set, 14 by default
-- `truetool -r` or ` truetool --restore` restores a specific backup by name
-- `truetool -d` or ` truetool --delete` deletes a specific backup by name
+After a month or so, the TrueCharts Team officially started refactoring this expanded bash-port. Due to personal reasons, HeavyBullets by then decided to seperate from TrueCharts after merging the TrueCharts refactor into his own work. The beauty of OpenSource.
-### Other
+From this point onwards the HeavyScript and TrueTool diverged a bit.
+We internally review changes within our staff team, to verify we somewhat stick to best-practices. This means, in some cases, we decided not to port certain features from HeavyScript and did decide to add features we think are usefull and safe.
+But this also means we can give guarantees TrueTool works optimally with our Catalog of TrueNAS SCALE Apps, as well as official Apps.
-- `truetool -s` or ` truetool --sync` to sync the catalogs before running updates
-- `truetool -p` or ` truetool --prune` to prune (remove) old docker images after running auto-update
-
-### Important note
-
-Please use the above arguments seperatly, combining them might not work as you would expect.
-So use: `truetool -u -b -p -s -a`
-not: `truetool -ubpsa`
\ No newline at end of file
+Users from HeavyScript can safely start using TrueTool, as we've made precautions to ensure the backups take over smoothly.
+We, however, do *not* advice using HeavyScript with TrueCharts Apps. Not because it's a bad App, but because we offer an alternative that is validated by our Staff.
diff --git a/includes/backup.sh b/includes/backup.sh
new file mode 100755
index 00000000..1f412c6c
--- /dev/null
+++ b/includes/backup.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+## Simple shortcut to just list the backups without promts and such
+listBackups(){
+echo -e "${BWhite}Backup Listing Tool${Color_Off}"
+clear -x && echo "pulling all restore points.."
+list_backups=$(cli -c 'app kubernetes list_backups' | grep -v system-update | sort -t '_' -Vr -k2,7 | tr -d " \t\r" | awk -F '|' '{print $2}' | nl | column -t)
+[[ -z "$list_backups" ]] && echo -e "${IRed}No restore points available${Color_Off}" && exit || echo "Detected Backups:" && echo "$list_backups"
+}
+export -f listBackups
+
+## Lists backups, except system-created backups, and promts which one to delete
+deleteBackup(){
+echo -e "${BWhite}Backup Deletion Tool${Color_Off}"
+clear -x && echo "pulling all restore points.."
+list_delete_backups=$(cli -c 'app kubernetes list_delete_backups' | grep -v system-update | sort -t '_' -Vr -k2,7 | tr -d " \t\r" | awk -F '|' '{print $2}' | nl | column -t)
+clear -x
+# shellcheck disable=SC2015
+[[ -z "$list_delete_backups" ]] && echo -e "${IRed}No restore points available${Color_Off}" && exit || { title; echo -e "Choose a restore point to delete\nThese may be out of order if they are not TrueTool backups" ; }
+# shellcheck disable=SC2015
+echo "$list_delete_backups" && read -rt 600 -p "Please type a number: " selection && restore_point=$(echo "$list_delete_backups" | grep ^"$selection " | awk '{print $2}')
+[[ -z "$selection" ]] && echo "${IRed}Your selection cannot be empty${Color_Off}" && exit #Check for valid selection. If none, kill script
+[[ -z "$restore_point" ]] && echo "Invalid Selection: $selection, was not an option" && exit #Check for valid selection. If none, kill script
+echo -e "\nWARNING:\nYou CANNOT go back after deleting your restore point" || { echo "${IRed}FAILED${Color_Off}"; exit; }
+# shellcheck disable=SC2015
+echo -e "\n\nYou have chosen:\n$restore_point\n\nWould you like to continue?" && echo -e "1 Yes\n2 No" && read -rt 120 -p "Please type a number: " yesno || { echo "${IRed}FAILED${Color_Off}"; exit; }
+if [[ $yesno == "1" ]]; then
+ echo -e "\nDeleting $restore_point" && cli -c 'app kubernetes delete_backup backup_name=''"'"$restore_point"'"' &>/dev/null && echo -e "${IGreen}Sucessfully deleted${Color_Off}" || echo -e "${IRed}Deletion FAILED${Color_Off}"
+elif [[ $yesno == "2" ]]; then
+ echo "You've chosen NO, killing script."
+else
+ echo -e "${IRed}Invalid Selection${Color_Off}"
+fi
+}
+export -f deleteBackup
+
+## Creates backups and deletes backups if a "backups to keep"-count is exceeded.
+# backups-to-keep takes only heavyscript and truetool created backups into account, as other backups aren't guaranteed to be sorted correctly
+backup(){
+echo -e "${BWhite}Backup Tool${Color_Off}"
+echo -e "\nNumber of backups was set to $number_of_backups"
+date=$(date '+%Y_%m_%d_%H_%M_%S')
+[[ "$verbose" == "true" ]] && cli -c 'app kubernetes backup_chart_releases backup_name=''"'TrueTool_"$date"'"'
+[[ -z "$verbose" ]] && echo -e "\nNew Backup Name:" && cli -c 'app kubernetes backup_chart_releases backup_name=''"'TrueTool_"$date"'"' | tail -n 1
+mapfile -t list_create_backups < <(cli -c 'app kubernetes list_create_backups' | grep 'HeavyScript\|TrueTool_' | sort -t '_' -Vr -k2,7 | awk -F '|' '{print $2}'| tr -d " \t\r")
+# shellcheck disable=SC2309
+if [[ ${#list_create_backups[@]} -gt "number_of_backups" ]]; then
+ echo -e "\nDeleting the oldest backup(s) for exceeding limit:"
+ overflow=$(( ${#list_create_backups[@]} - "$number_of_backups" ))
+ mapfile -t list_overflow < <(cli -c 'app kubernetes list_create_backups' | grep "TrueTool_" | sort -t '_' -V -k2,7 | awk -F '|' '{print $2}'| tr -d " \t\r" | head -n "$overflow")
+ for i in "${list_overflow[@]}"
+ do
+ cli -c 'app kubernetes delete_backup backup_name=''"'"$i"'"' &> /dev/null || echo "${IRed}FAILED${Color_Off} to delete $i"
+ echo "$i"
+ done
+fi
+}
+export -f backup
+
+## Lists available backup and prompts the users to select a backup to restore
+restore(){
+echo -e "${BWhite}Backup Restoration Tool${Color_Off}"
+clear -x && echo "pulling restore points.."
+list_restore_backups=$(cli -c 'app kubernetes list_restore_backups' | grep "TrueTool_" | sort -t '_' -Vr -k2,7 | tr -d " \t\r" | awk -F '|' '{print $2}' | nl | column -t)
+clear -x
+# shellcheck disable=SC2015
+[[ -z "$list_restore_backups" ]] && echo "No TrueTool restore points available" && exit || { title; echo "Choose a restore point" ; }
+echo "$list_restore_backups" && read -rt 600 -p "Please type a number: " selection && restore_point=$(echo "$list_restore_backups" | grep ^"$selection " | awk '{print $2}')
+[[ -z "$selection" ]] && echo "Your selection cannot be empty" && exit #Check for valid selection. If none, kill script
+[[ -z "$restore_point" ]] && echo "Invalid Selection: $selection, was not an option" && exit #Check for valid selection. If none, kill script
+echo -e "\nWARNING:\nThis is NOT guranteed to work\nThis is ONLY supposed to be used as a LAST RESORT\nConsider rolling back your applications instead if possible" || { echo "${IRed}FAILED${Color_Off}"; exit; }
+# shellcheck disable=SC2015
+echo -e "\n\nYou have chosen:\n$restore_point\n\nWould you like to continue?" && echo -e "1 Yes\n2 No" && read -rt 120 -p "Please type a number: " yesno || { echo "${IRed}FAILED${Color_Off}"; exit; }
+if [[ $yesno == "1" ]]; then
+ echo -e "\nStarting Backup, this will take a ${BWhite}LONG${Color_Off} time." && cli -c 'app kubernetes restore_backup backup_name=''"'"$restore_point"'"' || echo "Restore ${IRed}FAILED${Color_Off}"
+elif [[ $yesno == "2" ]]; then
+ echo "You've chosen NO, killing script. Good luck."
+else
+ echo -e "${IRed}Invalid Selection${Color_Off}"
+fi
+}
+export -f restore
diff --git a/includes/chores.sh b/includes/chores.sh
new file mode 100755
index 00000000..df6d3a73
--- /dev/null
+++ b/includes/chores.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+helmEnable(){
+echo -e "${BWhite}Enabling Helm${Color_Off}"
+export KUBECONFIG=/etc/rancher/k3s/k3s.yaml && echo -e "${IGreen}Helm Enabled${Color_Off}"|| echo -e "${IRed}Helm Enable FAILED${Color_Off}"
+}
+export -f helmEnable
+
+aptEnable(){
+echo -e "${BWhite}Enabling Apt-Commands${Color_Off}"
+chmod +x /usr/bin/apt* && echo -e "${IGreen}APT enabled${Color_Off}"|| echo -e "${IRed}APT Enable FAILED${Color_Off}"
+}
+export -f aptEnable
+
+# Prune unused docker images to prevent dataset/snapshot bloat related slowdowns on SCALE
+prune(){
+echo -e "${BWhite}Docker Prune${Color_Off}"
+echo "Pruning Docker Images..."
+docker image prune -af | grep "^Total" && echo -e "${IGreen}Docker Prune Successfull${Color_Off}" || echo "Docker Prune ${IRed}FAILED${Color_Off}"
+
+# TODO Switch to middleware prune on next release
+# midclt call container.prune '{"remove_unused_images": true, "remove_stopped_containers": true}' &> /dev/null && echo "Docker Prune completed"|| echo "Docker Prune ${IRed}FAILED${Color_Off}"
+}
+export -f prune
+
+#
+sync(){
+echo -e "${BWhite}Starting Catalog Sync...${Color_Off}"
+cli -c 'app catalog sync_all' &> /dev/null && echo -e "${IGreen}Catalog sync complete${Color_Off}" || echo -e "${IRed}Catalog Sync Failed${Color_Off}"
+}
+export -f sync
diff --git a/includes/colors.sh b/includes/colors.sh
new file mode 100755
index 00000000..a1f8cbdd
--- /dev/null
+++ b/includes/colors.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+# shellcheck disable=SC2034
+
+# Reset
+Color_Off='\033[0m' # Text Reset
+
+# Regular Colors
+Black='\033[0;30m' # Black
+Red='\033[0;31m' # Red
+Green='\033[0;32m' # Green
+Yellow='\033[0;33m' # Yellow
+Blue='\033[0;34m' # Blue
+Purple='\033[0;35m' # Purple
+Cyan='\033[0;36m' # Cyan
+White='\033[0;37m' # White
+
+# Bold
+BBlack='\033[1;30m' # Black
+BRed='\033[1;31m' # Red
+BGreen='\033[1;32m' # Green
+BYellow='\033[1;33m' # Yellow
+BBlue='\033[1;34m' # Blue
+BPurple='\033[1;35m' # Purple
+BCyan='\033[1;36m' # Cyan
+BWhite='\033[1;37m' # White
+
+# Underline
+UBlack='\033[4;30m' # Black
+URed='\033[4;31m' # Red
+UGreen='\033[4;32m' # Green
+UYellow='\033[4;33m' # Yellow
+UBlue='\033[4;34m' # Blue
+UPurple='\033[4;35m' # Purple
+UCyan='\033[4;36m' # Cyan
+UWhite='\033[4;37m' # White
+
+# High Intensity
+IBlack='\033[0;90m' # Black
+IRed='\033[0;91m' # Red
+IGreen='\033[0;92m' # Green
+IYellow='\033[0;93m' # Yellow
+IBlue='\033[0;94m' # Blue
+IPurple='\033[0;95m' # Purple
+ICyan='\033[0;96m' # Cyan
+IWhite='\033[0;97m' # White
+
+
+# Bold High Intensity
+BIBlack='\033[1;90m' # Black
+BIRed='\033[1;91m' # Red
+BIGreen='\033[1;92m' # Green
+BIYellow='\033[1;93m' # Yellow
+BIBlue='\033[1;94m' # Blue
+BIPurple='\033[1;95m' # Purple
+BICyan='\033[1;96m' # Cyan
+BIWhite='\033[1;97m' # White
+
+noColor(){
+# Reset
+Color_Off=""
+
+# Regular Colors
+Black=""
+Red=""
+Green=""
+Yellow=""
+Blue=""
+Purple=""
+Cyan=""
+White=""
+
+# Bold
+BBlack=""
+BRed=""
+BGreen=""
+BYellow=""
+BBlue=""
+BPurple=""
+BCyan=""
+BWhite=""
+
+# Underline
+UBlack=""
+URed=""
+UGreen=""
+UYellow=""
+UBlue=""
+UPurple=""
+UCyan=""
+UWhite=""
+
+# High Intensity
+IBlack=""
+IRed=""
+IGreen=""
+IYellow=""
+IBlue=""
+IPurple=""
+ICyan=""
+IWhite=""
+
+
+# Bold High Intensity
+BIBlack=""
+BIRed=""
+BIGreen=""
+BIYellow=""
+BIBlue=""
+BIPurple=""
+BICyan=""
+BIWhite=""
+ }
+ export -f noColor
diff --git a/includes/dns.sh b/includes/dns.sh
new file mode 100755
index 00000000..0e12e90a
--- /dev/null
+++ b/includes/dns.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+dns(){
+ echo -e "${BWhite}Service DNS Names Tool${Color_Off}"
+clear -x
+echo "Generating Internal Service DNS Names..."
+#ignored dependency pods, may need to add more in the future.
+dep_ignore="\-cronjob\-|^kube-system|\ssvclb|NAME|\-memcached\-.[^custom\-app]|\-postgresql\-.[^custom\-app]|\-redis\-.[^custom\-app]|\-mariadb\-.[^custom\-app]|\-promtail\-.[^custom\-app]"
+
+# Pulling pod names
+mapfile -t main < <(k3s kubectl get pods -A | grep -Ev "$dep_ignore" | sort)
+
+# Pulling all ports
+all_ports=$(k3s kubectl get service -A)
+
+clear -x
+count=0
+for i in "${main[@]}"
+do
+ [[ count -le 0 ]] && echo -e "\n" && ((count++))
+ appName=$(echo "$i" | awk '{print $2}' | sed 's/-[^-]*-[^-]*$//' | sed 's/-0//')
+ ixName=$(echo "$i" | awk '{print $1}')
+ port=$(echo "$all_ports" | grep -E "\s$appName\s" | awk '{print $6}' | grep -Eo "^[[:digit:]]+{1}")
+ [[ -n "$port" ]] && echo -e "$appName.$ixName.svc.cluster.local $port"
+done | uniq | nl -b t | sed 's/\s\s\s$/- -------- ----/' | column -t -R 1 -N "#,DNS_Name,Port" -L
+}
+export -f dns
diff --git a/includes/help.sh b/includes/help.sh
new file mode 100755
index 00000000..5b778c75
--- /dev/null
+++ b/includes/help.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+help(){
+[[ $help == "true" ]] && clear -x
+echo ""
+echo -e "${BWhite}Basic Utilities${Color_Off}"
+echo "--mount | Initiates mounting feature, choose between unmounting and mounting PVC data"
+echo "--restore | Opens a menu to restore a \"truetool\" backup that was taken on your \"ix-applications\" dataset"
+echo "--delete-backup | Opens a menu to delete backups on your system"
+echo "--list-backups | Prints a list of backups available"
+echo "--helm-enable | Enables Helm command access on SCALE"
+echo "--apt-enable | Enables Apt command access on SCALE"
+echo "--dns | list all of your applications DNS names and their web ports"
+echo
+echo -e "${BWhite}Update Options${Color_Off}"
+echo "-U | Update all applications, ignores versions"
+echo "-u | Update all applications, does not update Major releases"
+echo "-b | Back-up your ix-applications dataset, specify a number after -b"
+echo "-i | Add application to ignore list, one by one, see example below."
+echo "-v | verbose output"
+echo "-t | Set a custom timeout in seconds when checking if either an App or Mountpoint correctly Started, Stopped or (un)Mounted. Defaults to 500 seconds"
+echo "-s | sync catalog"
+echo "-p | Prune unused/old docker images"
+echo
+echo -e "${BWhite}Examples${Color_Off}"
+echo "bash truetool.sh -b 14 -i portainer -i arch -i sonarr -i radarr -t 600 -vrsUp"
+echo "bash /mnt/tank/scripts/truetool.sh -t 150 --mount"
+echo "bash /mnt/tank/scripts/truetool.sh --dns"
+echo "bash /mnt/tank/scripts/truetool.sh --restore"
+echo "bash /mnt/tank/scripts/truetool.sh --delete-backup"
+echo
+exit
+}
+export -f help
diff --git a/includes/mount.sh b/includes/mount.sh
new file mode 100755
index 00000000..7d5a6019
--- /dev/null
+++ b/includes/mount.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+mountPVC(){
+echo -e "${BWhite}PVC Mounting Tool${Color_Off}"
+clear -x
+title
+echo -e "1 Mount\n2 Unmount All" && read -rt 600 -p "Please type a number: " selection
+[[ -z "$selection" ]] && echo "Your selection cannot be empty" && exit #Check for valid selection. If none, kill script
+if [[ $selection == "1" ]]; then
+ list=$(k3s kubectl get pvc -A | sort -u | awk '{print NR-1, "\t" $1 "\t" $2 "\t" $4}' | column -t | sed "s/^0/ /")
+ echo "$list" && read -rt 120 -p "Please type a number: " selection
+ [[ -z "$selection" ]] && echo "Your selection cannot be empty" && exit #Check for valid selection. If none, kill script
+ app=$(echo -e "$list" | grep ^"$selection " | awk '{print $2}' | cut -c 4- )
+ [[ -z "$app" ]] && echo "Invalid Selection: $selection, was not an option" && exit #Check for valid selection. If none, kill script
+ pvc=$(echo -e "$list" | grep ^"$selection ")
+ status=$(cli -m csv -c 'app chart_release query name,status' | grep -E "^$app\b" | awk -F ',' '{print $2}'| tr -d " \t\n\r")
+ if [[ "$status" != "STOPPED" ]]; then
+ [[ -z $timeout ]] && echo -e "\nDefault Timeout: 500" && timeout=500 || echo -e "\nCustom Timeout: $timeout"
+ SECONDS=0 && echo -e "\nScaling down $app" && midclt call chart.release.scale "$app" '{"replica_count": 0}' &> /dev/null
+ else
+ echo -e "\n$app is already stopped"
+ fi
+ while [[ "$SECONDS" -le "$timeout" && "$status" != "STOPPED" ]]
+ do
+ status=$(cli -m csv -c 'app chart_release query name,status' | grep -E "^$app\b" | awk -F ',' '{print $2}'| tr -d " \t\n\r")
+ echo -e "Waiting $((timeout-SECONDS)) more seconds for $app to be STOPPED" && sleep 5
+ done
+ data_name=$(echo "$pvc" | awk '{print $3}')
+ volume_name=$(echo "$pvc" | awk '{print $4}')
+ full_path=$(zfs list | grep "$volume_name" | awk '{print $1}')
+ echo -e "\nMounting\n$full_path\nTo\n/mnt/truetool/$data_name" && zfs set mountpoint="/truetool/$data_name" "$full_path" && echo -e "Mounted, Use the Unmount All option to unmount\n"
+ exit
+elif [[ $selection == "2" ]]; then
+ mapfile -t unmount_array < <(basename -a /mnt/truetool/* | sed "s/*//")
+ [[ -z ${unmount_array[*]} ]] && echo "Theres nothing to unmount" && exit
+ for i in "${unmount_array[@]}"
+ do
+ main=$(k3s kubectl get pvc -A | grep -E "\s$i\s" | awk '{print $1, $2, $4}')
+ app=$(echo "$main" | awk '{print $1}' | cut -c 4-)
+ pvc=$(echo "$main" | awk '{print $3}')
+ mapfile -t path < <(find /mnt/*/ix-applications/releases/"$app"/volumes/ -maxdepth 0 | cut -c 6-)
+ if [[ "${#path[@]}" -gt 1 ]]; then #if there is another app with the same name on another pool, use the current pools application, since the other instance is probably old, or unused.
+ echo "$i is a name used on more than one pool.. attempting to use your current kubernetes apps pool"
+ pool=$(cli -c 'app kubernetes config' | grep -E "dataset\s\|" | awk -F '|' '{print $3}' | awk -F '/' '{print $1}' | tr -d " \t\n\r")
+ full_path=$(find /mnt/"$pool"/ix-applications/releases/"$app"/volumes/ -maxdepth 0 | cut -c 6-)
+ zfs set mountpoint=legacy "$full_path""$pvc" && echo "$i unmounted" && rmdir /mnt/truetool/"$i" || echo "${IRed}FAILED${Color_Off} to unmount $i"
+ else
+ # shellcheck disable=SC2128
+ zfs set mountpoint=legacy "$path""$pvc" && echo "$i unmounted" && rmdir /mnt/truetool/"$i" || echo "${IRed}FAILED${Color_Off} to unmount $i"
+ fi
+ done
+ rmdir /mnt/truetool
+else
+ echo -e "${IRed}Invalid selection, \"$selection\" was not an option${Color_Off}"
+fi
+}
+export -f mountPVC
diff --git a/includes/no_args.sh b/includes/no_args.sh
new file mode 100644
index 00000000..7db6e4a5
--- /dev/null
+++ b/includes/no_args.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+no_args(){
+ echo "0 Show Help"
+ echo "1 List Internal Service DNS Names"
+ echo "2 Mount and Unmount PVC storage for easy access"
+ echo "3 List Backups"
+ echo "4 Create a Backup"
+ echo "5 Restore a Backup"
+ echo "6 Delete a Backup"
+ echo "7 Enable Helm Commands"
+ echo "8 Enable Apt and Apt-Get Commands"
+ echo "9 Update All Apps"
+ read -rt 600 -p "Please select an option by number: " selection
+
+ case $selection in
+ 1)
+ help="true"
+ ;;
+ 2)
+ dns="true"
+ ;;
+ 3)
+ mountPVC="true"
+ ;;
+ 4)
+ listBackups="true"
+ ;;
+ 5)
+ read -rt 600 -p "Please type the max number of backups to keep: " backups
+ re='^[0-9]+$'
+ number_of_backups=$backups
+ ! [[ $backups =~ $re ]] && echo -e "Error: -b needs to be assigned an interger\n\"""$number_of_backups""\" is not an interger" >&2 && exit
+ [[ "$number_of_backups" -le 0 ]] && echo "Error: Number of backups is required to be at least 1" && exit
+ ;;
+ 6)
+ restore="true"
+ ;;
+ 7)
+ deleteBackup="true"
+ ;;
+ 8)
+ helmEnable="true"
+ ;;
+ 9)
+ aptEnable="true"
+ ;;
+ 10)
+ echo ""
+ echo "1 Update Apps Excluding likely breaking major changes"
+ echo "2 Update Apps Including likely breaking major changes"
+ read -rt 600 -p "Please select an option by number: " updateType
+ if [[ "$updateType" == "1" ]]; then
+ update_apps="true"
+ elif [[ "$updateType" == "2" ]]; then
+ update_all_apps="true"
+ else
+ echo "INVALID ENTRY" && exit 1
+ fi
+ ;;
+ *)
+ echo "Unknown option" && exit 1
+ ;;
+ esac
+ echo ""
+}
+export -f no_args
diff --git a/includes/title.sh b/includes/title.sh
new file mode 100755
index 00000000..b6c9e740
--- /dev/null
+++ b/includes/title.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# Fancy ascii title.
+title(){
+if [[ -z $titleShown ]]; then
+ echo -e "${IRed} _______ _____ _ _ ";
+ echo " |__ __| / ____| | | | ";
+ echo " | |_ __ _ _ ___| | | |__ __ _ _ __| |_ ___ ";
+ echo -e "${IYellow} | | '__| | | |/ _ \ | | '_ \ / _\` | '__| __/ __|";
+ echo " | | | | |_| | __/ |____| | | | (_| | | | |_\__ \\";
+ echo -e "${IGreen} __|_|_| \__,_|\___|\_____|_| |_|\__,_|_| \__|___/";
+ echo " |__ __| |__ __| | | ";
+ echo -e "${IBlue} | |_ __ _ _ ___| | ___ ___ | | ";
+ echo " | | '__| | | |/ _ \ |/ _ \ / _ \| | ";
+ echo -e "${IPurple} | | | | |_| | __/ | (_) | (_) | | ";
+ echo " |_|_| \__,_|\___|_|\___/ \___/|_| ";
+ echo " ";
+ echo -e "${Color_Off} ";
+fi
+titleShown='true'
+}
+export -f title
diff --git a/includes/update.sh b/includes/update.sh
new file mode 100755
index 00000000..0aaa7506
--- /dev/null
+++ b/includes/update.sh
@@ -0,0 +1,123 @@
+#!/bin/bash
+
+update_apps(){
+echo -e "${BWhite}App Updater${Color_Off}"
+[[ -z $timeout ]] && echo -e "Default Timeout: 500" && timeout=500 || echo -e "\nCustom Timeout: $timeout"
+[[ "$timeout" -le 120 ]] && echo "Warning: Your timeout is set low and may lead to premature rollbacks or skips"
+
+echo ""
+echo "Creating list of Apps to update..."
+
+# Render a list of ignored applications, so users can verify their ignores got parsed correctly.
+if [[ -z ${ignore[*]} ]]; then
+ echo "No apps added to ignore list, continuing..."
+else
+ echo "ignored applications:"
+ for ignored in "${ignore[@]}"
+ do
+ echo "${ignored}"
+ done
+fi
+echo ""
+
+mapfile -t array < <(cli -m csv -c 'app chart_release query name,update_available,human_version,human_latest_version,container_images_update_available,status' | grep -E ",true(,|$)" | sort)
+[[ -z ${array[*]} ]] && echo -e "\nThere are no updates available or middleware timed out" && return 0 || echo -e "\n${#array[@]} update(s) available:"
+PIDlist=()
+
+# Draft a list of app names, seperate from actuall execution
+# This prevents outputs getting mixed together
+for i in "${array[@]}"
+do
+ app_name=$(echo "$i" | awk -F ',' '{print $1}') #print out first catagory, name.
+ echo "$app_name"
+done
+
+echo ""
+echo "Updating Apps..."
+
+# Create a background task for each update as async solution
+for i in "${array[@]}"
+do
+ executeUpdate "${i}" &
+ PIDlist+=($!)
+done
+echo ""
+echo "Waiting for update results..."
+
+# Wait for all the async updates to complete
+for p in "${PIDlist[@]}"
+do
+ wait "${p}" ||:
+done
+
+}
+export -f update_apps
+
+
+
+# This is a combination of stopping previously-stopped apps and apps stuck Deploying after update
+after_update_actions(){
+SECONDS=0
+count=0
+sleep 15
+
+# Keep this running and exit the endless-loop based on a timer, instead of a countered-while-loop
+# shellcheck disable=SC2050
+while [[ "0" != "1" ]]
+do
+ (( count++ ))
+ status=$(cli -m csv -c 'app chart_release query name,update_available,human_version,human_latest_version,status' | grep "^$app_name," | awk -F ',' '{print $2}')
+ if [[ "$status" == "ACTIVE" && "$startstatus" == "STOPPED" ]]; then
+ [[ "$verbose" == "true" ]] && echo "Returing to STOPPED state.."
+ midclt call chart.release.scale "$app_name" '{"replica_count": 0}' &> /dev/null && echo "Stopped"|| echo "FAILED"
+ break
+ elif [[ "$SECONDS" -ge "$timeout" && "$status" == "DEPLOYING" && "$failed" != "true" ]]; then
+ echo -e "Error: Run Time($SECONDS) for $app_name has exceeded Timeout($timeout)\nIf this is a slow starting application, set a higher timeout with -t\nIf this applicaion is always DEPLOYING, you can disable all probes under the Healthcheck Probes Liveness section in the edit configuration\nReverting update.."
+ midclt call chart.release.rollback "$app_name" "{\"item_version\": \"$rollback_version\"}" &> /dev/null
+ [[ "$startstatus" == "STOPPED" ]] && failed="true" && after_update_actions && unset failed #run back after_update_actions function if the app was stopped prior to update
+ break
+ elif [[ "$SECONDS" -ge "$timeout" && "$status" == "DEPLOYING" && "$failed" == "true" ]]; then
+ echo -e "Error: Run Time($SECONDS) for $app_name has exceeded Timeout($timeout)\nThe application failed to be ACTIVE even after a rollback,\nManual intervention is required\nAbandoning"
+ break
+ elif [[ "$status" == "STOPPED" ]]; then
+ [[ "$count" -le 1 && "$verbose" == "true" ]] && echo "Verifying Stopped.." && sleep 15 && continue #if reports stopped on FIRST time through loop, double check
+ [[ "$count" -le 1 && -z "$verbose" ]] && sleep 15 && continue #if reports stopped on FIRST time through loop, double check
+ echo "Stopped" && break #if reports stopped any time after the first loop, assume its extermal services.
+ elif [[ "$status" == "ACTIVE" ]]; then
+ [[ "$count" -le 1 && "$verbose" == "true" ]] && echo "Verifying Active.." && sleep 15 && continue #if reports active on FIRST time through loop, double check
+ [[ "$count" -le 1 && -z "$verbose" ]] && sleep 15 && continue #if reports active on FIRST time through loop, double check
+ echo "Active" && break #if reports active any time after the first loop, assume actually active.
+ else
+ [[ "$verbose" == "true" ]] && echo "Waiting $((timeout-SECONDS)) more seconds for $app_name to be ACTIVE"
+ sleep 15
+ continue
+ fi
+done
+}
+export -f after_update_actions
+
+# Determine what all the required information for the App to update, check it and execute the update using the SCALE API
+executeUpdate(){
+ app_name=$(echo "$1" | awk -F ',' '{print $1}') #print out first catagory, name.
+ old_app_ver=$(echo "$1" | awk -F ',' '{print $4}' | awk -F '_' '{print $1}' | awk -F '.' '{print $1}') #previous/current Application MAJOR Version
+ new_app_ver=$(echo "$1" | awk -F ',' '{print $5}' | awk -F '_' '{print $1}' | awk -F '.' '{print $1}') #new Application MAJOR Version
+ old_chart_ver=$(echo "$1" | awk -F ',' '{print $4}' | awk -F '_' '{print $2}' | awk -F '.' '{print $1}') # Old Chart MAJOR version
+ new_chart_ver=$(echo "$1" | awk -F ',' '{print $5}' | awk -F '_' '{print $2}' | awk -F '.' '{print $1}') # New Chart MAJOR version
+ status=$(echo "$1" | awk -F ',' '{print $2}') #status of the app: STOPPED / DEPLOYING / ACTIVE
+ startstatus=$status
+ diff_app=$(diff <(echo "$old_app_ver") <(echo "$new_app_ver")) #caluclating difference in major app versions
+ diff_chart=$(diff <(echo "$old_chart_ver") <(echo "$new_chart_ver")) #caluclating difference in Chart versions
+ old_full_ver=$(echo "$1" | awk -F ',' '{print $4}') #Upgraded From
+ new_full_ver=$(echo "$1" | awk -F ',' '{print $5}') #Upraded To
+ rollback_version=$(echo "$1" | awk -F ',' '{print $4}' | awk -F '_' '{print $2}')
+ printf '%s\0' "${ignore[@]}" | grep -iFxqz "${app_name}" && echo -e "\n$app_name\nIgnored, skipping" && return #If application is on ignore list, skip
+ if [[ "$diff_app" == "$diff_chart" || "$update_all_apps" == "true" ]]; then #continue to update
+ [[ "$verbose" == "true" ]] && echo "Updating.."
+ # shellcheck disable=SC2015
+ cli -c 'app chart_release upgrade release_name=''"'"$app_name"'"' &> /dev/null && echo -e "Updated $app_name\n$old_full_ver\n$new_full_ver" && after_update_actions || { echo -e "$app_name: update ${IRed}FAILED${Color_Off}"; return; }
+ else
+ echo -e "\n$app_name\nMajor Release, update manually"
+ return
+ fi
+}
+export -f executeUpdate
diff --git a/includes/update_self.sh b/includes/update_self.sh
new file mode 100755
index 00000000..7ade24e2
--- /dev/null
+++ b/includes/update_self.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+## AutoUpdate TrueTool using Git
+updater(){
+echo -e "${BWhite}Checking for updates...${Color_Off}"
+git remote set-url origin "${targetRepo}"
+BRANCH=$(git rev-parse --abbrev-ref HEAD)
+git fetch -q
+git update-index -q --refresh
+if [[ $(git status --branch --porcelain) == *"behind"* ]]; then
+ echo -e "${IPurple}TrueTool requires update${Color_Off}"
+ git reset --hard -q
+ git checkout -q "${BRANCH}"
+ git pull -q
+ echo "script updated"
+ if [[ "$CHANGED" == "true" ]]; then
+ echo "LOOP DETECTED, exiting"
+ exit 1
+ else
+ echo "restarting script after update..."
+ export CHANGED="true"
+ . "${SCRIPT_DIR}/truetool.sh" "$@"
+ exit
+ fi
+else
+ echo -e "${IGreen}script up-to-date${Color_Off}"
+ export CHANGED="false"
+fi
+echo ""
+}
+export -f updater
diff --git a/setup.py b/setup.py
deleted file mode 100644
index fee39cbb..00000000
--- a/setup.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from setuptools import setup, find_packages
-from os.path import abspath, dirname, join
-
-# Fetches the content from README.md
-# This will be used for the "long_description" field.
-README_MD = open(join(dirname(abspath(__file__)), "README.md")).read()
-
-setup(
- name="truetool",
- version="3.0.3",
-
- # The packages that constitute your project.
- # For my project, I have only one - "pydash".
- # Either you could write the name of the package, or
- # alternatively use setuptools.findpackages()
- #
- # If you only have one file, instead of a package,
- # you can instead use the py_modules field instead.
- # EITHER py_modules OR packages should be present.
- packages=find_packages(),
-
- entry_points = {
- 'console_scripts': ['truetool=truetool.command_line:main'],
- },
-
- # The description that will be shown on PyPI.
- # Keep it short and concise
- # This field is OPTIONAL
- description="An easy utility to managed frequently used TrueNAS SCALE CLI features",
-
- # The content that will be shown on your project page.
- # In this case, we're displaying whatever is there in our README.md file
- # This field is OPTIONAL
- long_description=README_MD,
-
- # Now, we'll tell PyPI what language our README file is in.
- # In my case it is in Markdown, so I'll write "text/markdown"
- # Some people use reStructuredText instead, so you should write "text/x-rst"
- # If your README is just a text file, you have to write "text/plain"
- # This field is OPTIONAL
- long_description_content_type="text/markdown",
-
- # The url field should contain a link to a git repository, the project's website
- # or the project's documentation. I'll leave a link to this project's Github repository.
- # This field is OPTIONAL
- url="https://github.com/truecharts/truetool",
-
- # The author name and email fields are self explanatory.
- # These fields are OPTIONAL
- author_name="truecharts",
- author_email="into@truecharts.org",
-
- # For additional fields, check:
- # https://github.com/pypa/sampleproject/blob/master/setup.py
-)
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/truetool.sh b/truetool.sh
new file mode 100755
index 00000000..a7cffded
--- /dev/null
+++ b/truetool.sh
@@ -0,0 +1,166 @@
+#!/bin/bash
+
+# Constants
+SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
+dir=$(basename "$SCRIPT_DIR")
+
+# Change this if you want to fork the project
+enableUpdate="true"
+targetRepo="https://github.com/truecharts/truetool.git"
+
+
+# Includes
+# shellcheck source=includes/backup.sh
+source includes/backup.sh
+# shellcheck source=includes/chores.sh
+source includes/chores.sh
+# shellcheck source=includes/colors.sh
+source includes/colors.sh
+# shellcheck source=includes/dns.sh
+source includes/dns.sh
+# shellcheck source=includes/help.sh
+source includes/help.sh
+# shellcheck source=includes/mount.sh
+source includes/mount.sh
+# shellcheck source=includes/no_args.sh
+source includes/no_args.sh
+# shellcheck source=includes/title.sh
+source includes/title.sh
+# shellcheck source=includes/update.sh
+source includes/update.sh
+# shellcheck source=includes/update_self.sh
+source includes/update_self.sh
+
+# CD to the folder containing the script to ensure consistent runs
+cd "${SCRIPT_DIR}" || echo -e "${IRed}ERROR: Something went wrong accessing the script directory${Color_Off}"
+
+title
+
+[[ "$enableUpdate" == "true" ]] && updater "$@"
+
+#If no argument is passed, kill the script.
+if [[ -z "$*" || "-" == "$*" || "--" == "$*" ]]; then
+ no_args
+else
+
+ # Parse script options
+ while getopts ":si:b:t:uUpSv-:" opt
+ do
+ case $opt in
+ -)
+ case "${OPTARG}" in
+ help)
+ help="true"
+ ;;
+ dns)
+ dns="true"
+ ;;
+ mount)
+ mountPVC="true"
+ ;;
+ restore)
+ restore="true"
+ ;;
+ delete-backup)
+ deleteBackup="true"
+ ;;
+ list-backups)
+ listBackups="true"
+ ;;
+ helm-enable)
+ helmEnable="true"
+ ;;
+ apt-enable)
+ aptEnable="true"
+ ;;
+ no-color)
+ noColor
+ ;;
+ *)
+ echo -e "Invalid Option \"--$OPTARG\"\n" && help
+ exit
+ ;;
+ esac
+ ;;
+ \?)
+ echo -e "Invalid Option \"-$OPTARG\"\n" && help
+ exit
+ ;;
+ :)
+ echo -e "Option: \"-$OPTARG\" requires an argument\n" && help
+ exit
+ ;;
+ b)
+ re='^[0-9]+$'
+ number_of_backups=$OPTARG
+ ! [[ $OPTARG =~ $re ]] && echo -e "Error: -b needs to be assigned an interger\n\"""$number_of_backups""\" is not an interger" >&2 && exit
+ [[ "$number_of_backups" -le 0 ]] && echo "Error: Number of backups is required to be at least 1" && exit
+ ;;
+ i)
+ ignore+=("$OPTARG")
+ ;;
+ t)
+ re='^[0-9]+$'
+ timeout=$OPTARG
+ ! [[ $timeout =~ $re ]] && echo -e "Error: -t needs to be assigned an interger\n\"""$timeout""\" is not an interger" >&2 && exit
+ ;;
+ s)
+ sync="true"
+ ;;
+ U)
+ update_all_apps="true"
+ ;;
+ u)
+ update_apps="true"
+ ;;
+ p)
+ prune="true"
+ ;;
+ v)
+ verbose="true"
+ ;;
+ *)
+ echo -e "Invalid Option \"--$OPTARG\"\n" && help
+ exit
+ ;;
+ esac
+ done
+fi
+
+## Exit if incompatable functions are called
+[[ "$update_all_apps" == "true" && "$update_apps" == "true" ]] && echo -e "-U and -u cannot BOTH be called" && exit
+
+## Exit if unsafe combinations are used
+# Restore and update right after eachother, might cause super weird issues tha are hard to bugtrace
+[[ ( "$update_all_apps" == "true" || "$update_apps" == "true" ) && ( "$restore" == "true" ) ]] && echo -e "Update and Restore cannot both be done in the same run..." && exit
+
+# Backup Deletion is generally considered to be a "once in a while" thing and not great to sync with automated updates for that reason
+[[ ( "$update_all_apps" == "true" || "$update_apps" == "true" ) && ( "$deleteBackup" == "true" ) ]] && echo -e "Update Backup-Deletion cannot both be done in the same run..." && exit
+
+# Backup Deletion is generally considered to be a "once in a while" thing and not great to sync with automated updates for that reason
+[[ ( "$update_all_apps" == "true" || "$update_apps" == "true" ) && ( "$deleteBackup" == "true" ) ]] && echo -e "Update and Backup-Deletion cannot both be done in the same run..." && exit
+
+# Backup listing is a printout, which would either clutter the output or be already outdated when combined with backup
+[[ ( "$update_all_apps" == "true" || "$update_apps" == "true" ) && ( "$listBackups" == "true" ) ]] && echo -e "Update and Listing Backups cannot both be done in the same run..." && exit
+
+# Backup backup would be done after a backup is restored, which would lead to a backup that is... the same as the one restored...
+[[ ( "$restore" == "true" && "$number_of_backups" -ge 1 )]] && echo -e "Restoring a backup and making a backup cannot both be done in the same run..." && exit
+
+# While technically possible, this is asking for user error... where a user by habit mistakes one prompt, for the other.
+[[ ( "$restore" == "true" && "$deleteBackup" == "true" )]] && echo -e "restoring a backup and deleting a backup cannot both be done in the same run..." && exit
+
+
+# Continue to call functions in specific order
+[[ "$help" == "true" ]] && help
+[[ "$helmEnable" == "true" ]] && helmEnable
+[[ "$aptEnable" == "true" ]] && aptEnable
+[[ "$aptEnable" == "true" || "$helmEnable" == "true" ]] && exit
+[[ "$listBackups" == "true" ]] && listBackups && exit
+[[ "$deleteBackup" == "true" ]] && deleteBackup && exit
+[[ "$dns" == "true" ]] && dns && exit
+[[ "$restore" == "true" ]] && restore && exit
+[[ "$mountPVC" == "true" ]] && mountPVC && exit
+[[ "$number_of_backups" -ge 1 ]] && backup
+[[ "$sync" == "true" ]] && sync
+[[ "$update_all_apps" == "true" || "$update_apps" == "true" ]] && update_apps
+[[ "$prune" == "true" ]] && prune
diff --git a/truetool/__init__.py b/truetool/__init__.py
deleted file mode 100644
index cb06dc45..00000000
--- a/truetool/__init__.py
+++ /dev/null
@@ -1,238 +0,0 @@
-import subprocess
-import sys
-import argparse
-import time
-from datetime import datetime
-
-class Chart(object):
- def __setattr__(self, name, value):
- if name == 'status':
- value = value.casefold()
- if 'update_available' in name:
- value = True if value.casefold() == "true" else False
- super(Chart, self).__setattr__(name, value)
-
- def new_attr(self, attr):
- setattr(self, attr, attr)
-
-INSTALLED_CHARTS = []
-
-def parse_headers(charts: str):
- for line in charts.split("\n"):
- if line.startswith("+-"):
- continue
- if "name" in line.casefold():
- return [col.strip() for col in line.casefold().strip().split("|") if col and col != ""]
-
-def parse_charts(charts: str):
- headers = parse_headers(charts)
- table_data = charts.split("\n")[3:-2:] # Skip the header part of the table
- for row in table_data:
- row = [section.strip() for section in row.split("|") if section and section != ""]
- chart = Chart()
- for item in zip(headers, row):
- setattr(chart, item[0], item[1])
- INSTALLED_CHARTS.append(chart)
-
-def check_semver(current: str, latest: str):
- split_current_semver = current.split(".", 3)
- split_latest_semver = latest.split(".", 3)
- if split_current_semver[0] != split_latest_semver[0]:
- type="major"
- if VERSIONING == "major":
- return True
- if split_current_semver[1] != split_latest_semver[1]:
- type="minor"
- if VERSIONING != "patch":
- return True
- if split_current_semver[2] != split_latest_semver[2]:
- type="patch"
- return True
- return False
-
-
-def execute_upgrades():
- if UPDATE:
- if ALL:
- if CATALOG == "ALL":
- filtered = filter(lambda a: a.update_available, INSTALLED_CHARTS)
- else:
- filtered = filter(lambda a: a.update_available and a.catalog == CATALOG, INSTALLED_CHARTS)
- else:
- if CATALOG == "ALL":
- filtered = filter(lambda a: a.update_available and a.status == "active", INSTALLED_CHARTS)
- else:
- filtered = filter(lambda a: a.update_available and a.status == "active" and a.catalog == CATALOG, INSTALLED_CHARTS)
- for chart in filtered:
- pre_update_ver = chart.human_version
- post_update_ver = chart.human_latest_version
- split_current_version = chart.human_version.split("_", 1)
- current_version = split_current_version[1]
- split_latest = chart.human_latest_version.split("_", 1)
- latest_version = split_latest[1]
- if check_semver(current_version, latest_version):
- print(f"Updating {chart.name}... \n")
- pre_update_ver = chart.human_version
- result = subprocess.run(['cli', '-c', f'app chart_release upgrade release_name="{chart.name}"'], capture_output=True)
- post_update_ver = chart.human_latest_version
- if "Upgrade complete" not in result.stdout.decode('utf-8'):
- print(f"{chart.name} failed to upgrade. \n{result.stdout.decode('utf-8')}")
- else:
- print(f"{chart.name} upgraded ({pre_update_ver} --> {post_update_ver})")
- else:
- print("Update disabled, skipping...")
-
-def fetch_charts():
- rawcharts = subprocess.run(["cli", "-c", "app chart_release query"], stdout=subprocess.PIPE)
- charts = rawcharts.stdout.decode('utf-8')
- return(charts)
-
-def process_args():
- global CATALOG
- global VERSIONING
- global SYNC
- global PRUNE
- global ALL
- global BACKUP
- global UPDATE
- global RESTORE
- global LIST
- global DELETE
- parser = argparse.ArgumentParser(description='TrueCharts CLI Tool. Warning: please do NOT combine short arguments like -ubs always use -u -b -s etc.')
- parser.add_argument('-c', '--catalog', nargs='?', default='ALL', help='name of the catalog you want to process in caps. Or "ALL" to render all catalogs.')
- parser.add_argument('-v', '--versioning', nargs='?', default='minor', help='Name of the versioning scheme you want to update. Options: major, minor or patch. Defaults to minor')
- parser.add_argument('-b', '--backup', nargs='?', const='14', help='backup the complete Apps system prior to updates, add a number to specify the max old backups to keep')
- parser.add_argument('-r', '--restore', nargs='?', help='restore a previous backup, disables all other features')
- parser.add_argument('-d', '--delete', nargs='?', help='delete a specific backup')
- parser.add_argument('-s', '--sync', action="store_true", help='sync catalogs before trying to update')
- parser.add_argument('-u', '--update', action="store_true", help='update the Apps in the selected catalog')
- parser.add_argument('-p', '--prune', action="store_true", help='prune old docker images after update')
- parser.add_argument('-a', '--all', action="store_true", help='update all apps for said catalog, including "stopped" or "stuck" apps')
- parser.add_argument('-l', '--list', action="store_true", help='lists existing backups')
- args = parser.parse_args()
- CATALOG = args.catalog
- VERSIONING = args.versioning
- RESTORE = args.restore
- BACKUP = args.backup
- DELETE = args.delete
- if args.update:
- UPDATE = True
- else:
- UPDATE = False
- if args.sync:
- SYNC = True
- else:
- SYNC = False
- if args.prune:
- PRUNE = True
- else:
- PRUNE = False
- if args.all:
- ALL = True
- else:
- ALL = False
- if args.list:
- LIST = True
- else:
- LIST = False
-
-
-def sync_catalog():
- if SYNC:
- print("Syncing Catalogs...\n")
- process = subprocess.Popen(["cli", "-c", "app catalog sync_all"], stdout=subprocess.PIPE)
- while process.poll() is None:
- lines = process.stdout.readline()
- print (lines.decode('utf-8'))
- temp = process.stdout.read()
- if temp:
- print (temp.decode('utf-8'))
- else:
- print("Catalog Sync disabled, skipping...")
-
-def docker_prune():
- if PRUNE:
- print("Pruning old docker images...\n")
- process = subprocess.run(["docker", "image", "prune", "-af"], stdout=subprocess.PIPE)
- print("Images pruned.\n")
- else:
- print("Container Image Pruning disabled, skipping...")
-
-def apps_backup():
- if BACKUP:
- print(f"Cleaning old backups to a max. of {BACKUP}...\n")
- backups_fetch = get_backups_names()
- backups_cleaned = [k for k in backups_fetch if 'TrueTool' in k]
- backups_remove = backups_cleaned[:len(backups_cleaned)-int(BACKUP)]
- for backup in backups_remove:
- backups_delete(backup)
-
- print("Running App Backup...\n")
- now = datetime.now()
- command = "app kubernetes backup_chart_releases backup_name=TrueTool_"+now.strftime("%Y_%m_%d_%H_%M_%S")
- process = subprocess.Popen(["cli", "-c", command], stdout=subprocess.PIPE)
- while process.poll() is None:
- lines = process.stdout.readline()
- print (lines.decode('utf-8'))
- temp = process.stdout.read()
- if temp:
- print (temp.decode('utf-8'))
- else:
- print("Backup disabled, skipping...")
-
-def backups_list():
- if LIST:
- print("Generating Backup list...\n")
- backups = get_backups_names()
- for backup in backups:
- print(f"{backup}")
-
-def backups_delete(backup: str):
- print(f"removing {backup}...")
- process = subprocess.run(["midclt", "call", "kubernetes.delete_backup", backup], stdout=subprocess.PIPE)
-
-def get_backups_names():
- names = []
- process = subprocess.run(["cli", "-c", "app kubernetes list_backups"], stdout=subprocess.PIPE)
- output = process.stdout.decode('utf-8')
- for line in output.split("\n"):
- if line.startswith("+-"):
- continue
- else:
- rowlist = [col.strip() for col in line.strip().split("|") if col and col != ""]
- if rowlist:
- names.append(rowlist[0])
- names.sort()
- return names
-
-def apps_restore():
- print("Running Backup Restore...\n")
- process = subprocess.run(["midclt", "call", "kubernetes.restore_backup", RESTORE], stdout=subprocess.PIPE)
- time.sleep(5)
- print("Restoration started, please check the restoration process in the TrueNAS SCALE Web GUI...\n")
- print("Please remember: This can take a LONG time.\n")
-
-def run():
- process_args()
- print("Starting TrueCharts TrueTool...\n")
- if RESTORE:
- apps_restore()
- elif LIST:
- backups_list()
- elif DELETE:
- backups_delete(DELETE)
- else:
- apps_backup()
- sync_catalog()
- charts = fetch_charts()
- parse_charts(charts)
- print("Executing Updates...\n")
- execute_upgrades()
- docker_prune()
- print("TrueTool Finished\n")
- exit(0)
-
-
-if __name__ == '__main__':
- run()
-
diff --git a/truetool/command_line.py b/truetool/command_line.py
deleted file mode 100644
index 33e703bc..00000000
--- a/truetool/command_line.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import truetool
-
-def main():
- truetool.run()
\ No newline at end of file