New Version of TrueTool

This commit is contained in:
Kjeld Schouten-Lebbing 2022-06-14 10:36:45 +02:00
parent e487c84139
commit 975493bb85
No known key found for this signature in database
GPG Key ID: 3D586240A9175B99
21 changed files with 907 additions and 433 deletions

26
.github/workflows/shellcheck.yml vendored Normal file
View File

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

View File

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

View File

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

7
.gitignore vendored
View File

@ -1,7 +0,0 @@
__pycache__/
build/
dist/
*.egg-info/
*.egg
venv/
.env

21
.pre-commit-config.yaml Normal file
View File

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

135
README.md
View File

@ -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`
<br>
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<br>_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<br>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<br>_Creates backups up to the number you've chosen_ |
| -i | -i nextcloud -i sonarr | String | Applications listed will be ignored during updating<br>_List one application after another as shown in the example_ |
| -v | -v | None | Verbose Output<br>_Look at the bottom of this page for an example_ |
| -t | -t 150 | Integer | Set a custom timeout to be used with either:<br>`-m` <br>_Time the script will wait for application to be "STOPPED"_<br>or<br>`-(u\|U)` <br>_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 |
<br>
<br>
## 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`
<br>
## How to Update
run `pip install --upgrade truetool`
TrueTool updates itself automatically.
## How to use
<br >
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
<br >
<br >
## 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
<br >
<br >
### 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`
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.

82
includes/backup.sh Executable file
View File

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

31
includes/chores.sh Executable file
View File

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

113
includes/colors.sh Executable file
View File

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

27
includes/dns.sh Executable file
View File

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

34
includes/help.sh Executable file
View File

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

57
includes/mount.sh Executable file
View File

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

67
includes/no_args.sh Normal file
View File

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

22
includes/title.sh Executable file
View File

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

123
includes/update.sh Executable file
View File

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

31
includes/update_self.sh Executable file
View File

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

View File

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

View File

166
truetool.sh Executable file
View File

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

View File

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

View File

@ -1,4 +0,0 @@
import truetool
def main():
truetool.run()