diff --git a/proxmox/backup.pve.content.sh b/proxmox/backup.pve.content.sh new file mode 100755 index 0000000..c3006e5 --- /dev/null +++ b/proxmox/backup.pve.content.sh @@ -0,0 +1,290 @@ +#!/bin/sh + +# This script will backup /etc/pve content : +# 1. Make an archive to a first directory (default: /etc/proxmox.pve/backup/). +# 2. Hard link a fix archive name (pve.latest.tar.gz) to new archive +# Easy to monitor (eg. this path can be expected). +# 3. Limit permissions to backup directory (default: backup:adm). +# 4. Clean backups older than retention time (default: 7). +# 5. (optionnal) Copy backup to a second directory (nfs mountpoint, other hdd,…). +# + +# This script can be call by a cronjob (eg. daily). + +# Vars {{{ +readonly PROGNAME=$(basename "${0}") +readonly PROGDIR=$(readlink -m $(dirname "${0}")) +readonly ARGS="${*}" +readonly NBARGS="${#}" +[ -z "${DEBUG}" ] && DEBUG=1 + +readonly DEFAULT_FIRST_BKP_DIR="/etc/proxmox.pve/backup" +readonly TODAY_VAR=$(date +%Y%m%d) +readonly DEFAULT_RETENTION_TIME="7" +readonly DEFAULT_USER="backup" +readonly DEFAULT_GROUP="adm" + +## Colors +readonly PURPLE='\033[1;35m' +readonly RED='\033[0;31m' +readonly RESET='\033[0m' +readonly COLOR_DEBUG="${PURPLE}" +# }}} + +usage() { # {{{ + + cat <<- EOF +usage: $PROGNAME [-d|-f|-g|-h|-r|-s|-u] + +Backup /etc/pve content. + +EXAMPLES : + - Backup /etc/pve content to ${DEFAULT_FIRST_BKP_DIR} directory + ${PROGNAME} + + - Backup /etc/pve content to /var/backups/pve directory + ${PROGNAME} --first-directory /var/backups/pve + + - Backup to default path and keep backups for 14 days + ${PROGNAME} --retention 14 + + - Duplicate backups to a second directory (/mnt/nfs/pve) + ${PROGNAME} --second-directory /mnt/nfs/pve + +OPTIONS : + -d,--debug + Enable debug messages. + + -f,--first,--first-directory + Path to a first directory to store backup + And override default path ${DEFAULT_FIRST_BKP_DIR}. + + -g,--group + Group of the backup files (default: ${DEFAULT_GROUP}). + + -h,--help + Print this help message. + + -r,--retention,--retention-time + Backups older than retention time (default: ${DEFAULT_RETENTION_TIME}) + will be delete. + + -s,--second,--second-directory + Path to a second directory to duplicate backups (default: not set). + + -u,--user + Owner of the backup files (default: ${DEFAULT_USER}). + + EOF + +} +# }}} +debug_message() { # {{{ + + local_message="${1}" + + ## Print message if DEBUG is enable (=0) + [ "${DEBUG}" -eq "0" ] && printf '\e[1;35m%-6b\e[m\n' "DEBUG − ${PROGNAME} : ${local_message}" + + return 0 +} +# }}} +define_vars() { # {{{ + + ## If first_bkp_dir wasn't defined {{{ + if [ -z "${first_bkp_dir}" ]; then + ## Use default path to store backup + first_bkp_dir="${DEFAULT_FIRST_BKP_DIR}" + fi + ## }}} + ## If retention_time wasn't defined {{{ + if [ -z "${retention_time}" ]; then + ## Use default retention time to clean backups + retention_time="${DEFAULT_RETENTION_TIME}" + fi + ## }}} + ## If user_bkp_dir wasn't defined {{{ + if [ -z "${user_bkp_dir}" ]; then + ## Use default user as owner of backup files + user_bkp_dir="${DEFAULT_USER}" + fi + ## }}} + ## If group_bkp_dir wasn't defined {{{ + if [ -z "${group_bkp_dir}" ]; then + ## Use default group for backup files + group_bkp_dir="${DEFAULT_GROUP}" + fi + ## }}} + +} +# }}} +is_directory_absent() { # {{{ + + local_directory_absent="${1}" + + ## Directory exists by default + return_is_directory_absent="1" + + ### Check if the directory exists + # shellcheck disable=SC2086 + if test -d "${local_directory_absent}"; then + return_is_directory_absent="1" + debug_message "is_directory_absent − \ +The directory ${RED}${local_directory_absent}${COLOR_DEBUG} exists." + else + return_is_directory_absent="0" + debug_message "is_directory_absent − \ +The directory ${RED}${local_directory_absent}${COLOR_DEBUG} doesn't exist." + fi + + return "${return_is_directory_absent}" + +} +# }}} +main() { # {{{ + + ## Define all vars + define_vars + + ## Verify if /etc/pve directory is absent {{{ + ### Display an explicit error message + ### AND exit with error code 1 + is_directory_absent /etc/pve \ + && printf '%b\n' "${RED}/etc/pve directory doesn't seems available. Are you sure you run this script on a Proxmox host?${RESET}" \ + && exit 1 + ## }}} + ## Verify if the first destination directory is absent {{{ + ### AND create it + is_directory_absent "${first_bkp_dir}" \ + && mkdir -p -- "${first_bkp_dir}" + ## }}} + + ## Create an archive of /etc/pve to $first_bkp_dir {{{ + ### OR exit with error code 2 if it fails + tar --exclude='*/lock' -czf "${first_bkp_dir}/pve.${TODAY_VAR}.tar.gz" -C /etc/ pve/ \ + || exit 2 + ## }}} + ## Create an hard link to pve.latest.tar.gz {{{ + ### OR exit with error code 3 if it fails + ln --force -- "${first_bkp_dir}/pve.${TODAY_VAR}.tar.gz" "${first_bkp_dir}/pve.latest.tar.gz" \ + || exit 3 + ## }}} + ## Fix backups permissions {{{ + ### Only readable by specified user:group (default: backup:adm) + chown -R "${user_bkp_dir}:${group_bkp_dir}" -- "${first_bkp_dir}" \ + && chmod 'u+rwX,g+rX,o-rwx' -R -- "${first_bkp_dir}" + ## }}} + ## Clean files older than $retention_time {{{ + ### OR exit with error code 4 if it fails + find "${first_bkp_dir}" -maxdepth 1 -type f -mtime +"${retention_time}" -iname "pve.*.tar.gz" -delete \ + || exit 4 + ## }}} + + ## If second directory is defined {{{ + if [ -n "${second_bkp_dir}" ]; then + ### Verify if the second destination directory is absent {{{ + #### AND create it + is_directory_absent "${second_bkp_dir}" \ + && mkdir -p -- "${second_bkp_dir}" + ### }}} + ### Synchronize first directory to second {{{ + #### OR exit with error code 12 if it fails + #### rsync "-a" option might fail with some network share + #### So, remove --group and --owner options + rsync --recursive --links --perms --times -D -- "${first_bkp_dir}/" "${second_bkp_dir}/" \ + || exit 12 + ### }}} + fi + ## }}} +} +# }}} + +# Manage arguments # {{{ +# This code can't be in a function due to argument management + +if [ ! "${NBARGS}" -eq "0" ]; then + + manage_arg="0" + + ## If the first argument is not an option + if ! printf -- '%s' "${1}" | grep -q -E -- "^-+"; + then + ## Print help message and exit + printf '%b\n' "${RED}Invalid option: ${1}${RESET}" + printf '%b\n' "---" + usage + + exit 1 + fi + + # Parse all options (start with a "-") one by one + while printf -- '%s' "${1}" | grep -q -E -- "^-+"; do + + case "${1}" in + -d|--debug ) ## debug + DEBUG=0 + ;; + -f|--first|--first-directory ) ## first directory to store backup + ## Move to the next argument + shift + ## Define first_bkp_dir + first_bkp_dir="${1}" + ;; + -g|--group ) ## group of backup files + ## Move to the next argument + shift + ## Define group_bkp_dir + group_bkp_dir="${1}" + ;; + -h|--help ) ## help + usage + ## Exit after help informations + exit 0 + ;; + -r|--retention,--retention-time ) ## clean backups older than retention time + ## Move to the next argument + shift + ## Define retention_time + retention_time="${1}" + ;; + -s|--second|--second-directory ) ## second directory to duplicate backup + ## Move to the next argument + shift + ## Define second_bkp_dir + second_bkp_dir="${1}" + ;; + -u|--user ) ## owner of backup files + ## Move to the next argument + shift + ## Define user_bkp_dir + user_bkp_dir="${1}" + ;; + * ) ## unknow option + printf '%b\n' "${RED}Invalid option: ${1}${RESET}" + printf '%b\n' "---" + usage + exit 1 + ;; + esac + + debug_message "Arguments management − \ +${RED}${1}${COLOR_DEBUG} option managed." + + ## Move to the next argument + shift + manage_arg=$((manage_arg+1)) + + done + + debug_message "Arguments management − \ +${RED}${manage_arg}${COLOR_DEBUG} argument(s) successfully managed." +else + debug_message "Arguments management − \ +No arguments/options to manage." +fi + +# }}} + +main + +exit 0 diff --git a/proxmox/build.network.file b/proxmox/build.network.file new file mode 100755 index 0000000..d9d2577 --- /dev/null +++ b/proxmox/build.network.file @@ -0,0 +1,48 @@ +#!/bin/sh + +# Purpose : {{{ +# Proxmox only reads /etc/network/interfaces from a while +# See : https://forum.proxmox.com/threads/anyway-to-support-interfaces-d.34739/ +# So any bridge,… in the subdirectory /etc/network/interfaces.d won't be +# available from webgui to manage KVM/LXC's network device. +# }}} +# This script will try to fix this waiting for a Proxmox's fix. {{{ +# Check if /etc/network/interfaces.d contains file(s) +# Create a temp interfaces file +# Add some basic/header informations to the temp file +# Cat the content of interfaces.d files to the temp file +# Replace default interfaces file with the temp one + +# }}} + +interfaces_dir="/etc/network/interfaces.d" +interfaces_temp="/etc/network/interfaces.temp.script" + +# Test if interfaces.d is empty +if [ -z "$(ls -A ${interfaces_dir})" ]; then + exit 0 +fi + +# Create temp file +rm -f -- "${interfaces_temp}" +touch -- "${interfaces_temp}" + +# Header informations +printf '%b' "# This file describes the network interfaces available on your system +# and how to activate them. For more information, see interfaces(5). +" >> "${interfaces_temp}" + +# Loopback informations if not present in interfaces.d +grep -q -R "iface lo" "${interfaces_dir}" || printf '%b' " +# The loopback network interface +auto lo +iface lo inet loopback +" >> "${interfaces_temp}" + +# Cat the content of interfaces.d to temp file (POSIX way) +find "${interfaces_dir}" -type f -exec cat {} >> "${interfaces_temp}" \; + +# Define it a new interfaces file +mv -- "${interfaces_temp}" /etc/network/interfaces + +exit 0 diff --git a/proxmox/proxmox.template.debian.sh b/proxmox/proxmox.template.debian.sh new file mode 100755 index 0000000..67c9f54 --- /dev/null +++ b/proxmox/proxmox.template.debian.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# +# Some commands to generate a "good" custom Debian template + +# SSH {{{ + +# allow root connection with a password +/bin/sed -i 's/\(^\|^\#\)\(PermitRootLogin\).*/\2 yes/g' /etc/ssh/sshd_config ; +systemctl restart sshd + +# or download admin ssh pubkey + +# }}} + +# time {{{ + +# set timezone +echo "Europe/Paris" > /etc/timezone +rm --force -- /etc/localtime +dpkg-reconfigure --frontend noninteractive -- tzdata + +# }}} + +# manage locale {{{ + +NEW_L="en_US.UTF-8" +## Fix locale for the script +export LANGUAGE="${NEW_L}" +export LANG="${NEW_L}" +export LC_ALL="${NEW_L}" + +## Generate new locale +sed -i -e "s/# \(${NEW_L} UTF-8\)/\1/" /etc/locale.gen +locale-gen +echo "LANG=\"${NEW_L}\"" > /etc/default/locale +dpkg-reconfigure --frontend noninteractive -- locales +update-locale LANG="${NEW_L}" + +# }}} + +# download an additionnal script to manage rsyslog and logrotate {{{ + +# Previously used hostnamectl but it can't works correctly on LXC container with Apparmor +debian_version=$(grep VERSION_CODENAME /etc/os-release | cut --delimiter="=" --fields=2) + +wget https://git.ipr.univ-rennes.fr/cellinfo/tftpboot/raw/master/scripts/latecommand.tar.gz --output-document=/tmp/latecommand.tar.gz +tar xzf /tmp/latecommand.tar.gz --directory=/tmp/ +/bin/sh /tmp/latecommand/post."${debian_version}".sh + +# }}} + +# APT {{{ + +## Clean downloaded and list of packages +aptitude clean +rm --force -- /var/cache/apt/*.bin + +# }}} + +# clean the system {{{ +true > /etc/resolv.conf +find /var/log -type f -iname "*.log" -delete -exec touch {} \; +find /var/log -type f \( -iname "*.gz" -o -iname ".*.0" -o -iname "dmesg.*" \) -delete +rm --force -- /root/.bash_history +rm --recursive --force -- /var/log/journal/* + +# }}} + +exit 0 diff --git a/proxmox/proxmox.template.debian.upgrade.sh b/proxmox/proxmox.template.debian.upgrade.sh new file mode 100755 index 0000000..32bd68c --- /dev/null +++ b/proxmox/proxmox.template.debian.upgrade.sh @@ -0,0 +1,351 @@ +#!/bin/sh +# +# This script will try to upgrade an LXC Debian template +# 1. Start the LXC container if available +# 2. Run some script to upgrade the container +# 3. Stop the container + +# This script can be call by a cronjob (eg. daily, weekly,…) + +# Vars {{{ +readonly PROGNAME=$(basename "${0}") +readonly PROGDIR=$(readlink -m $(dirname "${0}")) +readonly ARGS="${*}" +readonly NBARGS="${#}" +[ -z "${DEBUG}" ] && DEBUG=1 +## Export DEBUG for sub-script +export DEBUG + +readonly CT_ID_DEFAULT="199124" +readonly SLEEP_DELAY_DEFAULT="20" + +## Colors +readonly PURPLE='\033[1;35m' +readonly RED='\033[0;31m' +readonly RESET='\033[0m' +readonly COLOR_DEBUG="${PURPLE}" +# }}} +usage() { # {{{ + + cat <<- EOF +usage: $PROGNAME [-d|-h|-i|-s] + +Start and upgrade an LXC container for Debian template + +EXAMPLES : + - Upgrade default LXC container (${CT_ID_DEFAULT}) : + ${PROGNAME} + + - Specify the container ID : + ${PROGNAME} --id 666 + +OPTIONS : + -d,--debug + Enable debug messages. + + -h,--help + Print this help message. + + -i,--id + Choose a different LXC container ID (default ${CT_ID_DEFAULT}). + + -s,--sleep + Set a different delay to test container between each + different state (default: ${SLEEP_DELAY_DEFAULT}). + + EOF + +} +# }}} +debug_message() { # {{{ + + local_message="${1}" + + ## Print message if DEBUG is enable (=0) + [ "${DEBUG}" -eq "0" ] && printf '\e[1;35m%-6b\e[m\n' "DEBUG − ${PROGNAME} : ${local_message}" + + return 0 +} +# }}} +error_message() { # {{{ + + local_error_message="${1}" + local_error_code="${2}" + + ## Print error message + printf '%b\n' "ERROR − ${PROGNAME} : ${RED}${local_error_message}${RESET}" + + exit "${local_error_code:=66}" +} +# }}} +define_vars() { # {{{ + + ## If ct_id wasn't defined (argument) {{{ + if [ -z "${ct_id}" ]; then + ## Use default value + readonly ct_id="${CT_ID_DEFAULT}" + fi + ## }}} + + ## If sleep_delay wasn't defined (argument) {{{ + if [ -z "${sleep_delay}" ]; then + ## Use default value + readonly sleep_delay="${SLEEP_DELAY_DEFAULT}" + fi + ## }}} + +} +# }}} +is_proxmox_absent() { # {{{ + + ## By default, Proxmox is absent on an host + return_is_proxmox_absent="0" + + ## Check if the system runs on a PVE kernel + if ! uname --kernel-release -- | grep --quiet -- "pve"; + then + return_is_proxmox_absent="0" + debug_message "is_proxmox_absent − \ +Proxmox is ${RED}absent${COLOR_DEBUG} on this host." + else + return_is_proxmox_absent="1" + debug_message "is_proxmox_absent − \ +Proxmox seems ${RED}present${COLOR_DEBUG} on this host." + fi + + return "${return_is_proxmox_absent}" + +} +# }}} +is_lxc_container_absent() { # {{{ + + local_ct_id="${1}" + + ## By default, the LXC container is absent from the current host + return_is_lxc_container_absent="0" + + ## Check if the system runs on a PVE kernel + if ! /usr/sbin/pct list -- | grep --quiet "${local_ct_id}"; + then + return_is_lxc_container_absent="0" + debug_message "is_lxc_container_absent − \ +The LXC container (${RED}${local_ct_id}${COLOR_DEBUG}) is ${RED}absent${COLOR_DEBUG} from this host." + else + return_is_lxc_container_absent="1" + debug_message "is_lxc_container_absent − \ +The LXC container (${RED}${local_ct_id}${COLOR_DEBUG}) is ${RED}present${COLOR_DEBUG} on this host." + fi + + return "${return_is_lxc_container_absent}" + +} +# }}} +is_lxc_container_state() { # {{{ + + local_ct_id="${1}" + local_ct_state="${2}" + + ## Compare the status of the LXC container with argument + if /usr/sbin/pct status "${local_ct_id}" -- | grep --quiet --word-regexp "${local_ct_state}"; + then + return_is_lxc_container_state="0" + debug_message "is_lxc_container_state − \ +The LXC container (${RED}${local_ct_id}${COLOR_DEBUG}) is in ${RED}${local_ct_state}${COLOR_DEBUG} state." + else + return_is_lxc_container_state="1" + debug_message "is_lxc_container_state − \ +The LXC container (${RED}${local_ct_id}${COLOR_DEBUG}) is not in ${RED}${local_ct_state}${COLOR_DEBUG} state." + fi + + return "${return_is_lxc_container_state}" + +} +# }}} +start_lxc_container() { # {{{ + + local_ct_id="${1}" + + ## By default, the container is not running + return_start_lxc_container="1" + + ## Start LXC container state + /usr/sbin/pct start "${local_ct_id}" || exit 1 + + ## Wait a little for the container to start + sleep "${sleep_delay}" + + ## Verify LXC container status + if is_lxc_container_state "${local_ct_id}" "running"; + then + return_start_lxc_container="0" + debug_message "start_lxc_container − \ +LXC container ${RED}${local_ct_id}${COLOR_DEBUG} is now ${RED}running${COLOR_DEBUG}." + else + return_start_lxc_container="1" + debug_message "start_lxc_container − \ +LXC container ${RED}${local_ct_id}${COLOR_DEBUG} is still ${RED}stopped${COLOR_DEBUG}." + fi + + return "${return_start_lxc_container}" + +} +# }}} +stop_lxc_container() { # {{{ + + local_ct_id="${1}" + + ## By default, the container is running + return_stop_lxc_container="1" + + ## Stop LXC container state + /usr/sbin/pct stop "${local_ct_id}" || exit 1 + + ## Wait a little for the container to stop + sleep "${sleep_delay}" + + ## Verify LXC container status + if is_lxc_container_state "${local_ct_id}" "stopped"; + then + return_stop_lxc_container="0" + debug_message "stop_lxc_container − \ +LXC container ${RED}${local_ct_id}${COLOR_DEBUG} is now ${RED}stopped${COLOR_DEBUG}." + else + return_stop_lxc_container="1" + debug_message "stop_lxc_container − \ +LXC container ${RED}${local_ct_id}${COLOR_DEBUG} is still ${RED}running${COLOR_DEBUG}." + fi + + return "${return_stop_lxc_container}" + +} +# }}} +upgrade_container() { # {{{ + + local_ct_id="${1}" + + ## Keep output if DEBUG mode is activated + if [ "${DEBUG}" -eq "0" ]; then + /usr/sbin/pct exec "${local_ct_id:-/dev/null}" -- bash -c "wget https://git.ipr.univ-rennes.fr/cellinfo/scripts/raw/master/proxmox/proxmox.template.debian.sh --output-document=/tmp/proxmox.template.debian.sh" || exit 2 + /usr/sbin/pct exec "${local_ct_id:-/dev/null}" -- bash -c "chmod +x /tmp/proxmox.template.debian.sh" || exit 2 + /usr/sbin/pct exec "${local_ct_id:-/dev/null}" -- bash -c "/tmp/proxmox.template.debian.sh" || exit 2 + else + /usr/sbin/pct exec "${local_ct_id:-/dev/null}" -- bash -c "wget --quiet https://git.ipr.univ-rennes.fr/cellinfo/scripts/raw/master/proxmox/proxmox.template.debian.sh --output-document=/tmp/proxmox.template.debian.sh" || exit 2 + /usr/sbin/pct exec "${local_ct_id:-/dev/null}" -- bash -c "chmod +x /tmp/proxmox.template.debian.sh" || exit 2 + /usr/sbin/pct exec "${local_ct_id:-/dev/null}" -- bash -c "/tmp/proxmox.template.debian.sh" > /dev/null 2>&1 || exit 2 + fi + +} +# }}} + +main() { # {{{ + + ## If Proxmox is not yet available on this host {{{ + ### Exit + is_proxmox_absent \ + && exit 0 + ## }}} + + ## Define all vars + define_vars + + ## If the LXC container ID is absent from this host {{{ + ### Exit + is_lxc_container_absent "${ct_id}" \ + && exit 0 + ## }}} + + ## If the LXC container ID is already running {{{ + ### Exit with error message + is_lxc_container_state "${ct_id}" "running" \ + && error_message "LXC container (${ct_id}) is already running. \ +Please power it off if you don't have any work in progress." "3" + ## }}} + + ## If the LXC container ID is stopped {{{ + ### Try to start the container + is_lxc_container_state "${ct_id}" "stopped" \ + && start_lxc_container "${ct_id}" + ## }}} + + ## Try to upgrade the container {{{ + ### And re-stop the container + ### Then exit with success the script + upgrade_container "${ct_id}" \ + && stop_lxc_container "${ct_id}" \ + && exit 0 + ## }}} + +} +# }}} + +# Manage arguments # {{{ +# This code can't be in a function due to argument management + +if [ ! "${NBARGS}" -eq "0" ]; then + + manage_arg="0" + + ## If the first argument is not an option + if ! printf -- '%s' "${1}" | grep -q -E -- "^-+"; + then + ## Print help message and exit + printf '%b\n' "${RED}Invalid option: ${1}${RESET}" + printf '%b\n' "---" + usage + + exit 1 + fi + + # Parse all options (start with a "-") one by one + while printf -- '%s' "${1}" | grep -q -E -- "^-+"; do + + case "${1}" in + -d|--debug ) ## debug + DEBUG=0 + ;; + -h|--help ) ## help + usage + ## Exit after help informations + exit 0 + ;; + -i|--id ) ## Use given CT ID + ## Move to the next argument + shift + ## Define ct_id + readonly ct_id="${1}" + ;; + -s|--sleep ) ## Use given sleeping delay + ## Move to the next argument + shift + ## Define sleep_delay + readonly sleep_delay="${1}" + ;; + * ) ## unknow option + printf '%b\n' "${RED}Invalid option: ${1}${RESET}" + printf '%b\n' "---" + usage + exit 1 + ;; + esac + + debug_message "Arguments management − \ +${RED}${1}${COLOR_DEBUG} option managed." + + ## Move to the next argument + shift + manage_arg=$((manage_arg+1)) + + done + + debug_message "Arguments management − \ +${RED}${manage_arg}${COLOR_DEBUG} argument(s) successfully managed." +else + debug_message "Arguments management − \ +No arguments/options to manage." +fi + +# }}} + +main + +exit 255 diff --git a/proxmox/vzdump-hook-debian-lxc-template-default.pl b/proxmox/vzdump-hook-debian-lxc-template-default.pl new file mode 100755 index 0000000..0f25de2 --- /dev/null +++ b/proxmox/vzdump-hook-debian-lxc-template-default.pl @@ -0,0 +1,83 @@ +#!/usr/bin/perl -w + +# hook script to copy a dump as a new LXC template (with --script option) + +# Template directory +my $TEMPLATE_DIR = "/var/lib/vz/template/cache"; +# Template file name +my $TEMPLATE_FILE_LINK = "debian.buster.template.tar.gz"; +# Number of template to keep available +my $RETENTION_TIME = "2"; + +use strict; + +print "HOOK: " . join (' ', @ARGV) . "\n"; + +my $phase = shift; + +if ($phase eq 'job-start' || + $phase eq 'job-end' || + $phase eq 'job-abort') { + + my $dumpdir = $ENV{DUMPDIR}; + + my $storeid = $ENV{STOREID}; + + print "HOOK-ENV: dumpdir=$dumpdir;storeid=$storeid\n"; + + # do what you want + +} elsif ($phase eq 'backup-start' || + $phase eq 'backup-end' || + $phase eq 'backup-abort' || + $phase eq 'log-end' || + $phase eq 'pre-stop' || + $phase eq 'pre-restart' || + $phase eq 'post-restart') { + + my $mode = shift; # stop/suspend/snapshot + + my $vmid = shift; + + my $vmtype = $ENV{VMTYPE}; # openvz/qemu + + my $dumpdir = $ENV{DUMPDIR}; + + my $storeid = $ENV{STOREID}; + + my $hostname = $ENV{HOSTNAME}; + + # tarfile is only available in phase 'backup-end' + my $tarfile = $ENV{TARFILE}; + + # logfile is only available in phase 'log-end' + my $logfile = $ENV{LOGFILE}; + + print "HOOK-ENV: vmtype=$vmtype;vmid=$vmid;dumpdir=$dumpdir;storeid=$storeid;hostname=$hostname;tarfile=$tarfile;logfile=$logfile\n"; + + # copy resulting backup file as a template + if ($phase eq 'backup-end') { + # Copy the dump as a LXC template + system ("cp -- $tarfile $TEMPLATE_DIR") == 0 || + die "copy tar file as a template failed"; + + # Unlink (eg hostname=debian.buster.template.tar.gz) + system ("unlink $TEMPLATE_DIR/$TEMPLATE_FILE_LINK"); + # no die cause if the previous backup exit on tarfile copy, the link might not exist + + # Link last template file to a better name + system ("find $TEMPLATE_DIR -iname 'vzdump-lxc-$vmid*.tar.*' -mmin -60 -exec ln -s {} $TEMPLATE_DIR/$TEMPLATE_FILE_LINK \\;") == 0 || + die "link template to a better name failed"; + + # Ensure to remove template older than $RETENTION_TIME + system ("find $TEMPLATE_DIR -iname 'vzdump-lxc-$vmid*.tar.*' -mtime +$RETENTION_TIME -delete ") == 0 || + die "remove oldest template failed"; + } + +} else { + + die "got unknown phase '$phase'"; + +} + +exit (0); diff --git a/proxmox/vzdump-hook-hardlink-latest.sh b/proxmox/vzdump-hook-hardlink-latest.sh new file mode 100755 index 0000000..aaa31d5 --- /dev/null +++ b/proxmox/vzdump-hook-hardlink-latest.sh @@ -0,0 +1,180 @@ +#!/bin/sh +# +# Purpose {{{ +# This script will hardlink latest Proxmox's backup with a explicit name. +# +# This script is intended to be used as a hook script for the Proxmox +# VZDump utility. +# +# In order to use it, use the configuration directive "script" of the +# vzdump utility. This can be done for scheduled backups by putting +# "script: /path/to/this/script" in /etc/vzdump.conf. Don't forget to +# set executable permission for the script file. +# +# Based on RobHost's vzdump_hook script : +# https://github.com/robhost/proxmox-scripts/blob/master/vzdump_hook.sh +# }}} +# Vars {{{ +readonly PROGNAME=$(basename "${0}") +readonly ARGS="${*}" +[ -z "${DEBUG}" ] && DEBUG=0 +## Export DEBUG for sub-script +export DEBUG + +## Colors +readonly PURPLE='\033[1;35m' +readonly RED='\033[0;31m' +readonly RESET='\033[0m' +readonly COLOR_DEBUG="${PURPLE}" +# }}} +usage() { # {{{ + + cat <<- HELP +usage: ${PROGNAME} [-h] + +Hardlink latest Proxmox's backup with a explicit name. + +OPTIONS : + -h,--help + Print this help message. +HELP + +} +# }}} +debug_message() { # {{{ + + local_debug_message="${1}" + local_temp_log="/tmp/pve.log" + + if [ ! -f "${local_temp_log}" ]; then + true > "${local_temp_log}" + fi + + ## Print message if DEBUG is enable (=0) + [ "${DEBUG}" -eq "0" ] && printf '\e[1;35m%-6b\e[m\n' "DEBUG − ${PROGNAME} : ${local_debug_message}" >> "${local_temp_log}" + + unset local_debug_message + + return 0 +} +# }}} +error_message() { # {{{ + + local_error_message="${1}" + local_error_code="${2}" + + ## Print message + printf '%b\n' "ERROR − ${PROGNAME} : ${RED}${local_error_message}${RESET}" + + exit "${local_error_code:=66}" +} +# }}} + +main() { # {{{ + + debug_message "main − \ +ARGS=${ARGS} +env=$(env)" + + # If backup is complete {{{ + if [ "${phase}" = "backup-end" ]; then + ## Get TARGET's file extension + target_archive_extension="$(printf '%s' "${TARGET}" | sed -n "s/.*\.\([[:alnum:]]*\.[[:alnum:]]*\)$/\1/p")" + ## Set path for LATEST archive file + latest_archive="${dumpdir}/vzdump-${vmtype}-${vmid}-latest.${target_archive_extension}" + debug_message "backup-end − \ +target_archive_extension=${target_archive_extension} +latest_archive=${latest_archive}" + + ## hardlink TARGET archive to LATEST + debug_message "hardlink TARGET archive (${target_archive}) to \ +LATEST (${latest_archive})." + ln --force "${target_archive}" "${latest_archive}" + fi + # }}} + + # If log is complete {{{ + if [ "${phase}" = "log-end" ]; then + ## Set path for LATEST log file + latest_log="${dumpdir}/vzdump-${vmtype}-${vmid}-latest.log" + debug_message "log-end − \ +latest_log=${latest_log}" + + ## hardlink TARGET log to LATEST + debug_message "hardlink TARGET logs (${logfile}) to \ +LATEST (${latest_log})." + ln --force "${logfile}" "${latest_log}" + fi + # }}} + + debug_message "---" + +} +# }}} + +# Manage arguments # {{{ +# This code can't be in a function due to argument management + + phase="${1}" # (job|backup)-(start|end|abort)|log-end|pre-(stop|restart)|post-restart + + case "${phase}" in + # set variables for the phases + #backup-start|backup-end|backup-abort|log-end|pre-stop|pre-restart|post-restart) + #;; + # do work + job-init|job-start|job-end|job-abort) + # Available vars {{{ + # Arguments : job-start + #LVM_SUPPRESS_FD_WARNINGS=1 + #DUMPDIR=/path/to/backup/dump + #STOREID=backup.id.from.storage.cfg + # }}} + ;; + backup-start|backup-end|backup-abort) + # Available vars {{{ + #ARGS=backup-start stop vm.id + #HOSTNAME=hostname.domain.tld + #TARGET=/path/to/backup/dump/vzdump-$VMTYPE_VALUE-$2_VALUE-YYYY_MM_DD-hh-mm-ss.tar.zst + #LOGFILE=/path/to/backup/dump/vzdump-$VMTYPE_VALUE-$2_VALUE-YYYY_MM_DD-hh-mm-ss.log + #LVM_SUPPRESS_FD_WARNINGS=1 + #DUMPDIR=/path/to/backup/dump + #VMTYPE=lxc|qemu + #STOREID=backup.id.from.storage.cfg + # }}} + vmid="${3}" + target_archive="${TARGET}" + dumpdir="${DUMPDIR}" + vmtype="${VMTYPE}" + ;; + log-end) + # Available vars {{{ + #ARGS=log-end stop vm.id + #HOSTNAME=hostname.domain.tld + #TARGET=/path/to/backup/dump/vzdump-$VMTYPE_VALUE-$2_VALUE-YYYY_MM_DD-hh-mm-ss.tar.zst + #LOGFILE=/path/to/backup/dump/vzdump-$VMTYPE_VALUE-$2_VALUE-YYYY_MM_DD-hh-mm-ss.log + #LVM_SUPPRESS_FD_WARNINGS=1 + #DUMPDIR=/path/to/backup/dump + #VMTYPE=lxc|qemu + #STOREID=backup.id.from.storage.cfg + # }}} + vmid="${3}" + logfile="${LOGFILE}" + dumpdir="${DUMPDIR}" + vmtype="${VMTYPE}" + ;; + pre-stop) + ;; + pre-restart) + ;; + post-restart) + ;; + *) + error_message "Unknown phase ${phase}." 1 + ;; + esac + +# }}} + +main + +exit 0 diff --git a/proxmox/vzdump-hook-lxc-buster-template.pl b/proxmox/vzdump-hook-lxc-buster-template.pl new file mode 100755 index 0000000..a52db91 --- /dev/null +++ b/proxmox/vzdump-hook-lxc-buster-template.pl @@ -0,0 +1,85 @@ +#!/usr/bin/perl -w + +# hook script to copy a dump as a new LXC template (with --script option) + +# Template directory +my $TEMPLATE_DIR = "/mnt/pve/ibmbkp.daily/template/cache"; +# Template file name +my $TEMPLATE_FILE_LINK = "buster.template.ipr.univ-rennes1.fr.tar.gz"; +# Number of template to keep available +my $RETENTION_TIME = "2"; + +use strict; + +print "HOOK: " . join (' ', @ARGV) . "\n"; + +my $phase = shift; + +if ($phase eq 'job-start' || + $phase eq 'job-end' || + $phase eq 'job-abort') { + + my $dumpdir = $ENV{DUMPDIR}; + + my $storeid = $ENV{STOREID}; + + print "HOOK-ENV: dumpdir=$dumpdir;storeid=$storeid\n"; + + # do what you want + +} elsif ($phase eq 'backup-start' || + $phase eq 'backup-end' || + $phase eq 'backup-abort' || + $phase eq 'log-end' || + $phase eq 'pre-stop' || + $phase eq 'pre-restart' || + $phase eq 'post-restart') { + + my $mode = shift; # stop/suspend/snapshot + + my $vmid = shift; + + my $vmtype = $ENV{VMTYPE}; # openvz/qemu + + my $dumpdir = $ENV{DUMPDIR}; + + my $storeid = $ENV{STOREID}; + + my $hostname = $ENV{HOSTNAME}; + + # tarfile is only available in phase 'backup-end' + ## TODO: TARFILE is deprecated in Proxmox 6 and was removed in Proxmox 7. Need to replace with TARGET. + ## See "Breaking Changes" for Proxmox 7 : https://pve.proxmox.com/wiki/Roadmap#Proxmox_VE_7.0 + my $tarfile = $ENV{TARFILE}; + + # logfile is only available in phase 'log-end' + my $logfile = $ENV{LOGFILE}; + + print "HOOK-ENV: vmtype=$vmtype;vmid=$vmid;dumpdir=$dumpdir;storeid=$storeid;hostname=$hostname;tarfile=$tarfile;logfile=$logfile\n"; + + # copy resulting backup file as a template + if ($phase eq 'backup-end') { + # Copy the dump as a LXC template + system ("cp -- $tarfile $TEMPLATE_DIR") == 0 || + die "copy tar file as a template failed"; + + # Unlink (eg hostname=buster.ipr.univ-rennes1.fr) + system ("unlink $TEMPLATE_DIR/$TEMPLATE_FILE_LINK"); + # no die cause if the previous backup exit on tarfile copy, the link might not exist + + # Link last template file to a better name + system ("find $TEMPLATE_DIR -iname 'vzdump-lxc-$vmid*.tar.*' -mmin -60 -exec ln -s {} $TEMPLATE_DIR/$TEMPLATE_FILE_LINK \\;") == 0 || + die "link template to a better name failed"; + + # Ensure to remove template older than $RETENTION_TIME + system ("find $TEMPLATE_DIR -iname 'vzdump-lxc-$vmid*.tar.*' -mtime +$RETENTION_TIME -delete ") == 0 || + die "remove oldest template failed"; + } + +} else { + + die "got unknown phase '$phase'"; + +} + +exit (0);