Skip to content

Instantly share code, notes, and snippets.

@mikesmithgh
Forked from snixon/centos8-chroot.sh
Last active August 22, 2022 17:20
Show Gist options
  • Save mikesmithgh/e5efb12fd5380271e5c5aebda7b87efa to your computer and use it in GitHub Desktop.
Save mikesmithgh/e5efb12fd5380271e5c5aebda7b87efa to your computer and use it in GitHub Desktop.
Build a CentOS7 AWS AMI in a chroot with GPT partitioning scheme
#!/bin/bash -ex
# Build a new Centos7 install on EBS volume in a chroot
# Run from RHEL7 or CentOS7 instance - eg: ami-0c322300a1dd5dc79 in us-east-1 (RHEL 7 official image)
# Script expects a second EBS volume, I add them as /dev/sdf in the console
# When the script completes, turn the second EBS volume into your new AMI through the console.
# Adjust the section below to match the device names you're using. Defaults are for an m5.large
# m5 series requires the updated device names
DEVICE=/dev/nvme1n1
ROOTFS=/rootfs
TMPDIR=/tmp
# I build in the ENA drivers from amazon - https://github.com/amzn/amzn-drivers
ENA_VER=2.7.4
ENA_COMMIT=2034880
# Directories to bind mount in ROOTFS
BINDMNTS="sys etc/hosts etc/resolv.conf"
# Unmount if script previously ran and left them in a mounted state
for d in $BINDMNTS ; do
umount ${ROOTFS}/${d} || true
done
umount ${ROOTFS}/dev || true
umount ${ROOTFS}/proc || true
# Hush, I'm old.
sync;sync;sync
umount ${ROOTFS} || true
# Need a package for setting up disk
yum install -y gdisk
# Need sgdisk to setup the partitions properly
sgdisk -og ${DEVICE}
sgdisk -n 128:2048:4095 -c 128:"BIOS Boot Partition" -t 128:ef02 ${DEVICE}
ENDSECTOR=$(sgdisk -E ${DEVICE})
sgdisk -n 1:4096:${ENDSECTOR} -c 1:"Linux filesystem" -t 1:8300 ${DEVICE}
mkfs.xfs -f -L root ${DEVICE}p1
mkdir -p ${ROOTFS}
mount ${DEVICE}p1 ${ROOTFS}
mkdir ${ROOTFS}/{dev,proc}
# Setup bindmounts
mount -t proc none ${ROOTFS}/proc
mount --bind /dev ${ROOTFS}/dev
### Basic CentOS Install
# Grab close mirror
CENT_MIRROR=""
CENT_MIRRORS=($(curl -s "http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=os" | sort -R))
for mirror in "${CENT_MIRRORS[@]}"; do
http_code=$(curl -s -o /dev/null -I -w "%{http_code}\n" $mirror)
if [ $http_code -eq 200 ]; then
CENT_MIRROR=$mirror
break
fi
done
if [ "$CENT_MIRROR" == ""]; then
echo "Failed to fetch a valid download mirror"
exit 1
fi
# Grab current release
CENT_REL=$(echo ${CENT_MIRROR} | rev | cut -f4 -d'/' | rev | sed 's/\./-/')
CENT_REL_RPM=$(curl -sL "${CENT_MIRROR}Packages" | grep centos-release-7 | sed -r 's/.*href="([^"]+).*/\1/g')
rpm --root=${ROOTFS} --initdb
# This is hardcoded for the moment.
rpm --root=${ROOTFS} -ivh \
${CENT_MIRROR}Packages/${CENT_REL_RPM}
# Install necessary packages
yum --installroot=${ROOTFS} --nogpgcheck -y update
yum --installroot=${ROOTFS} --nogpgcheck -y groupinstall "Minimal Install"
yum --installroot=${ROOTFS} --nogpgcheck -y install openssh-server grub2 acpid tuned kernel drpm epel-release
# Install helpful packages for everyone
yum --installroot=${ROOTFS} --nogpgcheck -y install \
cloud-init \
cloud-utils-growpart \
dracut-config-generic \
dracut-norescue \
dkms \
dstat \
ethtool \
gdisk \
htop \
jq \
lsof \
make \
@python36 \
net-tools \
nmap-ncat \
chrony \
openssl \
psmisc \
rsync \
strace \
sysstat \
tmux \
unzip \
wget \
yum-utils
# Clean out old Firmware and packages I don't use
yum --installroot=${ROOTFS} -C -y remove \
aic94xx-firmware \
alsa-firmware \
alsa-lib \
alsa-tools-firmware \
biosdevname \
dracut-config-rescue \
iprutils \
ivtv-firmware \
iwl100-firmware \
iwl1000-firmware \
iwl105-firmware \
iwl135-firmware \
iwl2000-firmware \
iwl2030-firmware \
iwl3160-firmware \
iwl3945-firmware \
iwl4965-firmware \
iwl5000-firmware \
iwl5150-firmware \
iwl6000-firmware \
iwl6000g2a-firmware \
iwl6000g2b-firmware \
iwl6050-firmware \
iwl7260-firmware \
libertas-sd8686-firmware \
libertas-sd8787-firmware \
libertas-usb8388-firmware \
linux-firmware \
plymouth
# Create homedir for root
cp -a /etc/skel/.bash* ${ROOTFS}/root
# RHEL7 AMI KS thievery
sed -i '/^#NAutoVTs=.*/ a\
NAutoVTs=0' ${ROOTFS}/etc/systemd/logind.conf
echo xen-netfront >> ${ROOTFS}/etc/modules-load.d/xen-netfront.conf
## Networking setup
cat > ${ROOTFS}/etc/hosts << END
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
END
touch ${ROOTFS}/etc/resolv.conf
mkdir -p ${ROOTFS}/etc/sysconfig/network-scripts
cat > ${ROOTFS}/etc/sysconfig/network << END
NETWORKING=yes
NOZEROCONF=yes
END
cat > ${ROOTFS}/etc/sysconfig/network-scripts/ifcfg-eth0 << END
DEVICE=eth0
ONBOOT=yes
BOOTPROTO=dhcp
IPV6INIT=yes
DHCPV6C=yes
DHCPV6C_OPTIONS=-nw
END
# Add timestamps to history commands
cat > ${ROOTFS}/etc/profile.d/history.sh << END
export HISTTIMEFORMAT="%d/%m/%y %T "
END
cp /usr/share/zoneinfo/UTC ${ROOTFS}/etc/localtime
echo 'ZONE="UTC"' > ${ROOTFS}/etc/sysconfig/clock
# fstab
# Need the UUID of our device
ROOTUUID=$(blkid -o export ${DEVICE}p1 -s UUID | grep ^UUID)
cat > ${ROOTFS}/etc/fstab << END
${ROOTUUID} / xfs defaults 0 1
END
#grub config taken from /etc/sysconfig/grub on RHEL7 AMI
cat > ${ROOTFS}/etc/default/grub << END
GRUB_TIMEOUT=0
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto console=ttyS0,115200n8 console=tty0 net.ifnames=0 tsc=reliable clocksource=tsc"
GRUB_DISABLE_RECOVERY="true"
END
echo 'RUN_FIRSTBOOT=NO' > ${ROOTFS}/etc/sysconfig/firstboot
# Make sure we have the right resources mounted
for d in $BINDMNTS ; do
mount --bind /${d} ${ROOTFS}/${d}
done
# Install grub2
chroot ${ROOTFS} grub2-mkconfig -o /boot/grub2/grub.cfg
chroot ${ROOTFS} grub2-install $DEVICE
chroot ${ROOTFS} chmod og-rwx /boot/grub2/grub.cfg
# Install cloud-init from epel
chroot ${ROOTFS} systemctl mask tmp.mount
# Configure cloud-init
cat > ${ROOTFS}/etc/cloud/cloud.cfg << END
users:
- default
disable_root: 1
ssh_pwauth: 0
mount_default_fields: [~, ~, 'auto', 'defaults,nofail', '0', '2']
resize_rootfs_tmp: /dev
ssh_svcname: sshd
ssh_deletekeys: 0
ssh_genkeytypes: [ 'rsa', 'ecdsa', 'ed25519' ]
syslog_fix_perms: ~
preserve_hostname: true
cloud_init_modules:
- migrator
- bootcmd
- write-files
- growpart
- resizefs
- set_hostname
- update_hostname
- update_etc_hosts
- rsyslog
- users-groups
- ssh
cloud_config_modules:
- mounts
- locale
- set-passwords
- yum-add-repo
- package-update-upgrade-install
- timezone
- disable-ec2-metadata
- runcmd
cloud_final_modules:
- rightscale_userdata
- scripts-per-once
- scripts-per-boot
- scripts-per-instance
- scripts-user
- ssh-authkey-fingerprints
- keys-to-console
- phone-home
- final-message
system_info:
default_user:
name: centos
lock_passwd: true
gecos: Cloud User
groups: [wheel, adm, systemd-journal]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/bash
distro: rhel
paths:
cloud_dir: /var/lib/cloud
templates_dir: /etc/cloud/templates
ssh_svcname: sshd
mounts:
- [ ephemeral0, /media/ephemeral0 ]
- [ ephemeral1, /media/ephemeral1 ]
- [ swap, none, swap, sw, "0", "0" ]
datasource_list: [ Ec2, None ]
# vim:syntax=yaml
END
# Setup cloud-init logging
cat > ${ROOTFS}/etc/cloud/cloud.cfg.d/05_logging.cfg << END
## This yaml formated config file handles setting
## logger information. The values that are necessary to be set
## are seen at the bottom. The top '_log' are only used to remove
## redundency in a syslog and fallback-to-file case.
##
## The 'log_cfgs' entry defines a list of logger configs
## Each entry in the list is tried, and the first one that
## works is used. If a log_cfg list entry is an array, it will
## be joined with '\n'.
_log:
- &log_base |
[loggers]
keys=root,cloudinit
[handlers]
keys=consoleHandler,cloudLogHandler
[formatters]
keys=simpleFormatter,arg0Formatter
[logger_root]
level=DEBUG
handlers=consoleHandler,cloudLogHandler
[logger_cloudinit]
level=DEBUG
qualname=cloudinit
handlers=
propagate=1
[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=arg0Formatter
args=(sys.stderr,)
[formatter_arg0Formatter]
format=%(asctime)s - %(filename)s[%(levelname)s]: %(message)s
[formatter_simpleFormatter]
format=[CLOUDINIT] %(filename)s[%(levelname)s]: %(message)s
- &log_file |
[handler_cloudLogHandler]
class=FileHandler
level=DEBUG
formatter=arg0Formatter
args=('/var/log/cloud-init.log',)
- &log_syslog |
[handler_cloudLogHandler]
class=handlers.SysLogHandler
level=DEBUG
formatter=simpleFormatter
args=("/dev/log", handlers.SysLogHandler.LOG_USER)
log_cfgs:
# These will be joined into a string that defines the configuration
- [ *log_base, *log_syslog ]
# These will be joined into a string that defines the configuration
- [ *log_base, *log_file ]
# A file path can also be used
# - /etc/log.conf
# this tells cloud-init to redirect its stdout and stderr to
# 'tee -a /var/log/cloud-init-output.log' so the user can see output
# there without needing to look on the console.
output: {all: '| tee -a /var/log/cloud-init-output.log'}
END
# Setup services
chroot ${ROOTFS} systemctl enable sshd.service
chroot ${ROOTFS} systemctl enable cloud-init.service
chroot ${ROOTFS} systemctl enable chronyd.service
chroot ${ROOTFS} systemctl disable firewalld.service
# From RHEL
echo 'install_items+=" sgdisk "' > ${ROOTFS}/etc/dracut.conf.d/sgdisk.conf
# Disable dracut rescue kernels if dracut-config-rescue is reinstalled
echo 'dracut_rescue_image="no"' > ${ROOTFS}/etc/dracut.conf.d/02-rescue.conf
# Chrony Config
cat > ${ROOTFS}/etc/chrony.conf << END
# Amazon Local Time Service
server 169.254.169.123 prefer iburst
# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
server 0.centos.pool.ntp.org iburst
server 1.centos.pool.ntp.org iburst
server 2.centos.pool.ntp.org iburst
server 3.centos.pool.ntp.org iburst
# Record the rate at which the system clock gains/losses time.
driftfile /var/lib/chrony/drift
# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3
# Enable kernel synchronization of the real-time clock (RTC).
rtcsync
# Specify directory for log files.
logdir /var/log/chrony
END
cat > ${ROOTFS}/etc/sysconfig/chronyd << END
# Command-line options for chronyd
OPTIONS=" -u chrony"
END
cat > ${ROOTFS}/etc/issue << END
Welcome to CentOS, 3rd Rock from the Sun
END
# SSH Config
cat > ${ROOTFS}/etc/ssh/sshd_config << END
# Minimized sshd_config
Protocol 2
LogLevel INFO
MaxAuthTries 4
IgnoreRhosts yes
HostbasedAuthentication no
PermitRootLogin no
PermitEmptyPasswords no
PermitUserEnvironment no
Ciphers aes256-ctr,aes192-ctr,aes128-ctr
MACs [email protected],[email protected],[email protected],hmac-sha2-512,hmac-sha2-256,[email protected]
ClientAliveInterval 300
ClientAliveCountMax 0
LoginGraceTime 60
Banner /etc/issue
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
SyslogFacility AUTHPRIV
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials no
UsePAM yes
X11Forwarding no
UsePrivilegeSeparation sandbox # Default for new installations.
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem sftp /usr/libexec/openssh/sftp-server
END
cat > ${ROOTFS}/etc/sysctl.d/10-linux-network.conf << END
## sysctl settings for controller
net.core.somaxconn=1024
net.ipv4.ip_local_port_range=2048 65535
net.core.rmem_default = 425984
net.core.wmem_default = 425984
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_wmem = 4096 12582912 16777216
net.ipv4.tcp_rmem = 4096 12582912 16777216
net.core.netdev_max_backlog = 1024
net.core.rps_sock_flow_entries = 32768
net.ipv4.tcp_fin_timeout = 30
END
cat > ${ROOTFS}/etc/sysctl.d/11-CIS-network.conf << END
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
END
# CIS Block unused filesystems
cat > ${ROOTFS}/etc/modprobe.d/CIS.conf << END
install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
install hfs /bin/true
install hfsplus /bin/true
install squashfs /bin/true
install udf /bin/true
install vfat /bin/true
END
# CIS Update create mask for rsyslogd
cat > ${ROOTFS}/etc/rsyslog.d/cis.conf << END
$umask 0000
$FileCreateMmode 0640
END
# CIS pam settings
cat > ${ROOTFS}/etc/pam.d/su << END
#%PAM-1.0
auth sufficient pam_rootok.so
# Uncomment the following line to implicitly trust users in the "wheel" group.
#auth sufficient pam_wheel.so trust use_uid
# Uncomment the following line to require a user to be in the "wheel" group.
auth required pam_wheel.so use_uid
auth substack system-auth
auth include postlogin
account sufficient pam_succeed_if.so uid = 0 use_uid quiet
account include system-auth
password include system-auth
session include system-auth
session include postlogin
session optional pam_xauth.so
END
# Tighten up some permissions
chroot ${ROOTFS} chmod og-rwx /etc/crontab
chroot ${ROOTFS} chmod og-rwx /etc/cron.hourly
chroot ${ROOTFS} chmod og-rwx /etc/cron.daily
chroot ${ROOTFS} chmod og-rwx /etc/cron.weekly
chroot ${ROOTFS} chmod og-rwx /etc/cron.monthly
chroot ${ROOTFS} chmod og-rwx /etc/cron.d
# Add additional AWS drivers
# Need to build this thing on its own and install
KVER=$(chroot ${ROOTFS} rpm -q kernel | sed -e 's/^kernel-//')
yum --installroot=${ROOTFS} --nogpgcheck -y install kernel-devel-${KVER}
# Enable Amazon ENA
# Create an archive file locally from git first
yum -y install git
rm -rf ${TMPDIR}/ena
mkdir -p ${TMPDIR}/ena
git clone https://github.com/amzn/amzn-drivers.git ${TMPDIR}/ena
cd ${TMPDIR}/ena
git archive --prefix ena-${ENA_VER}/ ${ENA_COMMIT} | tar xC ${ROOTFS}/usr/src
cat > ${ROOTFS}/usr/src/ena-${ENA_VER}/dkms.conf << END
PACKAGE_NAME="ena"
PACKAGE_VERSION="${ENA_VER}"
CLEAN="make -C kernel/linux/ena clean"
MAKE="make -C kernel/linux/ena/ BUILD_KERNEL=\${kernelver}"
BUILT_MODULE_NAME[0]="ena"
BUILT_MODULE_LOCATION="kernel/linux/ena"
DEST_MODULE_LOCATION[0]="/updates"
DEST_MODULE_NAME[0]="ena"
AUTOINSTALL="yes"
END
chroot ${ROOTFS} dkms add -m ena -v ${ENA_VER}
chroot ${ROOTFS} dkms build -m ena -v ${ENA_VER} -k ${KVER}
chroot ${ROOTFS} dkms install -m ena -v ${ENA_VER} -k ${KVER}
yum --installroot=${ROOTFS} clean all
chroot ${ROOTFS} depmod ${KVER}
# Fix all the selinux file contexts
chroot ${ROOTFS} setfiles -v /etc/selinux/targeted/contexts/files/file_contexts /
# We're done!
for d in $BINDMNTS ; do
umount ${ROOTFS}/${d}
done
umount ${ROOTFS}/dev
umount ${ROOTFS}/proc
# Hush, I'm old.
sync;sync;sync
umount ${ROOTFS}
# Additional validation to confirm device is partitioned correctly
gdisk -l $DEVICE | grep 'Found valid GPT'
[[ 1 == $(gdisk -l $DEVICE | grep 'Linux filesystem' | awk ' { print $1 } ') ]]
[[ 128 == $(gdisk -l $DEVICE | grep 'BIOS Boot Partition' | awk ' { print $1 } ') ]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment