mirror of
				https://github.com/flynx/proxmox-utils.git
				synced 2025-11-03 21:50:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			441 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			441 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
#!/usr/bin/bash
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
CT_DIR=${CT_DIR:=/etc/pve/lxc/}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# XXX this is quite generic, might be a good idea to move this to a 
 | 
						|
# 		seporate lib/file...
 | 
						|
 | 
						|
# Execute (optionally) and print a command.
 | 
						|
#
 | 
						|
#QUIET=
 | 
						|
#DRY_RUN=
 | 
						|
ECHO_PREFIX="### "
 | 
						|
@(){
 | 
						|
	if [ -z $DRY_RUN ] ; then
 | 
						|
		! [ $QUIET ] \
 | 
						|
			&& echo "${ECHO_PREFIX}$@"
 | 
						|
		"$@"
 | 
						|
	else
 | 
						|
		echo $@
 | 
						|
	fi
 | 
						|
	return $?
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# 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 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'
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
 | 
						|
#
 | 
						|
#	getLatestTemplate PATTERN [VAR]
 | 
						|
#
 | 
						|
# see:
 | 
						|
#	https://pve.proxmox.com/wiki/Linux_Container
 | 
						|
getLatestTemplate(){
 | 
						|
	#IFS=$'\n'
 | 
						|
	#@ 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}"
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	xread [-n] MSG VAR
 | 
						|
#
 | 
						|
xread(){
 | 
						|
	local non_empty=
 | 
						|
	if [[ $1 == '-n' ]] ; then
 | 
						|
		shift
 | 
						|
		local non_empty=1
 | 
						|
	fi
 | 
						|
	[ -z ${!2} ] \
 | 
						|
		&& eval 'read -ep "'$1'" -i "$DFL_'$2'" '${2}''
 | 
						|
	if [ -z $non_empty ] ; then
 | 
						|
		eval ''$2'=${'$2':=$DFL_'$2'}'
 | 
						|
	fi
 | 
						|
	[ $SCRIPTING ] \
 | 
						|
		&& echo "$2=${!2}"
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	xreadpass VAR
 | 
						|
#
 | 
						|
xreadpass(){
 | 
						|
	local PASS1
 | 
						|
	local PASS2
 | 
						|
	for attempt in 1 2 3 ; do
 | 
						|
		read -sep "password (Enter to skip): " PASS1
 | 
						|
		echo
 | 
						|
		if [ -z $PASS1 ] ; then
 | 
						|
			return
 | 
						|
		fi
 | 
						|
		read -sep "retype password: " PASS2
 | 
						|
		echo
 | 
						|
		if [[ $PASS1 != $PASS2 ]] ; then
 | 
						|
			echo "ERR: passwords do not match."
 | 
						|
			continue
 | 
						|
		fi
 | 
						|
		eval ''$1'='${PASS1}''
 | 
						|
		return
 | 
						|
	done
 | 
						|
	return 1
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# Wait for /etc/inithooks.conf to be generated then cleared
 | 
						|
#
 | 
						|
# 	tklWaitForSetup
 | 
						|
#
 | 
						|
# 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 $ID -- test -e /etc/inithooks.conf) ; do
 | 
						|
			printf '.'
 | 
						|
			sleep ${TIMEOUT:=5}
 | 
						|
		done
 | 
						|
		printf '+'
 | 
						|
		sleep ${TIMEOUT:=5}
 | 
						|
		while ! [[ $(lxc-attach $ID -- cat /etc/inithooks.conf | wc -c) < 2 ]] ; do
 | 
						|
			printf '.'
 | 
						|
			sleep ${TIMEOUT:=5}
 | 
						|
		done
 | 
						|
	else
 | 
						|
		printf '.+..'
 | 
						|
	fi
 | 
						|
	printf 'ready.\n'
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# 
 | 
						|
# 	readConfig
 | 
						|
# 
 | 
						|
readConfig(){
 | 
						|
	if [ -z $NO_DEFAULTS ] ; then
 | 
						|
		[ -e ../config.global ] \
 | 
						|
			&& source ../config.global
 | 
						|
		[ -e ./config ] \
 | 
						|
			&& source ./config
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	readVars
 | 
						|
#
 | 
						|
# 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
 | 
						|
#
 | 
						|
readVars(){
 | 
						|
	xread -n "Email: " EMAIL
 | 
						|
	xread -n "Domain: " DOMAIN
 | 
						|
	xread "ID: " ID
 | 
						|
	xread "Hostname: " CTHOSTNAME
 | 
						|
 | 
						|
	# hardware...
 | 
						|
	xread "CPU cores: " CORES
 | 
						|
	xread "RAM (MB): " RAM
 | 
						|
	xread "SWAP (MB): " SWAP
 | 
						|
	xread "DRIVE (GB): " DRIVE
 | 
						|
 | 
						|
	# bridge config...
 | 
						|
	xread "WAN bridge: vmbr" WAN_BRIDGE
 | 
						|
	xread "LAN bridge: vmbr" LAN_BRIDGE
 | 
						|
	xread "ADMIN bridge: vmbr" ADMIN_BRIDGE
 | 
						|
 | 
						|
	# gateway...
 | 
						|
	# IPs can be:
 | 
						|
	#	<empty>
 | 
						|
	#	<IP>/<mask>
 | 
						|
	#	dhcp
 | 
						|
	# Gateways can be:
 | 
						|
	#	<empty>
 | 
						|
	#	<IP>
 | 
						|
	xread "WAN ip: " WAN_IP
 | 
						|
	xread "WAN gateway: " WAN_GATE
 | 
						|
	xread "LAN ip: " LAN_IP
 | 
						|
	xread "LAN gateway: " LAN_GATE
 | 
						|
	xread "ADMIN ip: " ADMIN_IP
 | 
						|
	xread "ADMIN gateway: " ADMIN_GATE
 | 
						|
 | 
						|
	# root password...
 | 
						|
	if [ -z $ROOTPASS ] ; then
 | 
						|
		xreadpass PASS \
 | 
						|
			|| exit 1
 | 
						|
	else
 | 
						|
		PASS=$ROOTPASS
 | 
						|
	fi
 | 
						|
 | 
						|
	# extra stuff...
 | 
						|
	xread "pct extra options: " PCT_EXTRA
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	buildAssets [TEMPLATES [ASSETS]]
 | 
						|
#
 | 
						|
buildAssets(){
 | 
						|
	local TEMPLATE_DIR=$1
 | 
						|
	TEMPLATE_DIR=${TEMPLATE_DIR:=templates}
 | 
						|
	local ASSETS_DIR=$2
 | 
						|
	ASSETS_DIR=${ASSETS_DIR:=assets}
 | 
						|
 | 
						|
	if ! [ -e $TEMPLATE_DIR ] ; then
 | 
						|
		return
 | 
						|
	fi
 | 
						|
 | 
						|
	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 "${ASSETS_DIR}/${file}")"
 | 
						|
		cat "${TEMPLATE_DIR}/${file}" \
 | 
						|
			| sed \
 | 
						|
				-e 's/\${EMAIL}/'${EMAIL/\//\\/}'/g' \
 | 
						|
				-e 's/\${DOMAIN}/'${DOMAIN/\//\\/}'/g' \
 | 
						|
				-e 's/\${CTHOSTNAME}/'${CTHOSTNAME/\//\\/}'/g' \
 | 
						|
				-e 's/\${GATE_HOSTNAME}/'${GATE_HOSTNAME/\//\\/}'/g' \
 | 
						|
				-e 's/\${NS_HOSTNAME}/'${NS_HOSTNAME/\//\\/}'/g' \
 | 
						|
				-e 's/\${GATE_LAN_IP}/'${GATE_LAN_IP/\//\\/}'/g' \
 | 
						|
				-e 's/\${GATE_LAN_IPn}/'${GATE_LAN_IP/\/*}'/g' \
 | 
						|
				-e 's/\${GATE_ADMIN_IP}/'${GATE_ADMIN_IP/\//\\/}'/g' \
 | 
						|
				-e 's/\${GATE_ADMIN_IPn}/'${GATE_ADMIN_IP/\/*}'/g' \
 | 
						|
				-e 's/\${NS_LAN_IP}/'${NS_LAN_IP/\//\\/}'/g' \
 | 
						|
				-e 's/\${NS_LAN_IPn}/'${NS_LAN_IP/\/*}'/g' \
 | 
						|
				-e 's/\${NS_ADMIN_IP}/'${NS_ADMIN_IP/\//\\/}'/g' \
 | 
						|
				-e 's/\${NS_ADMIN_IPn}/'${NS_ADMIN_IP/\/*}'/g' \
 | 
						|
				-e 's/\${WAN_IP}/'${WAN_IP/\//\\/}'/g' \
 | 
						|
				-e 's/\${WAN_IPn}/'${WAN_IP/\/*}'/g' \
 | 
						|
				-e 's/\${WAN_GATE}/'${WAN_GATE/\//\\/}'/g' \
 | 
						|
				-e 's/\${LAN_IP}/'${LAN_IP/\//\\/}'/g' \
 | 
						|
				-e 's/\${LAN_IPn}/'${LAN_IP/\/*}'/g' \
 | 
						|
				-e 's/\${LAN_GATE}/'${LAN_GATE/\//\\/}'/' \
 | 
						|
				-e 's/\${ADMIN_IP}/'${ADMIN_IP/\//\\/}'/g' \
 | 
						|
				-e 's/\${ADMIN_IPn}/'${ADMIN_IP/\/*}'/g' \
 | 
						|
				-e 's/\${ADMIN_GATE}/'${ADMIN_GATE/\//\\/}'/g' \
 | 
						|
			> "${ASSETS_DIR}/${file}"
 | 
						|
	done
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#
 | 
						|
#	pctCreate ID TEMPLATE ARGS [PASS]
 | 
						|
#
 | 
						|
pctCreate(){
 | 
						|
	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 $1 \
 | 
						|
		${2} \
 | 
						|
		${3} \
 | 
						|
		--password="$TMP_PASS" \
 | 
						|
		--start 1 \
 | 
						|
	|| exit 1
 | 
						|
	# set actual root password...
 | 
						|
	if [ "$4" ] ; then
 | 
						|
		echo "root:$4" \
 | 
						|
			| @ lxc-attach $1 chpasswd
 | 
						|
	fi
 | 
						|
}
 | 
						|
 | 
						|
#
 | 
						|
#	pctCreate<distro> ID ARGS [PASS]
 | 
						|
#
 | 
						|
pctCreateAlpine(){
 | 
						|
	local TEMPLATE
 | 
						|
	getLatestTemplate alpine TEMPLATE
 | 
						|
 | 
						|
	pctCreate $1 "$TEMPLATE" "$2" "$3"
 | 
						|
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
 | 
						|
	@ lxc-attach $1 apk update
 | 
						|
	@ lxc-attach $1 apk upgrade
 | 
						|
}
 | 
						|
pctCreateDebian(){
 | 
						|
	local TEMPLATE
 | 
						|
	getLatestTemplate 'debian-12-standard' TEMPLATE
 | 
						|
 | 
						|
	pctCreate $1 "$TEMPLATE" "$2" "$3"
 | 
						|
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
 | 
						|
	@ lxc-attach $1 apt update
 | 
						|
	@ lxc-attach $1 -- apt upgrade -y
 | 
						|
}
 | 
						|
pctCreateUbuntu(){
 | 
						|
	local TEMPLATE
 | 
						|
	getLatestTemplate ubuntu TEMPLATE
 | 
						|
 | 
						|
	pctCreate $1 "$TEMPLATE" "$2" "$3"
 | 
						|
 | 
						|
	sleep ${TIMEOUT:=5}
 | 
						|
 | 
						|
	@ 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
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#----------------------------------------------------------------------
 | 
						|
# vim:set ts=4 sw=4 nowrap :
 |