setup: add freeipa-client module and FreeIPA group-based module automation
- Add freeipa-client module (sssd, cyrus-sasl-gssapi, freeipa-client AUR) with post-install enrollment hints; wired into tui-install.sh and install-modules.sh - Add ansipa-install-modules.sh: reads IPA host groups named ansipa-module-<name>, applies matching module scripts via a yay wrapper that drops to ANSIPA_USER so AUR builds work from the root service - Add ansipa-install-modules.service + .timer (boot + 30 min) - Add deploy-ansipa-modules.yml Ansible playbook that deploys scripts, writes /etc/ansipa-modules.conf, and enables the timer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
9e708556d5
commit
c51af40fce
|
|
@ -119,6 +119,7 @@ count_steps() {
|
|||
[[ "$sel" == *"podman"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"cockpit"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"ssh-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"freeipa-client"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"freeipa-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"freeipa-image"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$sel" == *"python"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
|
|
@ -185,6 +186,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-client" "FreeIPA Client sssd + ipa-client-install + enrollment" off \
|
||||
"freeipa-server" "FreeIPA Server interactive server setup + client gen" off \
|
||||
"freeipa-image" "FreeIPA Image OCI/LXC/Proxmox LXC builder + Keycloak" off \
|
||||
"python" "Python tools pyright · pipx · pynvim" off \
|
||||
|
|
@ -230,6 +232,7 @@ SUMMARY=""
|
|||
[[ "$SELECTED" == *"podman"* ]] && SUMMARY+=" ✦ Podman\n"
|
||||
[[ "$SELECTED" == *"cockpit"* ]] && SUMMARY+=" ✦ Cockpit\n"
|
||||
[[ "$SELECTED" == *"ssh-server"* ]] && SUMMARY+=" ✦ SSH server\n"
|
||||
[[ "$SELECTED" == *"freeipa-client"* ]] && SUMMARY+=" ✦ FreeIPA Client\n"
|
||||
[[ "$SELECTED" == *"freeipa-server"* ]] && SUMMARY+=" ✦ FreeIPA Server\n"
|
||||
[[ "$SELECTED" == *"freeipa-image"* ]] && SUMMARY+=" ✦ FreeIPA Image Builder\n"
|
||||
[[ "$SELECTED" == *"python"* ]] && SUMMARY+=" ✦ Python tools\n"
|
||||
|
|
@ -278,6 +281,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-client"* ]] && run_module "FreeIPA Client" "$APPS/freeipa-client.sh"
|
||||
[[ "$SELECTED" == *"freeipa-server"* ]] && run_module "FreeIPA Server" "$APPS/freeipa-server.sh"
|
||||
[[ "$SELECTED" == *"freeipa-image"* ]] && run_module "FreeIPA Image" "$APPS/freeipa-image-builder.sh"
|
||||
[[ "$SELECTED" == *"python"* ]] && run_module "Python Tools" "$MODULES/optional-Modules/python.sh"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Apply setup modules based on FreeIPA ansipa-module-* host groups
|
||||
After=network-online.target sssd.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/ansipa-install-modules.sh
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env bash
|
||||
# ansipa-install-modules.sh — apply setup modules to this host based on
|
||||
# FreeIPA host group membership.
|
||||
#
|
||||
# Host groups follow the naming convention:
|
||||
# ansipa-module-<name> e.g. ansipa-module-docker, ansipa-module-ollama
|
||||
#
|
||||
# When this host is a member of such a group, the corresponding module
|
||||
# script in /usr/local/lib/ansipa-modules/<name>.sh is executed (once,
|
||||
# stamped in /var/lib/ansipa-modules/).
|
||||
#
|
||||
# Configuration: /etc/ansipa-modules.conf
|
||||
# ANSIPA_USER=<username> non-root user for AUR helper (yay)
|
||||
# MODULES_DIR=/usr/local/lib/ansipa-modules
|
||||
# STATE_DIR=/var/lib/ansipa-modules
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG=/etc/ansipa-modules.conf
|
||||
[[ -f "$CONFIG" ]] && source "$CONFIG"
|
||||
|
||||
ANSIPA_USER="${ANSIPA_USER:-}"
|
||||
MODULES_DIR="${MODULES_DIR:-/usr/local/lib/ansipa-modules}"
|
||||
STATE_DIR="${STATE_DIR:-/var/lib/ansipa-modules}"
|
||||
PREFIX="ansipa-module-"
|
||||
LOG_TAG="ansipa-modules"
|
||||
|
||||
log() { echo "[$LOG_TAG] $*"; logger -t "$LOG_TAG" "$*" 2>/dev/null || true; }
|
||||
warn() { echo "[$LOG_TAG][WARN] $*" >&2; logger -t "$LOG_TAG" "WARN: $*" 2>/dev/null || true; }
|
||||
|
||||
# ── Resolve ANSIPA_USER ───────────────────────────────────────────────────────
|
||||
if [[ -z "$ANSIPA_USER" ]]; then
|
||||
# Use the first non-root, non-system user with a login shell
|
||||
ANSIPA_USER=$(awk -F: '($3>=1000 && $7!~/nologin|false/) {print $1; exit}' /etc/passwd)
|
||||
fi
|
||||
if [[ -z "$ANSIPA_USER" ]]; then
|
||||
warn "Cannot determine ANSIPA_USER. Set it in $CONFIG."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Running as root, AUR helper delegated to user: $ANSIPA_USER"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
# ── Create a yay wrapper so module scripts can call 'yay' as non-root ────────
|
||||
YAY_BIN=$(command -v yay 2>/dev/null || true)
|
||||
WRAP_DIR=$(mktemp -d /tmp/ansipa-wrap.XXXXXX)
|
||||
trap 'rm -rf "$WRAP_DIR"' EXIT
|
||||
|
||||
if [[ -n "$YAY_BIN" ]]; then
|
||||
cat > "$WRAP_DIR/yay" <<EOF
|
||||
#!/bin/bash
|
||||
exec sudo -u "$ANSIPA_USER" "$YAY_BIN" "\$@"
|
||||
EOF
|
||||
chmod +x "$WRAP_DIR/yay"
|
||||
fi
|
||||
|
||||
# ── Discover which ansipa-module-* host groups this host belongs to ───────────
|
||||
HOST_FQDN=$(hostname -f 2>/dev/null || hostname)
|
||||
|
||||
if ! command -v ipa &>/dev/null; then
|
||||
warn "ipa command not found — host not enrolled in FreeIPA. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# kinit with host keytab so IPA commands work from the service context
|
||||
kinit -k "host/$HOST_FQDN" &>/dev/null || true
|
||||
|
||||
RAW_GROUPS=$(ipa host-show "$HOST_FQDN" --all 2>/dev/null \
|
||||
| grep -i "Member of host-groups:" | sed 's/.*: //' || true)
|
||||
|
||||
if [[ -z "$RAW_GROUPS" ]]; then
|
||||
log "Host '$HOST_FQDN' is not a member of any host groups — nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse comma-separated list, keep only ansipa-module-* entries
|
||||
WANTED_MODULES=()
|
||||
while IFS=',' read -ra GRP_ARRAY; do
|
||||
for g in "${GRP_ARRAY[@]}"; do
|
||||
g="${g// /}" # strip spaces
|
||||
if [[ "$g" == ${PREFIX}* ]]; then
|
||||
WANTED_MODULES+=("${g#$PREFIX}")
|
||||
fi
|
||||
done
|
||||
done <<< "$RAW_GROUPS"
|
||||
|
||||
if [[ ${#WANTED_MODULES[@]} -eq 0 ]]; then
|
||||
log "No ansipa-module-* host groups found for '$HOST_FQDN'."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Modules requested for this host: ${WANTED_MODULES[*]}"
|
||||
|
||||
# ── Apply each module ─────────────────────────────────────────────────────────
|
||||
for MODULE in "${WANTED_MODULES[@]}"; do
|
||||
STAMP="$STATE_DIR/${MODULE}.done"
|
||||
SCRIPT="$MODULES_DIR/${MODULE}.sh"
|
||||
|
||||
if [[ -f "$STAMP" ]]; then
|
||||
log "Module '$MODULE' already applied (stamp: $STAMP) — skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SCRIPT" ]]; then
|
||||
warn "Module script not found: $SCRIPT — skipping '$MODULE'."
|
||||
continue
|
||||
fi
|
||||
|
||||
log "Applying module: $MODULE"
|
||||
if env PATH="$WRAP_DIR:$PATH" bash "$SCRIPT" >>"$STATE_DIR/${MODULE}.log" 2>&1; then
|
||||
touch "$STAMP"
|
||||
log "Module '$MODULE' applied successfully."
|
||||
else
|
||||
warn "Module '$MODULE' failed — see $STATE_DIR/${MODULE}.log"
|
||||
fi
|
||||
done
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Periodic FreeIPA module sync
|
||||
|
||||
[Timer]
|
||||
OnBootSec=3min
|
||||
OnUnitActiveSec=30min
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
# deploy-ansipa-modules.yml — deploy the module auto-installer to enrolled hosts.
|
||||
#
|
||||
# Prerequisites on target hosts:
|
||||
# - FreeIPA client enrolled (sssd running, ipa command available)
|
||||
# - A non-root user with yay access (set ANSIPA_USER in /etc/ansipa-modules.conf)
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory deploy-ansipa-modules.yml
|
||||
# ansible-playbook -i inventory deploy-ansipa-modules.yml -e ansipa_user=amir
|
||||
#
|
||||
# FreeIPA host group convention:
|
||||
# Create host groups named ansipa-module-<name> (e.g. ansipa-module-docker)
|
||||
# and add hosts to them. The timer will apply the matching module automatically.
|
||||
|
||||
- name: Deploy FreeIPA module auto-installer
|
||||
hosts: all
|
||||
become: yes
|
||||
|
||||
vars:
|
||||
ansipa_user: "{{ lookup('env', 'ANSIPA_USER') | default('', true) }}"
|
||||
modules_dir: /usr/local/lib/ansipa-modules
|
||||
state_dir: /var/lib/ansipa-modules
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Create module directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop:
|
||||
- "{{ modules_dir }}"
|
||||
- "{{ state_dir }}"
|
||||
|
||||
- name: Write /etc/ansipa-modules.conf
|
||||
copy:
|
||||
dest: /etc/ansipa-modules.conf
|
||||
mode: '0644'
|
||||
content: |
|
||||
# ansipa-modules configuration
|
||||
# ANSIPA_USER: non-root user used to run the AUR helper (yay).
|
||||
# Leave blank to auto-detect the first non-system user.
|
||||
ANSIPA_USER={{ ansipa_user }}
|
||||
MODULES_DIR={{ modules_dir }}
|
||||
STATE_DIR={{ state_dir }}
|
||||
when: ansipa_user != ""
|
||||
|
||||
- name: Deploy main module installer script
|
||||
copy:
|
||||
src: ansipa-install-modules.sh
|
||||
dest: /usr/local/bin/ansipa-install-modules.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Deploy module scripts
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: "{{ modules_dir }}/{{ item | basename }}"
|
||||
mode: '0755'
|
||||
with_fileglob:
|
||||
- "../optional-Modules/apps/*.sh"
|
||||
|
||||
- name: Install systemd service
|
||||
copy:
|
||||
dest: /etc/systemd/system/ansipa-install-modules.service
|
||||
mode: '0644'
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Apply setup modules based on FreeIPA ansipa-module-* host groups
|
||||
After=network-online.target sssd.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/ansipa-install-modules.sh
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
- name: Install systemd timer
|
||||
copy:
|
||||
dest: /etc/systemd/system/ansipa-install-modules.timer
|
||||
mode: '0644'
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Periodic FreeIPA module sync
|
||||
|
||||
[Timer]
|
||||
OnBootSec=3min
|
||||
OnUnitActiveSec=30min
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
- name: Reload systemd
|
||||
command: systemctl daemon-reload
|
||||
|
||||
- name: Enable and start module timer
|
||||
systemd:
|
||||
name: ansipa-install-modules.timer
|
||||
enabled: yes
|
||||
state: started
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# FreeIPA client — installs client packages and optionally enrolls this host.
|
||||
# Packages: sssd + cyrus-sasl-gssapi from pacman; freeipa-client (AUR) for
|
||||
# ipa-client-install, ipa-getkeytab, etc.
|
||||
|
||||
PACMAN_PKGS=(sssd cyrus-sasl-gssapi openldap krb5 oddjob)
|
||||
AUR_PKGS=(freeipa-client)
|
||||
|
||||
echo "[+] Installing FreeIPA client packages..."
|
||||
pacman -S --noconfirm --needed "${PACMAN_PKGS[@]}"
|
||||
|
||||
if command -v yay &>/dev/null; then
|
||||
echo "[+] Installing freeipa-client (AUR)..."
|
||||
yay -S --noconfirm --needed "${AUR_PKGS[@]}"
|
||||
else
|
||||
echo "[!] yay not found — skipping AUR packages (freeipa-client)."
|
||||
echo " Install yay, then run: yay -S --needed freeipa-client"
|
||||
fi
|
||||
|
||||
# Enable sssd (without starting — host is not enrolled yet)
|
||||
systemctl enable sssd.service 2>/dev/null || true
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLIENT_ENROLL="$SCRIPT_DIR/../../FreeipaAnsible/freeipa-client.sh"
|
||||
|
||||
echo ""
|
||||
echo "[✓] FreeIPA client packages installed."
|
||||
echo ""
|
||||
echo " To enroll this host, run one of:"
|
||||
echo " ipa-client-install --domain=<domain> --server=<server> --principal=admin"
|
||||
if [[ -f "$CLIENT_ENROLL" ]]; then
|
||||
echo " $CLIENT_ENROLL --interactive"
|
||||
echo " $CLIENT_ENROLL --answerfile /path/to/answerfile.json"
|
||||
fi
|
||||
echo ""
|
||||
echo " After enrollment, enable auto-home-dir creation:"
|
||||
echo " authselect select sssd with-mkhomedir --force"
|
||||
|
|
@ -129,6 +129,7 @@ count_steps() {
|
|||
[[ "$a" == *"podman"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"cockpit"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"ssh-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"freeipa-client"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"freeipa-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"freeipa-image"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
[[ "$a" == *"python"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||||
|
|
@ -220,6 +221,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-client" "FreeIPA Client sssd + ipa-client-install + enrollment" off \
|
||||
"freeipa-server" "FreeIPA Server interactive server setup + client gen" off \
|
||||
"freeipa-image" "FreeIPA Image OCI/LXC/Proxmox LXC builder + Keycloak" off \
|
||||
"python" "Python tools pyright · pipx · pynvim" off \
|
||||
|
|
@ -272,6 +274,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-client"* ]] && SUMMARY+=" ✦ FreeIPA Client\n"
|
||||
[[ "$SELECTED_APPS" == *"freeipa-server"* ]] && SUMMARY+=" ✦ FreeIPA Server\n"
|
||||
[[ "$SELECTED_APPS" == *"freeipa-image"* ]] && SUMMARY+=" ✦ FreeIPA Image Builder\n"
|
||||
[[ "$SELECTED_APPS" == *"python"* ]] && SUMMARY+=" ✦ Python tools\n"
|
||||
|
|
@ -335,6 +338,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-client"* ]] && run_module "FreeIPA Client" "$APPS/freeipa-client.sh"
|
||||
[[ "$SELECTED_APPS" == *"freeipa-server"* ]] && run_module "FreeIPA Server" "$APPS/freeipa-server.sh"
|
||||
[[ "$SELECTED_APPS" == *"freeipa-image"* ]] && run_module "FreeIPA Image" "$APPS/freeipa-image-builder.sh"
|
||||
[[ "$SELECTED_APPS" == *"python"* ]] && run_module "Python Tools" "$MODULES/optional-Modules/python.sh"
|
||||
|
|
|
|||
Loading…
Reference in New Issue