Skip to content

Instantly share code, notes, and snippets.

@kfiresmith
Created February 27, 2025 21:36
Show Gist options
  • Save kfiresmith/695f1f1377938d7cd1c711ff8fc06ece to your computer and use it in GitHub Desktop.
Save kfiresmith/695f1f1377938d7cd1c711ff8fc06ece to your computer and use it in GitHub Desktop.
#!/bin/bash
# Does the equivalent of sysprep for linux boxes to prepare them for cloning.
# Based on https://lonesysadmin.net/2013/03/26/preparing-linux-template-vms/
AUTHOR='kfiresmith'
BASENAME="${0##*/}"
MODIFIED='20231020'
VERSION='0.1.1'
parse_args() {
[[ -n $1 ]] || {
printf 'No arguments specified, use -h for help.\n'
exit 0
}
while [[ -n $1 ]]; do
if [[ $1 == -v ]]; then
printf '%s: Version %s, updated %s by %s\n' \
"$BASENAME" "$VERSION" "$MODIFIED" "$AUTHOR"
shift
[[ -n $1 ]] || exit 0
elif [[ $1 == -h ]]; then
printf 'Cloning preparation script for linux systems.\n\n'
printf 'Usage: %s [-v ] (-h | -y [-b] [-l <log_file>] [-s])\n\n' \
"$BASENAME"
printf 'Options:\n'
printf ' -h Display this help text.\n'
printf ' -b Used for firstboot (internal).\n'
printf ' -l Specify log file location.\n'
printf ' -s Shutdown on completion.\n'
printf ' -v Emit version header.\n'
printf ' -y Confirm sysprep.\n'
exit 0
elif [[ $1 == -b ]]; then
FIRSTBOOT=true
break
elif [[ $1 == -l ]]; then
shift
LOGFILE=$1
shift
elif [[ $1 == -s ]]; then
SHUTDOWN=true
shift
elif [[ $1 == -y ]]; then
CONFIRM=true
shift
else
printf 'Invalid argument specified, use -h for help.\n'
exit 0
fi
done
}
say() {
LOGFILE=${LOGFILE:=/var/log/sysprep.log}
if [[ -n $LOGFILE && ! $LOGFILE == no ]]; then
[[ -f $LOGFILE ]] || UMASK=027 /usr/bin/touch "$LOGFILE"
printf '%s: %s\n' "$(date -u +%FT%TZ)" "$@" | tee -a "$LOGFILE"
else
printf '%s: %s\n' "$(date -u +%FT%TZ)" "$@"
fi
}
apt_purge() {
vers=$(/bin/ls -tr /boot/vmlinuz-* | /usr/bin/head -n -1 |
/bin/grep -v "$(uname -r)" | /usr/bin/cut -d- -f2-)
for i in $vers; do
debs+="$(echo linux-{image,headers,modules}-$i) "
done
/usr/bin/apt remove -qy --purge $debs &> /dev/null &&
/usr/bin/apt autoremove -qy --purge &> /dev/null
}
firstboot() {
say 'Running sysprep first-boot setup script.'
/usr/bin/find /etc/ssh/*key &>/dev/null || {
say 'Regenerating SSH host keys...'
/usr/sbin/dpkg-reconfigure openssh-server
}
[[ $(hostname) == CHANGEME ]] && {
say 'Regenerating hostname and rebooting...'
/bin/hostnamectl set-hostname changeme.local
/bin/systemctl reboot
}
[[ -f /var/lib/aide/aide.db.gz ]] && {
say 'Regenerating AIDE database...'
/sbin/aide --update
/bin/mv -f /var/lib/aide/aide.db{.new,}.gz
}
say 'Sysprep firtst-boot setup complete, disabling service.'
/bin/systemctl disable sysprep-firstboot
exit 0
}
clean_packages() {
say 'Removing old kernels.'
if ! command -v purge-old-kernels &> /dev/null; then
/usr/bin/apt install -qy byobu &> /dev/null
fi
/usr/bin/purge-old-kernels -qy --keep 1 &> /dev/null || apt_purge
say 'Clearing package cache.'
/usr/bin/apt clean &> /dev/null
/bin/rm -rf /var/cache/apt/archives/*
return 0
}
clean_logs() {
say 'Clearing old logs.'
/usr/sbin/logrotate -f /etc/logrotate.conf
/usr/bin/find /var/log -type f -regextype posix-extended -regex \
".*/*(-[0-9]{8}|.[0-9]|.gz)$" -delete
/bin/rm -rf /var/log/journal && /bin/mkdir /var/log/journal
/bin/rm -f /var/log/dmesg.old
/bin/rm -f /var/log/anaconda/*
say 'Clearing audit logs.'
: > /var/log/audit/audit.log
: > /var/log/wtmp
: > /var/log/lastlog
: > /var/log/grubby
return 0
}
clean_network() {
say 'Clearing udev persistent rules.'
/bin/rm -f /etc/udev/rules.d/70*
say 'Wipe netplan config'
/bin/rm -f /etc/netplan/*.yaml
/bin/cp 02-no-network.yaml.example 02-no-network.yaml
/usr/sbin/netplan apply
return 0
}
clean_files() {
say 'Cleaning out temp directories.'
/bin/rm -rf /tmp/*
/bin/rm -rf /var/tmp/*
/bin/rm -rf /var/cache/*
say 'Cleaning up root home directory.'
unset HISTFILE
/bin/rm -f /root/.bash_history
/bin/rm -f /root/anaconda-ks.cfg
/bin/rm -rf /root/.ssh/
/bin/rm -rf /root/.gnupg/
return 0
}
generalize() {
say 'Removing SSH host keys.'
/bin/rm -f /etc/ssh/*key*
say 'Clearing machine-id'
: > /etc/machine-id
say 'Removing random-seed'
/bin/rm -f /var/lib/systemd/random-seed
say 'Resetting hostname.'
/bin/hostnamectl set-hostname 'CHANGEME'
return 0
}
setup_firstboot() {
say 'Enabling sysprep firstboot service.'
FBSERVICE=/etc/systemd/system/sysprep-firstboot.service
[[ -f $FBSERVICE ]] || /bin/cat <<'EOF' > $FBSERVICE
[Unit]
Description=Sysprep first-boot setup tasks
[Service]
Type=simple
ExecStart=/usr/local/sbin/sysprep -y -b
[Install]
WantedBy=multi-user.target
EOF
/bin/systemctl enable sysprep-firstboot
return 0
}
sysprep() {
parse_args "$@"
[[ $CONFIRM == true ]] || {
say 'Confirm with -y to start sysprep.'
exit 0
}
say 'Beginning sysprep.'
[[ $FIRSTBOOT == true ]] && firstboot
say 'Stopping logging and auditing daemons.'
/bin/systemctl stop rsyslog.service
/usr/sbin/service auditd stop
clean_packages
clean_logs
clean_network
clean_files
generalize
setup_firstboot
say 'End of sysprep.'
[[ $SHUTDOWN == true ]] && {
say 'Shutting down the system.'
/bin/systemctl poweroff
}
exit 0
}
# Only execute if not being sourced
[[ ${BASH_SOURCE[0]} == "$0" ]] && sysprep "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment