openSUSE Builder

by archie
48 deployments · 28 still active · last rev. 4 months ago

This script customizes the openSUSE distribution provided by Linode.

It allows configuring additional RPM repositories as well as some other handy pre-configuration such as SSH security hardening.

Compatible with: openSUSE Leap 15.0, openSUSE Leap 42.3
# $Id: 1079 2019-04-04 14:55:03Z archie $

# Configuration
# <UDF name="SS_SUSE_RELEASE" label="openSUSE Release" default="42.3" oneOf="42.3,15.0,15.1,15.2">
# <UDF name="SS_HOSTNAME" label="Hostname" default="" example="E.g., mybox. Stored in /etc/HOSTNAME. Note openSUSE will strip off any domain part when setting the hostname(5)">
# <UDF name="SS_ENABLE_IPV6" label="Enable IPv6?" oneOf="Yes,No" default="Yes" example="Normally IPv6 is enabled, but you can disable it if you don't need/want it">
# <UDF name="SS_EXIM_ORIGIN_DOMAIN" label="Exim origin domain" default="" example="Email sent to a local user like 'jfrost' will automatically have this domain appended before routing (e.g., '' would result in ''). Useful when you want no mail to be delivered locally. Automatically adds exim.">
# <UDF name="SS_EXIM_LOCALHOST_ONLY" label="Accept SMTP on localhost only" oneOf="Yes,No" default="No" example="Accept SMTP connections (port 25) on localhost only. Recommended unless you're planning to run a mail hub. Automatically adds exim.">
# <UDF name="SS_HARDEN_SSH" label="Enable additional sshd security hardening" default="Yes" oneOf="Yes,No" example="This disables password auth (i.e., allow pubkey auth only) and disables root login via SSH. You must therefore also create a sudo(8) user configured with a public key (see below) for root access over the network.">
# <UDF name="SS_SUDO_USER" label="Initial sudo(8) user username" default="" example="E.g., 'jfrost'. User will be automatically created and have full sudo(8) rights">
# <UDF name="SS_SUDO_USER_REALNAME" label="Initial sudo(8) user's real name" default="" example="E.g., Jack Frost">
# <UDF name="SS_SUDO_USER_PUBKEY" label="Initial sudo(8) user's SSH public key" default="" example="Will be put into e.g., ~jfrost/.ssh/authorized_keys so user can login using SSH pubkey authentication">
# <UDF name="SS_NTP" label="Install and configure NTP daemon" default="No" oneOf="Yes,No" example="Will set up NTP daemon to sync from">
# <UDF name="SS_REPO_1" label="Additional repository #1" example="Either a complete URL like or an openSUSE Build Service project name like 'Apache:Modules'" default="">
# <UDF name="SS_REPO_2" label="Additional repository #2" example="URL for any zypper-supported RPM repository or an openSUSE Build Service project name" default="">
# <UDF name="SS_REPO_3" label="Additional repository #3" example="URL for any zypper-supported RPM repository or an openSUSE Build Service project name" default="">
# <UDF name="SS_REPO_4" label="Additional repository #4" example="URL for any zypper-supported RPM repository or an openSUSE Build Service project name" default="">
# <UDF name="SS_ADD_PACKAGES" label="Additional packages to install" default="man-pages iputils" example="Provide RPM names here. NOTE: These RPMs must all be found in openSUSE OSS, openSUSE Non-OSS, or one of your additional repositories">
# <UDF name="SS_INSTALL_RECOMMENDS" label="Install recommended packages" default="Yes" oneOf="Yes,No" example="Installs other packages recommended by the packages you are installing">

# Definitions

# Desired openSUSE release

# Functions
    echo "**** [$$]" ${1+"$@"} >/dev/console
    sleep 2

    REPO=`echo ${1}`
    test -z "${REPO}" && return
    if ! echo "${REPO}" | grep -q //; then
        REPODIR=`echo "${REPO}" | sed -r 's|:|:/|g'`
    showmsg Adding repository: "${REPO}"
    runcmd zypper addrepo "${REPO}"

    cp -a "${FILE}" "${FILE}.new"
    sed ${1+"$@"} < "${FILE}" > "${FILE}.new"
    mv "${FILE}.new" "${FILE}"
    showmsg Modified file: "${FILE}"

    echo '==> ' ${1+"$@"} >/dev/console
    ${1+"$@"} >/dev/console 2>&1

# Initial greeting
showmsg "openSUSE Builder for ${SUSEREL} starting"

# Work around Linode bug where two instances are started
if ! mkdir "${LOCKDIR}" 2>/dev/null; then
    showmsg "ERROR: detected concurrent openSUSE Builder, exiting (if not, rmdir ${LOCKDIR} and retry)"
    exit 1

# Record input variables for debugging
set > /root/"${NAME}".vars

# Stop on any errors or signals and report them
    showmsg "ERROR: command failed: ${BASH_COMMAND}"

    showmsg "ERROR: fatal signal rec'd while executing command: ${BASH_COMMAND}"
    rmdir "${LOCKDIR}" >/dev/null || :

set -e
trap "handlefail" ERR
trap "handlesignal" 2 3 5 10 13 15

# Clean up lock directory on exit
    rmdir "${LOCKDIR}" >/dev/null || :
trap "handleexit" EXIT

# Blow away old repositories
showmsg Removing old zypper repositories
rm -f /etc/zypp/repos.d/*
rm -rf /var/cache/zypp/*/*

# Setup normal openSUSE repos
showmsg Configuring zypper with ${SUSEREL} repositories
runcmd zypper addrepo${SUSEREL}/repo/oss/       "repo-oss"
runcmd zypper addrepo${SUSEREL}/repo/non-oss/   "repo-non-oss"
runcmd zypper addrepo${SUSEREL}/oss/                  "repo-update"
runcmd zypper addrepo${SUSEREL}/non-oss/              "repo-update-non-oss"

# Update zypper itself
showmsg Refreshing update repository
runcmd zypper -n refresh -f repo-update
showmsg Updating zypper itself
yes | runcmd zypper -n install zypper libzypp 

# Configure additional repositories
addrepo "${SS_REPO_1}"
addrepo "${SS_REPO_2}"
addrepo "${SS_REPO_3}"
addrepo "${SS_REPO_4}"

# Refresh repos and accept keys
showmsg Refreshing repositories
runcmd zypper --gpg-auto-import-keys refresh

# Perform distribution upgrade: step #1 download
if [ "${SS_INSTALL_RECOMMENDS}" = "No" ]; then
showmsg Downloading new RPMs
runcmd zypper -n dist-upgrade --auto-agree-with-licenses "${DUP_FLAG}" --download-only

# Perform distribution upgrade: step #2 install
showmsg Installing new RPMs
runcmd zypper -n dist-upgrade --auto-agree-with-licenses "${DUP_FLAG}"

# Make "required only" permanent if configured
if [ "${SS_INSTALL_RECOMMENDS}" = "No" ]; then
    sedfile /etc/zypp/zypp.conf -r 's/^(#[[:space:]]*)?(solver.onlyRequires = ).*$/\2true/g'

# We need sudo(8) if there is an initial sudo user
if [ "${SS_SUDO_USER}" != "" ]; then

# We need ntp(8) if SS_NTP is set
if [ "${SS_NTP}" = "Yes" ]; then

# We need exim(8) for exim stuff
if [ -n "${SS_EXIM_ORIGIN_DOMAIN}" -o "${SS_EXIM_LOCALHOST_ONLY}" = "Yes" ]; then

# Install additional packages
if [ "${SS_ADD_PACKAGES}" != "" ]; then
    showmsg Installing additional RPMS: "${SS_ADD_PACKAGES}"
    runcmd zypper -n install --auto-agree-with-licenses ${SS_ADD_PACKAGES}

# Set hostname
if [ -n "${SS_HOSTNAME}" ]; then
    showmsg Setting hostname to "${SS_HOSTNAME}"
    echo "${SS_HOSTNAME}" > /etc/HOSTNAME

    # Prevent DHCP client from changing the hostname
    showmsg Configuring DHCP client to not overwrite hostname
    sedfile /etc/sysconfig/network/dhcp -r 's/^(DHCLIENT6?_SET_HOSTNAME=).*$/\1"no"/g'

# Enable/disable IPv6
if [ "${SS_ENABLE_IPV6}" = "No" ]; then
    showmsg Disabling IPv6
    sedfile /etc/sysctl.conf -r 's/^#?[[:space:]]*(net.ipv6.conf.all.disable_ipv6).*$/\1 = '"${TEMP}"'/g'

# Configure NTP
if [ "${SS_NTP}" = "Yes" ]; then
    showmsg Configuring NTP
    printf 'restrict default ignore\n' >> /etc/ntp.conf
    printf 'restrict -6 default ignore\n' >> /etc/ntp.conf
    printf 'restrict\n' >> /etc/ntp.conf
    printf 'restrict -6 ::1 \n' >> /etc/ntp.conf
    for i in 1 2 3 4; do
        printf 'server\n' "${i}" >> /etc/ntp.conf
        printf 'restrict nomodify notrap nopeer noquery\n' "${i}" >> /etc/ntp.conf
    test -e /etc/sysconfig/ntp.orig || cp -a /etc/sysconfig/ntp /etc/sysconfig/ntp.orig
    systemctl enable ntpd.service

# Configure exim 'origin domain'
if [ -n "${SS_EXIM_ORIGIN_DOMAIN}" ]; then
    if [ -f /etc/exim/exim.conf ]; then
        showmsg "Configuring exim with origin domain ${SS_EXIM_ORIGIN_DOMAIN}"
        test -e /etc/exim/exim.conf.orig || cp -a /etc/exim/exim.conf /etc/exim/exim.conf.orig
        sedfile /etc/exim/exim.conf -r 's/^#?[[:space:]]*(qualify_domain[[:space:]]*=).*$/\1 '"${SS_EXIM_ORIGIN_DOMAIN}"'/g'
        showmsg "Didn't find exim - can't configure for origin domain"

# Configure exim local_interfaces
if [ "${SS_EXIM_LOCALHOST_ONLY}" = "Yes" ]; then
    if [ -f /etc/exim/exim.conf ]; then
        showmsg "Configuring exim to only accept connections on localhost"
        test -e /etc/exim/exim.conf.orig || cp -a /etc/exim/exim.conf /etc/exim/exim.conf.orig
        sedfile /etc/exim/exim.conf -r '/^#?[[:space:]]*(qualify_domain[[:space:]]*=)/alocal_interfaces ='
        showmsg "Didn't find exim - can't configure for localhost only"

# Optionally create sudo(8) user
if [ "${SS_SUDO_USER}" != "" ]; then

    # Create user
    showmsg Creating user "${SS_SUDO_USER}"
    runcmd useradd -p '*' -c "${SS_SUDO_USER_REALNAME}" -m -s /bin/bash "${SS_SUDO_USER}"

    # Give user full sudo rights
    showmsg Giving user "${SS_SUDO_USER}" full 'sudo(8)' rights
    printf "%s\tALL=(ALL) NOPASSWD: ALL\n" "${SS_SUDO_USER}" >> /etc/sudoers

    # Optionally configure SSH pubkey
    if [ "${SS_SUDO_USER_PUBKEY}" != "" ]; then
        showmsg Configuring SSH public key 'for' user "${SS_SUDO_USER}"
        mkdir -m 0755 /home/"${SS_SUDO_USER}"/.ssh
        echo "${SS_SUDO_USER_PUBKEY}" > /home/"${SS_SUDO_USER}"/.ssh/authorized_keys
        chown -R "${SS_SUDO_USER}":users /home/"${SS_SUDO_USER}"/.ssh

# Optionally harden SSH
if [ "${SS_HARDEN_SSH}" = "Yes" ]; then
    showmsg Security hardening SSHD configuration
    test -e /etc/ssh/sshd_config.orig || cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.orig
    sedfile /etc/ssh/sshd_config -r \
      -e 's/^#?(PermitRootLogin ).*$/\1no/g' \
      -e 's/^#?(PasswordAuthentication ).*$/\1no/g' \
      -e 's/^#?(ChallengeResponseAuthentication ).*$/\1no/g' \
      -e 's/^#?(UsePAM ).*$/\1no/g'

# Disable graphics
showmsg Setting multi-user mode as default '(i.e., disabling graphical mode)'
systemctl set-default

# Done
showmsg "Installation done... reboot your system for changes to take effect!"