368 lines
13 KiB
Bash
Executable File
368 lines
13 KiB
Bash
Executable File
#! /usr/bin/env bash
|
||
|
||
RPH_UID=${RPH_UID:-${UID}}
|
||
|
||
[ "${SUDO}" == "sudo" ] || SUDO=
|
||
|
||
# Identify source path (even if symlinked)
|
||
SOURCE=${BASH_SOURCE[0]}
|
||
while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||
DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
|
||
SOURCE=$(readlink "$SOURCE")
|
||
[[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||
done
|
||
SCRIPT_DIR=$( cd -- "$( dirname -- "${SOURCE}" )" &> /dev/null && pwd )
|
||
|
||
BASE_NAME="$(basename -- "$0")"
|
||
BASE_BASE_NAME="${BASE_NAME%.*}"
|
||
|
||
RUNTIPI_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||
RUNTIPI_CLI="${RUNTIPI_ROOT}/runtipi-cli"
|
||
set -e
|
||
|
||
cd "${RUNTIPI_ROOT}" > /dev/null
|
||
|
||
. ./user-config/.env.local
|
||
|
||
runtipi-cli() {
|
||
if ! [ -x "${RUNTIPI_CLI}" ]; then
|
||
>&2 printf '%s\n' \
|
||
"ERROR: ${RUNTIPI_CLI} not found or not executable!"
|
||
return 1
|
||
fi
|
||
"${RUNTIPI_CLI}" "${@}"
|
||
}
|
||
|
||
append_file_param() {
|
||
[ -e "${2:?File name missing}" ] || return
|
||
echo "${*}"
|
||
}
|
||
|
||
sort-tipi() {
|
||
# 111 is followed an NBSP literal
|
||
# the 111 lines will be added only if `runtipi` is found
|
||
# sort --unique makes sure only one such line exists
|
||
# This in effect creates a methodical spacing between runtipi
|
||
# and the rest of the list
|
||
sed -Ee 's|^|555|; s|^555runtipi|111 \n000runtipi|;' \
|
||
| sort --unique \
|
||
| sed -Ee 's/^[[:digit:]]{3}//'
|
||
}
|
||
dls() {
|
||
local base='{{.Status}}\t{{.ID}}\t{{.Names}}\t{{.Image}}' #'\t{{.Networks}}\t{{.Ports}}\t{{.Mounts}}'
|
||
local compose='{{.Label "com.docker.compose.project"}}\t{{.Label "com.docker.compose.service"}}'
|
||
local format="table $compose\t$base"
|
||
${SUDO} docker container ls --all --format "$format" | (sed -u '1s/.*/\U&/; q'; sort-tipi)
|
||
}
|
||
|
||
get-docker-list() {
|
||
local POST_PROCESS=" | sed -Ee '#PP1 #PP2 #PP3' #PP_SORT"
|
||
local PP1='s/@.+//g;'
|
||
local PP2='s/ ix-/\nix-/g; s/(^|\n)ix-/\1/g;'
|
||
local PP3=''
|
||
local PP_SORT='| sort-tipi'
|
||
local SRC="" SRC_DOCKER="${SUDO} docker ps -a --format '{{.Label \"com.docker.compose.project\"}}@{{.Names}}' | sed -Ee 's/^@/<_no-compose-project_>:/;'"
|
||
local FILTER=""
|
||
local LIST=''
|
||
local MK_SEARCH_LIST="| xargs -r | sed 's/@//g; s/^/^(/; s/ *$/)@/; s/ /|/g;'"
|
||
local ARG=${1:---help}
|
||
while [ -n "$ARG" ]; do
|
||
case "$ARG" in
|
||
--debug)
|
||
set -x
|
||
;;
|
||
-G)
|
||
POST_PROCESS="${MK_SEARCH_LIST}"
|
||
PP_SORT=''
|
||
;;
|
||
-a)
|
||
SRC="${SRC_DOCKER}"
|
||
if [ -z "$LIST" ]; then
|
||
ARG="+X"
|
||
continue
|
||
fi
|
||
;;
|
||
-c)
|
||
PP1=''
|
||
if [ -z "$SRC" ]; then
|
||
ARG="-r"
|
||
continue
|
||
fi
|
||
;;
|
||
-r)
|
||
SRC="${SRC_DOCKER/ps -a /ps }"
|
||
if [ -z "$LIST" ]; then
|
||
ARG="+X"
|
||
continue
|
||
fi
|
||
;;
|
||
+X)
|
||
PP2=''
|
||
;;
|
||
ix|truenas)
|
||
LIST="${SUDO} find /mnt/.ix-apps/app_configs/* -maxdepth 0 -printf 'ix-%f@ '"
|
||
;;
|
||
tp|runtipi)
|
||
LIST="${SUDO} find apps/* -maxdepth 0 -printf '%f@ ' ; echo runtipi"
|
||
;;
|
||
other)
|
||
FILTER="grep -vE \""$(get-docker-list ix -G)"\" | grep -vE \""$(get-docker-list tp -G)"\""
|
||
if [ -z "$LIST" ] && [ -z "$SRC" ]; then
|
||
ARG="-c"
|
||
continue
|
||
fi
|
||
;;
|
||
*)
|
||
printf '%s %-12s %s\n' \
|
||
"Usage:" "" ""\
|
||
" ${BASE_NAME} ls <options>" "" ""\
|
||
"" "" "" \
|
||
"Available options:" "" ""\
|
||
"" "-G" "Create grep -E matching logic" \
|
||
"" "-a" "All containers (including inactive ones)" \
|
||
"" "-r" "Runing containers only" \
|
||
"" "+X" "for IX apps - preserve the ix- prefix" \
|
||
"" "" "" \
|
||
"" "ix|truenas" "IX (TrueNAS SCALE) apps" \
|
||
"" "tp|runtipi" "Runtipi apps" \
|
||
"" "other" "All other apps" \
|
||
&& return 1
|
||
;;
|
||
esac
|
||
shift
|
||
ARG="${1}"
|
||
done
|
||
if [ 0 -eq "${#LIST}" ]; then
|
||
LIST="${SRC}"
|
||
SRC=''
|
||
fi
|
||
if [ 0 -eq "${#LIST}" ]; then
|
||
printf "Source missing, must specify one of the following: -a | -r | ix | tp | other\n" >&2
|
||
return 1
|
||
fi
|
||
POST_PROCESS="${POST_PROCESS/\#PP1/${PP1}}"
|
||
POST_PROCESS="${POST_PROCESS/\#PP2/${PP2}}"
|
||
POST_PROCESS="${POST_PROCESS/\#PP3/${PP3}}"
|
||
POST_PROCESS="${POST_PROCESS/\#PP_SORT/${PP_SORT}}"
|
||
|
||
[ -n "$SRC" ] \
|
||
&& CLI="$SRC | grep -E \"\$(($LIST) $MK_SEARCH_LIST)\"" \
|
||
|| CLI="($LIST)"
|
||
CLI="$CLI ${FILTER:+| $FILTER} ${POST_PROCESS}"
|
||
printf '%s\n' $(eval "$CLI")
|
||
}
|
||
|
||
list-docker-apps() {
|
||
local LIST="$(get-docker-list "$@")"
|
||
|
||
local RUNNING="$(get-docker-list "$@" -r | sort-tipi)"
|
||
local LIMBO="$(get-docker-list "$@" -a | grep -xF "$LIST" | grep -vxF "$RUNNING" | sort-tipi)"
|
||
local DOWN="$(grep -Fxv -f <(printf '%s\n' "$RUNNING" "$LIMBO") <<<"$LIST" | sort-tipi)"
|
||
|
||
printf 'RUNNING\n' && printf ' %s\n' ${RUNNING:-"<nothing>"}
|
||
[ -n "$LIMBO" ] && printf '\nLIMBO \n' && printf ' %s\n' $LIMBO || true
|
||
[ -n "$DOWN" ] && printf '\nDOWN \n' && printf ' %s\n' $DOWN || true
|
||
}
|
||
|
||
runtipi-app-docker-compose() {
|
||
if [ $# -eq 0 ]; then
|
||
list-docker-apps tp
|
||
return 1
|
||
fi
|
||
local APP="${1:-Must supply app name}"
|
||
|
||
${SUDO} docker compose \
|
||
$(append_file_param --env-file user-config/.env.local) \
|
||
$(append_file_param --env-file app-data/${APP}/app.env) \
|
||
$(append_file_param --env-file user-config/${APP}/app.env) \
|
||
--project-name ${APP} \
|
||
$(if [ "$APP" != "runtipi" ]; then
|
||
echo --file apps/${APP}/docker-compose.yml
|
||
append_file_param --file repos/29ca930bfdaffa1dfabf5726336380ede7066bc53297e3c0c868b27c97282903/apps/docker-compose.common.yml
|
||
append_file_param --file "user-config/${APP}/docker-compose.yml"
|
||
else
|
||
append_file_param --file "docker-compose.yml"
|
||
append_file_param --file "user-config/tipi-compose.yml"
|
||
fi) \
|
||
${2:-ps -a} ${3:+"${@:3}"}
|
||
}
|
||
|
||
ix-app-docker-compose() {
|
||
if [ $# -eq 0 ]; then
|
||
list-docker-apps ix
|
||
|
||
return 1
|
||
fi
|
||
local APP="${1:-Must supply app name}"
|
||
|
||
DCSRC="$(
|
||
${SUDO} find /mnt/.ix-apps/app_configs/ -type f \
|
||
-path "*/${APP}/*/rendered/docker-compose.yaml" -printf '%T@ %p\n' \
|
||
| sort -n | cut -d' ' -f2- | head -1
|
||
)"
|
||
set -x
|
||
${SUDO} docker compose "--file=${DCSRC}" "--project-name=ix-${APP}" ${2:-ps -a} ${3:+"${@:3}"}
|
||
}
|
||
|
||
link-exists() {
|
||
local EXIT_CODE=0
|
||
printf 'Current status:\n link: '
|
||
${SUDO} ip -br -c link show "${1:?Must supply device name}" 2>&1 || return 1
|
||
}
|
||
manage-ipvlan() {
|
||
|
||
local IPVLAN_IF="${2:-ipvlan-lan}"
|
||
local NET_IP="${NET_IP:-${INTERNAL_IP:?}}"
|
||
local IP_BASE="${NET_IP%\.[[:digit:]]*}"
|
||
local NET_IF="${NET_IF:-"$(ip -4 -br a s to "${NET_IP}" | cut -d' ' -f1)"}"
|
||
NET_IF="${NET_IF:-$($(ip -4 -br a s to "${IP_BASE}.0/24" | cut -d' ' -f1))}"
|
||
NET_IF="${NET_IF:?"Could not detect network interface for ${NET_IP}"}"
|
||
|
||
case "${1}" in
|
||
rm)
|
||
manage-ipvlan status "${IPVLAN_IF}" && ( \
|
||
${SUDO} ip link delete "${IPVLAN_IF}" \
|
||
&& printf '%s\n' "${IPVLAN_IF} removed"
|
||
) || return 1
|
||
;;
|
||
add)
|
||
link-exists "${IPVLAN_IF}" > /dev/null 2>&1 \
|
||
&& link-exists "${IPVLAN_IF}" || ( \
|
||
${SUDO} ip link add "${IPVLAN_IF}" link "${NET_IF}" type ipvlan mode l2 \
|
||
&& ${SUDO} ip addr add ${NET_IP} dev "${IPVLAN_IF}" \
|
||
&& ${SUDO} ip link set "${IPVLAN_IF}" up \
|
||
&& printf '%s\n' "${IPVLAN_IF} created"\
|
||
&& link-exists "${IPVLAN_IF}" \
|
||
|| ${SUDO} ip link delete "${IPVLAN_IF}"
|
||
)
|
||
;;
|
||
route)
|
||
local ROUTE_SCOPE="${3:?Must supply routing scope in the form of ###.###.###.###[/##]}"
|
||
|
||
manage-ipvlan add "${IPVLAN_IF}" > /dev/null 2>&1 \
|
||
&& for IP in "${@:3}"; do
|
||
# Only add route if it does not already exists
|
||
printf 'Adding route for %-15s ' "$IP"
|
||
${SUDO} ip route get "$IP" 2> /dev/null | grep -q "dev ${IPVLAN_IF} src ${NET_IP//\./\\.}" \
|
||
&& printf 'Skipping, already exists.' \
|
||
|| ${SUDO} ip route add "$IP" dev "${IPVLAN_IF}" \
|
||
&& printf '\n' \
|
||
|| (printf 'Could NOT add %s\n' "$IP" >&2; return 1)
|
||
done
|
||
|
||
link-exists "${IPVLAN_IF}"
|
||
;;
|
||
status)
|
||
link-exists "${IPVLAN_IF}" || return 1
|
||
;;
|
||
*)
|
||
printf '%s %-12s %s\n' \
|
||
"$(manage-ipvlan status)" "" ""\
|
||
"" "" ""\
|
||
"Usage:" "" ""\
|
||
" [SUDO=sudo] ${BASE_NAME} ipvlan <command>" "" ""\
|
||
"" "" "" \
|
||
"Available commands:" "" ""\
|
||
"" "" "" \
|
||
"" "fix" "implement ipvlan fix" \
|
||
"" "rm" "remove ipvlan fix" \
|
||
"" "status" "output interface status" \
|
||
return 0
|
||
;;
|
||
esac
|
||
|
||
( printf 'addr: ' && ip -4 -br -c addr show "${IPVLAN_IF}" \
|
||
&& printf 'Routes:\n' \
|
||
&& ${SUDO} ip -c route show dev "${IPVLAN_IF}" \
|
||
| awk '{print} END{if (NR==0) print "<none found>"}'
|
||
) 2>&1 | sed -e '/[^:]$/s/^/ /' \
|
||
|| return 1
|
||
# ip link show [ DEVICE | group GROUP ] [up] [master DEV] [vrf NAME] [type TYPE] [nomaster]
|
||
}
|
||
|
||
case "${1}" in
|
||
cli)
|
||
runtipi-cli "${@:2}"
|
||
;;
|
||
log|logs)
|
||
POSTGRES_PASSWORD=_ TIPI_VERSION=_ LOCAL_DOMAIN=_ DOMAIN=_ runtipi-app-docker-compose "${2:-runtipi}" logs ${3:+"${@:3}"}
|
||
;;
|
||
start)
|
||
ROOT_FOLDER_HOST="${RUNTIPI_ROOT}" RUNTIPI_APP_DATA_PATH="${RUNTIPI_ROOT}" \
|
||
runtipi-cli start --env-file user-config/.env.local ${2:---no-permissions}
|
||
;;
|
||
update)
|
||
if [ -z "${2}" ]; then
|
||
${SCRIPT_DIR}/checkver.sh
|
||
else
|
||
runtipi-cli update --env-file user-config/.env.local --no-permissions "${2:?Must supply version}" "${@:3}"
|
||
fi
|
||
;;
|
||
dls)
|
||
dls "${@:2}"
|
||
;;
|
||
ls)
|
||
get-docker-list "${@:2}"
|
||
;;
|
||
tpcompose)
|
||
runtipi-app-docker-compose "${@:2}"
|
||
;;
|
||
ixcompose)
|
||
ix-app-docker-compose "${@:2}"
|
||
;;
|
||
shell)
|
||
runtipi-app-docker-compose "${2:?}" exec ${5:+"${@:5}"} -it "${4:-${2}}" "${3:-bash}"
|
||
;;
|
||
ipvlan)
|
||
manage-ipvlan "${@:2}"
|
||
;;
|
||
setup)
|
||
ln -s $2 "$(cd -- "${SCRIPT_DIR}" && pwd)/${BASE_NAME}" "${3:-$HOME/.local/bin/}"
|
||
;;
|
||
edit)
|
||
${VISUAL:-${EDITOR:-vi}} $0
|
||
;;
|
||
_load)
|
||
echo "alias ${BASE_NAME}cd='cd \"$SCRIPT_DIR/..\"'"
|
||
;;
|
||
*)
|
||
printf '%s %-12s %s\n' \
|
||
"" "" ""\
|
||
"Usage:" "" ""\
|
||
" [SUDO=sudo] ${BASE_NAME} <command> [args...]" "" ""\
|
||
"" "" "" \
|
||
"Available commands:" "" ""\
|
||
"" "" "" \
|
||
"runtipi" "" ""\
|
||
"" "cli" "runtipi-cli" \
|
||
"" "log" "runtipi docker stack logs" \
|
||
"" "start" "start runtipi" \
|
||
"" "update" "update runtipi to a specific version" \
|
||
"" "" "" \
|
||
"docker/docker-compose" "" ""\
|
||
"" "tpcompose" "docker compose for runtipi apps" \
|
||
"" "ixcompose" "docker compose for ix/TrueNAS SCALE docker based app" \
|
||
"" "ls" "list applications (or contrainers)" \
|
||
"" "dls" "stylized docker ls" \
|
||
"" "shell" "enter an insteractive shell" \
|
||
"" "down-all" "stop and remove everything" \
|
||
"" "" "" \
|
||
"networking" "" ""\
|
||
"" "ipvlan" "manage ipvlan networking interface fix" \
|
||
"" "" "" \
|
||
"misc." "" ""\
|
||
"" "exec" "execute within the shell, START_DIR env applies" \
|
||
"" "" "" \
|
||
"" "setup" "setup runtipictl in user's .local/bin dir" \
|
||
"" "" "${BASE_NAME} setup" \
|
||
"" "" "${BASE_NAME} setup '' ~/.local/bin/${BASE_BASE_NAME}" \
|
||
"" "" "${BASE_NAME} setup '' ~/.local/bin/runtipictl" \
|
||
"" "" "" \
|
||
"Related env. vars:" "" "" \
|
||
"" "VISUAL EDITOR RPH_UID QUIET START_DIR ROOT_EXEC" ""
|
||
|
||
;;
|
||
esac
|
||
|
||
# vim: set ft=sh expandtab tabstop=4 shiftwidth=4:
|