#!/bin/bash

# setup-host-env
#
# Bootstrap a Debian host before it is managed by Ansible.
# Minimum target: Debian 6.
#
# Compatibility policy:
#   - Prefer current capabilities when available.
#   - Fall back to older supported interfaces.
#   - Keep a proven legacy path for older Debian releases.
#
# Host identity policy:
#   - Static/kernel hostname: short hostname
#   - Canonical FQDN: result of "hostname -f"
#   - A successful hostname setup must leave "hostname -f" valid.

PACKAGE_NAME="devop-tools"

ADMIN="Administrator"
DEF_USER="hostmaster"
VERBOSE=
WGET_OPTS="--no-check-certificate"
AUTHORIZED_KEYS_LOCAL="/usr/share/devop-tools/public/authorized_keys"
AUTHORIZED_KEYS_URL="http://www.ac.pestinger.net/devop-tools/authorized_keys"

DOMAIN="$(hostname -d 2>/dev/null || echo "")"
HOST="$(hostname -s 2>/dev/null || echo "")"

DO_INIT=0
DO_SSH=0
HOST_REQUEST=""
AD_ACTION=""
AD_ADMIN_OPTION_USED=0
ACTION_REQUESTED=0

###############################################################################
# Generic helpers
###############################################################################

Usage()
{
  echo -e "\nUsage:"
  echo -e "\t$(basename "$0") [-d <domain>][-h][-H <hostname>][-I][-S][-u <user>][-v][-V]"

  if HasRealmdCapability; then
    echo -e "\t$(basename "$0") [-a <admin>][-J|-L]"
  fi

  echo -e "\nGeneral options:"
  echo -e "\t-d <domain>\tDefault DNS/AD domain [${DOMAIN}]."
  echo -e "\t-h\t\tDisplay this help message."
  echo -e "\t-H <hostname>\tDefine new hostname or FQDN."
  echo -e "\t-I\t\tInitialize default setup."
  echo -e "\t-S\t\tSetup SSH access."
  echo -e "\t-u <user>\tDefault user [${DEF_USER}]."
  echo -e "\t-v\t\tBe verbose."
  echo -e "\t-V, --version\tDisplay version information."

  if HasRealmdCapability; then
    echo -e "\nActive Directory options (realmd available):"
    echo -e "\t-a <admin>\tDomain administrator [${ADMIN}]."
    echo -e "\t-J\t\tJoin default domain."
    echo -e "\t-L\t\tLeave default domain."
  else
    echo -e "\nActive Directory options are unavailable because realmd is not available."
  fi

  echo
  exit "$1"
}

Abend()
{
  local msg="$1"
  local ret="$2"

  echo -e "\n$(basename "$0") - ABEND: ${msg}\n" >&2

  if [ -n "${ret}" ] && [ "${ret}" -gt 0 ] 2>/dev/null; then
    exit "${ret}"
  fi

  exit 1
}

Verbose()
{
  [ -z "${VERBOSE}" ] || echo "$@"
}

GetVersion()
{
  local version

  version="$(dpkg-query -W -f='${Version}' "${PACKAGE_NAME}" 2>/dev/null)" \
    || version="unpackaged"

  [ -n "${version}" ] || version="unpackaged"
  printf '%s\n' "${version}"
}

ShowVersion()
{
  printf '%s %s\n' "$(basename "$0")" "$(GetVersion)"
}

RequireRoot()
{
  [ "$(id -u)" -eq 0 ] || Abend "This operation must be run as root." 1
}

PkgInstalled()
{
  local pkg="$1"

  dpkg-query -W -f='${Status}' "${pkg}" 2>/dev/null \
    | grep -q '^install ok installed$'
}

PkgAvailable()
{
  local pkg="$1"

  PkgInstalled "${pkg}" && return 0
  apt-cache show "${pkg}" 2>/dev/null | grep -q '^Package:'
}

AssurePkg()
{
  local pkg
  local rc

  Verbose "AssurePkg: $*"

  for pkg in "$@"; do
    if PkgInstalled "${pkg}"; then
      continue
    fi

    if ! PkgAvailable "${pkg}"; then
      Abend "Package <${pkg}> is not available from the configured repositories." 1
    fi

    Verbose "AssurePkg: Installing ${pkg} ..."
    apt-get install -y "${pkg}"
    rc=$?

    [ ${rc} -eq 0 ] || Abend "AssurePkg cannot install <${pkg}>" "${rc}"
  done
}

AssurePkgIfAvailable()
{
  local pkg="$1"

  if PkgAvailable "${pkg}"; then
    AssurePkg "${pkg}"
    return $?
  fi

  echo "WARNING: Package <${pkg}> is not available; skipping guest support package." >&2
  return 0
}

PatchConfig()
{
  local key="$1"
  local val="$2"
  local cfg="$3"

  [ -f "${cfg}" ] || Abend "Configuration file <${cfg}> does not exist." 1

  if grep -qE "^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*=" "${cfg}"; then
    sed -r -i \
      "s|^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*=.*$|${key} = ${val}|" \
      "${cfg}"
  else
    echo "${key} = ${val}" >> "${cfg}"
  fi
}

PatchYamlScalar()
{
  local key="$1"
  local val="$2"
  local cfg="$3"

  [ -f "${cfg}" ] || Abend "Configuration file <${cfg}> does not exist." 1

  if grep -qE "^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*:" "${cfg}"; then
    sed -r -i \
      "s|^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*:.*$|${key}: ${val}|" \
      "${cfg}"
  else
    printf '\n%s: %s\n' "${key}" "${val}" >> "${cfg}"
  fi
}

EnsureLine()
{
  local line="$1"
  local file="$2"

  [ -e "${file}" ] || touch "${file}"
  grep -Fqx "${line}" "${file}" 2>/dev/null || echo "${line}" >> "${file}"
}

###############################################################################
# Input validation helpers
###############################################################################

_is_valid_dns_label()
{
  local s="$1"

  [[ -n "${s}" ]] || return 1
  (( ${#s} <= 63 )) || return 1
  [[ "${s}" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$ ]] || return 1
  return 0
}

_is_valid_domain_name()
{
  local d="$1"
  local IFS='.'
  local part

  [[ -n "${d}" ]] || return 1
  (( ${#d} <= 253 )) || return 1
  [[ "${d}" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)+$ ]] || return 1

  for part in ${d}; do
    _is_valid_dns_label "${part}" || return 1
  done

  return 0
}

_is_valid_host_name()
{
  _is_valid_dns_label "$1"
}

_is_valid_fqdn_or_host()
{
  local x="$1"
  local host
  local dom

  [[ -n "${x}" ]] || return 1
  [[ "${x}" =~ ^[^[:space:]]+$ ]] || return 1

  if [[ "${x}" == *.* ]]; then
    host="${x%%.*}"
    dom="${x#*.}"
    _is_valid_host_name "${host}" || return 1
    _is_valid_domain_name "${dom}" || return 1
    return 0
  fi

  _is_valid_host_name "${x}"
}

_normalize_dns_name()
{
  local s="$1"

  s="${s%.}"
  echo "${s,,}"
}

###############################################################################
# Capability detection
###############################################################################

has_timeout()
{
  command -v timeout >/dev/null 2>&1
}

run_with_timeout()
{
  local t="$1"
  shift

  if has_timeout; then
    timeout "${t}" "$@"
  else
    "$@"
  fi
}

HasSystemd()
{
  command -v systemctl >/dev/null 2>&1 || return 1
  [ -d /run/systemd/system ] || return 1
  [ "$(ps -p 1 -o comm= 2>/dev/null)" = "systemd" ] || return 1
  return 0
}

HasHostnamectl()
{
  command -v hostnamectl >/dev/null 2>&1 || return 1
  HasSystemd || return 1
  run_with_timeout 2 hostnamectl status >/dev/null 2>&1 || return 1
  return 0
}

HasRealmdCapability()
{
  command -v realm >/dev/null 2>&1 && return 0
  PkgAvailable realmd
}

HasCloudInit()
{
  command -v cloud-init >/dev/null 2>&1 && return 0
  [ -f /etc/cloud/cloud.cfg ] && return 0
  return 1
}

###############################################################################
# Compatibility actions
###############################################################################

SetHostname()
{
  local new_hostname="$1"
  local help

  if HasHostnamectl; then
    help="$(hostnamectl --help 2>&1)"

    # systemd >= 249
    if echo "${help}" | grep -qE '^[[:space:]]*hostname[[:space:]]+\[NAME\]'; then
      Verbose "Set static hostname using current hostnamectl syntax"
      hostnamectl hostname "${new_hostname}" \
        && return 0
    fi

    # Older systemd
    if echo "${help}" | grep -qE '^[[:space:]]*set-hostname[[:space:]]'; then
      Verbose "Set static hostname using legacy hostnamectl syntax"
      hostnamectl set-hostname "${new_hostname}" \
        && return 0
    fi
  fi

  Verbose "Set static hostname using legacy Debian method"
  printf '%s\n' "${new_hostname}" > /etc/hostname \
    || Abend "Cannot update /etc/hostname." 1

  hostname "${new_hostname}" \
    || Abend "Cannot set runtime hostname." 1
}

ServiceEnable()
{
  local service_name="$1"

  if HasSystemd; then
    systemctl enable "${service_name}"
    return $?
  fi

  if command -v update-rc.d >/dev/null 2>&1; then
    update-rc.d "${service_name}" defaults >/dev/null
    return $?
  fi

  return 1
}

ServiceRestart()
{
  local service_name="$1"

  if HasSystemd; then
    systemctl restart "${service_name}"
    return $?
  fi

  if command -v service >/dev/null 2>&1; then
    service "${service_name}" restart
    return $?
  fi

  if [ -x "/etc/init.d/${service_name}" ]; then
    "/etc/init.d/${service_name}" restart
    return $?
  fi

  return 1
}

ConfigureCloudInitHostIdentity()
{
  local cfg

  HasCloudInit || return 0

  Verbose "Configure cloud-init to preserve locally managed host identity"

  if [ -d /etc/cloud/cloud.cfg.d ]; then
    cfg="/etc/cloud/cloud.cfg.d/99-devop-tools-hostname.cfg"

    cat > "${cfg}" <<'CLOUDCFG'
# Managed by devop-tools.
# Host identity is managed locally after initial provisioning.
preserve_hostname: true
manage_etc_hosts: false
CLOUDCFG
    return 0
  fi

  if [ -f /etc/cloud/cloud.cfg ]; then
    PatchYamlScalar preserve_hostname true /etc/cloud/cloud.cfg
    PatchYamlScalar manage_etc_hosts false /etc/cloud/cloud.cfg
    return 0
  fi

  Abend "cloud-init detected but no supported configuration location was found." 1
}

###############################################################################
# Virtualization and guest support
###############################################################################

DetectVirtualization()
{
  local virt

  if command -v systemd-detect-virt >/dev/null 2>&1; then
    virt="$(systemd-detect-virt 2>/dev/null || true)"
    [ -n "${virt}" ] || virt="none"
    echo "${virt}"
    return 0
  fi

  # Legacy fallback. Installation is handled by InstallGuestSupport before
  # entering this function so command substitution remains side-effect free.
  if ! command -v virt-what >/dev/null 2>&1; then
    echo "unknown"
    return 0
  fi

  virt="$(virt-what 2>/dev/null || true)"

  if echo "${virt}" | grep -q '^vmware$'; then
    echo "vmware"
  elif echo "${virt}" | grep -Eq '^(kvm|qemu)$'; then
    echo "kvm"
  elif echo "${virt}" | grep -q '^xen-dom0$'; then
    echo "xen-dom0"
  elif echo "${virt}" | grep -Eq '^xen($|-domU$|-hvm$)'; then
    echo "xen"
  elif [ -z "${virt}" ]; then
    echo "none"
  else
    echo "${virt}" | head -n1
  fi
}

InstallGuestSupport()
{
  local virt

  # Prefer the detector shipped with systemd. Only install virt-what on
  # systems where systemd-detect-virt is not available.
  if ! command -v systemd-detect-virt >/dev/null 2>&1 \
     && ! command -v virt-what >/dev/null 2>&1; then
    if PkgAvailable virt-what; then
      AssurePkg virt-what
    else
      Verbose "No virtualization detector available."
      return 0
    fi
  fi

  virt="$(DetectVirtualization)"
  Verbose "Detected virtualization: ${virt}"

  case "${virt}" in
    vmware)
      AssurePkgIfAvailable open-vm-tools
      ;;

    kvm|qemu)
      AssurePkgIfAvailable qemu-guest-agent
      ;;

    xen)
      AssurePkgIfAvailable xenstore-utils
      ;;

    xen-dom0)
      Verbose "Xen dom0 detected; no guest support package required."
      ;;

    none)
      Verbose "No virtualization detected."
      ;;

    *)
      Verbose "No guest support package mapping for virtualization type <${virt}>."
      ;;
  esac
}

###############################################################################
# Action functions
###############################################################################

SetupHostName()
{
  local fqdn="$1"
  local newHostName="${fqdn%%.*}"
  local oldHostName="${HOST}"
  local newDomainName="${fqdn#*.}"
  local oldDomainName
  local oldFqdn
  local newFqdn
  local oldFqdnRe
  local has127=0
  local actualFqdn

  if [ "${newHostName}" = "${fqdn}" ]; then
    newDomainName="${DOMAIN}"
  fi

  [ -n "${newDomainName}" ] \
    || Abend "Cannot establish an FQDN for <${newHostName}> because no domain is defined." 2

  oldDomainName="$(hostname -d 2>/dev/null || echo "")"

  oldFqdn="${oldHostName}"
  [ -n "${oldDomainName}" ] && oldFqdn="${oldHostName}.${oldDomainName}"

  newFqdn="${newHostName}.${newDomainName}"
  oldFqdnRe="${oldFqdn//./\\.}"

  ConfigureCloudInitHostIdentity

  # Debian-style hosts file: normalize the 127.0.1.1 entry.
  grep -qE '^[[:space:]]*127\.0\.1\.1[[:space:]]+' /etc/hosts && has127=1

  if [ ${has127} -eq 1 ]; then
    local line
    local before
    local current_norm
    local desired_norm

    line="$(grep -E '^[[:space:]]*127\.0\.1\.1[[:space:]]+' /etc/hosts | head -n1)"
    before="${line%%#*}"

    current_norm="$(echo "${before}" | tr -s '[:space:]' ' ' | sed -r 's/^ //; s/ $//')"
    desired_norm="$(echo "127.0.1.1 ${newFqdn} ${newHostName}" | tr -s '[:space:]' ' ' | sed -r 's/^ //; s/ $//')"

    if [ "${current_norm}" != "${desired_norm}" ]; then
      Verbose "Update /etc/hosts (127.0.1.1): ${current_norm} -> ${desired_norm}"

      sed -r -i \
        "s|^([[:space:]]*127\.0\.1\.1[[:space:]]+)[^#]*([[:space:]]*#.*)?$|\\1${newFqdn} ${newHostName}\\2|" \
        /etc/hosts
    else
      Verbose "/etc/hosts (127.0.1.1) already up to date - skipping."
    fi
  else
    # Custom hosts file: update only matching hostname tokens.
    local need_hosts_update=0

    if [ -n "${oldDomainName}" ] && [ "${oldDomainName}" != "${newDomainName}" ]; then
      grep -qE "^[[:space:]]*[^#]*([^[:alnum:]-]|^)${oldFqdnRe}([^[:alnum:]-]|$)" /etc/hosts \
        && need_hosts_update=1
    fi

    if [ ${need_hosts_update} -eq 0 ]; then
      grep -qE "^[[:space:]]*[^#]*([[:space:]]|^)${oldHostName}\.[[:alnum:]-]" /etc/hosts \
        && need_hosts_update=1
    fi

    if [ ${need_hosts_update} -eq 0 ]; then
      grep -qE "^[[:space:]]*[^#]*([[:space:]]|^)${oldHostName}([[:space:]]|$)" /etc/hosts \
        && need_hosts_update=1
    fi

    if [ ${need_hosts_update} -eq 1 ]; then
      Verbose "Update /etc/hosts (custom): updating host/FQDN tokens"

      sed -r -i "
        /^[[:space:]]*#/b
        h
        s/[[:space:]]*#.*$//

        $( [ -n "${oldDomainName}" ] && [ "${oldDomainName}" != "${newDomainName}" ] && \
           echo "s/(^|[[:space:]])${oldFqdnRe}([[:space:]]|$)/\\1${newFqdn}\\2/g" )

        s/(^|[[:space:]])${oldHostName}\.([[:alnum:]-][[:alnum:].-]*)([[:space:]]|$)/\\1${newHostName}.\\2\\3/g
        s/(^|[[:space:]])${oldHostName}([[:space:]]|$)/\\1${newHostName}\\2/g

        x
        s/^[^#]*//
        x
        G
        s/\n//
      " /etc/hosts
    else
      Verbose "No matching hostname tokens in /etc/hosts - skipping."
    fi
  fi

  # Always normalize the static hostname. Comparing hostname -s is not enough,
  # because a static FQDN has the same short hostname.
  Verbose "Configure static hostname: ${newHostName}"
  SetHostname "${newHostName}"

  actualFqdn="$(_normalize_dns_name "$(hostname -f 2>/dev/null || echo "")")"

  [ "${actualFqdn}" = "${newFqdn}" ] \
    || Abend "FQDN validation failed: expected <${newFqdn}>, got <${actualFqdn}>." 1

  [ -e /etc/mailname ] && printf '%s\n' "${newFqdn}" > /etc/mailname

  HOST="$(hostname -s)"
  DOMAIN="${newDomainName}"

  Verbose "Host identity validated: hostname=${HOST}, fqdn=${actualFqdn}"
}

InitDefaultSetup()
{
  Verbose "Initialize default setup"

  AssurePkg vim sudo
  InstallGuestSupport

  if [ -e /root/.bashrc ]; then
    Verbose "Patch /root/.bashrc ..."
    sed -r -e "s/^#[[:space:]]*export[[:space:]]+LS_OPTIONS=(.*)$/export LS_OPTIONS=\\1/g" -i /root/.bashrc
    sed -r -e 's/^#[[:space:]]*eval[[:space:]]+("`dircolors`")/eval \1/g' -i /root/.bashrc
    sed -r -e "s/^#[[:space:]]*alias[[:space:]]+ls=(.*)$/alias ls=\\1/g" -i /root/.bashrc
    sed -r -e "s/^#[[:space:]]*alias[[:space:]]+ll=(.*)$/alias ll=\\1\nalias la='ls \$LS_OPTIONS -la'/g" -i /root/.bashrc
  fi

  if [ -e /etc/skel/.bashrc ]; then
    Verbose "Patch /etc/skel/.bashrc ..."
    sed -r -e "s/^#[[:space:]]*alias[[:space:]]+ll=(.*)$/alias ll='ls -l'/g" -i /etc/skel/.bashrc
    sed -r -e "s/^#[[:space:]]*alias[[:space:]]+la=(.*)$/alias la='ls -la'/g" -i /etc/skel/.bashrc
  fi

  if [ -e "/home/${DEF_USER}/.bashrc" ]; then
    Verbose "Patch /home/${DEF_USER}/.bashrc ..."
    sed -r -e "s/^#[[:space:]]*alias[[:space:]]+ll=(.*)$/alias ll='ls -l'/g" -i "/home/${DEF_USER}/.bashrc"
    sed -r -e "s/^#[[:space:]]*alias[[:space:]]+la=(.*)$/alias la='ls -la'/g" -i "/home/${DEF_USER}/.bashrc"
  fi

  if getent passwd "${DEF_USER}" >/dev/null 2>&1 && getent group sudo >/dev/null 2>&1; then
    adduser "${DEF_USER}" sudo >/dev/null \
      || Abend "Cannot add <${DEF_USER}> to group <sudo>." 1
  fi
}

DoJoinDomain()
{
  local domain="$1"
  local sssd_version

  HasRealmdCapability \
    || Abend "Active Directory support requires realmd from the configured repositories." 2

  Verbose "Joining domain: ${domain}"

  AssurePkg realmd packagekit policykit-1
  mkdir -p /var/lib/samba/private

  Verbose "realm join --user=${ADMIN} ${domain}"
  realm join --user="${ADMIN}" "${domain}"
  [ $? -eq 0 ] || Abend "AD join failed." 1

  sssd_version="$(dpkg-query -W -f='${Version}' sssd 2>/dev/null || echo "")"
  Verbose "SSSD version: ${sssd_version}"

  PatchConfig enumerate "True" /etc/sssd/sssd.conf
  PatchConfig use_fully_qualified_names "False" /etc/sssd/sssd.conf
  PatchConfig fallback_homedir "/home/%d/%u" /etc/sssd/sssd.conf
  PatchConfig ldap_id_mapping "False" /etc/sssd/sssd.conf

  if [ -n "${sssd_version}" ] && dpkg --compare-versions "${sssd_version}" ge '1.12.0'; then
    PatchConfig ad_gpo_access_control "permissive" /etc/sssd/sssd.conf
  fi

  if [ -n "${sssd_version}" ] && dpkg --compare-versions "${sssd_version}" ge '2.4.1'; then
    PatchConfig ad_gpo_ignore_unreadable "True" /etc/sssd/sssd.conf
  fi

  ServiceEnable sssd || Abend "Cannot enable sssd service." 1
  ServiceRestart sssd || Abend "Cannot restart sssd service." 1

  EnsureLine \
    "session required pam_mkhomedir.so skel=/etc/skel/ umask=0022" \
    /etc/pam.d/common-session

  AssurePkg libsss-sudo

  EnsureLine "%domain\\ admins ALL=(ALL) ALL" /etc/sudoers.d/domain_admins
  EnsureLine "%domain\\ staff ALL=(ALL) ALL" /etc/sudoers.d/domain_staff
  EnsureLine "%staff ALL=(ALL) ALL" /etc/sudoers.d/domain_staff

  chmod 0440 /etc/sudoers.d/domain_admins /etc/sudoers.d/domain_staff

  echo "The computer is joined to the domain. Please reboot and verify domain connectivity before logging in with domain credentials."
}

DoLeaveDomain()
{
  local domain="$1"

  HasRealmdCapability \
    || Abend "Active Directory support requires realmd from the configured repositories." 2

  AssurePkg realmd

  Verbose "Leaving domain: ${domain}"
  Verbose "realm leave --user=${ADMIN} ${domain}"

  realm leave --user="${ADMIN}" "${domain}"
  [ $? -eq 0 ] || Abend "AD leave failed." 1

  sed '/session required pam_mkhomedir.so skel=\/etc\/skel\/ umask=0022/d' -i /etc/pam.d/common-session

  [ ! -e /etc/sudoers.d/domain_admins ] \
    || sed '/%domain\\ admins/d' -i /etc/sudoers.d/domain_admins

  [ ! -e /etc/sudoers.d/domain_staff ] \
    || sed -e '/%domain\\ staff/d' -e '/%staff/d' -i /etc/sudoers.d/domain_staff
}

SetupSSHAccess()
{
  local home_dir="$1"
  local user="$2"
  local ssh_dir="${home_dir}/.ssh"
  local user_group

  AssurePkg openssh-server

  getent passwd "${user}" >/dev/null 2>&1 \
    || Abend "Cannot setup SSH access: user <${user}> does not exist." 1

  user_group="$(id -gn "${user}")"

  if [ ! -d "${ssh_dir}" ]; then
    mkdir -p "${ssh_dir}" || Abend "Cannot create <${ssh_dir}>." 1
  fi

  chown "${user}:${user_group}" "${ssh_dir}"
  chmod 0700 "${ssh_dir}"

  if [ ! -e "${ssh_dir}/authorized_keys" ]; then
    if [ -r "${AUTHORIZED_KEYS_LOCAL}" ]; then
      cp "${AUTHORIZED_KEYS_LOCAL}" "${ssh_dir}/authorized_keys" \
        || Abend "Cannot install packaged authorized_keys." 1
    else
      AssurePkg wget
      wget -O "${ssh_dir}/authorized_keys" ${WGET_OPTS} "${AUTHORIZED_KEYS_URL}" \
        || Abend "Cannot download authorized_keys." 1
    fi
  fi

  chown "${user}:${user_group}" "${ssh_dir}/authorized_keys"
  chmod 0600 "${ssh_dir}/authorized_keys"
}

###############################################################################
# Command-line parsing
###############################################################################

# Handle the GNU-style long version option before getopts.
# Version reporting must not require root privileges or perform any host changes.
for arg in "$@"; do
  if [ "${arg}" = "--version" ]; then
    ShowVersion
    exit 0
  fi
done

while getopts ":a:d:hH:IJLSu:vV" opt; do
  case "${opt}" in
    a)
      ADMIN="${OPTARG}"
      AD_ADMIN_OPTION_USED=1
      ;;

    d)
      _d="$(_normalize_dns_name "${OPTARG}")"
      _is_valid_domain_name "${_d}" \
        || Abend "Invalid domain name format for -d: '${OPTARG}'" 2
      DOMAIN="${_d}"
      ;;

    h)
      Usage 0
      ;;

    H)
      _h="$(_normalize_dns_name "${OPTARG}")"
      _is_valid_fqdn_or_host "${_h}" \
        || Abend "Invalid host name / FQDN format for -H: '${OPTARG}'" 2
      HOST_REQUEST="${_h}"
      ACTION_REQUESTED=1
      ;;

    I)
      DO_INIT=1
      ACTION_REQUESTED=1
      ;;

    J)
      [ -z "${AD_ACTION}" ] || Abend "Options -J and -L are mutually exclusive." 2
      AD_ACTION="join"
      ACTION_REQUESTED=1
      ;;

    L)
      [ -z "${AD_ACTION}" ] || Abend "Options -J and -L are mutually exclusive." 2
      AD_ACTION="leave"
      ACTION_REQUESTED=1
      ;;

    S)
      DO_SSH=1
      ACTION_REQUESTED=1
      ;;

    u)
      DEF_USER="${OPTARG}"
      ;;

    v)
      VERBOSE=1
      ;;

    V)
      ShowVersion
      exit 0
      ;;

    \?)
      Abend "Invalid option: -${OPTARG}" 2
      ;;

    :)
      Abend "Option -${OPTARG} requires an argument." 2
      ;;
  esac
done

shift $((OPTIND - 1))

[ ${ACTION_REQUESTED} -eq 1 ] || Usage 2

###############################################################################
# Execute actions
###############################################################################

RequireRoot

# Required base tools. Keep this after argument parsing so -h never changes the host.
AssurePkg hostname sed grep

if [ -n "${AD_ACTION}" ] || [ ${AD_ADMIN_OPTION_USED} -eq 1 ]; then
  HasRealmdCapability \
    || Abend "Active Directory options are unavailable because realmd is not available from the configured repositories." 2
fi

# Deterministic execution order:
#   1. Initialize default setup
#   2. Configure host identity
#   3. Configure SSH access
#   4. Perform optional AD action
if [ ${DO_INIT} -eq 1 ]; then
  InitDefaultSetup
fi

if [ -n "${HOST_REQUEST}" ]; then
  SetupHostName "${HOST_REQUEST}"
fi

if [ ${DO_SSH} -eq 1 ]; then
  SetupSSHAccess /root root

  def_user_path="$(getent passwd "${DEF_USER}" | cut -d: -f6)"
  if [ -n "${def_user_path}" ] && [ -d "${def_user_path}" ]; then
    SetupSSHAccess "${def_user_path}" "${DEF_USER}"
  fi
fi

case "${AD_ACTION}" in
  join)
    [ -n "${DOMAIN}" ] || Abend "DOMAIN is undefined!" 2
    DoJoinDomain "${DOMAIN}"
    ;;

  leave)
    [ -n "${DOMAIN}" ] || Abend "DOMAIN is undefined!" 2
    DoLeaveDomain "${DOMAIN}"
    ;;
esac

exit 0
