setup: add FreeIPA server module and generic client script
freeipa-server.sh: interactive installer that collects domain, realm, IP, admin/DM passwords, DNS, KRA, NTP, and AWX/Ansible settings; runs conflict pre-flight (checks for existing named/dirsrv/krb5kdc, ports 389/636/88, and /etc/ipa/default.conf); configures firewalld/ufw; runs ipa-server-install; and outputs a ready-to-distribute client package to ~/freeipa-output/ containing: - freeipa-enroll.sh (server defaults baked in) - freeipa-client.sh (server defaults baked in) - freeipa-client-answerfile.json (pre-filled, password intentionally blank) - auto-enroll-ansible.sh (AWX defaults embedded, still overridable) - README.txt freeipa-client.sh: thin wrapper around freeipa-enroll.sh with three modes: --answerfile FILE read JSON with jq, build args, exec freeipa-enroll.sh --interactive prompt for every field, then exec freeipa-enroll.sh [flags] passthrough directly to freeipa-enroll.sh freeipa-client-answerfile.json: template with current server defaults (freeipa.abdelbaki.eu); freeipa-server.sh sed-replaces these when generating customized copies. Supported server OS: RHEL/Rocky/AlmaLinux/Fedora (primary), Arch (warned). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
36a938c3ec
commit
7279a781b0
|
|
@ -119,6 +119,7 @@ count_steps() {
|
|||
[[ "$sel" == *"podman"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"cockpit"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"ssh-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"freeipa-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"python"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"zfs"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"wprs"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
|
|
@ -183,6 +184,7 @@ SELECTED=$(dialog --backtitle "$BACKTITLE" \
|
|||
"podman" "Podman rootless containers · buildah" off \
|
||||
"cockpit" "Cockpit web UI · machines · podman" off \
|
||||
"ssh-server" "SSH server openssh · key-auth · enabled" off \
|
||||
"freeipa-server" "FreeIPA Server interactive server setup + client gen" off \
|
||||
"python" "Python tools pyright · pipx · pynvim" off \
|
||||
"zfs" "ZFS zfs-dkms kernel module" off \
|
||||
"wprs" "WPRS wprs-git (AUR)" off \
|
||||
|
|
@ -226,6 +228,7 @@ SUMMARY=""
|
|||
[[ "$SELECTED" == *"podman"* ]] && SUMMARY+=" ✦ Podman\n"
|
||||
[[ "$SELECTED" == *"cockpit"* ]] && SUMMARY+=" ✦ Cockpit\n"
|
||||
[[ "$SELECTED" == *"ssh-server"* ]] && SUMMARY+=" ✦ SSH server\n"
|
||||
[[ "$SELECTED" == *"freeipa-server"* ]] && SUMMARY+=" ✦ FreeIPA Server\n"
|
||||
[[ "$SELECTED" == *"python"* ]] && SUMMARY+=" ✦ Python tools\n"
|
||||
[[ "$SELECTED" == *"zfs"* ]] && SUMMARY+=" ✦ ZFS\n"
|
||||
[[ "$SELECTED" == *"wprs"* ]] && SUMMARY+=" ✦ WPRS\n"
|
||||
|
|
@ -272,6 +275,7 @@ count_steps "$SELECTED"
|
|||
[[ "$SELECTED" == *"podman"* ]] && run_module "Podman" "$APPS/podman.sh"
|
||||
[[ "$SELECTED" == *"cockpit"* ]] && run_module "Cockpit" "$APPS/cockpit.sh"
|
||||
[[ "$SELECTED" == *"ssh-server"* ]] && run_module "SSH Server" "$APPS/ssh-server.sh"
|
||||
[[ "$SELECTED" == *"freeipa-server"* ]] && run_module "FreeIPA Server" "$APPS/freeipa-server.sh"
|
||||
[[ "$SELECTED" == *"python"* ]] && run_module "Python Tools" "$MODULES/optional-Modules/python.sh"
|
||||
[[ "$SELECTED" == *"zfs"* ]] && run_module "ZFS" "$MODULES/optional-Modules/zfs.sh"
|
||||
[[ "$SELECTED" == *"wprs"* ]] && run_module "WPRS" "$MODULES/optional-Modules/wprs.sh"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"domain": "freeipa.abdelbaki.eu",
|
||||
"realm": "FREEIPA.ABDELBAKI.EU",
|
||||
"server": "freeipa.abdelbaki.eu",
|
||||
"hostname": "",
|
||||
"principal": "admin",
|
||||
"password": "",
|
||||
"mkhomedir": true,
|
||||
"sudo": true,
|
||||
"dns_update": true,
|
||||
"ntp_server": "",
|
||||
"fido2": false,
|
||||
"fido2_users": []
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
#!/bin/bash
|
||||
# freeipa-client.sh — FreeIPA client enrollment
|
||||
#
|
||||
# Three modes:
|
||||
# --answerfile FILE Read settings from a JSON file (jq required)
|
||||
# --interactive Prompt for every setting
|
||||
# [flags] Pass any flags directly to freeipa-enroll.sh
|
||||
#
|
||||
# All flags accepted by freeipa-enroll.sh work here too and override
|
||||
# any values loaded from an answerfile or interactive prompts.
|
||||
#
|
||||
# JSON answerfile schema:
|
||||
# {
|
||||
# "domain": "freeipa.abdelbaki.eu",
|
||||
# "realm": "FREEIPA.ABDELBAKI.EU",
|
||||
# "server": "freeipa.abdelbaki.eu",
|
||||
# "hostname": "", <- leave blank to use current hostname
|
||||
# "principal": "admin",
|
||||
# "password": "", <- will prompt if blank
|
||||
# "mkhomedir": true,
|
||||
# "sudo": true,
|
||||
# "dns_update": true,
|
||||
# "ntp_server": "",
|
||||
# "fido2": false,
|
||||
# "fido2_users": []
|
||||
# }
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENROLL="$SELF_DIR/freeipa-enroll.sh"
|
||||
|
||||
[[ ! -x "$ENROLL" ]] && { echo "Error: freeipa-enroll.sh not found at $ENROLL" >&2; exit 1; }
|
||||
|
||||
# Defaults (match freeipa-enroll.sh hardcoded values; freeipa-server.sh will
|
||||
# sed-replace these when generating customized copies)
|
||||
IPA_DOMAIN="freeipa.abdelbaki.eu"
|
||||
IPA_REALM="FREEIPA.ABDELBAKI.EU"
|
||||
IPA_SERVER="freeipa.abdelbaki.eu"
|
||||
|
||||
MODE=""
|
||||
ANSWERFILE=""
|
||||
PASSTHROUGH=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--answerfile) MODE="answerfile"; ANSWERFILE="$2"; shift 2 ;;
|
||||
--interactive) MODE="interactive"; shift ;;
|
||||
*) PASSTHROUGH+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ─── Answerfile mode ──────────────────────────────────────────────────────────
|
||||
if [[ "$MODE" == "answerfile" ]]; then
|
||||
[[ ! -f "$ANSWERFILE" ]] && { echo "Error: answerfile not found: $ANSWERFILE" >&2; exit 1; }
|
||||
command -v jq &>/dev/null || { echo "Error: jq is required for answerfile mode" >&2; exit 1; }
|
||||
|
||||
get() { jq -r "$1 // empty" "$ANSWERFILE"; }
|
||||
get_bool() { jq -r "$1 // empty" "$ANSWERFILE"; }
|
||||
|
||||
AF_DOMAIN=$(get '.domain')
|
||||
AF_REALM=$(get '.realm')
|
||||
AF_SERVER=$(get '.server')
|
||||
AF_HOSTNAME=$(get '.hostname')
|
||||
AF_PRINCIPAL=$(get '.principal')
|
||||
AF_PASSWORD=$(get '.password')
|
||||
AF_NTP=$(get '.ntp_server')
|
||||
AF_MKHOMEDIR=$(get_bool '.mkhomedir')
|
||||
AF_SUDO=$(get_bool '.sudo')
|
||||
AF_DNS_UPDATE=$(get_bool '.dns_update')
|
||||
AF_FIDO2=$(get_bool '.fido2')
|
||||
mapfile -t AF_FIDO2_USERS < <(jq -r '.fido2_users // [] | .[]' "$ANSWERFILE")
|
||||
|
||||
ARGS=()
|
||||
[[ -n "$AF_DOMAIN" ]] && ARGS+=(--domain "$AF_DOMAIN")
|
||||
[[ -n "$AF_REALM" ]] && ARGS+=(--realm "$AF_REALM")
|
||||
[[ -n "$AF_SERVER" ]] && ARGS+=(--server "$AF_SERVER")
|
||||
[[ -n "$AF_HOSTNAME" ]] && ARGS+=(--hostname "$AF_HOSTNAME")
|
||||
[[ -n "$AF_PRINCIPAL" ]] && ARGS+=(--principal "$AF_PRINCIPAL")
|
||||
[[ -n "$AF_NTP" ]] && ARGS+=(--ntp-server "$AF_NTP")
|
||||
|
||||
if [[ -z "$AF_PASSWORD" ]]; then
|
||||
printf '[?] Password for %s@%s: ' "${AF_PRINCIPAL:-admin}" "${AF_REALM:-REALM}"
|
||||
read -rs AF_PASSWORD; echo
|
||||
fi
|
||||
ARGS+=(--password "$AF_PASSWORD")
|
||||
|
||||
[[ "$AF_MKHOMEDIR" == "false" ]] && ARGS+=(--no-mkhomedir)
|
||||
[[ "$AF_SUDO" == "false" ]] && ARGS+=(--no-sudo)
|
||||
[[ "$AF_DNS_UPDATE" == "false" ]] && ARGS+=(--no-dns-update)
|
||||
[[ "$AF_FIDO2" == "true" ]] && ARGS+=(--fido2)
|
||||
for U in "${AF_FIDO2_USERS[@]}"; do
|
||||
[[ -n "$U" ]] && ARGS+=(--fido2-user "$U")
|
||||
done
|
||||
|
||||
exec "$ENROLL" "${ARGS[@]}" "${PASSTHROUGH[@]}"
|
||||
fi
|
||||
|
||||
# ─── Interactive mode ─────────────────────────────────────────────────────────
|
||||
if [[ "$MODE" == "interactive" ]]; then
|
||||
p() { printf '\033[0;35m[?]\033[0m %s ' "$*"; }
|
||||
|
||||
p "IPA Domain [$IPA_DOMAIN]:"; read -r I; IPA_DOMAIN="${I:-$IPA_DOMAIN}"
|
||||
IPA_REALM="${IPA_DOMAIN^^}"
|
||||
p "Kerberos Realm [$IPA_REALM]:"; read -r I; IPA_REALM="${I:-$IPA_REALM}"
|
||||
p "IPA Server [$IPA_SERVER]:"; read -r I; IPA_SERVER="${I:-$IPA_SERVER}"
|
||||
|
||||
CURRENT_HOST=$(hostname -f 2>/dev/null || hostname)
|
||||
p "This host's FQDN [$CURRENT_HOST]:"; read -r I; CLIENT_HOSTNAME="${I:-$CURRENT_HOST}"
|
||||
|
||||
p "Admin principal [admin]:"; read -r PRINCIPAL; PRINCIPAL="${PRINCIPAL:-admin}"
|
||||
p "Admin password (no echo):"; read -rs PASSWORD; echo
|
||||
[[ -z "$PASSWORD" ]] && { echo "Error: password is required" >&2; exit 1; }
|
||||
|
||||
p "Enable home directory creation? [Y/n]:"; read -r I
|
||||
MKHOMEDIR=true; [[ "${I,,}" == "n"* ]] && MKHOMEDIR=false
|
||||
|
||||
p "Configure sudo via SSSD? [Y/n]:"; read -r I
|
||||
SUDO=true; [[ "${I,,}" == "n"* ]] && SUDO=false
|
||||
|
||||
p "Update DNS record? [Y/n]:"; read -r I
|
||||
DNS_UPDATE=true; [[ "${I,,}" == "n"* ]] && DNS_UPDATE=false
|
||||
|
||||
p "NTP server (blank to skip):"; read -r NTP_SERVER
|
||||
|
||||
p "Enable FIDO2 authentication? [y/N]:"; read -r I
|
||||
FIDO2=false; [[ "${I,,}" == "y"* ]] && FIDO2=true
|
||||
|
||||
FIDO2_USER_LIST=()
|
||||
if [[ "$FIDO2" == true ]]; then
|
||||
p "FIDO2 users (comma-separated, blank to skip):"; read -r I
|
||||
if [[ -n "$I" ]]; then
|
||||
IFS=',' read -ra FIDO2_USER_LIST <<< "$I"
|
||||
fi
|
||||
fi
|
||||
|
||||
ARGS=(
|
||||
--domain "$IPA_DOMAIN"
|
||||
--realm "$IPA_REALM"
|
||||
--server "$IPA_SERVER"
|
||||
--hostname "$CLIENT_HOSTNAME"
|
||||
--principal "$PRINCIPAL"
|
||||
--password "$PASSWORD"
|
||||
)
|
||||
[[ "$MKHOMEDIR" == false ]] && ARGS+=(--no-mkhomedir)
|
||||
[[ "$SUDO" == false ]] && ARGS+=(--no-sudo)
|
||||
[[ "$DNS_UPDATE" == false ]] && ARGS+=(--no-dns-update)
|
||||
[[ "$FIDO2" == true ]] && ARGS+=(--fido2)
|
||||
[[ -n "$NTP_SERVER" ]] && ARGS+=(--ntp-server "$NTP_SERVER")
|
||||
for U in "${FIDO2_USER_LIST[@]}"; do
|
||||
[[ -n "${U// /}" ]] && ARGS+=(--fido2-user "${U// /}")
|
||||
done
|
||||
|
||||
exec "$ENROLL" "${ARGS[@]}" "${PASSTHROUGH[@]}"
|
||||
fi
|
||||
|
||||
# ─── Direct passthrough ───────────────────────────────────────────────────────
|
||||
# No --answerfile or --interactive: forward all args directly to freeipa-enroll.sh
|
||||
exec "$ENROLL" "${PASSTHROUGH[@]}"
|
||||
|
|
@ -0,0 +1,399 @@
|
|||
#!/bin/bash
|
||||
# freeipa-server.sh — Interactive FreeIPA server installer
|
||||
# Collects all configuration, installs the IPA server, and outputs customized
|
||||
# client enrollment scripts (freeipa-enroll.sh, freeipa-client.sh, answerfile,
|
||||
# and optionally auto-enroll-ansible.sh) ready for distribution.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[+]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||||
error() { echo -e "${RED}[✗]${NC} $*" >&2; }
|
||||
info() { echo -e "${CYAN}[i]${NC} $*"; }
|
||||
section() { echo -e "\n${BLUE}━━━ $* ━━━${NC}"; }
|
||||
ask() { printf "${MAGENTA}[?]${NC} %s " "$*"; }
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
IPA_BASE="$SCRIPT_DIR/../../FreeipaAnsible"
|
||||
|
||||
# ─── Root check ───────────────────────────────────────────────────────────────
|
||||
[[ $EUID -ne 0 ]] && { error "Must be run as root."; exit 1; }
|
||||
|
||||
# ─── OS detection ─────────────────────────────────────────────────────────────
|
||||
source /etc/os-release
|
||||
OS_ID="${ID}"; OS_PRETTY="${PRETTY_NAME}"
|
||||
|
||||
section "System check"
|
||||
log "OS: $OS_PRETTY"
|
||||
|
||||
case "$OS_ID" in
|
||||
rhel|centos|rocky|almalinux)
|
||||
PKG_MGR="dnf"
|
||||
IPA_SERVER_PKGS="freeipa-server freeipa-server-dns freeipa-server-trust-ad"
|
||||
ANSIBLE_PKGS="ansible-core python3-netaddr"
|
||||
;;
|
||||
fedora)
|
||||
PKG_MGR="dnf"
|
||||
IPA_SERVER_PKGS="freeipa-server freeipa-server-dns"
|
||||
ANSIBLE_PKGS="ansible-core python3-netaddr"
|
||||
;;
|
||||
arch)
|
||||
PKG_MGR="pacman"
|
||||
IPA_SERVER_PKGS="freeipa"
|
||||
ANSIBLE_PKGS="ansible python-netaddr"
|
||||
warn "Arch: FreeIPA server support is community-maintained. RHEL/Rocky recommended for production."
|
||||
;;
|
||||
*)
|
||||
error "FreeIPA server is not supported on $OS_PRETTY."
|
||||
error "Supported: RHEL, Rocky, AlmaLinux, CentOS, Fedora, Arch."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# ─── Conflict pre-flight ──────────────────────────────────────────────────────
|
||||
section "Conflict checks"
|
||||
|
||||
if [[ -f /etc/ipa/default.conf ]]; then
|
||||
if grep -q "mode=production" /etc/ipa/default.conf 2>/dev/null || \
|
||||
grep -q "^basedn=" /etc/ipa/default.conf 2>/dev/null; then
|
||||
error "/etc/ipa/default.conf exists — this host may already be an IPA server or enrolled client."
|
||||
error "Uninstall first: ipa-server-install --uninstall or ipa-client-install --uninstall"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
for svc in named dirsrv krb5kdc; do
|
||||
if systemctl is-active "$svc" &>/dev/null; then
|
||||
warn "Service '$svc' is already running and may conflict with FreeIPA."
|
||||
ask "Continue anyway? [y/N]:"; read -r C
|
||||
[[ "${C,,}" != "y"* ]] && exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
for port in 389 636 88; do
|
||||
if ss -tlnp 2>/dev/null | grep -q ":${port} " || \
|
||||
ss -ulnp 2>/dev/null | grep -q ":${port} "; then
|
||||
warn "Port $port is already in use. FreeIPA requires this port."
|
||||
ask "Continue anyway? [y/N]:"; read -r C
|
||||
[[ "${C,,}" != "y"* ]] && exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
log "Conflict checks passed."
|
||||
|
||||
# ─── Gather configuration ─────────────────────────────────────────────────────
|
||||
section "Configuration"
|
||||
info "Leave any field blank to accept the value shown in [brackets]."
|
||||
echo
|
||||
|
||||
CURRENT_FQDN=$(hostname -f 2>/dev/null || hostname)
|
||||
ask "Server FQDN [$CURRENT_FQDN]:"; read -r I
|
||||
SERVER_HOSTNAME="${I:-$CURRENT_FQDN}"
|
||||
|
||||
GUESSED_DOMAIN="${SERVER_HOSTNAME#*.}"
|
||||
ask "IPA Domain [$GUESSED_DOMAIN]:"; read -r I
|
||||
IPA_DOMAIN="${I:-$GUESSED_DOMAIN}"
|
||||
|
||||
GUESSED_REALM="${IPA_DOMAIN^^}"
|
||||
ask "Kerberos Realm [$GUESSED_REALM]:"; read -r I
|
||||
IPA_REALM="${I:-$GUESSED_REALM}"
|
||||
|
||||
GUESSED_IP=$(ip route get 1 2>/dev/null | awk '{print $7; exit}')
|
||||
ask "Server IP address [$GUESSED_IP]:"; read -r I
|
||||
SERVER_IP="${I:-$GUESSED_IP}"
|
||||
|
||||
while true; do
|
||||
ask "IPA admin password (min 8 chars, no echo):"; read -rs ADMIN_PASSWORD; echo
|
||||
ask "Confirm admin password:"; read -rs ADMIN_PASSWORD2; echo
|
||||
[[ "$ADMIN_PASSWORD" == "$ADMIN_PASSWORD2" && ${#ADMIN_PASSWORD} -ge 8 ]] && break
|
||||
warn "Passwords do not match or are too short."
|
||||
done
|
||||
|
||||
while true; do
|
||||
ask "Directory Manager password (min 8 chars, no echo):"; read -rs DM_PASSWORD; echo
|
||||
ask "Confirm Directory Manager password:"; read -rs DM_PASSWORD2; echo
|
||||
[[ "$DM_PASSWORD" == "$DM_PASSWORD2" && ${#DM_PASSWORD} -ge 8 ]] && break
|
||||
warn "Passwords do not match or are too short."
|
||||
done
|
||||
|
||||
ask "Set up integrated DNS (bind) with FreeIPA? [Y/n]:"; read -r I
|
||||
SETUP_DNS=true; [[ "${I,,}" == "n"* ]] && SETUP_DNS=false
|
||||
|
||||
DNS_FORWARDER=""
|
||||
AUTO_REVERSE=true
|
||||
if [[ "$SETUP_DNS" == true ]]; then
|
||||
ask "DNS forwarder IP (blank for no forwarding):"; read -r DNS_FORWARDER
|
||||
ask "Auto-create reverse DNS zone? [Y/n]:"; read -r I
|
||||
[[ "${I,,}" == "n"* ]] && AUTO_REVERSE=false
|
||||
fi
|
||||
|
||||
ask "NTP server (blank for system default):"; read -r NTP_SERVER
|
||||
|
||||
ask "Install KRA (Key Recovery Authority)? [y/N]:"; read -r I
|
||||
SETUP_KRA=false; [[ "${I,,}" == "y"* ]] && SETUP_KRA=true
|
||||
|
||||
echo
|
||||
ask "Enable Ansible/AWX auto-enrollment integration? [y/N]:"; read -r I
|
||||
SETUP_ANSIBLE=false; [[ "${I,,}" == "y"* ]] && SETUP_ANSIBLE=true
|
||||
|
||||
AWX_URL=""; AWX_TOKEN=""; AWX_INVENTORY=""
|
||||
if [[ "$SETUP_ANSIBLE" == true ]]; then
|
||||
ask "AWX/Controller URL (e.g. https://awx.corp.example.com):"; read -r AWX_URL
|
||||
ask "AWX API token:"; read -r AWX_TOKEN
|
||||
ask "AWX inventory name:"; read -r AWX_INVENTORY
|
||||
fi
|
||||
|
||||
DEFAULT_OUTDIR="$(eval echo ~"${SUDO_USER:-root}")/freeipa-output"
|
||||
ask "Output directory for client scripts [$DEFAULT_OUTDIR]:"; read -r I
|
||||
OUTPUT_DIR="${I:-$DEFAULT_OUTDIR}"
|
||||
|
||||
# ─── Confirm ──────────────────────────────────────────────────────────────────
|
||||
section "Confirm"
|
||||
echo
|
||||
printf " %-22s %s\n" "Hostname:" "$SERVER_HOSTNAME"
|
||||
printf " %-22s %s\n" "Domain:" "$IPA_DOMAIN"
|
||||
printf " %-22s %s\n" "Realm:" "$IPA_REALM"
|
||||
printf " %-22s %s\n" "IP:" "$SERVER_IP"
|
||||
printf " %-22s %s\n" "Integrated DNS:" "$SETUP_DNS"
|
||||
[[ -n "$DNS_FORWARDER" ]] && printf " %-22s %s\n" "DNS Forwarder:" "$DNS_FORWARDER"
|
||||
printf " %-22s %s\n" "Install KRA:" "$SETUP_KRA"
|
||||
printf " %-22s %s\n" "Ansible/AWX:" "$SETUP_ANSIBLE"
|
||||
[[ "$SETUP_ANSIBLE" == true ]] && printf " %-22s %s\n" "AWX URL:" "$AWX_URL"
|
||||
printf " %-22s %s\n" "Output dir:" "$OUTPUT_DIR"
|
||||
echo
|
||||
ask "Proceed with installation? [y/N]:"; read -r CONFIRM
|
||||
[[ "${CONFIRM,,}" != "y"* ]] && { echo "Aborted."; exit 0; }
|
||||
|
||||
# ─── Hostname ─────────────────────────────────────────────────────────────────
|
||||
section "Configuring hostname"
|
||||
hostnamectl set-hostname "$SERVER_HOSTNAME"
|
||||
SHORT_NAME="${SERVER_HOSTNAME%%.*}"
|
||||
sed -i "/\b${SERVER_HOSTNAME}\b/d" /etc/hosts
|
||||
sed -i "/\b${SHORT_NAME}\b/d" /etc/hosts
|
||||
echo "$SERVER_IP $SERVER_HOSTNAME $SHORT_NAME" >> /etc/hosts
|
||||
log "Hostname: $SERVER_HOSTNAME → $SERVER_IP"
|
||||
|
||||
# ─── Packages ─────────────────────────────────────────────────────────────────
|
||||
section "Installing packages"
|
||||
case "$PKG_MGR" in
|
||||
dnf)
|
||||
dnf install -y $IPA_SERVER_PKGS
|
||||
[[ "$SETUP_ANSIBLE" == true ]] && dnf install -y $ANSIBLE_PKGS
|
||||
;;
|
||||
pacman)
|
||||
pacman -Sy --noconfirm $IPA_SERVER_PKGS
|
||||
[[ "$SETUP_ANSIBLE" == true ]] && pacman -Sy --noconfirm $ANSIBLE_PKGS
|
||||
;;
|
||||
esac
|
||||
log "Packages installed."
|
||||
|
||||
# ─── Firewall ────────────────────────────────────────────────────────────────
|
||||
section "Configuring firewall"
|
||||
if command -v firewall-cmd &>/dev/null && systemctl is-active firewalld &>/dev/null; then
|
||||
for svc in freeipa-ldap freeipa-ldaps kerberos kpasswd https ntp; do
|
||||
firewall-cmd --permanent --add-service="$svc"
|
||||
done
|
||||
[[ "$SETUP_DNS" == true ]] && firewall-cmd --permanent --add-service=dns
|
||||
firewall-cmd --reload
|
||||
log "firewalld rules applied."
|
||||
elif command -v ufw &>/dev/null; then
|
||||
for rule in 80/tcp 443/tcp 389/tcp 636/tcp 88/tcp 88/udp 464/tcp 464/udp 123/udp; do
|
||||
ufw allow "$rule"
|
||||
done
|
||||
[[ "$SETUP_DNS" == true ]] && { ufw allow 53/tcp; ufw allow 53/udp; }
|
||||
log "UFW rules applied."
|
||||
else
|
||||
warn "No firewall manager found — manually open: 80,443,389,636,88,464,123$([ "$SETUP_DNS" == true ] && echo ",53")"
|
||||
fi
|
||||
|
||||
# ─── ipa-server-install ───────────────────────────────────────────────────────
|
||||
section "Installing FreeIPA server (this takes several minutes)"
|
||||
|
||||
IPA_ARGS=(
|
||||
--realm="$IPA_REALM"
|
||||
--domain="$IPA_DOMAIN"
|
||||
--admin-password="$ADMIN_PASSWORD"
|
||||
--ds-password="$DM_PASSWORD"
|
||||
--hostname="$SERVER_HOSTNAME"
|
||||
--ip-address="$SERVER_IP"
|
||||
--mkhomedir
|
||||
--unattended
|
||||
)
|
||||
|
||||
if [[ "$SETUP_DNS" == true ]]; then
|
||||
IPA_ARGS+=(--setup-dns)
|
||||
[[ -n "$DNS_FORWARDER" ]] && IPA_ARGS+=(--forwarder="$DNS_FORWARDER") || IPA_ARGS+=(--no-forwarders)
|
||||
[[ "$AUTO_REVERSE" == true ]] && IPA_ARGS+=(--auto-reverse) || IPA_ARGS+=(--no-reverse)
|
||||
fi
|
||||
[[ -n "$NTP_SERVER" ]] && IPA_ARGS+=(--ntp-server="$NTP_SERVER")
|
||||
[[ "$SETUP_KRA" == true ]] && IPA_ARGS+=(--setup-kra)
|
||||
|
||||
ipa-server-install "${IPA_ARGS[@]}"
|
||||
log "FreeIPA server installed."
|
||||
|
||||
# ─── Post-install ────────────────────────────────────────────────────────────
|
||||
section "Post-install verification"
|
||||
if echo "$ADMIN_PASSWORD" | kinit admin &>/dev/null; then
|
||||
log "Kerberos: admin ticket obtained."
|
||||
kdestroy &>/dev/null
|
||||
else
|
||||
warn "Could not obtain Kerberos ticket — check connectivity."
|
||||
fi
|
||||
|
||||
# ─── Ansible integration ─────────────────────────────────────────────────────
|
||||
if [[ "$SETUP_ANSIBLE" == true ]]; then
|
||||
section "Setting up Ansible auto-enrollment"
|
||||
cp "$IPA_BASE/ansible/ansipa-install-packages.sh" /usr/local/bin/
|
||||
cp "$IPA_BASE/ansible/auto-add-baseuser.sh" /usr/local/bin/
|
||||
chmod +x /usr/local/bin/ansipa-install-packages.sh /usr/local/bin/auto-add-baseuser.sh
|
||||
|
||||
for f in ansipa-install.service ansipa-install.timer \
|
||||
baseuser-sync.path baseuser-sync.service; do
|
||||
cp "$IPA_BASE/ansible/$f" /etc/systemd/system/
|
||||
done
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now ansipa-install.timer
|
||||
systemctl enable --now baseuser-sync.path
|
||||
log "Ansible auto-enrollment services enabled."
|
||||
fi
|
||||
|
||||
# ─── Generate output scripts ──────────────────────────────────────────────────
|
||||
section "Generating client scripts → $OUTPUT_DIR"
|
||||
REAL_OUTDIR="${OUTPUT_DIR/#\~/"$(eval echo ~"${SUDO_USER:-root}")"/}"
|
||||
mkdir -p "$REAL_OUTDIR"
|
||||
|
||||
# 1. Customized freeipa-enroll.sh (server defaults baked in)
|
||||
sed \
|
||||
-e "s|^IPA_DOMAIN=.*|IPA_DOMAIN=\"$IPA_DOMAIN\"|" \
|
||||
-e "s|^IPA_REALM=.*|IPA_REALM=\"$IPA_REALM\"|" \
|
||||
-e "s|^IPA_SERVER=.*|IPA_SERVER=\"$SERVER_HOSTNAME\"|" \
|
||||
"$IPA_BASE/freeipa-enroll.sh" > "$REAL_OUTDIR/freeipa-enroll.sh"
|
||||
chmod +x "$REAL_OUTDIR/freeipa-enroll.sh"
|
||||
log "freeipa-enroll.sh"
|
||||
|
||||
# 2. Customized freeipa-client.sh (same server defaults)
|
||||
sed \
|
||||
-e "s|^IPA_DOMAIN=.*|IPA_DOMAIN=\"$IPA_DOMAIN\"|" \
|
||||
-e "s|^IPA_REALM=.*|IPA_REALM=\"$IPA_REALM\"|" \
|
||||
-e "s|^IPA_SERVER=.*|IPA_SERVER=\"$SERVER_HOSTNAME\"|" \
|
||||
"$IPA_BASE/freeipa-client.sh" > "$REAL_OUTDIR/freeipa-client.sh"
|
||||
chmod +x "$REAL_OUTDIR/freeipa-client.sh"
|
||||
log "freeipa-client.sh"
|
||||
|
||||
# 3. JSON answerfile (pre-filled; password intentionally left blank)
|
||||
cat > "$REAL_OUTDIR/freeipa-client-answerfile.json" <<JSONEOF
|
||||
{
|
||||
"domain": "$IPA_DOMAIN",
|
||||
"realm": "$IPA_REALM",
|
||||
"server": "$SERVER_HOSTNAME",
|
||||
"hostname": "",
|
||||
"principal": "admin",
|
||||
"password": "",
|
||||
"mkhomedir": true,
|
||||
"sudo": true,
|
||||
"dns_update": true,
|
||||
"ntp_server": "${NTP_SERVER:-}",
|
||||
"fido2": false,
|
||||
"fido2_users": []
|
||||
}
|
||||
JSONEOF
|
||||
log "freeipa-client-answerfile.json"
|
||||
|
||||
# 4. Customized auto-enroll-ansible.sh (AWX defaults baked in, still overridable)
|
||||
if [[ "$SETUP_ANSIBLE" == true ]]; then
|
||||
{
|
||||
printf '#!/usr/bin/env bash\nset -e\n\n'
|
||||
printf '# Generated by freeipa-server.sh for server: %s\n' "$SERVER_HOSTNAME"
|
||||
printf '# Override via env vars or positional args:\n'
|
||||
printf '# CONTROLLER_URL=... ./auto-enroll-ansible.sh\n'
|
||||
printf '# ./auto-enroll-ansible.sh <url> <token> <inventory>\n\n'
|
||||
printf 'CONTROLLER_URL="${CONTROLLER_URL:-${1:-%s}}"\n' "$AWX_URL"
|
||||
printf 'API_TOKEN="${API_TOKEN:-${2:-%s}}"\n' "$AWX_TOKEN"
|
||||
printf 'INVENTORY_NAME="${INVENTORY_NAME:-${3:-%s}}"\n\n' "$AWX_INVENTORY"
|
||||
# Include body of original (skip shebang + old arg block)
|
||||
awk '/^INVENTORY_NAME=/{found=1; next} found{print}' \
|
||||
"$IPA_BASE/auto-enroll-ansible.sh"
|
||||
} > "$REAL_OUTDIR/auto-enroll-ansible.sh"
|
||||
chmod +x "$REAL_OUTDIR/auto-enroll-ansible.sh"
|
||||
log "auto-enroll-ansible.sh"
|
||||
fi
|
||||
|
||||
# 5. README
|
||||
cat > "$REAL_OUTDIR/README.txt" <<READMEEOF
|
||||
FreeIPA Client Scripts — generated $(date)
|
||||
==========================================
|
||||
Server: $SERVER_HOSTNAME
|
||||
Domain: $IPA_DOMAIN
|
||||
Realm: $IPA_REALM
|
||||
|
||||
─── Files ───────────────────────────────────────────────────
|
||||
|
||||
freeipa-client-answerfile.json
|
||||
JSON answerfile with server defaults pre-filled.
|
||||
Fill in "password" (and optionally "hostname") before use.
|
||||
|
||||
freeipa-client.sh [recommended]
|
||||
Flexible client enrollment script. Modes:
|
||||
|
||||
# JSON answerfile (fill in password first):
|
||||
sudo ./freeipa-client.sh --answerfile freeipa-client-answerfile.json
|
||||
|
||||
# Fully interactive:
|
||||
sudo ./freeipa-client.sh --interactive
|
||||
|
||||
# Direct CLI flags (same as freeipa-enroll.sh):
|
||||
sudo ./freeipa-client.sh --password <pw>
|
||||
|
||||
freeipa-enroll.sh
|
||||
Direct enrollment with server defaults baked in.
|
||||
Accepts full set of CLI flags for one-liner use:
|
||||
sudo ./freeipa-enroll.sh --password <pw>
|
||||
|
||||
$([ "$SETUP_ANSIBLE" == "true" ] && cat <<'AWXEOF'
|
||||
auto-enroll-ansible.sh
|
||||
Registers this client in AWX/Controller inventory after enrollment.
|
||||
AWX URL and inventory are pre-configured; API token can be overridden:
|
||||
./auto-enroll-ansible.sh
|
||||
API_TOKEN=mytoken ./auto-enroll-ansible.sh
|
||||
|
||||
AWXEOF
|
||||
)─── Quick start on a client machine ────────────────────────
|
||||
|
||||
1. Copy this directory to the client (scp, croc, etc.)
|
||||
2. Fill in "password" in freeipa-client-answerfile.json
|
||||
3. sudo ./freeipa-client.sh --answerfile freeipa-client-answerfile.json
|
||||
4. (optional) ./auto-enroll-ansible.sh
|
||||
|
||||
─── Manual registration after enrollment ───────────────────
|
||||
|
||||
• List FIDO2 credentials: cat /etc/u2f_mappings
|
||||
• Register a FIDO2 key: pamu2fcfg -u <user> -o pam://\$(hostname -f) >> /etc/u2f_mappings
|
||||
• Check SSSD: sssctl domain-status $IPA_DOMAIN
|
||||
• Get Kerberos ticket: kinit admin@$IPA_REALM
|
||||
|
||||
READMEEOF
|
||||
log "README.txt"
|
||||
|
||||
# ─── Summary ─────────────────────────────────────────────────────────────────
|
||||
section "Done"
|
||||
cat <<EOF
|
||||
|
||||
FreeIPA Server: $SERVER_HOSTNAME
|
||||
Web UI: https://$SERVER_HOSTNAME/ipa/ui
|
||||
Domain: $IPA_DOMAIN
|
||||
Realm: $IPA_REALM
|
||||
Admin: admin@$IPA_REALM
|
||||
DNS: $SETUP_DNS
|
||||
KRA: $SETUP_KRA
|
||||
Ansible: $SETUP_ANSIBLE
|
||||
|
||||
Client scripts → $REAL_OUTDIR/
|
||||
freeipa-client.sh + freeipa-client-answerfile.json
|
||||
freeipa-enroll.sh
|
||||
$([ "$SETUP_ANSIBLE" == "true" ] && echo " auto-enroll-ansible.sh")
|
||||
README.txt
|
||||
|
||||
EOF
|
||||
|
|
@ -129,6 +129,7 @@ count_steps() {
|
|||
[[ "$a" == *"podman"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"cockpit"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"ssh-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"freeipa-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"python"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"zfs"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"wprs"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
|
|
@ -218,6 +219,7 @@ SELECTED_APPS=$(dialog --backtitle "$BACKTITLE" \
|
|||
"podman" "Podman rootless containers · buildah" off \
|
||||
"cockpit" "Cockpit web UI · machines · podman" off \
|
||||
"ssh-server" "SSH server openssh · key-auth · enabled" off \
|
||||
"freeipa-server" "FreeIPA Server interactive server setup + client gen" off \
|
||||
"python" "Python tools pyright · pipx · pynvim" off \
|
||||
"zfs" "ZFS zfs-dkms kernel module" off \
|
||||
"wprs" "WPRS wprs-git (AUR)" off \
|
||||
|
|
@ -268,6 +270,7 @@ if [[ -n "$SELECTED_APPS" ]]; then
|
|||
[[ "$SELECTED_APPS" == *"podman"* ]] && SUMMARY+=" ✦ Podman (rootless) + Buildah\n"
|
||||
[[ "$SELECTED_APPS" == *"cockpit"* ]] && SUMMARY+=" ✦ Cockpit web UI\n"
|
||||
[[ "$SELECTED_APPS" == *"ssh-server"* ]] && SUMMARY+=" ✦ SSH server (openssh, key auth)\n"
|
||||
[[ "$SELECTED_APPS" == *"freeipa-server"* ]] && SUMMARY+=" ✦ FreeIPA Server\n"
|
||||
[[ "$SELECTED_APPS" == *"python"* ]] && SUMMARY+=" ✦ Python tools\n"
|
||||
[[ "$SELECTED_APPS" == *"zfs"* ]] && SUMMARY+=" ✦ ZFS\n"
|
||||
[[ "$SELECTED_APPS" == *"wprs"* ]] && SUMMARY+=" ✦ WPRS\n"
|
||||
|
|
@ -329,6 +332,7 @@ fi
|
|||
[[ "$SELECTED_APPS" == *"podman"* ]] && run_module "Podman" "$APPS/podman.sh"
|
||||
[[ "$SELECTED_APPS" == *"cockpit"* ]] && run_module "Cockpit" "$APPS/cockpit.sh"
|
||||
[[ "$SELECTED_APPS" == *"ssh-server"* ]] && run_module "SSH Server" "$APPS/ssh-server.sh"
|
||||
[[ "$SELECTED_APPS" == *"freeipa-server"* ]] && run_module "FreeIPA Server" "$APPS/freeipa-server.sh"
|
||||
[[ "$SELECTED_APPS" == *"python"* ]] && run_module "Python Tools" "$MODULES/optional-Modules/python.sh"
|
||||
[[ "$SELECTED_APPS" == *"zfs"* ]] && run_module "ZFS" "$MODULES/optional-Modules/zfs.sh"
|
||||
[[ "$SELECTED_APPS" == *"wprs"* ]] && run_module "WPRS" "$MODULES/optional-Modules/wprs.sh"
|
||||
|
|
|
|||
Loading…
Reference in New Issue