mirror of
https://github.com/flynx/proxmox-utils.git
synced 2025-10-28 10:40:07 +00:00
1013 lines
18 KiB
Bash
1013 lines
18 KiB
Bash
#!/usr/bin/bash
|
|
#----------------------------------------------------------------------
|
|
#
|
|
#
|
|
#----------------------------------------------------------------------
|
|
|
|
CT_DIR=${CT_DIR:=/etc/pve/lxc/}
|
|
|
|
# XXX setup path...
|
|
# XXX
|
|
|
|
|
|
EDITOR=${EDITOR:-nano}
|
|
|
|
LAST_RUN_CONFIG=config.last-run
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# 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 1
|
|
fi
|
|
if [ -z ${!var} ] ; then
|
|
if [[ "$(eval "echo \$DFL_${var}")" == "SKIP" ]] ; then
|
|
eval "$var="
|
|
return 1
|
|
elif [ -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 [msg] VAR
|
|
#
|
|
xreadpass(){
|
|
local msg
|
|
if [[ $# == 2 ]] ; then
|
|
msg="$1 "
|
|
shift
|
|
fi
|
|
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}${msg}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
|
|
}
|
|
|
|
|
|
# Like cat but a prettier...
|
|
#
|
|
# listFile PATH
|
|
#
|
|
listFile(){
|
|
if [ -e "$1" ] ; then
|
|
echo "--- $1 ---"
|
|
cat "$1"
|
|
echo '---'
|
|
else
|
|
echo "$FUNCNAME: $1: No such file or directory."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
|
|
# Review changes in PATH.new, then edit/apply changes to PATH
|
|
#
|
|
# reviewApplyChanges PATH [apply|edit|skip]
|
|
#
|
|
#
|
|
# NOTE: if changes are not applied this will return non-zero making this
|
|
# usable in conditionals...
|
|
reviewApplyChanges(){
|
|
local file=$1
|
|
if ! [ -e "$file".new ] ; then
|
|
echo "$FUNCNAME: $1: No such file or directory."
|
|
return 1
|
|
fi
|
|
|
|
# default option...
|
|
local dfl=
|
|
local a=a
|
|
local e=e
|
|
local s=s
|
|
case "${2,,}" in
|
|
a|apply)
|
|
a=A
|
|
dfl=a
|
|
;;
|
|
e|edit)
|
|
e=E
|
|
dfl=e
|
|
;;
|
|
s|skip)
|
|
s=S
|
|
dfl=s
|
|
;;
|
|
esac
|
|
|
|
echo "# Review updated: ${file}.new:"
|
|
listFile ${file}.new
|
|
local res
|
|
while true ; do
|
|
read -ep "# [$a]pply, [$e]dit, [$s]kip? " res
|
|
if [ -z $res ] ; then
|
|
if [ -z $dfl ] ; then
|
|
continue
|
|
fi
|
|
res=$dfl
|
|
fi
|
|
case "${res,,}" in
|
|
a|apply)
|
|
break
|
|
;;
|
|
e|edit)
|
|
${EDITOR} "${file}.new"
|
|
listFile ${file}.new
|
|
;;
|
|
s|skip)
|
|
echo "# Changes kept 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 ./$LAST_RUN_CONFIG ] \
|
|
&& [ -z $CLEAN_RUN ] \
|
|
&& source ./$LAST_RUN_CONFIG
|
|
[ -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=$LAST_RUN_CONFIG
|
|
local ct_cfg=$ID-$CTHOSTNAME.cfg
|
|
echo "# Saving config to: $cfg and $ct_cfg"
|
|
{
|
|
echo "#"
|
|
echo "# This file is auto-generated, any changes here will be overwritten."
|
|
echo "#"
|
|
} > "$cfg"
|
|
saveConfig -d -a "$cfg" ${XREAD_VARS[@]}
|
|
cp "$cfg" "$ct_cfg"
|
|
}
|
|
|
|
|
|
#
|
|
# 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 "ADMIN bridge: vmbr" ADMIN_BRIDGE
|
|
xread "WAN bridge: vmbr" WAN_BRIDGE
|
|
xread "LAN bridge: vmbr" LAN_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 root 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 :
|