diff --git a/setup/arch-autoinstall.sh b/setup/arch-autoinstall.sh index 8911ad0..6f4a164 100755 --- a/setup/arch-autoinstall.sh +++ b/setup/arch-autoinstall.sh @@ -461,7 +461,7 @@ echo "Detected GPU: ${GPU_INFO:-none}" # GPU_PKGS is intentionally unquoted so it expands to multiple words (or nothing). # shellcheck disable=SC2086 pacstrap -K /mnt base base-devel "$KERNEL" linux-firmware vim bash zsh git less btop fastfetch \ - networkmanager grub cryptsetup libfido2 pam-u2f efibootmgr sudo btrfs-progs lvm2 jq $GPU_PKGS + networkmanager openssh grub cryptsetup libfido2 pam-u2f efibootmgr sudo btrfs-progs lvm2 jq $GPU_PKGS ############################################ # FSTAB @@ -511,9 +511,15 @@ ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime hwclock --systohc echo "$HOSTNAME" > /etc/hostname -# NetworkManager +# NetworkManager + SSH # Enable at boot so the system has networking on first login without manual setup. systemctl enable NetworkManager +# Enable SSH server so the machine is reachable headlessly after install. +systemctl enable sshd +# Allow root login via password for initial automated post-install steps; +# FreeIPA enrollment and ansipa deploy lock this down afterwards. +sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config +echo "root:${USERPASS}" | chpasswd # Populate /etc/skel before user creation so useradd -m copies everything echo "Cloning dotfiles into /etc/skel..." diff --git a/setup/modules/FreeipaAnsible/ansible/ansipa-enforce-policies.sh b/setup/modules/FreeipaAnsible/ansible/ansipa-enforce-policies.sh index 8944288..77ca638 100755 --- a/setup/modules/FreeipaAnsible/ansible/ansipa-enforce-policies.sh +++ b/setup/modules/FreeipaAnsible/ansible/ansipa-enforce-policies.sh @@ -23,12 +23,25 @@ # the drop-in on the next run. # # User-group naming conventions (per-user policies — follow the user across devices): +# usr_admin Members get full sudo on every enrolled host via a sudoers +# drop-in (/etc/sudoers.d/ansipa-usr-admin). Applied while the +# FreeIPA user group exists; drop-in is removed when the group is +# deleted. Uses SSSD so group membership resolves without local +# accounts. # usr_block-binary- Prevent members of this FreeIPA user group from running # on any enrolled host. Use __ in place of . to support Flatpak # app IDs (e.g. usr_block-binary-org__gimp__Gimp blocks the # Flatpak org.gimp.Gimp). Enforced via a PATH-priority wrapper # that checks group membership at runtime via SSSD/id(1). # Removing the user group from FreeIPA reverts the wrapper. +# usr_prt_ Auto-add to CUPS on any enrolled host where a member +# is logged in. The printer URI is stored in the IPA group +# description (e.g. ipps://host/printers/name). Access is +# per-user only (allow:); each new member who logs in +# gets added to the allow list. When the IPA group is deleted +# or all members leave, the printer is removed from CUPS on that +# host. A profile.d snippet + NOPASSWD sudo rule trigger the +# enforcer at login for immediate setup. # usr_scan-notify Members receive scan alert notifications on every enrolled device # they log into. The fetch-alerts timer is installed fleet-wide # when the group exists; the notification daemon starts on login @@ -72,6 +85,9 @@ WANT_TIMESHIFT_BACKUP=false WANT_SECURITY_SCAN=false WANT_SCAN_NOTIFY=false WANT_NO_LOCAL_USERS=false +WANT_USR_ADMIN=false +ACTIVE_PRT_GROUPS=() +ACTIVE_PRT_PRINTERS=() if [[ -n "$RAW_GROUPS" ]]; then while IFS=',' read -ra GRP_ARRAY; do @@ -107,6 +123,26 @@ while IFS= read -r _grp; do done <<< "$_BLOCK_LIST" unset _BLOCK_LIST _grp _raw +# ── Fetch user-group-based admin policy from FreeIPA ───────────────────────── +# usr_admin is a FreeIPA *user* group. When it exists, a sudoers drop-in grants +# full sudo to the group on every enrolled host (resolved via SSSD/NSS). +if ipa group-show usr_admin >/dev/null 2>&1; then + WANT_USR_ADMIN=true +fi + +# ── Fetch user-group-based printer policies from FreeIPA ───────────────────── +# usr_prt_ groups grant CUPS access to for members. +_PRT_LIST=$(ipa group-find --pkey-only 2>/dev/null \ + | awk '/Group name:/ {print $NF}' \ + | grep "^usr_prt_" | sort -u || true) + +while IFS= read -r _pgrp; do + [[ -z "$_pgrp" ]] && continue + ACTIVE_PRT_GROUPS+=("$_pgrp") + ACTIVE_PRT_PRINTERS+=("${_pgrp#usr_prt_}") +done <<< "$_PRT_LIST" +unset _PRT_LIST _pgrp + # ── Fetch user-group-based scan-notify policy from FreeIPA ──────────────────── # usr_scan-notify is a FreeIPA *user* group — membership follows the user to # every enrolled host. Install the fetch-alerts timer on any device where the @@ -119,7 +155,9 @@ log "Device policies — daemon-enable: ${ACTIVE_DAEMON_ENABLE[*]:-none}" \ "| daemon-disable: ${ACTIVE_DAEMON_DISABLE[*]:-none}" \ "| timeshift-backup: $WANT_TIMESHIFT_BACKUP | security-scan: $WANT_SECURITY_SCAN" \ "| no-local-users: $WANT_NO_LOCAL_USERS | local-sudo: ${ACTIVE_LOCAL_SUDO_USERS[*]:-none}" -log "User policies — block-binary: ${ACTIVE_BLOCK_BINARIES[*]:-none}" \ +log "User policies — admin: $WANT_USR_ADMIN" \ + "| block-binary: ${ACTIVE_BLOCK_BINARIES[*]:-none}" \ + "| printers: ${ACTIVE_PRT_PRINTERS[*]:-none}" \ "| scan-notify: $WANT_SCAN_NOTIFY" # ── Helpers ─────────────────────────────────────────────────────────────────── @@ -543,4 +581,194 @@ else fi fi +# ── usr_admin: global sudo for FreeIPA admin group ─────────────────────────── +# When the FreeIPA user group usr_admin exists, install a sudoers drop-in that +# grants full sudo to that group (resolved via SSSD/NSS) on this host. +# The drop-in is removed when the group is deleted from FreeIPA. + +USR_ADMIN_DROPIN="/etc/sudoers.d/ansipa-usr-admin" +USR_ADMIN_STATE="$STATE_DIR/usr-admin" + +if [[ "$WANT_USR_ADMIN" == true ]]; then + if [[ ! -f "$USR_ADMIN_DROPIN" ]]; then + log "Installing usr_admin sudoers drop-in" + printf '%%usr_admin ALL=(ALL:ALL) ALL\n' > "$USR_ADMIN_DROPIN" + chmod 440 "$USR_ADMIN_DROPIN" + fi + touch "$USR_ADMIN_STATE" +else + if [[ -f "$USR_ADMIN_STATE" ]] || [[ -f "$USR_ADMIN_DROPIN" ]]; then + rm -f "$USR_ADMIN_DROPIN" "$USR_ADMIN_STATE" + log "Removed usr_admin sudoers drop-in (IPA group no longer exists)" + fi +fi + +# ── usr_prt_: per-user printer auto-add via FreeIPA groups ─────────── +# For each usr_prt_ group in FreeIPA: +# - The group description must contain the printer URI +# (e.g. "ipps://printserver.corp.example.com/printers/hp-office") +# - When a logged-in user is a member of the group, the printer is added to +# CUPS on this host and restricted to that user (allow:). +# - Multiple users in the group can each gain access on the same host. +# - When a user leaves the group their allow entry is revoked; when no users +# remain the printer is removed from CUPS on this host entirely. +# The profile.d snippet /etc/profile.d/ansipa-printers.sh triggers this enforcer +# at login so printers appear immediately rather than waiting for the 30-min tick. +# +# To register a printer in FreeIPA: +# ipa group-mod usr_prt_hp-office \ +# --desc='ipps://printserver.corp.example.com/printers/hp-office' + +PRT_STATE_DIR="$STATE_DIR/printer-users" +PRT_DEFS_DIR="$STATE_DIR/printer-defs" +PRT_PROFILED="/etc/profile.d/ansipa-printers.sh" +PRT_SUDO_DROPIN="/etc/sudoers.d/ansipa-printers-trigger" +mkdir -p "$PRT_STATE_DIR" "$PRT_DEFS_DIR" + +# Read/write per-printer authorized-user state files (.users) +_prt_read_users() { + local sf="$PRT_STATE_DIR/${1}.users" + [[ -f "$sf" ]] && grep -v '^$' "$sf" | sort -u || true +} +_prt_write_users() { + local printer="$1"; shift + if [[ $# -gt 0 ]]; then + printf '%s\n' "$@" | sort -u > "$PRT_STATE_DIR/${printer}.users" + else + > "$PRT_STATE_DIR/${printer}.users" + fi +} +# Apply the full allow list from state (or remove printer if list is empty) +_prt_apply_acl() { + local printer="$1" + local -a users + mapfile -t users < <(_prt_read_users "$printer") + if [[ ${#users[@]} -gt 0 ]]; then + local allow_list + allow_list=$(IFS=','; echo "${users[*]}") + lpadmin -p "$printer" -u "allow:${allow_list}" 2>/dev/null \ + || warn "lpadmin ACL update failed for printer $printer" + else + lpadmin -x "$printer" 2>/dev/null \ + && log "Removed printer $printer (no authorized users remaining)" \ + || warn "lpadmin -x failed for printer $printer" + rm -f "$PRT_STATE_DIR/${printer}.users" + fi +} +_prt_is_active() { + local needle="$1" + for _ap in "${ACTIVE_PRT_PRINTERS[@]+"${ACTIVE_PRT_PRINTERS[@]}"}"; do + [[ "$_ap" == "$needle" ]] && return 0 + done + return 1 +} + +if command -v lpadmin &>/dev/null; then + # ── Process each active usr_prt_* group ────────────────────────────────── + for _i in "${!ACTIVE_PRT_PRINTERS[@]}"; do + _PRT="${ACTIVE_PRT_PRINTERS[$_i]}" + _GRP="${ACTIVE_PRT_GROUPS[$_i]}" + + # Fetch URI from IPA group description (must start with a scheme://) + _URI=$(ipa group-show "$_GRP" --all 2>/dev/null \ + | awk -F': ' '/Description:/{print $2; exit}' \ + | grep -E '^[a-z][a-z+.-]+://' || true) + + if [[ -z "$_URI" ]]; then + warn "Group $_GRP has no printer URI in its description — set it with:" + warn " ipa group-mod $_GRP --desc='ipps://printserver/printers/$_PRT'" + continue + fi + echo "$_URI" > "$PRT_DEFS_DIR/${_GRP}.uri" + + mapfile -t _CUR_USERS < <(_prt_read_users "$_PRT") + _UPDATED=false + + # Check logged-in users for new members to grant access to + while IFS= read -r _LUSER; do + [[ -z "$_LUSER" ]] && continue + id -Gn "$_LUSER" 2>/dev/null | tr ' ' '\n' | grep -qxF "$_GRP" || continue + printf '%s\n' "${_CUR_USERS[@]+"${_CUR_USERS[@]}"}" | grep -qxF "$_LUSER" && continue + + # Add the printer to CUPS if not present yet + if ! lpstat -p "$_PRT" &>/dev/null; then + log "Adding printer $_PRT (URI: $_URI)" + lpadmin -p "$_PRT" -v "$_URI" -m everywhere -E 2>/dev/null \ + || lpadmin -p "$_PRT" -v "$_URI" -E 2>/dev/null \ + || { warn "lpadmin failed to add printer $_PRT"; continue; } + fi + + log "Granting printer $_PRT access to $_LUSER" + _CUR_USERS+=("$_LUSER") + _UPDATED=true + done < <(who -u 2>/dev/null | awk '{print $1}' | sort -u) + + # Revoke users who left the group + _VALID_USERS=() + for _U in "${_CUR_USERS[@]+"${_CUR_USERS[@]}"}"; do + if id -Gn "$_U" 2>/dev/null | tr ' ' '\n' | grep -qxF "$_GRP"; then + _VALID_USERS+=("$_U") + else + log "Revoking printer $_PRT access for $_U (no longer in $_GRP)" + _UPDATED=true + fi + done + + if [[ "$_UPDATED" == true ]]; then + _prt_write_users "$_PRT" "${_VALID_USERS[@]+"${_VALID_USERS[@]}"}" + _prt_apply_acl "$_PRT" + fi + done + unset _i _PRT _GRP _URI _LUSER _U _CUR_USERS _VALID_USERS _UPDATED _ap + + # ── Remove printers whose IPA group was deleted ─────────────────────────── + shopt -s nullglob + for _sf in "$PRT_STATE_DIR"/*.users; do + [[ -f "$_sf" ]] || continue + _PRT=$(basename "$_sf" .users) + _prt_is_active "$_PRT" && continue + log "Removing printer $_PRT (IPA group usr_prt_$_PRT deleted)" + lpadmin -x "$_PRT" 2>/dev/null || true + rm -f "$_sf" + done + shopt -u nullglob + unset _sf _PRT + + # ── profile.d login trigger ─────────────────────────────────────────────── + # Installs a profile.d snippet + NOPASSWD sudo rule so that when a user + # logs in, the enforcer runs immediately and adds their printers without + # waiting for the next 30-minute timer tick. + if [[ ${#ACTIVE_PRT_PRINTERS[@]} -gt 0 ]]; then + if [[ ! -f "$PRT_SUDO_DROPIN" ]]; then + log "Installing ansipa-printers sudo trigger rule" + printf 'ALL ALL=(root) NOPASSWD: /usr/bin/systemctl start ansipa-enforce-policies.service\n' \ + > "$PRT_SUDO_DROPIN" + chmod 440 "$PRT_SUDO_DROPIN" + fi + if [[ ! -f "$PRT_PROFILED" ]]; then + log "Installing /etc/profile.d/ansipa-printers.sh" + cat > "$PRT_PROFILED" <<'PROFILED' +# ansipa-printers: trigger policy enforcer at login to auto-add printers. +# Managed by ansipa-enforce-policies — do not edit manually. +sudo -n /usr/bin/systemctl start ansipa-enforce-policies.service 2>/dev/null & +disown 2>/dev/null || true +PROFILED + chmod 644 "$PRT_PROFILED" + fi + else + if [[ -f "$PRT_SUDO_DROPIN" ]]; then + rm -f "$PRT_SUDO_DROPIN" + log "Removed ansipa-printers sudo trigger (no active printer groups)" + fi + if [[ -f "$PRT_PROFILED" ]]; then + rm -f "$PRT_PROFILED" + log "Removed /etc/profile.d/ansipa-printers.sh (no active printer groups)" + fi + fi +else + [[ ${#ACTIVE_PRT_PRINTERS[@]} -gt 0 ]] && \ + warn "lpadmin not found — install cups to enforce printer policies" +fi +unset _ap + log "Policy enforcement complete." diff --git a/setup/modules/FreeipaAnsible/ansible/ansipa-install-packages.sh b/setup/modules/FreeipaAnsible/ansible/ansipa-install-packages.sh index edda048..e6ab3b5 100644 --- a/setup/modules/FreeipaAnsible/ansible/ansipa-install-packages.sh +++ b/setup/modules/FreeipaAnsible/ansible/ansipa-install-packages.sh @@ -2,6 +2,8 @@ set -e PREFIX="dev_pkg_" +STATE_FILE="/var/lib/ansipa-packages/installed.list" +mkdir -p "$(dirname "$STATE_FILE")" # Detect distro if [ -f /etc/os-release ]; then @@ -62,7 +64,7 @@ remove_pkg() { # ----------------------------- # Get FreeIPA install groups # ----------------------------- -IPA_GROUPS=$(ipa group-find --pkey-only | awk '{print $1}' | grep "^$PREFIX" || true) +IPA_GROUPS=$(ipa group-find --pkey-only | awk '/Group name:/{print $NF}' | grep "^$PREFIX" || true) # Extract package names from groups DESIRED_PKGS=() @@ -93,36 +95,40 @@ case "$DISTRO" in esac # ----------------------------- -# Install missing packages +# Install missing packages and record them # ----------------------------- +[[ -f "$STATE_FILE" ]] || touch "$STATE_FILE" + for PKG in "${DESIRED_PKGS[@]}"; do if ! echo "$INSTALLED_PKGS" | grep -qx "$PKG"; then echo "[INFO] Installing package: $PKG" install_pkg "$PKG" fi + # Record that ansipa manages this package + grep -qxF "$PKG" "$STATE_FILE" || echo "$PKG" >> "$STATE_FILE" done # ----------------------------- # Remove packages no longer desired +# Only remove packages previously installed by ansipa (tracked in state file). # ----------------------------- -for PKG in $INSTALLED_PKGS; do - if [[ "$PKG" =~ ^.*$ ]]; then - if [[ "$PKG" == "$PREFIX"* ]]; then - continue - fi - fi - - if echo "$PKG" | grep -q "^"; then +while IFS= read -r PKG; do + [[ -z "$PKG" ]] && continue + # Still desired? Leave it alone. + if printf '%s\n' "${DESIRED_PKGS[@]+"${DESIRED_PKGS[@]}"}" | grep -qxF "$PKG"; then continue fi + echo "[INFO] Removing package no longer required by any IPA group: $PKG" + remove_pkg "$PKG" + # Remove from state file + grep -vxF "$PKG" "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE" +done < "$STATE_FILE" - if [[ " ${DESIRED_PKGS[*]} " != *" $PKG "* ]]; then - # Only remove packages that were installed via ansipa-install - if ipa group-find --pkey-only | grep -q "^$PREFIX$PKG$"; then - continue - fi - - echo "[INFO] Removing package no longer required: $PKG" - remove_pkg "$PKG" - fi -done +# Persist current desired list (union of what's already tracked + newly added) +if [[ ${#DESIRED_PKGS[@]} -gt 0 ]]; then + printf '%s\n' "${DESIRED_PKGS[@]}" | sort -u > "${STATE_FILE}.tmp" + # Merge: keep previously-managed packages that are still desired + sort -u "$STATE_FILE" "${STATE_FILE}.tmp" > "${STATE_FILE}.merged" + comm -12 "${STATE_FILE}.merged" "${STATE_FILE}.tmp" > "$STATE_FILE" + rm -f "${STATE_FILE}.tmp" "${STATE_FILE}.merged" +fi diff --git a/setup/modules/FreeipaAnsible/ansible/deploy-ansipa-policies.yml b/setup/modules/FreeipaAnsible/ansible/deploy-ansipa-policies.yml index 95bfa16..efaf488 100644 --- a/setup/modules/FreeipaAnsible/ansible/deploy-ansipa-policies.yml +++ b/setup/modules/FreeipaAnsible/ansible/deploy-ansipa-policies.yml @@ -11,7 +11,9 @@ # dev_local-sudo- Grant local sudo on this device; reverted when host leaves # # User policies (FreeIPA user groups — follow the user across all enrolled devices): +# usr_admin Grant full sudo on every enrolled host (sudoers drop-in, SSSD-resolved) # usr_block-binary- Block execution of via a PATH-priority wrapper +# usr_prt_ Auto-add printer at login (per-user); URI in IPA group description # usr_scan-notify Fetch alerts from server, notify user every 10 min until acknowledged # # Prerequisites: @@ -34,12 +36,13 @@ tasks: - - name: Install samba-client (required for scan upload and alert fetch) + - name: Install required packages package: name: "{{ item }}" state: present loop: - samba-client + - cups ignore_errors: yes - name: Deploy SMB credentials file diff --git a/setup/modules/FreeipaAnsible/image/Dockerfile b/setup/modules/FreeipaAnsible/image/Dockerfile index 6962f3d..f4e3708 100644 --- a/setup/modules/FreeipaAnsible/image/Dockerfile +++ b/setup/modules/FreeipaAnsible/image/Dockerfile @@ -24,6 +24,8 @@ ENV container=docker \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 +RUN dnf install -y glibc-langpack-en + RUN dnf install -y --setopt=install_weak_deps=False \ freeipa-server \ freeipa-server-dns \ @@ -57,6 +59,7 @@ RUN systemctl mask \ network.service \ NetworkManager.service +COPY docker-env.service /etc/systemd/system/docker-env.service COPY ipa-first-boot.sh /usr/local/sbin/ipa-first-boot.sh COPY ipa-first-boot.service /etc/systemd/system/ipa-first-boot.service COPY ansipa-smb-setup.sh /usr/local/sbin/ansipa-smb-setup.sh @@ -65,6 +68,7 @@ COPY ansipa-check-scans.sh /usr/local/sbin/ansipa-check-scans.sh RUN chmod +x /usr/local/sbin/ipa-first-boot.sh \ && chmod +x /usr/local/sbin/ansipa-smb-setup.sh \ && chmod +x /usr/local/sbin/ansipa-check-scans.sh \ + && systemctl enable docker-env.service \ && systemctl enable ipa-first-boot.service \ && systemctl enable ansipa-smb.service \ && systemctl enable smb.service nmb.service crond.service diff --git a/setup/modules/FreeipaAnsible/image/ansipa-smb.service b/setup/modules/FreeipaAnsible/image/ansipa-smb.service index 8accb43..9192801 100644 --- a/setup/modules/FreeipaAnsible/image/ansipa-smb.service +++ b/setup/modules/FreeipaAnsible/image/ansipa-smb.service @@ -2,14 +2,15 @@ Description=Ansipa Scan Results SMB Share Setup # Run before smb so smb.conf and the Samba user exist when smbd starts. Before=smb.service -After=network.target +After=network.target docker-env.service +Wants=docker-env.service [Service] Type=oneshot RemainAfterExit=yes -# SMB_SCAN_PASSWORD comes from the container environment on first boot. -# On subsequent restarts it is read from /data/samba/ansipa-smb.env by the script. -PassEnvironment=SMB_SCAN_PASSWORD +# Passwords come from /etc/container.env (written by docker-env.service from Docker -e flags). +# On subsequent restarts they are read from /data/samba/ansipa-smb.env by the script. +EnvironmentFile=/etc/container.env ExecStart=/usr/local/sbin/ansipa-smb-setup.sh StandardOutput=journal StandardError=journal diff --git a/setup/modules/FreeipaAnsible/image/docker-compose.yml b/setup/modules/FreeipaAnsible/image/docker-compose.yml index 71b472f..106c6f9 100644 --- a/setup/modules/FreeipaAnsible/image/docker-compose.yml +++ b/setup/modules/FreeipaAnsible/image/docker-compose.yml @@ -12,6 +12,18 @@ # # To scale out (clients enroll against the same IPA server): # docker compose --profile clients up +# +# ── cgroupns NOTE ──────────────────────────────────────────────────────────── +# On hosts using cgroup v2 (WSL2, recent Linux with unified hierarchy) the +# FreeIPA container runs /sbin/init (systemd) and exits immediately with code +# 255 unless the container shares the host cgroup namespace. Docker Compose +# does not yet expose a cgroupns_mode property in its schema, so use the +# provided run.sh wrapper instead of plain `docker compose up` on those hosts: +# +# ./run.sh — starts freeipa only (cgroupns host applied automatically) +# ./run.sh all — starts freeipa + postgres + keycloak +# +# On plain cgroup v1 hosts `docker compose up -d` works without run.sh. volumes: freeipa-data: diff --git a/setup/modules/FreeipaAnsible/image/docker-env.service b/setup/modules/FreeipaAnsible/image/docker-env.service new file mode 100644 index 0000000..a6fe2f3 --- /dev/null +++ b/setup/modules/FreeipaAnsible/image/docker-env.service @@ -0,0 +1,12 @@ +[Unit] +Description=Export Docker container env vars for systemd services +DefaultDependencies=no +Before=basic.target ipa-first-boot.service ansipa-smb.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/bash -c "tr '\\0' '\\n' < /proc/1/environ | grep -E '^(IPA|SMB|LUKS|KEYCLOAK)_' > /etc/container.env; chmod 600 /etc/container.env" + +[Install] +WantedBy=sysinit.target diff --git a/setup/modules/FreeipaAnsible/image/ipa-first-boot.service b/setup/modules/FreeipaAnsible/image/ipa-first-boot.service index eb11d49..91948cd 100644 --- a/setup/modules/FreeipaAnsible/image/ipa-first-boot.service +++ b/setup/modules/FreeipaAnsible/image/ipa-first-boot.service @@ -1,11 +1,12 @@ [Unit] Description=FreeIPA Server First-Boot Configuration -After=network-online.target dirsrv.target -Wants=network-online.target +After=network-online.target dirsrv.target docker-env.service +Wants=network-online.target docker-env.service ConditionPathExists=!/etc/ipa/default.conf [Service] Type=oneshot +EnvironmentFile=/etc/container.env ExecStart=/usr/local/sbin/ipa-first-boot.sh RemainAfterExit=yes StandardOutput=journal+console diff --git a/setup/modules/FreeipaAnsible/image/ipa-first-boot.sh b/setup/modules/FreeipaAnsible/image/ipa-first-boot.sh index 53e7739..9465b93 100755 --- a/setup/modules/FreeipaAnsible/image/ipa-first-boot.sh +++ b/setup/modules/FreeipaAnsible/image/ipa-first-boot.sh @@ -47,6 +47,7 @@ ARGS=( --ip-address="$(hostname -I | awk '{print $1}')" --mkhomedir --unattended + --skip-mem-check ) if [[ "$IPA_SETUP_DNS" == "true" ]]; then @@ -55,8 +56,6 @@ if [[ "$IPA_SETUP_DNS" == "true" ]]; then && ARGS+=(--forwarder="$IPA_DNS_FORWARDER") \ || ARGS+=(--no-forwarders) [[ "$IPA_AUTO_REVERSE" == "true" ]] && ARGS+=(--auto-reverse) || ARGS+=(--no-reverse) -else - ARGS+=(--no-reverse) fi [[ "$IPA_NO_NTP" == "true" ]] && ARGS+=(--no-ntp) diff --git a/setup/modules/FreeipaAnsible/image/run.sh b/setup/modules/FreeipaAnsible/image/run.sh new file mode 100755 index 0000000..e22da6f --- /dev/null +++ b/setup/modules/FreeipaAnsible/image/run.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# run.sh — Start the FreeIPA stack with correct cgroup namespace on any host. +# +# Usage: +# ./run.sh # freeipa only +# ./run.sh all # freeipa + postgres + keycloak +# ./run.sh down # stop and remove containers +# +# Why this exists: +# The FreeIPA container runs systemd (/sbin/init) and requires the host cgroup +# namespace on systems that use cgroup v2 (WSL2, recent Fedora/Ubuntu, etc.). +# Docker Compose's schema does not expose a cgroupns_mode field, so this script +# starts the freeipa container via `docker run --cgroupns host` and defers the +# rest of the stack to `docker compose up`. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +[[ -f .env ]] || { echo "ERROR: .env not found. Copy .env.example and fill in values."; exit 1; } +# shellcheck disable=SC1091 +source .env + +MODE="${1:-}" + +stop_freeipa() { + docker rm -f freeipa 2>/dev/null || true +} + +start_freeipa() { + stop_freeipa + + # Ensure the compose-managed network exists before docker run uses it + docker compose up --no-start freeipa 2>/dev/null || true + NETWORK=$(docker network ls --filter name=ipa-net --format '{{.Name}}' | grep ipa-net | head -1) + if [[ -z "$NETWORK" ]]; then + docker network create --subnet=172.30.0.0/24 ipa-net + NETWORK=ipa-net + fi + + # Ensure the data volume exists + docker volume create freeipa-data 2>/dev/null || true + + docker run -d \ + --name freeipa \ + --hostname "${IPA_HOSTNAME:-ipa.example.com}" \ + --privileged \ + --cgroupns host \ + --tmpfs /run \ + --tmpfs /tmp \ + -v /sys/fs/cgroup:/sys/fs/cgroup:rw \ + -v freeipa-data:/data \ + --network "$NETWORK" \ + --ip 172.30.0.10 \ + -e IPA_DOMAIN="${IPA_DOMAIN:?}" \ + -e IPA_REALM="${IPA_REALM:-}" \ + -e IPA_ADMIN_PASSWORD="${IPA_ADMIN_PASSWORD:?}" \ + -e IPA_DM_PASSWORD="${IPA_DM_PASSWORD:?}" \ + -e IPA_SETUP_DNS="${IPA_SETUP_DNS:-false}" \ + -e IPA_DNS_FORWARDER="${IPA_DNS_FORWARDER:-}" \ + -e IPA_SETUP_KRA="${IPA_SETUP_KRA:-false}" \ + -e SMB_SCAN_PASSWORD="${SMB_SCAN_PASSWORD:?}" \ + -e LUKS_KEY_UPLOAD_PASSWORD="${LUKS_KEY_UPLOAD_PASSWORD:?}" \ + -p 389:389 \ + -p 636:636 \ + -p 88:88 \ + -p 88:88/udp \ + -p 464:464 \ + -p 464:464/udp \ + -p 443:443 \ + -p 445:445 \ + -p 139:139 \ + -p 137:137/udp \ + -p 138:138/udp \ + freeipa-server:local + + echo "FreeIPA container started. Watch first-boot progress:" + echo " docker exec freeipa journalctl -f -u ipa-first-boot.service" + echo " (first-boot takes ~10 min)" +} + +case "$MODE" in + down) + stop_freeipa + docker compose down + echo "Stack stopped." + ;; + all) + start_freeipa + docker compose up -d postgres keycloak + echo "Full stack started (freeipa + postgres + keycloak)." + ;; + "") + start_freeipa + ;; + *) + echo "Usage: $0 [all|down]" + exit 1 + ;; +esac