Server IP : 164.52.202.56 / Your IP : 216.73.216.208 Web Server : Apache System : Linux e2e-70-56.ssdcloudindia.net 4.18.0-553.27.1.el8_10.x86_64 #1 SMP Tue Nov 5 04:50:16 EST 2024 x86_64 User : rubyaromatics ( 1052) PHP Version : 7.2.34 Directory (0555) : /sbin/../sbin/ |
[ Home ] | [ Terminal ] | [ Upload File ] |
---|
#!/bin/sh # ---------------------------------------------------------------------------- # # Copyright 2020-2021, OpenNebula Project, OpenNebula Systems # # # # Licensed under the Apache License, Version 2.0 (the "License"); you may # # not use this file except in compliance with the License. You may obtain # # a copy of the License at # # # # http://www.apache.org/licenses/LICENSE-2.0 # # # # Unless required by applicable law or agreed to in writing, software # # distributed under the License is distributed on an "AS IS" BASIS, # # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # # See the License for the specific language governing permissions and # # limitations under the License. # # ---------------------------------------------------------------------------- # # This script tries to reimplement libguestfs.org's sysprep tool while trying # to support as much sysprep's operations (below) as possible. The intention # behind this tool is to provide the almost same functionality but from within # the running VM (as opposed to the original libguest's sysprep). # # On top of the original operations there are extra new operations - prefixed # with 'one-' to avoid a naming conflict. These are specific to this tool and # extend the original functionality which was too much redhat-centric. set -e ################################################################################ # globals # CMD=$(basename "$0") CMDLINE="${0} ${*}" # here are declared all sysprep operations - keep the following format: # <operation>:<default>:<comment> # for each operation there should be corresponding function: # op_<operation-with_underscores> ALL_SYSPREP_OPERATIONS=' abrt-data :1: Remove the crash data generated by ABRT backup-files :1: Remove editor backup files from the guest bash-history :1: Remove the bash history in the guest blkid-tab :1: Remove blkid tab in the guest ca-certificates :0: Remove CA certificates in the guest crash-data :1: Remove the crash data generated by kexec-tools cron-spool :1: Remove user at-jobs and cron-jobs customize :1: (NOT IMPLEMENTED) dhcp-client-state :1: Remove DHCP client leases dhcp-server-state :1: Remove DHCP server leases dovecot-data :1: Remove Dovecot (mail server) data firewall-rules :0: Remove the firewall rules flag-reconfiguration :0: Flag the system for reconfiguration fs-uuids :0: (NOT IMPLEMENTED) kerberos-data :0: Remove Kerberos data in the guest logfiles :1: Remove many log files from the guest lvm-uuids :1: (NOT IMPLEMENTED) machine-id :1: Remove the local machine ID mail-spool :1: Remove email from the local mail spool directory net-hostname :1: Remove HOSTNAME and DHCP_HOSTNAME net-hwaddr :1: Remove HWADDR (hard-coded MAC address) pacct-log :1: Remove the process accounting log files package-manager-cache :1: Remove package manager cache pam-data :1: Remove the PAM data in the guest passwd-backups :1: Remove /etc/passwd- and similar backup files puppet-data-log :1: Remove the data and log files of puppet rh-subscription-manager :1: Remove the RH subscription manager files rhn-systemid :1: Remove the RHN system ID rpm-db :1: Remove host-specific RPM database files samba-db-log :1: Remove the database and log files of Samba script :1: (NOT IMPLEMENTED) smolt-uuid :1: Remove the Smolt hardware UUID ssh-hostkeys :1: Remove the SSH host keys in the guest ssh-userdir :1: Remove ".ssh" directories in the guest sssd-db-log :1: Remove the database and log files of sssd tmp-files :1: Remove temporary files udev-persistent-net :1: Remove udev persistent net rules user-account :0: Remove the user accounts in the guest utmp :1: Remove the utmp file yum-uuid :1: Remove the yum UUID one-cleanup :1: Remove OpenNebula-owned working directories one-shell-history :1: Remove the .history file one-hostname :1: Remove hostname and fix hosts file one-resolvconf :1: Remove nameservers one-network :1: Nuke all the networking configuration one-zerofill :0: Fill the free space with zeroes and discard it one-trim :1: Trim the discarded/unused space ' ################################################################################ # functions # usage() ( cat <<EOF NAME ${CMD} - OpenNebula System Preparation Tool DESCRIPTION Tool for cleaning up the system from sensitive or undesirable information and preparing it as a base for cloning or instantiation of VMs. It tries to meet the functionality of the libguestfs sysprep tool with the exception of being able to run from within already running virtual machine. While the libguestfs sysprep operates on the poweroffed system and modifies its disk image from outside. The libguestfs approach is safer but it also means that not all images and their filesystems can be supported. The idea is to prepare image for further usage - for example like this: 1. instantiate the VM on top of a persistent image 2. create necessary changes (updates, edits, add data, files, etc.) 3. optionally switch to the single user mode (if not updating packages) 4. run '${CMD}' 5. poweroff (manually or via ${CMD}) 6. and lastly switch the image to non-persistent (!) USAGE ${CMD} --help This help ${CMD} --list-operations It will list all supported operations and mark the default one with the asterisk '*'. ${CMD} [--operations <operation-list>]... [--yes] [--strict] [--update] [--selinux-relabel] [--remove-user-accounts <user-list>] [--keep-user-accounts <user-list>] [--root-password <selector>] [--password <user:selector>] [--verbose] [--poweroff] '--operations' If no '--operations' nor one option 'yes' is used then ${CMD} will first ask you interactively if you really want to run this command and if so then it will proceed with the execution of the default set of operations. With provided '--operations' or one option 'yes' it will execute (without prompting you) the desired operations or default ones if no other is specified. '--operations' can be used more than once - the lists are then spliced together. The argument is a comma separated list of operations where amongst the actual names of the operations are also recognized special: 'default' and 'all' Their meaning is obvious. For the list of all supported operations use the '--list-operations'. Operation can also be prefixed with the '-' which will mean that this particular operation will not be executed even if it would otherwise. '--yes' Never ask for permission - run non-interactively - otherwise in some circumstances (for your own safety) program will refuse to continue until you type 'yes'. '--strict' Abort the run of this program if system is not recognized. '--update' It will update installed packages. '--selinux-relabel' It will relabel all files if the system supports SElinux. '--remove-user-accounts' It will add users to the list of accounts to be deleted. When a user is listed in both '--remove-user-accounts' and '--keep-user--accounts' then the latter has a precedence and such user is preserved. '--keep-user-accounts' When there is a request for user account deletion either via previous parameter or by 'user-account' operation - users specified here will be whitelisted and preserved in all cases. '--root-password' This parameter manages root account - selector here is one of: file:FILENAME password:PASSWORD random disabled locked locked:file:FILENAME locked:password:PASSWORD locked:random locked:disabled '--password' Same as with the root password but selector is on-top prefixed with the username, e.g.: username:locked:disabled '--verbose' Print executed commands and show their output. '--poweroff' At the end of the run of ${CMD} - poweroff the system. Although no such parameter is in virt-sysprep (because it operates on the image of an already poweroffed system) - it makes sense here. EXAMPLES ${CMD} Run all default operations but only if explicitly replied 'YES' ${CMD} --operations default,-cron-spool Run all default operations EXCEPT the cron-spool ${CMD} --operations machine-id,ca-certificates Run only these two operations ${CMD} --yes --poweroff --root-password locked:disabled Run all default operations, delete and lock root password and poweroff the system immediately after - no questions asked ${CMD} --yes --strict --operations all,-one-zerofill Run all the available operations *EXCEPT* the 'one-zerofill' and without asking but abort promptly if system is not recognized. BEWARE: 'one-zerofill' operation will fill up the free space of the VM with zeroes to claim the unused space. By doing this the disk can be effectively sparsified by the trim command in the next step. The issue is that zerofill must basically claim the size of the whole disk - if the definition of your disk image does not have discard='unmap' (on QEMU/KVM for example) and the host system does not support it then you will end up with *MAXIMIZED* disk image size instead of the shrinked size... EOF ) print_help() ( printf "${SETCOLOR_ON_COMMAND}[!] try help: %s${SETCOLOR_OFF}\n" \ "${CMD} --help" >&2 ) msg() ( printf "${SETCOLOR_ON_HIGHLIGHT2}[%s]${SETCOLOR_OFF}: ${*}\n" \ "$CMD" ) err() ( { printf "${SETCOLOR_ON_HIGHLIGHT2}[%s]${SETCOLOR_OFF}" \ "$CMD" printf " ${SETCOLOR_ON_FAILURE}%s${SETCOLOR_OFF}\n" \ "[!] ERROR: ${*}" } >&2 ) warn() ( { printf "${SETCOLOR_ON_HIGHLIGHT2}[%s]${SETCOLOR_OFF}" \ "$CMD" printf " ${SETCOLOR_ON_FAILURE}%s${SETCOLOR_OFF}\n" \ "[!] WARNING: ${*}" } >&2 ) # arg(s): <command> <arg>... run_cmd() { if is_one_option 'verbose' ; then # verbose command execution printf "${SETCOLOR_ON_HIGHLIGHT2}[%s]${SETCOLOR_OFF}:" \ "$CMD" printf " ${SETCOLOR_ON_COMMAND}%s${SETCOLOR_OFF}\n" \ "${*}" fi if ! "$@" && [ "$IGNORE_ERROR" != 'yes' ] ; then err "Command failed - ABORT" exit 1 fi } # trivial test of awk command capabilities and compliance - it will output # either YES or NO is_awk_correct() ( _awk_result=$(echo 'abcABC xyz123.' | awk ' { sub(/[[:upper:]]+[[:space:]]+[[:alnum:]]+/, ""); print; }') if [ "${_awk_result}" = 'abc.' ] ; then echo 'YES' else echo 'NO' fi ) # arg: [normal|summary] # if used with argument 'summary' then it will print out summary of supported # operations - otherwise it will output parseable: <op>:<default> parse_sysprep_operations() ( echo "$ALL_SYSPREP_OPERATIONS" | awk -v output="${1:-normal}" ' BEGIN { FS = ":"; count=0; } { if ($0 == "") next; op = $1; sub(/^[ ]*/, "", op); sub(/[ ]*$/, "", op); default_op = $2; sub(/^[ ]*/, "", default_op); sub(/[ ]*$/, "", default_op); comment = $3; sub(/^[ ]*/, "", comment); sub(/[ ]*$/, "", comment); count++; ops[count] = op; ops_comment[op] = comment; ops_default[op] = default_op; } END { if (output == "normal"){ for (i = 1; i <= count; i++) { op = ops[i]; printf("%s:%s\n", op, ops_default[op]); } } else if (output == "summary") { max_length = 0; for (i = 1; i <= count; i++) { if (length(ops[i]) > max_length) max_length = length(ops[i]); } for (i = 1; i <= count; i++) { op = ops[i]; if (ops_default[op] == "1") asterisk = "*"; else asterisk = " "; # this sadly is not supported in busybox... # printf("%s %-*s %s\n", asterisk, max_length, op, ops_comment[op]); # busybox workaround extra_spaces_count = max_length - length(op); extra_spaces = ""; for (s = 1; s <= extra_spaces_count; s++) extra_spaces = extra_spaces " "; printf("%s %s%s %s\n", asterisk, op, extra_spaces, ops_comment[op]); } } } ' ) list_operations() ( printf "LIST OF ALL SUPPORTED OPERATIONS:\n\n" printf "(asterisk '*' designates a default operation)\n\n" parse_sysprep_operations summary ) ask_yes() ( _reply='' while [ -z "$_reply" ] ; do printf "%s (${SETCOLOR_ON_COMMAND}%s${SETCOLOR_OFF})? " \ "Do you really want to continue" \ "'y/yes/Y/YES'" read -r _reply case "$_reply" in y|Y|yes|YES) return 0 ;; esac done return 1 ) ask_to_run_sysprep() { # shellcheck disable=SC2059 printf "${SETCOLOR_ON_FAILURE}" printf "[!] BEWARE: This will erase some system data!\n" printf "[!] If you are not certain what this program does - try help:\n" printf " %s --help\n\n" "${CMD}" # shellcheck disable=SC2059 printf "${SETCOLOR_OFF}" if ! is_one_option 'yes' && ! ask_yes ; then msg "ABORTED" exit 0 fi } ask_to_enter_single_user_mode() { warn "You are about to enter the single user mode - this program will be terminated!" printf "[!] NOTE: You must invoke this program again once you reach the single user mode:\n" printf " %s\n\n" "${CMDLINE}" if ! is_one_option 'yes' && ! ask_yes ; then msg "ABORTED" exit 0 fi } ask_to_poweroff() { warn "You are about to poweroff this system!" if ! is_one_option 'yes' && ! ask_yes ; then msg "ABORTED" exit 0 fi } ask_to_proceed_on_unsupported_system() { if ! is_one_option 'yes' && ! ask_yes ; then msg "ABORTED" exit 0 fi warn "We will continue on the best-effort basis..." } # arg: all|default|<op> # it will return (based on argument): # list of all operations # list of all default operations # operation if exists # empty string if operation does not exist get_operations() ( case "$1" in all) parse_sysprep_operations normal | cut -d: -f1 ;; default) parse_sysprep_operations normal | sed -n 's/^\(.*\):1$/\1/p' ;; *) parse_sysprep_operations normal | sed -n "s/^\(${1}\):[0-1]\$/\1/p" ;; esac ) sanity_check() { # test the sanity of an awk implementation _ONE_VALID_AWK=$(is_awk_correct) export _ONE_VALID_AWK if [ "${_ONE_VALID_AWK}" = 'NO' ] ; then warn "The currently used awk command does not support POSIX:" printf "\n%s\n\n" "$(awk -W version 2>&1 || true)" printf "[!] NOTE: Install an alternative (e.g. gawk) or some operations may be skipped!\n\n" fi } # this will try to guess the system it is running on and if it is supported by # this script syscheck() { # detect the OS if [ -f /etc/os-release ] ; then ID= # shellcheck disable=SC1091 . /etc/os-release _ONE_OS_ID=$(echo "$ID" | tr '[:upper:]' '[:lower:]') # check for legacy RHEL/CentOS 6 elif [ -f /etc/centos-release ]; then _ONE_OS_ID='centos' elif [ -f /etc/redhat-release ]; then _ONE_OS_ID='rhel' else _ONE_OS_ID=$(uname | tr '[:upper:]' '[:lower:]') fi # return success if listed here: # # (I am breaking these into more cases just to avoid having long lines) case "$_ONE_OS_ID" in alpine|altlinux) return 0 ;; debian|ubuntu|devuan) return 0 ;; fedora|centos|rhel|almalinux|ol|rocky|amzn) return 0 ;; opensuse*|sles|sled) return 0 ;; freebsd) return 0 ;; esac # [!] we failed the check - this system is not supported... # print the warning warn "This system ('${_ONE_OS_ID:-UNKNOWN}') is not supported!" # possibly abort if is_one_option 'strict' ; then err "System check failed while 'strict' option used - ABORT" exit 1 else ask_to_proceed_on_unsupported_system return 0 fi return 1 } # it will either switch to the single user mode (if possible) or it will return # immediately when it is already in one enter_single_mode() { # are we already switched? case "$_ONE_OS_ID" in alpine) _runlevel=$(rc-status -r) if [ "$_runlevel" = 'single' ] ; then return 0 fi ;; debian|ubuntu|devuan|fedora|centos|rhel|almalinux|ol|rocky|altlinux|opensuse*) _runlevel=$(runlevel | cut -d" " -f2) case "$_runlevel" in 1|S) return 0 ;; esac ;; freebsd) warn "FreeBSD has no (clever) means of detecting the current runlevel - SKIP" printf "[!] NOTE: You can enter the single user mode prior to running this program:\n" printf " shutdown now\n\n" if ! is_one_option 'yes' && ! ask_yes ; then msg "ABORTED" exit 0 fi ;; *) warn "Unable to detect the runlevel on this system ('${_ONE_OS_ID:-UNKNOWN}')" printf "[!] NOTE: You can abort this script and switch manually if you wish...\n\n" if ! is_one_option 'yes' && ! ask_yes ; then msg "ABORTED" exit 0 fi ;; esac # we are not in single user mode - so we will attempt to enter it... case "$_ONE_OS_ID" in alpine) ask_to_enter_single_user_mode msg "Entering single user mode..." rc single ;; debian|ubuntu|devuan|fedora|centos|rhel|almalinux|ol|rocky|altlinux|opensuse*) ask_to_enter_single_user_mode msg "Entering single user mode..." telinit 1 ;; freebsd) : # TODO: for future reference: #shutdown now ## enable rw on rootfs again #/sbin/mount -urw / ;; esac } # arg: <passwd-selector-list> # return: user:locked:disabled:file:password:random:value... parse_password_arguments() { _raw_password_list=$(echo "$1" | tr ',' ' ') _parsed_password_list= # selector should have format: # <user>:[locked:]disabled|file:<filename>|password:<passwd>|random for _selector in ${_raw_password_list} ; do _username=$(echo "$_selector" | cut -d":" -f1) _locked_or_atr=$(echo "$_selector" | cut -d":" -f2) _atr_or_value=$(echo "$_selector" | cut -d":" -f3) _value=$(echo "$_selector" | cut -d":" -f4) _error=$(echo "$_selector" | cut -d":" -f5) # simple check to catch extra fields if [ -n "$_error" ] ; then err "Wrong password selector: ${_selector} - ABORT" exit 1 fi # selector attributes _locked= _disabled= _file= _password= _random= case "$_locked_or_atr" in locked) _locked=yes ;; disabled) _disabled=yes ;; file) _file=yes ;; password) _password=yes ;; random) _random=yes ;; *) err "Wrong password selector: ${_selector} - ABORT" exit 1 ;; esac case "$_atr_or_value" in '') # is missing a value? if [ -n "${_file}${_password}" ] ; then # selector is not complete err "Wrong password selector: ${_selector} - ABORT" exit 1 fi ;; disabled|file|password|random) # it can be non-locked attribute or a value if [ -n "${_locked}" ] ; then # locked was set - so this is attribute eval "_${_atr_or_value}=yes" elif [ -z "${_value}${_disabled}${_random}" ] ; then # it must be value _value="$_atr_or_value" else err "Wrong password selector: ${_selector} - ABORT" exit 1 fi ;; *) # it must be value or error if [ -z "${_locked}${_value}${_disabled}${_random}" ] ; then _value="$_atr_or_value" else err "Wrong password selector: ${_selector} - ABORT" exit 1 fi ;; esac # we should have all the pieces... _passwd_arg="${_username}:${_locked}:${_disabled}:${_file}:${_password}:${_random}:${_value}" _parsed_password_list="${_parsed_password_list} ${_passwd_arg}" done echo "$_parsed_password_list" } # arg: <length> # shellcheck disable=SC2120 gen_password() { pw_length="${1:-16}" new_pw='' while true ; do if command -v pwgen >/dev/null ; then new_pw=$(pwgen -s "${pw_length}" 1) break elif command -v openssl >/dev/null ; then # shellcheck disable=SC2086 new_pw="${new_pw}$(openssl rand -base64 ${pw_length} | tr -dc '[:alnum:]')" else new_pw="${new_pw}$(head /dev/urandom | tr -dc '[:alnum:]')" fi # shellcheck disable=SC2000 # shellcheck disable=SC2086 [ "$(echo $new_pw | wc -c)" -ge "$pw_length" ] && break done echo "$new_pw" | cut -c1-"${pw_length}" } # arg: <user:locked:disabled:file:password:random:value...> set_passwords() { for _selector in ${1} ; do _username=$(echo "$_selector" | cut -d":" -f1) _locked=$(echo "$_selector" | cut -d":" -f2) _disabled=$(echo "$_selector" | cut -d":" -f3) _file=$(echo "$_selector" | cut -d":" -f4) _password=$(echo "$_selector" | cut -d":" -f5) _random=$(echo "$_selector" | cut -d":" -f6) _value=$(echo "$_selector" | cut -d":" -f7) # skip if user does not exist _uid=$(id -u "$_username" 2>/dev/null || true) if [ -z "$_uid" ] ; then msg "Password change: the user '${_username}' does not exist" continue fi case "$(uname | tr '[:upper:]' '[:lower:]')" in linux) if [ -n "$_disabled" ] ; then msg "Password delete: ${_username}" run_cmd passwd -d "$_username" elif [ -n "$_file" ] ; then if [ -f "$_value" ] ; then msg "Password change: ${_username}" _newpasswd=$(sed -n 1p "$_value") if command -v chpasswd > /dev/null ; then # busybox echo "${_username}:${_newpasswd}" | run_cmd chpasswd else echo "${_newpasswd}" | run_cmd passwd --stdin "$_username" fi else err "Password file '${_value}' does not exist - ABORT" exit 1 fi elif [ -n "$_password" ] ; then msg "Password change: ${_username}" if command -v chpasswd > /dev/null ; then # busybox echo "${_username}:${_value}" | run_cmd chpasswd else echo "${_value}" | run_cmd passwd --stdin "$_username" fi elif [ -n "$_random" ] ; then msg "Password change: ${_username}" _newpasswd=$(gen_password) if command -v chpasswd > /dev/null ; then # busybox echo "${_username}:${_newpasswd}" | run_cmd chpasswd else echo "${_newpasswd}" | run_cmd passwd --stdin "$_username" fi fi if [ -n "$_locked" ] ; then msg "Password lock: ${_username}" run_cmd passwd -l "$_username" fi ;; freebsd) if [ -n "$_disabled" ] ; then msg "Password delete: ${_username}" run_cmd pw usermod -n "$_username" -h - elif [ -n "$_file" ] ; then if [ -f "$_value" ] ; then msg "Password change: ${_username}" sed -n 1p "$_value" | \ run_cmd pw usermod -n "$_username" -h 0 else err "Password file '${_value}' does not exist - ABORT" exit 1 fi elif [ -n "$_password" ] ; then msg "Password change: ${_username}" echo "$_value" | \ run_cmd pw usermod -n "$_username" -h 0 elif [ -n "$_random" ] ; then msg "Password change: ${_username}" _newpasswd=$(gen_password) echo "$_newpasswd" | \ run_cmd pw usermod -n "$_username" -h 0 fi if [ -n "$_locked" ] ; then msg "Password lock: ${_username}" IGNORE_ERROR=yes run_cmd pw lock "$_username" IGNORE_ERROR= fi ;; *) warn "Password change: this system ('$(uname)') is not supported" ;; esac done } run_prep() { # run compatibiltity/support check and abort here if 'strict' is used syscheck # --update ? if is_one_option 'update' ; then msg "Update the system..." if command -v apk >/dev/null ; then run_cmd apk --update-cache upgrade elif command -v apt-get >/dev/null ; then DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND run_cmd apt-get -qy update run_cmd apt-get -qy \ -o Dpkg::Options::="--force-confdef" \ -o DPkg::Options::="--force-confold" \ upgrade run_cmd apt-get -qy autoremove elif command -v dnf >/dev/null ; then run_cmd dnf -y --best upgrade elif command -v yum >/dev/null ; then run_cmd yum -y --obsoletes update elif command -v zypper >/dev/null ; then run_cmd zypper --non-interactive update --auto-agree-with-licenses elif command -v pkg >/dev/null ; then # TODO: FreeBSD returns error when no update is available... run_cmd freebsd-update --not-running-from-cron fetch || true run_cmd freebsd-update --not-running-from-cron install || true else warn "No package manager found - SKIP" fi fi # --remove-user-accounts ? if [ -n "$ARG_USERS_REMOVE" ] ; then remove_arbitrary_users "$ARG_USERS_REMOVE" "$ARG_USERS_KEEP" fi # --root-password|--password ? if [ -n "$ARG_PASSWORD" ] ; then set_passwords "$ARG_PASSWORD" fi # TODO: this requires too much fiddling for just saving a user one command # if the single runlevel is requested then try to switch to it...or maybe # we already did that and we just want to continue... #if is_one_option 'single' ; then # enter_single_mode #fi } run_last() { # --selinux-relabel ? if is_one_option 'selinux-relabel' ; then msg "Try to relabel SELinux context..." # is this SELinux system? if [ "$(uname | tr '[:upper:]' '[:lower:]')" = 'linux' ] && \ command -v restorecon >/dev/null ; then if command -v fixfiles >/dev/null ; then run_cmd fixfiles -f relabel else run_cmd touch /.autorelabel fi else warn "This not a SELinux enabled system - SKIP" fi fi # this does not hurt - sync to disk if command -v sync >/dev/null ; then msg "Running 'sync'..." sync fi # --poweroff? if is_one_option 'poweroff' ; then # firstly ask if not 'yes' ask_to_poweroff msg "We will try to turn off the machine..." case "$_ONE_OS_ID" in freebsd) run_cmd poweroff ;; *) if command -v poweroff >/dev/null ; then run_cmd poweroff elif command -v shutdown >/dev/null ; then run_cmd shutdown -p now elif command -v halt >/dev/null ; then run_cmd halt -p else err "Poweroff requested but no relevant command was found!" fi ;; esac fi } # arg: <list-of-operations> run_ops() { _all_ops=$(echo "$1" | tr ',' ' ') _whitelist= _blacklist= for _op in ${_all_ops} ; do # is it blacklisted operation? _black_op=$(echo "$_op" | sed -n 's/^-\(.*\)/\1/p') if [ -n "$_black_op" ] ; then # add excluded operation(s) to the blacklist _ops=$(get_operations "$_black_op") if [ -n "$_ops" ] ; then _blacklist="${_blacklist} ${_ops}" else err "Unsupported operation expr.: ${_op}" exit 1 fi else # extend the whitelist for requested operation(s) _ops=$(get_operations "$_op") if [ -n "$_ops" ] ; then _whitelist="${_whitelist} ${_ops}" else err "Unsupported operation expr.: ${_op}" exit 1 fi fi done # deduplicate and sort the both lists _whitelist=$(echo "$_whitelist" | tr ' ' '\n' | sort -u) _blacklist=$(echo "$_blacklist" | tr ' ' '\n' | sort -u) # filter out blacklisted operations and execute only the whitelisted ones _ops= _max_oplength=0 # loop over the ALL available operations to honor the original order for _op in $(get_operations all) ; do # NOTE: on FreeBSD newlines are not preserved in the variable which is # strange because POSIX says that only trailing newlines are removed... # if the operation is NOT whitelisted/requested then skip it if ! echo "$_whitelist" | tr ' ' '\n' | grep -q "^${_op}\$" ; then continue fi # if the operation is NOT blacklisted then add it to the final list if ! echo "$_blacklist" | tr ' ' '\n' | grep -q "^${_op}\$" ; then # find the longest operation name if [ "${#_op}" -gt "$_max_oplength" ] ; then _max_oplength="${#_op}" fi _ops="${_ops} ${_op}" fi done # export arguments into operation subshells export ARG_ONE_OPTION_LIST export ARG_OPS_LIST export ARG_PASSWORD export ARG_USERS_KEEP export ARG_USERS_REMOVE # execute requested operations one by one for _op in ${_ops} ; do _op_func=$(echo "$_op" | tr '-' '_') if is_one_option 'verbose' ; then # run verbosely printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}:" \ "Run operation" printf " ${SETCOLOR_ON_COMMAND}%s${SETCOLOR_OFF}\n" \ "${_op}" # if-wrap if eval "op_${_op_func}" ; then _op_func_return_code=$? else _op_func_return_code=$? fi case "${_op_func_return_code}" in 0) printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}" \ "Result" printf " ... ${SETCOLOR_ON_OK}%s${SETCOLOR_OFF}\n\n" \ "OK" ;; 1) printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}" \ "Result" printf " ... ${SETCOLOR_ON_FAILURE}%s${SETCOLOR_OFF}\n\n" \ "FAILED" ;; 2) printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}" \ "Result" printf " ... ${SETCOLOR_ON_FAILURE}%s${SETCOLOR_OFF}\n\n" \ "SKIPPED" ;; esac else # run non-verbosely printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}:" \ "Run operation" printf " ${SETCOLOR_ON_COMMAND}%-*s${SETCOLOR_OFF} ... " \ "$_max_oplength" \ "${_op}" # if-wrap if _op_func_output=$(eval "op_${_op_func}" 2>&1) ; then _op_func_return_code=$? else _op_func_return_code=$? fi case "${_op_func_return_code}" in 0) # shellcheck disable=SC2059 printf "${SETCOLOR_ON_OK}OK${SETCOLOR_OFF}\n" ;; 1) # shellcheck disable=SC2059 printf "${SETCOLOR_ON_FAILURE}FAILED${SETCOLOR_OFF}\n" echo "$_op_func_output" ;; 2) # shellcheck disable=SC2059 printf "${SETCOLOR_ON_FAILURE}SKIPPED${SETCOLOR_OFF}\n" echo "$_op_func_output" ;; esac fi done } # arg: <option-list> # this function initialize a global env. variable _ONE_OPTIONS based on the # recognized options and force an abort when an unknown option is found # # NOTE: this is left as it is for when the need for '--one-options' rise again # - that is why the options are replicated here... # # _ONE_OPTIONS is then used inside the is_one_option function for convenience parse_one_options() { # comb the list _all_options=$(echo "$1" | \ sed -e 's/,/ /g' -e 's/[[:space:]]\+/\n/g' -e '/^$/d' | \ sort -u) # leave empty _ONE_OPTIONS= _abort= for _option in ${_all_options} ; do case "$_option" in yes|strict|verbose|update|poweroff|selinux-relabel) _ONE_OPTIONS="${_ONE_OPTIONS} ${_option}" ;; *) err "Unknown option: ${_option}" _abort=yes ;; esac done # there were found unknown options - abort if [ -n "$_abort" ] ; then print_help exit 1 fi } # arg: <option> # it will simply check if option from argument was set (inside _ONE_OPTIONS) is_one_option() ( for _option in ${_ONE_OPTIONS} ; do if [ "$1" = "$_option" ] ; then return 0 fi done return 1 ) # inspired here: # https://unix.stackexchange.com/questions/485070/all-round-100-portable-terminal-color-support-check # and modified for alpine's busybox (which supports colors) is_terminal_colorful() { { command -v tput && { tput setaf || tput AF ; } } >/dev/null 2>&1 # shellcheck disable=SC2181 if [ $? -eq 0 ] ; then return 0 fi # naive workaround for systems without ncurses test "$TERM" = 'xterm-256color' return $? } # args: <remove-user-list> <keep-user-list> # if a user is in both lists then preserving will take precedence # TODO: ERRROR FreeBSD does not save newlines in vars - echo | grep does not work!!!!!!!!!ยง remove_arbitrary_users() { # comb the lists _remove_list=$(echo "$1" | \ sed -e 's/,/ /g' -e 's/[[:space:]]\+/\n/g' -e '/^$/d' | \ sort -u) _keep_list=$(echo "$2" | \ sed -e 's/,/ /g' -e 's/[[:space:]]\+/\n/g' -e '/^$/d' | \ sort -u) # finalize the remove list by removing the preserved users _users= for _user in ${_remove_list} ; do # NOTE: on FreeBSD newlines are not preserved in the variable which is # strange because POSIX says that only trailing newlines are removed... if ! echo "$_keep_list" | tr ' ' '\n' | grep -q "^${_user}\$" ; then # user is not preserved so we can delete it if exists _uid=$(id -u "$_user" 2>/dev/null || true) if [ -n "$_uid" ] ; then _users="${_users} ${_user}:${_uid}" else msg "Removing user '${_user}': not present on the system" fi fi done case "$(uname | tr '[:upper:]' '[:lower:]')" in linux) for _user in ${_users} ; do _username=$(echo "$_user" | cut -d":" -f1) _uid=$(echo "$_user" | cut -d":" -f2) msg "Removing user '${_username}'..." # keep deluser first: alpine has both commands but does not # have the --selinux-user option for userdel... if command -v deluser >/dev/null ; then run_cmd deluser --remove-home "$_username" elif command -v userdel >/dev/null ; then run_cmd userdel --remove --selinux-user "$_username" fi # wipe-out all deleted user's files on the system IGNORE_ERROR=yes run_cmd find / -depth -user "$_uid" -delete IGNORE_ERROR= done ;; freebsd) for _user in ${_users} ; do _username=$(echo "$_user" | cut -d":" -f1) _uid=$(echo "$_user" | cut -d":" -f2) msg "Removing user '${_username}'..." run_cmd pw userdel "$_username" -r # wipe-out all deleted user's files on the system IGNORE_ERROR=yes run_cmd find / -depth -user "$_uid" -delete IGNORE_ERROR= done ;; *) warn "Removing users: this system ('$(uname)') is not supported" ;; esac } ################################################################################ # sysprep operations # # reimplemented operations op_abrt_data() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -rvf /var/spool/abrt/* ) op_backup_files() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op find / \( -name '*.bak' -o -name '*~' \) -delete || true ) op_bash_history() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf /home/*/.bash_history /root/.bash_history ) op_blkid_tab() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /var/run/blkid.tab \ /var/run/blkid.tab.old \ /etc/blkid/blkid.tab \ /etc/blkid/blkid.tab.old \ /etc/blkid.tab \ /etc/blkid.tab.old \ /dev/.blkid.tab \ /dev/.blkid.tab.old \ ; ) op_ca_certificates() ( run_op() { echo "+ ${*}" ; "$@" ; } for f in \ /etc/pki/CA/certs/*.crt \ /etc/pki/CA/crl/*.crt \ /etc/pki/CA/newcerts/*.crt \ /etc/pki/CA/private/*.key \ /etc/pki/tls/private/*.key \ /etc/pki/tls/certs/*.crt \ ; do case "$f" in /etc/pki/tls/certs/ca-bundle.crt) # skip :;; /etc/pki/tls/certs/ca-bundle.trust.crt) # skip :;; *) # all else delete run_op rm -vf "$f" ;; esac done ) op_crash_data() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -rvf /var/crash/* /var/log/dump/* ) op_cron_spool() ( run_op() { echo "+ ${*}" ; "$@" ; } for d in \ /var/spool/cron/ \ /var/spool/cron/atjobs/ \ /var/spool/atjobs/ \ /var/spool/atspool/ \ /var/spool/at/ \ ; do if [ -d "$d" ] ; then run_op find "$d" -type f -not -name .SEQ -delete find "$d" -type f -name .SEQ -print | while read -r _seq_file ; do echo "+ truncate ${_seq_file}" cat /dev/null > "$_seq_file" done fi done ) # TODO: should we support customize? op_customize() ( echo NOOP ) op_dhcp_client_state() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -rvf /var/lib/dhclient/* /var/lib/dhcp/* ) op_dhcp_server_state() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -rvf /var/lib/dhcpd/* ) op_dovecot_data() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf /var/lib/dovecot/* ) op_firewall_rules() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /etc/sysconfig/iptables \ /etc/firewalld/services/* \ /etc/firewalld/zones/* \ ; ) op_flag_reconfiguration() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op touch /.unconfigured ) # TODO: is this possible on mounted fs? op_fs_uuids() ( echo NOOP ) op_kerberos_data() ( run_op() { echo "+ ${*}" ; "$@" ; } for f in /var/kerberos/krb5kdc/* ; do case "$f" in /var/kerberos/krb5kdc/kadm5.acl) # skip :;; /var/kerberos/krb5kdc/kdc.conf) # skip :;; *) # all else delete run_op rm -vf "$f" ;; esac done ) # TODO: can this be improved? Stopping all services, syslog etc.? # TODO: can the list be extended? This is copy-pasted from the latest libguestfs op_logfiles() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -rvf \ /var/log/*.log* \ /var/log/audit/* \ /var/log/btmp* \ /var/log/cron* \ /var/log/dmesg* \ /var/log/lastlog* \ /var/log/maillog* \ /var/log/mail/* \ /var/log/messages* \ /var/log/secure* \ /var/log/spooler* \ /var/log/tallylog* \ /var/log/wtmp* \ /var/log/apache2/*_log \ /var/log/apache2/*_log-* \ /var/log/ntp \ /var/log/tuned/tuned.log \ /var/log/debug* \ /var/log/syslog* \ /var/log/faillog* \ /var/log/firewalld* \ /var/log/grubby* \ /var/log/xferlog* \ /var/log/BackupPC/LOG \ /var/log/ceph/*.log \ /var/log/chrony/*.log \ /var/log/cups/*_log* \ /var/log/glusterfs/*glusterd.vol.log \ /var/log/glusterfs/glusterfs.log \ /var/log/httpd/*log \ /var/log/jetty/jetty-console.log \ /var/log/libvirt/libxl/*.log \ /var/log/libvirt/libvirtd.log \ /var/log/libvirt/lxc/*.log \ /var/log/libvirt/qemu/*.log \ /var/log/libvirt/uml/*.log \ /var/named/data/named.run \ /var/log/ppp/connect-errors \ /var/log/setroubleshoot/*.log \ /var/log/squid/*.log \ /var/lib/logrotate.status \ /root/install.log \ /root/install.log.syslog \ /root/anaconda-ks.cfg \ /root/anaconda-post.log \ /root/initial-setup-ks.cfg \ /root/original-ks.cfg \ /var/log/anaconda.syslog \ /var/log/anaconda/* \ /var/log/installer/* \ /var/cache/gdm/* \ /var/lib/AccountService/users/* \ /var/lib/fprint/* \ /var/cache/fontconfig/* \ /var/cache/man/* \ /var/log/sa/* \ /var/log/gdm/* \ /var/log/lightdm/* \ /var/log/ntpstats/* \ /etc/Pegasus/*.cnf \ /etc/Pegasus/*.crt \ /etc/Pegasus/*.csr \ /etc/Pegasus/*.pem \ /etc/Pegasus/*.srl \ /var/log/rhsm/* \ /var/log/journal/* \ /var/log/aptitude* \ /var/log/apt/* \ /var/log/exim4/* \ /var/log/ConsoleKit/* \ /var/log/landscape/* \ /var/log/unattended-upgrades/* \ ; ) # TODO: can ths be implemented? op_lvm_uuids() ( echo NOOP ) op_machine_id() ( run_op() { echo "+ ${*}" ; "$@" ; } for f in /etc/machine-id /var/lib/dbus/machine-id ; do if [ -f "$f" ] ; then echo "+ truncate ${f}" cat /dev/null > "$f" fi done ) op_mail_spool() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -rvf /var/spool/mail/* /var/mail/* ) # this is redhat-centric but fixed in one-network and one-hostname op_net_hostname() ( run_op() { echo "+ ${*}" ; "$@" ; } for f in /etc/sysconfig/network-scripts/ifcfg-* ; do if [ -f "$f" ] ; then run_op sed -i -e '/^HOSTNAME=/d' -e '/^DHCP_HOSTNAME=/d' "$f" fi done ) # this is redhat-centric but fixed in one-network op_net_hwaddr() ( run_op() { echo "+ ${*}" ; "$@" ; } for f in /etc/sysconfig/network-scripts/ifcfg-* ; do if [ -f "$f" ] ; then run_op sed -i -e '/^HWADDR=/d' "$f" fi done ) op_pacct_log() ( run_op() { echo "+ ${*}" ; "$@" ; } if [ -f /var/account/pacct ] ; then run_op rm -vf /var/account/pacct* run_op touch /var/account/pacct fi if [ -f /var/log/account/pacct ] ; then run_op rm -vf /var/log/account/pacct* run_op touch /var/log/account/pacct fi ) # this one is extended from libguestfs sysprep version op_package_manager_cache() ( run_op() { echo "+ ${*}" ; "$@" ; } if command -v apk >/dev/null ; then run_op apk cache clean elif command -v apt-get >/dev/null ; then run_op apt-get clean elif command -v apt >/dev/null ; then run_op apt clean elif command -v dnf >/dev/null ; then run_op dnf clean all --enablerepo=\* elif command -v yum >/dev/null ; then run_op yum clean all --enablerepo=\* elif command -v zypper >/dev/null ; then run_op zypper clean --all elif command -v pkg >/dev/null ; then run_op pkg clean --all --yes fi for d in \ /var/cache/apt/archives/ \ /var/cache/dnf/ \ /var/cache/yum/ \ /var/cache/zypp* \ /var/cache/apk/* \ ; do if [ -d "$d" ] ; then run_op find "$d" -type f -delete fi done ) op_pam_data() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /var/run/console/* \ /var/run/faillock/* \ /var/run/sepermit/* \ ; ) op_passwd_backups() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /etc/group- \ /etc/gshadow- \ /etc/passwd- \ /etc/shadow- \ /etc/subuid- \ /etc/subgid- \ ; ) op_puppet_data_log() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /var/log/puppet/* \ /var/lib/puppet/*/* \ /var/lib/puppet/*/*/* \ ; ) op_rhn_systemid() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /etc/sysconfig/rhn/systemid \ /etc/sysconfig/rhn/osad-auth.conf \ ; ) op_rh_subscription_manager() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -rvf /etc/pki/consumer/* /etc/pki/entitlement/* ) op_rpm_db() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf /var/lib/rpm/__db.* ) op_samba_db_log() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /var/log/samba/old/* \ /var/log/samba/* \ /var/lib/samba/*/* \ /var/lib/samba/* \ ; ) op_script() ( echo NOOP ) op_smolt_uuid() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /etc/sysconfig/hw-uuid \ /etc/smolt/uuid \ /etc/smolt/hw-uuid \ ; ) op_ssh_hostkeys() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf /etc/ssh/*_host_* ) op_ssh_userdir() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -rvf /home/*/.ssh /root/.ssh ) op_sssd_db_log() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /var/log/sssd/* \ /var/lib/sss/db/* \ ; ) op_tmp_files() ( run_op() { echo "+ ${*}" ; "$@" ; } for d in /tmp/ /var/tmp/ ; do if [ -d "$d" ] ; then run_op find "$d" -maxdepth 1 -mindepth 1 -exec rm -rvf '{}' \; fi done ) op_udev_persistent_net() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf /etc/udev/rules.d/70-persistent-net.rules ) # it honors the --keep-user-accounts list op_user_account() ( run_op() { echo "+ ${*}" ; "$@" ; } if [ "${_ONE_VALID_AWK}" = 'NO' ] ; then printf "[!] This test is skipped due to the incompatible awk implementation!\n" return 2 fi # comb the list _keep_list=$(echo "$ARG_USERS_KEEP" | \ sed -e 's/,/ /g' -e 's/[[:space:]]\+/\n/g' -e '/^$/d' | \ sort -u) case "$(uname | tr '[:upper:]' '[:lower:]')" in linux) if [ -f /etc/login.defs ] ; then _uid_min=$(</etc/login.defs \ awk '/^[[:space:]]*UID_MIN[[:space:]]/ {print $2}') _uid_max=$(</etc/login.defs \ awk '/^[[:space:]]*UID_MAX[[:space:]]/ {print $2}') # setup the default values according to manual pages for login.defs if [ -z "$_uid_min" ] ; then _uid_min=1000 fi if [ -z "$_uid_max" ] ; then _uid_max=60000 fi _users=$(getent passwd | \ awk -v uid_min="$_uid_min" -v uid_max="$_uid_max" \ ' BEGIN { FS = ":"; } { if (($3 >= uid_min) && ($3 <= uid_max)) printf("%s:%s\n", $1, $3); }' | sort -u) for _user in ${_users} ; do _username=$(echo "$_user" | cut -d":" -f1) _uid=$(echo "$_user" | cut -d":" -f2) # is this user preserved? # NOTE: on FreeBSD newlines are not preserved in the variable # which is strange because POSIX says that only trailing # newlines are removed...just to be on the safe side I will # reintroduce them on the linux too... if echo "$_keep_list" | tr ' ' '\n' | grep -q "^${_username}\$" ; then continue fi # keep deluser first: alpine has both commands but does not # have the --selinux-user option for userdel... if command -v deluser >/dev/null ; then run_op deluser --remove-home "$_username" elif command -v userdel >/dev/null ; then run_op userdel --remove --selinux-user "$_username" fi # wipe-out all deleted user's files on the system run_op find / -depth -user "$_uid" -delete || true done else err "No /etc/login.defs - we cannot guess the MIN-MAX of UIDs/GIDs" return 1 fi ;; freebsd) if [ -f /etc/pw.conf ] ; then _uid_min=$(</etc/pw.conf \ awk '/^[[:space:]]*minuid[[:space:]]/ {print $2}') _uid_max=$(</etc/pw.conf \ awk '/^[[:space:]]*maxuid[[:space:]]/ {print $2}') fi # setup the default values according to manual pages for pw.conf if [ -z "$_uid_min" ] ; then _uid_min=1000 fi if [ -z "$_uid_max" ] ; then _uid_max=32000 fi _users=$(getent passwd | \ awk -v uid_min="$_uid_min" -v uid_max="$_uid_max" \ ' BEGIN { FS = ":"; } { if (($3 >= uid_min) && ($3 <= uid_max)) printf("%s:%s\n", $1, $3); }' | sort -u) for _user in ${_users} ; do _username=$(echo "$_user" | cut -d":" -f1) _uid=$(echo "$_user" | cut -d":" -f2) # is this user preserved? # NOTE: on FreeBSD newlines are not preserved in the variable # which is strange because POSIX says that only trailing # newlines are removed... if echo "$_keep_list" | tr ' ' '\n' | grep -q "^${_username}\$" ; then continue fi run_op pw userdel "$_username" -r # wipe-out all deleted user's files on the system run_op find / -depth -user "$_uid" -delete || true done ;; *) warn "Removing users: this system ('$(uname)') is not supported" ;; esac ) op_utmp() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf /var/run/utmp ) op_yum_uuid() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf /var/lib/yum/uuid ) # extra one operations op_one_cleanup() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vrf \ /var/lib/one-context/ \ ; ) op_one_shell_history() ( run_op() { echo "+ ${*}" ; "$@" ; } run_op rm -vf \ /home/*/.history \ /root/.history \ /home/*/.*_history \ /root/.*_history \ ; ) # TODO: this is disabled for now because it depends on user-account and due to # the alphabetical order this is executed sooner which then causes problem... op_one_group_account() ( run_op() { echo "+ ${*}" ; "$@" ; } if [ "${_ONE_VALID_AWK}" = 'NO' ] ; then printf "[!] This test is skipped due to the incompatible awk implementation!\n" return 2 fi case "$(uname | tr '[:upper:]' '[:lower:]')" in linux) if [ -f /etc/login.defs ] ; then _gid_min=$(</etc/login.defs \ awk '/^[[:space:]]*GID_MIN[[:space:]]/ {print $2}') _gid_max=$(</etc/login.defs \ awk '/^[[:space:]]*GID_MAX[[:space:]]/ {print $2}') # setup the default values according to manual pages for login.defs if [ -z "$_gid_min" ] ; then _gid_min=1000 fi if [ -z "$_gid_max" ] ; then _gid_max=60000 fi _groups=$(getent group | \ awk -v gid_min="$_gid_min" -v gid_max="$_gid_max" \ ' BEGIN { FS = ":"; } { if (($3 >= gid_min) && ($3 <= gid_max)) printf("%s:%s\n", $1, $3); }' | sort -u) for _group in ${_groups} ; do _groupname=$(echo "$_group" | cut -d":" -f1) _gid=$(echo "$_group" | cut -d":" -f2) if command -v groupdel >/dev/null ; then run_op groupdel "$_groupname" elif command -v delgroup >/dev/null ; then run_op delgroup "$_groupname" fi # wipe-out all deleted group's files on the system run_op find / -depth -group "$_gid" -delete || true done else err "No /etc/login.defs - we cannot guess the MIN-MAX of UIDs/GIDs" return 1 fi ;; freebsd) if [ -f /etc/pw.conf ] ; then _gid_min=$(</etc/pw.conf \ awk '/^[[:space:]]*mingid[[:space:]]/ {print $2}') _gid_max=$(</etc/pw.conf \ awk '/^[[:space:]]*maxgid[[:space:]]/ {print $2}') fi # setup the default values according to manual pages for pw.conf if [ -z "$_gid_min" ] ; then _gid_min=1000 fi if [ -z "$_gid_max" ] ; then _gid_max=32000 fi _groups=$(getent group | \ awk -v gid_min="$_gid_min" -v gid_max="$_gid_max" \ ' BEGIN { FS = ":"; } { if (($3 >= gid_min) && ($3 <= gid_max)) printf("%s:%s\n", $1, $3); }' | sort -u) for _group in ${_groups} ; do _groupname=$(echo "$_group" | cut -d":" -f1) _gid=$(echo "$_group" | cut -d":" -f2) run_op pw groupdel "$_groupname" # wipe-out all deleted group's files on the system run_op find / -depth -group "$_gid" -delete || true done ;; *) warn "Removing groups: this system ('$(uname)') is not supported" ;; esac ) op_one_hostname() ( run_op() { echo "+ ${*}" ; "$@" ; } # wipe-out hostname if [ -f /etc/hostname ] ; then echo "+ echo localhost" echo localhost > /etc/hostname fi # wipe-out hosts cat > /etc/hosts <<EOF # The following lines are desirable for IPv4 capable hosts 127.0.0.1 localhost # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts EOF ) op_one_resolvconf() ( run_op() { echo "+ ${*}" ; "$@" ; } # wipe-out resolv.conf echo "+ nameserver 127.0.0.1" echo nameserver 127.0.0.1 > /etc/resolv.conf ) op_one_network() ( run_op() { echo "+ ${*}" ; "$@" ; } case "$(uname | tr '[:upper:]' '[:lower:]')" in linux) if [ -f /etc/network/interfaces ] ; then for f in /etc/network/interfaces* ; do if [ -f "$f" ] ; then run_op rm -vf /etc/network/interfaces* fi done echo "+ reset /etc/network/interfaces" cat > /etc/network/interfaces <<EOF auto lo iface lo inet loopback EOF fi if [ -f /etc/networks ] ; then echo "+ reset /etc/networks" cat > /etc/networks <<EOF default 0.0.0.0 loopback 127.0.0.0 link-local 169.254.0.0 EOF fi # systemd run_op rm -rvf /etc/systemd/network/* # redhat/suse scripts if [ -d /etc/sysconfig/network-scripts ] || \ [ -d /etc/sysconfig/network/ ] ; then run_op find /etc/sysconfig/network*/ \ -maxdepth 1 \ -type f \ \( -name ifcfg-\* -a -not -name ifcfg-lo \) \ -delete run_op find /etc/sysconfig/network*/ \ -maxdepth 1 \ -type f \ \( -name route-\* -o -name ifroute-\* \) \ -delete fi ;; freebsd) run_op rm -vf /etc/hostid run_op rm -vf /etc/rc.conf.d/network ;; *) warn "Cleaning network settings: this system ('$(uname)') is not supported" ;; esac # wipe-out hosts.allow|deny|etc files if [ -f /etc/hosts.allow ] ; then echo "+ clear /etc/hosts.allow" cat /dev/null > /etc/hosts.allow fi if [ -f /etc/hosts.deny ] ; then echo "+ clear /etc/hosts.deny" cat /dev/null > /etc/hosts.deny fi if [ -f /etc/hosts.equiv ] ; then echo "+ clear /etc/hosts.equiv" cat /dev/null > /etc/hosts.equiv fi if [ -f /etc/hosts.lpd ] ; then echo "+ clear /etc/hosts.lpd" cat /dev/null > /etc/hosts.lpd fi ) # fill up the free space with zeroes to claim the whole free/unused space for # sparsify or trim (below) - to fully utilize this - one should create virtual # disk similar to this (on QEMU/KVM): # <disk type='file' device='disk'> # <driver name='qemu' type='raw' discard='unmap'/> # <source file='/some.img'/> # <target dev='sda' bus='scsi'/> # <address type='drive' controller='0' bus='0' target='0' unit='0'/> # </disk> op_one_zerofill() ( run_op() { echo "+ ${*}" ; "$@" ; } msg "Fill the free space with zeroes and delete it" # 1MiB - busybox workaround _bytes=$(( 1024 * 1024 )) run_op dd if=/dev/zero of=/.zero bs=${_bytes} || true run_op sync run_op rm -f /.zero ) # trim/discard the unused space # TODO: FreeBSD version will break if the device path has some whitespace which # is very unlikely... op_one_trim() ( run_op() { echo "+ ${*}" ; "$@" ; } case "$(uname | tr '[:upper:]' '[:lower:]')" in linux) msg "Trim/discard the unused space" if command -v fstrim > /dev/null ; then run_op fstrim -a -v || run_op fstrim -v / else warn "Missing fstrim tool - SKIP" fi ;; freebsd) # TODO: I don't know what device to use here - let that be an # improvement for the future... #msg "Trim/discard the unused space" # TODO: check awk sanity if used... #if command -v trim > /dev/null ; then # _devs=$(< /etc/fstab awk ' # { # if ($0 !~ /^[[:space:]]*#/) # print $1; # }') # for _dev in ${_devs} ; do # run_op trim -f "$_dev" # done #else # warn "Missing fstrim tool - SKIP" #fi warn "Trim not supported on the FreeBSD - SKIP" ;; esac ) ################################################################################ # main # # are we colorful? if is_terminal_colorful ; then SETCOLOR_ON_OK='\033[0;32;1m' # green SETCOLOR_ON_FAILURE='\033[0;31;1m' # red SETCOLOR_ON_HIGHLIGHT='\033[0;32;1m' # green SETCOLOR_ON_HIGHLIGHT2='\033[0;36;1m' # cyan SETCOLOR_ON_COMMAND='\033[0;33;1m' # yellow SETCOLOR_OFF='\033[0m' else SETCOLOR_ON_OK= SETCOLOR_ON_FAILURE= SETCOLOR_ON_HIGHLIGHT= SETCOLOR_ON_HIGHLIGHT2= SETCOLOR_ON_COMMAND= SETCOLOR_OFF= fi # print banner cat <<EOF ___ _ __ ___ / _ \ | '_ \ / _ \ OpenNebula System Preparation Tool | (_) || | | || __/ (c) 2002-2021, OpenNebula Project, OpenNebula Systems \___/ |_| |_| \___| Version 6.4.0 EOF # leave this empty IGNORE_ERROR= ARG_ONE_OPTION_LIST= ARG_OPS_LIST= ARG_PASSWORD= ARG_USERS_KEEP= ARG_USERS_REMOVE= # parse arguments state=nil action= abort= while [ $# -gt 0 ] ; do case "$state" in nil) case "$1" in --help) state=help case "$action" in '') action=help ;; help) err "Redundant argument: ${1}" ;; *) err "Arguments mismatch" abort=yes ;; esac ;; --list-operations) state=list case "$action" in '') action=list-operations ;; list-operations) err "Redundant argument: ${1}" ;; *) err "Arguments mismatch" abort=yes ;; esac ;; --operations) state=ops case "$action" in ''|operations) action=operations ;; *) err "Arguments mismatch" abort=yes ;; esac ;; # DISABLED: #--one-options) # state=options # ;; --yes|--strict|--update|--verbose|--poweroff|--selinux-relabel) ARG_ONE_OPTION_LIST="${ARG_ONE_OPTION_LIST},${1##--}" ;; --root-password) state=root-password ;; --password) state=password ;; --remove-user-accounts) state=remove-users ;; --keep-user-accounts) state=keep-users ;; *) err "Unknown argument: ${1}" abort=yes ;; esac ;; help) err "Extra argument: ${1}" abort=yes ;; list) err "Extra argument: ${1}" abort=yes ;; ops) ARG_OPS_LIST="${ARG_OPS_LIST},${1}" state=nil ;; # DISABLED: #options) # ARG_ONE_OPTION_LIST="${ARG_ONE_OPTION_LIST},${1}" # state=nil # ;; root-password) ARG_PASSWORD="${ARG_PASSWORD},root:${1}" state=nil ;; password) ARG_PASSWORD="${ARG_PASSWORD},${1}" state=nil ;; remove-users) ARG_USERS_REMOVE="${ARG_USERS_REMOVE},${1}" state=nil ;; keep-users) ARG_USERS_KEEP="${ARG_USERS_KEEP},${1}" state=nil ;; esac # there were usage errors - abort if [ -n "$abort" ] ; then print_help exit 1 fi shift done # verify that arguments are not short of values... case "$state" in ops|options|root-password|password|remove-users|keep-users) err "Missing argument(s)" exit 1 ;; esac # validate one options parse_one_options "$ARG_ONE_OPTION_LIST" # validate password arguments and save them again in parsed format ARG_PASSWORD=$(parse_password_arguments "$ARG_PASSWORD") # execute the requested action case "$action" in '') sanity_check # ask before running ask_to_run_sysprep # run default operations run_prep run_ops default ;; help) usage exit 0 ;; list-operations) list_operations exit 0 ;; operations) sanity_check # ask before running ask_to_run_sysprep run_prep run_ops "$ARG_OPS_LIST" ;; esac # optional last sysprep steps (like poweroff) run_last msg "DONE" exit 0