New Version of TrueTool
This commit is contained in:
parent
e487c84139
commit
975493bb85
26
.github/workflows/shellcheck.yml
vendored
Normal file
26
.github/workflows/shellcheck.yml
vendored
Normal 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
|
@ -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
|
43
.github/workflows/upload-to-pip.yml
vendored
43
.github/workflows/upload-to-pip.yml
vendored
@ -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
7
.gitignore
vendored
@ -1,7 +0,0 @@
|
||||
__pycache__/
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
*.egg
|
||||
venv/
|
||||
.env
|
21
.pre-commit-config.yaml
Normal file
21
.pre-commit-config.yaml
Normal 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
135
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`
|
||||
<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
82
includes/backup.sh
Executable 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
31
includes/chores.sh
Executable 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
113
includes/colors.sh
Executable 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
27
includes/dns.sh
Executable 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
34
includes/help.sh
Executable 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
57
includes/mount.sh
Executable 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
67
includes/no_args.sh
Normal 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
22
includes/title.sh
Executable 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
123
includes/update.sh
Executable 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
31
includes/update_self.sh
Executable 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
|
55
setup.py
55
setup.py
@ -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
|
||||
)
|
166
truetool.sh
Executable file
166
truetool.sh
Executable 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
|
@ -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()
|
||||
|
@ -1,4 +0,0 @@
|
||||
import truetool
|
||||
|
||||
def main():
|
||||
truetool.run()
|
Loading…
Reference in New Issue
Block a user