Scripting

docker compose helper scripts
/usr/local/bin/uplog
Helper Scripts
The docker helper scripts are to allow more rapid access to frequent requests
create 
/usr/local/bin/uplog
chmod +x 
/usr/local/bin/uplog
#!/bin/bash
# /usr/local/bin/uplog
if [[ $# -eq 0 ]]
then
  docker compose up -d && docker compose logs -f
else
  docker compose -f $1.yml up -d && docker compose -f $1.yml logs -f
fi
update profile aliases
vi 
~/.bash_aliases
logout and back in
# additional bash_alias entries for docker management
alias down='docker compose down'
alias up='docker compose up -d'
alias logs='docker compose logs -f'
no additional details
docker-image-prune-3months
a script to remove all un-used docker images over 3 months old
#!/bin/bash
# docker-image-prune-3months.sh
echo "BEFORE $(docker image ls |wc -l)"
time docker image prune --all --filter "until=2160h"
echo "AFTER $(docker image ls |wc -l)"
docker-latest-images
pulls the latest docker images and restarts affected containers
#!/bin/bash
# docker-latest-images.sh 
find /home/docker/services -iname docker-compose.yml -exec docker compose -f "{}" pull \;
find /home/docker/services -mindepth 1 -maxdepth 1 -type d | while read -r DIR
do
	cd "${DIR}"
	TEST=$(docker compose ps -q)
	[ -z $TEST ] && cd "${DIR}" && docker compose up
	[ -z $TEST ] || echo "${DIR} not running"
done
further info
Product Home
n/a
Documentation
n/a
Github
n/a
DockerHub
n/a
Misc
n/a

SSL Wildcard Letsencrypt Certificate Renewal
/usr/local/bin/renew-ssl-wildcard.sh
SSL Check and Renew Wildcard
uses Letsencrypt with OVH api to request a wildcard certificate 
#@pknw1
#!/bin/bash
# renew wildcard SSL certs
DOMAIN=$1
function renew() {
sudo docker run -it --rm --name certbot \
    -v "/etc/letsencrypt:/etc/letsencrypt" \
    -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
    -v "/root/ovh.conf:/ovh.conf" \
    certbot/dns-ovh certonly --dns-ovh --dns-ovh-credentials /ovh.conf \
    --agree-tos -m pknw1@hotmail.co.uk \
    -d *."${DOMAIN}" -d "${DOMAIN}"
}
function merge() {
	if [ -f /etc/ssl/private/wildcard-"${DOMAIN}".pem ]; then sudo rm /etc/ssl/private/wildcard-"${DOMAIN}".pem; fi
	sudo find /etc/letsencrypt/live -type l -iname '*pem' -mmin -3 -exec cat "{}" >> /etc/ssl/private/wildcard-"${DOMAIN}".pem \;
}
function pfx() {
	if [ -f /etc/ssl/private/wildcard-"${DOMAIN}".pem ]; then sudo openssl pkcs12 -inkey /etc/ssl/private/wildcard-"${DOMAIN}".pem -in /etc/ssl/private/wildcard-"${DOMAIN}".pem -export -out /etc/ssl/private/wildcard-"${DOMAIN}".pfx -passout pass:
	fi
}
renew
merge
pfx
ntfy.sh ssl renewal complete
pemCheck script
This script checks a certificate to check if it is expired or not
#!/bin/bash
# /usr/local/bin/pemCheck <certfile>
CURRENT=$(date +%s)
CERT=$(openssl x509 -enddate -noout -in "${1}" | awk -F\= '{print $2}')
CHECK=$(date -d"${CERT}" +%s)
if [[ $CHECK -lt $CURRENT ]]
then
	echo "removing ${1}"
	rm "${1}"
else
	echo "valid cert ${1} expires ${CERT}"
fi
asdas
script info
check mark
further info
Product Home
Link
Documentation
Link
Github
Link
DockerHub
Link
Misc
Link
more text more text

ntfy.sh notification wrapper
/usr/local/bin/ntfy.sh
Custom ntfy wrapper
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. 
#!/bin/bash
#/usr/local/bin/ntfy.sh
TOPIC="notflix"
LOGFILE="/var/log/ntfy-${TOPIC}"
OUTPUT=""
if [ $# -eq 0 ]
then
	echo "requires at least one arg"
else
	for i in "$@"
	do
		MSG="${MSG} $i"
	done
	OUTPUT=$(curl -s -d "${MSG}" ntfy.sh/"${TOPIC}")
	echo "${OUTPUT}" >> "${LOGFILE}"
	echo "Message${MSG} sent to topic ${TOPIC}"
fi
sdfsd
requires local install of ntfy cli
further info
Product Home
Link
Documentation
Link
Github
Link
DockerHub
Link
Misc
Link
more text more text

folder-backup configurable folder backup wrapper
/usr/local/bin/folder-backup
Custom Backup Script
Custom script using a configurable persistent local config to archive and compress the folder contents while excluding any file/folder patterns
#!/bin/bash
TARGET='/dev/shm'
LOGPATH='/dev/shm'
if [[ $# -eq 0 ]]
then
  SOURCE=$(pwd)
else
  SOURCE="${1}"
fi
if ! [[ -f "${SOURCE}/.backup-options" ]]
then
   FULL_PATH=$(realpath "${SOURCE}")
   SOURCE_FOLDER=$(echo "${FULL_PATH}" | awk -F/ '{print $NF}')
   touch "${FULL_PATH}/.backup-options"
   cat <<EOF > "${FULL_PATH}/.backup-options"
BACKUP_NAME=${SOURCE_FOLDER}
BACKUP_ROOT=${FULL_PATH}
EXCLUDE=(.git .venv)
DESTINATION=${TARGET}
EOF
echo "created config file .backup-options - update exclusions"
cat "${FULL_PATH}/.backup-options"
else
  source "${SOURCE}/.backup-options"
  excludes=()    # start with an empty array
  for excl in  ${EXCLUDE[*]} ; do    # for each extra argument...
    excludes+=(--exclude "$excl")    # add an exclude to the array
  done
  if [[ -f ${DESTINATION}/${BACKUP_NAME}.tar ]]
  then
    BACKUP_OPTIONS="uzvf"
  else
    BACKUP_OPTIONS="czvf"
  fi
  echo $(pwd)
  cat << EOF >> ${LOGPATH}/${BACKUP_NAME}.log
  
  $(date) start backup
EOF
  tar -${BACKUP_OPTIONS} ${DESTINATION}/${BACKUP_NAME}.tgz ${excludes[@]} * 2>&1 >> ${LOGPATH}/${BACKUP_NAME}.log
  cat << EOF >> ${LOGPATH}/${BACKUP_NAME}.log
  
  $(date) end backup
EOF
fi
if [ -f  ${LOGPATH}/${BACKUP_NAME}.log ]
	then
	tail -n20  ${LOGPATH}/${BACKUP_NAME}.log
fi
execution
either change to the directory that is targetted for backup or pass the full path as a paramater
1st run creates the local 
.backup-options file with no backup
BACKUP_NAME=sso
BACKUP_ROOT=/home/docker/config/sso
EXCLUDE=(.git .venv)
DESTINATION=/dev/shm
2nd and subsequent executions read the backup parameters from the config file and tar/compress the backup
further info
Product Home
Link
Documentation
Link
Github
Link
DockerHub
Link
Misc
Link
more text more text

mkv2mp4 ffmpeg/ffprobe wrapper
/usr/local/bin/mkv2mp4.sh
Quick MKV to MP4 conversion with FFMPEG
Converts from MKV to MP4 to allow rapid streaming for media
#!/bin/bash
#  mkv2mp4.sh
function quit() {
	echo "[FAIL] - ${1}" 
	exit
}
function success() {
	echo "[ OK ] - ${1}"
}
[[ -z $1 ]] && quit "no source file specified"
! [[ -z $(which ffmpeg) ]] && success "ffmpeg binary located" || quit "unable to locate ffmpeg" 
! [[ -z $(which ffprobe) ]] && success "ffprobe binary located" || quit "unable to locate ffprobe"
! [[ -z $(which figlet) ]] && success "figlet binary located" || quit "unable to locate figlet"
INPUT="${1}"
FULL_SOURCE=$(realpath "${INPUT}")
FOLDER=$(dirname "${FULL_SOURCE}")
SOURCE_FILE=$(echo "${FULL_SOURCE}"| awk -F/ '{print $NF}')
SOURCE_EXT=$(echo "${SOURCE_FILE}"| awk -F. '{print $NF}')
TARGET_FILE=$(echo "${SOURCE_FILE}"| sed 's/.mkv/.mp4/')
cd "${FOLDER}"
figlet MKV2MP4 >> "${FOLDER}/mkv2mp4.log"
cat << EOF >> "${FOLDER}/mkv2mp4.log"
DATE	:	$(date)
SOURCE	:	${FULL_SOURCE}
TARGET	:	${FOLDER}/${TARGET_FILE}
EOF
#[[ -f "${SOURCE_FILE}" ]] && success "source file located" || quit "unable to access source file"
#touch /tmp/test/.mkv2mp4 && success "folder writeable" || quit "no permission to write to folder"
#[[ -f "${FOLDER}/${TARGET_FILE}" ]] && quit "target file ${TARGET_FILE} already exists" || success "target filename ${TARGET} is available"
#figlet SOURCE >> "${FOLDER}/mkv2mp4.log"
#echo $(date) >> "${FOLDER}/mkv2mp4.log"
#ffprobe "${FULL_SOURCE}" >> "${FOLDER}/mkv2mp4.log" || quit "ffprobe failed to scan file"
#figlet ENCODE >> "${FOLDER}/mkv2mp4.log"
#echo $(date) >> "${FOLDER}/mkv2mp4.log"
ffmpeg -i "${FULL_SOURCE}" -c copy "${TARGET_FILE}" && mv "${FULL_SOURCE}" /tmp/encodes/ 
#figlet OUTPUT >> "${FOLDER}/mkv2mp4.log"
#echo $(date) >> "${FOLDER}/mkv2mp4.log"
#ffprobe "${FOLDER}/${TARGET_FILE}" >> "${FOLDER}/mkv2mp4.log" || quit "ffprobe failed to scan file"
#[[ -f "${FOLDER}/${TARGET_FILE}" ]] && mv "${FULL_SOURCE}" /tmp/encodes/
sudo chmod -R 777 "${FOLDER}"
execution
mkv2mp4.sh </path/to/inputfile.mkv> 
reads the source file 
uses ffprobe to get metadata
fast re-code the mkv to mp4
replace bad charachters in file and foldernames
further info
Product Home
Link
Documentation
Link
Github
Link
DockerHub
Link
Misc
Link
more text more text

Docker Container Execute on Host Client/Server App
/usr/local/bin/pipe-exec
/usr/local/bin/pipe-response
Client Server App for Host execution from within docker
The app runs in the container and uses fifo pipes to pass commands to the server side of the app running on the docker host; the commands are executed and responses piped back to the container
Server Side Component
#!/bin/bash
# /usr/local/bin/pipe-exec
# server side of app
if ! [[ -p /var/run/exec ]]
then
  [[ -d /var/run/exec ]] && rm -rf /var/run/exec 
  mkfifo /var/run/exec || echo "error" && exit 99
fi
if ! [[ -p /var/run/response ]]
then
  [[ -d /var/run/response ]] && rm -rf /var/run/response
  mkfifo /var/run/response || echo "error" && exit 99
fi
while true; do eval "$(cat /var/run/exec)" > /var/run/response; done
pipe-exec runs on a docker host
listens to fifo pipe /var/run/exec 
creates pipe if doesnmt exist
executes the data from the pipe on the docker host
sends output of the command to fifo pipe /var/run/response
ideally run server component as a service
# systemd service file to run pipe-exec on startup
# /etc/systemd/system/pipe_exec.service
[Unit]
Description=Pipe Exec
[Service]
Type=simple
ExecStart=/usr/local/bin/pipe-exec
[Install]
WantedBy=multi-user.target
#!/bin/bash
# client side of app
# /usr/local/bin/pipe-response
echo "$1 $2 $3" > /var/run/exec
cat /var/run/response
Client Application for inclusion and execution within docker container
pipe-response should be on the docker contaoner filesystem or mapped in as a volume
/var/run/exec must be mapped as a volume
/var/run/response must be mapped in as a volume
# example docker-compose.yml for mapping folders and script
# within the container pass commands as
# pipe-response curl ifconfig.me
# returns host IP
---
services:
  your_container:
    container_name: your_container
    image: jamesread/your_container
    user: root
    volumes:
      - ./config/your_container:/config 
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/run/exec:/var/run/exec
      - /var/run/response:/var/run/response:ro
      - ./config/pipe-response:/usr/bin/pipe-response:ro
    restart: unless-stopped
    networks:
      - admin
    environment:
      - VIRTUAL_HOST=your_container.admin.pknw1.co.uk
      - VIRTUAL_PORT=1337
networks:
  admin:
    external: true
    name: admin
further info
Product Home
Link
Documentation
Link
Github
Link
DockerHub
Link
Misc
Link
more text more text

Movie Update TimeStamp to Theatrical Release Date
/usr/local/bin/releaseDate
Movie File Date/Time modifier
Reads the selected movie theatrical release date from previously stored metadata and sets the movie date and time to the release date so that it displays orderly in Plex
#!/bin/bash
# set releaseDate 
TS=$(cat "${1}" | jq '.releaseDate' | sed 's/"//g' | awk -FT '{print $1}' | awk -F- '{print $1$2$3}')0000.00
FOLDER=$(dirname "${1}")
if [ "${TS}" == "0000.00" ] || [ "${TS}" == "null0000.00" ]
then 
	echo "${FOLDER} invalid"
else
	echo "${TS} ${FOLDER}" || echo "${FOLDER} invalid"
	touch -a -m -t "${TS}" "${FOLDER}" || "${FOLDER} invalid"
	touch -a -m -t "${TS}" "${FOLDER}"/* || "${FOLDER} invalid"
fi
execution
ensure the movie has been scraped 
requires <movie-name>.nfo to exist
parses nfo for release date
updates the file timestamp
further info
Product Home
Link
Documentation
Link
Github
Link
DockerHub
Link
Misc
Link
more text more text

Simple Log File Reset/Wipe
/usr/local/bin/wipe
Script Headline
a simple wrapper to truncate any log file or open file by cat'ing /dev/null into the file
#!/bin/bash
# /usr/local/bin/wipe
cat /dev/null > $1
sdfsd
wipe /var/log/auth.log
service rsyslog restart
further info
Product Home
Link
Documentation
Link
Github
Link
DockerHub
Link
Misc
Link
more text more text

docker-compose create helper script (dialog)
/usr/local/bin/create-compose.sh
Script Headline
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. 
# Create a temporary file to store dialog output
tempfile=$(mktemp /tmp/dialog.XXXXXX)
BACKGROUND_TITLE="Docker Compose Configurator"
dir=$(dirname $(pwd) | awk -F/ '{print $NF}')
existing_networks=$(docker network ls |awk '{print $2" "$3}'|grep -v NAME)
existing_users=$(cat /etc/passwd|egrep -v 'nologin|false'| awk -F: '{print $1" "$3}')
existing_user_ids=$(cat /etc/passwd|egrep -v 'nologin|false'| awk -F: '{print ":"$3":"}')
tz=$(cat /etc/timezone)
# Function to get user input with dialog
exec 3>&1;
function status() {
    status_update="${1}"
    echo "${status_update}" >> /tmp/dialog-log
}
function wrapper() {
    feature="${1}"
    title="${2}"
    dimensions="${3}"
    data "${4}"
    
    response=$(dialog --backtitle "${BACKGROUND_TITLE} - ${title}" \
                      --${feature} "${title}" \
                      "${dimensions}" \
                      "${data}" \
                      2>&1 1>&3 )
    exit_code=$?
    
    case $exit_code in
      0) status "${2} - success exit code"
         ;;
      3) status "${2} - alternate button"
         ;;
      *) status "${2} - non-zero error code"
         ;;
    esac
    
    if [[ -z "${response}" ]]
    then
      echo "${response}"
    fi
}
## SERVICE_NAME
    service_name=$(dialog  --backtitle "${BACKGROUND_TITLE} - Service Name"  --inputbox 'Service Name' 0 0 ${dir} 2>&1 1>&3);
## DOCKER COMPOSE IMAGE and DOCKER IMAGE TEST PULL
    image_name=$(dialog --backtitle "${BACKGROUND_TITLE} - Docker Image Name" --inputbox 'Image Name' 0 0 2>&1 1>&3);
    rm /tmp/image_pull
    image_test=$(docker pull $image_name || echo error > /tmp/image_pull)
    if [[ $(cat /tmp/image_pull) == "error" ]]
    then
        image_name=$(dialog --backtitle "${BACKGROUND_TITLE} - Docker Error" --inputbox 'Docker Image not found - Try again' 0 0 2>&1 1>&3);
    fi
## TOP LEVEL DOMAIN
    top_level_domain=$(dialog --backtitle "${BACKGROUND_TITLE}" --title "Top Level Domain" --inputbox "domain" 8 50 "pknw1.co.uk"  2>&1 1>&3);
## DOCKER CONFIG ROOT FOLDER
    config_folder=$(dialog --backtitle "${BACKGROUND_TITLE}" --title "config folder" --inputbox "enter the docker config root path" 8 50 "/etc/docker/config" 2>&1 1>&3)
## DOCKER NETWORK SELECTION & CREATION
    docker_network=$(dialog --backtitle "${BACKGROUND_TITLE}" --menu "Choose Network: " 22 50 15 ${existing_networks} New Network 2>&1 1>&3)
    if [[ ${docker_network} == "New" ]]
    then
        docker_network=$(dialog --backtitle "${BACKGROUND_TITLE}" --inputbox 'New Network Name:' 0 0 2>&1 1>&3);
    fi
## USER SELECTION
    choose_user=$(dialog --backtitle "${BACKGROUND_TITLE}" --menu "Choose User: " 22 35 10 $existing_users 2>&1 1>&3)
    rm /tmp/id
    
## GROUP SELECTION
    for ID in $existing_user_ids; do cat /etc/group | grep $ID|awk -F: '{print $1" "$3}' >> /tmp/id; done
    existing_groups=$(cat /tmp/id| sort -u)
    select_group=$(dialog --backtitle "${BACKGROUND_TITLE}" --menu "Choose Group: " 22 35 10 $existing_groups 2>&1 1>&3)
 
## RESTART POLICY 
 restart=$(dialog --backtitle "${BACKGROUND_TITLE}" --menu "Choose Restart Option: " 22 35 10 no 'never restart' always 'always start' on-failure 'fail' unless-stopped 'unl'  2>&1 1>&3)
## NGINX-PROXY APP PORT
    app_port=$(dialog --inputbox 'App Port for Proxy' 0 0 "8080" 2>&1 1>&3);
### Volumes and Mounts
    active_volumes=$(docker ps --format "{{.Names}}" | xargs -n1 docker inspect -f '{{json .Mounts}}' | jq -r '.[] | "\(.Source):\(.Destination) \(.Source):\(.Destination) off"' |sort -u)     
    filtered_volumes=$(echo "${active_volumes}" | egrep -v 'sock|squid|yml|config' | sed 's/media off/media on/g'| sed 's/fuse off/fuse on/g')
    selected_volumes=$(dialog --backtitle "${BACKGROUND_TITLE} - Add predefined volumes" --extra-button --extra-label "Add Custom Mounts" --checklist "Select Volumes to Map"  20 100 20 $filtered_volumes 2>&1 1>&3);
    dialog_exit_code=$?
    case $dialog_exit_code in
      0) echo "Add Selected Volumes"
         echo ${selected_volumes}
         add_volume="false"
         ;;
      1) echo "Cancel"
         add_volume="false"
         ;;
      3) echo "Add Custom Mount" 
         add_volume="true"
         ;;
      *) echo "" 
         add_volume="false"
         ;;
    esac
    volumes=()
    for SEL in "${selected_volumes}"
    do
      volumes+=("${SEL}")
    done
    while [[ "${add_volume}" == "true" ]]
    do
        vol=$(dialog --backtitle "${BACKGROUND_TITLE} - Add Custom Mount" --separate-widget $'\n' --title "Add custom mount" --extra-button --extra-label "Add Another" --form ""  0 0 0 "Source:" 1 1 "$source" 1 10 30 0 "Mount:" 2 1 "$mount" 2 10 30 0 2>&1 1>&3 )
        dialog_exit_code=$?
        form=$(echo ${vol}| tr ' ' ':')
        case $dialog_exit_code in
          0) echo "Add Single Custom"
             volumes+=" ${form}"
             add_volume="false"
             ;;
          1) echo "Cancel"
             add_volume="false"
             ;;
          3) echo "Add More" 
             volumes+=" ${form}"
             add_volume="true"
             ;;
          *) echo "" 
             add_volume="false"
             ;;
        esac
done
## PRIVILEGED MODE
    if dialog --clear --backtitle "${BACKGROUND_TITLE}" --title "Priviliged Mode" --yesno "Enable Privilged Mode?" 0 0 ;then priv=true;else priv=false;fi
## DNS OVERRIDE
    dns=$(dialog --backtitle "${BACKGROUND_TITLE}" --inputbox 'Override Primary DNS' 0 0 "8.8.8.8" 2>&1 1>&3);
## RESOURCE LIMITING
    cpus=$(dialog --backtitle "${BACKGROUND_TITLE}" --inputbox 'Resource Allocate CPUs' 0 0 "1" 2>&1 1>&3);
    memory=$(dialog --backtitle "${BACKGROUND_TITLE}" --inputbox 'Resource Allocate Memory' 0 0 "200" 2>&1 1>&3);
## TimeZone 
    timezone=$(dialog --backtitle "${BACKGROUND_TITLE}" --inputbox 'Select Timezone' 0 0 $tz 2>&1 1>&3);
exec 3>&-;
rm -f "$tempfile"
# Display results to confirm input
dialog --backtitle "${BACKGROUND_TITLE} - Configuration Summary" --title "Confirmation" --msgbox "\
    Service Name: $service_name\n\
    Image Name: $image_name\n\
    Top Level Domain: $top_level_domain\n\
    Config Folder: $config_folder\n\
    Network: $docker_network\n\
    User: $choose_user\n\
    Group: $select_group\n\
    Privileged: $priv\n\
    Vhost: "${service_name}.pknw1.co.uk"\n\
    Port: $app_port\n\
    DNS: $dns\n\
    CPU: $cpus\n\
    Memory: $memory\n\
    TimeZone: $timezone\n\
    Volumes: $volumes\n\
    Timezone: $timezone" 20 70
clear
sdfsd
script info
check mark
further info
Product Home
Link
Documentation
Link
Github
Link
DockerHub
Link
Misc
Link
more text more text