mirror of
				https://github.com/flynx/proxmox-utils.git
				synced 2025-11-03 21:50:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			949 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			949 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
#!/usr/bin/bash
 | 
						|
#----------------------------------------------------------------------
 | 
						|
#
 | 
						|
#
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
CT_DIR=${CT_DIR:=/etc/pve/lxc/}
 | 
						|
 | 
						|
# XXX setup path...
 | 
						|
# XXX
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# XXX this is quite generic, might be a good idea to move this to a 
 | 
						|
# 		seporate lib/file...
 | 
						|
 | 
						|
# Execute (optionally) and print a command.
 | 
						|
#
 | 
						|
#	@ COMMAND ARGS
 | 
						|
#
 | 
						|
#QUIET=
 | 
						|
#DRY_RUN=
 | 
						|
ECHO_PREFIX="### "
 | 
						|
function @ (){
 | 
						|
	if [ -z $DRY_RUN ] ; then
 | 
						|
		! [ $QUIET ] \
 | 
						|
			&& echo -e "${ECHO_PREFIX}$@"
 | 
						|
		"$@"
 | 
						|
	else
 | 
						|
		echo -e $@
 | 
						|
	fi
 | 
						|
	return $?
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
#
 | 
						|
#	check MSG COMMAND ..
 | 
						|
#
 | 
						|
check(){
 | 
						|
	local MSG=$1
 | 
						|
	shift
 | 
						|
	for cmd in "$@" ; do
 | 
						|
		which $cmd > /dev/null 2>&1 \
 | 
						|
			|| eval "echo \"$MSG\"" >&2
 | 
						|
	done
 | 
						|
}
 | 
						|
 | 
						|
need(){
 | 
						|
	check 'ERROR: "$cmd": needed by this script but not in path.' "$@"
 | 
						|
}
 | 
						|
would-like(){
 | 
						|
	check 'WARNING: "$cmd": is not in path.' "$@"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# Fill section...
 | 
						|
#
 | 
						|
# XXX this is quite generic -- move to a more logical place...
 | 
						|
 | 
						|
fillsection(){ (
 | 
						|
	usage(){
 | 
						|
		echo "Usage:"
 | 
						|
		echo "    ${FUNCNAME[0]} [-h]"
 | 
						|
		echo "    ${FUNCNAME[0]} [-r] NAME FILE [CONTENT]"
 | 
						|
		echo
 | 
						|
	}
 | 
						|
	while true ; do
 | 
						|
		case $1 in
 | 
						|
			-h|--help)
 | 
						|
				usage
 | 
						|
				echo "Options:"
 | 
						|
				#	  .	  .					.
 | 
						|
				echo "    -h | --help       print this help message and exit."
 | 
						|
				echo "    -r | --return     replace section markers with CONTENT."
 | 
						|
				echo
 | 
						|
				return 0
 | 
						|
				;;
 | 
						|
			-r|--replace)
 | 
						|
				local replace=1
 | 
						|
				shift
 | 
						|
				;;
 | 
						|
 | 
						|
			*)
 | 
						|
				break
 | 
						|
				;;
 | 
						|
		esac
 | 
						|
	done
 | 
						|
	if [[ $# < 2 ]] ; then
 | 
						|
		usage
 | 
						|
		return 1
 | 
						|
	fi
 | 
						|
 | 
						|
	name=$1
 | 
						|
	file=$2
 | 
						|
	content=$3
 | 
						|
	content=${content:=/dev/stdin}
 | 
						|
 | 
						|
	# print file upto section marker...
 | 
						|
	if [ $replace ] ; then
 | 
						|
		sed "/${name^^} BEGIN/q" "$file" | sed '$d'
 | 
						|
	else
 | 
						|
		sed "/${name^^} BEGIN/q" "$file"
 | 
						|
	fi
 | 
						|
	# print content...
 | 
						|
	cat $content
 | 
						|
	# print file from section end marker...
 | 
						|
	if [ $replace ] ; then
 | 
						|
		sed -ne "/${name^^} END/,$ p" "$file" | sed '1d' 
 | 
						|
	else
 | 
						|
		sed -ne "/${name^^} END/,$ p" "$file"
 | 
						|
	fi
 | 
						|
) }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# CT hostname <-> CT id...
 | 
						|
 | 
						|
ct2hostname(){
 | 
						|
	local ct=${CT_DIR}/${1}.conf
 | 
						|
	local host=$(cat $ct \
 | 
						|
		| grep -e '^\s*hostname:' \
 | 
						|
		| head -1)
 | 
						|
	echo ${host/hostname: /}
 | 
						|
}
 | 
						|
 | 
						|
hostname2ct(){
 | 
						|
	if [ -e "${CT_DIR}/${1}.conf" ] ; then
 | 
						|
		echo $1
 | 
						|
	fi
 | 
						|
	local running=$2
 | 
						|
	running=${running:=any}
 | 
						|
	local ct
 | 
						|
	local host
 | 
						|
	for ct in "${CT_DIR}"/*.conf ; do
 | 
						|
		host=$(cat $ct | grep hostname | head -1)
 | 
						|
		host=${host/hostname: /}
 | 
						|
		if [ "$host" = $1 ] ; then
 | 
						|
			ct=${ct#${CT_DIR}}
 | 
						|
			ct=${ct%.conf}
 | 
						|
			ct=${ct#\/}
 | 
						|
			# filter results if needed...
 | 
						|
			if [ $running = "any" ] ; then
 | 
						|
				echo $ct
 | 
						|
			else
 | 
						|
				local status=`pct status $ct`
 | 
						|
				if [ "$running" = "${status/status: /}" ] ; then
 | 
						|
					echo $ct
 | 
						|
				fi
 | 
						|
			fi
 | 
						|
		fi
 | 
						|
	done
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
normpath(){
 | 
						|
	echo $1 \
 | 
						|
		| sed \
 | 
						|
			-e 's/\/\+/\//g' \
 | 
						|
			-e 's/\/.\//\//g' \
 | 
						|
			-e 's/[^\/]\+\/\.\.//g' \
 | 
						|
			-e 's/\/\+/\//g' \
 | 
						|
			-e 's/\/\.$/\//g'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
#
 | 
						|
#	xread [-n] MSG VAR
 | 
						|
#
 | 
						|
# This saves all user input variables to the $XREAD_VARS array.
 | 
						|
xread(){
 | 
						|
	local non_empty=
 | 
						|
	if [[ $1 == '-n' ]] ; then
 | 
						|
		shift
 | 
						|
		local non_empty=1
 | 
						|
	fi
 | 
						|
	local prefix=
 | 
						|
	if [ $SCRIPTING ] ; then
 | 
						|
		prefix='# '
 | 
						|
	fi
 | 
						|
	# skip...
 | 
						|
	if [[ "${!2}" == "SKIP" ]] \
 | 
						|
			|| [[ "$(eval "echo \$DFL_$2")" == "SKIP" ]] ; then
 | 
						|
		eval "$2="
 | 
						|
		return
 | 
						|
	fi
 | 
						|
	if [ -z ${!2} ] ; then
 | 
						|
		eval 'read -ep "'$prefix''$1'" -i "$DFL_'$2'" '${2}''
 | 
						|
		XREAD_VARS+=(${2})
 | 
						|
	fi
 | 
						|
	if [ -z $non_empty ] ; then
 | 
						|
		eval ''$2'=${'$2':=$DFL_'$2'}'
 | 
						|
	fi
 | 
						|
	[ $SCRIPTING ] \
 | 
						|
		&& echo "$2=${!2}"
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	xreadYes MSG [VAR]
 | 
						|
#
 | 
						|
xreadYes(){
 | 
						|
	if [ -z ${2} ] ; then
 | 
						|
		local var=__LOCAL
 | 
						|
		local __LOCAL
 | 
						|
	else
 | 
						|
		local var=${2}
 | 
						|
		local mode=
 | 
						|
	fi
 | 
						|
	local prefix=
 | 
						|
	if [ $SCRIPTING ] ; then
 | 
						|
		prefix='# '
 | 
						|
	fi
 | 
						|
	# XXX check DFL_..???
 | 
						|
	if [[ "${!var}" == "SKIP" ]] ; then
 | 
						|
		eval "$var="
 | 
						|
		return
 | 
						|
	fi
 | 
						|
	if [ -z ${!var} ] ; then
 | 
						|
		if [ -z $(eval "echo \$DFL_${var}") ] ; then
 | 
						|
			local yes=y
 | 
						|
			local no=N
 | 
						|
			local dfl=
 | 
						|
		else
 | 
						|
			local yes=Y
 | 
						|
			local no=n
 | 
						|
			local dfl=1
 | 
						|
		fi
 | 
						|
		eval 'read -ep "'$prefix''$1' ('$yes'/'$no') " '${var}''
 | 
						|
		XREAD_VARS+=(${var})
 | 
						|
		# normalize...
 | 
						|
		eval "${var}=${!var,,}"
 | 
						|
		if [[ "${!var}" == 'y' ]] ; then
 | 
						|
			eval "${var}=1"
 | 
						|
		elif [[ ${!var} == 'n' ]] ; then
 | 
						|
			eval "${var}="
 | 
						|
		# set default if empty...
 | 
						|
		else
 | 
						|
			eval "${var}=\${${var}:-$dfl}"
 | 
						|
		fi
 | 
						|
	fi
 | 
						|
	[ $SCRIPTING ] \
 | 
						|
		&& [[ "$var" != '__LOCAL' ]] \
 | 
						|
		&& echo "$var=${!var}"
 | 
						|
 | 
						|
	if [ -z ${!var} ] ; then
 | 
						|
		return 1
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	xreadpass VAR
 | 
						|
#
 | 
						|
xreadpass(){
 | 
						|
	if [[ ${!1} == 'SKIP' ]] ; then
 | 
						|
		return
 | 
						|
	fi
 | 
						|
	local prefix=
 | 
						|
	if [ $SCRIPTING ] ; then
 | 
						|
		prefix='# '
 | 
						|
	fi
 | 
						|
	local PASS1
 | 
						|
	local PASS2
 | 
						|
	for attempt in 1 2 3 ; do
 | 
						|
		read -sep "${prefix}password (Enter to skip): " PASS1
 | 
						|
		echo
 | 
						|
		if [ -z $PASS1 ] ; then
 | 
						|
			return
 | 
						|
		fi
 | 
						|
		read -sep "${prefix}retype password: " PASS2
 | 
						|
		echo
 | 
						|
		if [[ $PASS1 != $PASS2 ]] ; then
 | 
						|
			echo "ERROR: passwords do not match." >&2
 | 
						|
			continue
 | 
						|
		fi
 | 
						|
		eval ''$1'='${PASS1}''
 | 
						|
		return
 | 
						|
	done
 | 
						|
	return 1
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# Review changes in PATH.new, then edit/apply changes to PATH
 | 
						|
#
 | 
						|
#	reviewApplyChanges PATH
 | 
						|
#
 | 
						|
# NOTE: if changes are not applied this will return non-zero making this 
 | 
						|
#		usable in conditionals...
 | 
						|
reviewApplyChanges(){
 | 
						|
	local file=$1
 | 
						|
	echo "# Review updated: ${file}.new:"
 | 
						|
	@ cat ${file}.new
 | 
						|
	echo '---'
 | 
						|
	local res
 | 
						|
	while true ; do
 | 
						|
		read -ep "# [a]pply, [e]dit, [s]kip? " res
 | 
						|
		case "${res,,}" in
 | 
						|
			a|apply)
 | 
						|
				break
 | 
						|
				;;
 | 
						|
			e|edit)
 | 
						|
				${EDITOR} "${file}"
 | 
						|
				;;
 | 
						|
			s|skip)
 | 
						|
				echo "# file saved as: ${file}.new"
 | 
						|
				return 1
 | 
						|
				;;
 | 
						|
			*)
 | 
						|
				echo "ERROR: unknown command: \"$res\"" >&2
 | 
						|
				continue
 | 
						|
				;;
 | 
						|
		esac
 | 
						|
	done
 | 
						|
	@ mv -b "${file}"{.new,}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
# 
 | 
						|
# 	readConfig
 | 
						|
#
 | 
						|
# Envioronment variables:
 | 
						|
#	CLEAN_RUN	- if set ignore ./config.last-run
 | 
						|
#	CONFIG		- config file to load last
 | 
						|
#
 | 
						|
# XXX need this not to make this behave with explicitly set vars...
 | 
						|
readConfig(){
 | 
						|
	if [ -z $NO_DEFAULTS ] ; then
 | 
						|
		local IFS=$'\n'
 | 
						|
		#__ENV=($( (set -o posix ; set | grep -v 'BASHOPTS=') ))
 | 
						|
		#__ENV=($( (declare -xp) ))
 | 
						|
		[ -e ../config.global ] \
 | 
						|
			&& source ../config.global
 | 
						|
		[ -e ./config ] \
 | 
						|
			&& source ./config
 | 
						|
		# XXX is this the right priority for this???
 | 
						|
		[ -e ./config.last-run ] \
 | 
						|
			&& [ -z $CLEAN_RUN ] \
 | 
						|
			&& source ./config.last-run
 | 
						|
		[ -e "$CONFIG" ] \
 | 
						|
			&& source $CONFIG
 | 
						|
		#eval "${__ENV[@]}"
 | 
						|
		#__ENV=
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	saveConfig [-d|-a] CONFIG VAR ..
 | 
						|
#
 | 
						|
saveConfig(){
 | 
						|
	local prefix=
 | 
						|
	local append=
 | 
						|
	while true ; do
 | 
						|
		case $1 in
 | 
						|
			-d|--default)
 | 
						|
				prefix=DFL_
 | 
						|
				shift
 | 
						|
				;;
 | 
						|
			-a|--append)
 | 
						|
				append=1
 | 
						|
				shift
 | 
						|
				;;
 | 
						|
			*)
 | 
						|
				break
 | 
						|
				;;
 | 
						|
		esac
 | 
						|
	done
 | 
						|
	local cfg=$1
 | 
						|
	shift
 | 
						|
 | 
						|
	if [ -z $append ] ; then
 | 
						|
		printf '' > "$cfg"
 | 
						|
	fi
 | 
						|
	{
 | 
						|
		for var in $@ ; do
 | 
						|
			echo "${prefix}${var}=${!var}"
 | 
						|
		done
 | 
						|
		echo
 | 
						|
	} >> "$cfg"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
saveLastRunConfig(){
 | 
						|
	local cfg=config.last-run
 | 
						|
	echo "# Saving config to: config.last-run"
 | 
						|
	{
 | 
						|
		echo "#"
 | 
						|
		echo "# This file is auto-generated, any changes here will be overwritten." 
 | 
						|
		echo "#"
 | 
						|
	} > "$cfg"
 | 
						|
	saveConfig -d -a "$cfg" ${XREAD_VARS[@]}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	webAppConfig NAME
 | 
						|
#
 | 
						|
webAppConfig(){
 | 
						|
	local name=${1^^}
 | 
						|
	eval "${name}_SUBDOMAIN=\${${name}_SUBDOMAIN:=\${DFL_SUB${name}_DOMAIN}}
 | 
						|
		${name}_SUBDOMAIN=\${${name}_SUBDOMAIN:+\${${name}_SUBDOMAIN%.}.}
 | 
						|
		${name}_DOMAIN=\${${name}_DOMAIN:=\${DFL_${name}_DOMAIN}}
 | 
						|
		# prioretize \${name}_*
 | 
						|
		DFL_DOMAIN=\${DFL_DOMAIN:+\${${name}_SUBDOMAIN}\${DFL_DOMAIN}}
 | 
						|
		DFL_DOMAIN=\${DOMAIN:+\${${name}_SUBDOMAIN}\${DOMAIN}}
 | 
						|
		if [ \$${name}_DOMAIN ] ; then
 | 
						|
			DFL_DOMAIN=\${${name}_SUBDOMAIN}\${${name}_DOMAIN}
 | 
						|
		fi"
 | 
						|
	# force check of domain...
 | 
						|
	DOMAIN=
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	readVars
 | 
						|
#	readCTHardwareVars
 | 
						|
#	readBridgeVars
 | 
						|
#
 | 
						|
# Variables this handles:
 | 
						|
#	EMAIL
 | 
						|
#	DOMAIN
 | 
						|
#	ID
 | 
						|
#	CTHOSTNAME
 | 
						|
#	WAN_BRIDGE
 | 
						|
#	LAN_BRIDGE
 | 
						|
#	ADMIN_BRIDGE
 | 
						|
#	WAN_IP
 | 
						|
#	WAN_GATE
 | 
						|
#	LAN_IP
 | 
						|
#	LAN_GATE
 | 
						|
#	ADMIN_IP
 | 
						|
#	ADMIN_GATE
 | 
						|
#	ROOTPASS
 | 
						|
#	PCT_EXTRA
 | 
						|
#
 | 
						|
# Variables this sets:
 | 
						|
#	PASS
 | 
						|
#
 | 
						|
# Variables used:
 | 
						|
# 	TMP_PASS_LEN
 | 
						|
# 	ROOTPASS
 | 
						|
#
 | 
						|
readCTVars(){
 | 
						|
	xread "ID: " ID
 | 
						|
	xread "Hostname: " CTHOSTNAME
 | 
						|
 | 
						|
	# hardware...
 | 
						|
	xread "CPU cores: " CORES
 | 
						|
	xread "RAM (MB): " RAM
 | 
						|
	xread "SWAP (MB): " SWAP
 | 
						|
	xread "DRIVE (GB): " DRIVE
 | 
						|
}
 | 
						|
readBridgeVars(){
 | 
						|
	# bridge config...
 | 
						|
	xread "WAN bridge: vmbr" WAN_BRIDGE
 | 
						|
	xread "LAN bridge: vmbr" LAN_BRIDGE
 | 
						|
	xread "ADMIN bridge: vmbr" ADMIN_BRIDGE
 | 
						|
}
 | 
						|
readVars(){
 | 
						|
	xread -n "Email: " EMAIL
 | 
						|
	xread -n "Domain: " DOMAIN
 | 
						|
 | 
						|
	xread -n "Gate ID: " GATE_ID
 | 
						|
 | 
						|
	readCTVars
 | 
						|
 | 
						|
	readBridgeVars
 | 
						|
 | 
						|
	# gateway...
 | 
						|
	# IPs can be:
 | 
						|
	#	<empty>
 | 
						|
	#	<IP>/<mask>
 | 
						|
	#	dhcp
 | 
						|
	# Gateways can be:
 | 
						|
	#	<empty>
 | 
						|
	#	<IP>
 | 
						|
	# XXX these are the same...
 | 
						|
	xread "WAN ip: " WAN_IP
 | 
						|
	if [[ $WAN_IP != "dhcp" ]] ; then
 | 
						|
		xread "WAN gateway: " WAN_GATE
 | 
						|
	else
 | 
						|
		WAN_GATE=
 | 
						|
	fi
 | 
						|
	xread "LAN ip: " LAN_IP
 | 
						|
	if [[ $LAN_IP != "dhcp" ]] ; then
 | 
						|
		xread "LAN gateway: " LAN_GATE
 | 
						|
	else
 | 
						|
		LAN_GATE=
 | 
						|
	fi
 | 
						|
	xread "ADMIN ip: " ADMIN_IP
 | 
						|
	if [[ $ADMIN_IP != "dhcp" ]] ; then
 | 
						|
		xread "ADMIN gateway: " ADMIN_GATE
 | 
						|
	else
 | 
						|
		ADMIN_GATE=
 | 
						|
	fi
 | 
						|
 | 
						|
	# root password...
 | 
						|
	if [ -z $ROOTPASS ] ; then
 | 
						|
		xreadpass PASS \
 | 
						|
			|| exit 1
 | 
						|
	else
 | 
						|
		PASS=$ROOTPASS
 | 
						|
	fi
 | 
						|
 | 
						|
	# extra stuff...
 | 
						|
	xread "pct extra options: " PCT_EXTRA
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	makeTemplateSEDPatterns VAR ...
 | 
						|
#
 | 
						|
makeTemplateSEDPatterns(){
 | 
						|
	local var
 | 
						|
	for var in "$@" ; do
 | 
						|
		local val=${!var}
 | 
						|
		if [[ $val == SKIP ]] ; then
 | 
						|
			val=
 | 
						|
		fi
 | 
						|
		echo "-e 's/\\\${${var}}/${val//\//\\/}/g'"
 | 
						|
	done
 | 
						|
}
 | 
						|
 | 
						|
# same as makeTemplateSEDPatterns but adds default vars + generates *_IPn vars...
 | 
						|
PCT_TEMPLATE_VARS=(
 | 
						|
	EMAIL
 | 
						|
	DOMAIN
 | 
						|
	CTHOSTNAME
 | 
						|
	HOST_ADMIN_IP
 | 
						|
	GATE_HOSTNAME
 | 
						|
	GATE_LAN_IP
 | 
						|
	GATE_ADMIN_IP
 | 
						|
	NS_HOSTNAME
 | 
						|
	NS_LAN_IP
 | 
						|
	NS_ADMIN_IP
 | 
						|
	WAN_IP
 | 
						|
	WAN_GATE
 | 
						|
	LAN_IP
 | 
						|
	LAN_GATE
 | 
						|
	ADMIN_IP
 | 
						|
	ADMIN_GATE
 | 
						|
)
 | 
						|
makePCTTemplateSEDPatterns(){
 | 
						|
	local vars=("${PCT_TEMPLATE_VARS[@]}" "$@")
 | 
						|
	# strip ips and save to *_IPn var...
 | 
						|
	local ip_vars=()
 | 
						|
	local var
 | 
						|
	local val
 | 
						|
	for var in ${vars[@]} ; do
 | 
						|
		if [[ $var =~ .*_IP ]] ; then
 | 
						|
			local val=${!var}
 | 
						|
			if [[ $val == SKIP ]] ; then
 | 
						|
				val=
 | 
						|
			fi
 | 
						|
			ip_vars+=("${var}n")
 | 
						|
			eval "local ${var}n=\"${val/\/*}\""
 | 
						|
		fi
 | 
						|
	done
 | 
						|
 | 
						|
	makeTemplateSEDPatterns "${vars[@]}" "${ip_vars[@]}"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	expandTemplate PATH VAR ...
 | 
						|
#	.. | expandTemplate VAR ...
 | 
						|
#
 | 
						|
PCT_TEMPLATE_PATTERNS=
 | 
						|
expandTemplate(){
 | 
						|
	if [ -t 0 ] ; then
 | 
						|
		local input=$1
 | 
						|
		shift
 | 
						|
	else
 | 
						|
		local input=/dev/stdin
 | 
						|
	fi
 | 
						|
 | 
						|
	if [ -z "$PCT_TEMPLATE_PATTERNS" ] ; then
 | 
						|
		local patterns=($(makeTemplateSEDPatterns "$@"))
 | 
						|
	else
 | 
						|
		local patterns=("${PCT_TEMPLATE_PATTERNS[@]}")
 | 
						|
	fi
 | 
						|
 | 
						|
	cat "${input}" \
 | 
						|
		| eval "sed ${patterns[@]}" 
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	expandPCTTemplate PATH [VAR ...]
 | 
						|
#	.. | expandPCTTemplate [VAR ...]
 | 
						|
#
 | 
						|
expandPCTTemplate(){
 | 
						|
	local input=
 | 
						|
	if [ -t 0 ] ; then
 | 
						|
		input=$1
 | 
						|
		shift
 | 
						|
	fi
 | 
						|
 | 
						|
	local PCT_TEMPLATE_PATTERNS=($(makePCTTemplateSEDPatterns "$@"))
 | 
						|
 | 
						|
	expandTemplate "${input}"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	buildAssets [VAR ..]
 | 
						|
#
 | 
						|
# XXX add vars in filenames (???)
 | 
						|
NOTES=NOTES.md
 | 
						|
buildAssets(){
 | 
						|
	local template_dir=${TEMPLATE_DIR:-templates}
 | 
						|
	local assets_dir=${ASSETS_DIR:-assets}
 | 
						|
	local staging_dir=${STAGING_DIR:-staging}
 | 
						|
 | 
						|
	local PCT_TEMPLATE_PATTERNS=($(makePCTTemplateSEDPatterns "$@"))
 | 
						|
 | 
						|
	# assets...
 | 
						|
	if [ -e "${assets_dir}" ] ; then
 | 
						|
		mkdir -p "${staging_dir}"
 | 
						|
		cp -R "${assets_dir}"/* "${staging_dir}"/
 | 
						|
	fi
 | 
						|
 | 
						|
	# template dir...
 | 
						|
	if [ -e $template_dir ] ; then
 | 
						|
		local TEMPLATES=($(find "$template_dir" -type f))
 | 
						|
		for file in "${TEMPLATES[@]}" ; do
 | 
						|
			file=${file#${template_dir}}
 | 
						|
			echo Generating: ${file}...
 | 
						|
			[ $DRY_RUN ] \
 | 
						|
				&& continue
 | 
						|
			# ensure the directory exists...
 | 
						|
			mkdir -p "$(dirname "${staging_dir}/${file}")"
 | 
						|
			cat "${template_dir}/${file}" \
 | 
						|
				| expandTemplate \
 | 
						|
				> "${staging_dir}/${file}"
 | 
						|
		done
 | 
						|
	fi
 | 
						|
 | 
						|
	# special case: NOTES.md...
 | 
						|
	if [ -z "$DESCRIPTION" ] && [ -e "$NOTES" ] ; then
 | 
						|
		DESCRIPTION="$(\
 | 
						|
			cat ${NOTES} \
 | 
						|
				| expandTemplate)"
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
#
 | 
						|
#	pctPushAssets ID
 | 
						|
#
 | 
						|
pctPushAssets(){
 | 
						|
	@ pct-push-r $1 "${STAGING_DIR:-./staging}" /
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	traefikPushConfig
 | 
						|
#
 | 
						|
# XXX generate config in a staging location...
 | 
						|
TRAEFIK_CONFIG=traefik.yml
 | 
						|
TRAEFIK_PATH=/etc/traefik.d/
 | 
						|
TRAEFIK_STAGING=traefik/
 | 
						|
traefikPushConfig(){
 | 
						|
	local filename="${CTHOSTNAME}.yml"
 | 
						|
	local source="${TRAEFIK_STAGING}/${filename}"
 | 
						|
	local target="${TRAEFIK_PATH}"/"${filename}"
 | 
						|
 | 
						|
	# source file not found...
 | 
						|
	if ! [ -e "${TRAEFIK_CONFIG}" ] ; then
 | 
						|
		echo "${TRAEFIK_CONFIG}: not found." >&2
 | 
						|
		return
 | 
						|
	fi
 | 
						|
 | 
						|
	# generat config...
 | 
						|
	mkdir -p "${TRAEFIK_STAGING}"
 | 
						|
	cat ${TRAEFIK_CONFIG} \
 | 
						|
		| expandPCTTemplate \
 | 
						|
		> "${source}"
 | 
						|
 | 
						|
	# get things we need if they are not set...
 | 
						|
	xread "Gate CT id: " GATE_ID
 | 
						|
 | 
						|
	# check if $filename exists...
 | 
						|
	if @ lxc-attach $GATE_ID -- test -e ${target} \
 | 
						|
			&& ! xreadYes "Overwrite existing \"${target}\"?" ; then
 | 
						|
		@ lxc-attach $GATE_ID -- mv "${target}" "${target}.bak"
 | 
						|
	fi
 | 
						|
 | 
						|
	@ pct push $GATE_ID "${source}" "${target}"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	pveGetLatestTemplate PATTERN [VAR]
 | 
						|
#
 | 
						|
# see:
 | 
						|
#	https://pve.proxmox.com/wiki/Linux_Container
 | 
						|
pveGetLatestTemplate(){
 | 
						|
	if [ $DRY_RUN ] ; then
 | 
						|
		[ -z $2 ] \
 | 
						|
			|| eval "$2=${CT_TEMPLATE:-\\\$CT_TEMPLATE}"
 | 
						|
		return
 | 
						|
	fi
 | 
						|
 | 
						|
	#@ pveam update 
 | 
						|
 | 
						|
	local templates=($(pveam available | grep -o ''${1}'.*$'))
 | 
						|
	local latest=${templates[-1]}
 | 
						|
 | 
						|
	@ pveam download local ${latest}
 | 
						|
 | 
						|
	latest=$(pveam list local | grep -o "^.*$latest")
 | 
						|
	#latest=($(ls /var/lib/vz/template/cache/${1}*))
 | 
						|
 | 
						|
	[ -z $2 ] \
 | 
						|
		|| eval "$2=${latest}"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	pctBaseCreate ID TEMPLATE ARGS [PASS]
 | 
						|
#
 | 
						|
pctBaseCreate(){
 | 
						|
	local ID=$1
 | 
						|
	local TEMPLATE=$2
 | 
						|
	local ARGS=$3
 | 
						|
	local PASS=$4
 | 
						|
 | 
						|
	local TMP_PASS=$(cat /dev/urandom | base64 | head -c ${TMP_PASS_LEN:=32})
 | 
						|
	# NOTE: we are not setting the password here to avoid printing it to the terminal...
 | 
						|
	@ pct create $ID \
 | 
						|
		"${TEMPLATE}" \
 | 
						|
		${ARGS} \
 | 
						|
		--password="$TMP_PASS" \
 | 
						|
		--start 1 \
 | 
						|
	|| exit 1
 | 
						|
 | 
						|
	# set actual root password...
 | 
						|
	if [ "$PASS" ] ; then
 | 
						|
		echo "root:$PASS" \
 | 
						|
			| @ lxc-attach $ID chpasswd
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	pctCreate ID TEMPLATE [PASS]
 | 
						|
#
 | 
						|
#OPTS_STAGE_1=
 | 
						|
#INTERFACES=
 | 
						|
#CTHOSTNAME=
 | 
						|
#CORES=
 | 
						|
#RAM=
 | 
						|
#SWAP=
 | 
						|
#DRIVE=
 | 
						|
#PCT_EXTRA=
 | 
						|
pctCreate(){
 | 
						|
	# build network args...
 | 
						|
	local interfaces_args=()
 | 
						|
	local i=0
 | 
						|
	local interface
 | 
						|
	for interface in "${INTERFACES[@]}" ; do
 | 
						|
		interfaces_args+=("--net${i} "${interface}"")
 | 
						|
		i=$(( i + 1 ))
 | 
						|
	done
 | 
						|
 | 
						|
	# NOTE: TKL gui will not function correctly without nesting enabled...
 | 
						|
	local args="\
 | 
						|
		--hostname $CTHOSTNAME \
 | 
						|
		--cores $CORES \
 | 
						|
		--memory $RAM \
 | 
						|
		--swap $SWAP \
 | 
						|
		"${interfaces_args[@]}" \
 | 
						|
		--storage local-lvm \
 | 
						|
		--rootfs local-lvm:$DRIVE \
 | 
						|
		--unprivileged 1 \
 | 
						|
		--features nesting=1 \
 | 
						|
		${PCT_EXTRA} \
 | 
						|
	"
 | 
						|
 | 
						|
	pctBaseCreate "$1" "$2" "${OPTS_STAGE_1:-"${args}"}" "$3"
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	pctCreate<distro> ID [PASS]
 | 
						|
#
 | 
						|
pctCreateAlpine(){
 | 
						|
	local TEMPLATE
 | 
						|
	pveGetLatestTemplate alpine TEMPLATE
 | 
						|
 | 
						|
	pctCreate $1 "$TEMPLATE" "$2"
 | 
						|
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
 | 
						|
	@ lxc-attach $1 apk update
 | 
						|
	@ lxc-attach $1 apk upgrade
 | 
						|
}
 | 
						|
pctCreateDebian(){
 | 
						|
	local TEMPLATE
 | 
						|
	pveGetLatestTemplate 'debian-12-standard' TEMPLATE
 | 
						|
 | 
						|
	pctCreate $1 "$TEMPLATE" "$2"
 | 
						|
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
 | 
						|
	@ lxc-attach $1 apt update
 | 
						|
	@ lxc-attach $1 -- apt upgrade -y
 | 
						|
}
 | 
						|
pctCreateUbuntu(){
 | 
						|
	local TEMPLATE
 | 
						|
	pveGetLatestTemplate ubuntu TEMPLATE
 | 
						|
 | 
						|
	pctCreate $1 "$TEMPLATE" "$2"
 | 
						|
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
 | 
						|
	@ lxc-attach $1 apt update
 | 
						|
	@ lxc-attach $1 -- apt upgrade -y
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	pctCreateTurnkey APP ID [PASS]
 | 
						|
#
 | 
						|
pctCreateTurnkey(){
 | 
						|
	local app=$1
 | 
						|
	shift
 | 
						|
	local TEMPLATE
 | 
						|
	pveGetLatestTemplate '.*-turnkey-'$app TEMPLATE
 | 
						|
 | 
						|
	pctCreate $1 "$TEMPLATE" "$2"
 | 
						|
 | 
						|
	tklWaitForSetup $1
 | 
						|
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
}
 | 
						|
 | 
						|
# Wait for /etc/inithooks.conf to be generated then cleared
 | 
						|
#
 | 
						|
# 	tklWaitForSetup ID
 | 
						|
#
 | 
						|
# for tkl inithooks doc see:
 | 
						|
# 	https://www.turnkeylinux.org/docs/inithooks
 | 
						|
tklWaitForSetup(){
 | 
						|
	printf "# TKL setup, this may take a while"
 | 
						|
	if [ -z $DRY_RUN ] ; then
 | 
						|
		while ! $(lxc-attach $1 -- test -e /etc/inithooks.conf) ; do
 | 
						|
			printf '.'
 | 
						|
			sleep ${TIMEOUT:=5}
 | 
						|
		done
 | 
						|
		printf '+'
 | 
						|
		sleep ${TIMEOUT:=5}
 | 
						|
		while ! [[ $(lxc-attach $1 -- cat /etc/inithooks.conf | wc -c) < 2 ]] ; do
 | 
						|
			printf '.'
 | 
						|
			sleep ${TIMEOUT:=5}
 | 
						|
		done
 | 
						|
	else
 | 
						|
		printf '.+..'
 | 
						|
	fi
 | 
						|
	printf 'ready.\n'
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	pctUpdateTurnkey ID
 | 
						|
#
 | 
						|
pctUpdateTurnkey(){
 | 
						|
	@ lxc-attach $1 apt update
 | 
						|
	@ lxc-attach $1 -- apt upgrade -y
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	pctSet ID [ARGS [REBOOT]]
 | 
						|
#
 | 
						|
pctSet(){
 | 
						|
	[ "$2" ] \
 | 
						|
		&& @ pct set $1 \
 | 
						|
			${2}
 | 
						|
	[ "$3" ] \
 | 
						|
		&& @ pct reboot $1
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	pctSetNotes ID [DESCRIPTION]
 | 
						|
#
 | 
						|
pctSetNotes(){
 | 
						|
	# XXX for some reason this complains quote alot...
 | 
						|
	#[ "$DESCRIPTION" ] \
 | 
						|
	#	&& @ pct set $1 \
 | 
						|
	#		"${DESCRIPTION:+--description \""${DESCRIPTION}"\"}"
 | 
						|
	local ID=$1
 | 
						|
	local NOTES="$(\
 | 
						|
		echo -e "${2:-${DESCRIPTION}}" \
 | 
						|
			| sed -e 's/^/#/')"
 | 
						|
 | 
						|
	if [ "$DRY_RUN" ] ; then
 | 
						|
		return
 | 
						|
	fi
 | 
						|
 | 
						|
	local CONF="$(cat "${CT_DIR}/${ID}.conf")"
 | 
						|
	local TEXT="\
 | 
						|
"${NOTES}"
 | 
						|
"${CONF}"
 | 
						|
"
 | 
						|
	echo -e "${TEXT}" > "${CT_DIR}/${ID}.conf"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	showNotes [VAR ...]
 | 
						|
#
 | 
						|
BUILD_NOTES=BUILD_NOTES
 | 
						|
showNotes(){
 | 
						|
	[ -e "${BUILD_NOTES}" ] \
 | 
						|
		&& mv "${BUILD_NOTES}"{,.bak}
 | 
						|
	[ -e "${BUILD_NOTES}".tpl ] \
 | 
						|
		&& ( cat "${BUILD_NOTES}".tpl \
 | 
						|
			| expandPCTTemplate $@ \
 | 
						|
			| tee "${BUILD_NOTES}" )
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	pushNotes ID
 | 
						|
#
 | 
						|
pushNotes(){
 | 
						|
	[ -e "${BUILD_NOTES}" ] \
 | 
						|
		&& @ pct-push-r $1 "${BUILD_NOTES}" /root/
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# vim:set ts=4 sw=4 nowrap :
 |